From 3397352835d126b7883c4c7950d479e8b63a42bc Mon Sep 17 00:00:00 2001 From: AmanTahiliani Date: Sat, 8 Aug 2026 12:48:15 -0400 Subject: [PATCH] feat(command-center): balance columns and open the calendar on the current GP Move the paddock briefing under the season calendar in the main column so the sidebar (championship + data note) stops running longer than the left side of the dashboard. Open the season strip scrolled to the round in focus rather than at round one, so the relevant GP is centred on load. Re-centring repeats while the strip reflows (font load, resize) and stops as soon as the reader scrolls it themselves; the edge fade only applies when the strip actually overflows. Co-Authored-By: Claude Opus 5 --- frontend/src/pages/CommandCenterPage.tsx | 60 +++++++++++++++++- frontend/src/styles/app.css | 16 ++++- frontend/src/styles/digest.css | 1 - .../__snapshots__/desktop/command-center.png | Bin 184364 -> 192561 bytes .../__snapshots__/mobile/command-center.png | Bin 121159 -> 126156 bytes .../__snapshots__/tablet/command-center.png | Bin 157271 -> 163852 bytes 6 files changed, 72 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/CommandCenterPage.tsx b/frontend/src/pages/CommandCenterPage.tsx index af50173..337f6ee 100644 --- a/frontend/src/pages/CommandCenterPage.tsx +++ b/frontend/src/pages/CommandCenterPage.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from 'react' +import { useEffect, useMemo, useRef, useState } from 'react' import { useQueries, useQuery } from '@tanstack/react-query' import { Link } from '@tanstack/react-router' import { @@ -56,6 +56,9 @@ function pickAnalysisSession(weekend: Weekend | undefined): WeekendSession | und export function CommandCenterPage() { const [now, setNow] = useState(() => Date.now()) + const calendarScrollRef = useRef(null) + const focusCardRef = useRef(null) + const railTakenOver = useRef(false) const seasonsQuery = useQuery({ queryKey: ['seasons'], @@ -198,6 +201,56 @@ export function CommandCenterPage() { ? `${nextSession.session_name} is next on the timetable. The weekend rail keeps every session and its local analysis in reach.` : 'The weekend timetable is ready to explore.' + // Park the calendar rail on the GP in focus so it lands centred, instead of + // making the reader scroll past every round that has already happened. + const focusMeetingKey = focusMeeting?.meeting_key + useEffect(() => { + railTakenOver.current = false + }, [focusMeetingKey]) + + useEffect(() => { + const rail = calendarScrollRef.current + const card = focusCardRef.current + if (!rail || !card) return + + const sync = () => { + const overflows = rail.scrollWidth > rail.clientWidth + 1 + rail.classList.toggle('is-scrollable', overflows) + if (!overflows || railTakenOver.current) return + const railBox = rail.getBoundingClientRect() + const cardBox = card.getBoundingClientRect() + const offset = rail.scrollLeft + (cardBox.left - railBox.left) + const target = offset - (rail.clientWidth - cardBox.width) / 2 + rail.scrollLeft = Math.max(0, Math.min(target, rail.scrollWidth - rail.clientWidth)) + } + + sync() + + const takeOver = () => { + railTakenOver.current = true + } + rail.addEventListener('wheel', takeOver, { passive: true }) + rail.addEventListener('touchstart', takeOver, { passive: true }) + rail.addEventListener('pointerdown', takeOver) + rail.addEventListener('keydown', takeOver) + + // The strip reflows as fonts load and as the column width changes, so keep + // re-centring until the reader takes the rail over themselves. + let observer: ResizeObserver | undefined + if (typeof ResizeObserver !== 'undefined') { + observer = new ResizeObserver(sync) + observer.observe(rail) + } + + return () => { + observer?.disconnect() + rail.removeEventListener('wheel', takeOver) + rail.removeEventListener('touchstart', takeOver) + rail.removeEventListener('pointerdown', takeOver) + rail.removeEventListener('keydown', takeOver) + } + }, [focusMeetingKey, seasonMeetings.length]) + if (seasonsQuery.isLoading) { return
loading command center…
} @@ -317,7 +370,7 @@ export function CommandCenterPage() { {seasonMeetings.length} rounds · {meetingStats.full}/{meetingStats.total || 0} local -
+
{seasonMeetings.map((meeting, index) => { const weekend = weekendsByKey.get(meeting.meeting_key) const target = pickAnalysisSession(weekend) @@ -327,6 +380,7 @@ export function CommandCenterPage() { return ( )} +
@@ -456,7 +511,6 @@ export function CommandCenterPage() { )} -