Skip to content

Commit 452e943

Browse files
pfeerickclaude
andcommitted
feat(site): follow OS theme preference with explicit override control
Theme now has three states (system/light/dark) persisted only when the user explicitly picks light or dark; the default 'system' state tracks prefers-color-scheme live via a matchMedia listener. Replaced the single sun/moon toggle with a 3-icon segmented control so users can return to OS-following at any time by clicking the monitor icon. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 5d8cb6f commit 452e943

1 file changed

Lines changed: 51 additions & 13 deletions

File tree

‎tools/generate_site.py‎

Lines changed: 51 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -159,22 +159,41 @@ def _safe_json(obj) -> str:
159159
>
160160
</div>
161161
162-
<button
163-
@click="darkMode = !darkMode; localStorage.setItem('darkMode', darkMode)"
164-
class="p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg transition"
165-
:aria-label="darkMode ? 'Light mode' : 'Dark mode'"
166-
>
167-
<template x-if="darkMode">
168-
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
162+
<div class="flex items-center gap-1 bg-gray-200 dark:bg-gray-700 rounded-lg p-1" role="group" aria-label="Theme">
163+
<button
164+
@click="setTheme('light')"
165+
:class="theme === 'light' ? 'bg-white dark:bg-gray-900 text-red-600 shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'"
166+
class="p-1.5 rounded-md transition"
167+
aria-label="Light theme"
168+
:aria-pressed="theme === 'light'"
169+
>
170+
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
169171
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z"/>
170172
</svg>
171-
</template>
172-
<template x-if="!darkMode">
173-
<svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
173+
</button>
174+
<button
175+
@click="setTheme('system')"
176+
:class="theme === 'system' ? 'bg-white dark:bg-gray-900 text-red-600 shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'"
177+
class="p-1.5 rounded-md transition"
178+
aria-label="Use system theme"
179+
:aria-pressed="theme === 'system'"
180+
>
181+
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
182+
<path stroke-linecap="round" stroke-linejoin="round" d="M9 17.25v1.007a3 3 0 0 1-.879 2.122L7.5 21h9l-.621-.621A3 3 0 0 1 15 18.257V17.25m6-12V15a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 15V5.25m18 0A2.25 2.25 0 0 0 18.75 3H5.25A2.25 2.25 0 0 0 3 5.25m18 0V12a2.25 2.25 0 0 1-2.25 2.25H5.25A2.25 2.25 0 0 1 3 12V5.25"/>
183+
</svg>
184+
</button>
185+
<button
186+
@click="setTheme('dark')"
187+
:class="theme === 'dark' ? 'bg-white dark:bg-gray-900 text-red-600 shadow-sm' : 'text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100'"
188+
class="p-1.5 rounded-md transition"
189+
aria-label="Dark theme"
190+
:aria-pressed="theme === 'dark'"
191+
>
192+
<svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
174193
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z"/>
175194
</svg>
176-
</template>
177-
</button>
195+
</button>
196+
</div>
178197
</div>
179198
</header>
180199
@@ -428,7 +447,21 @@ def _safe_json(obj) -> str:
428447
search: '',
429448
activeCategory: null,
430449
activeTags: [],
431-
darkMode: localStorage.getItem('darkMode') === 'true',
450+
theme: localStorage.getItem('theme') || 'system',
451+
osDark: window.matchMedia('(prefers-color-scheme: dark)').matches,
452+
453+
get darkMode() {
454+
return this.theme === 'system' ? this.osDark : this.theme === 'dark';
455+
},
456+
457+
setTheme(t) {
458+
this.theme = t;
459+
if (t === 'system') {
460+
localStorage.removeItem('theme');
461+
} else {
462+
localStorage.setItem('theme', t);
463+
}
464+
},
432465
433466
get filtered() {
434467
return this.scripts.filter(script => {
@@ -470,6 +503,11 @@ def _safe_json(obj) -> str:
470503
},
471504
472505
init() {
506+
// Follow OS theme changes live while no explicit choice overrides it
507+
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
508+
this.osDark = e.matches;
509+
});
510+
473511
// Listen for filter-tag events from tag chips
474512
window.addEventListener('filter-tag', (e) => {
475513
this.toggleTag(e.detail.tag);

0 commit comments

Comments
 (0)