fix(#75): preserve Race Hub meeting/session on Weekend return

Add an explicit Back to Weekend link that deep-links to /?meeting_key&session_key,
restore that focus on the adaptive Weekend home with a Continue analysis CTA, and
regenerate Race Hub visual baselines against the integrated #80 nav.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-12 19:40:04 -04:00
parent 5d5987cc7e
commit 469028b07a
20 changed files with 370 additions and 37 deletions

View File

@@ -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 (
<section
className="wk-focus"
data-testid="wk-focus-context"
data-meeting-key={meetingKey ?? undefined}
data-session-key={sessionKey ?? undefined}
aria-label="Selected weekend context"
>
<div className="wk-focus-copy">
<span className="wk-focus-eyebrow mono">From Race Hub</span>
<h2 className="wk-focus-title" data-testid="wk-focus-meeting">
{meetingLabel}
</h2>
{sessionLabel && (
<p className="wk-focus-session" data-testid="wk-focus-session">
Selected session · {sessionLabel}
</p>
)}
</div>
{sessionKey != null && sessionKey > 0 && (
<Link
to="/race-hub"
search={{ session_key: sessionKey }}
className="wk-cta wk-cta-primary wk-focus-cta"
data-testid="wk-return-analysis"
>
Continue analysis <ChevronRight size={15} aria-hidden="true" />
</Link>
)}
</section>
)
}

View File

@@ -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<string, unknown>): 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<string, unknown>): 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 } : {}),
}
}

View File

@@ -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
</button>
<a href="/" className="rh-recover-btn">
Back to Command Center
</a>
<Link to="/" search={{}} className="rh-recover-btn" data-testid="rh-back-weekend">
Back to Weekend
</Link>
</div>
</div>
</div>
@@ -150,9 +151,9 @@ export function RaceHubPage({ sessionKey }: Props) {
>
Browse Weekends
</button>
<a href="/" className="rh-empty-action">
Back to Command Center
</a>
<Link to="/" search={{}} className="rh-empty-action" data-testid="rh-back-weekend">
Back to Weekend
</Link>
</div>
</div>
{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 (
<div className="rh-page" data-testid="race-hub-error" style={accentStyle}>
@@ -209,14 +206,16 @@ export function RaceHubPage({ sessionKey }: Props) {
>
Retry
</button>
<a
href={backHref}
<Link
to="/"
search={backSearch}
className="rh-recover-btn"
data-testid="rh-back-weekend"
data-meeting-key={backMeeting ?? undefined}
data-session-key={sessionKey}
>
Back to Weekend
</a>
</Link>
</div>
</div>
</div>
@@ -242,6 +241,16 @@ export function RaceHubPage({ sessionKey }: Props) {
return (
<div className="rh-page" data-testid="race-hub" style={accentStyle}>
<div className="rh-topbar">
<Link
to="/"
search={weekendFocusSearch(meetingKey, sessionKey)}
className="rh-back-weekend"
data-testid="rh-back-weekend"
data-meeting-key={meetingKey ?? undefined}
data-session-key={sessionKey}
>
Back to Weekend
</Link>
<span className="rh-topbar-label mono">
box-box · race hub
{data.meeting?.year ? ` · ${data.meeting.year}` : ''}

View File

@@ -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 && (
<WeekendFocusBanner meetingKey={focusMeetingKey} sessionKey={focusSessionKey} />
)}
{renderState(view, { context, now, championship, briefing, preview })}
</main>
)

View File

@@ -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<string, unknown>) => parseWeekendSearch(search),
component: function WeekendRoute() {
const { meeting_key, session_key } = weekendRoute.useSearch()
return (
<WeekendPage focusMeetingKey={meeting_key} focusSessionKey={session_key} />
)
},
})
export const exploreRoute = createRoute({
@@ -42,10 +47,7 @@ export const exploreRoute = createRoute({
export const raceHubRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/race-hub',
validateSearch: (search: Record<string, unknown>): RaceHubSearch => {
const sessionKey = Number(search.session_key)
return Number.isFinite(sessionKey) && sessionKey > 0 ? { session_key: sessionKey } : {}
},
validateSearch: (search: Record<string, unknown>) => parseRaceHubSearch(search),
component: function RaceHubRoute() {
const { session_key } = raceHubRoute.useSearch()
return <RaceHubPage sessionKey={session_key ?? 0} />

View File

@@ -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; }

View File

@@ -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; }

View File

@@ -243,6 +243,19 @@ function renderRaceHub(sessionKey: number) {
</QueryClientProvider>
),
})
const weekendRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
validateSearch: (search: Record<string, unknown>) => {
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: () => <div data-testid="weekend-stub" />,
})
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/))
})
})

View File

@@ -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) {
</QueryClientProvider>
),
})
const homeRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: () => <WeekendPage /> })
const homeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
validateSearch: (s: Record<string, unknown>) => parseWeekendSearch(s),
component: function HomeRoute() {
const { meeting_key, session_key } = homeRoute.useSearch()
return <WeekendPage focusMeetingKey={meeting_key} focusSessionKey={session_key} />
},
})
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)
})
})

View File

@@ -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 })
})
})

View File

@@ -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()
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 52 KiB