Restore prominent leaderboard summary stats

This commit is contained in:
2026-10-06 19:30:39 -04:00
parent 711191c388
commit 9d31fc0580
2 changed files with 29 additions and 14 deletions
+22 -11
View File
@@ -207,13 +207,13 @@
{/if} {/if}
{#if data} {#if data}
<div class="scope-summary"> <section class="overview" aria-label={`All-time stats for ${scope}`}>
<span>{number.format(data.summary.players)} players · {number.format(data.summary.matches)} completed games · Highest round {number.format(data.summary.highestRound)}</span> <div class="record"><span>Highest round</span><strong>{number.format(data.summary.highestRound)}</strong><small>{game || map ? 'Selection record' : 'Fleet record'}</small></div>
<details class="totals"><summary>Scope totals</summary> <div><span>Total kills</span><strong>{number.format(data.summary.kills)}</strong><small>Across all players</small></div>
<p>{number.format(data.summary.kills)} total kills · {number.format(data.summary.roundsSurvived)} player rounds</p> <div><span>Total rounds</span><strong>{number.format(data.summary.roundsSurvived)}</strong><small>Player rounds</small></div>
<p>All recorded players in {scope}; player search and qualification do not change these totals.</p> <div><span>Games tracked</span><strong>{number.format(data.summary.matches)}</strong><small>Completed games</small></div>
</details> <div><span>Players</span><strong>{number.format(data.summary.players)}</strong><small>Unique players</small></div>
</div> </section>
<section class="rankings" aria-labelledby="rankings-title" aria-busy={loading || changingFilters}> <section class="rankings" aria-labelledby="rankings-title" aria-busy={loading || changingFilters}>
<div class="rankings-header"> <div class="rankings-header">
@@ -298,7 +298,7 @@
<summary>How these records work</summary> <summary>How these records work</summary>
<div> <div>
<p>Each GUID has one leaderboard entry across Black Ops I and Black Ops II, so name changes keep a player's records together. The latest reported name across both games appears here and on the player's profile, regardless of filters.</p> <p>Each GUID has one leaderboard entry across Black Ops I and Black Ops II, so name changes keep a player's records together. The latest reported name across both games appears here and on the player's profile, regardless of filters.</p>
<p>Only completed games contribute to records. Games in progress and interrupted games stay out of totals and rankings. Players who left a completed game keep their recorded stats. Game and map filters choose which matches contribute; player search preserves ranks. Player totals count each GUID once.</p> <p>Only completed games contribute to records. Games in progress and interrupted games stay out of totals and rankings. Players who left a completed game keep their recorded stats. Game and map filters choose which matches contribute; player search preserves ranks. Player totals count each GUID once. The top stats cover all recorded players in the selected game and map; player search and ranking qualification do not change those totals.</p>
<p>Highest round includes mid-game joins. Total rounds counts round progression during observed participation, excluding earlier rounds. Joining at round 8 records a highest round of 8; reaching round 9 adds one to total rounds. The fleet total adds all players’ rounds.</p> <p>Highest round includes mid-game joins. Total rounds counts round progression during observed participation, excluding earlier rounds. Joining at round 8 records a highest round of 8; reaching round 9 adds one to total rounds. The fleet total adds all players’ rounds.</p>
<p>Kills, headshots, revives, and downs accumulate across recorded games. Highest reported score is the largest observed points balance, not the total points a player has earned. Equal values share a rank.</p> <p>Kills, headshots, revives, and downs accumulate across recorded games. Highest reported score is the largest observed points balance, not the total points a player has earned. Equal values share a rank.</p>
<p>Records cover telemetry saved since tracking began; older unrecorded games cannot be included.{#if data?.summary.firstTrackedAt}{' '}Tracking began <time datetime={data.summary.firstTrackedAt}>{$timeFormat.dateTime(data.summary.firstTrackedAt)}</time> for this selection.{/if}{#if data?.summary.updatedAt}{' '}Latest report: <time datetime={data.summary.updatedAt}>{$timeFormat.dateTime(data.summary.updatedAt)}</time>.{/if}</p> <p>Records cover telemetry saved since tracking began; older unrecorded games cannot be included.{#if data?.summary.firstTrackedAt}{' '}Tracking began <time datetime={data.summary.firstTrackedAt}>{$timeFormat.dateTime(data.summary.firstTrackedAt)}</time> for this selection.{/if}{#if data?.summary.updatedAt}{' '}Latest report: <time datetime={data.summary.updatedAt}>{$timeFormat.dateTime(data.summary.updatedAt)}</time>.{/if}</p>
@@ -307,8 +307,13 @@
</section> </section>
<style> <style>
.scope-summary { color: var(--text-secondary); font-size: var(--text-sm); } .overview { display: grid; grid-template-columns: 1.2fr repeat(4, minmax(0, 1fr)); border: 1px solid var(--border-subtle); border-radius: var(--radius-lg); background: var(--surface); overflow: hidden; }
.totals p { margin: 4px 0; font-size: var(--text-xs); } .overview > div { display: flex; flex-direction: column; justify-content: center; gap: var(--space-2); padding: 24px; border-right: 1px solid var(--border-subtle); min-width: 0; }
.overview > div:last-child { border-right: 0; }
.overview span, .overview small { color: var(--text-secondary); font-size: var(--text-xs); }
.overview strong { font-size: clamp(28px, 3vw, 40px); line-height: 1.15; font-weight: var(--weight-semibold); font-variant-numeric: tabular-nums; letter-spacing: -.035em; overflow-wrap: anywhere; }
.overview .record { background: linear-gradient(125deg, color-mix(in srgb, var(--accent-primary) 16%, transparent), transparent); }
.record strong { color: var(--accent-primary-hover); font-size: clamp(36px, 4vw, 52px); }
.metric-help, .record-details { color: var(--text-secondary); font: normal var(--text-xs) var(--font-body); margin-top: 4px; white-space: normal; } .metric-help, .record-details { color: var(--text-secondary); font: normal var(--text-xs) var(--font-body); margin-top: 4px; white-space: normal; }
summary { cursor: pointer; } summary { cursor: pointer; }
.metric-help p { margin-top: 8px; } .metric-help p { margin-top: 8px; }
@@ -319,7 +324,6 @@
.player-details dt { color: var(--text-secondary); font-size: var(--text-xs); } .player-details dt { color: var(--text-secondary); font-size: var(--text-xs); }
.player-details dd { margin: 4px 0 0; font-variant-numeric: tabular-nums; } .player-details dd { margin: 4px 0 0; font-variant-numeric: tabular-nums; }
.text-button:focus-visible, summary:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 3px; } .text-button:focus-visible, summary:focus-visible { outline: 2px solid var(--accent-primary); outline-offset: 3px; }
.totals summary { cursor: pointer; padding: 10px 0; font-size: var(--text-sm); color: var(--text-secondary); }
.page { display: flex; flex-direction: column; gap: var(--space-5); } .page { display: flex; flex-direction: column; gap: var(--space-5); }
.filters { display: grid; grid-template-columns: minmax(140px, .8fr) minmax(180px, 1fr) minmax(180px, 1fr) minmax(230px, 1.25fr); gap: var(--space-4); } .filters { display: grid; grid-template-columns: minmax(140px, .8fr) minmax(180px, 1fr) minmax(180px, 1fr) minmax(230px, 1.25fr); gap: var(--space-4); }
.field, .search { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; } .field, .search { display: flex; flex-direction: column; gap: var(--space-2); min-width: 0; }
@@ -367,6 +371,13 @@
.tracking-notes p { margin: var(--space-3) 0 0; } .tracking-notes p { margin: var(--space-3) 0 0; }
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; } .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
@media (max-width: 1000px) { .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } } @media (max-width: 1000px) { .filters { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 800px) {
.overview { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.overview > div { padding: 20px; }
.overview .record { grid-column: 1 / -1; border-right: 0; }
.overview > div:nth-child(-n+3) { border-bottom: 1px solid var(--border-subtle); }
.overview > div:nth-child(3) { border-right: 0; }
}
@media (max-width: 640px) { @media (max-width: 640px) {
.page { gap: var(--space-4); } .page { gap: var(--space-4); }
.filters { gap: var(--space-3); } .filters { gap: var(--space-3); }
+7 -3
View File
@@ -19,7 +19,7 @@ function scope(search = '') {
function html(data = report()) { return render(Leaderboard, { props: { data: { leaderboard: data, user: null, setupRequired: false } } }).body; } function html(data = report()) { return render(Leaderboard, { props: { data: { leaderboard: data, user: null, setupRequired: false } } }).body; }
beforeEach(() => scope()); beforeEach(() => scope());
it('shows five columns, preserves recorded ranks, and collapses totals, identity and evidence', () => { it('shows prominent totals and five columns, preserving ranks and collapsed identity and evidence', () => {
const body = html(); const body = html();
const headers = body.slice(body.indexOf('<thead'), body.indexOf('</thead>')); const headers = body.slice(body.indexOf('<thead'), body.indexOf('</thead>'));
expect(headers.match(/scope="col"/g)).toHaveLength(5); expect(headers.match(/scope="col"/g)).toHaveLength(5);
@@ -27,7 +27,11 @@ it('shows five columns, preserves recorded ranks, and collapses totals, identity
expect(headers).not.toContain('Headshots'); expect(headers).not.toContain('Headshots');
expect(body).toMatch(/<span[^>]*>27<\/span>/); expect(body).toMatch(/<span[^>]*>27<\/span>/);
expect(body).toContain('26–26 of 60 players'); expect(body).toContain('26–26 of 60 players');
expect(body).toContain('473 players · 411 completed games · Highest round 49'); const overview = body.slice(body.indexOf('class="overview'), body.indexOf('class="rankings'));
expect(overview).toContain('All-time stats for All games &amp; maps');
for (const value of ['49', '112,899', '7,062', '411', '473']) expect(overview).toMatch(new RegExp(`<strong[^>]*>${value}</strong>`));
expect(overview).toContain('Fleet record');
expect(overview).not.toContain('<details');
expect(body).not.toMatch(/<details[^>]*open/); expect(body).not.toMatch(/<details[^>]*open/);
expect(body).not.toContain('Copy GUID'); expect(body).not.toContain('Every round counts'); expect(body).not.toContain('Copy GUID'); expect(body).not.toContain('Every round counts');
expect(body).toContain('Record details'); expect(body).toContain('First observed at round 8'); expect(body).toContain('Record details'); expect(body).toContain('First observed at round 8');
@@ -45,7 +49,7 @@ it('deduplicates the selected metric and preserves scope and pagination on playe
const recordBody = html(); const recordBody = html();
expect(recordBody).toContain('/matches/71?returnTo=%2Fleaderboard'); expect(recordBody).toContain('/matches/71?returnTo=%2Fleaderboard');
expect(recordBody).toContain('map%3Dt5%253Atheater'); expect(recordBody).toContain('map%3Dt5%253Atheater');
expect(recordBody).toContain('Scope totals'); expect(recordBody).not.toContain('Fleet totals'); expect(recordBody).toContain('Selection record'); expect(recordBody).not.toContain('Fleet record');
}); });
it('keeps qualification and denominator visible for efficiency rankings and missing values distinct', () => { it('keeps qualification and denominator visible for efficiency rankings and missing values distinct', () => {