diff --git a/frontend/src/components/weekend/WeekendFocusBanner.tsx b/frontend/src/components/weekend/WeekendFocusBanner.tsx new file mode 100644 index 0000000..e0212f7 --- /dev/null +++ b/frontend/src/components/weekend/WeekendFocusBanner.tsx @@ -0,0 +1,65 @@ +import { Link } from '@tanstack/react-router' +import { useQuery } from '@tanstack/react-query' +import { ChevronRight } from 'lucide-react' +import { fetchWeekend } from '../../api' +import { meetingIdentity } from '../../lib/weekendContext' + +/** + * Restores Race Hub → Weekend meeting/session context from the URL search + * contract. Deep-linkable and reload-safe — no hidden component memory. + */ +export function WeekendFocusBanner({ + meetingKey, + sessionKey, +}: { + meetingKey?: number + sessionKey?: number +}) { + const hasFocus = (meetingKey != null && meetingKey > 0) || (sessionKey != null && sessionKey > 0) + const weekendQuery = useQuery({ + queryKey: ['weekend', meetingKey], + queryFn: () => fetchWeekend(meetingKey!), + enabled: meetingKey != null && meetingKey > 0, + staleTime: 60_000, + }) + + if (!hasFocus) return null + + const weekend = weekendQuery.data + const meeting = meetingIdentity(weekend?.meeting) + const session = weekend?.sessions.find((s) => s.session.session_key === sessionKey)?.session + const meetingLabel = meeting?.meeting_name ?? (meetingKey ? `Meeting ${meetingKey}` : 'Selected weekend') + const sessionLabel = session?.session_name ?? (sessionKey ? `Session ${sessionKey}` : null) + + return ( +
+
+ From Race Hub +

+ {meetingLabel} +

+ {sessionLabel && ( +

+ Selected session · {sessionLabel} +

+ )} +
+ {sessionKey != null && sessionKey > 0 && ( + + Continue analysis
+ ) +} diff --git a/frontend/src/lib/routeSearch.ts b/frontend/src/lib/routeSearch.ts new file mode 100644 index 0000000..5e514ed --- /dev/null +++ b/frontend/src/lib/routeSearch.ts @@ -0,0 +1,40 @@ +/** Shared URL search contracts for deep-linkable Weekend ↔ Race Hub navigation. */ + +export type WeekendSearch = { + meeting_key?: number + session_key?: number +} + +export type RaceHubSearch = { + session_key?: number +} + +function parsePositiveInt(value: unknown): number | undefined { + const n = Number(value) + return Number.isFinite(n) && n > 0 ? n : undefined +} + +export function parseWeekendSearch(search: Record): WeekendSearch { + const meeting_key = parsePositiveInt(search.meeting_key) + const session_key = parsePositiveInt(search.session_key) + return { + ...(meeting_key ? { meeting_key } : {}), + ...(session_key ? { session_key } : {}), + } +} + +export function parseRaceHubSearch(search: Record): RaceHubSearch { + const session_key = parsePositiveInt(search.session_key) + return session_key ? { session_key } : {} +} + +/** Build Weekend `/` search so a Race Hub return is reload-safe and deep-linkable. */ +export function weekendFocusSearch( + meetingKey?: number | null, + sessionKey?: number | null, +): WeekendSearch { + return { + ...(meetingKey && meetingKey > 0 ? { meeting_key: meetingKey } : {}), + ...(sessionKey && sessionKey > 0 ? { session_key: sessionKey } : {}), + } +} diff --git a/frontend/src/pages/RaceHubPage.tsx b/frontend/src/pages/RaceHubPage.tsx index b59e724..c50d2ff 100644 --- a/frontend/src/pages/RaceHubPage.tsx +++ b/frontend/src/pages/RaceHubPage.tsx @@ -1,7 +1,8 @@ import { useEffect, useState } from 'react' import { useQuery } from '@tanstack/react-query' -import { useNavigate } from '@tanstack/react-router' +import { Link, useNavigate } from '@tanstack/react-router' import { fetchRaceHub, fetchWeekend, fetchWeekendContext } from '../api' +import { weekendFocusSearch } from '../lib/routeSearch' import { DatasetStrip } from '../components/DatasetStrip' import { RaceStoryCanvas } from '../components/RaceStoryCanvas' import { TabBar, type Tab } from '../components/TabBar' @@ -123,9 +124,9 @@ export function RaceHubPage({ sessionKey }: Props) { > Retry - - Back to Command Center - + + Back to Weekend + @@ -150,9 +151,9 @@ export function RaceHubPage({ sessionKey }: Props) { > Browse Weekends - - Back to Command Center - + + Back to Weekend + {switcherOpen && ( @@ -183,14 +184,10 @@ export function RaceHubPage({ sessionKey }: Props) { ) } if (raceHubQuery.isError || !data) { - const backMeeting = context?.focus_meeting?.meeting_key - const backSession = - defaultAnalysisSessionKey(context) ?? - context?.previous_completed_session?.session.session_key - const backHref = - backSession && backSession > 0 - ? `/race-hub?session_key=${backSession}` - : '/race-hub' + const backMeeting = + context?.focus_meeting?.meeting_key ?? + context?.previous_completed_session?.meeting?.meeting_key + const backSearch = weekendFocusSearch(backMeeting, sessionKey) return (
@@ -209,14 +206,16 @@ export function RaceHubPage({ sessionKey }: Props) { > Retry - Back to Weekend - +
@@ -242,6 +241,16 @@ export function RaceHubPage({ sessionKey }: Props) { return (
+ + Back to Weekend + box-box · race hub {data.meeting?.year ? ` · ${data.meeting.year}` : ''} diff --git a/frontend/src/pages/WeekendPage.tsx b/frontend/src/pages/WeekendPage.tsx index 1ae64a2..57c99fc 100644 --- a/frontend/src/pages/WeekendPage.tsx +++ b/frontend/src/pages/WeekendPage.tsx @@ -4,6 +4,7 @@ import { BetweenSessionsView } from '../components/weekend/BetweenSessionsView' import { LiveHandoffView } from '../components/weekend/LiveHandoffView' import { PreSessionView } from '../components/weekend/PreSessionView' import { WeekendError, WeekendLimited, WeekendLoading } from '../components/weekend/StatusViews' +import { WeekendFocusBanner } from '../components/weekend/WeekendFocusBanner' import { resolveViewState } from '../lib/weekendContext' import type { WeekendBriefingItem, @@ -59,8 +60,21 @@ function renderState(view: WeekendViewState, args: RenderArgs) { } } -export function WeekendPage({ preview = false }: { preview?: boolean }) { +export function WeekendPage({ + preview = false, + focusMeetingKey, + focusSessionKey, +}: { + preview?: boolean + /** Restored from `/?meeting_key=` when returning from Race Hub analysis. */ + focusMeetingKey?: number + /** Restored from `/?session_key=` when returning from Race Hub analysis. */ + focusSessionKey?: number +}) { const { context, loadState, error, championship, briefing, now } = useWeekendContext() + const hasFocus = + (focusMeetingKey != null && focusMeetingKey > 0) || + (focusSessionKey != null && focusSessionKey > 0) if (loadState === 'loading') { return ( @@ -87,7 +101,12 @@ export function WeekendPage({ preview = false }: { preview?: boolean }) { data-state={view} data-temporal-state={context.temporal_state} data-preview={preview ? 'true' : undefined} + data-meeting-key={focusMeetingKey ?? undefined} + data-session-key={focusSessionKey ?? undefined} > + {hasFocus && ( + + )} {renderState(view, { context, now, championship, briefing, preview })} ) diff --git a/frontend/src/router.tsx b/frontend/src/router.tsx index d38430e..fd8bac5 100644 --- a/frontend/src/router.tsx +++ b/frontend/src/router.tsx @@ -8,10 +8,7 @@ import { LiveTimingPage } from './pages/LiveTimingPage' import { BriefingPage } from './pages/BriefingPage' import { ChampionshipPage } from './pages/ChampionshipPage' import { DriverProfilePage } from './pages/DriverProfilePage' - -type RaceHubSearch = { - session_key?: number -} +import { parseRaceHubSearch, parseWeekendSearch } from './lib/routeSearch' type DriverProfileSearch = { year?: number @@ -27,10 +24,18 @@ const rootRoute = createRootRoute({ }) // Weekend is the adaptive home. `/` renders the current temporal state. +// Optional meeting_key/session_key search restores Race Hub return context +// (deep-linkable, reload-safe — no hidden component memory). export const weekendRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', - component: WeekendPage, + validateSearch: (search: Record) => parseWeekendSearch(search), + component: function WeekendRoute() { + const { meeting_key, session_key } = weekendRoute.useSearch() + return ( + + ) + }, }) export const exploreRoute = createRoute({ @@ -42,10 +47,7 @@ export const exploreRoute = createRoute({ export const raceHubRoute = createRoute({ getParentRoute: () => rootRoute, path: '/race-hub', - validateSearch: (search: Record): RaceHubSearch => { - const sessionKey = Number(search.session_key) - return Number.isFinite(sessionKey) && sessionKey > 0 ? { session_key: sessionKey } : {} - }, + validateSearch: (search: Record) => parseRaceHubSearch(search), component: function RaceHubRoute() { const { session_key } = raceHubRoute.useSearch() return diff --git a/frontend/src/styles/app.css b/frontend/src/styles/app.css index d8a90a0..4d41ee6 100644 --- a/frontend/src/styles/app.css +++ b/frontend/src/styles/app.css @@ -2826,6 +2826,31 @@ a { color: inherit; text-decoration: none; } .rh-topbar-spacer { flex: 1; } +.rh-back-weekend { + flex-shrink: 0; + padding: 4px 10px; + font-size: 10px; + font-weight: 700; + font-family: var(--f-mono); + letter-spacing: 0.08em; + text-transform: uppercase; + color: var(--text-2); + background: var(--surface); + border: 1px solid var(--border); + border-radius: 2px; + transition: color 0.1s, border-color 0.1s, background 0.1s; +} +.rh-back-weekend:hover, +.rh-back-weekend:focus-visible { + color: var(--text); + border-color: var(--gp-accent); + background: var(--surface-h); + outline: none; +} +.rh-back-weekend:focus-visible { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--gp-accent) 45%, transparent); +} + .rh-switcher-toggle { padding: 4px 10px; font-size: 10px; @@ -3401,7 +3426,10 @@ a { color: inherit; text-decoration: none; } .rh-overview-row { grid-template-columns: 1fr; } } @media (max-width: 640px) { - .rh-page { padding: var(--s4); gap: var(--s4); } + .rh-page { padding: var(--s4); gap: var(--s4); padding-bottom: 88px; } + .rh-topbar { flex-wrap: wrap; gap: var(--s3); } + .rh-topbar-spacer { display: none; } + .rh-back-weekend { order: -1; } .rh-identity-body { padding: var(--s3) var(--s4); gap: var(--s3); } .rh-identity-decal { font-size: 28px; } .rh-identity-name { font-size: 17px; } diff --git a/frontend/src/styles/weekend.css b/frontend/src/styles/weekend.css index 2f03a9b..9414d29 100644 --- a/frontend/src/styles/weekend.css +++ b/frontend/src/styles/weekend.css @@ -6,6 +6,36 @@ width: 100%; } +.wk-focus { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--s5); + margin-bottom: var(--s5); + padding: var(--s5); + background: var(--surface); + border: 1px solid var(--border); + border-left: 3px solid var(--red); + border-radius: 8px; +} +.wk-focus-copy { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; } +.wk-focus-eyebrow { + font-size: 10px; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--text-3); +} +.wk-focus-title { + font-size: 18px; + font-weight: 700; + line-height: 1.2; +} +.wk-focus-session { + font-size: 13px; + color: var(--text-2); +} +.wk-focus-cta { flex-shrink: 0; } + .wk-eyebrow { display: flex; align-items: center; @@ -313,6 +343,11 @@ } @media (max-width: 640px) { .wk-page { padding: var(--s5) var(--s5) 88px; } + .wk-focus { + flex-direction: column; + align-items: stretch; + } + .wk-focus-cta { width: 100%; justify-content: center; } .wk-event-name { font-size: 20px; } .wk-sessions-title { font-size: 22px; } .wk-count-unit b { font-size: 32px; } diff --git a/frontend/src/test/RaceHubPage.test.tsx b/frontend/src/test/RaceHubPage.test.tsx index 60dbe5b..4bd998b 100644 --- a/frontend/src/test/RaceHubPage.test.tsx +++ b/frontend/src/test/RaceHubPage.test.tsx @@ -243,6 +243,19 @@ function renderRaceHub(sessionKey: number) { ), }) + const weekendRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + validateSearch: (search: Record) => { + const meetingKey = Number(search.meeting_key) + const sk = Number(search.session_key) + return { + ...(Number.isFinite(meetingKey) && meetingKey > 0 ? { meeting_key: meetingKey } : {}), + ...(Number.isFinite(sk) && sk > 0 ? { session_key: sk } : {}), + } + }, + component: () =>
, + }) const raceHubRoute = createRoute({ getParentRoute: () => rootRoute, path: '/race-hub', @@ -256,7 +269,7 @@ function renderRaceHub(sessionKey: number) { }, }) const router = createRouter({ - routeTree: rootRoute.addChildren([raceHubRoute]), + routeTree: rootRoute.addChildren([weekendRoute, raceHubRoute]), history: undefined, }) @@ -541,10 +554,22 @@ describe('RaceHubPage', () => { await waitFor(() => expect(screen.getByTestId('race-hub-error')).toBeInTheDocument()) expect(screen.getByTestId('rh-retry')).toBeInTheDocument() const back = screen.getByTestId('rh-back-weekend') - expect(back).toHaveAttribute('href', '/race-hub?session_key=9472') + expect(back).toHaveAttribute('href', expect.stringMatching(/meeting_key=1229/)) + expect(back).toHaveAttribute('href', expect.stringMatching(/session_key=9472/)) + expect(back.getAttribute('href')).toMatch(/^\//) mockFetchRaceHub.mockResolvedValue(raceHub) fireEvent.click(screen.getByTestId('rh-retry')) await waitFor(() => expect(screen.getByTestId('race-hub')).toBeInTheDocument()) }) + + it('exposes a Back to Weekend link that carries meeting and session context', async () => { + renderRaceHub(9472) + + await waitFor(() => expect(screen.getByTestId('race-hub')).toBeInTheDocument()) + const back = screen.getByRole('link', { name: 'Back to Weekend' }) + expect(back).toHaveAttribute('data-testid', 'rh-back-weekend') + expect(back).toHaveAttribute('href', expect.stringMatching(/meeting_key=1229/)) + expect(back).toHaveAttribute('href', expect.stringMatching(/session_key=9472/)) + }) }) diff --git a/frontend/src/test/WeekendPage.test.tsx b/frontend/src/test/WeekendPage.test.tsx index f0e0515..3a3f229 100644 --- a/frontend/src/test/WeekendPage.test.tsx +++ b/frontend/src/test/WeekendPage.test.tsx @@ -20,12 +20,14 @@ import type { TemporalState, WeekendContext, } from '../types' +import { parseWeekendSearch } from '../lib/routeSearch' vi.mock('../api', () => ({ fetchWeekendContext: vi.fn(), fetchChampionshipHub: vi.fn(), fetchNews: vi.fn(), fetchRaceHub: vi.fn(), + fetchWeekend: vi.fn(), // Consumed transitively by RacePreviewPage (folded into PreSessionView): fetchSeasons: vi.fn(), fetchMeetings: vi.fn(), @@ -40,6 +42,7 @@ import { fetchChampionshipHub, fetchNews, fetchRaceHub, + fetchWeekend, fetchSeasons, fetchMeetings, fetchSessions, @@ -52,6 +55,7 @@ const mockContext = vi.mocked(fetchWeekendContext) const mockHub = vi.mocked(fetchChampionshipHub) const mockNews = vi.mocked(fetchNews) const mockRaceHub = vi.mocked(fetchRaceHub) +const mockWeekend = vi.mocked(fetchWeekend) const mockSeasons = vi.mocked(fetchSeasons) const mockMeetings = vi.mocked(fetchMeetings) const mockSessions = vi.mocked(fetchSessions) @@ -152,7 +156,15 @@ function renderAt(path: string) { ), }) - const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: () => }) + const homeRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + validateSearch: (s: Record) => parseWeekendSearch(s), + component: function HomeRoute() { + const { meeting_key, session_key } = homeRoute.useSearch() + return + }, + }) const previewRoute = createRoute({ getParentRoute: () => rootRoute, path: '/preview', @@ -192,6 +204,19 @@ describe('WeekendPage canonical contract rendering', () => { mockHub.mockResolvedValue(hub) mockNews.mockResolvedValue([]) mockRaceHub.mockResolvedValue(raceHub) + mockWeekend.mockResolvedValue({ + source: 'local', + meeting_key: 1, + meeting: meeting(), + sessions: [ + { + session: session({ session_key: 11, session_name: 'Race' }), + source: 'local', + datasets: {}, + }, + ], + default_session_key: 11, + }) mockSeasons.mockResolvedValue([2026]) mockMeetings.mockResolvedValue([meeting()]) mockSessions.mockResolvedValue([session()]) @@ -306,4 +331,27 @@ describe('WeekendPage canonical contract rendering', () => { // The between-races surface must NOT be what /preview renders. expect(screen.queryByTestId('weekend-between-races')).not.toBeInTheDocument() }) + + it('restores Race Hub meeting/session focus from the Weekend URL search contract', async () => { + mockContext.mockResolvedValue( + context({ + temporal_state: 'season_complete', + previous_completed_session: ctxSession({ session: session({ session_key: 11, session_name: 'Race' }) }), + default_analysis_session: ctxSession({ session: session({ session_key: 11 }) }), + }), + ) + renderAt('/?meeting_key=1&session_key=11') + await waitFor(() => expect(screen.getByTestId('wk-focus-context')).toBeInTheDocument()) + expect(screen.getByTestId('weekend-page')).toHaveAttribute('data-meeting-key', '1') + expect(screen.getByTestId('weekend-page')).toHaveAttribute('data-session-key', '11') + await waitFor(() => + expect(screen.getByTestId('wk-focus-meeting')).toHaveTextContent('British Grand Prix'), + ) + await waitFor(() => + expect(screen.getByTestId('wk-focus-session')).toHaveTextContent('Race'), + ) + const continueAnalysis = screen.getByRole('link', { name: /Continue analysis/i }) + expect(continueAnalysis).toHaveAttribute('href', expect.stringContaining('session_key=11')) + expect(mockWeekend).toHaveBeenCalledWith(1) + }) }) diff --git a/frontend/src/test/routeSearch.test.ts b/frontend/src/test/routeSearch.test.ts new file mode 100644 index 0000000..0bae53c --- /dev/null +++ b/frontend/src/test/routeSearch.test.ts @@ -0,0 +1,24 @@ +import { describe, it, expect } from 'vitest' +import { parseRaceHubSearch, parseWeekendSearch, weekendFocusSearch } from '../lib/routeSearch' + +describe('routeSearch', () => { + it('parses positive weekend meeting/session keys and drops invalid values', () => { + expect(parseWeekendSearch({ meeting_key: '1229', session_key: '9000' })).toEqual({ + meeting_key: 1229, + session_key: 9000, + }) + expect(parseWeekendSearch({ meeting_key: '0', session_key: '-1', foo: 'bar' })).toEqual({}) + expect(parseWeekendSearch({})).toEqual({}) + }) + + it('parses race-hub session keys', () => { + expect(parseRaceHubSearch({ session_key: '9472' })).toEqual({ session_key: 9472 }) + expect(parseRaceHubSearch({ session_key: 'nope' })).toEqual({}) + }) + + it('builds Weekend focus search for Race Hub returns', () => { + expect(weekendFocusSearch(1229, 9000)).toEqual({ meeting_key: 1229, session_key: 9000 }) + expect(weekendFocusSearch(null, 9000)).toEqual({ session_key: 9000 }) + expect(weekendFocusSearch(1229, undefined)).toEqual({ meeting_key: 1229 }) + }) +}) diff --git a/tests/race-hub.spec.ts b/tests/race-hub.spec.ts index 62052c5..9c915c0 100644 --- a/tests/race-hub.spec.ts +++ b/tests/race-hub.spec.ts @@ -150,11 +150,49 @@ test.describe('Race Hub Weekend Workspace', () => { await expect(page).toHaveURL(new RegExp(`session_key=${CORE_ONLY_SESSION}`)) await expect(page.getByTestId('rh-identity')).toContainText('Monaco') - // Back to Weekend via bare /race-hub — Weekend Context should restore the - // same meeting's default analysis session. - await page.goto('/race-hub') - await expect(page).toHaveURL(new RegExp(`session_key=${FULL_SESSION}`)) + // Explicit Race Hub → Weekend return carries meeting/session in the URL. + const back = page.getByRole('link', { name: 'Back to Weekend' }) + await expect(back).toBeVisible() + await expect(back).toHaveAttribute('href', /meeting_key=1229/) + await expect(back).toHaveAttribute('href', /session_key=9000/) + await back.click() + + await expect(page).toHaveURL(/\/\?.*meeting_key=1229/) + await expect(page).toHaveURL(/session_key=9000/) + await expect(page.getByTestId('weekend-page')).toBeVisible() + await expect(page.getByTestId('weekend-page')).toHaveAttribute('data-meeting-key', '1229') + await expect(page.getByTestId('weekend-page')).toHaveAttribute('data-session-key', '9000') + await expect(page.getByTestId('wk-focus-context')).toBeVisible() + await expect(page.getByTestId('wk-focus-meeting')).toContainText('Monaco') + await expect(page.getByTestId('wk-focus-session')).toContainText('Core Only') + + // Contextual action returns to the exact selected analysis session. + const continueAnalysis = page.getByRole('link', { name: /Continue analysis/i }) + await expect(continueAnalysis).toHaveAttribute( + 'href', + new RegExp(`session_key=${CORE_ONLY_SESSION}`), + ) + await continueAnalysis.click() + await expect(page).toHaveURL(new RegExp(`session_key=${CORE_ONLY_SESSION}`)) + await expect(page.getByTestId('race-hub')).toBeVisible() await expect(page.getByTestId('rh-identity')).toContainText('Monaco') - await expect(page.getByTestId('rh-overview')).toBeVisible() + }) + + test('Back to Weekend is keyboard-focusable and works at mobile width', async ({ page }) => { + await page.setViewportSize({ width: 390, height: 844 }) + await page.goto(`/race-hub?session_key=${CORE_ONLY_SESSION}`) + await expect(page.getByTestId('race-hub')).toBeVisible() + + const back = page.getByRole('link', { name: 'Back to Weekend' }) + await expect(back).toBeVisible() + await back.focus() + await expect(back).toBeFocused() + await page.keyboard.press('Enter') + + await expect(page.getByTestId('weekend-page')).toBeVisible() + await expect(page.getByTestId('weekend-page')).toHaveAttribute('data-meeting-key', '1229') + await expect(page.getByTestId('weekend-page')).toHaveAttribute('data-session-key', '9000') + await expect(page.getByTestId('wk-focus-context')).toBeVisible() + await expect(page.getByRole('link', { name: /Continue analysis/i })).toBeVisible() }) }) diff --git a/tests/visual/__snapshots__/desktop/race-hub-future.png b/tests/visual/__snapshots__/desktop/race-hub-future.png index cf38847..ad4afce 100644 Binary files a/tests/visual/__snapshots__/desktop/race-hub-future.png and b/tests/visual/__snapshots__/desktop/race-hub-future.png differ diff --git a/tests/visual/__snapshots__/desktop/race-hub.png b/tests/visual/__snapshots__/desktop/race-hub.png index 6345515..b098846 100644 Binary files a/tests/visual/__snapshots__/desktop/race-hub.png and b/tests/visual/__snapshots__/desktop/race-hub.png differ diff --git a/tests/visual/__snapshots__/desktop/race-story.png b/tests/visual/__snapshots__/desktop/race-story.png index 09c0582..ae2993f 100644 Binary files a/tests/visual/__snapshots__/desktop/race-story.png and b/tests/visual/__snapshots__/desktop/race-story.png differ diff --git a/tests/visual/__snapshots__/mobile/race-hub-future.png b/tests/visual/__snapshots__/mobile/race-hub-future.png index 01787cd..20ef9e2 100644 Binary files a/tests/visual/__snapshots__/mobile/race-hub-future.png and b/tests/visual/__snapshots__/mobile/race-hub-future.png differ diff --git a/tests/visual/__snapshots__/mobile/race-hub.png b/tests/visual/__snapshots__/mobile/race-hub.png index c3ff86a..6f55abc 100644 Binary files a/tests/visual/__snapshots__/mobile/race-hub.png and b/tests/visual/__snapshots__/mobile/race-hub.png differ diff --git a/tests/visual/__snapshots__/mobile/race-story.png b/tests/visual/__snapshots__/mobile/race-story.png index 339bf9a..0b5c0c1 100644 Binary files a/tests/visual/__snapshots__/mobile/race-story.png and b/tests/visual/__snapshots__/mobile/race-story.png differ diff --git a/tests/visual/__snapshots__/tablet/race-hub-future.png b/tests/visual/__snapshots__/tablet/race-hub-future.png index f7a1d07..f6e1e86 100644 Binary files a/tests/visual/__snapshots__/tablet/race-hub-future.png and b/tests/visual/__snapshots__/tablet/race-hub-future.png differ diff --git a/tests/visual/__snapshots__/tablet/race-hub.png b/tests/visual/__snapshots__/tablet/race-hub.png index ede04d0..e03ede7 100644 Binary files a/tests/visual/__snapshots__/tablet/race-hub.png and b/tests/visual/__snapshots__/tablet/race-hub.png differ diff --git a/tests/visual/__snapshots__/tablet/race-story.png b/tests/visual/__snapshots__/tablet/race-story.png index 548a7ed..dccccb1 100644 Binary files a/tests/visual/__snapshots__/tablet/race-story.png and b/tests/visual/__snapshots__/tablet/race-story.png differ