Theming done
Some checks failed
ci / site (push) Has been cancelled
publish-image / publish (push) Has been cancelled

This commit is contained in:
2026-02-10 20:10:06 -05:00
parent 6cb4d55241
commit 70710239c7
19 changed files with 1260 additions and 42 deletions

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-02-10

View 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.

View 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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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