# S02 — Dark Theme + Cyan Accents + Mobile Responsive Fix — Research

**Date:** 2026-03-30

## Summary

The Chrysopedia frontend uses a single CSS file (`frontend/src/App.css`, ~780 lines) with zero CSS custom properties — every color is hardcoded. There are ~40 unique color values scattered across 29 uses of `#fff`, 24 of `#6b7280`, 17 of `#374151`, etc. The current accent is indigo (`#6366f1`). There's no theming infrastructure at all.

The approach is straightforward: introduce CSS custom properties (`:root` variables) for all semantic colors, replace every hardcoded color reference, and set the dark theme values directly (no toggle needed — the spec says dark theme, not dark/light toggle). The cyan accent replaces the current indigo. Mobile horizontal scroll is caused by missing `overflow-x: hidden` on body/html and a few `min-width` / `white-space: nowrap` declarations that don't account for narrow viewports.

## Recommendation

**CSS custom properties on `:root`, dark-first, single pass through App.css.** No CSS framework needed. The codebase uses plain CSS with BEM-ish class names — this is a theming pass, not an architecture change. The work divides cleanly into: (1) define the custom property palette, (2) replace all hardcoded colors with `var(--*)` references, (3) fix mobile overflow issues. No React component changes needed — all styling is in App.css with no inline styles.

## Implementation Landscape

### Key Files

- `frontend/src/App.css` — **The only file that needs significant changes.** Contains all styling (~780 lines). Every hardcoded color needs to become a CSS variable. Two `@media (max-width: 640px)` blocks exist but miss key mobile overflow cases.
- `frontend/index.html` — May need `<meta name="theme-color">` update for browser chrome color on mobile. Currently has correct viewport meta tag.
- `frontend/src/App.tsx` — No changes needed. All classes are already semantic.
- `frontend/src/pages/*.tsx` — No changes needed. No inline styles exist anywhere.
- `frontend/src/components/*.tsx` — No changes needed.
- `docker/Dockerfile.web` — No changes needed (standard Vite build).

### Color Mapping (Current → Dark Theme + Cyan)

**Semantic grouping of current colors to define custom properties:**

| Semantic Role | Current Value | Dark Theme Target |
|---|---|---|
| Page background | `#f4f4f8` | `#0f0f14` (near-black) |
| Card/surface background | `#fff` (29 uses) | `#1a1a24` (dark surface) |
| Card/surface hover | `#f9fafb`, `#f3f4f6` | `#22222e` (slightly lighter) |
| Primary text | `#1a1a2e`, `#374151` | `#e2e2ea` (light gray) |
| Secondary text | `#6b7280` | `#8b8b9a` (medium gray) |
| Muted text | `#9ca3af` | `#6b6b7a` |
| Border | `#e2e2e8`, `#d1d5db` | `#2a2a38` (subtle dark border) |
| Accent (links, focus, active) | `#6366f1` (indigo) | `#22d3ee` (cyan-400) |
| Accent hover | `#a5b4fc` (indigo-300) | `#67e8f9` (cyan-300) |
| Accent subtle bg | `rgba(99,102,241,0.1/0.15)` | `rgba(34,211,238,0.1/0.15)` |
| Header background | `#1a1a2e` | `#0a0a12` (darker than page) |
| Header active sort/pill | `#1a1a2e` on white | cyan on dark |
| Semantic: pending/amber | `#fef3c7`/`#92400e` | Darker amber variants |
| Semantic: approved/green | `#d1fae5`/`#065f46` | Darker green variants |
| Semantic: rejected/red | `#fee2e2`/`#991b1b` | Darker red variants |
| Semantic: edited/blue | `#dbeafe`/`#1e40af` | Darker blue variants |
| Dialog overlay | `rgba(0,0,0,0.4)` | `rgba(0,0,0,0.6)` (denser) |

### Mobile Overflow Fixes

1. **Missing `overflow-x: hidden` on `html, body`** — primary cause of horizontal scroll
2. **`.mode-toggle__label` has `white-space: nowrap`** (line 324) — mode toggle text can push header width on narrow screens
3. **`.creator-row__stats` has `white-space: nowrap`** (line 1459) — stats can overflow on mobile
4. **`.app-header__right`** at 640px gets `width: 100%` but nav items + mode toggle can still exceed viewport width
5. **`.search-container` has `max-width: 36rem`** without ensuring the input doesn't overflow its parent

### Build Order

1. **Define CSS custom property palette in `:root`** — establish all color tokens. This is the foundation everything else depends on.
2. **Replace hardcoded colors with `var(--*)` references** — systematic find-and-replace through App.css, section by section. This is the bulk of the work but mechanically straightforward.
3. **Fix mobile responsive issues** — add `overflow-x: hidden` on body, fix header nav wrapping, handle `white-space: nowrap` cases, ensure no element exceeds viewport width.
4. **Update index.html `<title>` and `<meta name="theme-color">`** — cosmetic but contributes to the dark theme feel on mobile.

### Verification Approach

1. **`npm run build` in frontend/** — must succeed with zero TypeScript errors (no TS changes expected, but confirms build pipeline works)
2. **Visual verification at desktop (1280×800)** — dark backgrounds, cyan accents, readable text contrast, cards distinct from page background
3. **Visual verification at mobile (390×844)** — no horizontal scrollbar, header doesn't overflow, cards stack properly, all text readable
4. **Check specific pages:** Home, Creators, Topics, a Technique page, Review Queue — each has distinct styling patterns
5. **Deploy via `docker compose build chrysopedia-web && docker compose up -d chrysopedia-web`** on ub01, verify at http://ub01:8096

## Constraints

- **Single CSS file, no build tooling for CSS** — no PostCSS, no Sass, no Tailwind. CSS custom properties are the right primitive here; they're native and require zero build changes.
- **No component changes desired** — the TSX files use className props only, no inline styles. The entire theme can be applied purely in CSS.
- **Status badges (pending/approved/rejected/edited) must remain visually distinct** — their semantic colors need dark-theme variants that preserve the meaning while being readable on dark backgrounds.
- **Title in index.html says "Chrysopedia Admin"** — should be updated to just "Chrysopedia" since it's the public-facing app.

## Common Pitfalls

- **Insufficient contrast on dark backgrounds** — dark gray text on dark backgrounds becomes unreadable. Use WCAG AA minimum (4.5:1 for body text). The muted text `#9ca3af` on dark `#1a1a24` would fail — need brighter muted values.
- **Cards blending into page background** — in dark themes, the distinction between page background and card surface must be maintained through both color difference AND border visibility. The current `1px solid #e2e2e8` border is critical and must translate to a visible dark border.
- **Badge readability** — status badges use light backgrounds with dark text. In dark theme, either darken the badge backgrounds (keeping text light) or use a different approach. The safest is darker, more saturated badge backgrounds with lighter text.
- **Focus rings invisible** — the current `box-shadow: 0 0 0 3px rgba(99,102,241,0.15)` focus ring on inputs will need to use cyan and be visible against dark surfaces.
