(function () { var LIVE_IMAGE = "live/live.png"; var LIVE_DATA = "live/live.json"; var REFRESH_MS = 60 * 1000; var TICK_MS = 1000; var img = document.getElementById("live-feed"); var timestampEl = document.getElementById("live-timestamp"); var statUptime = document.getElementById("stat-uptime"); var statListeners = document.getElementById("stat-listeners"); var statStatus = document.getElementById("stat-status"); var lastCapturedAt = null; if (!img) { return; } function formatTimeAgo(unixSeconds) { var diff = Math.floor(Date.now() / 1000) - unixSeconds; if (diff < 0) { diff = 0; } if (diff < 60) { return diff === 1 ? "1 second ago" : diff + " seconds ago"; } var minutes = Math.floor(diff / 60); if (minutes < 60) { return minutes === 1 ? "1 minute ago" : minutes + " minutes ago"; } var hours = Math.floor(diff / 3600); if (hours < 24) { return hours === 1 ? "1 hour ago" : hours + " hours ago"; } var days = Math.floor(diff / 86400); return days === 1 ? "1 day ago" : days + " days ago"; } function formatDuration(seconds) { var diff = Math.max(0, Math.floor(seconds)); var days = Math.floor(diff / 86400); var hours = Math.floor((diff % 86400) / 3600); var minutes = Math.floor((diff % 3600) / 60); if (days > 0) { return days + "d " + hours + "h"; } if (hours > 0) { return hours + "h " + minutes + "m"; } if (minutes > 0) { return minutes + "m"; } return diff + "s"; } function formatUptime(value) { if (value === null || value === undefined || value === "") { return "---"; } if (typeof value === "number") { return formatDuration(value); } return String(value); } function formatListeners(value) { if (value === null || value === undefined || value === "") { return "---"; } return String(value); } function formatStatus(value) { if (value === null || value === undefined || value === "") { return "---"; } return String(value); } function updateTimeAgo() { if (!timestampEl) { return; } if (!lastCapturedAt) { timestampEl.textContent = ""; return; } timestampEl.textContent = "Captured " + formatTimeAgo(lastCapturedAt); } function updateStats(data) { if (statUptime) { statUptime.textContent = formatUptime(data.uptime); } if (statListeners) { var listeners = data.listeners; if (listeners === undefined) { listeners = data.live_listeners; } statListeners.textContent = formatListeners(listeners); } if (statStatus) { var statusText = formatStatus(data.status); statStatus.textContent = statusText; statStatus.classList.remove("stat-value--ok", "stat-value--bad"); if (statusText !== "---" && String(data.status).toLowerCase() !== "online") { statStatus.classList.add("stat-value--bad"); } else if (statusText !== "---") { statStatus.classList.add("stat-value--ok"); } } } function clearStats() { updateStats({}); } function fetchLiveData() { fetch(LIVE_DATA + "?t=" + Date.now()) .then(function (response) { if (!response.ok) { throw new Error("live data unavailable"); } return response.json(); }) .then(function (data) { if (data && data.timestamp) { lastCapturedAt = data.timestamp; updateTimeAgo(); } else { lastCapturedAt = null; updateTimeAgo(); } updateStats(data || {}); }) .catch(function () { lastCapturedAt = null; updateTimeAgo(); clearStats(); }); } function refreshLiveImage() { img.src = LIVE_IMAGE + "?t=" + Date.now(); fetchLiveData(); } img.addEventListener("error", function () { img.alt = "Live feed unavailable"; }); refreshLiveImage(); setInterval(refreshLiveImage, REFRESH_MS); setInterval(updateTimeAgo, TICK_MS); })();