Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion internal/web/public/css/wall.css
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,19 @@ body.cursor-idle * {
text-transform: uppercase;
letter-spacing: 0.1em;
}
/* Shown while a camera is on but pump-cv hasn't decoded a frame yet
(cold start / RTSP disconnected) — beats a broken-image icon. */
.wall-cam-wait {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.8rem;
color: #6a6a72;
letter-spacing: 0.04em;
}
.wall-cam-wait[hidden] { display: none; }
/* Toggle switch — small, kiosk-friendly */
.wall-cam-toggle {
position: relative;
Expand Down Expand Up @@ -448,7 +461,14 @@ body.sleeping .wall-sleep { cursor: pointer; }
Hidden by default; wall.js toggles `hidden` based on the state poll. */
.wall-calib {
position: fixed;
inset: 0;
/* Start below the shared navbar (header.html) so the kiosk operator can
still navigate away from the AI page while calibration is pending —
inset:0 here used to paint the overlay over the navbar entirely.
wall.js measures the real navbar height into --wall-navbar-h. */
top: var(--wall-navbar-h, 56px);
right: 0;
bottom: 0;
left: 0;
background: var(--wall-bg, #0a0a0c);
z-index: 1000;
display: flex;
Expand Down Expand Up @@ -497,6 +517,12 @@ body.sleeping .wall-sleep { cursor: pointer; }
height: 100%;
object-fit: cover;
}
.wall-calib-prev-wait {
font-size: 0.9rem;
color: #6a6a72;
letter-spacing: 0.04em;
}
.wall-calib-prev-wait[hidden] { display: none; }
.wall-calib-prev-label {
position: absolute;
top: 6px;
Expand Down
42 changes: 40 additions & 2 deletions internal/web/public/js/wall.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,21 @@
clipClose: document.getElementById('wallClipClose'),
};

// ─── Navbar height → CSS var ────────────────────────────────────────
// The shared navbar (header.html) sits above the wall grid. Its real
// height depends on the logo size and font metrics, so measure it and
// publish it as --wall-navbar-h. Both the grid's height calc and the
// calibration overlay's top offset read this var; without it they fall
// back to a 56px guess that's ~25px short of the actual navbar.
function measureNavbar() {
const nav = document.querySelector('.navbar');
if (!nav) return;
const h = Math.round(nav.getBoundingClientRect().height);
if (h > 0) document.documentElement.style.setProperty('--wall-navbar-h', h + 'px');
}
measureNavbar();
window.addEventListener('resize', measureNavbar, { passive: true });

// ─── Header date + clock ────────────────────────────────────────────
function renderHeader() {
const now = new Date();
Expand Down Expand Up @@ -156,6 +171,7 @@
</div>
<div class="wall-cam-frame">
<img class="wall-cam-img" alt="${escapeHTML(c.name)} preview" hidden>
<div class="wall-cam-wait" hidden>waiting for camera…</div>
<div class="wall-cam-off">off</div>
</div>
</div>
Expand All @@ -180,8 +196,16 @@
if (!tile) return;
const img = tile.querySelector('.wall-cam-img');
const off = tile.querySelector('.wall-cam-off');
img.hidden = false;
const wait = tile.querySelector('.wall-cam-wait');
off.hidden = true;
// Until the first frame decodes, show a "waiting" tile rather than a
// broken-image icon. pump-cv 404s the snapshot endpoint while a camera
// has no decoded frame yet (cold start / RTSP disconnected), so the
// <img> error fires every poll until the stream comes up.
img.hidden = true;
if (wait) wait.hidden = false;
img.onload = () => { img.hidden = false; if (wait) wait.hidden = true; };
img.onerror = () => { img.hidden = true; if (wait) wait.hidden = false; };
const tick = () => {
// Cache-buster — the snapshot endpoint sends Cache-Control: no-store
// anyway, but `?t=` defends against any intermediate caching layer.
Expand All @@ -198,8 +222,11 @@
if (!tile) return;
const img = tile.querySelector('.wall-cam-img');
const off = tile.querySelector('.wall-cam-off');
const wait = tile.querySelector('.wall-cam-wait');
img.onload = img.onerror = null;
img.hidden = true;
img.removeAttribute('src');
if (wait) wait.hidden = true;
off.hidden = false;
}

Expand Down Expand Up @@ -448,10 +475,21 @@
calib.prevs.innerHTML = camNames.map(n => `
<div class="wall-calib-prev" data-cam="${escapeHTML(n)}">
<span class="wall-calib-prev-label">${escapeHTML(n)}</span>
<img alt="${escapeHTML(n)} preview">
<img alt="${escapeHTML(n)} preview" hidden>
<span class="wall-calib-prev-wait">waiting for camera…</span>
<span class="wall-calib-prev-detect" hidden></span>
</div>
`).join('');
// Show the frame once it decodes; fall back to the "waiting" label
// whenever the snapshot 404s (no frame yet) instead of a broken image.
camNames.forEach(n => {
const tile = calib.prevs.querySelector(`.wall-calib-prev[data-cam="${cssEscape(n)}"]`);
if (!tile) return;
const img = tile.querySelector('img');
const wait = tile.querySelector('.wall-calib-prev-wait');
img.onload = () => { img.hidden = false; if (wait) wait.hidden = true; };
img.onerror = () => { img.hidden = true; if (wait) wait.hidden = false; };
});
}

function calibStartPreviews(camNames) {
Expand Down
5 changes: 4 additions & 1 deletion internal/web/public/wall-sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
// cache. v4: the /wall/ HTML shell is now served network-first (below), so a
// stale shell can no longer be pinned on a long-lived kiosk — this bump also
// flushes any v3 cache still holding the pre-navbar shell.
const CACHE_NAME = "pump-wall-v4";
// v5: wall.css/wall.js changed (calibration overlay no longer paints over the
// navbar; navbar height measured into --wall-navbar-h) — flush the v4 precache
// so the cache-first /fs/ handler stops serving the old shell assets.
const CACHE_NAME = "pump-wall-v5";
const SHELL = [
"/wall/",
"/fs/public/css/wall.css",
Expand Down
Loading