type Theme = 'system' | 'light' | 'dark' const STORAGE_KEY = 'theme' const getSavedTheme = (): Theme => { const raw = localStorage.getItem(STORAGE_KEY) if (raw === 'light' || raw === 'dark') return raw return 'system' } const getEffectiveTheme = (theme: Theme): 'light' | 'dark' => { if (theme !== 'system') return theme return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' } const applyTheme = (theme: Theme): void => { const html = document.documentElement if (theme === 'system') { html.removeAttribute('data-theme') } else { html.setAttribute('data-theme', theme) } localStorage.setItem(STORAGE_KEY, theme) updateToggleButton(theme) } const cycleTheme = (): void => { const current = getSavedTheme() const next: Theme = current === 'system' ? 'light' : current === 'light' ? 'dark' : 'system' applyTheme(next) } const updateToggleButton = (theme: Theme): void => { const btn = document.getElementById('theme-toggle') if (!btn) return const label = btn.querySelector('[data-theme-label]') as HTMLElement | null if (label) { label.textContent = theme } const svg = btn.querySelector('svg') if (!svg) return if (theme === 'light') { svg.innerHTML = '' svg.setAttribute('stroke', 'currentColor') svg.setAttribute('fill', 'none') } else if (theme === 'dark') { svg.innerHTML = '' svg.setAttribute('stroke', 'currentColor') svg.setAttribute('fill', 'none') } else { svg.innerHTML = '' svg.setAttribute('stroke', 'currentColor') svg.setAttribute('fill', 'none') } } const initTheme = (): void => { const saved = getSavedTheme() applyTheme(saved) const btn = document.getElementById('theme-toggle') if (btn) { btn.addEventListener('click', cycleTheme) } window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { if (getSavedTheme() === 'system') { applyTheme('system') } }) } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initTheme) } else { initTheme() }