diff options
| author | tv <tv@krebsco.de> | 2026-10-30 19:22:39 +0100 |
|---|---|---|
| committer | tv <tv@krebsco.de> | 2026-10-30 19:22:39 +0100 |
| commit | 94c857eaf7197d1dc6caa3cb295c73a959095192 (patch) | |
| tree | 519e04fa343d57b45b933e0b93e2586d6cf5ffc7 /src/api/dashboard/dashboard.html | |
| parent | c9f556a6d329117673adfc8a9237b6b81b9ade72 (diff) | |
dashboard: add status pulse
Diffstat (limited to 'src/api/dashboard/dashboard.html')
| -rw-r--r-- | src/api/dashboard/dashboard.html | 25 |
1 files changed, 21 insertions, 4 deletions
diff --git a/src/api/dashboard/dashboard.html b/src/api/dashboard/dashboard.html index dd0c3cc..f6f6693 100644 --- a/src/api/dashboard/dashboard.html +++ b/src/api/dashboard/dashboard.html @@ -27,6 +27,16 @@ #connection { margin-left: auto; color: var(--muted); } #connection.live { color: var(--ok); } #connection.down { color: var(--bad); } + /* A dot in the status's colour that breathes once with every event. */ + #dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; + background: currentColor; margin-right: 6px; vertical-align: 1px; opacity: .45; } + #dot.pulse { animation: breath .9s ease-in-out; } + @keyframes breath { + 0% { opacity: .45; transform: scale(1); } + 30% { opacity: 1; transform: scale(1.35); box-shadow: 0 0 6px currentColor; } + 100% { opacity: .45; transform: scale(1); } + } + @media (prefers-reduced-motion: reduce) { #dot.pulse { animation-duration: .01s; } } body.stale main { opacity: .55; } /* Swarm on the left, the full height; peers (as tall as their rows, up to 8) above the figures (the rest) on the right. */ @@ -95,7 +105,7 @@ <header> <h1>continuum</h1> <span id="node" class="id"></span> - <span id="connection">connecting…</span> + <span id="connection"><span id="dot"></span><span id="connection-text">connecting…</span></span> </header> <main> <section id="map"> @@ -613,8 +623,15 @@ graph.addEventListener("mouseleave", () => hover(null)); // --- Events --- const connection = document.getElementById("connection"); +const dot = document.getElementById("dot"); +// One breath per event from the node. +function pulse() { + dot.classList.remove("pulse"); + void dot.offsetWidth; // restarts the animation + dot.classList.add("pulse"); +} function status(text, live) { - connection.textContent = text; + document.getElementById("connection-text").textContent = text; connection.className = live ? "live" : "down"; document.body.classList.toggle("stale", !live && state !== null); } @@ -632,11 +649,11 @@ function connect() { return; } state = snapshot; stateAt = Date.now(); live = true; - status("live", true); render(); + status("live", true); pulse(); render(); }); events.addEventListener("patch", event => { if (!state) return; - state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); render(); + state = mergePatch(state, JSON.parse(event.data)); stateAt = Date.now(); pulse(); render(); }); // The node stops: it says so, and ends the stream. Back once it is. events.addEventListener("offline", () => { |
