# UI Theming — switchable themes + "cockpit" dark skin ## Context The current UI is functional but flat and generic. The user wants to try a different look (reference: a dark "cockpit" dashboard mockup — deep navy surfaces, cyan/amber accents, glow on the live reading, photo banner) **without losing the ability to switch back**. Today there is no theming layer at all: **0 CSS custom properties, ~430 hardcoded colours across 16 CSS files**. So the switch has to be built before any new skin can exist. Key finding that reduces scope: `Main.jsx` **already has the card structure** the mockup shows — `.weighing-section` containing `.weight-card` elements for Tare / Gross / Scale / Net, each with header, value and meta. No restructuring of the detail panel is needed; this is a styling job. ## Approach Token layer + `data-theme` attribute on ``. Two themes: - `classic` — reproduces today's look exactly (the safe default) - `cockpit` — the new dark skin ### 1. Token layer New `frontend/src/styles/tokens.css`, imported once in `index.css`: ```css :root, :root[data-theme="classic"] { --bg, --surface, --surface-raised, --border, --text, --text-muted, --accent, --accent-contrast, --success, --warning, --danger, --radius, --shadow-card, --glow-accent } :root[data-theme="cockpit"] { /* same names, dark values */ } ``` Token NAMES are semantic (`--surface`), never literal (`--navy`), so a third theme is one block. ### 2. Convert existing CSS to var() Mechanical replacement of hardcoded colours. `classic` values are lifted from the current CSS so the default theme is **visually unchanged** — that is the acceptance test. ### 3. Theme switch - `ThemeContext` (mirrors the existing `AuthContext`/`NomenclatureContext` pattern in `frontend/src/contexts/`) - Sets `data-theme` on `document.documentElement` - Persists to `localStorage`, wrapped in try/catch (private mode can throw) - Toggle control in `Header.jsx` - **Default: `classic`** — nothing changes for operators unless they opt in ### 4. Cockpit theme + banner Dark palette, raised card surfaces, cyan accent, glow on the live reading, monospace weight readouts. Banner strip behind the vehicle title in `.vehicle-detail` — image asset TBD (CSS gradient placeholder until the user supplies one). Banner is cockpit-only and must collapse on short viewports; this may run on a weigh-station monitor. ## Staging Deliberately two stages so a wrong palette is caught after 3 files, not 16. **Stage 1** — tokens, ThemeContext, header toggle, and convert: - `frontend/src/components/Main.css` (68 colours) - `frontend/src/components/Header.css` (17) - `frontend/src/components/ScaleMonitor/ScaleMonitor.css` (47) - `frontend/src/index.css`, `frontend/src/App.css` → user reviews both themes on the main weighing screen. **Stage 2** — remaining 13 files, once the direction is approved: NomenclatureManager (79), NomenclatureManagementOverlay (54), LicenseManager (45), ChangePasswordOverlay (22), ExtraCombinationsTable (20), DataDisplay (19), Login (15), NomenclatureEntryForm (14), NomenclatureDropdown (14), FormOverlay, ReportPreviewModal. ## Critical files - `frontend/src/styles/tokens.css` (new) - `frontend/src/contexts/ThemeContext.jsx` (new) - `frontend/src/components/Header.jsx` — toggle control - `frontend/src/App.jsx` — wrap in ThemeProvider - `frontend/src/components/Main.css` — the main surface Report editor CSS (`components/ReportEditor/`) is **out of scope**: the A4 designer renders a paper page that must stay white regardless of theme. ## Verification 1. `npx vitest run` — 201 tests still pass 2. Classic theme vs `git stash` screenshot: **visually identical** 3. Toggle switches instantly, no reload; survives refresh (localStorage) 4. Cockpit: check contrast on the live reading, balanced/unbalanced states, and that the banner collapses on a short viewport 5. Real browser check of both themes on the main weighing screen