TV display: courts pinned at top, lower pane scrolls and pages itself
Release 0.5.3. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
This commit is contained in:
1 parent
594247ef56
commit
3d68ea1e7d
4 files changed
+25
-4
No files matched your search
@@ -9,6 +9,13 @@ so you can always tell what a deployment is actually serving.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.5.3] - 2026-09-03
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- TV display: the courts (and banner) stay pinned at the top of the screen; up next, brackets
|
||||||
|
and standings scroll in a pane underneath, paging automatically every 12 seconds and
|
||||||
|
wrapping back to the top. Idle courts and the banner now use dark-theme colors on the TV.
|
||||||
|
|
||||||
## [0.5.2] - 2026-09-03
|
## [0.5.2] - 2026-09-03
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
{"name":"courtside","type":"module","version":"0.5.2","scripts":{"test":"node --test test/*.test.js","sim":"node simulate.js","start":"node --disable-warning=ExperimentalWarning server/index.js","dev":"node --disable-warning=ExperimentalWarning --watch server/index.js","demo":"node --disable-warning=ExperimentalWarning scripts/seed-demo.js","smoke":"bash test/smoke.sh"},"description":"Self-hosted volleyball tournament desk: one QR code from check-in to live scores","main":"server/index.js","directories":{"test":"test"},"keywords":[],"author":"","license":"MIT","dependencies":{"qrcode":"^1.5.4","ws":"^8.21.3"},"engines":{"node":">=22.13"},"repository":{"type":"git","url":"https://gitea.cloudfreeiot.com/bkvargyas/courtside.git"}}
|
{"name":"courtside","type":"module","version":"0.5.3","scripts":{"test":"node --test test/*.test.js","sim":"node simulate.js","start":"node --disable-warning=ExperimentalWarning server/index.js","dev":"node --disable-warning=ExperimentalWarning --watch server/index.js","demo":"node --disable-warning=ExperimentalWarning scripts/seed-demo.js","smoke":"bash test/smoke.sh"},"description":"Self-hosted volleyball tournament desk: one QR code from check-in to live scores","main":"server/index.js","directories":{"test":"test"},"keywords":[],"author":"","license":"MIT","dependencies":{"qrcode":"^1.5.4","ws":"^8.21.3"},"engines":{"node":">=22.13"},"repository":{"type":"git","url":"https://gitea.cloudfreeiot.com/bkvargyas/courtside.git"}}
|
||||||
@@ -94,7 +94,8 @@ ul.plain,ol.plain{list-style:none;padding:0;margin:8px 0}ol.plain{counter-reset:
|
|||||||
.loading{color:var(--muted);padding:24px}
|
.loading{color:var(--muted);padding:24px}
|
||||||
/* display (TV) mode */
|
/* display (TV) mode */
|
||||||
body.display{font-size:22px;background:#0F1418;color:#F2F5F7}
|
body.display{font-size:22px;background:#0F1418;color:#F2F5F7}
|
||||||
body.display .top,body.display .foot{display:none}body.display main{max-width:none;padding:20px 28px}
|
body.display .top,body.display .foot{display:none}body.display main{max-width:none;padding:14px 24px 0;height:100vh;height:100dvh;box-sizing:border-box}
|
||||||
|
body.display .app{display:flex;flex-direction:column;height:100%}body.display .pin{flex:none;padding-bottom:10px;border-bottom:1px solid #2B3640}body.display .scrollpane{flex:1;min-height:0;overflow-y:auto;padding:10px 0 24px;scrollbar-width:none}body.display .scrollpane::-webkit-scrollbar{display:none}body.display .courts{margin-bottom:0}body.display .court.idle,body.display .court.paused{background:#1A222B;color:#9AA6B1;border-color:#2B3640}body.display .court.idle .nextup{color:#F2F5F7;border-top-color:#2B3640}body.display .banner{background:#3A3120;color:#F0C46B;border-left-color:#D9B26E}body.display .brow .bs{color:#9AA6B1}body.display .brow.won .bs{color:#F2F5F7}
|
||||||
body.display .display-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}body.display h1{font-size:56px}
|
body.display .display-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}body.display h1{font-size:56px}
|
||||||
body.display .court .t{font-size:40px}body.display .court .s{font-size:52px}body.display .court{min-height:150px}
|
body.display .court .t{font-size:40px}body.display .court .s{font-size:52px}body.display .court{min-height:150px}
|
||||||
body.display .card{background:#1A222B;border-color:#2B3640;color:#F2F5F7}body.display td{border-color:#2B3640}body.display .bm{background:#12181F;border-color:#2B3640}body.display .brow+.brow{border-color:#2B3640}body.display .bcanvas path{stroke:#3A4652}body.display .btag{background:#1A222B;border-color:#2B3640}
|
body.display .card{background:#1A222B;border-color:#2B3640;color:#F2F5F7}body.display td{border-color:#2B3640}body.display .bm{background:#12181F;border-color:#2B3640}body.display .brow+.brow{border-color:#2B3640}body.display .bcanvas path{stroke:#3A4652}body.display .btag{background:#1A222B;border-color:#2B3640}
|
||||||
|
|||||||
+15
-2
@@ -45,7 +45,15 @@
|
|||||||
if (s.pools.length) parts.push(renderPools());
|
if (s.pools.length) parts.push(renderPools());
|
||||||
if (!s.pools.length && !s.bracket.length) parts.push(renderRoster());
|
if (!s.pools.length && !s.bracket.length) parts.push(renderRoster());
|
||||||
if (s.withdrawn.length && !display) parts.push(`<p class="muted small">Withdrawn: ${s.withdrawn.map(t => esc(t.name)).join(', ')}</p>`);
|
if (s.withdrawn.length && !display) parts.push(`<p class="muted small">Withdrawn: ${s.withdrawn.map(t => esc(t.name)).join(', ')}</p>`);
|
||||||
app.innerHTML = parts.join('');
|
if (display) {
|
||||||
|
// TV: courts stay pinned at the top; everything else scrolls underneath
|
||||||
|
const pane = app.querySelector('.scrollpane');
|
||||||
|
const keep = pane ? pane.scrollTop : 0;
|
||||||
|
const pinCount = parts.findIndex(p => p.startsWith('<section class="queue"') || p.startsWith('<section class="card bracketwrap') || p.startsWith('<section class="pools"') || p.startsWith('<section class="card"'));
|
||||||
|
const cut = pinCount === -1 ? parts.length : pinCount;
|
||||||
|
app.innerHTML = `<div class="pin">${parts.slice(0, cut).join('')}</div><div class="scrollpane">${parts.slice(cut).join('')}</div>`;
|
||||||
|
const np = app.querySelector('.scrollpane'); if (np) np.scrollTop = keep;
|
||||||
|
} else app.innerHTML = parts.join('');
|
||||||
fitBracket();
|
fitBracket();
|
||||||
notifyMine();
|
notifyMine();
|
||||||
}
|
}
|
||||||
@@ -257,5 +265,10 @@
|
|||||||
connect();
|
connect();
|
||||||
// belt and braces: a full refresh every 2 minutes in case a proxy drops the socket silently
|
// belt and braces: a full refresh every 2 minutes in case a proxy drops the socket silently
|
||||||
setInterval(() => fetch(`/t/${state.slug}/state.json`).then(r => r.json()).then(s => { state = s; render(); }).catch(() => {}), 120000);
|
setInterval(() => fetch(`/t/${state.slug}/state.json`).then(r => r.json()).then(s => { state = s; render(); }).catch(() => {}), 120000);
|
||||||
if (display) setInterval(() => document.documentElement.scrollTo({ top: (Date.now() / 1000 | 0) % 2 ? 0 : document.body.scrollHeight, behavior: 'smooth' }), 15000);
|
// TV: page the lower pane (up next, brackets, standings) every 12 s and wrap to the top; courts never move
|
||||||
|
if (display) setInterval(() => {
|
||||||
|
const pane = app.querySelector('.scrollpane'); if (!pane || pane.scrollHeight <= pane.clientHeight) return;
|
||||||
|
const atEnd = pane.scrollTop + pane.clientHeight >= pane.scrollHeight - 4;
|
||||||
|
pane.scrollTo({ top: atEnd ? 0 : pane.scrollTop + pane.clientHeight * 0.85, behavior: 'smooth' });
|
||||||
|
}, 12000);
|
||||||
})();
|
})();
|
||||||
Reference in new issue
Block a user