diff --git a/site/index.html b/site/index.html index 6c016b6..f629f42 100644 --- a/site/index.html +++ b/site/index.html @@ -140,6 +140,19 @@ h1 { color: #fff; font-size: 1.5em; text-align: center; } .disconnected-overlay .retry { color: #555; font-size: 0.8em; margin-top: 15px; } body.greyed-out .container { filter: grayscale(80%) brightness(0.5); pointer-events: none; } +/* ===== STALE STATE ===== */ +.stale-banner { + position: fixed; top: 0; left: 0; right: 0; z-index: 400; + background: rgba(30, 30, 50, 0.92); border-bottom: 1px solid #444; + padding: 8px 20px; text-align: center; font-size: 0.85em; color: #aaa; + display: none; backdrop-filter: blur(2px); + animation: stale-fade-in 0.3s ease-out; +} +@keyframes stale-fade-in { from { opacity: 0; transform: translateY(-100%); } to { opacity: 1; transform: translateY(0); } } +.stale-banner .icon { margin-right: 6px; } +.stale-banner .age { color: #f39c12; font-weight: 500; } +body.stale .container { filter: brightness(0.8) saturate(0.7); } + /* ===== GAME OVER ===== */ .game-over-banner { position: fixed; top: 0; left: 0; right: 0; @@ -223,6 +236,12 @@ body.game-over .container { padding-top: 100px; } + +
+ ⏸️ + Game data hasn't updated in -- β€” the bridge may have stopped +
+ @@ -238,6 +257,7 @@ body.game-over .container { padding-top: 100px; } + @@ -267,6 +287,8 @@ let retryCountdown = 0; let retryTimer = null; let gameOverShown = false; // only trigger confetti once per game let confettiRunning = false; +let lastStateTimestamp = null; // ISO string from state.lastUpdated +const STALE_THRESHOLD_MS = 20000; function showView(view) { currentView = view; @@ -553,6 +575,37 @@ function isGameOver(state) { return false; } +// ===== STALE DETECTION ===== +function checkStale() { + if (!lastStateTimestamp || currentView === 'zero' || currentView === 'gameover') { + hideStale(); + return; + } + const age = Date.now() - new Date(lastStateTimestamp).getTime(); + if (age > STALE_THRESHOLD_MS) { + showStale(age); + } else { + hideStale(); + } +} + +function showStale(ageMs) { + const secs = Math.floor(ageMs / 1000); + let ageText; + if (secs < 60) ageText = `${secs}s`; + else if (secs < 3600) ageText = `${Math.floor(secs / 60)}m ${secs % 60}s`; + else ageText = `${Math.floor(secs / 3600)}h ${Math.floor((secs % 3600) / 60)}m`; + + document.getElementById('stale-age').textContent = ageText; + document.getElementById('stale-banner').style.display = 'block'; + document.body.classList.add('stale'); +} + +function hideStale() { + document.getElementById('stale-banner').style.display = 'none'; + document.body.classList.remove('stale'); +} + // ===== CONNECTION MONITORING ===== function showDisconnected() { if (currentView === 'disconnected') return; @@ -592,6 +645,10 @@ async function update() { if (consecutiveFailures >= 3) hideDisconnected(); consecutiveFailures = 0; + // Track last update time for stale detection + if (state.lastUpdated) lastStateTimestamp = state.lastUpdated; + checkStale(); + // Determine what to show if (!state.players || state.players.length === 0) { // No players = no game @@ -633,6 +690,7 @@ async function update() { } catch (e) { consecutiveFailures++; + checkStale(); // still update stale timer on failures if (consecutiveFailures >= 3) { showDisconnected(); } @@ -719,7 +777,9 @@ function debugState(mode) { if (mode === 'reset') { debugMode = null; lastUpdate = ''; + lastStateTimestamp = null; hideDisconnected(); + hideStale(); update(); return; } @@ -741,6 +801,14 @@ function debugState(mode) { document.getElementById('winner-name').textContent = `πŸ† ${winner} WINS!`; startConfetti(); document.getElementById('status').textContent = 'Debug: Game Over'; + } else if (mode === 'stale') { + const st = demoPlayingState(); + st.lastUpdated = new Date(Date.now() - 120000).toISOString(); // 2 min ago + lastStateTimestamp = st.lastUpdated; + showView('playing'); + renderGame(st); + checkStale(); + document.getElementById('status').textContent = 'Debug: Stale data'; } else if (mode === 'disconnected') { // Show game underneath the overlay const st = demoPlayingState();