import { Link } from '@tanstack/react-router' import { Activity, ArrowRight, Play } from 'lucide-react' import { sessionTypeAbbrev } from '../lib/coverage' import { countryDecal, countryFlag, formatGpDateRange } from '../lib/gpIdentity' import { classifySessionStatus, type HeroStateKind } from '../lib/hero' import { sortLiveTimingRows, trackStatusInfo } from '../lib/live' import { formatCountdown, formatSessionScheduleTime, meetingStartTime, sessionStartTime, type FocusMeetingKind, } from '../lib/schedule' import type { EnrichedResult, LiveStreamData, Meeting, Session, WeekendSession } from '../types' import '../styles/hero.css' export interface CommandCenterHeroProps { state: HeroStateKind now: Date accent: string liveActive: boolean liveData: LiveStreamData | null | undefined focusMeeting: Meeting focusKind: FocusMeetingKind sessions: WeekendSession[] currentSession: Session | null nextSession: Session | null analysisSessionKey?: number analysisSessionName?: string lastRaceName: string lastRacePodium: EnrichedResult[] lastRaceSessionKey?: number nextMeeting: Meeting | null } export function CommandCenterHero({ state, now, accent, liveActive, liveData, focusMeeting, focusKind, sessions, currentSession, nextSession, analysisSessionKey, analysisSessionName, lastRaceName, lastRacePodium, lastRaceSessionKey, nextMeeting, }: CommandCenterHeroProps) { const accentStyle = { '--gp-accent': accent, '--hero-accent': accent, } as React.CSSProperties return (
) } function LiveHero({ liveActive, liveData, currentSession, focusMeeting, }: { liveActive: boolean liveData: LiveStreamData | null | undefined currentSession: Session | null focusMeeting: Meeting }) { const sessionName = liveData?.Session?.SessionName ?? currentSession?.session_name ?? 'Live session' const trackStatus = trackStatusInfo(liveData?.TrackStatus) const topThree = sortLiveTimingRows(liveData).filter((r) => r.Position > 0).slice(0, 3) const decal = countryDecal(focusMeeting) return ( <>
{decal}
● Live now

{sessionName}

{[focusMeeting.meeting_name, focusMeeting.circuit_short_name].filter(Boolean).join(' · ')}
{trackStatus.label}
{topThree.length > 0 && (
{topThree.map((row) => (
P{row.Position} {row.Info?.Tla ?? row.RacingNumber} {row.Position === 1 ? 'LEAD' : row.Driver.Interval || row.Driver.GapToLeader || '—'}
))}
)}
{liveActive ? 'SignalR' : 'On track'}
LIVE
{sessionName}
Open Live Timing
) } function UpcomingHero({ now, focusMeeting, focusKind, sessions, currentSession, nextSession, liveActive, analysisSessionKey, analysisSessionName, }: { now: Date focusMeeting: Meeting focusKind: FocusMeetingKind sessions: WeekendSession[] currentSession: Session | null nextSession: Session | null liveActive: boolean analysisSessionKey?: number analysisSessionName?: string }) { const decal = countryDecal(focusMeeting) const countdownTarget = nextSession && sessionStartTime(nextSession) ? sessionStartTime(nextSession)! : meetingStartTime(focusMeeting) const kindLabel = focusKind === 'current' ? 'Current weekend' : focusKind === 'next' ? 'Next weekend' : 'Weekend' const sortedSessions = [...sessions].sort((a, b) => { const left = sessionStartTime(a.session)?.getTime() ?? 0 const right = sessionStartTime(b.session)?.getTime() ?? 0 return left - right }) return ( <>
{decal}
{kindLabel}

{focusMeeting.meeting_name}

{[focusMeeting.location, focusMeeting.circuit_short_name].filter(Boolean).join(' · ')}
{formatGpDateRange(focusMeeting)}
{nextSession ? ( <>
Next · {nextSession.session_name}
{countdownTarget && (
{formatCountdown(countdownTarget, now)}
)}
{formatSessionScheduleTime(nextSession.date_start)}
) : ( <>
Status
Complete
Weekend finished
)}
{sortedSessions.length > 0 && (
{sortedSessions.map(({ session }) => { const status = classifySessionStatus(session, now) const isNext = nextSession?.session_key === session.session_key const isCurrent = currentSession?.session_key === session.session_key const isLive = isCurrent && liveActive return (
{sessionTypeAbbrev(session.session_type, session.session_name)}
{session.session_name}
{formatSessionScheduleTime(session.date_start)}
{isCurrent ? 'On track' : status === 'done' ? 'Done' : isNext ? 'Next' : 'Upcoming'}
) })}
)}
Watch Live {analysisSessionKey != null && ( Open Analysis {analysisSessionName ? ` · ${analysisSessionName}` : ''} )}
) } function BetweenHero({ now, lastRaceName, lastRacePodium, lastRaceSessionKey, nextMeeting, analysisSessionKey, analysisSessionName, }: { now: Date lastRaceName: string lastRacePodium: EnrichedResult[] lastRaceSessionKey?: number nextMeeting: Meeting | null analysisSessionKey?: number analysisSessionName?: string }) { const nextStart = nextMeeting ? meetingStartTime(nextMeeting) : null const podium = lastRacePodium .filter((r) => r.position >= 1 && r.position <= 3) .sort((a, b) => a.position - b.position) return ( <>
Between race weekends
{lastRaceName && (

After {lastRaceName}

)} {!lastRaceName &&

Season pause

}
{podium.length > 0 && (
{podium.map((r) => (
P{r.position} {r.name_acronym}
))}
)} {nextMeeting && (
{countryFlag(nextMeeting) && ( )}
Next GP
{nextMeeting.meeting_name}
{nextStart && ( <>
{formatCountdown(nextStart, now)}
{formatGpDateRange(nextMeeting)}
)}
)}
{lastRaceSessionKey != null && ( View {lastRaceName || 'last race'} results → )} {analysisSessionKey != null && analysisSessionKey !== lastRaceSessionKey && ( Open Analysis{analysisSessionName ? ` · ${analysisSessionName}` : ''} )} Live Timing →
) }