zebra-report: hide-panel toggle + camera PiP overlay — mirror

This commit is contained in:
russell@unturf.com 2026-06-02 08:57:16 -04:00
parent 1e6a6a7c1b
commit a6de7024e8
No known key found for this signature in database

View file

@ -29,12 +29,14 @@
/* buttons + inputs */
html.theme-dark button,
html.theme-dark .game-tabs button,
html.theme-dark .theme-toggle {
html.theme-dark .theme-toggle,
html.theme-dark .controls-toggle {
background: #000; color: #ccc; border-color: #444;
}
html.theme-dark button:hover:not(:disabled),
html.theme-dark .game-tabs button:hover:not(.active),
html.theme-dark .theme-toggle:hover { background: #1a1a1a; }
html.theme-dark .theme-toggle:hover,
html.theme-dark .controls-toggle:hover { background: #1a1a1a; }
html.theme-dark button.invert { background: #ccc; color: #000; border-color: #ccc; }
html.theme-dark button.invert:hover:not(:disabled) { background: #999; }
html.theme-dark .game-tabs button.active { background: #ccc; color: #000; border-color: #ccc; }
@ -83,13 +85,15 @@
html.theme-dark footer span { color: #555 !important; }
html.theme-dark footer a { color: #777 !important; }
.theme-toggle {
position: fixed; top: 0.6rem; right: 0.8rem; z-index: 9999;
.theme-toggle, .controls-toggle {
position: fixed; top: 0.6rem; z-index: 9999;
background: #fff; color: #000; border: 1px solid #000;
padding: 0.2rem 0.55rem; font-family: monospace; font-size: 0.7rem;
cursor: pointer; line-height: 1;
}
.theme-toggle:hover { background: #f0f0f0; }
.theme-toggle { right: 0.8rem; }
.controls-toggle { right: 4.3rem; }
.theme-toggle:hover, .controls-toggle:hover { background: #f0f0f0; }
/* two-column page on desktop: left = timeline (1fr, takes remaining
* space — hosts screen shares + the running event feed), right =
* controls (kept narrow so the screen share gets maximum pixels).
@ -101,9 +105,22 @@
gap: 1.25rem;
align-items: start;
}
/* collapsed: right column folds away so the screen share fills the
* full viewport width. The timeline column's right border + padding
* collapse with it. */
.page.controls-collapsed {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
.page.controls-collapsed > .controls { display: none; }
.page.controls-collapsed > .timeline { border-right: none; padding-right: 0; }
/* honour the pre-paint pref so first-paint matches saved state */
html.controls-collapsed-pref .page { grid-template-columns: minmax(0, 1fr); gap: 0; }
html.controls-collapsed-pref .page > .controls { display: none; }
html.controls-collapsed-pref .page > .timeline { border-right: none; padding-right: 0; }
.timeline {
border-right: 1px solid #ddd; padding-right: 1.25rem; min-height: 60vh;
min-width: 0; display: flex; flex-direction: column;
min-width: 0; display: flex; flex-direction: column; position: relative;
}
.controls { min-width: 0; }
.game-tabs { display: flex; gap: 0.4rem; margin-bottom: 0.5rem; flex-wrap: wrap; }
@ -185,6 +202,26 @@
}
.camera-tile.needs-tap .tap-play { display: flex; }
/* PiP mode: when a screen is being shared, cameras get out of the way —
* they float as a small vertical strip in the bottom-right of the
* timeline column, layered over the screen tile. Standard Zoom/Meet
* pattern. Scrolls vertically if there are more than fit. The screen
* keeps the full timeline width so 1080p screen-share stays huge. */
.timeline:has(> #sec-screens:not(.hidden)) > #sec-cameras {
position: absolute; right: 0.6rem; bottom: 0.6rem;
width: clamp(180px, 22%, 320px);
max-height: 70vh; overflow-y: auto;
z-index: 50; padding: 0.35rem;
background: rgba(0,0,0,0.55);
border-radius: 4px;
}
.timeline:has(> #sec-screens:not(.hidden)) > #sec-cameras > h2 {
color: #ddd; font-size: 0.75rem; margin: 0 0 0.3rem; border-bottom: none;
}
.timeline:has(> #sec-screens:not(.hidden)) #cameras { grid-template-columns: 1fr; gap: 0.3rem; }
.timeline:has(> #sec-screens:not(.hidden)) .camera-tile video { max-height: 16vh; }
.timeline:has(> #sec-screens:not(.hidden)) .camera-tile .screen-meta { font-size: 0.6rem; padding: 0.15rem 0.3rem; }
/* latency panel: one row per live PC, fixed-width columns so the numbers
* line up vertically as values bounce. lat-good < 50ms, mid < 150ms,
* bad ≥ 150ms — colour-tagged so the user can scan at a glance. */
@ -319,10 +356,18 @@ try {
document.documentElement.classList.add('theme-dark');
}
} catch(_){ document.documentElement.classList.add('theme-dark'); }
/* sidebar-collapsed pre-paint: apply to <html> so the .page child can
* read it via :has() or via a body-level class added on DOMContentLoaded */
try {
if (localStorage.getItem('zebra-spaces-controls-v1') === 'hidden'){
document.documentElement.classList.add('controls-collapsed-pref');
}
} catch(_){}
</script>
</head>
<body>
<button id="btn-theme" class="theme-toggle" type="button" aria-label="toggle dark mode">dark</button>
<button id="btn-controls-toggle" class="controls-toggle" type="button" aria-label="hide side panel" title="hide right side panel">hide panel</button>
<div class="page">
@ -538,6 +583,28 @@ $('btn-theme').addEventListener('click', () => {
try { localStorage.setItem(THEME_KEY, goDark ? 'dark' : 'light'); } catch(_){}
applyThemeLabel();
});
/* controls-panel collapse — promotes the pre-paint <html> pref onto the
* .page element, then keeps both in sync on click. Pre-paint class on
* <html> stays so a fresh load with a saved 'hidden' pref still shows
* the layout collapsed instantly. */
const CONTROLS_KEY = 'zebra-spaces-controls-v1';
function applyControlsLabel(){
const hidden = document.querySelector('.page').classList.contains('controls-collapsed');
const btn = $('btn-controls-toggle'); if (btn) btn.textContent = hidden ? 'show panel' : 'hide panel';
}
if (document.documentElement.classList.contains('controls-collapsed-pref')){
document.querySelector('.page').classList.add('controls-collapsed');
}
applyControlsLabel();
$('btn-controls-toggle').addEventListener('click', () => {
const page = document.querySelector('.page');
const goHidden = !page.classList.contains('controls-collapsed');
page.classList.toggle('controls-collapsed', goHidden);
document.documentElement.classList.toggle('controls-collapsed-pref', goHidden);
try { localStorage.setItem(CONTROLS_KEY, goHidden ? 'hidden' : 'shown'); } catch(_){}
applyControlsLabel();
});
const VAULT_PREFIX = 'zspc-id-v1|';
const PBKDF2_ITER = 600000;
@ -2183,8 +2250,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');
<footer style="margin:2.2rem auto 0;font-size:0.65rem;color:#999;line-height:1.7;word-break:break-all;font-family:monospace">
<span id="pi-seal" style="color:#777;cursor:default;user-select:none" title="">page integrity</span> &nbsp;·&nbsp; built <span class="stamp-date">2026-06-02</span><br>
md5 <span class="stamp-md5">df76b79d099ed55cab1336afdf62e8a8</span><br>
sha256 <span class="stamp-sha">f2a8f4316571f5accc3ed4b6be564ab31328c732323f2f64ce62675d3de1701c</span><br>
md5 <span class="stamp-md5">016e64ea183b4be1c9803669e0558045</span><br>
sha256 <span class="stamp-sha">9ad330fb516bb1e632b1f66511fb840cdcbd2cfc14390101fcb1bb5c362c978b</span><br>
<span style="color:#bbb">hashes are of this page with these two fields zeroed — to verify, blank them and re-hash</span><br>
<span style="color:#bbb">one self-contained file — <strong>save a copy</strong> and verify against these hashes; point at your own servers with ?signal= and ?turncred=, or <a href="host-your-own.html" style="color:#999">host your own community</a></span>
</footer>