fix player win % and visual overhaul

This commit is contained in:
SowinskiBraeden committed 2026-09-05 19:19:46 -07:00
1 parent 1b9f9510ec
commit 5c09266d75
16 files changed
+3123 -1490

No files matched your search

+99 -39
View File
@@ -9,19 +9,19 @@
<h1 class="page-header__title">Leaderboard</h1>
<p class="page-header__sub">{{ session_count }} session{{ 's' if session_count != 1 else '' }}{% if selected_session_date %} through {{ selected_session_label }}{% endif %}</p>
</div>
<div class="leaderboard-hero-stats">
<div class="stat">
<span class="kicker">Cash paid out</span>
<div class="stat__val" style="color:var(--accent)">{{ cash_paid_out_cents | money }}</div>
</div>
<div class="stat">
<span class="kicker">Sessions</span>
<div class="stat__val" style="color:var(--text-strong)">{{ session_count }}{% if total_session_count != session_count %}<span style="color:var(--faintest);font-size:16px"> / {{ total_session_count }}</span>{% endif %}</div>
</div>
<div class="stat">
<span class="kicker">Players</span>
<div class="stat__val" style="color:var(--text-strong)">{{ all_count }}</div>
</div>
</div>
<div class="leaderboard-hero-stats">
<div class="stat">
<span class="kicker">Cash paid out</span>
<div class="stat__val" style="color:var(--accent)">{{ cash_paid_out_cents | money }}</div>
</div>
<div class="stat">
<span class="kicker">Sessions</span>
<div class="stat__val" style="color:var(--text-strong)">{{ session_count }}{% if total_session_count != session_count %}<span style="color:var(--faintest);font-size:16px"> / {{ total_session_count }}</span>{% endif %}</div>
</div>
<div class="stat">
<span class="kicker">Players</span>
<div class="stat__val" style="color:var(--text-strong)">{{ all_count }}</div>
</div>
</div>
@@ -30,10 +30,14 @@
<div>
<span class="kicker">Trend</span>
<h2 class="panel__title">Cumulative profit</h2>
<span class="panel__note" id="chartSeriesNote"></span>
</div>
<div style="display:flex;gap:6px;">
<button class="btn btn--ghost btn--sm" type="button" id="collapseLeaderboardChart">Collapse</button>
<button class="btn btn--ghost btn--sm" type="button" id="expandLeaderboardChart">Show all</button>
</div>
<button class="btn btn--ghost btn--sm" type="button" id="expandLeaderboardChart">Expand</button>
</div>
<div class="chart-wrap">
<div class="chart-wrap" id="leaderboardChartWrap">
<div class="chart-frame chart-frame--tall">
<canvas id="leaderboardChart"></canvas>
</div>
@@ -53,15 +57,6 @@
Recent <span class="seg__count">5 sess</span>
</button>
</div>
<p class="toolbar__hint">
{% if mode == 'eligible' %}
{{ eligible_min_sessions }}+ sessions required. Keeps one-off guests from skewing standings.
{% elif mode == 'all' %}
Everyone who's sat down — rankings can swing on tiny samples.
{% else %}
Form guide — aggregated over the last 5 sessions only.
{% endif %}
</p>
</div>
<div class="toolbar__right">
{% if seasons %}
@@ -90,6 +85,15 @@
</form>
</div>
</div>
<p class="toolbar__hint">
{% if mode == 'eligible' %}
{{ eligible_min_sessions }}+ sessions required. Keeps one-off guests from skewing standings.
{% elif mode == 'all' %}
Everyone who's sat down — rankings can swing on tiny samples.
{% else %}
Form guide — aggregated over the last 5 sessions only.
{% endif %}
</p>
<div class="panel tbl-scroll" style="margin-bottom:20px;">
<table class="tbl" id="leaderboardTable" data-sort-key="net" data-sort-direction="desc">
@@ -190,12 +194,17 @@
</div>
<script>
const CHART_PALETTE = ['#9b8cf0','#6fc093','#e0b15c','#cf6f86','#8f93c2','#7cb9e0','#f4a261','#a78bb0','#5cb8a0','#e58f3a'];
const CHART_PALETTE = ['#9B8FE3','#35C6B8','#5AA2F5','#E0B85C','#58D18E','#F06F7A','#B0A6EF','#8FA4BB','#C99BE0','#6FB2C9'];
const DEFAULT_SERIES = 6;
const leaderboardChartData = {{ chart_data | tojson }};
const rootStyles = getComputedStyle(document.documentElement);
const textMuted = rootStyles.getPropertyValue('--text-muted').trim();
const lineColor = rootStyles.getPropertyValue('--line').trim();
const textMuted = rootStyles.getPropertyValue('--muted').trim() || '#9AA5B4';
const gridColor = 'rgba(255,255,255,0.05)';
const axisColor = 'rgba(255,255,255,0.10)';
// Order players by activity so the most relevant get the clearest colors,
// then default the chart to the most active players to keep it readable.
leaderboardChartData.datasets.sort((a, b) => (b.sessions_played || 0) - (a.sessions_played || 0));
leaderboardChartData.datasets.forEach((ds, i) => {
ds.borderColor = CHART_PALETTE[i % CHART_PALETTE.length];
ds.backgroundColor = CHART_PALETTE[i % CHART_PALETTE.length];
@@ -208,24 +217,31 @@ function buildChartConfig(data, compact) {
labels: data.labels,
datasets: data.datasets.map(ds => ({
...ds,
pointRadius: compact ? 0 : 2,
pointHoverRadius: 5,
borderWidth: compact ? 2 : 2.5,
tension: 0.28,
pointRadius: 0,
pointHoverRadius: 4,
borderWidth: 2,
tension: 0.35,
spanGaps: true,
fill: false,
})),
},
options: {
responsive: true,
maintainAspectRatio: false,
interaction: { mode: 'nearest', intersect: false },
interaction: { mode: 'nearest', axis: 'x', intersect: false },
elements: { line: { capBezierPoints: true }, point: { hitRadius: 8 } },
plugins: {
legend: {
display: true,
position: 'bottom',
labels: { color: textMuted, usePointStyle: true, boxWidth: 8, boxHeight: 8, padding: 14 },
labels: { color: textMuted, usePointStyle: true, pointStyle: 'line', boxWidth: 18, boxHeight: 2, padding: 16, font: { size: 12 } },
},
tooltip: {
mode: 'nearest', axis: 'x', intersect: false,
backgroundColor: 'rgba(13,17,22,0.95)',
borderColor: 'rgba(255,255,255,0.12)', borderWidth: 1,
padding: 10, cornerRadius: 6, usePointStyle: true,
titleColor: '#F2F0ED', bodyColor: '#C7CED8',
callbacks: {
label(ctx) {
const v = ctx.parsed.y;
@@ -236,16 +252,19 @@ function buildChartConfig(data, compact) {
},
scales: {
x: {
ticks: { color: textMuted, maxRotation: 0, autoSkip: true, maxTicksLimit: compact ? 6 : 10 },
grid: { color: lineColor, display: !compact },
ticks: { color: textMuted, maxRotation: 0, autoSkip: true, maxTicksLimit: compact ? 6 : 10, font: { size: 11 } },
grid: { display: false },
border: { color: axisColor },
},
y: {
ticks: {
color: textMuted,
maxTicksLimit: compact ? 8 : 10,
maxTicksLimit: compact ? 6 : 9,
font: { size: 11 },
callback(v) { return (v < 0 ? '-' : '') + '$' + Math.abs(Number(v)).toFixed(0); }
},
grid: { color: lineColor },
grid: { color: gridColor, drawTicks: false },
border: { display: false },
}
}
}
@@ -255,12 +274,20 @@ function buildChartConfig(data, compact) {
const ctx = document.getElementById('leaderboardChart');
let mainChart = null;
if (ctx && leaderboardChartData.labels.length > 0) {
const filtered = leaderboardChartData.datasets.filter(ds => (ds.sessions_played || 0) > 1);
const chartData = {
labels: leaderboardChartData.labels,
datasets: filtered.length ? filtered : leaderboardChartData.datasets,
datasets: leaderboardChartData.datasets.slice(0, DEFAULT_SERIES),
};
mainChart = new Chart(ctx, buildChartConfig(chartData, true));
const note = document.getElementById('chartSeriesNote');
if (note) {
const total = leaderboardChartData.datasets.length;
const shown = chartData.datasets.length;
note.textContent = total > shown
? `Showing ${shown} most active players of ${total} — use Show all`
: `${total} player${total === 1 ? '' : 's'}`;
}
}
const modal = document.getElementById('chartModal');
@@ -286,6 +313,39 @@ document.querySelectorAll('[data-close-modal]').forEach(el => {
});
});
(function () {
const wrap = document.getElementById('leaderboardChartWrap');
const collapseBtn = document.getElementById('collapseLeaderboardChart');
const STORAGE_KEY = 'leaderboard_chart_collapsed';
let collapsed = localStorage.getItem(STORAGE_KEY) === '1';
function apply(animate) {
if (collapsed) {
if (animate) {
wrap.style.transition = 'max-height .25s ease, opacity .2s ease';
}
wrap.style.maxHeight = '0';
wrap.style.overflow = 'hidden';
wrap.style.opacity = '0';
collapseBtn.textContent = 'Expand chart';
} else {
wrap.style.transition = animate ? 'max-height .3s ease, opacity .25s ease' : '';
wrap.style.maxHeight = '600px';
wrap.style.overflow = '';
wrap.style.opacity = '1';
collapseBtn.textContent = 'Collapse';
}
}
apply(false);
collapseBtn.addEventListener('click', () => {
collapsed = !collapsed;
localStorage.setItem(STORAGE_KEY, collapsed ? '1' : '0');
apply(true);
});
})();
document.addEventListener('keydown', e => {
if (e.key === 'Escape' && modal.style.display === 'flex') {
modal.style.display = 'none';