Theming done
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-02-10
|
||||
106
openspec/changes/archive/2026-02-11-dch-theming/design.md
Normal file
106
openspec/changes/archive/2026-02-11-dch-theming/design.md
Normal file
@@ -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 `<html>`
|
||||
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 `<head>` 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 `<button>` to open/close (for touch) OR a `<fieldset role="radiogroup">` with three radio-like buttons.
|
||||
- Ensure it is reachable via keyboard and has clear `aria-label`s.
|
||||
|
||||
Placement decisions:
|
||||
- Use a CSS variable `--theme-notch-top` to position it.
|
||||
- A small inline script computes this based on `.site-header` height and, if a `.subnav` exists near the top, positions below it.
|
||||
|
||||
### 7. High Contrast theme semantics
|
||||
The high-contrast theme will be a dedicated palette (not only increased brightness) with:
|
||||
- strong background/foreground contrast
|
||||
- high visibility focus ring
|
||||
- more assertive stroke borders
|
||||
|
||||
Additionally, handle OS forced-colors mode:
|
||||
- In `@media (forced-colors: active)`, prefer system colors and avoid gradients that reduce clarity.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[Notch overlaps content]** -> compute top offset from header/subnav; provide safe-area padding; add responsive rules for small viewports.
|
||||
- **[Theme transitions reduce readability]** -> scope transitions to a short window and limit properties; disable via `prefers-reduced-motion`.
|
||||
- **[High contrast breaks brand feel]** -> keep layout/typography unchanged and only adjust palette and borders.
|
||||
- **[CSP constraints]** -> keep head script minimal and consider moving to an external script if CSP hardening becomes a priority.
|
||||
27
openspec/changes/archive/2026-02-11-dch-theming/proposal.md
Normal file
27
openspec/changes/archive/2026-02-11-dch-theming/proposal.md
Normal file
@@ -0,0 +1,27 @@
|
||||
## Why
|
||||
|
||||
Add modern theming controls (dark/light/high-contrast) to improve accessibility and give the site a polished, customizable "WOW" experience.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add three user-selectable themes: **Dark**, **Light**, and **High Contrast**.
|
||||
- Add a floating theme switcher "notch" on the right edge of the screen:
|
||||
- positioned just below the primary nav bar
|
||||
- leaves enough vertical space for secondary navigation
|
||||
- hover state includes a tasteful animation
|
||||
- Theme switching uses a smooth transition (not an abrupt flash).
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
- `site-theming`: Theme tokens and a theme application mechanism that can switch between Dark/Light/High Contrast across the site.
|
||||
- `theme-switcher-notch`: A floating, accessible UI control (right-side notch) that lets the user switch themes.
|
||||
|
||||
### Modified Capabilities
|
||||
- `wcag-responsive-ui`: Extend accessibility baseline to include theme switching requirements (keyboard, focus, reduced motion) and ensure High Contrast theme is supported.
|
||||
|
||||
## Impact
|
||||
|
||||
- Affected UI/CSS: global design tokens (CSS variables), background layers, card/CTA styling, focus styling.
|
||||
- Affected layout: a new floating notch component that must not overlap navigation across breakpoints.
|
||||
- Affected UX/accessibility: keyboard navigation and motion preferences during theme transitions.
|
||||
@@ -0,0 +1,55 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Site themes
|
||||
The site MUST support three themes:
|
||||
- `dark`
|
||||
- `light`
|
||||
- `high-contrast`
|
||||
|
||||
Themes MUST be applied by setting a `data-theme` attribute on the root document element (`<html>`).
|
||||
|
||||
#### Scenario: Dark theme active
|
||||
- **WHEN** `data-theme="dark"` is set on `<html>`
|
||||
- **THEN** the site's background, text, and component styling reflect the dark palette
|
||||
|
||||
#### Scenario: Light theme active
|
||||
- **WHEN** `data-theme="light"` is set on `<html>`
|
||||
- **THEN** the site's background, text, and component styling reflect the light palette
|
||||
|
||||
#### Scenario: High contrast theme active
|
||||
- **WHEN** `data-theme="high-contrast"` is set on `<html>`
|
||||
- **THEN** the site uses a high-contrast palette with a clearly visible focus ring and high-contrast borders
|
||||
|
||||
### Requirement: Theme persistence
|
||||
The site MUST persist the user's theme selection so it is retained across page loads and navigations.
|
||||
|
||||
Persistence MUST be stored locally in the browser (e.g., localStorage).
|
||||
|
||||
#### Scenario: Theme persists across reload
|
||||
- **WHEN** the user selects `light` theme and reloads the page
|
||||
- **THEN** the `light` theme remains active
|
||||
|
||||
### Requirement: Default theme selection
|
||||
If the user has not explicitly selected a theme, the site MUST choose a default theme using environment signals.
|
||||
|
||||
Default selection order:
|
||||
1) If forced colors / high-contrast mode is active, default to `high-contrast`
|
||||
2) Else if the system prefers light color scheme, default to `light`
|
||||
3) Else default to `dark`
|
||||
|
||||
#### Scenario: No stored preference uses system settings
|
||||
- **WHEN** the user has no stored theme preference
|
||||
- **THEN** the site selects a default theme based on forced-colors and prefers-color-scheme
|
||||
|
||||
### Requirement: Theme switching transition
|
||||
Theme changes initiated by the user MUST transition smoothly.
|
||||
|
||||
The transition MUST be disabled or substantially reduced when `prefers-reduced-motion: reduce` is set.
|
||||
|
||||
#### Scenario: Smooth transition on switch
|
||||
- **WHEN** the user switches from `dark` to `light` theme
|
||||
- **THEN** theme-affecting properties transition smoothly instead of abruptly switching
|
||||
|
||||
#### Scenario: Reduced motion disables theme animation
|
||||
- **WHEN** `prefers-reduced-motion: reduce` is set and the user switches theme
|
||||
- **THEN** the theme change occurs without noticeable animation
|
||||
@@ -0,0 +1,42 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Floating theme switcher notch
|
||||
The site MUST provide a floating theme switcher control anchored to the right side of the viewport.
|
||||
|
||||
The control MUST be positioned below the primary navigation bar and MUST leave sufficient vertical space for secondary navigation.
|
||||
|
||||
#### Scenario: Notch positioned below header
|
||||
- **WHEN** the page loads
|
||||
- **THEN** the theme switcher notch is visible on the right side and does not overlap the sticky header or sub-navigation
|
||||
|
||||
### Requirement: Notch interaction and animation
|
||||
The notch MUST provide a hover affordance (a small, tasteful animation) that indicates it is interactive.
|
||||
|
||||
The hover animation MUST be disabled or substantially reduced under `prefers-reduced-motion: reduce`.
|
||||
|
||||
#### Scenario: Hover animation present
|
||||
- **WHEN** a pointer user hovers the notch
|
||||
- **THEN** the notch animates in a way that suggests it can be expanded or interacted with
|
||||
|
||||
#### Scenario: Reduced motion disables hover animation
|
||||
- **WHEN** `prefers-reduced-motion: reduce` is set
|
||||
- **THEN** hovering the notch does not trigger a noticeable animation
|
||||
|
||||
### Requirement: Theme selection UI
|
||||
The notch MUST expose the three theme options (`dark`, `light`, `high-contrast`) and allow the user to select one.
|
||||
|
||||
The control MUST be keyboard accessible:
|
||||
- it MUST be reachable via `Tab`
|
||||
- it MUST have a visible focus indicator
|
||||
- selection MUST be possible using keyboard input
|
||||
|
||||
#### Scenario: Keyboard selects theme
|
||||
- **WHEN** a keyboard user focuses the notch and selects `high-contrast`
|
||||
- **THEN** the site updates to the `high-contrast` theme and the selection is persisted
|
||||
|
||||
### Requirement: Accessibility labels
|
||||
The notch and theme options MUST have accessible labels.
|
||||
|
||||
#### Scenario: Screen reader announces theme switcher
|
||||
- **WHEN** a screen reader user focuses the theme switcher control
|
||||
- **THEN** it announces an appropriate label (e.g., "Theme" or "Theme switcher") and the currently selected theme
|
||||
@@ -0,0 +1,30 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Theme switching accessibility
|
||||
Theme switching controls MUST be accessible and usable with keyboard and assistive technology.
|
||||
|
||||
The theme switcher control MUST:
|
||||
- be reachable via keyboard navigation
|
||||
- provide a visible focus indication
|
||||
- expose an accessible name/label
|
||||
- allow selecting any supported theme without requiring a pointer
|
||||
|
||||
#### Scenario: Theme switcher is keyboard reachable
|
||||
- **WHEN** a keyboard user tabs through the page
|
||||
- **THEN** the theme switcher notch receives focus and shows a visible focus indicator
|
||||
|
||||
#### Scenario: Theme switcher is labeled
|
||||
- **WHEN** a screen reader user focuses the theme switcher
|
||||
- **THEN** it announces a meaningful label and the current theme state
|
||||
|
||||
### Requirement: High contrast theme meets WCAG intent
|
||||
The `high-contrast` theme MUST provide materially higher contrast than the default theme.
|
||||
|
||||
The theme MUST keep text readable and interactive affordances obvious, including:
|
||||
- strong foreground/background contrast
|
||||
- clearly visible focus ring
|
||||
- strong borders on interactive elements
|
||||
|
||||
#### Scenario: High contrast theme improves readability
|
||||
- **WHEN** the user enables `high-contrast` theme
|
||||
- **THEN** primary text and secondary UI labels remain clearly readable and interactive elements are visually distinct
|
||||
26
openspec/changes/archive/2026-02-11-dch-theming/tasks.md
Normal file
26
openspec/changes/archive/2026-02-11-dch-theming/tasks.md
Normal file
@@ -0,0 +1,26 @@
|
||||
## 1. Theme Tokens And Application
|
||||
|
||||
- [x] 1.1 Add `data-theme` overrides in `site/public/styles/global.css` for `light` and `high-contrast` (keep `:root` as default dark)
|
||||
- [x] 1.2 Add `color-scheme` rules per theme so native form controls match (dark/light)
|
||||
- [x] 1.3 Add theme initialization script in `site/src/layouts/BaseLayout.astro` to set theme before first paint (stored preference → forced colors/high contrast → prefers-color-scheme)
|
||||
- [x] 1.4 Persist theme selection to localStorage and update `data-theme` on change
|
||||
- [x] 1.5 Implement scoped smooth transitions for user-initiated theme changes (no global transition on initial load)
|
||||
|
||||
## 2. Theme Switcher Notch UI
|
||||
|
||||
- [x] 2.1 Add markup for a fixed-position right-side notch in `site/src/layouts/BaseLayout.astro`
|
||||
- [x] 2.2 Implement notch positioning below `.site-header` and below `.subnav` when present (compute top offset; handle resize)
|
||||
- [x] 2.3 Add notch hover animation (expand/slide) and ensure it feels intentional
|
||||
- [x] 2.4 Add keyboard and screen reader support (label, focus styles, keyboard selection)
|
||||
- [x] 2.5 Ensure notch does not overlap critical content on mobile (responsive rules; safe-area)
|
||||
|
||||
## 3. High Contrast Theme Verification
|
||||
|
||||
- [x] 3.1 Ensure high-contrast theme has strong fg/bg contrast, obvious focus ring, and strong strokes on interactive elements
|
||||
- [x] 3.2 Add `@media (forced-colors: active)` adjustments to avoid illegible gradients and ensure system colors are respected
|
||||
|
||||
## 4. Verification
|
||||
|
||||
- [x] 4.1 Run `npm run build` and verify output HTML includes the head theme-init script
|
||||
- [x] 4.2 Manual smoke test: switch themes on `/`, `/videos`, `/podcast`, `/blog` and verify persistence across reload
|
||||
- [x] 4.3 Manual a11y checks: keyboard-only interaction, focus visibility, prefers-reduced-motion behavior
|
||||
Reference in New Issue
Block a user