# Dark Mode

Extends `02-design-tokens.md` (admin) and `07-storefront-design-system.md` (storefront) to support Light, Dark, and System appearance — an Apple HIG-inspired layered palette, not a color inversion. Applies to both `admin/` and `storefront/`, each keeping its own accent hue.

## Architecture

Two tiers, both already implemented in each app's `index.css`:

1. **Primitive scale** — the existing `color.neutral.*`/`color.accent.*`/semantic tokens from `02-design-tokens.md` are **theme-relative**: their values are redefined wholesale under `:root[data-theme="dark"]`, on top of the same `:root` values used in light mode. Because every component already consumes these tokens exclusively (verified by a full hardcoded-color audit across both apps), this re-themes the entire existing UI with zero per-component changes.
2. **Semantic alias layer** — `--surface-app`, `--surface-card`, `--surface-secondary`, `--text-primary`, `--text-secondary`, `--text-muted`, `--border-default`, `--border-divider`, `--focus-ring`, `--state-hover`, `--state-selected`, `--overlay` are defined as aliases onto the primitive scale. New or touched components should prefer these by role; existing components keep working unchanged via the primitives directly.

## Fixed tokens (never flip with the theme)

A handful of components use a scale token for a role that must **not** invert:

- `--color-static-white` — text/icons on a solid saturated fill (primary/danger buttons, badges, filled timeline steps) where the fill itself stays roughly the same hue in both themes, so white-on-color is correct either way.
- `--color-ink` / `--color-ink-contrast` — surfaces that are deliberately dark regardless of app theme (tooltips, image-zoom overlays on product photos, a "selected" variant pill, a sold-out badge, storefront's dark promo/footer surface). Defined as fixed literals, not aliased to `--color-neutral-900` (which does flip).

Before adding a new dark-mode override to an existing color scale, check whether every usage of that token shares one role — a token used as both "surface" and "fixed-dark-chip" (or both "background" and "on-color text") needs one side migrated to a fixed token first, or the reinterpretation breaks the other usage. This audit already happened for both apps; see the exemption comments left in each affected file (`Tooltip.module.css`, `ProductDetail.module.css`, `Step5Images.module.css`, `VariantSelector.module.css`, `Badge.module.css`, `Checkout.module.css`, button/icon-button/notification-badge files) for the exact reasoning per site.

## Deliberately unthemed exceptions

A few hardcoded colors are intentional and were left as-is (each has a one-line comment at its site):

- `Step7Seo.module.css` — mocks a real Google search result, which is always a white card regardless of the admin's theme.
- `layoutPreview/*` (storefront) — a wireframe-diagram tool loaded in an iframe inside the admin's layout editor, never seen by a shopper.
- `StarDisplay.module.css` / `StarRatingInput.module.css` — rating gold is a fixed, universally-recognized convention (kept gold in dark mode by virtually every real product).
- Any gradient/text sitting directly on top of arbitrary merchant-uploaded photography (hero banners, gallery overlays) — these adapt to the photo, not the theme.

## Palette values

Layered dark surfaces per app (admin indigo / storefront purple accent, same neutral ramp shape): darkest tier is the page background (`neutral-50`, `#1c1c1e` admin / `#1c1a1e` storefront) — never pure black — with `neutral-0` and `neutral-100` progressively lighter for cards and further-elevated surfaces (popovers, dropdowns), preserving the same "card reads brighter than the page" relationship light mode already has. Accent and semantic (success/warning/danger) hues are brightened for dark backgrounds rather than reused as-is, since the light-mode values are tuned for contrast against white. A new `info` semantic pair (`--color-info-500`/`-100`, a blue) was added to both apps' light and dark palettes — the spec's semantic list included "Info" and neither app had one.

## Theme switching

`ThemeContext`/`ThemeProvider` (`admin/src/theme/`, `storefront/src/theme/`) holds `"light" | "dark" | "system"`, persisted to `localStorage` only (per-device — matches the existing `DisplayCurrencyContext` precedent; no server-side/per-account sync). `"system"` subscribes to `window.matchMedia('(prefers-color-scheme: dark)')`'s `change` event so an OS-level theme change applies live, no reload. A small inline script in each app's `index.html` sets `data-theme` on `<html>` before first paint to avoid a flash of the wrong theme. A global `transition` on `background-color`/`border-color`/`color` (guarded by `prefers-reduced-motion`) makes the re-paint a fade rather than a flash.
