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.
This commit is contained in:
calesthio
2026-07-10 15:27:44 -07:00
parent 2ab5773ef7
commit d3a8fcf3bc
2 changed files with 21 additions and 10 deletions

View File

@@ -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); }

View File

@@ -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