fix: smooth intermission clock by preserving local anchor across renders
Snapshot the locally-computed clock state before each re-render and restore it afterwards, so the API response doesn't cause a visible jump. Only resync to the API value in the final 60 seconds, where accuracy matters. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,14 @@ function updateScoreboard(data) {
|
||||
const grid = document.getElementById(gridId);
|
||||
const hasGames = games && games.length > 0;
|
||||
section.classList.toggle('hidden', !hasGames);
|
||||
|
||||
// Snapshot current clock state before blowing away the DOM
|
||||
const clockSnapshot = snapshotClocks(grid);
|
||||
|
||||
grid.innerHTML = hasGames ? games.map(render).join('') : '';
|
||||
|
||||
// Restore smooth local anchors unless we're in the final 60s
|
||||
if (hasGames) restoreClocks(grid, clockSnapshot);
|
||||
}
|
||||
|
||||
updateGauges();
|
||||
@@ -56,7 +63,7 @@ function renderLiveGame(game) {
|
||||
</div>` : '';
|
||||
|
||||
return `
|
||||
<div class="game-box ${intermission ? 'game-box-intermission' : 'game-box-live'}">
|
||||
<div class="game-box ${intermission ? 'game-box-intermission' : 'game-box-live'}" data-game-key="${game['Away Team']}|${game['Home Team']}">
|
||||
<div class="card-header">
|
||||
<div class="badges">
|
||||
${periodLabel}
|
||||
@@ -139,6 +146,8 @@ function updateGauges() {
|
||||
});
|
||||
}
|
||||
|
||||
const CLOCK_SYNC_THRESHOLD = 60; // seconds — only resync from API in final 60s
|
||||
|
||||
// ── Clock ─────────────────────────────────────────────
|
||||
|
||||
function timeToSeconds(str) {
|
||||
@@ -154,6 +163,35 @@ function secondsToTime(s) {
|
||||
return `${String(m).padStart(2, '0')}:${String(sec).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
function snapshotClocks(grid) {
|
||||
const snapshot = new Map();
|
||||
grid.querySelectorAll('[data-game-key]').forEach(card => {
|
||||
const badge = card.querySelector('[data-seconds][data-received-at]');
|
||||
if (!badge) return;
|
||||
const seconds = parseInt(badge.dataset.seconds, 10);
|
||||
const receivedAt = parseInt(badge.dataset.receivedAt, 10);
|
||||
const elapsed = Math.floor((Date.now() - receivedAt) / 1000);
|
||||
const current = Math.max(0, seconds - elapsed);
|
||||
snapshot.set(card.dataset.gameKey, { current, ts: Date.now() });
|
||||
});
|
||||
return snapshot;
|
||||
}
|
||||
|
||||
function restoreClocks(grid, snapshot) {
|
||||
grid.querySelectorAll('[data-game-key]').forEach(card => {
|
||||
const prior = snapshot.get(card.dataset.gameKey);
|
||||
if (!prior) return;
|
||||
const badge = card.querySelector('[data-seconds][data-received-at]');
|
||||
if (!badge) return;
|
||||
// Only restore if we're outside the final sync window
|
||||
if (prior.current > CLOCK_SYNC_THRESHOLD) {
|
||||
badge.dataset.seconds = prior.current;
|
||||
badge.dataset.receivedAt = prior.ts;
|
||||
badge.textContent = secondsToTime(prior.current);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function tickClocks() {
|
||||
const now = Date.now();
|
||||
document.querySelectorAll('[data-seconds][data-received-at]').forEach(el => {
|
||||
|
||||
Reference in New Issue
Block a user