commit 466b5043ff18c40f4e162351784e75f130b14b8a
parent 381f9ead853dde3a6e7de5e6fd85ee691b11a4bd
Author: I Mean I'm Just Saying <imeanimjustsaying@kiwifarms.st>
Date: Thu, 1 Oct 2026 01:03:36 -0400
report-to-video: snapWindow lives in deck.mjs; posts leave front-loaded
build-video imported snapWindow from chrome-posts.mjs, which pulls in
chrome-deck.mjs; under Turbopack that module's GSAP asset URL fails
fileURLToPath at load, so umtool's next build failed collecting page data.
The window arithmetic is pure and now sits in deck.mjs beside postWindows;
the page modules stay behind compose-chrome's dynamic import.
The cards' leave eases power2.out, so they are mostly gone by the
mid-dissolve, where the deck hands over its title.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Diffstat:
3 files changed, 25 insertions(+), 22 deletions(-)
diff --git a/umtool/report-to-video/build-video.mjs b/umtool/report-to-video/build-video.mjs
@@ -81,10 +81,8 @@ import { createCueSource, siteOriginFromManifest } from "./cues.mjs";
// the schedule down -- it never has a copy of the arithmetic.
import {
assertChrome, deckGeometry, deckOn, deckSchedule, frameCount, postsGeometry, postWindows, resolveDeck,
- scheduleFrom, validatePosts,
+ scheduleFrom, snapWindow, validatePosts,
} from "./deck.mjs";
-// The posts windows' frame arithmetic, which the overlay and verify-build share.
-import { snapWindow } from "./chrome-posts.mjs";
// The per-platform yt-dlp args (Rumble's `--impersonate chrome`): the ONE table,
// in common, plain JS so bare `node` can load it.
import { platformArgsForUrl } from "yt-dlp-transcript-common/ytdlp/platformArgs.mjs";
diff --git a/umtool/report-to-video/chrome-posts.mjs b/umtool/report-to-video/chrome-posts.mjs
@@ -32,9 +32,13 @@
// Every cue is a fromTo whose FROM is stated, for the deck's reason: a render
// is a seek per frame, from parallel workers, in any order.
import { formatDeckDate } from "./attribution.mjs";
-import { frameCount, postsGeometry, resolveDeck } from "./deck.mjs";
+import { postsGeometry, resolveDeck, snapWindow } from "./deck.mjs";
import { mix, rgba } from "./chrome-deck.mjs";
+// The window arithmetic is deck.mjs's (pure, and loaded by the build without
+// this page module); re-exported for the page's own callers.
+export { snapWindow };
+
const esc = (s) =>
String(s ?? "")
.replace(/&/g, "&")
@@ -54,22 +58,6 @@ export const PLATFORM_LABEL = Object.freeze({ bluesky: "Bluesky", x: "X" });
*/
export const POSTS_MOTION = Object.freeze({ enter: 0.35, slide: 0.35, rise: 18, gap: 14 });
-/**
- * A `postWindows` entry snapped OUTWARD to the frame grid, and kept inside the
- * cut: `from` down to a frame, `to` up to one, never past the cut's last frame.
- * `f0` is the cut frame a window's frame 1 lands on; `frames` is
- * `frameCount(to ā from, fps)` of the snapped window -- the sequence's length,
- * which verify-build checks.
- *
- * @returns {{ segment: string, from: number, to: number, f0: number, frames: number }}
- */
-export function snapWindow(window, { fps, total }) {
- const f0 = Math.max(0, Math.floor(window.from * fps + 1e-6));
- const f1 = Math.min(Math.ceil(window.to * fps - 1e-6), frameCount(total, fps));
- if (!(f1 > f0)) throw new Error(`posts: the window for ${window.segment} is empty (${window.from}sā${window.to}s)`);
- return { segment: window.segment, from: f0 / fps, to: f1 / fps, f0, frames: frameCount((f1 - f0) / fps, fps) };
-}
-
/** The schedule's posts for one window's segment, in slot order. */
export function windowPosts(schedule, segment) {
return (schedule.posts ?? []).filter((p) => p.segment === segment).sort((a, b) => a.slot - b.slot);
@@ -86,7 +74,7 @@ export function windowPosts(schedule, segment) {
* `rise` px over `enter` s); cards stack top-down `gap` apart; when card j
* would overflow the column, the oldest slide up and out (the whole stack
* moves up over `slide` s, the departing cards fading as they go); every card
- * still up leaves over its `out` (a fade and a slight shrink).
+ * still up leaves over its `out` (a front-loaded fade and a slight shrink).
*
* @returns {{ tops: number[], init: Record<string, object>,
* cues: Array<{ k: string, at: number, dur: number, from: object, to: object, ease: string, why: string }> }}
@@ -125,7 +113,8 @@ export function postsCues({ posts, heights, column, gap = 14, enter = 0.35, slid
}
for (const j of visible) {
const [a, b] = posts[j].out;
- add(`c${j}`, a, b - a, { autoAlpha: 0, scale: 0.97 }, "power2.in", `leave ${posts[j].id}`);
+ // Front-loaded: mostly gone by the mid-dissolve, where the deck hands over.
+ add(`c${j}`, a, b - a, { autoAlpha: 0, scale: 0.97 }, "power2.out", `leave ${posts[j].id}`);
}
// Order, clamp (a cue never starts before the last one on its element has
diff --git a/umtool/report-to-video/deck.mjs b/umtool/report-to-video/deck.mjs
@@ -736,6 +736,22 @@ export function postWindows(schedule) {
}
/**
+ * A `postWindows` entry snapped OUTWARD to the frame grid, and kept inside the
+ * cut: `from` down to a frame, `to` up to one, never past the cut's last frame.
+ * `f0` is the cut frame a window's frame 1 lands on; `frames` is
+ * `frameCount(to ā from, fps)` of the snapped window -- the sequence's length,
+ * which verify-build checks.
+ *
+ * @returns {{ segment: string, from: number, to: number, f0: number, frames: number }}
+ */
+export function snapWindow(window, { fps, total }) {
+ const f0 = Math.max(0, Math.floor(window.from * fps + 1e-6));
+ const f1 = Math.min(Math.ceil(window.to * fps - 1e-6), frameCount(total, fps));
+ if (!(f1 > f0)) throw new Error(`posts: the window for ${window.segment} is empty (${window.from}sā${window.to}s)`);
+ return { segment: window.segment, from: f0 / fps, to: f1 / fps, f0, frames: frameCount((f1 - f0) / fps, fps) };
+}
+
+/**
* Where the posts region sits in the frame: a column inside the footage box,
* `inset` from its top and from the chosen side, `width` wide and the box's
* height less the insets. Cards stack top-down inside it.