diff --git a/web/src/app.css b/web/src/app.css index e342abf..f7eafb3 100644 --- a/web/src/app.css +++ b/web/src/app.css @@ -37,6 +37,7 @@ /* ---- Light Theme (default) ---- */ :root, [data-theme="light"] { + color-scheme: light; --bg-primary: #fdfbff; --bg-secondary: #f9f4fb; --bg-tertiary: #f2e9f4; @@ -74,10 +75,19 @@ --section-alt-bg: var(--bg-secondary); --grid-color: rgba(148, 163, 184, 0.15); + + --hl-comment: #64748b; + --hl-arg: #15803d; + --hl-operator:#6b7280; + --hl-value: #a16207; + --hl-keyword: #7c3aed; + --hl-string: #c2410c; + --hl-builtin: #0369a1; } /* ---- Dark Theme ---- */ [data-theme="dark"] { + color-scheme: dark; --bg-primary: #0f1117; --bg-secondary: #151821; --bg-tertiary: #1a1d28; @@ -115,6 +125,14 @@ --section-alt-bg: var(--bg-secondary); --grid-color: rgba(31, 41, 55, 0.25); + + --hl-comment: #94a3b8; + --hl-arg: #22c55e; + --hl-operator:#e5e7eb; + --hl-value: #eab308; + --hl-keyword: #a855f7; + --hl-string: #f97316; + --hl-builtin: #0ea5e9; } /* ---- Reset & Base ---- */ @@ -137,7 +155,7 @@ body { background-color: var(--bg-primary); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - transition: background-color var(--transition-base), color var(--transition-base); + transition: color var(--transition-base); position: relative; overflow-x: hidden; max-width: 100vw; @@ -242,6 +260,19 @@ p { color: white; } +/* ---- Docs syntax highlighting ---- */ +/* Colors are CSS variables so Safari picks them up via inheritance, not + selector re-evaluation (which WebKit defers for complex layouts). */ +.hl-comment { color: var(--hl-comment); } +.hl-command { color: var(--accent); } +.hl-arg, +.hl-flag { color: var(--hl-arg); } +.hl-operator { color: var(--hl-operator); } +.hl-value { color: var(--hl-value); } +.hl-keyword { color: var(--hl-keyword); } +.hl-string { color: var(--hl-string); } +.hl-builtin { color: var(--hl-builtin); } + /* Scrollbar */ ::-webkit-scrollbar { width: 6px; diff --git a/web/src/lib/theme.svelte.ts b/web/src/lib/theme.svelte.ts index db5ee31..2e6f7d2 100644 --- a/web/src/lib/theme.svelte.ts +++ b/web/src/lib/theme.svelte.ts @@ -2,6 +2,24 @@ import { browser } from '$app/environment'; type Theme = 'light' | 'dark'; +// Must match --bg-primary in app.css for each theme. +// Safari doesn't repaint body.backgroundColor when CSS vars change on html, +// so we set it directly in JS with the known values. +const BG: Record = { + light: '#fdfbff', + dark: '#0f1117', +}; + +function safeLocalStorage(action: 'get' | 'set', key: string, value?: string): string | null { + try { + if (action === 'get') return localStorage.getItem(key); + localStorage.setItem(key, value!); + return null; + } catch { + return null; + } +} + function getSystemTheme(): Theme { if (!browser) return 'light'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; @@ -9,7 +27,7 @@ function getSystemTheme(): Theme { function getStoredPreference(): 'light' | 'dark' | 'system' { if (!browser) return 'system'; - return (localStorage.getItem('hashprep-theme') as 'light' | 'dark' | 'system') || 'system'; + return (safeLocalStorage('get', 'hashprep-theme') as 'light' | 'dark' | 'system') || 'system'; } let preference = $state<'light' | 'dark' | 'system'>(getStoredPreference()); @@ -18,12 +36,18 @@ let resolved = $derived(preference === 'system' ? getSystemTheme() : pref function apply(t: Theme) { if (!browser) return; document.documentElement.setAttribute('data-theme', t); + document.documentElement.style.colorScheme = t; + // Safari bug: body's background-color using var(--bg-primary) is not repainted + // when the CSS variable changes via a [data-theme] attribute selector on . + // The CSS transition on background-color also fights inline style updates in + // Safari, so we removed that transition and set the value directly here. + document.body.style.backgroundColor = BG[t]; } function setPreference(pref: 'light' | 'dark' | 'system') { preference = pref; if (browser) { - localStorage.setItem('hashprep-theme', pref); + safeLocalStorage('set', 'hashprep-theme', pref); } apply(pref === 'system' ? getSystemTheme() : pref); }