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:
@@ -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>
|
||||
Reference in New Issue
Block a user