From a9a46e949f8627bd2592301491950745ed2dd355 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Sun, 16 Aug 2026 11:03:07 +0200 Subject: [PATCH] Turn the next-sync indicator into a live ticking countdown Replaces the one-time UTC-to-local formatting with a per-second countdown (HH:MM:SS, or MM:SS under an hour) that fits the dark cockpit theme's instrument-panel feel, falling back to "due now" once the target passes instead of showing a negative duration. Co-Authored-By: Claude Sonnet 5 --- app/web/static/app.js | 45 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/app/web/static/app.js b/app/web/static/app.js index 62b24cc..0ee0719 100644 --- a/app/web/static/app.js +++ b/app/web/static/app.js @@ -1,10 +1,39 @@ -document.addEventListener("DOMContentLoaded", () => { - document.querySelectorAll("time[data-utc]").forEach((el) => { - const date = new Date(el.dataset.utc); - if (Number.isNaN(date.getTime())) { - return; +function formatCountdown(remainingMs) { + if (remainingMs <= 0) { + return "due now"; + } + const totalSeconds = Math.floor(remainingMs / 1000); + const hours = Math.floor(totalSeconds / 3600); + const minutes = Math.floor((totalSeconds % 3600) / 60); + const seconds = totalSeconds % 60; + const pad = (n) => String(n).padStart(2, "0"); + if (hours > 0) { + return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; + } + return `${pad(minutes)}:${pad(seconds)}`; +} + +function startSyncCountdown(el) { + const target = new Date(el.dataset.utc); + if (Number.isNaN(target.getTime())) { + return; + } + el.title = `${target.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" })} ยท ${el.dataset.utc} UTC`; + + let intervalId = null; + const tick = () => { + const remaining = target.getTime() - Date.now(); + el.textContent = formatCountdown(remaining); + if (remaining <= 0 && intervalId !== null) { + clearInterval(intervalId); } - el.textContent = date.toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" }); - el.title = `${el.dataset.utc} (UTC)`; - }); + }; + tick(); + if (target.getTime() - Date.now() > 0) { + intervalId = setInterval(tick, 1000); + } +} + +document.addEventListener("DOMContentLoaded", () => { + document.querySelectorAll("time.next-sync[data-utc]").forEach(startSyncCountdown); });