| 1 | // Two independent theme axes: |
| 2 | // • the site theme — the room's lighting (palette + haze), defined in style.css |
| 3 | // as :root[data-site-theme='…'] blocks; |
| 4 | // • the code theme — one of Strudel's CodeMirror themes, for syntax colors. |
| 5 | // They are separate because the code pane deliberately drops its own background |
| 6 | // and sits in the site's haze, so only a code theme's *colors* usually apply. |
| 7 | import { themes as codeThemes, settings as codeThemeSettings } from '@strudel/codemirror'; |
| 8 | |
| 9 | // Each site theme names the code theme whose syntax colors sit best in its light. |
| 10 | export const SITE_THEMES = [ |
| 11 | { id: 'haze', label: 'Purple haze', code: 'aura' }, |
| 12 | { id: 'ember', label: 'Ember', code: 'monokai' }, |
| 13 | { id: 'laser', label: 'Laser', code: 'strudelTheme' }, |
| 14 | { id: 'blackout', label: 'Blackout', code: 'vscodeDark' }, |
| 15 | ]; |
| 16 | |
| 17 | export const CODE_THEMES = Object.keys(codeThemes).sort((a, b) => a.localeCompare(b)); |
| 18 | |
| 19 | // A code theme that expects a light background can't sit in a dark haze. |
| 20 | const isLight = (code) => Boolean(codeThemeSettings[code]?.light); |
| 21 | |
| 22 | const KEY = 'strudel-claude-theme'; |
| 23 | const DEFAULT = { site: 'haze', code: 'aura' }; |
| 24 | |
| 25 | function load() { |
| 26 | try { |
| 27 | const saved = JSON.parse(localStorage.getItem(KEY) || '{}'); |
| 28 | return { |
| 29 | site: SITE_THEMES.some((t) => t.id === saved.site) ? saved.site : DEFAULT.site, |
| 30 | code: CODE_THEMES.includes(saved.code) ? saved.code : DEFAULT.code, |
| 31 | }; |
| 32 | } catch { |
| 33 | return { ...DEFAULT }; |
| 34 | } |
| 35 | } |
| 36 | |
| 37 | let current = load(); |
| 38 | export const currentTheme = () => ({ ...current }); |
| 39 | |
| 40 | // `editor` is the StrudelMirror instance; it owns the CodeMirror theme extension. |
| 41 | function apply(editor) { |
| 42 | document.documentElement.dataset.siteTheme = current.site; |
| 43 | document.body.classList.toggle('code-light', isLight(current.code)); |
| 44 | editor.setTheme(current.code); |
| 45 | try { |
| 46 | localStorage.setItem(KEY, JSON.stringify(current)); |
| 47 | } catch { |
| 48 | /* storage blocked — the theme still applies, it just won't persist */ |
| 49 | } |
| 50 | } |
| 51 | |
| 52 | export function setSiteTheme(editor, id) { |
| 53 | const site = SITE_THEMES.find((t) => t.id === id); |
| 54 | if (!site) return; |
| 55 | // Switching rigs also switches to that rig's paired code theme; picking a code |
| 56 | // theme afterwards overrides it. |
| 57 | current = { site: site.id, code: site.code }; |
| 58 | apply(editor); |
| 59 | } |
| 60 | |
| 61 | export function setCodeTheme(editor, code) { |
| 62 | if (!CODE_THEMES.includes(code)) return; |
| 63 | current = { ...current, code }; |
| 64 | apply(editor); |
| 65 | } |
| 66 | |
| 67 | export function initThemes(editor) { |
| 68 | apply(editor); |
| 69 | } |