From 91cc79b14ea4b17afcbbfcb88fd1bf25998b8b4b Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Thu, 18 Jun 2026 19:46:56 +0200 Subject: [PATCH] feat(web): adopt TRM design-system tokens (tokens only) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Linked Claude Design project "TRM — Tracking & Race Management" is a race-timing kit, not a parking design. Adopted its TOKENS only — no TRM components. Aligned the existing term-* accents onto TRM's exact night/ semantic values (surfaces → night scale; amber→#f2a516, green→#2e8c4a, red→#e8412b flag, cyan→#2563c8 blue) so the whole booth UI shifts palette with zero component edits. Exposed TRM's full vocabulary (night/ink/paper scales, flag/amber/green/blue, viz-1..8, 4px spacing, type scale, square radii, sharp offset shadows) as Tailwind v4 utilities for new work. Offline appliance: dropped TRM's Google-Fonts @import (no runtime network); Goldplay display face not self-hosted yet — falls back to a sans stack. Web build green; login renders on the new palette. Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V --- apps/web/src/index.css | 170 +++++++++++++++++++++++++++++---- wiki/concepts/booth-console.md | 7 +- wiki/log.md | 10 ++ 3 files changed, 170 insertions(+), 17 deletions(-) diff --git a/apps/web/src/index.css b/apps/web/src/index.css index 6980b34..089845c 100644 --- a/apps/web/src/index.css +++ b/apps/web/src/index.css @@ -3,28 +3,159 @@ /* Bloomberg-terminal aesthetic: dense, dark, monospace, keyboard-first. Tailwind v4 — design tokens live here in @theme (no tailwind.config.js). The booth runs on a fixed appliance display; we optimise for a dark room, - glanceable status colour, and high information density over whitespace. */ + glanceable status colour, and high information density over whitespace. + + Token vocabulary adopted from the "TRM" design system (Tracking & Race + Management) — TOKENS ONLY: colours, type scale, spacing, radii, shadows. + None of TRM's race-timing components are used. The existing `term-*` accents + are aligned onto TRM's exact values so the whole booth UI inherits the TRM + palette without renaming a single class. TRM's full vocabulary is also + exposed as utilities (night-*, ink-*, paper-*, flag/amber/green/blue, the + spacing/type/shadow scales) for new work. + + Offline appliance: NO webfont @import (no network at runtime). Goldplay (the + TRM display face) is not self-hosted yet — display/heading text falls back to + a clean sans stack; wire local Goldplay @font-face here if it's wanted. */ @theme { - /* Surfaces — near-black, layered greys for panels/borders. */ - --color-term-bg: #0a0e12; - --color-term-panel: #11161c; - --color-term-panel-2: #161d25; - --color-term-border: #232c37; - --color-term-muted: #6b7785; - --color-term-text: #c9d3de; + /* ============================================================ + TERMINAL ACCENTS — aligned onto TRM's exact values. + These keep their `term-*` names (used across every screen), + but now resolve to TRM colours so the palette is unified. + ============================================================ */ + /* Surfaces — TRM "night" (trackside dark) scale. */ + --color-term-bg: #0b0d10; /* TRM --night */ + --color-term-panel: #14171c; /* TRM --night-2 */ + --color-term-panel-2: #1e222a; /* TRM --night-3 */ + --color-term-border: #2a2f38; /* TRM --night-line */ + --color-term-muted: #8a8a82; /* TRM --night-fg-3 / --ink-4 */ + --color-term-text: #f2f2ee; /* TRM --night-fg */ - /* Status accents — the terminal's signal colours. */ - --color-term-amber: #f5a623; /* primary accent / headings / focus */ - --color-term-green: #2ecc71; /* entry / ok / free */ - --color-term-red: #ff4d4f; /* exit / fault / full */ - --color-term-cyan: #38bdf8; /* payment / info */ + /* Status accents — TRM semantic colours. */ + --color-term-amber: #f2a516; /* TRM --amber (caution / accent / focus) */ + --color-term-green: #2e8c4a; /* TRM --green (entry / ok / free) */ + --color-term-red: #e8412b; /* TRM --flag (exit / fault / full) */ + --color-term-cyan: #2563c8; /* TRM --blue (payment / info / live) */ - /* Monospace stack — IBM Plex Mono / JetBrains first, system mono fallback. */ + /* ============================================================ + TRM FULL VOCABULARY — exposed as Tailwind utilities for new work. + ============================================================ */ + /* Ink & paper (light surfaces — for any light-on-dark inversions). */ + --color-paper: #fafaf7; + --color-paper-2: #f2f2ee; + --color-paper-3: #e8e8e2; + --color-ink: #0e0e0c; + --color-ink-2: #2a2a26; + --color-ink-3: #5a5a53; + --color-ink-4: #8a8a82; + --color-ink-5: #b8b8b0; + --color-ink-6: #dcdcd4; + + /* Night scale (the booth's working surfaces). */ + --color-night: #0b0d10; + --color-night-2: #14171c; + --color-night-3: #1e222a; + --color-night-line: #2a2f38; + --color-night-fg: #f2f2ee; + --color-night-fg-2: #b8b8b0; + --color-night-fg-3: #8a8a82; + + /* Race accents + semantic. */ + --color-flag: #e8412b; + --color-flag-2: #c8331f; + --color-flag-tint: #fbe3de; + --color-amber: #f2a516; + --color-amber-2: #c88500; + --color-amber-tint: #fbefd0; + --color-green: #2e8c4a; + --color-green-2: #1f6a36; + --color-green-tint: #ddefe2; + --color-blue: #2563c8; + --color-blue-2: #1a4fa8; + --color-blue-tint: #dce6f8; + --color-violet: #6b46c1; + --color-magenta: #c9296f; + --color-teal: #188c8a; + + --color-ok: #2e8c4a; + --color-warn: #f2a516; + --color-danger: #e8412b; + --color-info: #2563c8; + + /* Data-viz categorical (8). */ + --color-viz-1: #e8412b; + --color-viz-2: #2563c8; + --color-viz-3: #2e8c4a; + --color-viz-4: #f2a516; + --color-viz-5: #6b46c1; + --color-viz-6: #188c8a; + --color-viz-7: #c9296f; + --color-viz-8: #5a5a53; + + /* ---------- TYPE — families ---------- */ + /* Mono is the booth's primary face (data-dense, tabular). Display/UI fall + back to a clean sans (Goldplay not self-hosted — see header note). */ --font-mono: "JetBrains Mono", "IBM Plex Mono", ui-monospace, "SFMono-Regular", "Menlo", "Consolas", monospace; + --font-display: "Goldplay", "Helvetica Neue", Arial, sans-serif; + --font-ui: "Goldplay", "Helvetica Neue", Arial, sans-serif; + --font-body: "Inter", "Helvetica Neue", Arial, sans-serif; - /* Tight radius — terminals are square. */ - --radius-term: 2px; + /* ---------- TYPE — scale (TRM, optimised for data density) ---------- */ + --text-overline: 11px; + --text-micro: 12px; + --text-small: 13px; + --text-body: 15px; + --text-lead: 17px; + --text-h6: 14px; + --text-h5: 16px; + --text-h4: 20px; + --text-h3: 26px; + --text-h2: 34px; + --text-h1: 48px; + --text-display: 72px; + --text-jumbo: 120px; + + /* ---------- SPACING (TRM 4px base) ---------- */ + --spacing-s0: 0; + --spacing-s1: 2px; + --spacing-s2: 4px; + --spacing-s3: 8px; + --spacing-s4: 12px; + --spacing-s5: 16px; + --spacing-s6: 20px; + --spacing-s7: 24px; + --spacing-s8: 32px; + --spacing-s9: 40px; + --spacing-s10: 48px; + --spacing-s11: 64px; + --spacing-s12: 80px; + --spacing-s13: 96px; + + /* ---------- RADIUS — TRM is square-edged ---------- */ + --radius-term: 2px; /* existing alias, kept */ + --radius-r0: 0; + --radius-r1: 2px; + --radius-r2: 4px; + --radius-r3: 6px; + --radius-r4: 10px; + + /* ---------- ELEVATION — TRM sharp "printed" offset shadows ---------- */ + --shadow-term-1: 0 1px 0 0 #0e0e0c; + --shadow-term-2: 2px 2px 0 0 #0e0e0c; + --shadow-term-3: 4px 4px 0 0 #0e0e0c; + --shadow-soft: 0 1px 2px rgba(14, 14, 12, 0.06), 0 4px 12px rgba(14, 14, 12, 0.04); + --shadow-pop: 0 8px 24px rgba(14, 14, 12, 0.12); + + /* ---------- MOTION (TRM) ---------- */ + --ease-snap: cubic-bezier(0.2, 0.8, 0.2, 1); + --ease-out: cubic-bezier(0.16, 1, 0.3, 1); + + /* ---------- COMPONENT TOKENS (TRM control heights, table rows) ---------- */ + --control-h-sm: 28px; + --control-h-md: 36px; + --control-h-lg: 44px; + --table-row-h: 36px; + --table-row-h-dense: 28px; } html, @@ -45,6 +176,13 @@ body { overscroll-behavior: none; } +/* Tabular numerics everywhere — counts, money, clocks must not jitter. */ +.num, +.tabular { + font-variant-numeric: tabular-nums; + letter-spacing: -0.01em; +} + /* Terminal scrollbars — thin, dark, unobtrusive. */ * { scrollbar-width: thin; diff --git a/wiki/concepts/booth-console.md b/wiki/concepts/booth-console.md index c60d5eb..26c7450 100644 --- a/wiki/concepts/booth-console.md +++ b/wiki/concepts/booth-console.md @@ -24,7 +24,12 @@ The operator UI outgrew "plain React + useState" once it needed live updates and the latest pushed occupancy. Anything durable is re-fetched via Query. - **Tailwind v4** with a **"Bloomberg-terminal" theme** (`apps/web/src/index.css`, `@theme`): near-black surfaces, amber/green/red/cyan status accents, monospace, dense/keyboard-first. **Radix** - primitives (Dialog, etc.) for accessible unstyled components. + primitives (Dialog, etc.) for accessible unstyled components. The token VALUES are adopted from the + **"TRM" design system** (Claude Design project; race-timing kit) — **tokens only**, no TRM + components: the `term-*` accents are aligned onto TRM's `night`/semantic colours and TRM's full + vocabulary (night/ink/paper scales, flag/amber/green/blue, the spacing/type/shadow scales) is + exposed as utilities for new work. Offline appliance ⇒ no webfont `@import`; Goldplay (TRM's display + face) not self-hosted yet — display text falls back to a sans stack. - **react-i18next** for [[i18n]] (Albanian default). > This SUPERSEDES the original "plain React, no framework" note on [[react-vite-spa]] — that held diff --git a/wiki/log.md b/wiki/log.md index ebe8b5a..881a472 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -854,3 +854,13 @@ The version selector picks "latest tariff_version with effectiveFrom ≤ session The owner asked for "first N hours × X, next N hours × Y, …, 24h cap" — which the stepped-block engine ALREADY does (ordered blocks, per-block rate, rolling-24h cap; computeFee tested). So no new axis: the work was making the model complete + footgun-free. Two changes. (1) **Forbid a bounded last block** — `validateTariffStructure` (shared) now rejects a final block with a non-null `uptoMin`, so the "thereafter" rate is always explicit; previously a bounded tail silently inherited its own rate past its bound (a hidden, never-stated price — e.g. the live ALL tariff's 180-min last block billed hour 4+ at the 3rd-hour rate). `rateAt()` still prices legacy bounded-tail versions gracefully and validation is publish-only, so immutable published versions are unaffected (no migration). (2) **Composer edits bands as a DURATION in hours**, not cumulative minutes — `BlockForm` carries `hours`; `toStructure` accumulates into cumulative `uptoMin` minutes; the last row is a pinned, non-removable, hours-less "thereafter (open-ended)" band; `blocksToForm` round-trips stored minutes back to band hours (legacy bounded tails still load). i18n: replaced `upToMin`/`egExample` with `bandDuration`/`hoursUnit`/`egHours` in sq+en (catalog parity green). Verified: validator rejects bounded-last / accepts open-ended; computeFee correct at 1/2/3/5/6/24h for a 0-2h@200,2-5h@100,5h+@50 + 1000 cap card. Full build green (shared/server/web). Updated [[tariff]]. No migration. NB considered-and-rejected: time-of-day / weekday wall-clock tiers ("timeframe") were offered but the owner explicitly chose the elapsed-duration ladder only — see [[tariff-time-tiers]] for the deferred wall-clock axis. + +## [2026-06-18] feat | Tariff V2 — legacy-parity pricing (time-of-day, category, seasonal, flat) + +Brought the legacy ParkSQL2017 pricing BREADTH onto our engine (keeping integer-minor-unit money + immutable signed versions; rejecting legacy float money / mutable rows). `TariffStructure` is now a discriminated union: V1 = the original bare ladder (UNCHANGED, verbatim algorithm — golden-regression-tested against the live production version); V2 = `{version:2, tz, , defaultCard, windowedCards[]}` where each card is flat OR a block ladder and may be scoped by wall-clock hour window / day-of-week / date range / vehicle category. computeFeeV2 prices by stepping one increment at a time, advancing the ladder by ELAPSED minutes (continuous) while selecting the active card by WALL-CLOCK time in the version's FROZEN tz. Decisions (with user): tz is a per-site setting (site_config.timezone, default Europe/Tirane) stamped server-side into each version on publish — never the host clock (reproducibility); default-card cap governs a mixed day; precedence = specificity (date>dow>hour) → priority → name (total, order-independent), validation rejects ambiguous ties; category = a card FIELD (not tariff scope), frozen in the signed vehicle_entry payload (site_config.default_vehicle_category default), read at both pricing call-sites. Composer: default card front-and-centre (flat/ladder toggle), tiers under an "Advanced" disclosure (window builder), emits BARE V1 when no tiers (back-compat). DB: migrations 0005 (timezone) + 0006 (default_vehicle_category) — applied to the live DB (backed up). Stood up vitest in @parking/shared (was zero tests on the ledger-feeding fee fn); 36 tests incl. golden V1 regression, happy-hour/overnight/dow/flat/category/cap edges, precedence shuffle-invariance, Europe/Tirane DST determinism, validation matrix — all green. Full monorepo build green; V2 publish verified end-to-end via inject (tz stamped from config not client; malformed V2 rejected). Updated [[tariff-time-tiers]] (status open→settled, as-built), [[tariff]], index. No event-chain change. + +NB incident: the running tsx-watch dev server (server + vite) crashed mid-edit on a half-saved file + a schema column the live DB lacked; recovered by finishing the edits, applying the migration to the live DB, and restarting both watchers. Live DB backup left at apps/server/parking.sqlite.bak-*. + +## [2026-06-18] feat | Booth UI — adopted "TRM" design-system tokens (tokens only) + +The owner linked a Claude Design project (`019ddfee-…`, "TRM — Tracking & Race Management") and asked to implement its designs in `apps/web/`. TRM is a RACE-TIMING design system (dashboard/leaderboard/marketing/mobile kits: RaceControl, HeroClock, LiveTable, BibCard, Ticker) — NOT a parking design; literally porting it would have reskinned the booth UI with race components. Flagged the mismatch; owner chose **tokens only**. So: brought TRM's token VOCABULARY into the Tailwind v4 `@theme` (`apps/web/src/index.css`) and ALIGNED the existing `term-*` accents onto TRM's exact values — surfaces → TRM `night` scale (#0b0d10/#14171c/#1e222a/#2a2f38), amber #f5a623→#f2a516, green #2ecc71→#2e8c4a, red #ff4d4f→#e8412b (flag), cyan #38bdf8→#2563c8 (blue). No component touched (170+ `term-*` references resolve unchanged). Also exposed TRM's full vocabulary as utilities for new work: night/ink/paper scales, flag/amber/green/blue + tints, viz-1..8, the 4px spacing scale (s0..s13), type scale (overline..jumbo), square radii, sharp "printed" offset shadows, control/table-row heights. Offline-appliance constraint ⇒ deliberately did NOT keep TRM's Google-Fonts `@import` (no runtime network); Goldplay (TRM display face) left un-self-hosted — display/heading falls back to a sans stack (mono is the booth's primary face anyway), noted for later wiring. Verified: web build green; login renders on the new palette (amber focus ring = #f2a516). Updated [[booth-console]]. No logic/schema/event-chain change.