From b7fea448bd94479b4a88ef137795d992b91b0aeb Mon Sep 17 00:00:00 2001 From: jedarden Date: Tue, 21 Apr 2026 17:04:02 -0400 Subject: [PATCH] =?UTF-8?q?feat(mobile):=20implement=20mobile-first=20resp?= =?UTF-8?q?onsive=20design=20per=20=C2=A716.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix nav/home breakpoints from 768px → 639px to match design system - Add leaderboard mobile card view with tap-to-expand stats and full-stats link - Add canvas wrapper aspect-ratio:1 on phone (fills full width, square viewport) - Add commentary text scroll on mobile, win-prob header stacking - Replay viewer: mobile controls, pinch-to-zoom, tap-to-play, swipe scrub, floating view-mode toggle, debug telemetry slide-up sheet (already in place) - Sandbox: desktop-required message with link already implemented Co-Authored-By: Claude Sonnet 4.6 --- web/app.html | 2 +- web/src/pages/home.ts | 7 +- web/src/pages/leaderboard.ts | 89 +++++++++++--- web/src/pages/replay.ts | 230 ++++++++++++++++++++++++++++++++++- web/src/replay-viewer.ts | 12 ++ web/src/styles/mobile.css | 34 +++++- 6 files changed, 351 insertions(+), 23 deletions(-) diff --git a/web/app.html b/web/app.html index e1614aa..e34e038 100644 --- a/web/app.html +++ b/web/app.html @@ -206,7 +206,7 @@ } /* Responsive Navigation */ - @media (max-width: 768px) { + @media (max-width: 639px) { .nav-container { height: 50px; } diff --git a/web/src/pages/home.ts b/web/src/pages/home.ts index 0ff443b..e7d7af5 100644 --- a/web/src/pages/home.ts +++ b/web/src/pages/home.ts @@ -511,11 +511,13 @@ export async function renderHomePage(): Promise { padding: 16px 0; } -/* Responsive */ -@media (max-width: 768px) { +/* Responsive — phone (<640px) */ +@media (max-width: 639px) { .home-grid { grid-template-columns: 1fr; } .home-hero h1 { font-size: 1.75rem; } .home-tagline { font-size: 1rem; } + .home-hero { padding: 20px 16px; } + .home-ctas { flex-wrap: wrap; } .home-season { flex-direction: column; gap: 10px; @@ -535,6 +537,7 @@ export async function renderHomePage(): Promise { gap: 8px; align-items: flex-start; } + .home-pl-card { width: 140px; } } `; } diff --git a/web/src/pages/leaderboard.ts b/web/src/pages/leaderboard.ts index 2c24730..159be88 100644 --- a/web/src/pages/leaderboard.ts +++ b/web/src/pages/leaderboard.ts @@ -18,7 +18,7 @@ export async function renderLeaderboardPage(): Promise { try { const data = await fetchLeaderboard(); - renderLeaderboardTable(content, data.entries, data.updated_at); + renderLeaderboard(content, data.entries, data.updated_at); } catch (error) { content.innerHTML = `
@@ -29,7 +29,7 @@ export async function renderLeaderboardPage(): Promise { } } -function renderLeaderboardTable( +function renderLeaderboard( container: HTMLElement, entries: LeaderboardEntry[], updatedAt: string @@ -47,22 +47,29 @@ function renderLeaderboardTable( container.innerHTML = `

Last updated: ${formatTimestamp(updatedAt)}

- - - - - - - - - - - - - ${entries.map(entry => renderLeaderboardRow(entry)).join('')} - -
RankBotRatingW/LWin RateStatus
+
+ + + + + + + + + + + + + ${entries.map(entry => renderLeaderboardRow(entry)).join('')} + +
RankBotRatingW/LWin RateStatus
+
+
+ ${entries.map(entry => renderMobileCard(entry)).join('')} +
`; + + initMobileCardToggles(container); } function renderLeaderboardRow(entry: LeaderboardEntry): string { @@ -87,6 +94,54 @@ function renderLeaderboardRow(entry: LeaderboardEntry): string { `; } +function renderMobileCard(entry: LeaderboardEntry): string { + const rankClass = entry.rank <= 3 ? `rank-${entry.rank}` : ''; + const statusClass = entry.health_status === 'healthy' ? 'status-healthy' : + entry.health_status === 'unhealthy' ? 'status-unhealthy' : 'status-unknown'; + const winRate = entry.win_rate.toFixed(1); + + return ` + + `; +} + +function initMobileCardToggles(container: HTMLElement): void { + container.querySelectorAll('.leaderboard-mobile-card').forEach(card => { + card.addEventListener('click', (e) => { + if ((e.target as HTMLElement).closest('a')) return; + const details = card.querySelector('.leaderboard-mobile-details'); + if (!details) return; + const expanded = details.classList.toggle('expanded'); + card.setAttribute('aria-expanded', String(expanded)); + }); + }); +} + function formatTimestamp(iso: string): string { try { return new Date(iso).toLocaleString(); diff --git a/web/src/pages/replay.ts b/web/src/pages/replay.ts index 0095629..0a05a59 100644 --- a/web/src/pages/replay.ts +++ b/web/src/pages/replay.ts @@ -33,9 +33,29 @@ function initReplayViewerWithClass(ReplayViewerClass: any, initialUrl?: string):
- +
Load a replay file to view
+ + +
+
+ + + + + T: 0/0 + +
+ +
+ + +
+ Load a replay +
+ + + +