Mercurial
comparison mrjunejune/src/index.js @ 256:30c2196d03d4
[site] Integrate Zenbu themes and components
Co-authored-by: Copilot <[email protected]>
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Tue, 04 Aug 2026 16:49:01 -0700 |
| parents | bcc76a156aea |
| children | 60a876c4587a |
comparison
equal
deleted
inserted
replaced
| 255:5ec271d612ae | 256:30c2196d03d4 |
|---|---|
| 1 const THEME_STORAGE_KEY = 'theme-preference'; | 1 const THEME_STORAGE_KEY = 'theme-preference'; |
| 2 // Get stored preference or detect system preference | 2 const THEMES = ['auto', 'paper', 'ink', 'playful']; |
| 3 function getThemePreference() | 3 const THEME_LABELS = { |
| 4 { | 4 auto: 'Auto', |
| 5 const stored = localStorage.getItem(THEME_STORAGE_KEY); | 5 paper: 'Paper', |
| 6 if (stored) { | 6 ink: 'Ink', |
| 7 return stored; | 7 playful: 'Playful', |
| 8 } | 8 }; |
| 9 return 'auto'; | 9 |
| 10 function normalizeTheme(value) { | |
| 11 if (value === 'light') return 'paper'; | |
| 12 if (value === 'dark') return 'ink'; | |
| 13 return THEMES.includes(value) ? value : 'auto'; | |
| 10 } | 14 } |
| 11 | 15 |
| 12 function applyTheme(preference) | 16 function getThemePreference() { |
| 13 { | 17 return normalizeTheme(localStorage.getItem(THEME_STORAGE_KEY)); |
| 14 const root = document.documentElement; | |
| 15 | |
| 16 if (preference === 'light') { | |
| 17 root.classList.remove('dark', 'auto'); | |
| 18 root.classList.add('light-mode'); | |
| 19 } else if (preference === 'dark') { | |
| 20 root.classList.remove('light-mode', 'auto'); | |
| 21 root.classList.add('dark'); | |
| 22 } else { | |
| 23 // Auto - remove manual overrides and let CSS media query handle it | |
| 24 root.classList.remove('light-mode', 'dark'); | |
| 25 root.classList.add('auto'); | |
| 26 } | |
| 27 } | 18 } |
| 28 | 19 |
| 29 (function() { | 20 function updateThemeColor() { |
| 30 const currentPreference = getThemePreference(); | 21 const probe = document.createElement('span'); |
| 31 applyTheme(currentPreference); | 22 probe.style.background = 'var(--zen-color-canvas)'; |
| 32 })(); | 23 document.body.append(probe); |
| 24 const color = getComputedStyle(probe).backgroundColor; | |
| 25 probe.remove(); | |
| 26 document.querySelector('meta[name="theme-color"]') | |
| 27 ?.setAttribute('content', color); | |
| 28 } | |
| 33 | 29 |
| 34 /* Toggle lights auto and dark */ | 30 function updateThemeControl(preference) { |
| 35 const toggle = document.querySelector('#themeToggle'); | 31 const toggle = document.querySelector('#themeToggle'); |
| 36 toggle.addEventListener('click', function() { | 32 const label = document.querySelector('#themeName'); |
| 37 let preference = getThemePreference(); | 33 if (label) label.textContent = THEME_LABELS[preference]; |
| 34 toggle?.setAttribute( | |
| 35 'aria-label', | |
| 36 `Change site theme. Current theme: ${THEME_LABELS[preference]}`, | |
| 37 ); | |
| 38 } | |
| 38 | 39 |
| 39 // Cycle through: auto -> light -> dark -> auto | 40 function applyTheme(value, persist = false) { |
| 40 if (preference === 'auto') { | 41 const preference = normalizeTheme(value); |
| 41 preference = 'light'; | 42 const root = document.documentElement; |
| 42 } else if (preference === 'light') { | 43 root.classList.remove('light-mode', 'dark', 'auto'); |
| 43 preference = 'dark'; | 44 if (preference === 'auto') delete root.dataset.zenTheme; |
| 44 } else { | 45 else root.dataset.zenTheme = preference; |
| 45 preference = 'auto'; | 46 if (persist) localStorage.setItem(THEME_STORAGE_KEY, preference); |
| 46 } | 47 updateThemeControl(preference); |
| 48 requestAnimationFrame(updateThemeColor); | |
| 49 document.dispatchEvent(new CustomEvent('zen-theme-change', { | |
| 50 detail: { theme: preference }, | |
| 51 })); | |
| 52 return preference; | |
| 53 } | |
| 47 | 54 |
| 48 localStorage.setItem(THEME_STORAGE_KEY, preference); | 55 const currentPreference = applyTheme(getThemePreference()); |
| 49 applyTheme(preference); | 56 if (localStorage.getItem(THEME_STORAGE_KEY) !== currentPreference) { |
| 57 localStorage.setItem(THEME_STORAGE_KEY, currentPreference); | |
| 58 } | |
| 59 | |
| 60 document.querySelector('#themeToggle')?.addEventListener('click', () => { | |
| 61 const current = getThemePreference(); | |
| 62 const next = THEMES[(THEMES.indexOf(current) + 1) % THEMES.length]; | |
| 63 applyTheme(next, true); | |
| 50 }); | 64 }); |
| 65 | |
| 66 matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { | |
| 67 if (getThemePreference() === 'auto') updateThemeColor(); | |
| 68 }); | |
| 69 | |
| 70 window.MrJuneJuneTheme = Object.freeze({ | |
| 71 apply: theme => applyTheme(theme, true), | |
| 72 current: getThemePreference, | |
| 73 themes: [...THEMES], | |
| 74 }); |