web-player: P2 — tema: fuente única de verdad y sin parpadeo en claro

Elimina la duplicación de los ~18 tokens dinámicos por hue entre
theme.css y buildThemeTokens(): theme.css ya solo define neutrales
fijos, superficies, layout y z-index; los valores por hue/modo salen
únicamente de tokens.ts, inyectados por SSR como <style> inline en
+layout.svelte (helper tokensToCss) y reaplicados en cada cambio de
hue. app.html fija data-theme antes del primer paint leyendo
localStorage, eliminando el flash oscuro en modo claro guardado.

Los dos fondos con style= inline pasan a @utility app-backdrop/
app-vignette; el padding-bottom del main pasa a @utility app-main
activada por --mf-dock-h vía [data-has-track].

Nota: <style>{expr}</style> no interpola en Svelte (se compila como
CSS con scope al componente) — el bloque SSR se inyecta con
{@html `<style>${css}</style>`} ya que el contenido es 100% generado
(sin input de usuario).

Verificado en el navegador: --mf-accent/--mf-bg resuelven correctamente
en SSR, data-theme='light' se aplica antes del primer render sin flash.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-20 22:26:37 +02:00
co-authored by Claude Sonnet 5
parent 13f4f3457a
commit d447495933
6 changed files with 60 additions and 83 deletions
+10 -16
View File
@@ -16,7 +16,7 @@
import { liked } from '$lib/player/liked.svelte';
import { createPlaylistModal } from '$lib/playlists.svelte';
import { ACCENT_HUE } from '$lib/theme/color';
import { buildThemeTokens, applyThemeTokens } from '$lib/theme/tokens';
import { buildThemeTokens, applyThemeTokens, tokensToCss } from '$lib/theme/tokens';
import { themeMode } from '$lib/theme/mode.svelte';
import { trackNavigation } from '$lib/navigation.svelte';
import { page } from '$app/state';
@@ -32,6 +32,8 @@
const isAuthPage = $derived(page.url.pathname === '/login');
const hasTrack = $derived(player.currentId !== null);
const initialThemeCss = `:root{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'dark'))}}:root[data-theme='light']{${tokensToCss(buildThemeTokens(ACCENT_HUE, 'light'))}}`;
let hadTrack = false;
$effect(() => {
if (hasTrack && !hadTrack) queuePanel.show();
@@ -83,7 +85,10 @@
<svelte:window onkeydown={onWindowKeydown} />
<svelte:head><link rel="icon" href={favicon} /></svelte:head>
<svelte:head>
<link rel="icon" href={favicon} />
{@html `<style>${initialThemeCss}</style>`}
</svelte:head>
{#if isAuthPage || (!data.user && !data.allowAnonymousListening)}
<div class="min-h-screen bg-bg text-fg antialiased">
@@ -91,14 +96,8 @@
</div>
{:else}
<div class="relative flex min-h-screen text-fg antialiased">
<div
class="pointer-events-none fixed inset-0 -z-10 bg-bg"
style="background-image:var(--mf-ambient)"
></div>
<div
class="pointer-events-none fixed inset-0 -z-10"
style="background:radial-gradient(130% 85% at 50% 118%, color-mix(in oklch, var(--mf-scrim) 80%, transparent), transparent 62%)"
></div>
<div class="app-backdrop"></div>
<div class="app-vignette"></div>
<Sidebar user={data.user} playlists={data.sidebarPlaylists ?? []} />
@@ -106,12 +105,7 @@
<div class="flex min-w-0 flex-1 flex-col">
<MobileHeader user={data.user} accountUrl={data.accountUrl} />
<TopBar user={data.user} accountUrl={data.accountUrl} />
<main
class="flex-1 transition-[padding]"
style="padding-bottom:calc(var(--mf-nav-h) + var(--mf-safe-b) + {hasTrack
? 'var(--mf-player-h)'
: '0px'})"
>
<main class="flex-1 app-main" data-has-track={hasTrack ? '' : undefined}>
{#key page.url.pathname}
<div class="animate-fade">{@render children()}</div>
{/key}
+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;