diff --git a/zebra-report/zebra-spaces.html b/zebra-report/zebra-spaces.html
index 067a19c..6b3d54c 100644
--- a/zebra-report/zebra-spaces.html
+++ b/zebra-report/zebra-spaces.html
@@ -105,6 +105,16 @@
* them going display:none can't cause the others to slide. Controls
* column is also clamped to ≤50vw so a very wide window doesn't let
* the side panel eat the screen-share area. */
+ /* Page grid switches its template based on which columns are live.
+ * .cameras-col.hidden (no cameras) and .page.controls-collapsed
+ * (hide-panel) each remove a track; the timeline reassigns its
+ * grid-column for each combination so it always lands in the
+ * remaining slot. Four states:
+ * cams + ctrls : 220 1fr min(360,50vw) timeline col 2
+ * cams : 220 1fr timeline col 2
+ * ctrls only : 1fr min(360,50vw) timeline col 1
+ * alone : 1fr timeline col 1
+ */
.page {
display: grid;
grid-template-columns: 220px minmax(0, 1fr) minmax(0, min(360px, 50vw));
@@ -117,35 +127,41 @@
.cameras-col {
min-width: 0;
border-right: 1px solid #ddd; padding-right: 1.25rem;
- display: grid !important; /* beat the global .hidden util */
+ display: grid;
grid-auto-rows: max-content;
gap: 0.5rem;
}
- /* the cameras-col uses the .hidden class as a content-empty signal —
- * keep the column visible (track reserved) but hide its h2 + grid
- * and drop the right-border/padding so the empty column reads as
- * dead space rather than a divider for nothing */
- .cameras-col.hidden h2,
- .cameras-col.hidden #cameras { display: none; }
- .cameras-col.hidden { border-right: none; padding-right: 0; }
- /* hide-panel = focus mode: both side columns fold away, screen+timeline
- * fills the full viewport. Cameras and controls both go to display:none
- * so the screen-share / game iframe has nothing competing with it. */
- .page.controls-collapsed {
+ /* state: no cameras (column track gone, timeline + controls take 2-col) */
+ .page:has(> .cameras-col.hidden) {
+ grid-template-columns: minmax(0, 1fr) minmax(0, min(360px, 50vw));
+ }
+ .page:has(> .cameras-col.hidden) > .timeline { grid-column: 1; }
+ .page:has(> .cameras-col.hidden) > .controls { grid-column: 2; }
+
+ /* state: controls collapsed (right column gone) */
+ .page.controls-collapsed > .controls { display: none; }
+ .page.controls-collapsed:not(:has(> .cameras-col.hidden)) {
+ grid-template-columns: 220px minmax(0, 1fr);
+ }
+ /* state: controls collapsed AND no cameras → timeline owns the viewport */
+ .page.controls-collapsed:has(> .cameras-col.hidden) {
grid-template-columns: minmax(0, 1fr);
gap: 0;
}
- .page.controls-collapsed > .controls,
- .page.controls-collapsed > .cameras-col { 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;
+ .page.controls-collapsed:has(> .cameras-col.hidden) > .timeline {
+ grid-column: 1; border-right: none; padding-right: 0;
+ }
+ /* pre-paint pref so a 'hidden' reload doesn't flash the panel */
+ html.controls-collapsed-pref .page > .controls { display: none; }
+ html.controls-collapsed-pref .page:not(:has(> .cameras-col.hidden)) {
+ grid-template-columns: 220px minmax(0, 1fr);
+ }
+ html.controls-collapsed-pref .page:has(> .cameras-col.hidden) {
+ grid-template-columns: minmax(0, 1fr); gap: 0;
+ }
+ html.controls-collapsed-pref .page:has(> .cameras-col.hidden) > .timeline {
+ grid-column: 1; border-right: none; padding-right: 0;
}
- html.controls-collapsed-pref .page > .controls,
- html.controls-collapsed-pref .page > .cameras-col { 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: grid; grid-auto-rows: max-content;
@@ -2285,8 +2301,8 @@ logLine('', 'ready — pick a handle, type a rendezvous code, enter the space');