Split The code is public; the app answers only to the crew.

The leaderboard runs off a phone: seven columns in 360px, and `wins` is the one that goes #145

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/cs:scout/141-the-leaderboard-runs-off-a-phone-seven-c → main 1 commit fork

  • Merged. scout/141-the-leaderboard-runs-off-a-phone-seven-c was merged into main as 83014ee.

Review

Sign in to review this change.
2 files changed +34 −3
src/pages/game.test.ts +14
@@ -140,6 +140,20 @@ test("a recent match's row names the map it was played on", () => {
140140 expect(html).toContain("de_forge");
141141 });
142142
143 test("the leaderboard table carries the hook and labels a phone-width card layout needs", () => {
144 const stats = openDatabase(mkdtempSync(join(tmpdir(), "game-page-")));
145 const writer = { identity: "user:u_4", name: "sable", guest: false, session: "e".repeat(32) };
146 saveMatch(stats, writer, matchResult());
147 const html = statsHtml(stats, player);
148 // table.board is what the 480px breakpoint selects; lose the class and the
149 // seven columns go back to running off a 360px screen unstyled.
150 expect(html).toContain(`<table class="board">`);
151 // Each stat cell repeats its header as data-label, so a narrow layout that
152 // drops the table's columns down to one per line still says what each
153 // number means instead of leaving it a bare digit.
154 for (const label of ["matches", "kills", "K/D", "HS", "wins"]) expect(html).toContain(`data-label="${label}"`);
155 });
156
143157 test("a crosshair field can only offer the numbers the parser would keep", () => {
144158 const panel = element(gamePage(db, options), "menu");
145159 for (const [field, limit] of Object.entries(CROSSHAIR_LIMITS)) {
src/pages/game.ts +20 −3
@@ -28,8 +28,10 @@ export function statsHtml(db: Database, player: Player): string {
2828 const tag = (identity: string): string => (isGuest(identity) ? ` <span class="tag">guest</span>` : "");
2929 const rows = table
3030 .map(
31 (s, i) => `<tr><td>${i + 1}</td><td>${escape(s.name)}${tag(s.identity)}</td><td>${s.matches}</td><td>${s.kills}</td>` +
32 `<td>${ratio(s.kills, s.deaths)}</td><td>${headshotShare(s.headshots, s.kills)}</td><td>${s.wins}</td></tr>`,
31 (s, i) => `<tr><td>${i + 1}</td><td>${escape(s.name)}${tag(s.identity)}</td>` +
32 `<td data-label="matches">${s.matches}</td><td data-label="kills">${s.kills}</td>` +
33 `<td data-label="K/D">${ratio(s.kills, s.deaths)}</td><td data-label="HS">${headshotShare(s.headshots, s.kills)}</td>` +
34 `<td data-label="wins">${s.wins}</td></tr>`,
3335 )
3436 .join("");
3537 const status = (m: MatchRow): string =>
@@ -48,7 +50,7 @@ export function statsHtml(db: Database, player: Player): string {
4850 : `Signed in as <b>${escape(player.name)}</b> — your matches land under that name.`}</p>
4951 ${table.length === 0
5052 ? `<p class="empty">No matches yet. Play one and it lands here.</p>`
51 : `<table><thead><tr><th>#</th><th>player</th><th>matches</th><th>kills</th><th>K/D</th><th>HS</th><th>wins</th></tr></thead><tbody>${rows}</tbody></table>`}
53 : `<table class="board"><thead><tr><th>#</th><th>player</th><th>matches</th><th>kills</th><th>K/D</th><th>HS</th><th>wins</th></tr></thead><tbody>${rows}</tbody></table>`}
5254 <h3>Recent matches</h3>
5355 <ul class="recent">${feed || `<li class="empty">nothing yet</li>`}</ul>`;
5456 }
@@ -224,6 +226,21 @@ kbd { background:#1b2230; border:1px solid var(--line); border-radius:.25rem; pa
224226 footer { color:var(--dim); font-size:.8rem; padding:1rem; text-align:center; }
225227 code { background:#141821; padding:.1rem .35rem; border-radius:.25rem; }
226228 @media (max-width: 640px) { #stage { aspect-ratio:4/3; } h1 { font-size:1.2rem; } }
229 /* Seven columns of numbers do not fit a phone's width at any font size that
230 stays readable, so under ~480px each row becomes a card: rank and name on
231 their own line, the five stats below as wrapped "label: value" pairs from
232 data-label, which the desktop table ignores entirely. */
233 @media (max-width: 480px) {
234 table.board, table.board tbody { display:block; width:100%; }
235 table.board thead { display:none; }
236 table.board tr { display:flex; flex-wrap:wrap; align-items:baseline; column-gap:.6rem;
237 padding:.5rem 0; border-bottom:1px solid var(--line); }
238 table.board td { border:none; padding:0; }
239 table.board td:nth-child(1) { color:var(--dim); font-variant-numeric:tabular-nums; }
240 table.board td:nth-child(2) { flex-basis:100%; font-weight:600; }
241 table.board td[data-label] { font-size:.82rem; }
242 table.board td[data-label]::before { content: attr(data-label) ": "; color:var(--dim); }
243 }
227244 `;
228245
229246 export type PageOptions = {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs