Add video

This commit is contained in:
Andras Schmelczer 2026-05-05 22:15:29 +01:00
parent 589de0c5ac
commit 7c36cbfdd4
18 changed files with 2292 additions and 333 deletions

View file

@ -7,44 +7,98 @@ export const OUTPUT_DIR = 'output';
const aspect = process.env.ASPECT ?? '16x9';
export const VIEWPORT =
aspect === '9x16' ? { width: 1080, height: 1920 } : { width: 1920, height: 1080 };
export const CAPTURE_SCALE = Math.max(1, Number(process.env.CAPTURE_SCALE ?? 1.5));
export const VIDEO_SIZE = {
width: VIEWPORT.width,
height: VIEWPORT.height,
};
export const WEBM_BITRATE = process.env.WEBM_BITRATE ?? (CAPTURE_SCALE > 1 ? '18M' : '8M');
// Cold-open prompt. Punchy version of the user's intent — short enough that
// the typing animation fits in the AI scene without throttling pushing past
// the trim window. Each char costs ~80ms wall under boot CPU load.
export const PROMPT_TEXT =
process.env.PROMPT_TEXT ?? 'Near Kings Cross, EPC C+, under £600k';
process.env.PROMPT_TEXT ?? 'Flats or terraces <£450k, 35 min to Manchester, low crime';
// Filter the AI stub will "return". Keys must match real feature names from
// /api/features. Pulled from the running server's schema.
// Filters returned by the AI stub. Keys MUST match real feature names from
// /api/features (verified against the running server's schema).
export const STUBBED_FILTERS: Record<string, [number, number] | string[]> = {
'Estimated current price': [0, 600000],
'Number of bedrooms & living rooms': [4, 6],
'Property type': ['Detached', 'Semi-Detached', 'Terraced'],
'Distance to nearest train or tube station (km)': [0, 1.0],
'Property type': ['Flats/Maisonettes', 'Terraced'],
'Estimated current price': [175000, 450000],
'Serious crime per 1k residents (avg/yr)': [0, 55],
'Noise (dB)': [50, 68],
};
// Slider we'll drag in scene 3. Must be a numeric (range) feature, and must
// already be in STUBBED_FILTERS so the card is mounted by the time we drag.
export const DRAG_FILTER_NAME =
process.env.DRAG_FILTER_NAME ?? 'Estimated current price';
// Fraction of the track to drag the right thumb to (0..1 from the left).
export const DRAG_TO_FRACTION = 0.55;
// Travel-time filters returned by the AI stub. Slug matches the real
// /api/travel-destinations?mode=transit response.
export const STUBBED_TRAVEL_TIME_FILTERS: {
mode: 'transit' | 'car' | 'bicycle' | 'walking';
slug: string;
label: string;
min?: number;
max?: number;
}[] = [
{
mode: 'transit',
slug: 'manchester',
label: 'Manchester city centre',
max: 35,
},
];
// London-ish view used for the cold open.
export const COLD_OPEN_VIEW = '#lat=51.535&lon=-0.105&zoom=11';
// The travel-time card we'll drag manually after AI applies. The Filters
// component renders each travel-time entry with `data-filter-name="tt_${i}"`,
// and our stub only sets one entry, so it's tt_0.
export const TT_CARD_SELECTOR = '[data-filter-name="tt_0"]';
export const TT_SLIDER_MIN = 0;
export const TT_SLIDER_MAX = 120;
export const TT_DRAG_FROM_MIN = 35; // matches AI stub max above
export const TT_DRAG_TO_MIN = 20;
// Hard cap on the trimmed output. Scene-time overhead (CDP roundtrips,
// boundingBox calls, layout settling) varies run-to-run, so we trim to a
// deterministic length even if total scene wall time exceeds it.
// Cold-open zoom: how aggressively to magnify the AI box.
// 2.4 fills most of the viewport with the prompt card without blowing up text.
export const AI_ZOOM_SCALE = Number(process.env.AI_ZOOM_SCALE ?? 2.4);
// Cluster scene: how many wheel ticks (deck.gl smooths each one) and the
// per-tick delay. ~5 ticks at -120 each gets us +2 zoom levels.
export const CLUSTER_ZOOM_TICKS = 5;
export const CLUSTER_ZOOM_DELTA = -120;
export const CLUSTER_ZOOM_TICK_MS = 90;
// Initial map view used while we navigate. The AI scene zooms in on the
// sidebar so this only matters once we zoom out.
export const INITIAL_MAP_VIEW = {
lat: 53.4795,
lon: -2.2451,
zoom: 11.5,
};
// Postcode pre-selected on page load. The dashboard reads ?pc= and:
// 1. fetches /api/postcode/{pc}
// 2. mapFlyToRef → zoom 16 over the postcode
// 3. handleLocationSearch → opens the right pane populated with that postcode
// We use this to guarantee the right pane is open by the time the cluster
// scene plays. The visual cursor click is then ceremonial — pane is real,
// data is real, only the causation is staged.
//
// M44FZ is in Ancoats/Northern Quarter: central enough to read as Manchester,
// and it still has matching properties after the commute is tightened to 20m.
export const PRELOAD_POSTCODE = process.env.PRELOAD_POSTCODE ?? 'M44FZ';
// Hard cap on the trimmed output. Keep the homepage demo tight; the render
// trims from the outro if a dev-server hiccup stretches a scene.
export const MAX_DURATION_S = Number(process.env.MAX_DURATION_S ?? 15);
// Slow down all interactions and animations by this factor while recording,
// then speed the output back up by the same factor in ffmpeg. The visible
// animation speed in the final video is unchanged, but each visual frame had
// N× more wall time to render → fewer dropped frames, smoother motion.
//
// 1 = no slow-down (choppy on software GL)
// 2 = double recording length, ~2× more unique frames in output (recommended)
// 3-4 = even smoother, slower to produce; diminishing returns past 4
export const RECORD_SCALE = Math.max(1, Number(process.env.RECORD_SCALE ?? 3));
// Slow down all interactions while recording, then speed the output back up
// in ffmpeg. 2x gives a real 50fps final video from Playwright's 25fps raw
// recorder without making the take painfully long.
export const RECORD_SCALE = Math.max(1, Number(process.env.RECORD_SCALE ?? 2));
// Target fps of the FINAL output. We force ffmpeg to interpolate up to this
// rate so the speed-up doesn't leave gaps.
export const OUTPUT_FPS = Number(process.env.OUTPUT_FPS ?? 60);
// Target fps of the FINAL output. With RECORD_SCALE=2 this matches the real
// captured frame cadence, so the MP4 does not need synthetic interpolation.
export const OUTPUT_FPS = Number(process.env.OUTPUT_FPS ?? 50);
// Brand strings for the outro card.
export const BRAND_NAME = 'Perfect Postcode';
export const BRAND_TAGLINE = 'Find where you actually want to live.';
export const BRAND_URL = 'https://perfect-postcode.co.uk';