Add a dark/light theme switcher to the navbar

The palette already lived in CSS custom properties, so dark mode is a
token-override block in the new theme.css, applied via data-theme on
the document element. A moon/sun toggle sits top right in the navbar;
an explicit choice persists in localStorage, otherwise the app follows
the OS preference live. An inline head script applies the theme before
first paint so a dark reload never flashes light.

Token hygiene sweep alongside: literal colours that broke under a dark
palette (timeline chip text, color-mix against white, calendar stripes
and bands, warning banners, nav background) moved into ten new tokens
with light-mode values unchanged. The flip clock keeps its fixed dark
look by design; the map keeps standard light tiles in both themes.
This commit is contained in:
2026-08-30 14:07:21 +02:00
parent 48c7a55bbd
commit 2f55723585
6 changed files with 207 additions and 16 deletions
+10
View File
@@ -402,6 +402,16 @@ Proxies `https://nominatim.openstreetmap.org/search?format=jsonv2&limit=5&accept
- SPA served from `public/`; all non-`/api` GETs fall back to `public/index.html` is NOT required — a single `index.html` with hash-based routing (`#/login`, `#/trips`, `#/trip/:id[/:tab]`) is the expected design, so no server-side fallback is needed.
### Theme switcher (dark/light, frontend-only)
The palette already lives in CSS custom properties on `:root` (light). Dark mode is a token override, not a parallel stylesheet.
- **Mechanism**: `document.documentElement` carries `data-theme="dark"` or `data-theme="light"`. `public/css/theme.css` (new, linked in index.html after styles.css) defines the complete dark palette under `[data-theme="dark"]` — every colour token `:root` defines (brand/bg/surface/border/text/danger/success/warn soft variants, shadows) gets a dark value; nothing may be defined only in the dark block.
- **Resolution order**: `localStorage.theme` (`"light"`/`"dark"`) if set, else the OS preference via `prefers-color-scheme` (followed LIVE through a `matchMedia` change listener until the user explicitly chooses). A tiny inline `<script>` in `index.html`'s `<head>` applies the attribute BEFORE the stylesheets paint (no flash of wrong theme). `localStorage` access wrapped in try/catch (privacy modes throw).
- **Toggle**: an icon button at the TOP RIGHT of the navbar (in `.nav-right`, before the user area) — shows 🌙 when light (click → dark) and ☀️ when dark (click → light), `aria-label` "Switch to dark/light theme". Clicking stores the explicit choice and swaps the attribute; no page reload.
- **Token hygiene sweep** (required, same commit): hard-coded colours that break under a dark palette move to tokens — `timeline.css` chip/block text `#334155` → a new `--chip-text` token and its `color-mix(… , white)` backgrounds → `color-mix(… , var(--surface))`; any equivalent literals in `styles.css` likewise. The flip-clock stays dark by design (its own fixed colours are the aesthetic, both themes). The Leaflet map keeps standard light tiles in both themes (out of scope); its container chrome uses tokens like everything else. Entry/type accent colours from `format.js` (`--chip` inline vars) are theme-neutral and unchanged.
- styles.css is at its 500-line cap: the dark palette, the toggle button styles, and any new tokens' *dark* values live in `theme.css`; edits to styles.css are replacements only (introducing a new token's light value in `:root` is allowed, growth beyond the cap is not).
### Trip page tabs & compact nav header (frontend-only)
The trip page is split into three tabs, and the old full-width trip-header card is REMOVED — its contents move into the top navbar so the page starts with actual content.