|
42 | 42 | class="flex items-center gap-2 bg-transparent border-[1.5px] border-fg text-fg px-4 py-[0.45rem] font-sans text-[0.8125rem] font-medium tracking-widest uppercase cursor-pointer transition-colors duration-200 hover:bg-fg hover:text-bg" |
43 | 43 | > |
44 | 44 | <span aria-hidden="true" class="text-[0.9rem] leading-none">◐</span> |
45 | | - <span id="theme-label">Dark mode</span> |
| 45 | + <span id="theme-label">System mode</span> |
46 | 46 | </button> |
47 | 47 | </div> |
48 | 48 | </nav> |
49 | 49 |
|
50 | 50 | <script> |
| 51 | + const MODES = ['system', 'light', 'dark'] as const; |
| 52 | + type Mode = (typeof MODES)[number]; |
| 53 | + |
51 | 54 | const toggle = document.getElementById('theme-toggle'); |
52 | 55 | const label = document.getElementById('theme-label'); |
53 | 56 | const html = document.documentElement; |
54 | 57 |
|
55 | | - function applyTheme(theme: string) { |
56 | | - html.setAttribute('data-theme', theme); |
57 | | - if (label) label.textContent = theme === 'dark' ? 'Light mode' : 'Dark mode'; |
| 58 | + const modeLabels: Record<Mode, string> = { |
| 59 | + system: 'System mode', |
| 60 | + light: 'Light mode', |
| 61 | + dark: 'Dark mode', |
| 62 | + }; |
| 63 | + |
| 64 | + function getStoredMode(): Mode { |
| 65 | + const saved = localStorage.getItem('theme'); |
| 66 | + if (saved === 'light' || saved === 'dark') return saved; |
| 67 | + return 'system'; |
| 68 | + } |
| 69 | + |
| 70 | + function applyMode(mode: Mode) { |
| 71 | + if (mode === 'system') { |
| 72 | + localStorage.removeItem('theme'); |
| 73 | + const sys = window.matchMedia('(prefers-color-scheme: dark)').matches |
| 74 | + ? 'dark' |
| 75 | + : 'light'; |
| 76 | + html.setAttribute('data-theme', sys); |
| 77 | + } else { |
| 78 | + localStorage.setItem('theme', mode); |
| 79 | + html.setAttribute('data-theme', mode); |
| 80 | + } |
| 81 | + if (label) label.textContent = modeLabels[mode]; |
58 | 82 | } |
59 | 83 |
|
60 | | - const current = html.getAttribute('data-theme') || 'light'; |
61 | | - if (label) label.textContent = current === 'dark' ? 'Light mode' : 'Dark mode'; |
| 84 | + let currentMode: Mode = getStoredMode(); |
| 85 | + // Set label on init; theme is already applied by the no-flash inline script |
| 86 | + if (label) label.textContent = modeLabels[currentMode]; |
62 | 87 |
|
63 | 88 | toggle?.addEventListener('click', () => { |
64 | | - const next = html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'; |
65 | | - applyTheme(next); |
66 | | - localStorage.setItem('theme', next); |
| 89 | + const idx = MODES.indexOf(currentMode); |
| 90 | + currentMode = MODES[(idx + 1) % MODES.length]; |
| 91 | + applyMode(currentMode); |
67 | 92 | }); |
68 | 93 | </script> |
0 commit comments