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()
}