web-player: single source of truth for theme, no light-mode flash

Remove the duplication of the ~18 per-hue dynamic tokens between
theme.css and buildThemeTokens(): theme.css now only defines fixed
neutrals, surfaces, layout and z-index; hue/mode-dependent values
come solely from tokens.ts, injected via SSR as an inline <style> in
+layout.svelte (tokensToCss helper) and reapplied on every hue
change. app.html sets data-theme before first paint by reading
localStorage, removing the dark flash when light mode was saved.

The two inline-style backgrounds become @utility
app-backdrop/app-vignette; the main's bottom padding becomes
@utility app-main, driven by --mf-dock-h via [data-has-track].

Note: <style>{expr}</style> doesn't interpolate in Svelte (it
compiles as component-scoped CSS) — the SSR block is injected with
{@html `<style>${css}</style>`} since the content is fully generated
(no user input).

Verified in the browser: --mf-accent/--mf-bg resolve correctly during
SSR, data-theme='light' applies before the first render with no
flash.
This commit is contained in:
2026-09-20 22:26:37 +02:00
parent ea9bf07126
commit 7b1b81c45a
6 changed files with 60 additions and 83 deletions
+29
View File
@@ -168,6 +168,35 @@
grid-template-columns: repeat(auto-fill, minmax(16.5rem, 1fr));
}
@utility app-backdrop {
pointer-events: none;
position: fixed;
inset: 0;
z-index: -10;
background-color: var(--mf-bg);
background-image: var(--mf-ambient);
}
@utility app-vignette {
pointer-events: none;
position: fixed;
inset: 0;
z-index: -10;
background: radial-gradient(
130% 85% at 50% 118%,
color-mix(in oklch, var(--mf-scrim) 80%, transparent),
transparent 62%
);
}
@utility app-main {
--mf-dock-h: 0px;
padding-bottom: calc(var(--mf-nav-h) + var(--mf-safe-b) + var(--mf-dock-h));
transition: padding 0.3s;
&[data-has-track] {
--mf-dock-h: var(--mf-player-h);
}
}
@layer base {
svg.lucide {
stroke-width: 1.8;