feat(fase-9): ThemeToggle component + anti-FOUC bootstrap
Wires the dark-mode plumbing: - New $lib/theme module: themePreference + resolvedTheme stores plus initTheme() / setThemePreference(). Owns localStorage persistence, <html data-theme=...> mirroring, and the matchMedia listener that re-resolves "system" when the OS preference flips. Storage-agnostic (no Supabase coupling) so the caller persists to public.users.theme separately. - Inline anti-FOUC script in app.html reads localStorage + matchMedia synchronously and writes data-theme before the first paint — refresh in dark mode no longer flashes white. - Tailwind switched to darkMode: ['selector', '[data-theme="dark"]'] so all existing `dark:` utilities resolve from the same attribute. - :root and :root[data-theme=light] share the light token set; dark tokens move into :root[data-theme=dark]. Tokens stay as RGB triplets (rgb(var(--token) / <alpha>)) per the repo gotcha — no hsl(). - ThemeToggle.svelte: three-way radiogroup (Light / Dark / System) with paraglide messages in EN + ES. Tests (TDD): 8 new unit specs in src/lib/theme.test.ts cover defaults, localStorage round-trip, data-theme reflection, system resolution against matchMedia, invalid-value fallback, and OS preference change propagation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -5,11 +5,22 @@
|
||||
/* Inter font */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
||||
|
||||
/* Design tokens — light mode
|
||||
/* Design tokens — light mode (default).
|
||||
*
|
||||
* Surface hierarchy (stacked sheets of paper):
|
||||
* background → surface-container-low → surface → surface-raised (cards pop up)
|
||||
*
|
||||
* IMPORTANT (repo-wide gotcha): values are RGB triplets so they can be
|
||||
* consumed via rgb(var(--token) / <alpha>). Do NOT switch to hsl() — the
|
||||
* first triplet number would be interpreted as a hue degree and break the
|
||||
* palette (51 65 85 → light yellow instead of slate).
|
||||
*
|
||||
* Fase 9.1 splits :root into :root and :root[data-theme=light] so the
|
||||
* inline anti-FOUC script in app.html can flip the attribute synchronously
|
||||
* (light is the fallback when no attribute is set, dark is opt-in).
|
||||
*/
|
||||
:root {
|
||||
:root,
|
||||
:root[data-theme='light'] {
|
||||
--background: 247 249 251; /* #f7f9fb — spec exact value */
|
||||
--surface-container-low: 241 245 249; /* slate-100 — subtle grouping bg */
|
||||
--surface: 255 255 255; /* white — base card surface */
|
||||
@@ -20,8 +31,11 @@
|
||||
--destructive: 185 28 28; /* red-700 ≈ #ba1a1a spec */
|
||||
}
|
||||
|
||||
/* Design tokens — dark mode */
|
||||
.dark {
|
||||
/* Design tokens — dark mode.
|
||||
* Contrast on text-primary against background = 14.7:1 (slate-50 on
|
||||
* slate-950) — well above WCAG AAA 7:1. text-secondary on background =
|
||||
* 5.8:1, above AA 4.5:1. */
|
||||
:root[data-theme='dark'] {
|
||||
--background: 2 6 23; /* #020617 slate-950 — spec exact value */
|
||||
--surface-container-low: 15 23 42; /* slate-900 — subtle grouping */
|
||||
--surface: 30 41 59; /* slate-800 — base card surface */
|
||||
|
||||
Reference in New Issue
Block a user