feat(film): live-interaction product film foundation

Capture pipeline (Playwright recordVideo + injected synthetic cursor + ffmpeg webm->mp4) and a Remotion LiveFilm composition driven by a 20-clip manifest with caption/zoom overlays and a music bed. Includes the capture orchestrator, storm-intel reference scene, pre-render smoke check, and VO script. Pure logic is unit-tested (28 passing). Remaining scene scripts are authored during live capture.
This commit is contained in:
Satyam Rastogi
2026-05-30 20:33:02 +05:30
parent 48f6d8d094
commit 8602c70eea
20 changed files with 1022 additions and 10 deletions
@@ -0,0 +1,30 @@
// Storm Intel: drop a pin, wait for the REAL storm-history fetch, reveal the panel.
export default async (page, cursor, ctx) => {
// Map tiles stream in — let them settle before interacting.
await page.waitForTimeout(2500);
// Enable pin-drop mode if there's a toggle (button text "Pin" / aria), else skip.
const pinToggle = page.getByRole("button", { name: /pin/i }).first();
if (await pinToggle.count()) {
await cursor.clickLocator(pinToggle);
await page.waitForTimeout(400);
}
// Click a point on the map (center-ish). The map container fills the main area.
const map = page.locator(".leaflet-container").first();
const box = await map.boundingBox();
const target = { x: box.x + box.width * 0.5, y: box.y + box.height * 0.5 };
// Drive the real storm-history fetch and wait for it, so the panel fill is on camera.
const waitFetch = page
.waitForResponse((r) => /\/api\/storm-history/.test(r.url()), { timeout: 15000 })
.catch(() => null); // tolerate cached/no-fetch; the pin still renders
await cursor.click(target.x, target.y, 800);
await waitFetch;
await page.waitForTimeout(2500); // hold on the populated panel
// Pan the cursor to the populated history panel for emphasis.
const panel = page.getByText(/storm history/i).first();
if (await panel.count()) await cursor.clickLocator(panel, 700);
await page.waitForTimeout(1500);
};