/* ═══════════════════════════════════════════════════════════════════════════ SoulStream on Android — foundations. The palette is the desktop palette, unchanged (global.css / the mockup's tokens.css): same greys, same accent, same surface ladder. What is new is spatial — radii open up, type goes up a step, every tappable thing is 48dp — and the material: the page ground is the artwork in play, blurred. Text is authored in px; the Android WebView applies the system font scale to CSS px on its own, so there is no sp arithmetic here. ═══════════════════════════════════════════════════════════════════════════ */ /* the desktop's face, from the same files */ @font-face { font-family: "Nunito"; font-style: normal; font-weight: 400; font-display: swap; src: url("/assets/fonts/nunito-400.woff2") format("woff2"); } @font-face { font-family: "Nunito"; font-style: normal; font-weight: 600; font-display: swap; src: url("/assets/fonts/nunito-600.woff2") format("woff2"); } @font-face { font-family: "Nunito"; font-style: normal; font-weight: 700; font-display: swap; src: url("/assets/fonts/nunito-700.woff2") format("woff2"); } /* Typed registration so the per-artwork accent can TRANSITION. The desktop crossfades its accent on a rAF loop (state.svelte.js); on the phone the compositor does it — see the transition on html below. */ @property --accent { syntax: ""; inherits: true; initial-value: #1f9ad6; } @property --accent-hover { syntax: ""; inherits: true; initial-value: #41b7ee; } :root { --bg-base: #121212; --bg-elevated: #181818; --bg-highlight: #242424; --bg-press: #2a2a2a; --text-primary: #ffffff; --text-secondary: #b3b3b3; --text-muted: #7a7a7a; --accent: #1f9ad6; --accent-hover: #41b7ee; --accent-soft: color-mix(in srgb, var(--accent) 15%, transparent); --border: #2a2a2a; /* the surface ladder: the same three alphas the desktop's glass mode uses, because the ground under a surface is the ambience here */ --surface: rgba(255, 255, 255, 0.03); --hover: rgba(255, 255, 255, 0.08); --press: rgba(255, 255, 255, 0.11); --hairline: rgba(255, 255, 255, 0.1); --ring: inset 0 0 0 1px rgba(255, 255, 255, 0.06); --well: rgba(0, 0, 0, 0.18); --r-art: 10px; --r-card: 14px; --r-sheet: 26px; --r-pill: 999px; --pad: 18px; --gap: 10px; --nav-h: 60px; --mini-h: 70px; /* a 62dp row over a 16dp lane strip */ --bar-h: 52px; /* the glass bar */ --tap: 48px; /* Android's own insets: the status bar above, the gesture handle below. */ --inset-top: env(safe-area-inset-top, 0px); --inset-bottom: env(safe-area-inset-bottom, 0px); /* landscape moves the cutout and the gesture pill to the SIDES, and viewport-fit=cover puts our content under both */ --inset-left: env(safe-area-inset-left, 0px); --inset-right: env(safe-area-inset-right, 0px); --top-h: calc(var(--bar-h) + var(--inset-top)); /* ── motion: real springs, sampled ──────────────────────────────────── Material 3 Expressive specifies motion as springs; CSS takes a sampled curve through linear(). These are the mockup's generated tokens (research/mobile-mockup/tools/springs.mjs): the spatial ones overshoot because real objects do, the effects ones never do. */ --sp-spatial-fast: linear(0, 0.0512, 0.176, 0.3375, 0.5079, 0.6679, 0.8057, 0.9155, 0.9963, 1.0502, 1.081, 1.0937, 1.0931, 1.0838, 1.0696, 1.0535, 1.0378, 1.0237, 1.0121, 1.0033, 0.9971, 0.9933, 0.9915, 0.991, 0.9916, 0.9928, 0.9943, 0.9958, 0.9972, 0.9984, 0.9994, 1, 1.0005, 1.0008, 1); --spd-spatial-fast: 413ms; --sp-spatial-default: linear(0, 0.0329, 0.1132, 0.2191, 0.335, 0.4503, 0.558, 0.6543, 0.7374, 0.8069, 0.8634, 0.9081, 0.9425, 0.9681, 0.9865, 0.9992, 1.0074, 1.0122, 1.0145, 1.0152, 1.0146, 1.0134, 1.0118, 1.01, 1.0083, 1.0067, 1.0052, 1.0039, 1.0029, 1.002, 1.0014, 1.0009, 1.0005, 1.0002, 1); --spd-spatial-default: 482ms; --sp-spatial-slow: linear(0, 0.0309, 0.1068, 0.2078, 0.3194, 0.4314, 0.5372, 0.6329, 0.7165, 0.7874, 0.8459, 0.893, 0.9298, 0.9579, 0.9787, 0.9935, 1.0035, 1.0098, 1.0134, 1.0149, 1.0151, 1.0143, 1.013, 1.0114, 1.0097, 1.008, 1.0065, 1.0051, 1.0039, 1.0029, 1.002, 1.0014, 1.0009, 1.0005, 1); --spd-spatial-slow: 641ms; --sp-effects-fast: linear(0, 0.0527, 0.1672, 0.3005, 0.4304, 0.5464, 0.6448, 0.7255, 0.7901, 0.8409, 0.8803, 0.9105, 0.9334, 0.9507, 0.9637, 0.9733, 0.9804, 0.9857, 0.9896, 0.9924, 0.9945, 0.996, 0.9971, 0.9979, 0.9985, 0.9989, 0.9992, 0.9994, 0.9996, 0.9997, 0.9998, 0.9999, 0.9999, 0.9999, 1); --spd-effects-fast: 202ms; --sp-effects-default: linear(0, 0.0436, 0.1412, 0.2593, 0.3786, 0.4892, 0.5866, 0.6696, 0.7385, 0.7947, 0.84, 0.876, 0.9044, 0.9266, 0.9439, 0.9572, 0.9675, 0.9754, 0.9814, 0.986, 0.9894, 0.9921, 0.9941, 0.9956, 0.9967, 0.9975, 0.9982, 0.9986, 0.999, 0.9992, 0.9994, 0.9996, 0.9997, 0.9998, 1); --spd-effects-default: 280ms; --sp-effects-slow: linear(0, 0.0388, 0.1273, 0.2365, 0.3492, 0.4558, 0.5517, 0.635, 0.7056, 0.7644, 0.8128, 0.8521, 0.8837, 0.9089, 0.929, 0.9448, 0.9572, 0.9669, 0.9745, 0.9804, 0.9849, 0.9885, 0.9912, 0.9933, 0.9949, 0.9961, 0.997, 0.9977, 0.9983, 0.9987, 0.999, 0.9993, 0.9994, 0.9996, 1); --spd-effects-slow: 371ms; --m-fast: var(--spd-effects-fast) var(--sp-effects-fast); --m-base: var(--spd-spatial-fast) var(--sp-spatial-fast); --m-sheet: var(--spd-spatial-default) var(--sp-spatial-default); --m-spring: var(--spd-spatial-fast) var(--sp-spatial-fast); --art-grow: 1.035; --ambience: 0.55; /* how much of the artwork the page ground admits */ --grain: 0.05; color-scheme: dark; } /* The registered property only makes --accent animatable; this is the crossfade. Slow on purpose — it is the app changing colour, and it should read as a change rather than a flicker. */ html { transition: --accent var(--spd-effects-slow) linear, --accent-hover var(--spd-effects-slow) linear; } /* Appearance → Accent from artwork OFF: the brand blue everywhere */ html[data-accent="brand"] { --accent: #1f9ad6 !important; --accent-hover: #41b7ee !important; } /* Appearance → Ambience OFF: the honest floor, the desktop's measured greys */ html[data-material="solid"] { --ambience: 0; --surface: var(--bg-elevated); --hover: var(--bg-highlight); --press: var(--bg-press); --hairline: rgba(255, 255, 255, 0.07); } * { box-sizing: border-box; } html, body { margin: 0; padding: 0; height: 100%; background: var(--bg-base); color: var(--text-primary); font-family: "Nunito", -apple-system, "Segoe UI", Roboto, sans-serif; font-size: 15px; -webkit-font-smoothing: antialiased; -webkit-tap-highlight-color: transparent; overscroll-behavior: none; user-select: none; -webkit-user-select: none; } #mobile { height: 100%; } /* :where() so the reset carries zero specificity and never beats a component rule (the mockup lost every accent-filled control to this once) */ :where(button) { font: inherit; color: inherit; background: none; border: 0; padding: 0; margin: 0; cursor: default; touch-action: manipulation; } :where(input) { font: inherit; color: inherit; } img { display: block; }