Files
NHL-Scoreboard/app/static/script.js
josh cb712245c2
All checks were successful
CI / Lint (push) Successful in 5s
CI / Test (push) Successful in 6s
CI / Build & Push (push) Successful in 14s
fix: show shots bar during intermission
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 13:46:42 -04:00

176 lines
6.4 KiB
JavaScript

async function fetchScoreboardData() {
try {
const res = await fetch('/scoreboard');
if (!res.ok) throw new Error(res.status);
updateScoreboard(await res.json());
} catch (e) {
console.error('Failed to fetch scoreboard data:', e);
}
}
function updateScoreboard(data) {
const sections = [
{ sectionId: 'live-section', gridId: 'live-games-section', games: data.live_games, render: renderLiveGame },
{ sectionId: 'pre-section', gridId: 'pre-games-section', games: data.pre_games, render: renderPreGame },
{ sectionId: 'final-section', gridId: 'final-games-section', games: data.final_games, render: renderFinalGame },
];
for (const { sectionId, gridId, games, render } of sections) {
const section = document.getElementById(sectionId);
const grid = document.getElementById(gridId);
const hasGames = games && games.length > 0;
section.classList.toggle('hidden', !hasGames);
grid.innerHTML = hasGames ? games.map(render).join('') : '';
}
updateGauges();
}
// ── Renderers ────────────────────────────────────────
function renderLiveGame(game) {
const intermission = game['Intermission'];
const period = game['Period'];
const time = game['Time Remaining'];
const running = game['Time Running'];
const periodLabel = intermission
? `<span class="badge">${intermissionLabel(period)}</span>`
: `<span class="badge badge-live">${ordinalPeriod(period)}</span>`;
const dot = running ? `<span class="live-dot"></span>` : '';
const shots = shotsBar(game['Away Shots'], game['Home Shots']);
const hype = !intermission ? `
<div class="hype-meter">
<span class="hype-label">Hype Meter</span>
<div class="gauge-track">
<div class="gauge" data-score="${game['Priority']}"></div>
</div>
</div>` : '';
return `
<div class="game-box">
<div class="card-header">
<div class="badges">
${periodLabel}
<span class="badge">${time}</span>
</div>
${dot}
</div>
${teamRow(game, 'Away', 'live')}
${teamRow(game, 'Home', 'live')}
${shots}
${hype}
</div>`;
}
function renderPreGame(game) {
return `
<div class="game-box">
<div class="card-header">
<div class="badges">
<span class="badge">${game['Start Time']}</span>
</div>
</div>
${teamRow(game, 'Away', 'pre')}
${teamRow(game, 'Home', 'pre')}
</div>`;
}
function renderFinalGame(game) {
const labels = { REG: 'Final', OT: 'Final/OT', SO: 'Final/SO' };
const label = labels[game['Last Period Type']] ?? 'Final';
return `
<div class="game-box">
<div class="card-header">
<div class="badges">
<span class="badge badge-muted">${label}</span>
</div>
</div>
${teamRow(game, 'Away', 'final')}
${teamRow(game, 'Home', 'final')}
</div>`;
}
// ── Team Row ─────────────────────────────────────────
function teamRow(game, side, state) {
const name = game[`${side} Team`];
const logo = game[`${side} Logo`];
const score = game[`${side} Score`];
const sog = game[`${side} Shots`];
const pp = game[`${side} Power Play`];
const record = game[`${side} Record`];
const sogHtml = (state === 'final' && sog !== undefined)
? `<span class="team-sog">${sog} SOG</span>` : '';
const ppHtml = pp ? `<span class="team-pp">${pp}</span>` : '';
const right = state === 'pre'
? `<span class="team-record">${record}</span>`
: `<span class="team-score">${score}</span>`;
return `
<div class="team-row">
<img src="${logo}" alt="${name} logo" class="team-logo">
<div class="team-meta">
<span class="team-name">${name}</span>
${sogHtml}${ppHtml}
</div>
${right}
</div>`;
}
// ── Shots Bar ────────────────────────────────────────
function shotsBar(away, home) {
const total = (away || 0) + (home || 0);
const awayPct = total > 0 ? (away / total) * 100 : 50;
const homePct = total > 0 ? (home / total) * 100 : 50;
return `
<div class="shots-bar">
<div class="shots-row">
<span class="shots-num">${away}</span>
<div class="shots-track">
<div class="shots-fill shots-fill-away" style="width:${awayPct}%"></div>
<div class="shots-fill shots-fill-home" style="width:${homePct}%"></div>
</div>
<span class="shots-num">${home}</span>
</div>
<span class="shots-label">Shots on Goal</span>
</div>`;
}
// ── Gauge ────────────────────────────────────────────
function updateGauges() {
document.querySelectorAll('.gauge').forEach(el => {
const score = Math.min(700, Math.max(0, parseInt(el.dataset.score, 10)));
el.style.width = `${(score / 700) * 100}%`;
el.style.backgroundColor = score <= 300 ? '#4a90e2'
: score <= 550 ? '#f97316'
: '#ef4444';
});
}
// ── Helpers ──────────────────────────────────────────
function ordinalPeriod(period) {
return ['1st', '2nd', '3rd', 'OT'][period - 1] ?? 'SO';
}
function intermissionLabel(period) {
return ['1st Int', '2nd Int', '3rd Int'][period - 1] ?? 'Int';
}
// ── Init ─────────────────────────────────────────────
function autoRefresh() {
fetchScoreboardData();
setTimeout(autoRefresh, 5000);
}
window.addEventListener('load', autoRefresh);