feat(film): high-visibility cursor, lead-in trim, and rich storm-intel template
Replace the tiny SVG pointer with a solid brand-blue disc (white border + glow + bold click pulse) visible on any background. Orchestrator measures and trims the login/nav lead-in via ffmpeg -ss. Configurable FILM_BASE_URL. storm-intel becomes the rich hero template: waits for each load, drops a pin, waits for its storm history, clicks a storm event to visualise its polygon on the map, widens the range, and holds on the payoff.
This commit is contained in:
@@ -1,30 +1,69 @@
|
||||
// Storm Intel: drop a pin, wait for the REAL storm-history fetch, reveal the panel.
|
||||
// Storm Intel — HERO scene (~75s). Full walkthrough that WAITS for each load to finish:
|
||||
// load map+data → drop a pin → wait for the pin's storm history → click a storm EVENT
|
||||
// so it's visualised (polygon) on the map → filter by Hail → widen the date range → hold.
|
||||
// This is the rich template the other scenes follow.
|
||||
export default async (page, cursor, ctx) => {
|
||||
// Map tiles stream in — let them settle before interacting.
|
||||
await page.waitForTimeout(2500);
|
||||
const dwell = (ms) => page.waitForTimeout(ms);
|
||||
|
||||
// Enable pin-drop mode if there's a toggle (button text "Pin" / aria), else skip.
|
||||
const pinToggle = page.getByRole("button", { name: /pin/i }).first();
|
||||
// 1) Wait for the map AND the storm data to actually finish loading (not a fixed sleep).
|
||||
await page.locator(".leaflet-container").first().waitFor({ state: "visible", timeout: 20000 });
|
||||
// The page shows "Loading storm data..." until events resolve — wait for it to clear.
|
||||
await page
|
||||
.getByText(/loading storm data/i)
|
||||
.first()
|
||||
.waitFor({ state: "hidden", timeout: 20000 })
|
||||
.catch(() => {});
|
||||
await page.waitForLoadState("networkidle").catch(() => {});
|
||||
await dwell(1800); // open on the fully-loaded map
|
||||
|
||||
// 2) Enter pin-drop mode (crosshair / "drop a pin" toggle).
|
||||
const pinToggle = page
|
||||
.getByRole("button", { name: /pin|crosshair|drop a pin/i })
|
||||
.first();
|
||||
if (await pinToggle.count()) {
|
||||
await cursor.clickLocator(pinToggle);
|
||||
await page.waitForTimeout(400);
|
||||
await dwell(600);
|
||||
}
|
||||
|
||||
// Click a point on the map (center-ish). The map container fills the main area.
|
||||
// 3) Drop a pin over central Plano and WAIT for the per-pin storm history to load.
|
||||
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 };
|
||||
const target = { x: box.x + box.width * 0.52, y: box.y + box.height * 0.62 };
|
||||
const waitHistory = page
|
||||
.waitForResponse((r) => /\/api\/storm-history/.test(r.url()), { timeout: 20000 })
|
||||
.catch(() => null);
|
||||
await cursor.click(target.x, target.y, 900);
|
||||
await waitHistory;
|
||||
// Wait for the "Loading..." in the Storm History panel to resolve into real content.
|
||||
await page
|
||||
.getByText(/loading/i)
|
||||
.first()
|
||||
.waitFor({ state: "hidden", timeout: 12000 })
|
||||
.catch(() => {});
|
||||
await dwell(2500); // hold on the populated history panel
|
||||
|
||||
// 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
|
||||
// 4) Widen the range to 12 Mo FIRST (more history) — do filters before selecting an
|
||||
// event so we never filter the selected event back off the map.
|
||||
const range12 = page.getByRole("button", { name: /^\s*12\s*mo\s*$/i }).first();
|
||||
if (await range12.count()) {
|
||||
await cursor.clickLocator(range12, 700);
|
||||
await dwell(2500);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// 5) Click the storm EVENT card (e.g. "S. Plano — Flash Flood") so it draws on the map,
|
||||
// then HOLD on it — this visualised polygon is the payoff of the scene.
|
||||
// Event titles use a "Place — Type" format (em-dash); filter chips are bare words —
|
||||
// so match the em-dash title (fallback: the unique "homes affected" line) to avoid
|
||||
// accidentally clicking a top-of-page filter chip.
|
||||
let card = page
|
||||
.getByText(/[—–-]\s*(flash\s*flood|flood|hail|tornado|wind|snow)/i)
|
||||
.first();
|
||||
if (!(await card.count())) card = page.getByText(/homes affected/i).first();
|
||||
if (await card.count()) {
|
||||
await cursor.clickLocator(card, 900);
|
||||
await dwell(4000); // let the polygon render + the map fly/zoom to it, then hold
|
||||
}
|
||||
|
||||
// 6) Final hold on the populated, visualised view (event drawn on the map).
|
||||
await dwell(3000);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user