6.7 KiB
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
-
Palette swap —
frontend/assets/styles.css:roottokens (all pairs computed, ≥4.5:1):token dark value computed pair --bg#0a0e17ink on bg 16.2:1 --surface#121a2eink on surface 14.5:1 --ink#e8ebf4— --ink-soft#9aa4bdink-soft on surface 6.9:1 --line#26304adecorative --brand#6d78f2dark ink --bgon brand 5.2:1--brand-soft#232b52brand-ink on brand-soft 6.9:1 --brand-ink#a5b4fcbrand-ink on surface 8.7:1 --accent-bg#2b2110accent-ink on accent-bg 9.5:1 --accent-ink#fbbf24— --accent-line#f59e0bunchanged (8.9:1 on bg) --err-bg/--err-ink#2d1318/#fca5a59.1:1 --err-line#ef44444.6:1 on err-bg (UI boundary) --ok-bg/--ok-ink#10241b/#6ee7a810.6:1 Button text is
--bg(dark) on--brand— never white on brand (3.7:1, fails). Busy button: keep the#a5b4fcbackground (thetests/unit/test_frontend_feedback.pyassertion 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. -
Emoji purge — replace every emoji in chrome with inline SVG (
aria-hiddenkept, ~16–20px,currentColorwhere 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 adata:URI;.brand-mark🧠 → same mark; ⚠️ banner icon → SVG triangle; 👋 empty state → SVG glyph.frontend/sources.html: favicon,.brand-mark, 📂 empty state → SVG marks.
-
Tech details — mono wordmark with letter-spacing; stat values mono; radii
10px/6px; 1px--lineborders on cards/bubbles/table; 2px gradient hairline (brand→cyan, low alpha) under the sticky header. -
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: twolinear-gradients (1px lines,--lineat ~35% alpha),background-size: 44px 44px, masked with a radial fade (visible center-top, fading to the edges), animatedbackground-position0 0 → 44px 44px, 60s linear infinite (seamless loop — the delta equals one cell).body::after— two large soft radial glows: indigorgba(109,120,242,0.14)top-left, cyanrgba(34,211,238,0.10)bottom-right; 14s ease-in-out infinite alternate breathing (opacity/scale). Nofilter: blur(perf).- Both:
position: fixed; inset: 0; pointer-events: none; z-index: -1. Keep glow alpha low — subtle, never competing with text.
-
Reduced motion —
@media (prefers-reduced-motion: reduce):body::before, body::after { animation: none }(static grid + glows remain). Existing typing/spinner reduced-motion handling stays. -
Test updates (behavior unchanged):
tests/e2e/test_honest_deflection.py(~L104): deflection bubblebackgroundColorassertionrgb(255, 247, 232)→rgb(43, 33, 16); the border assertionrgb(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: 🧠 🧑 👋 📂 ⚠️).
-
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.htmlsince 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% onapp/. uv run ruff check . && uv run pyrightgreen.
Playwright Execution Phase
Run ONLY this story's suite:
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
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"