This commit is contained in:
Andras Schmelczer 2026-05-14 08:09:19 +01:00
parent a8165249a4
commit a4103b0896
64 changed files with 5376 additions and 3832 deletions

View file

@ -60,6 +60,19 @@ export interface AdScene {
left?: AdScenePanel;
right?: AdScenePanel;
items?: AdSceneItem[];
/** Optional single hero photo (URL) shown above the title. */
image?: string;
/** Optional [left, right] photos for split mode — used by "two streets apart" style ads. */
images?: [string, string];
/** Optional caption shown under the image for attribution / context. */
imageCaption?: string;
/**
* If true, render the scene with a transparent background so the dashboard
* stays visible behind. Useful for hooks where you want a floating kicker
* + title without occluding the live product. Defaults to false (full
* scrim, used by the closing "title" cards).
*/
transparent?: boolean;
}
/** A point on screen, resolved at runtime to viewport pixels. */
@ -140,7 +153,19 @@ export type Activity =
/** Fade the ad overlay away. */
| { kind: 'hideAdScene'; durationMs: number }
/** Fade away the opening vignette. */
| { kind: 'clearVignette'; durationMs: number };
| { kind: 'clearVignette'; durationMs: number }
/**
* Smoothly scroll the closest scrollable ancestor of `selector` to
* absolute pixel `top`. Used to surface a specific filter card or to
* scroll through the property-stats drawer after a postcode click.
*/
| { kind: 'scrollPane'; selector: string; top: number; durationMs: number }
/**
* Click the header of a collapsible filter group (e.g. "Transport",
* "Education") so the cards beneath it become visible. Idempotent
* if the group is already open this is a no-op click.
*/
| { kind: 'openFilterGroup'; selector: string; durationMs: number };
/**
* A narration cue + the activities that play alongside it.
@ -281,12 +306,27 @@ export function viewportFor(video: VideoConfig): { width: number; height: number
}
/**
* Recorded video resolution. Equal to the CSS viewport because
* Playwright's recordVideo writes frames at CSS pixel size regardless
* of `deviceScaleFactor`. Kept as a separate function so future
* supersample + post-encode flows (e.g. ffmpeg lanczos upscale) can
* plug in here without touching verify.ts.
* Recorded video resolution. Equal to the CSS viewport.
*
* Playwright's recordVideo captures the page at its CSS-pixel surface, so
* passing a size larger than the viewport just letterboxes the content
* into the top-left of an empty frame not a true high-DPR raster.
* Final-resolution upscale (e.g. mobile 540x960 1080x1920) is done in
* render.sh's ffmpeg pass with `scale=...:flags=lanczos`, which gives a
* sharp upscale because Chromium rasterises internally at DPR=captureScale.
*/
export function recordedSizeFor(video: VideoConfig): { width: number; height: number } {
return viewportFor(video);
}
/**
* The final mp4's resolution (after the lanczos upscale pass in render.sh).
* Storyboards drive their on-screen typography from CSS viewport sizes, but
* social platforms care about the file resolution so we expose a
* separate getter for the published dimensions.
*/
export function publishedSizeFor(video: VideoConfig): { width: number; height: number } {
const viewport = viewportFor(video);
const scale = Math.max(1, Math.round(video.captureScale));
return { width: viewport.width * scale, height: viewport.height * scale };
}