# Phase 08 — Story: Dark Tech Theme **Story:** `.agent/user_stories/dark-tech-theme.md` **Context:** `.agent/PLAN.md` §7 (UI/UX strategy), §10 (testing) ## Goal Re-skin the whole UI to a dark, techy, emoji-free look with a subtly animated pure-CSS background — zero behavior or layout changes, WCAG 2.1 AA re-proven on the new palette. ## Dependencies Phases 01–07 (all complete). Independent of 09/10; phase 10's viewer page inherits this theme, so 08 must land first. ## Locked decisions No anchors changed. Replaces the §7.2 light contrast pairs with the dark palette below (PLAN §7.2 already updated 2026-08-21 with owner permission). No new technology — pure CSS/HTML/inline SVG (A11). ## Implementation steps 1. **Palette swap** — `frontend/assets/styles.css` `:root` tokens (all pairs computed, ≥4.5:1): | token | dark value | computed pair | |---|---|---| | `--bg` | `#0a0e17` | ink on bg 16.2:1 | | `--surface` | `#121a2e` | ink on surface 14.5:1 | | `--ink` | `#e8ebf4` | — | | `--ink-soft` | `#9aa4bd` | ink-soft on surface 6.9:1 | | `--line` | `#26304a` | decorative | | `--brand` | `#6d78f2` | **dark ink `--bg` on brand 5.2:1** | | `--brand-soft` | `#232b52` | brand-ink on brand-soft 6.9:1 | | `--brand-ink` | `#a5b4fc` | brand-ink on surface 8.7:1 | | `--accent-bg` | `#2b2110` | accent-ink on accent-bg 9.5:1 | | `--accent-ink` | `#fbbf24` | — | | `--accent-line` | `#f59e0b` | unchanged (8.9:1 on bg) | | `--err-bg` / `--err-ink` | `#2d1318` / `#fca5a5` | 9.1:1 | | `--err-line` | `#ef4444` | 4.6:1 on err-bg (UI boundary) | | `--ok-bg` / `--ok-ink` | `#10241b` / `#6ee7a8` | 10.6:1 | Button text is `--bg` (dark) on `--brand` — **never white on brand** (3.7:1, fails). Busy button: keep the `#a5b4fc` background (the `tests/unit/test_frontend_feedback.py` assertion greps this token) with a **dark** arc (`--bg`, 9.7:1). Update derived light-mode values: shadows (black-based, lower alpha), selection, typing dots, chip hover. 2. **Emoji purge** — replace every emoji in chrome with inline SVG (`aria-hidden` kept, ~16–20px, `currentColor` where sensible): - `frontend/assets/app.js` (~L116, ~L132): avatars 🧠/🧑 → SVG circuit-node glyph (brain) / minimal silhouette (user) as JS string constants. - `frontend/index.html`: favicon 🧠 data-URI → SVG tech mark (hex + node, brand color on dark, <1 KB), still a `data:` URI; `.brand-mark` 🧠 → same mark; ⚠️ banner icon → SVG triangle; 👋 empty state → SVG glyph. - `frontend/sources.html`: favicon, `.brand-mark`, 📂 empty state → SVG marks. 3. **Tech details** — mono wordmark with letter-spacing; stat values mono; radii `10px`/`6px`; 1px `--line` borders on cards/bubbles/table; 2px gradient hairline (brand→cyan, low alpha) under the sticky header. 4. **Animated background (pure CSS, zero JS)** — working recipe: `html { background: var(--bg) }`, `body { background: transparent; position: relative }` (body must not create a stacking context): - `body::before` — fine grid: two `linear-gradient`s (1px lines, `--line` at ~35% alpha), `background-size: 44px 44px`, masked with a radial fade (visible center-top, fading to the edges), animated `background-position` `0 0 → 44px 44px`, 60s linear infinite (seamless loop — the delta equals one cell). - `body::after` — two large soft radial glows: indigo `rgba(109,120,242,0.14)` top-left, cyan `rgba(34,211,238,0.10)` bottom-right; 14s ease-in-out infinite alternate breathing (opacity/scale). No `filter: blur` (perf). - Both: `position: fixed; inset: 0; pointer-events: none; z-index: -1`. Keep glow alpha low — subtle, never competing with text. 5. **Reduced motion** — `@media (prefers-reduced-motion: reduce)`: `body::before, body::after { animation: none }` (static grid + glows remain). Existing typing/spinner reduced-motion handling stays. 6. **Test updates (behavior unchanged):** - `tests/e2e/test_honest_deflection.py` (~L104): deflection bubble `backgroundColor` assertion `rgb(255, 247, 232)` → `rgb(43, 33, 16)`; the border assertion `rgb(245, 158, 11)` is unchanged. - New integration test `tests/integration/test_api.py::test_ui_chrome_has_no_emoji`: GET `/`, `/sources.html`, `/assets/app.js`, `/assets/styles.css` — assert no characters in the emoji code-point set (U+1F300–U+1FAFF, U+2600–U+27BF, U+2B00–U+2BFF, U+FE0F, U+200D, plus the specific glyphs previously used: 🧠 🧑 👋 📂 ⚠️). 7. **PLAN.md §7.2** — dark contrast table already applied (2026-08-21, owner permission); no further plan edits in this phase. ## UI Verification Manual screenshot pass (1280px + 375px, both pages): grid is faint (barely-there), glows soft, no banding; brand button legible (dark text); deflection bubble distinct from normal answers; avatars crisp at 16px; reduced-motion preview (DevTools emulation) shows the static background. ## Testing & Quality - Integration: no-CDN check still green on both pages (it covers `/sources.html` since Phase 07); new emoji-guard test above. - **Existing E2E regression check:** after the reskin, run the existing story suites in isolation and confirm they stay green — at minimum `test_chat_rag.py`, `test_honest_deflection.py`, `test_responsive_ polish.py` (the contrast helper computes from live styles and must pass on the new palette). - Coverage: `uv run pytest --cov=app --cov-report=term-missing` — **>90%** on `app/`. - `uv run ruff check . && uv run pyright` green. ## Playwright Execution Phase Run ONLY this story's suite: ```bash uv run pytest tests/e2e/test_dark_tech_theme.py -v --no-cov ``` Implements the story mapping: dark bg + computed contrast pairs ≥4.5:1; no emoji in innerText/outerHTML on both pages; `body::before`/`::after` animate; reduced-motion context → `animation-name: none`; on-topic smoke (stream + chip + button recovery) unchanged; all assets local. ## Success criteria - [ ] both pages dark; every text pair ≥4.5:1 (computed in E2E) - [ ] zero emoji in chrome (E2E + new integration guard) - [ ] animated background subtle, pure CSS, reduced-motion honored - [ ] layout metrics + chat behavior unchanged (smoke E2E) - [ ] existing story E2E suites still green in isolation - [ ] unit + integration green, coverage >90%, ruff + pyright green - [ ] committed (force-add `.agent/PLAN.md` + this phase record — rule 8) ## Commit ```bash git add -A git add -f .agent/PLAN.md .agent/phases/todo/08_story_dark_tech_theme.md git commit --no-gpg-sign -m "feat(ui): dark tech theme — emoji-free chrome, subtle animated CSS background, WCAG AA dark palette" ```