export type ThemeChoice = 'light' | 'dark'; const STORAGE_KEY = 'theme'; let theme = $state('light'); function storage(): Storage | null { return typeof localStorage === 'undefined' ? null : localStorage; } function systemTheme(): ThemeChoice { if (typeof window === 'undefined' || !window.matchMedia) return 'light'; return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; } function apply(): void { if (typeof document === 'undefined') return; document.documentElement.dataset.theme = theme; } export function getTheme(): ThemeChoice { return theme; } export function setTheme(next: ThemeChoice): void { theme = next; storage()?.setItem(STORAGE_KEY, next); apply(); } /** * Догоняет состояние после гидрации: фактический атрибут уже выставлен * инлайн-скриптом в app.html; здесь он читается и синхронизируется с runes, * чтобы кнопка в шапке отражала реальную тему. */ export function initTheme(): void { if (typeof window === 'undefined') return; const attr = typeof document !== 'undefined' ? document.documentElement.dataset.theme : undefined; theme = attr === 'dark' || attr === 'light' ? attr : systemTheme(); apply(); }