From d3a8fcf3bca300b48dfdd10f14be7464700671d1 Mon Sep 17 00:00:00 2001 From: calesthio Date: Fri, 10 Jul 2026 15:27:44 -0700 Subject: [PATCH] fix(backlot): keep storyboard beside decisions rail; portrait-aware render player - Storyboard, found-media, and renders sections now render inside the main column instead of after the .board grid, so a tall decisions rail no longer pushes them below the fold (they flow beside the rail). - Render hero player: preload=metadata so the video has its intrinsic aspect ratio (and a poster frame) before playback, and natural-width centering so a 9:16 render displays as a portrait frame instead of a letterboxed landscape box. Degraded (no-grid) view keeps the old full-width flow. --- backlot/ui/board.css | 6 ++++-- backlot/ui/board.js | 25 +++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/backlot/ui/board.css b/backlot/ui/board.css index e4de79ff..2a417d38 100644 --- a/backlot/ui/board.css +++ b/backlot/ui/board.css @@ -505,8 +505,10 @@ a.backlink:hover { color: var(--text-2); } } /* render section */ -.render-hero { position: relative; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); background: #000; } -.render-hero video { width: 100%; display: block; max-height: 560px; } +.render-hero { position: relative; border-radius: 12px; overflow: hidden; border: 1px solid var(--border); background: #000; display: flex; justify-content: center; } +/* natural width up to the container: a 9:16 render shows as a centered + portrait frame instead of stretching into a letterboxed landscape box */ +.render-hero video { display: block; width: auto; max-width: 100%; max-height: 560px; } .render-meta { font-family: var(--mono); font-size: calc(10.5px * var(--fs-scale)); color: var(--text-3); padding: 8px 2px; display: flex; gap: 14px; flex-wrap: wrap; } .render-meta .v { color: var(--text-2); cursor: pointer; } .render-meta .v.active { color: var(--amber); } diff --git a/backlot/ui/board.js b/backlot/ui/board.js index f8ed62be..b56fed6b 100644 --- a/backlot/ui/board.js +++ b/backlot/ui/board.js @@ -528,7 +528,10 @@ function renderRenders(s) { // watch: carry playback position/state over to the recreated element. const prev = document.querySelector(".render-hero video"); const src = mediaURL(s.project_id, current.path); - const video = el("video", { src, controls: "", preload: "none" }); + // preload="metadata" gives the element its intrinsic aspect ratio (and a + // poster frame) before playback — without it a portrait 9:16 render sits + // in a letterboxed 100%-wide black box that reads as landscape. + const video = el("video", { src, controls: "", preload: "metadata" }); // Click the frame to start playback (controls handle pause/scrub) — the // big player was inert to a click on the picture itself. video.addEventListener("click", () => { if (video.paused) video.play().catch(() => {}); }); @@ -778,16 +781,22 @@ function render() { if (decisions) aside.append(decisions); if (activity) aside.append(activity); - if (script || decisions || activity) { - app.append(el("div", { class: "board" }, main, aside)); - } - + // Media sections live INSIDE the main column so a tall decisions rail + // never pushes them below the fold — the column flows beside the rail. const storyboard = renderStoryboard(s); - if (storyboard) app.append(storyboard); const found = renderFoundMedia(s); - if (found) app.append(found); const renders = renderRenders(s); - if (renders) app.append(renders); + + if (script || decisions || activity) { + for (const section of [storyboard, found, renders]) { + if (section) main.append(section); + } + app.append(el("div", { class: "board" }, main, aside)); + } else { + for (const section of [storyboard, found, renders]) { + if (section) app.append(section); + } + } } // Defensive normalization (F-02): the server contract guarantees these