diff --git a/openspec/changes/archive/2026-02-11-dch-theming/.openspec.yaml b/openspec/changes/archive/2026-02-11-dch-theming/.openspec.yaml new file mode 100644 index 0000000..70eb9e0 --- /dev/null +++ b/openspec/changes/archive/2026-02-11-dch-theming/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-02-10 diff --git a/openspec/changes/archive/2026-02-11-dch-theming/design.md b/openspec/changes/archive/2026-02-11-dch-theming/design.md new file mode 100644 index 0000000..99300bc --- /dev/null +++ b/openspec/changes/archive/2026-02-11-dch-theming/design.md @@ -0,0 +1,106 @@ +## Context + +The site currently uses a single dark theme defined via CSS variables in `site/public/styles/global.css` (e.g., `--bg0`, `--bg1`, `--fg`, `--muted`, `--stroke`, `--accent`). There is no existing theme selection mechanism (no `data-theme` attribute, no persisted preference). + +The site shell uses a sticky `.site-header` and a per-page `.subnav` row (when present). A theme switcher notch must be fixed-positioned such that it does not overlap the header and leaves enough space for the subnav region. + +## Goals / Non-Goals + +**Goals:** +- Provide three themes: `dark`, `light`, `high-contrast`. +- Allow switching themes via a floating notch on the right side of the screen positioned below the primary nav bar and not overlapping the subnav area. +- Make switching feel premium: + - hover animation on the notch + - smooth theme transitions (without an abrupt flash) +- Ensure accessibility: + - keyboard operable + - visible focus + - respects `prefers-reduced-motion` + - high contrast theme is meaningfully higher contrast, not just a color swap +- Persist the user's selection across page loads. + +**Non-Goals:** +- Rebuild the entire visual system or rewrite all CSS to a design-token framework. +- PWA theming (manifest/theme-color) beyond what is required to implement UI themes. +- Adding user accounts or server-side persistence of theme preference. + +## Decisions + +### 1. Theme selection mechanism: `data-theme` on `` +Use `document.documentElement.dataset.theme = "dark" | "light" | "high-contrast"`. + +Rationale: +- Works cleanly with CSS variables. +- Scopes theme styles across the entire page without specificity fights. + +Alternatives considered: +- Adding theme classes to `body` (works, but html-scoped variables are simpler for form control theming). + +### 2. Token strategy: override existing CSS variables per theme +Keep the existing variable names and provide theme-specific overrides: +- `:root` remains the default (dark) +- `html[data-theme="light"] { ... }` +- `html[data-theme="high-contrast"] { ... }` + +Rationale: +- Minimizes churn in existing CSS. +- Enables incremental migration of any remaining hard-coded colors to tokens. + +### 3. Default theme resolution order +On first load, resolve the active theme in this order: +1) stored user preference (`localStorage.theme`) +2) forced colors / high-contrast OS mode (if detected) -> `high-contrast` +3) system color scheme -> `light` if `prefers-color-scheme: light`, else `dark` + +Rationale: +- User choice wins. +- If the user is in a forced/high-contrast environment, defaulting to high-contrast aligns with accessibility intent. + +### 4. Prevent flash of wrong theme with a tiny head script +Insert a small inline script in the document `` that sets `data-theme` before first paint. + +Rationale: +- Avoids "flash" where the page renders in dark before switching to light/high-contrast. + +Trade-off: +- Inline scripts can constrain future CSP hardening; keep script small and self-contained. + +### 5. Smooth transitions without animating on every page load +Use a transient attribute/class (e.g., `data-theme-transition="on"`) only during user-initiated theme changes. + +Implementation shape: +- When switching: set `data-theme-transition="on"`, update `data-theme`, then remove after ~250ms. +- CSS applies transitions for color/background/border/shadow only when the attribute is present. + +Rationale: +- Avoids "everything animates" feeling during initial load. +- Avoids subtle jank on navigation. + +### 6. Notch UI: fixed-position, expands on hover/focus +Implement the switcher as a fixed-position control at the right edge: +- Default collapsed: a small vertical tab. +- On `:hover` and `:focus-within`: expands into a small panel exposing the three theme options. + +Accessibility decisions: +- Use a real `