Add design spec for dark cockpit-style visual redesign

Documents the color/type/layout token system and the next-sync
countdown evolution agreed on with the user, ahead of implementation
planning.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-16 10:48:43 +02:00
parent 722570c9d3
commit 5d75aa328d

View File

@@ -0,0 +1,157 @@
# Visual Redesign — "Ride Computer" Dark Theme — Design
Date: 2026-08-16
Status: Draft for user review
## 1. Goal
Replace the current light, generic admin-template look with a distinctive dark
"bike computer cockpit" theme grounded in the app's own subject matter (indoor
cycling sync, Garmin telemetry). The redesign is a shared foundation: it must
land before the planned dashboard summary tiles and live-update UX polish, so
those can be built directly in the new visual language instead of needing a
second pass.
This spec covers only the visual system and the already-shipped next-sync
indicator's evolution into a live countdown. It does not add new pages, new
data, or new business logic.
## 2. Scope
### In scope
- New CSS design-token system (color, spacing reuse, borders) replacing the
current light theme in `app/web/static/style.css`.
- Cockpit-style topbar (dark, brand mark, nav, live sync countdown).
- Restyled shared components: cards, buttons, badges, tables, forms, empty
states — applied globally via shared CSS classes so every existing template
(login, account-login, dashboard, account detail/edit, users
detail/new/edit, system, sync result fragment) picks it up without
structural rewrites.
- Typographic treatment: uppercase tracked labels for headings/section labels
(extends the existing table-header pattern), tabular monospace numerals for
all stats/timestamps/counters.
- Evolving the existing static next-sync timestamp (`app/web/static/app.js`,
`base.html`) into a live, client-ticking countdown.
- Accessible focus states (visible lime outline) and `prefers-reduced-motion`
handling for the one animated element (the countdown) and card entrance.
### Out of scope (separate follow-up specs)
- Dashboard summary/stat tiles (next phase, builds on this theme).
- Live sync results without full page reload (UX-polish phase).
- Any light-mode / theme-toggle support (explicitly rejected by user — dark
only).
- New charts/statistics views.
- Any change to routes, models, or sync/business logic.
## 3. Design tokens
### Color
| Token | Hex | Usage |
|---|---|---|
| `--bg` | `#12151A` | Page background |
| `--surface` | `#1A1F27` | Cards, topbar, table zebra-free rows |
| `--surface-raised` | `#232935` | Hover states, inputs |
| `--border` | `#2A3038` | Hairline dividers/card borders |
| `--text` | `#E7EAF0` | Primary text |
| `--text-muted` | `#8B93A3` | Secondary text, labels, hints |
| `--accent` | `#C8FF4D` | Electric lime — signature countdown, primary buttons, focus rings, "healthy" status |
Semantic status colors (each with a ~14% opacity tint of the same hue over
`--surface` for badge backgrounds, matching the existing `--*-bg` variable
pattern already in `style.css`):
| State | Hex | Meaning |
|---|---|---|
| Success / healthy | `--accent` `#C8FF4D` | Reuses the signature accent — a healthy sync *is* the good state the accent celebrates |
| Warning / degraded | `#FFB454` | Amber |
| Danger / action required / failed | `#FF5F6D` | Coral |
| Info / syncing / running | `#5FD4FF` | Cyan |
| Neutral / disabled | `#5A6472` | Slate |
These map 1:1 onto the existing `--success`, `--warning`, `--danger`,
`--info`, `--neutral` variable names already used by `.badge-*` classes in
`style.css` — only their values change, not the class structure, so templates
need no edits for badges.
### Typography
- Body/UI face: unchanged system stack (`-apple-system, BlinkMacSystemFont,
"Segoe UI", Roboto, Helvetica, Arial, sans-serif`) — no new font loads,
works offline in a self-hosted container.
- Monospace face for all numeric data (stats, timestamps, the countdown,
table numeric columns): `ui-monospace, SFMono-Regular, Menlo, Consolas,
"Liberation Mono", monospace`, with `font-variant-numeric: tabular-nums`.
- Headings and structural labels (`h1`/`h2`, `.badge`, table `th`, the new
topbar labels): uppercase, `letter-spacing: 0.06em`, extending the tracked
uppercase style `table th` already has today — applied consistently instead
of only in tables.
### Layout
- Topbar becomes the cockpit header: `--surface` background, hairline bottom
border, brand mark left, nav + live countdown grouped right (existing
`.topbar-right` wrapper from the next-sync work is reused).
- Cards: hairline `--border` outline, `--surface` background, no drop shadow
(shadows read poorly on dark; hairlines carry the "device bezel" feel
instead). Radius stays at the existing `--radius: 10px`, unchanged.
- Buttons: primary uses `--accent` background with dark text (for contrast
against the light lime); secondary keeps outline/ghost style against
`--surface`.
- Focus-visible: 2px `--accent` outline on all interactive elements (links,
buttons, inputs) — dark backgrounds need this to stay accessible since the
current subtle browser default focus ring is hard to see on `--surface`.
### Motion
- The live countdown ticks once per second (text content update only, no
layout shift).
- Cards fade/slide in ~150ms on initial page load, `translateY(4px) → 0`.
- Both respect `prefers-reduced-motion: reduce` (countdown text still updates,
since it's informational, not decorative; the card entrance animation is
skipped entirely).
## 4. Signature element: live sync countdown
`app/web/static/app.js` currently does a one-time UTC→local conversion of the
`time[data-utc]` element on `DOMContentLoaded`. It's extended to:
1. On load, read `data-utc` as the target instant.
2. If the target is in the future, start a `setInterval` (1s) that computes
the remaining duration and renders it as `HH:MM:SS` (or `MM:SS` under an
hour) in monospace, e.g. `NEXT SYNC ▸ 00:12:04`.
3. If the target is in the past (page left open past the sync time, or
`next_tick` not yet known on first boot), render `due now` instead of a
negative countdown.
4. The `title` attribute keeps showing the absolute local time (via
`toLocaleString()`) and the original UTC instant, so hovering still gives
an absolute reference — this preserves today's behavior as a fallback/aid.
5. `data-utc` stays the templating contract between server and client (same
attribute the current tests assert on), so no server-side route or test
changes are needed for this evolution — only `app.js` behavior and the
surrounding CSS/markup in `base.html` change.
No server-side change: `next_sync_tick()` in `app/web/routes.py` and the
`base.html` template variable wiring stay as they are; only the visual
presentation and `app.js` ticking logic change.
## 5. Testing
This is a CSS/JS-presentation change with one markup adjustment (countdown
wrapper element/label in `base.html`). Existing server-rendered tests assert
on `data-utc="..."` substrings and text content, not on CSS classes or exact
visual output, so no test breakage is expected. No new automated test is
meaningful for pure CSS token values; the existing
`tests/web/test_next_sync_display.py` continues to guard the server-side
contract (the attribute and value), and manual verification (screenshot) is
used to confirm the visual outcome, consistent with how the next-sync feature
was verified.
## 6. Rollout
Single pass across `style.css`, `base.html`, `app.js`. No template
restructuring needed beyond `base.html`'s topbar, since all other templates
already consume the shared `.card`, `.badge`, `button`/`.btn`, `table`, and
`form.stacked-form` classes this spec restyles centrally.