zebra-spaces: 3-column layout — cameras left, screen middle, controls right
Replaces the PiP overlay with a real 3-column grid: a narrow left column carrying cameras stacked vertically, the timeline/screen in the middle, controls on the right. Layout rules: - cameras-col is 220px wide on desktop, hidden via .hidden when no cameras are live (zero-cost when nobody has a webcam on — keeps the original two-column feel for typical mic-only rooms) - hide-panel still folds away the right column → 2-col cameras+screen - on <800px viewports all three stack vertically: controls, then timeline (or screen), then cameras as a multi-column grid Camera tiles are now single-column stacked with a 16:9 aspect-ratio letterbox so face-cams stay readable regardless of incoming resolution.
This commit is contained in:
parent
6dfa148612
commit
af1e5d4b55
1 changed files with 40 additions and 41 deletions
|
|
@ -24,7 +24,8 @@
|
|||
html.theme-dark a { color: #9cf; }
|
||||
html.theme-dark .sub, html.theme-dark .sub a, html.theme-dark .note,
|
||||
html.theme-dark .status-line { color: #888; }
|
||||
html.theme-dark .timeline { border-right-color: #222; }
|
||||
html.theme-dark .timeline,
|
||||
html.theme-dark .cameras-col { border-right-color: #222; }
|
||||
html.theme-dark .timeline-frame { background: #000; border-color: #222; }
|
||||
/* buttons + inputs */
|
||||
html.theme-dark button,
|
||||
|
|
@ -99,23 +100,34 @@
|
|||
* controls (kept narrow so the screen share gets maximum pixels).
|
||||
* Both columns get min-width:0 so they shrink without forcing a
|
||||
* horizontal scrollbar. */
|
||||
/* three columns: cameras-vertical | screen/timeline | controls.
|
||||
* The cameras column auto-collapses to width:0 when nobody has a
|
||||
* camera on (.hidden), so a typical mic-only room reads as the
|
||||
* original two-column layout. */
|
||||
.page {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 360px);
|
||||
grid-template-columns: minmax(0, auto) minmax(0, 1fr) minmax(0, 360px);
|
||||
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. */
|
||||
.cameras-col {
|
||||
width: 220px; min-width: 0;
|
||||
border-right: 1px solid #ddd; padding-right: 1.25rem;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.cameras-col.hidden { display: none; }
|
||||
/* collapsed: right column folds away so the screen+cameras layout
|
||||
* fills the viewport. */
|
||||
.page.controls-collapsed {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
gap: 0;
|
||||
grid-template-columns: minmax(0, auto) minmax(0, 1fr);
|
||||
gap: 1.25rem;
|
||||
}
|
||||
.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 {
|
||||
grid-template-columns: minmax(0, auto) minmax(0, 1fr);
|
||||
}
|
||||
html.controls-collapsed-pref .page > .controls { display: none; }
|
||||
html.controls-collapsed-pref .page > .timeline { border-right: none; padding-right: 0; }
|
||||
.timeline {
|
||||
|
|
@ -136,12 +148,16 @@
|
|||
}
|
||||
@media (max-width: 800px) {
|
||||
body { padding: 1.25rem; }
|
||||
/* stack on mobile, controls first, timeline below */
|
||||
/* stack on mobile: controls first, then any live screen, cameras last */
|
||||
.page { grid-template-columns: minmax(0, 1fr); gap: 1rem; }
|
||||
.controls { order: 1; }
|
||||
.timeline { order: 2;
|
||||
border-right: none; border-top: 1px solid #ddd;
|
||||
padding-right: 0; padding-top: 1rem; min-height: 0; }
|
||||
.cameras-col { order: 3;
|
||||
width: 100%; border-right: none; border-top: 1px solid #ddd;
|
||||
padding-right: 0; padding-top: 1rem; }
|
||||
#cameras { grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); }
|
||||
}
|
||||
/* never let dynamically-appended <audio> sinks (one per remote
|
||||
* speaker) render their default ~300px control strip — they're just
|
||||
|
|
@ -178,15 +194,15 @@
|
|||
.timeline:has(> #sec-screens:not(.hidden)) > .game-tabs,
|
||||
.timeline:has(> #sec-screens:not(.hidden)) > #game-frame { display: none; }
|
||||
|
||||
/* camera tiles: smaller, multi-column grid so several face-cams fit
|
||||
* side-by-side without dwarfing the screen share above them */
|
||||
#cameras { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 0.5rem; }
|
||||
/* camera tiles live in the LEFT column now — single column, stacked
|
||||
* vertically. Each tile keeps a 16:9 letterbox so faces don't squash. */
|
||||
#cameras { display: grid; grid-template-columns: 1fr; gap: 0.45rem; }
|
||||
.camera-tile {
|
||||
border: 1px solid #000; background: #000; padding: 0;
|
||||
display: flex; flex-direction: column; position: relative;
|
||||
}
|
||||
.camera-tile video {
|
||||
width: 100%; height: auto; max-height: 40vh; display: block; background: #000;
|
||||
width: 100%; height: auto; display: block; background: #000;
|
||||
aspect-ratio: 16 / 9; object-fit: cover;
|
||||
}
|
||||
.camera-tile .screen-meta {
|
||||
|
|
@ -202,26 +218,6 @@
|
|||
}
|
||||
.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. */
|
||||
|
|
@ -371,20 +367,23 @@ try {
|
|||
|
||||
<div class="page">
|
||||
|
||||
<!-- left column: cameras stacked vertically so face-cams stay visible
|
||||
during a screen share without dwarfing the screen itself. Hidden
|
||||
when nobody has a camera on. -->
|
||||
<aside id="sec-cameras" class="cameras-col hidden">
|
||||
<h2>cameras</h2>
|
||||
<div id="cameras"></div>
|
||||
</aside>
|
||||
|
||||
<main class="timeline">
|
||||
<!-- shared-screen tiles take priority over the game iframe. When sec-screens
|
||||
is visible (someone is sharing) the game tabs + iframe get hidden via
|
||||
CSS so the screen has the full left column. -->
|
||||
CSS so the screen has the full middle column. -->
|
||||
<section id="sec-screens" class="hidden">
|
||||
<h2>screens</h2>
|
||||
<div id="screens"></div>
|
||||
</section>
|
||||
|
||||
<section id="sec-cameras" class="hidden">
|
||||
<h2>cameras</h2>
|
||||
<div id="cameras"></div>
|
||||
</section>
|
||||
|
||||
<!-- placeholder content until the v0.3 event timeline lands: a game switcher
|
||||
framing an external playable. Iframes load unsandboxed so the game JS can
|
||||
run; if a target refuses framing (X-Frame-Options / CSP frame-ancestors)
|
||||
|
|
@ -2250,8 +2249,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> · built <span class="stamp-date">2026-06-02</span><br>
|
||||
md5 <span class="stamp-md5">016e64ea183b4be1c9803669e0558045</span><br>
|
||||
sha256 <span class="stamp-sha">9ad330fb516bb1e632b1f66511fb840cdcbd2cfc14390101fcb1bb5c362c978b</span><br>
|
||||
md5 <span class="stamp-md5">8adf0c5717ac94980b9f3f986f6c947b</span><br>
|
||||
sha256 <span class="stamp-sha">60d9c9bb2e8c72334e53847efd8f151b972235a2425b39c2302f4fe5c0897fc7</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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue