# Task 03 — Example theme (`indigo.css`) + authoring guide + Containerfile line **Phase:** `62_ui_customization` · **Source:** `TODO.md:3` — "…custom color themes…" **Story:** n/a (TODO-derived) ## Objective A working example theme a deployer can point `BOR_THEME` at out of the box — plus the documentation that makes "write my own theme" a 15-minute job, and the one Containerfile line that ships the directory in the container image (A7). ## Work 1. `frontend/assets/themes/indigo.css` (NEW) — overrides ONLY the 8 identity variables in a single `:root` block; the semantic families (accent/ok/err) are deliberately UNTOUCHED (they encode states — deflection amber, success green, error red — and are already AA in the built-in theme; a theme that keeps them stays honest): ```css /* Phase 62 example theme — dark indigo/slate. Overrides the :root identity palette from styles.css; every text/background pair meets WCAG 2.1 AA (>= 4.5:1): ink on bg 15.8:1 · ink on surface 14.6:1 · ink-soft on surface 8.2:1 dark bg ink on brand 6.4:1 · brand-ink on surface 11.9:1. Semantic families (accent/ok/err) inherit the built-in theme. */ :root { --bg: #0a0e1a; --surface: #111726; --ink: #e6e9f0; --ink-soft: #a8b0c8; --line: #232c44; --brand: #818cf8; --brand-soft: #1a1f38; --brand-ink: #c7d2fe; } ``` - Before committing, re-verify the five ratios above (e.g. a 10-line python contrast calc against the built-in pairs' house comment style) — if any pair misses 4.5:1, adjust the value, not the bar (AGENTS.md rule 5). - Spot-check that no component CSS hard-codes a color the theme should own: `grep -n "f43f5e\|#f0e6e6\|#0f0a0a" frontend/assets/styles.css` should match ONLY the `:root` palette block + comments (if a literal lives elsewhere, note it in the task report — do NOT refactor styles.css in this phase). 2. `frontend/assets/themes/README.md` (NEW) — the authoring guide, house-comment style: - How themes load: `BOR_THEME=` → `/api/config` → `brand.js` inserts `` AFTER `styles.css` (later wins the cascade — that is the whole mechanism). - The variable table: the 8 identity variables (bg/surface/ink/ink-soft/line/brand/brand-soft/brand-ink) with the built-in values as reference, and the note that accent/ok/err are semantic and should stay. - Rules: filename `^[a-z0-9_-]+\.css$` (lowercase, bare filename — the server validator rejects anything else at startup); one `:root` block; every text/background pair ≥4.5:1 (AGENTS.md rule 5); never white-on-brand (the built-in's documented 3.7:1 trap) — dark bg ink on brand, as the built-in does. - Deployment: works in dev immediately (served from the static dir); in the container, rebuild the image (the `cp -r` picks up whatever is in `frontend/assets/themes/` at build time — no Containerfile edit for new files, A7). 3. `Containerfile` — stage-1 `RUN` chain: after the `styles.css` minify line, add ``` && cp -r ./assets/themes /out/assets/themes \ ``` (A7: whole directory, no per-file esbuild — future themes need no Containerfile change). No other stage changes; the runtime stage copies the whole `/out` static tree, so the dir flows through untouched. 4. Dev-server sanity (record in task report): with `BOR_THEME=indigo.css`, `http://localhost:8000/assets/themes/indigo.css` returns 200 from the static dir; with the default env the built-in palette renders (the file existing but `BOR_THEME` empty changes NOTHING — loading is opt-in via the env var, not directory scanning). ## Testing & Quality - No Python logic; `uv run pytest` green; `uv run ruff check . && uv run pyright` clean. - Coverage: **>90%** on `app/` (validate.sh gate). - Add the theme-file unit pins to `tests/unit/test_frontend_brand.py` (or `test_stale_ui_copy.py`'s sibling if the executor prefers a new `tests/unit/test_themes.py`): the file exists, starts with a `:root` block, contains exactly the 8 `--` overrides (no other declarations), and the Containerfile contains the `cp -r ./assets/themes` line. - Browser proof (computed `--brand`, the link tag, visual sanity) lands in task 05's E2E. ## Completion Criteria - [ ] `frontend/assets/themes/indigo.css` + `README.md` exist; all five contrast ratios verified ≥4.5:1 (calc output in the task report). - [ ] `Containerfile` ships `assets/themes/` (the line is in stage 1, after the styles.css minify). - [ ] The `grep` spot-check for hard-coded identity colors is recorded (expect: `:root` block + comments only). - [ ] Unit pins green; full suite green; lint + types clean.