docs: import TRM design handoff + defer adoption to phase 3.8

A design handoff bundle generated by Claude Design (claude.ai/design)
on 2026-05-02. Defines the Bloomberg/F1-pit-wall aesthetic for TRM:
- ink-on-paper base + race-flag red accent (#E8412B)
- square-edged everything, sharp printed offset shadows
- mono numerics (JetBrains Mono) for any changing value
- Goldplay (real licensed font, three weights in bundle fonts/)
- four surfaces designed: dashboard / leaderboard / mobile / marketing
  (SPA scope is the first two)

The bundle is committed in-tree at TRM_Design_System-handoff/ so 3.8
has the full source material when it picks the work up. Includes:
- Top-level + project READMEs (the design spec)
- chats/chat1.md (intent + iteration history)
- colors_and_type.css (token set, drop-in for Tailwind 4 @theme)
- fonts/ (Goldplay regular/semibold/bold)
- ui_kits/ (HTML prototypes per surface)
- preview/ (per-token visual reference cards)

Updated phase-3-dogfood-readiness/README.md task 3.8 row to point at
the bundle and document the recommended approach (retheme shadcn via
CSS variable overrides + Tailwind 4 @theme, not replace).

Why deferred: foundational tokens are non-blocking for Phase 1 (login
+ placeholder home) and Phase 2 (live map without chrome). Applying
them now would either delay dogfood-blocking work or land partial
styling that gets reworked when 3.8 lands the full pass.
This commit is contained in:
2026-05-02 19:10:11 +02:00
parent 0467a4b7ef
commit 8223a566e4
72 changed files with 3154 additions and 1 deletions
@@ -0,0 +1,128 @@
<!doctype html>
<html><head><meta charset="utf-8">
<title>TRM Dashboard · UI Kit</title>
<link rel="stylesheet" href="../../colors_and_type.css">
<link rel="stylesheet" href="../kit.css">
<script src="https://unpkg.com/lucide@latest"></script>
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
</head>
<body>
<div id="root"></div>
<script type="text/babel" src="TopBar.jsx"></script>
<script type="text/babel" src="SideNav.jsx"></script>
<script type="text/babel" src="KpiStrip.jsx"></script>
<script type="text/babel" src="EventsTable.jsx"></script>
<script type="text/babel" src="AlertFeed.jsx"></script>
<script type="text/babel" src="RaceControl.jsx"></script>
<script type="text/babel">
const { useState, useEffect } = React;
const EVENTS = [
{ id: 1, name: 'Coastline 10K · Spring 2026', date: 'Apr 27, 09:00', sport: 'Run', starters: 1247, waves: 4, status: 'LIVE', statusClass: 'live', icon: 'radio' },
{ id: 2, name: 'Granite Crit Series · Round 3', date: 'May 03, 18:30', sport: 'Cycling', starters: 184, waves: 6, status: 'SCHEDULED', statusClass: 'info', icon: 'calendar' },
{ id: 3, name: 'Twin Peaks Triathlon', date: 'May 11, 06:30', sport: 'Triathlon', starters: 612, waves: 8, status: 'SCHEDULED', statusClass: 'info', icon: 'calendar' },
{ id: 4, name: 'Harbor Sprint Regatta', date: 'May 18, 11:00', sport: 'Sailing', starters: 88, waves: 3, status: 'DRAFT', statusClass: 'neutral', icon: 'edit' },
{ id: 5, name: 'Northshore Marathon', date: 'Apr 06, 07:30', sport: 'Run', starters: 3402, waves: 5, status: 'FINISHED', statusClass: 'ok', icon: 'check' },
{ id: 6, name: 'Iron Sands Ultra 100', date: 'Mar 22, 05:00', sport: 'Ultra', starters: 287, waves: 1, status: 'FINISHED', statusClass: 'ok', icon: 'check' },
];
const LEADERS = [
{ pos: 1, bib: 247, name: 'Maya Chen', wave: 'Elite', split: '00:42:18.4', gap: '—', pace: '04:13' },
{ pos: 2, bib: 188, name: 'Ravi Park', wave: 'Elite', split: '00:42:22.6', gap: '+0:04.2', pace: '04:14' },
{ pos: 3, bib: 44, name: 'Noemi Vega', wave: 'Elite', split: '00:42:30.1', gap: '+0:11.7', pace: '04:15' },
{ pos: 4, bib: 302, name: 'Tomás Riera', wave: 'Elite', split: '00:42:55.0', gap: '+0:36.6', pace: '04:18' },
{ pos: 5, bib: 119, name: 'Anika Joshi', wave: 'Elite', split: '00:43:01.2', gap: '+0:42.8', pace: '04:19' },
{ pos: 6, bib: 401, name: 'Felix Okafor', wave: 'Open', split: '00:43:08.7', gap: '+0:50.3', pace: '04:19' },
{ pos: 7, bib: 77, name: 'Sara Lindqvist',wave:'Open', split: '00:43:14.0', gap: '+0:55.6', pace: '04:20' },
{ pos: 8, bib: 215, name: 'David Müller', wave: 'Open', split: '00:43:22.4', gap: '+1:04.0', pace: '04:21' },
];
const ALERTS = [
{ msg: 'Bib 247 missed split 3', detail: 'Flagging for manual review', time: '14:02:11', color: 'var(--flag)' },
{ msg: 'Yellow flag · sector 2', detail: 'Debris cleared in 90s', time: '14:00:42', color: 'var(--amber)' },
{ msg: 'Wave 4 cleared start mat', detail: '412 / 412 starters', time: '13:45:00', color: 'var(--green)' },
{ msg: 'Tracking active', detail: '14 of 14 mats online', time: '13:30:00', color: 'var(--blue)' },
{ msg: 'Bib 088 retired', detail: 'Marked DNF · medical', time: '13:24:18', color: 'var(--ink-3)' },
];
const MATS = [
{ id: 'M-01', name: 'Start', km: 0, online: true, reads: '1,247' },
{ id: 'M-02', name: 'Split 1', km: 2.5, online: true, reads: '1,201' },
{ id: 'M-03', name: 'Split 2', km: 5.0, online: true, reads: '1,184' },
{ id: 'M-04', name: 'Split 3', km: 7.5, online: true, reads: '892' },
{ id: 'M-05', name: 'Finish', km: 10, online: true, reads: '0' },
{ id: 'M-06', name: 'Backup', km: 10, online: false, reads: '—' },
];
function App() {
const [active, setActive] = useState('events');
const [selectedEvent, setSelectedEvent] = useState(null);
const [clock, setClock] = useState('14:02:11');
useEffect(() => {
const id = setInterval(() => {
const d = new Date();
setClock(d.toTimeString().slice(0,8));
}, 1000);
return () => clearInterval(id);
}, []);
useEffect(() => { lucide.createIcons(); });
const showRaceControl = active === 'live' || (selectedEvent && selectedEvent.statusClass === 'live');
return (
<div className="kit-app">
<TopBar clock={clock} />
<div className="kit-main">
<SideNav active={showRaceControl ? 'live' : active} onNav={(id) => { setActive(id); setSelectedEvent(null); }} />
<main className="kit-content">
{showRaceControl ? (
<>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 14, marginBottom: 18 }}>
<button className="btn ghost" onClick={() => { setActive('events'); setSelectedEvent(null); }} style={{ padding: 0, height: 'auto', boxShadow: 'none', border: 0, color: 'var(--ink-3)', fontSize: 12 }}> All events</button>
<h1 style={{ fontSize: 28, margin: 0 }}>Coastline 10K · Spring 2026</h1>
<span className="pill live"><span className="dot"></span>LIVE</span>
<div style={{ marginLeft: 'auto', display: 'flex', gap: 8 }}>
<button className="btn"><i data-lucide="download"></i>Export</button>
<button className="btn flag"><i data-lucide="flag"></i>Issue red flag</button>
</div>
</div>
<div style={{ marginBottom: 16 }}>
<KpiStrip items={[
{ label: 'Active bibs', value: '1,247', delta: '↑ 12 in last 5 min', dir: 'up' },
{ label: 'Lead time', value: '42:18.4', delta: 'Bib 247 · split 3' },
{ label: 'Avg pace', value: '4:23/km', delta: '↓ 0:06 vs target', dir: 'dn' },
{ label: 'Mats online', value: '14 / 14', delta: '100% uptime', dir: 'up' },
]} />
</div>
<RaceControl leaders={LEADERS} alerts={ALERTS} mats={MATS} />
</>
) : (
<>
<div style={{ display: 'flex', alignItems: 'baseline', gap: 14, marginBottom: 18 }}>
<h1 style={{ fontSize: 32, margin: 0 }}>Events</h1>
<span style={{ fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--ink-4)' }}>12 total · 1 live · 3 scheduled</span>
</div>
<div style={{ marginBottom: 16 }}>
<KpiStrip items={[
{ label: 'Events this season', value: '12', delta: '↑ 3 vs last season', dir: 'up' },
{ label: 'Total starters', value: '5,820', delta: '↑ 18% YoY', dir: 'up' },
{ label: 'Avg finish rate', value: '94.2%', delta: '↑ 1.4 pts', dir: 'up' },
{ label: 'Outstanding reviews',value: '7', delta: '3 high priority' },
]} />
</div>
<EventsTable events={EVENTS} onSelect={setSelectedEvent} />
</>
)}
</main>
</div>
</div>
);
}
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
</script>
</body></html>