// Theme store — light is the PilotVault default. The user picks a *mode* // (light | dark | system); `theme` is the resolved value (light | dark) that // actually drives [data-theme] on . The pre-paint script in index.html // resolves the same way to avoid a flash before hydration. import { ref } from 'vue' const KEY = 'pv_theme' // Concrete ground colors (--bg-app per theme), set inline on so the // viewport canvas repaints reliably when the theme flips (Chromium quirk). const GROUND = { light: '#EEF0F3', dark: '#0B1730' } const media = typeof window !== 'undefined' && window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null function systemTheme() { return media && media.matches ? 'dark' : 'light' } function initialMode() { try { return localStorage.getItem(KEY) || 'light' } catch { return 'light' } } function resolve(mode) { return mode === 'system' ? systemTheme() : mode } function paint(resolved) { const html = document.documentElement html.setAttribute('data-theme', resolved) html.style.backgroundColor = GROUND[resolved] || GROUND.light } // The user's chosen mode, and the concrete resolved theme. export const themeMode = ref(initialMode()) export const theme = ref(resolve(themeMode.value)) export function setThemeMode(mode) { themeMode.value = mode const resolved = resolve(mode) theme.value = resolved paint(resolved) try { localStorage.setItem(KEY, mode) } catch { /* ignore */ } } // Back-compat alias used by older call sites. export const applyTheme = setThemeMode // Simple light/dark flip for the header toggle. From "system" it commits to the // opposite of whatever is currently showing. export function toggleTheme() { setThemeMode(theme.value === 'dark' ? 'light' : 'dark') } // Follow the OS while in "system" mode. if (media) { media.addEventListener('change', () => { if (themeMode.value === 'system') { const resolved = systemTheme() theme.value = resolved paint(resolved) } }) }