From 404029ff24ba56ede246f91c9945b050059d4e81 Mon Sep 17 00:00:00 2001 From: AmanTahiliani Date: Mon, 25 May 2026 02:58:36 -0400 Subject: [PATCH] Add MVP completion checklist --- .../refactor/08-v1-scope-and-phasing.md | 20 +- .../refactor/13-phase-5-react-race-hub.md | 6 +- .../refactor/21-mvp-completion-checklist.md | 75 ++ documentations/refactor/README.md | 2 + .../refactor/screens/command-center.html | 683 ------------- .../refactor/screens/component-notes.md | 375 ------- .../refactor/screens/data-library.html | 651 ------------ documentations/refactor/screens/index.html | 180 ---- .../refactor/screens/live-timing.html | 774 --------------- .../refactor/screens/mobile-live.html | 939 ------------------ documentations/refactor/screens/race-hub.html | 847 ---------------- documentations/refactor/screens/styles.css | 779 --------------- 12 files changed, 86 insertions(+), 5245 deletions(-) create mode 100644 documentations/refactor/21-mvp-completion-checklist.md delete mode 100644 documentations/refactor/screens/command-center.html delete mode 100644 documentations/refactor/screens/component-notes.md delete mode 100644 documentations/refactor/screens/data-library.html delete mode 100644 documentations/refactor/screens/index.html delete mode 100644 documentations/refactor/screens/live-timing.html delete mode 100644 documentations/refactor/screens/mobile-live.html delete mode 100644 documentations/refactor/screens/race-hub.html delete mode 100644 documentations/refactor/screens/styles.css diff --git a/documentations/refactor/08-v1-scope-and-phasing.md b/documentations/refactor/08-v1-scope-and-phasing.md index cab2854..92a239a 100644 --- a/documentations/refactor/08-v1-scope-and-phasing.md +++ b/documentations/refactor/08-v1-scope-and-phasing.md @@ -93,9 +93,7 @@ TUI requirements during v1: - Persist live events/snapshots as a separate append-only stream only after the extracted package has stable event/state types. - See [09 Phase 1 Live Extraction](09-phase-1-live-extraction.md) for the - implementation brief and - [Cursor Phase 1 Prompt](cursor-phase-1-live-extraction-prompt.md) for the - fresh-agent handoff. + original implementation brief. ### Phase 2: Store Foundation @@ -106,9 +104,7 @@ TUI requirements during v1: - Add normalized tables required for Race Hub v1. - Keep existing HTTP cache behavior unchanged. - See [10 Phase 2 Store Foundation](10-phase-2-store-foundation.md) for the - implementation brief and - [Cursor Phase 2 Prompt](cursor-phase-2-store-foundation-prompt.md) for the - fresh-agent handoff. + original implementation brief. ### Phase 3: Ingestion Foundation @@ -118,9 +114,7 @@ TUI requirements during v1: - Add conservative request delay, bounded retry, and 429/live-lockout handling. - Make ingestion idempotent and resumable. - See [11 Phase 3 Ingestion Foundation](11-phase-3-ingestion-foundation.md) for - the implementation brief and - [Cursor Phase 3 Prompt](cursor-phase-3-ingestion-foundation-prompt.md) for the - coding-agent handoff. + the original implementation brief. ### Phase 4: Local-First Web API @@ -129,9 +123,7 @@ TUI requirements during v1: - Migrate selected Web endpoints from direct OpenF1 calls to local-first reads. - Allow small opportunistic fetches only for missing screen-level data. - See [12 Phase 4 Local-First Web API](12-phase-4-local-first-web-api.md) for - the implementation brief and - [Cursor Phase 4 Prompt](cursor-phase-4-local-first-web-api-prompt.md) for the - coding-agent handoff. + the original implementation brief. ### Phase 5: React Race Hub Slice @@ -142,9 +134,7 @@ TUI requirements during v1: - Keep the old Web UI available until the replacement route is credible. - This is the first frontend phase. Use Claude for this phase. - See [13 Phase 5 React Race Hub](13-phase-5-react-race-hub.md) for the - implementation brief and - [Claude Phase 5 Prompt](claude-phase-5-react-race-hub-prompt.md) for the - frontend-agent handoff. + original implementation brief. ## Ingestion Rate-Limit Defaults diff --git a/documentations/refactor/13-phase-5-react-race-hub.md b/documentations/refactor/13-phase-5-react-race-hub.md index 704b5d5..7b17c86 100644 --- a/documentations/refactor/13-phase-5-react-race-hub.md +++ b/documentations/refactor/13-phase-5-react-race-hub.md @@ -46,8 +46,10 @@ Use `/api/v1/race-hub?session_key=...` as the primary API. ## Visual Direction -Follow the existing mockups in `documentations/refactor/screens/`, but treat -them as direction, not rigid specs. +The temporary static mockups used during early product exploration have been +removed now that the production React routes exist. Use the implemented React +screens as the current source of truth, and keep this visual direction in mind +for future refinement. The UI should feel like an F1 operations room: diff --git a/documentations/refactor/21-mvp-completion-checklist.md b/documentations/refactor/21-mvp-completion-checklist.md new file mode 100644 index 0000000..b97adb6 --- /dev/null +++ b/documentations/refactor/21-mvp-completion-checklist.md @@ -0,0 +1,75 @@ +# MVP Completion Checklist + +## Status + +The local-first Web UI MVP is now functionally assembled. The app can ingest +OpenF1 data into the SQLite domain store, serve local-first Race Hub and +navigation APIs, render the React Race Hub/Data Library/Live Timing routes, and +serve the built React app from Go web mode when `frontend/dist` is present. + +This document replaces the temporary agent handoff prompts as the main +checkpoint for what has been completed and what remains. + +## Completed + +- Live SignalR code extracted into `internal/live` and reused by TUI and Web + mode. +- SQLite domain store added for local historical data. +- Session, meeting, and weekend ingestion paths added with dry-run support. +- Optional analytics ingestion failures are partial, not hard blockers. +- Local-first Race Hub API added with dataset availability metadata. +- Local-first season, meeting, and weekend navigation APIs added. +- React + TypeScript frontend added with TanStack Query and Router. +- Race Hub route added for classification, grid, strategy, positions, laps, + race control, weather, and dataset status. +- Data Library route added for local season/weekend coverage and CLI guidance. +- Live Timing route added for current backend live snapshot/SSE state. +- Go web mode serves the built React app from `frontend/dist` and falls back to + embedded legacy assets when no build is present. + +## Verification Commands + +Run these before cutting an MVP tag or handing the repo to another agent: + +```bash +go test ./internal/live ./internal/store ./internal/ingest ./internal/query ./internal/web +npm test -- --run --prefix frontend +npm run build --prefix frontend +npm run test:e2e +``` + +For a local manual smoke test: + +```bash +go run ./scripts/seed-e2e-db/main.go --db /tmp/boxbox-mvp.db +BOXBOX_DISABLE_LIVE=1 go run ./cmd/main.go --web --db /tmp/boxbox-mvp.db --port 18080 +BOXBOX_API_PORT=18080 npm run dev --prefix frontend -- --host 127.0.0.1 --port 15173 --strictPort +``` + +Then open: + +- `http://127.0.0.1:15173/race-hub?session_key=9472` +- `http://127.0.0.1:15173/data-library` +- `http://127.0.0.1:15173/live` + +## Remaining Post-MVP Work + +- Add real visual-regression checks for the React screens. +- Improve high-density mobile/iPad behavior for Live Timing and Race Hub tables. +- Add persisted live-event capture and reconciliation only after defining the + live storage model. +- Add track outline ingestion/read models to the React app if the local data + source is reliable enough. +- Expand from weekend/session ingestion toward safe full-season backfill. +- Add Command Center, Drivers, Standings, and Settings as separate product + phases. +- Revisit static archive feasibility after source mapping is proven. + +## Notes + +- `--ingest-year` currently discovers season meetings and sessions. Use + `--ingest-meeting ` for full weekend ingestion. +- The React app should continue avoiding direct OpenF1 reads. New Web UI routes + should call local-first Go APIs. +- The TUI live mode remains intentionally preserved. Historical Web UI parity + with the TUI is not required for this MVP. diff --git a/documentations/refactor/README.md b/documentations/refactor/README.md index cff6ad6..efe8c6f 100644 --- a/documentations/refactor/README.md +++ b/documentations/refactor/README.md @@ -75,6 +75,8 @@ not implementation tickets yet. slice for making one command ingest a whole race weekend into the local DB. - [20 Phase 12 Data Library UI](20-phase-12-data-library-ui.md): frontend slice for showing local ingestion coverage and next CLI actions. +- [21 MVP Completion Checklist](21-mvp-completion-checklist.md): current + implementation status, verification commands, and remaining post-MVP work. ## External References diff --git a/documentations/refactor/screens/command-center.html b/documentations/refactor/screens/command-center.html deleted file mode 100644 index d8966ea..0000000 --- a/documentations/refactor/screens/command-center.html +++ /dev/null @@ -1,683 +0,0 @@ - - - - - - box-box — Command Center - - - - - - -
- - - - - -
- - -
-
- Round 8 - - - Race In Progress - - Open Live → -
- -
- 🇲🇨 -
-
Monaco Grand Prix
-
Circuit de Monaco · Monte Carlo · 22–25 May 2025
-
-
-
- - - - - -
-
- Race — In Progress - Open Live Timing → -
-
-
-
Lap
-
45/78
-
-
-
Leader
-
LEC
-
-
-
Gap P1–P2
-
+3.4s
-
-
-
Track
-
● GREEN
-
-
-
- - -
-
- Weekend Schedule - Circuit de Monaco · 3.337 km · 78 laps -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SessionDateLocal TimeUTCStatusData
FP1Thu 22 May11:3009:30DoneFull
FP2Thu 22 May15:0013:00DoneFull
FP3Sat 24 May11:3009:30DoneFull
QualifyingSat 24 May15:0013:00DoneFull
RaceSun 25 May14:0012:00LiveStreaming
-
- - -
-
- Weather - Race day · updated 2 min ago -
-
-
- Air - 24°C -
-
- Track - 36°C -
-
- Humidity - 62% -
-
- Wind - 7 km/h -
-
- Direction - NW -
-
- Rain Risk - 2% -
-
- Pressure - 1014 hPa -
-
-
- -
- - - -
- - - - diff --git a/documentations/refactor/screens/component-notes.md b/documentations/refactor/screens/component-notes.md deleted file mode 100644 index 0485566..0000000 --- a/documentations/refactor/screens/component-notes.md +++ /dev/null @@ -1,375 +0,0 @@ -# Component Notes - -Implementation reference for translating the static mockups into React components. -Each entry covers purpose, screens, data shape, and responsive behaviour. - ---- - -## App Navigation — `` - -**Purpose**: Persistent sticky top bar across all screens. Shows logo, route links, live status badge, and density toggle. - -**Appears in**: All screens (always mounted). - -**Props / data needed**: -```ts -interface AppNavProps { - activePage: 'home' | 'live' | 'race-hub' | 'data-library' | 'standings' | 'drivers' - isLive: boolean // shows animated red dot + RACE/QUALI badge - sessionLabel?: string // e.g. "Monaco GP — Race" - densityMode: 'default' | 'compact' - onDensityChange: (mode) => void -} -``` - -**Responsive**: On phone (<768px), collapse nav links to icon-only or a hamburger. Live badge and density toggle remain visible. Session label moves to the session banner. - ---- - -## Density Toggle — `` - -**Purpose**: Switches between default and compact row heights. Effect is applied as a CSS class on ``, not via React state cascade. - -**Appears in**: ``. - -**Implementation note**: Call `document.documentElement.classList.toggle('compact', ...)` directly. Store preference in `localStorage`. React state only tracks the value for rendering the active button — does not gate CSS. - -**Responsive**: Always visible. Two-button [D][C] works at any width. - ---- - -## Session Status Banner — `` - -**Purpose**: Real-time session state strip: session name, current lap, session clock, track status, DRS state, fastest lap. Fed by SSE. - -**Appears in**: `/live` (always visible at top, sticky). On phone, replaces AppNav as the primary context header. - -**Props / data needed**: -```ts -interface SessionBannerProps { - sessionName: string // "Monaco GP — Race" - lapCurrent: number - lapTotal: number - sessionClock: string // "1:02:34" - trackStatus: 'green' | 'yellow' | 'red' | 'sc' | 'vsc' | 'unknown' - drsEnabled: boolean - fastestLap?: { driverCode: string; lapTime: number; lap: number } - airTemp: number - trackTemp: number - isConnected: boolean // SSE connection state -} -``` - -**Critical state**: `isConnected: false` must show a visible DISCONNECTED indicator. Do not silently go stale. - -**Responsive**: Horizontal scroll on narrow viewports. On phone, show session + lap + track status as minimum; other fields scroll off-screen. - ---- - -## Timing Tower — `` - -**Purpose**: Dense real-time table. One row per driver. Driven by SSE. This is the primary surface of the live screen. - -**Appears in**: `/live`. - -**Props / data needed**: -```ts -interface TimingEntry { - position: number - driverCode: string - driverNumber: number - teamColor: string - gap: string // "LEADER" | "+3.456" | "+1 LAP" - interval: string // "+3.456" | "—" - tyre: 'S' | 'M' | 'H' | 'I' | 'W' - tyreAge: number // laps on current tyre - lastLap: number // seconds - bestLap: number // seconds - s1State: 'pb' | 'ob' | 'slow' | 'none' - s2State: 'pb' | 'ob' | 'slow' | 'none' - s3State: 'pb' | 'ob' | 'slow' | 'none' - isPitIn: boolean - isPitOut: boolean - hasFastestLap: boolean - isDNF: boolean -} - -interface TimingTowerProps { - entries: TimingEntry[] - pinnedDrivers: string[] // driver codes - fastestLapDriver: string -} -``` - -**Performance critical**: SSE ticks every 1–2 seconds. Memoize `` by driver number. Diff at the entry level, not the full array. Use `React.memo` + stable references. Avoid full re-renders. - -**Columns on phone**: P · Driver · Gap · Tyre · Last (hide Int, Age, Best, Sectors). -**Columns on iPad**: P · Driver · Gap · Int · Tyre · Age · Last (hide Best, Sectors). -**Columns on desktop**: All columns. - ---- - -## Race Control Feed — `` - -**Purpose**: Scrolling list of race control messages. Auto-scrolls to newest. Color-coded by message type (SC, VSC, DRS, penalty, fastest lap, flag). - -**Appears in**: `/live` sidebar, `/race-hub` Race Control tab (static version). - -**Props / data needed**: -```ts -interface RCMessage { - id: string - lap: number - type: 'sc' | 'vsc' | 'drs' | 'penalty' | 'fl' | 'flag' | 'info' - text: string - timestamp: string -} - -interface RaceControlFeedProps { - messages: RCMessage[] - autoScroll: boolean -} -``` - -**Responsive**: In `/live` sidebar on desktop. On phone, a full-screen panel accessed via bottom tab. In race-hub, inline full-width list. - ---- - -## Battle Row / Battles Panel — `` - -**Purpose**: Shows detected on-track pairs with gap value and closing/stable/opening trend. - -**Appears in**: `/live` sidebar. - -**Props / data needed**: -```ts -interface Battle { - aheadCode: string - aheadTeamColor: string - behindCode: string - behindTeamColor: string - gapSeconds: number - trend: 'closing' | 'stable' | 'opening' -} - -interface BattlesListProps { - battles: Battle[] -} -``` - -**Responsive**: On phone, shown in the Battles bottom-tab panel alongside Pit Window. - ---- - -## Pinned Driver Strip — `` - -**Purpose**: Compact horizontal cards for drivers the user has pinned. Shows position, gap, last lap, tyre state at a glance without scrolling the timing tower. - -**Appears in**: `/live` (below session banner, above timing tower). Hidden on phone to preserve space. - -**Props / data needed**: -```ts -interface PinnedCardData { - driverCode: string - teamColor: string - position: number - gap: string - lastLap: string - tyre: string - specialState?: 'pit-out' | 'fastest-lap' | 'dnf' -} - -interface PinnedStripProps { - cards: PinnedCardData[] - onUnpin: (driverCode: string) => void -} -``` - -**Responsive**: Desktop + iPad only. Hide on phone (<768px) — tower already shows all data. - ---- - -## Pit Window Panel — `` - -**Purpose**: For each driver with a notable stint age, shows current tyre, age, and pit window status (OPEN / SOON / OVERDUE / DONE). Computed from stint age and expected compound life. - -**Appears in**: `/live` sidebar (third section after RC and Battles). - -**Props / data needed**: -```ts -interface PitWindowEntry { - driverCode: string - teamColor: string - tyre: 'S' | 'M' | 'H' | 'I' | 'W' - tyreAge: number - windowStatus: 'open' | 'soon' | 'overdue' | 'done' -} -``` - -**Note**: Window status logic lives in the backend or a pure TS utility — not component logic. - -**Responsive**: Desktop sidebar. On phone, shown in Battles tab panel below battle list. - ---- - -## Strategy Chart — `` - -**Purpose**: D3-owned horizontal stint chart. One row per driver (top 10), colored blocks = tyre compound, width = laps. SC/VSC period shading. Lap counter axis. - -**Appears in**: `/race-hub` Strategy tab. - -**Props / data needed**: -```ts -interface Stint { - compound: 'S' | 'M' | 'H' | 'I' | 'W' - startLap: number - endLap: number -} - -interface StrategyDriver { - code: string - teamColor: string - stints: Stint[] -} - -interface StrategyChartProps { - drivers: StrategyDriver[] - totalLaps: number - scPeriods: Array<{ start: number; end: number }> - vscPeriods: Array<{ start: number; end: number }> -} -``` - -**D3 contract**: Component owns its SVG DOM node. Mount/update via `useEffect` with D3. Resize via `ResizeObserver`. No React inside the SVG. - -**Responsive**: Full-width SVG with `viewBox`, scales with container. Label column width fixed. On narrow screens (< 480px), driver labels may need abbreviating. - ---- - -## Position Evolution Chart — `` - -**Purpose**: D3-owned line chart. X = lap, Y = position (1 at top). One line per driver (top 6). SC/VSC period bands. End labels per driver. - -**Appears in**: `/race-hub` Positions tab. - -**Props / data needed**: -```ts -interface PositionPoint { lap: number; position: number } - -interface PositionDriver { - code: string - color: string - dashed: boolean // true for teammate (same team color) - points: PositionPoint[] -} - -interface PositionEvolutionProps { - drivers: PositionDriver[] - totalLaps: number - scPeriods: Array<{ start: number; end: number }> - vscPeriods: Array<{ start: number; end: number }> -} -``` - -**D3 contract**: Same as StrategyChart — D3 owns the SVG, React manages data and container sizing. - -**Responsive**: Full-width `viewBox` SVG. Right-side labels need right-padding. - ---- - -## Dataset Status Indicator — `` - -**Purpose**: Shows completeness of local data for a session. Used in two modes: full grid (Race Hub Dataset tab, Data Library detail) and mini (Race Hub aside, API response metadata strip). - -**Appears in**: `/race-hub` Dataset tab, `/race-hub` aside, `/data-library` detail panel. API response metadata. - -**Props / data needed**: -```ts -type DatasetState = 'local' | 'partial' | 'missing' | 'stale' | 'live' - -interface DatasetEntry { - name: string // e.g. "laps", "car_data_samples" - state: DatasetState - lastIngestedAt?: string - error?: string -} - -interface DatasetStatusProps { - datasets: DatasetEntry[] - variant: 'grid' | 'mini' | 'strip' -} -``` - -**Responsive**: Grid variant reflows to 1-column on phone. Mini variant stays compact at all widths. Strip variant is a horizontal overflow row (source strip in the sub-header). - ---- - -## Ingest Command Block — `` - -**Purpose**: Displays one or more CLI ingest commands with syntax highlighting (comment lines, command lines). Not interactive — display only. - -**Appears in**: `/race-hub` Dataset tab, `/data-library` detail panel. - -**Props / data needed**: -```ts -interface IngestLine { - type: 'comment' | 'command' - text: string -} - -interface IngestCommandBlockProps { - lines: IngestLine[] -} -``` - -**Responsive**: Horizontal scroll on overflow. Monospace font required. - ---- - -## Responsive Panel Shell — `` - -**Purpose**: Layout wrapper that switches between sidebar-on-right (desktop), stacked (tablet), and tab-driven (phone) based on viewport. Used in Live Timing and Race Hub. - -**Appears in**: Used internally by `/live` and `/race-hub`. - -**Props / data needed**: -```ts -interface PanelShellProps { - main: React.ReactNode // always visible - aside: React.ReactNode // sidebar on desktop, bottom on tablet - phoneTabs?: Array<{ // only on phone — replaces aside with tabs - id: string - label: string - icon: string - content: React.ReactNode - }> - asideWidth?: number // default 300 -} -``` - -**Responsive**: -- Desktop (>1024px): `main | aside` grid -- iPad (769–1024px): `main | aside` with narrower aside -- Phone (<768px): `main` full-width + bottom tab nav switching aside panels - -**Implementation note**: Density mode class on `` flows through without prop drilling. `PanelShell` does not need to know about density — CSS handles it. - ---- - -## Source/Freshness Strip — `` - -**Purpose**: Inline metadata bar attached to API responses. Shows data source, last ingest time, staleness state, and list of missing datasets. - -**Appears in**: Sub-headers of `/race-hub`, `/command-center` (data status for weekend), any screen that reads from the local DB. - -**Props / data needed**: -```ts -interface SourceStripProps { - source: 'local' | 'api' | 'cache' | 'live' | 'missing' - lastIngestedAt?: string - isStale?: boolean - missingDatasets?: string[] -} -``` - -**Responsive**: Wraps on narrow viewports. Badges remain readable at any width. diff --git a/documentations/refactor/screens/data-library.html b/documentations/refactor/screens/data-library.html deleted file mode 100644 index cd9b955..0000000 --- a/documentations/refactor/screens/data-library.html +++ /dev/null @@ -1,651 +0,0 @@ - - - - - - box-box — Data Library - - - - - - -
- - - - - -
- - -
- 2025 Season - 24 rounds · 7 complete · 2 partial · 1 live - -
- -
-
- -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
RndWeekendDateStatusSessionsLast Sync
1🇧🇭 Bahrain GP2 MarFull -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
2d ago
2🇸🇦 Saudi Arabia GP16 MarFull -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
5d ago
3🇦🇺 Australian GP30 MarFull -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
8d ago
4🇯🇵 Japanese GP13 AprPartial -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
21d ago
5🇨🇳 Chinese GP20 AprPartial -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
28d ago
6🇺🇸 Miami GP4 MayFull -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
14d ago
7🇮🇹 Emilia Romagna GP18 MayFull -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
3d ago
8🇲🇨 Monaco GP25 MayLive -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
Now
9🇨🇦 Canadian GP13 JunMissing -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
10🇦🇹 Austrian GP27 JunMissing -
-
F1
-
F2
-
F3
-
Q
-
R
-
-
- + 14 upcoming rounds not yet available -
-
- - -
- -
-
- 🇧🇭 Bahrain Grand Prix - Full -
-
Round 1 · 2 March 2025 · meeting_key 1230
-
Ingested: 2 Mar 2025 18:42 · 142 MB across 5 sessions
-
- - -
-
- FP1 - session_key 9120 -
-
-
laps2d ago
-
stints2d ago
-
weather2d ago
-
car_data_samplesnot ingested
-
-
- - -
-
- Race - session_key 9125 - -
-
-
session_results2d ago
-
starting_grid2d ago
-
laps2d ago
-
stints2d ago
-
pit_stops2d ago
-
positions2d ago
-
race_control2d ago
-
weather2d ago
-
car_data_samplesnot ingested
-
location_samplesnot ingested
-
team_radionot ingested
-
-
- - -
-
- Ingest Commands -
-
-
# Full weekend ingest (all sessions, default datasets)
-
box-box --ingest-meeting 1230
-
-
# Race session only
-
box-box --ingest-session 9125
-
-
# High-volume telemetry (explicit, large download)
-
box-box --ingest-session 9125 --datasets car_data,location
-
-
# Preview without downloading
-
box-box --ingest-meeting 1230 --dry-run
-
-
- -
- -
- -
- -
- - - - diff --git a/documentations/refactor/screens/index.html b/documentations/refactor/screens/index.html deleted file mode 100644 index 3c9178e..0000000 --- a/documentations/refactor/screens/index.html +++ /dev/null @@ -1,180 +0,0 @@ - - - - - - box-box — Screen Index - - - - - - - - - - - diff --git a/documentations/refactor/screens/live-timing.html b/documentations/refactor/screens/live-timing.html deleted file mode 100644 index c775b47..0000000 --- a/documentations/refactor/screens/live-timing.html +++ /dev/null @@ -1,774 +0,0 @@ - - - - - - box-box — Live Timing · Monaco GP Race - - - - - - -
- - -
-
- Session - Monaco GP — Race -
-
- Lap - 45 / 78 -
-
- Clock - 1:02:34 -
-
- Track - ● GREEN -
-
- DRS - ENABLED -
-
- Air / Track - 25°C / 38°C -
-
- Fastest Lap - NOR 1:14.756 -
-
- - -
- Pinned -
- P1 -
-
- LEC -
- LEADER - 1:15.234 -
-
- P3 -
-
- NOR -
- +7.2 - FL 1:14.756 -
-
- P8 -
-
- SAI -
- PIT OUT - 1:41.678 -
- Pin drivers with P in TUI or browser shortcut -
- - -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PDriverGapIntTyreAgeLastBestS1 S2 S3
1
LEC16
LEADERM121:15.2341:14.892
2
VER1
+3.456+3.456H81:15.6231:15.023
3
NOR4
+7.234+3.778H281:15.0121:14.756
4
PIA81
+12.567+5.333H221:15.8901:15.234
5
RUS63
+18.234+5.667H141:15.4561:15.100
6
HAM44
+24.567+6.333M81:16.0121:15.567
7
ALO14
+31.234+6.667H431:16.2341:15.890
8 -
-
- SAI - 55 - PIT OUT -
-
+38.901+7.667S21:41.6781:15.456
9
GAS10
+45.234+6.333H371:16.8901:16.234
10
OCO31
+52.567+7.333H231:17.0121:16.567
11
TSU22
+1 LAPH451:17.4561:17.012
-
-
- - -
- - -
-
- Race Control - -
-
-
- L27 - DRS ENABLED — Lap 27 -
-
- L26 - SAFETY CAR IN THIS LAP -
-
- L23 - SC DEPLOYED — ALB retirement T10 -
-
- L35 - 5s PENALTY — RUS · Unsafe release -
-
- L38 - FASTEST LAP — NOR 1:14.756 -
-
- L3 - DRS ENABLED — Lap 3 -
-
- L1 - RACE START — Track Clear -
-
-
- - -
-
- Battles -
-
-
-
- NOR - vs - PIA -
- +5.3s - -
-
-
- HAM - vs - ALO -
- +6.3s - -
-
-
- SAI - vs - GAS -
- +6.4s - -
-
-
- - -
-
- Pit Window - L45 -
-
-
-
-
- LEC -
- M - L12 - SOON -
-
-
-
- VER -
- H - L8 - OPEN -
-
-
-
- NOR -
- H - L28 - OVERDUE -
-
-
-
- RUS -
- H - L14 - OPEN -
-
-
-
- SAI -
- S - L2 - DONE -
-
-
- -
-
- - - - - - - - - - - - - - -
- - - - diff --git a/documentations/refactor/screens/mobile-live.html b/documentations/refactor/screens/mobile-live.html deleted file mode 100644 index 428036f..0000000 --- a/documentations/refactor/screens/mobile-live.html +++ /dev/null @@ -1,939 +0,0 @@ - - - - - -box-box · Live Timing — Phone Layout - - - - -
- Mobile Live Layout Demo · 390px phone viewport · - ← full responsive version · - index -
- - -
- - -
-
-
Monaco GP — Race
-
Lap 47/78
-
SC
-
- - -
- - - - - SSE disconnected — data may be stale - -
- - -
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PDriverGapTyreLast
1 -
-
- LEC -
-
LEADERM1:14.812
2 -
-
- VER -
-
+3.456M1:15.203
3 -
-
- NOR -
-
+8.102S1:16.044
4 -
-
- HAM -
-
+11.723H1:15.991
5 -
-
- RUS -
-
+14.088M1:16.388
6 -
-
- PIA -
-
+18.430S1:16.701
7 -
-
- SAI -
-
+22.115H1:16.924
8 -
-
- ANT -
-
+27.660M1:17.204
9 -
-
- ALO -
-
+35.291H1:17.450
10 -
-
- GAS -
-
+42.880M1:17.692
11
OCO
+51.334H1:18.103
12
HUL
+55.771S1:18.320
13
STR
+1:04.2M1:18.890
14
TSU
+1 LAPH1:19.210
15
ZHO
DNFS
-
- - -
-
Race Control
-
    -
  • - L47 -
    - SAFETY CAR DEPLOYED — Incident at Turn 10 (Bottas, Albon) -
  • -
  • - L47 -
    - PIT LANE OPEN during Safety Car period -
  • -
  • - L44 -
    - VER — 5 SECOND PENALTY · Causing a collision with NOR at T1 -
  • -
  • - L42 -
    - DRS ENABLED — Zones 1, 2 and 3 -
  • -
  • - L41 -
    - LEC — FASTEST LAP · 1:12.456 on Lap 41 -
  • -
  • - L38 -
    - RED FLAG — Track debris at Casino corner, extraction in progress -
  • -
  • - L38 -
    - RACE SUSPENDED · All cars to proceed to pit lane -
  • -
  • - L36 -
    - Stewards investigating incident between ALO and STR — Turn 6 -
  • -
  • - L31 -
    - VIRTUAL SAFETY CAR DEPLOYED — Bottas car recovered to pit lane -
  • -
  • - L33 -
    - VIRTUAL SAFETY CAR ENDING — Racing to resume next lap -
  • -
  • - L34 -
    - DRS ENABLED — Zones 1, 2 and 3 -
  • -
  • - L28 -
    - Weather: Track Temp 44°C / Air Temp 28°C / Humidity 62% -
  • -
-
- - -
-
- - -
-
-
- LEC -
- vs -
-
- VER -
- 3.456s - ↓ closing -
- -
-
-
- HAM -
- vs -
-
- RUS -
- 2.365s - — stable -
- -
-
-
- PIA -
- vs -
-
- SAI -
- 4.315s - ↓ closing -
- -
-
-
- ALO -
- vs -
-
- GAS -
- 6.411s - ↑ opening -
-
- - -
- - -
-
-
- VER - M - +24 -
- OVERDUE -
- -
-
-
- HAM - H - +31 -
- SOON -
- -
-
-
- RUS - M - +19 -
- OPEN -
- -
-
-
- ALO - H - +38 -
- OVERDUE -
- -
-
-
- LEC - M - +12 -
- DONE -
- -
-
-
- NOR - S - +1 -
- DONE -
-
-
- -
- - -
- - - -
- -
- - - - diff --git a/documentations/refactor/screens/race-hub.html b/documentations/refactor/screens/race-hub.html deleted file mode 100644 index 7bd6558..0000000 --- a/documentations/refactor/screens/race-hub.html +++ /dev/null @@ -1,847 +0,0 @@ - - - - - - box-box — Race Hub · Monaco GP 2025 - - - - - - - -
-
- 2025 - - Monaco GP - - Race -
-
- 78 Laps · 260.286 km · 25 May 2025 - Local data -
-
- - - - - -
-
- - -
- - -
- - -
- - - - - - -
- - -
- - -
-
- Final Classification - 78 laps · 1:32:14.456 -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PDriverTeamGridΔTime / GapFastest LapPts
1 -
-
- LEC16 -
-
Ferrari3↑21:32:14.4561:14.89225
2 -
-
- VER1 -
-
Red Bull1↓1+3.4561:15.02318
3 -
-
- NOR4 -
-
McLaren4↑1+8.1231:14.756 ●15
4 -
-
- PIA81 -
-
McLaren6↑2+12.3451:15.23412
5 -
-
- RUS63 -
-
Mercedes2↓3+15.6781:15.45610
6 -
-
- HAM44 -
-
Ferrari5↓1+21.2341:15.8908
7 -
-
- ALO14 -
-
Aston Martin8↑1+28.4561:16.2346
8 -
-
- SAI55 -
-
Williams7↓1+35.7891:16.5674
9 -
-
- GAS10 -
-
Alpine9+42.1231:16.8902
10 -
-
- OCO31 -
-
Haas10+48.4561:17.0121
-
-
- - -
-
- Tyre Strategy - 78 laps · SC L23–26 · VSC L58–60 -
-
-
-
Soft
-
Medium
-
Hard
-
-
- SC / VSC period -
-
-
- - -
-
- Position Evolution - Top 6 · SC L23–26 · VSC L58–60 -
-
-
-
LEC
-
VER
-
NOR
-
PIA
-
RUS
-
HAM
-
-
- - -
-
- Race Control - 14 messages -
-
-
- L1 - Start - RACE START — Track Clear -
-
- L3 - DRS - DRS ENABLED -
-
- L18 - Incident - ALB/STR at T10 under investigation -
-
- L23 - Safety Car - SAFETY CAR DEPLOYED — Incident T10 (ALB retirement) -
-
- L26 - Safety Car - SAFETY CAR IN THIS LAP -
-
- L27 - DRS - DRS ENABLED — Lap 27 -
-
- L35 - Penalty - 5-SECOND PENALTY — RUS · Unsafe release pit lane -
-
- L38 - Fastest Lap - NOR · 1:14.756 -
-
- L58 - VSC - VIRTUAL SAFETY CAR DEPLOYED — Debris T6 -
-
- L60 - VSC - VIRTUAL SAFETY CAR ENDING -
-
- L61 - DRS - DRS ENABLED — Lap 61 -
-
- L78 - Finish - CHEQUERED FLAG — Race complete -
-
-
- - -
-
- Weather - Race · sampled every 5 laps · highlighted = SC/VSC -
-
-
LapAir °CTrack °CHumid %Wind km/hDirRain
-
122.432.1656NW0%
-
1023.134.2637NW0%
-
2023.835.6626N1%
-
2324.036.0627N1%
-
3024.336.8618NW2%
-
4024.937.4608NW2%
-
5025.238.1599W3%
-
5825.538.3598W3%
-
7025.838.6587W4%
-
7826.138.8577NW4%
-
-
- - -
-
- Dataset Status - session_key 9158 · Monaco GP Race -
-
-
session_results3h ago
-
starting_grid3h ago
-
laps3h ago
-
stints3h ago
-
pit_stops3h ago
-
positions3h ago
-
race_control3h ago
-
weather3h ago
-
overtakes3h ago
-
track_outlinecached
-
car_data_samplesnot ingested
-
location_samplesnot ingested
-
team_radionot ingested
-
-
-
# Ingest telemetry (high-volume, explicit only)
-
box-box --ingest-session 9158 --datasets car_data,location
-
-
# Refresh all datasets for this session
-
box-box --ingest-session 9158 --refresh
-
-
- -
-
- - - - -
- - - - diff --git a/documentations/refactor/screens/styles.css b/documentations/refactor/screens/styles.css deleted file mode 100644 index 267dbec..0000000 --- a/documentations/refactor/screens/styles.css +++ /dev/null @@ -1,779 +0,0 @@ -/* ================================================================ - box-box design system — F1 Ops Room - ================================================================ */ - -:root { - /* Backgrounds */ - --c-bg: #0a0a0a; - --c-surface: #111111; - --c-surface-2: #181818; - --c-surface-3: #202020; - - /* Borders */ - --c-border: #242424; - --c-border-2: #303030; - - /* Text */ - --c-text: #ebebeb; - --c-text-2: #9c9c9c; - --c-text-3: #6c6c6c; - --c-text-inv: #0a0a0a; - - /* Signal colors */ - --c-red: #e10600; - --c-yellow: #ffd600; - --c-green: #00cc6a; - --c-purple: #b06fff; - --c-blue: #4499ff; - --c-orange: #ff8833; - - /* Tyre compounds */ - --tyre-s: #e8002d; - --tyre-m: #c8b400; - --tyre-h: #b8b8b8; - --tyre-i: #39b54a; - --tyre-w: #0067ff; - - /* Team colors */ - --t-rb: #3671c6; - --t-mcl: #ff8000; - --t-fer: #e8002d; - --t-mer: #27f4d2; - --t-am: #229971; - --t-alp: #ff87bc; - --t-wil: #64c4ff; - --t-vcarb: #6692ff; - --t-haas: #b6babd; - --t-ks: #52e252; - - /* Spacing */ - --s1: 4px; - --s2: 8px; - --s3: 12px; - --s4: 16px; - --s5: 20px; - --s6: 24px; - --s8: 32px; - - /* Typography */ - --f-ui: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', sans-serif; - --f-mono: 'SF Mono', ui-monospace, 'Fira Code', 'Courier New', monospace; - - /* Density — comfortable default */ - --row-h: 34px; - --pad-v: 6px; - --pad-h: 10px; - --sec-gap: 28px; -} - -html.compact { - --row-h: 26px; - --pad-v: 3px; - --pad-h: 8px; - --sec-gap: 18px; -} - -/* ── Reset ──────────────────────────────────────────────────────── */ - -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -html { - font-size: 13px; - background: var(--c-bg); - color: var(--c-text); - scroll-behavior: smooth; -} - -body { - font-family: var(--f-ui); - line-height: 1.4; - min-height: 100vh; -} - -a { color: inherit; text-decoration: none; } -button { cursor: pointer; font-family: var(--f-ui); } - -/* ── App Nav ────────────────────────────────────────────────────── */ - -.app-nav { - display: flex; - align-items: center; - gap: var(--s2); - height: 44px; - padding: 0 var(--s5); - background: var(--c-surface); - border-bottom: 1px solid var(--c-border); - position: sticky; - top: 0; - z-index: 200; -} - -.nav-logo { - font-size: 13px; - font-weight: 700; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--c-text); - margin-right: var(--s3); -} - -.nav-logo em { - color: var(--c-red); - font-style: normal; -} - -.nav-links { - display: flex; - gap: 2px; - flex: 1; -} - -.nav-links a { - padding: 4px var(--s3); - border-radius: 3px; - font-size: 12px; - color: var(--c-text-3); - letter-spacing: 0.02em; - transition: color 0.1s, background 0.1s; -} - -.nav-links a:hover { color: var(--c-text-2); background: var(--c-surface-2); } -.nav-links a.active { color: var(--c-text); } - -.nav-right { - display: flex; - align-items: center; - gap: var(--s3); -} - -.live-badge { - display: flex; - align-items: center; - gap: 5px; - font-size: 11px; - font-weight: 600; - letter-spacing: 0.06em; - color: var(--c-red); -} - -.live-dot { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--c-red); - animation: blink 1.4s ease-in-out infinite; -} - -@keyframes blink { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.25; } -} - -.density-toggle { - display: flex; - gap: 2px; -} - -.density-toggle button { - padding: 2px 7px; - background: none; - border: 1px solid var(--c-border); - color: var(--c-text-3); - font-size: 10px; - border-radius: 2px; - letter-spacing: 0.04em; -} - -.density-toggle button.active { - background: var(--c-surface-2); - border-color: var(--c-border-2); - color: var(--c-text-2); -} - -/* ── Section Headers ────────────────────────────────────────────── */ - -.sec-header { - display: flex; - align-items: baseline; - gap: var(--s3); - padding-bottom: var(--s2); - border-bottom: 1px solid var(--c-border); - margin-bottom: var(--s4); -} - -.sec-title { - font-size: 10px; - font-weight: 700; - letter-spacing: 0.12em; - text-transform: uppercase; - color: var(--c-text-2); -} - -.sec-meta { - font-size: 11px; - color: var(--c-text-3); - font-family: var(--f-mono); -} - -/* ── Data Table ─────────────────────────────────────────────────── */ - -.data-table { - width: 100%; - border-collapse: collapse; - font-size: 12px; -} - -.data-table th { - padding: var(--s1) var(--pad-h); - text-align: left; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--c-text-3); - border-bottom: 1px solid var(--c-border); - white-space: nowrap; -} - -.data-table td { - padding: var(--pad-v) var(--pad-h); - border-bottom: 1px solid var(--c-border); - height: var(--row-h); - white-space: nowrap; -} - -.data-table tbody tr:hover { background: var(--c-surface-2); } -.data-table tbody tr:last-child td { border-bottom: none; } -.data-table .num { font-family: var(--f-mono); text-align: right; } -.data-table .center { text-align: center; } - -/* ── Driver Identity Cell ───────────────────────────────────────── */ - -.drv-cell { - display: flex; - align-items: center; - gap: var(--s2); -} - -.drv-bar { - width: 3px; - height: 20px; - border-radius: 1px; - flex-shrink: 0; -} - -.drv-code { - font-size: 13px; - font-weight: 700; - letter-spacing: 0.03em; -} - -.drv-num { - font-family: var(--f-mono); - font-size: 10px; - color: var(--c-text-3); -} - -/* ── Tyre Badge ─────────────────────────────────────────────────── */ - -.tyre { - display: inline-flex; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 50%; - font-size: 9px; - font-weight: 800; - font-family: var(--f-mono); - flex-shrink: 0; -} - -.tyre.S { background: var(--tyre-s); color: #fff; } -.tyre.M { background: var(--tyre-m); color: #000; } -.tyre.H { background: var(--tyre-h); color: #333; } -.tyre.I { background: var(--tyre-i); color: #fff; } -.tyre.W { background: var(--tyre-w); color: #fff; } - -/* ── Status Dot ─────────────────────────────────────────────────── */ - -.dot { - display: inline-block; - width: 7px; - height: 7px; - border-radius: 50%; - flex-shrink: 0; -} - -.dot.live { background: var(--c-red); } -.dot.local { background: var(--c-green); } -.dot.partial { background: var(--c-yellow); } -.dot.missing { background: var(--c-text-3); } -.dot.stale { background: var(--c-orange); } -.dot.upcoming{ background: var(--c-border-2); } - -/* ── Status Badge (inline) ──────────────────────────────────────── */ - -.badge { - display: inline-flex; - align-items: center; - gap: 4px; - padding: 2px 6px; - border-radius: 2px; - font-size: 10px; - font-weight: 600; - letter-spacing: 0.05em; - text-transform: uppercase; - white-space: nowrap; -} - -.badge.local { background: rgba(0,204,106,0.12); color: var(--c-green); } -.badge.live { background: rgba(225,6,0,0.12); color: var(--c-red); } -.badge.partial { background: rgba(255,214,0,0.12); color: var(--c-yellow); } -.badge.missing { background: rgba(85,85,85,0.12); color: var(--c-text-3); } -.badge.stale { background: rgba(255,136,51,0.12); color: var(--c-orange); } - -/* ── Source Strip ───────────────────────────────────────────────── */ - -.source-strip { - display: flex; - align-items: center; - gap: var(--s3); - padding: var(--s2) var(--s4); - background: var(--c-surface); - border: 1px solid var(--c-border); - font-size: 11px; - color: var(--c-text-2); -} - -/* ── Countdown ──────────────────────────────────────────────────── */ - -.countdown { - display: flex; - align-items: baseline; - gap: var(--s3); -} - -.cd-unit { - display: flex; - flex-direction: column; - align-items: center; - gap: 2px; -} - -.cd-num { - font-family: var(--f-mono); - font-size: 36px; - font-weight: 700; - color: var(--c-text); - line-height: 1; -} - -.cd-label { - font-size: 9px; - letter-spacing: 0.1em; - text-transform: uppercase; - color: var(--c-text-3); -} - -.cd-sep { - font-family: var(--f-mono); - font-size: 30px; - color: var(--c-border-2); - padding-bottom: 4px; -} - -/* ── Session Banner (Live Timing) ───────────────────────────────── */ - -.session-banner { - display: flex; - align-items: center; - gap: var(--s5); - height: 40px; - padding: 0 var(--s5); - background: var(--c-surface); - border-bottom: 2px solid var(--c-border); - font-family: var(--f-mono); - font-size: 12px; - overflow-x: auto; - overflow-y: hidden; - flex-shrink: 0; -} - -.banner-item { - display: flex; - align-items: center; - gap: var(--s2); - flex-shrink: 0; -} - -.banner-label { - font-size: 9px; - font-family: var(--f-ui); - letter-spacing: 0.08em; - text-transform: uppercase; - color: var(--c-text-3); -} - -.banner-val { font-weight: 600; } - -.banner-sep { color: var(--c-border-2); padding: 0 var(--s1); } - -.track-green { color: var(--c-green); } -.track-yellow { color: var(--c-yellow); } -.track-red { color: var(--c-red); } - -/* ── Race Control Messages ──────────────────────────────────────── */ - -.rc-list { display: flex; flex-direction: column; } - -.rc-msg { - display: grid; - grid-template-columns: 56px 1fr; - gap: var(--s2); - padding: 6px var(--s3); - border-bottom: 1px solid var(--c-border); - font-size: 11px; - line-height: 1.4; -} - -.rc-msg:last-child { border-bottom: none; } - -.rc-lap { font-family: var(--f-mono); color: var(--c-text-3); padding-top: 1px; } -.rc-text { color: var(--c-text-2); } - -.rc-msg.sc .rc-text { color: var(--c-yellow); font-weight: 600; } -.rc-msg.vsc .rc-text { color: var(--c-yellow); } -.rc-msg.drs .rc-text { color: var(--c-green); } -.rc-msg.flag .rc-text { color: var(--c-red); font-weight: 600; } -.rc-msg.fl .rc-text { color: var(--c-purple); } - -/* ── Position Indicators ────────────────────────────────────────── */ - -.pos-gain { color: var(--c-green); font-weight: 600; } -.pos-loss { color: var(--c-red); } -.pos-same { color: var(--c-text-3); } - -/* ── Timing Tower specific ──────────────────────────────────────── */ - -.timing-table .pos-col { width: 30px; } -.timing-table .drv-col { min-width: 120px; } -.timing-table .gap-col { width: 80px; } -.timing-table .int-col { width: 70px; } -.timing-table .tyre-col { width: 40px; text-align: center; } -.timing-table .age-col { width: 36px; } -.timing-table .last-col { width: 78px; } -.timing-table .best-col { width: 78px; } - -.timing-table tr.fastest-lap td { background: rgba(176,111,255,0.06); } -.timing-table tr.pit-in td { background: rgba(255,214,0,0.05); } -.timing-table tr.dnf td { opacity: 0.45; } - -/* ── Section Tabs ───────────────────────────────────────────────── */ - -.section-tabs { - display: flex; - gap: 2px; - padding: var(--s3) var(--s5); - border-bottom: 1px solid var(--c-border); - overflow-x: auto; -} - -.tab-btn { - padding: 4px var(--s3); - background: none; - border: 1px solid transparent; - border-radius: 2px; - font-size: 11px; - font-weight: 600; - letter-spacing: 0.06em; - text-transform: uppercase; - color: var(--c-text-3); - white-space: nowrap; - transition: color 0.1s; -} - -.tab-btn:hover { color: var(--c-text-2); } -.tab-btn.active { color: var(--c-text); border-color: var(--c-border-2); background: var(--c-surface-2); } - -.tab-panel { display: none; } -.tab-panel.active { display: block; } - -/* ── Panels / Boxes ─────────────────────────────────────────────── */ - -.panel { - background: var(--c-surface); - border: 1px solid var(--c-border); -} - -/* ── Strategy Chart Container ───────────────────────────────────── */ - -.strategy-chart { width: 100%; overflow: hidden; } -.strategy-chart svg { display: block; width: 100%; height: auto; } - -/* ── CLI Block ──────────────────────────────────────────────────── */ - -.cli-block { - background: var(--c-surface-2); - border: 1px solid var(--c-border); - border-left: 3px solid var(--c-border-2); - padding: var(--s3) var(--s4); - font-family: var(--f-mono); - font-size: 11px; - color: var(--c-text-2); - line-height: 1.8; -} - -.cli-block .comment { color: var(--c-text-3); } -.cli-block .cmd { color: var(--c-green); } - -/* ── Dataset Status Row ─────────────────────────────────────────── */ - -.dataset-row { - display: flex; - align-items: center; - gap: var(--s3); - padding: var(--pad-v) 0; - border-bottom: 1px solid var(--c-border); - font-size: 11px; -} - -.dataset-row:last-child { border-bottom: none; } - -.ds-name { flex: 1; color: var(--c-text-2); } -.ds-status { font-family: var(--f-mono); font-size: 10px; } -.ds-time { font-family: var(--f-mono); font-size: 10px; color: var(--c-text-3); min-width: 80px; text-align: right; } - -/* ── Utilities ──────────────────────────────────────────────────── */ - -.mono { font-family: var(--f-mono); } -.t2 { color: var(--c-text-2); } -.t3 { color: var(--c-text-3); } -.t-green { color: var(--c-green); } -.t-red { color: var(--c-red); } -.t-yellow { color: var(--c-yellow); } -.t-purple { color: var(--c-purple); } -.t-orange { color: var(--c-orange); } -.t-blue { color: var(--c-blue); } - -.flex { display: flex; } -.flex-col { display: flex; flex-direction: column; } -.ai-c { align-items: center; } -.gap-1 { gap: var(--s1); } -.gap-2 { gap: var(--s2); } -.gap-3 { gap: var(--s3); } -.gap-4 { gap: var(--s4); } - -.divider { height: 1px; background: var(--c-border); margin: var(--sec-gap) 0; } - -.scroll-y { overflow-y: auto; } -.scroll-x { overflow-x: auto; } - -/* ── Responsive ─────────────────────────────────────────────────── */ - -@media (max-width: 768px) { - .nav-links a { font-size: 11px; padding: 4px var(--s2); } - .hide-mobile { display: none !important; } -} - -@media (max-width: 480px) { - .nav-links { gap: 0; } - .hide-sm { display: none !important; } -} - -/* ── Podium row accents ──────────────────────────────────────────── */ - -.pos-p1 { color: #d4a93a; font-size: 15px; font-weight: 800; } -.pos-p2 { color: #a8a8a8; font-size: 14px; font-weight: 700; } -.pos-p3 { color: #b07840; font-size: 14px; font-weight: 700; } - -/* ── Race Hub 2-column body ─────────────────────────────────────── */ - -.rh-body { - display: grid; - grid-template-columns: 1fr 290px; - align-items: start; - min-height: calc(100vh - 88px); -} - -.rh-aside { - border-left: 1px solid var(--c-border); - position: sticky; - top: 44px; - max-height: calc(100vh - 44px); - overflow-y: auto; - padding: var(--s4); - display: flex; - flex-direction: column; - gap: var(--s5); -} - -@media (max-width: 980px) { - .rh-body { grid-template-columns: 1fr; } - .rh-aside { - position: static; - max-height: none; - border-left: none; - border-top: 2px solid var(--c-border); - padding: var(--s5); - } -} - -/* ── Race Summary (aside) ────────────────────────────────────────── */ - -.podium-list { display: flex; flex-direction: column; gap: 1px; } - -.podium-item { - display: grid; - grid-template-columns: 22px 1fr auto; - align-items: center; - gap: var(--s2); - padding: 6px 0; - border-bottom: 1px solid var(--c-border); -} - -.podium-item:last-child { border-bottom: none; } -.podium-p { font-family: var(--f-mono); font-size: 12px; font-weight: 700; color: var(--c-text-3); } -.podium-gap-val { font-family: var(--f-mono); font-size: 11px; color: var(--c-text-2); } - -.summary-stat-row { - display: flex; - align-items: baseline; - justify-content: space-between; - padding: 4px 0; - border-bottom: 1px solid var(--c-border); - font-size: 11px; -} - -.summary-stat-row:last-child { border-bottom: none; } -.sstat-label { color: var(--c-text-3); } -.sstat-val { font-family: var(--f-mono); color: var(--c-text-2); font-size: 11px; } - -/* ── Pit Window ─────────────────────────────────────────────────── */ - -.pit-row { - display: flex; - align-items: center; - gap: var(--s2); - padding: 5px var(--s3); - border-bottom: 1px solid var(--c-border); - font-size: 11px; -} - -.pit-row:last-child { border-bottom: none; } -.pit-driver { flex: 1; } -.pit-tyre-age { font-family: var(--f-mono); font-size: 11px; color: var(--c-text-2); min-width: 32px; text-align: right; } - -.pit-status { - font-size: 10px; - font-weight: 700; - letter-spacing: 0.04em; - min-width: 56px; - text-align: right; -} - -.pit-status.open { color: var(--c-green); } -.pit-status.soon { color: var(--c-yellow); } -.pit-status.overdue { color: var(--c-orange); } -.pit-status.done { color: var(--c-text-3); } - -/* ── Pinned Driver Cards ─────────────────────────────────────────── */ - -.pinned-strip { - display: flex; - gap: var(--s2); - padding: var(--s2) var(--s4); - background: var(--c-surface); - border-bottom: 1px solid var(--c-border); - overflow-x: auto; - flex-shrink: 0; -} - -.pin-card { - display: flex; - align-items: center; - gap: var(--s2); - padding: 5px var(--s3); - background: var(--c-surface-2); - border: 1px solid var(--c-border); - border-radius: 2px; - flex-shrink: 0; -} - -.pin-pos { font-family: var(--f-mono); font-size: 11px; color: var(--c-text-3); min-width: 16px; } -.pin-gap { font-family: var(--f-mono); font-size: 11px; color: var(--c-text-2); } - -/* ── Phone bottom tab bar ────────────────────────────────────────── */ - -.phone-tab-bar { - display: none; - position: fixed; - bottom: 0; - left: 0; - right: 0; - height: 54px; - background: var(--c-surface); - border-top: 2px solid var(--c-border); - z-index: 300; -} - -.phone-tab-bar button { - flex: 1; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 2px; - background: none; - border: none; - color: var(--c-text-3); - font-size: 9px; - font-weight: 700; - letter-spacing: 0.07em; - text-transform: uppercase; - padding-bottom: 4px; - transition: color 0.1s; -} - -.phone-tab-bar button.active { color: var(--c-text); } -.phone-tab-bar button .tab-icon { font-size: 17px; line-height: 1; } - -/* Phone panel system */ -.lt-phone-panel { } - -@media (max-width: 768px) { - .phone-tab-bar { display: flex; } - .has-phone-tabs { - padding-bottom: 58px; - overflow: hidden; - height: calc(100vh - 44px - 40px); - } - .lt-phone-panel { display: none !important; } - .lt-phone-panel.phone-active { display: flex !important; } - /* Override grid for phone */ - .lt-body-phone { display: block !important; } -} - -/* ── Responsive: iPad intermediate ─────────────────────────────── */ - -@media (min-width: 769px) and (max-width: 1024px) { - .rh-body { grid-template-columns: 1fr 240px; } -} - -/* ── Gap sparkline ──────────────────────────────────────────────── */ - -.gap-spark { - display: inline-flex; - align-items: flex-end; - gap: 1px; - height: 12px; - margin-left: 4px; - vertical-align: middle; - opacity: 0.7; -} - -.gap-spark span { - width: 3px; - background: currentColor; - border-radius: 1px; -}