This commit is contained in:
Andras Schmelczer 2026-06-10 22:25:15 +01:00
parent 1241132095
commit 54a5c3ca9a
28 changed files with 826 additions and 422 deletions

View file

@ -43,12 +43,14 @@ type FormFactor = 'desktop' | 'mobile';
* length, padding short `during` blocks with a trailing wait.
*/
// School-count features as served by live /api/features TODAY. The data
// pipeline has already moved to modelled catchment counts ("Good+ primary
// school catchments"), so flip these two constants when that deploy lands
// on prod — preflight will fail loudly if the names drift from the API.
const SCHOOL_GOOD_PRIMARY = 'Good+ primary schools within 2km';
const SCHOOL_OUTSTANDING_PRIMARY = 'Outstanding primary schools within 2km';
// School features as served by live /api/features. The data pipeline moved
// to modelled catchment counts ("Good+ primary school catchments"), which the
// local stack already serves; prod still serves the older "…within 2km" names
// until that deploy lands. Preflight fails loudly if these drift from
// whichever API render.sh is pointed at — flip them back if you render against
// prod before the catchment model deploys there.
const SCHOOL_GOOD_PRIMARY = 'Good+ primary school catchments';
const SCHOOL_OUTSTANDING_PRIMARY = 'Outstanding primary school catchments';
// Cold-open lean-in on the AI card. Desktop only; kept moderate so the
// map remains visible on the right (zoomTo clamps the pan so the app
@ -59,10 +61,10 @@ const TT_CARD_SELECTOR = '[data-filter-name="tt_0"]';
const TT_SLIDER_MAX = 120;
const TT_DRAG_FROM_MIN = 35;
// 25 (not 20): tight enough that the drag visibly prunes the map, loose
// enough that street-level Manchester keeps plenty of matching postcodes —
// at 20 the brief emptied the centre and the postcode tap had nothing
// fresh to land on (the drawer then opened in its "filtered stats are
// empty" fallback).
// enough that street-level central London keeps plenty of matching
// postcodes — at 20 the brief emptied the centre and the postcode tap had
// nothing fresh to land on (the drawer then opened in its "filtered stats
// are empty" fallback).
const TT_DRAG_TO_MIN = 25;
// Where on the map the demo zoom-in lands. Desktop targets a fixed pixel
@ -138,8 +140,8 @@ const RECORDING_LOCALIZATIONS: Record<RecordingLocale, RecordingLocalization> =
voiceReferenceText:
"Welcome to the demonstration. This is the narrator voice you'll hear throughout the video.",
promptText:
'First home under £315k, 35 min to Manchester, good schools, low crime, quiet street, fast broadband',
travelTimeLabel: 'Manchester city centre',
'First home under £600k, 35 min to central London, good schools, low crime, quiet street, fast broadband',
travelTimeLabel: 'Central London',
exportButtonTitle: 'Export to Excel',
exportConfirmLabel: 'Export',
closeDrawerLabel: 'Close drawer',
@ -173,8 +175,8 @@ const RECORDING_LOCALIZATIONS: Record<RecordingLocale, RecordingLocalization> =
voiceReferenceText:
'Willkommen zur Demonstration. Diese Sprecherstimme hörst du im gesamten Video.',
promptText:
'Wohnung unter £300k, 35 Min. nach Manchester, gute Schulen, niedrige Kriminalität, ruhige Straßen',
travelTimeLabel: 'Stadtzentrum Manchester',
'Wohnung unter £600k, 35 Min. ins Zentrum von London, gute Schulen, niedrige Kriminalität, ruhige Straßen',
travelTimeLabel: 'Zentrum von London',
exportButtonTitle: 'Nach Excel exportieren',
exportConfirmLabel: 'Exportieren',
closeDrawerLabel: 'Drawer schließen',
@ -206,8 +208,8 @@ const RECORDING_LOCALIZATIONS: Record<RecordingLocale, RecordingLocalization> =
'Calm and cheerful Mandarin Chinese male narrator with clear standard Mandarin ' +
'pronunciation and a friendly, practical delivery.',
voiceReferenceText: '欢迎观看演示。整段视频都会使用这位旁白的声音。',
promptText: '30万英镑以内的公寓35分钟到曼彻斯特,学校好,犯罪率低,街道安静',
travelTimeLabel: '曼彻斯特市中心',
promptText: '60万英镑以内的公寓35分钟到伦敦市中心,学校好,犯罪率低,街道安静',
travelTimeLabel: '伦敦市中心',
exportButtonTitle: '导出为 Excel',
exportConfirmLabel: '导出',
closeDrawerLabel: '关闭侧栏',
@ -237,8 +239,8 @@ const RECORDING_LOCALIZATIONS: Record<RecordingLocale, RecordingLocalization> =
'and a friendly, practical delivery.',
voiceReferenceText:
"Welcome to the demonstration. This is the narrator voice you'll hear throughout the video.",
promptText: 'Flat under £300k, 35 min to Manchester, good schools, low crime, quiet streets',
travelTimeLabel: 'Manchester city centre',
promptText: 'Flat under £600k, 35 min to central London, good schools, low crime, quiet streets',
travelTimeLabel: 'Central London',
exportButtonTitle: 'Excel में export करें',
exportConfirmLabel: 'Export',
closeDrawerLabel: 'ड्रॉअर बंद करें',
@ -520,7 +522,7 @@ function buildVideoConfig(formFactor: FormFactor): VideoConfig {
outputFps: 50,
minDurationS: 10,
maxDurationS: 75,
// Right-pane inspection: Manchester map, filters applied, data pane
// Right-pane inspection: London map, filters applied, data pane
// open — the clearest paused-state preview.
posterTimeS: 31,
};
@ -536,20 +538,22 @@ function createRecordingStoryboard(
): Storyboard {
const copy = RECORDING_LOCALIZATIONS[locale];
// Mobile bumps the initial zoom from 11.5 → 12 so the narrower 540-wide
// viewport still shows a Manchester-metro slice densely populated with
// hexagons (otherwise the visible map gets dominated by Pennine moors
// on the east edge with no matches).
// viewport still shows an inner-London slice densely populated with
// hexagons (otherwise the visible map gets dominated by the low-density
// outer edges with no matches).
const initialZoom = formFactor === 'mobile' ? 12 : 11.5;
// On mobile the MobileBottomSheet covers the bottom ~44% of the
// viewport, so the map's geographic centre sits roughly at the seam
// between the visible map and the sheet. Shift the centre lat ~0.04°
// south so Manchester city centre (53.4795) lands in the upper half of
// the visible map area instead of getting hidden under the sheet. The
// south so central London (51.515) lands in the upper half of the
// visible map area instead of getting hidden under the sheet. The
// desktop layout already has the map dominate the viewport, so it
// keeps the original centre.
const mapLat = formFactor === 'mobile' ? 53.4395 : 53.4795;
const mapLon = -2.2451;
// keeps the original centre. -0.13 lon centres on Holborn/Bloomsbury,
// between the West End and the City, so the Bank-station travel filter
// and the deep zoom-in both land on richly matched inner-London blocks.
const mapLat = formFactor === 'mobile' ? 51.475 : 51.515;
const mapLon = -0.13;
return {
name: storyboardName(copy, formFactor),
@ -572,10 +576,15 @@ function createRecordingStoryboard(
// from /api/features (preflight validates against the live server).
stubbedFilters: {
'Property type': ['Flats/Maisonettes', 'Semi-Detached'],
'Estimated current price': [0, 315000],
// Loose enough to keep the Manchester map richly populated — a cap
// of 20 emptied the city centre and left the zoom with nothing to
// land on.
// £600k (not £315k like the old Manchester cut): central London is
// pricier, and at £315k the price+crime combo emptied the inner
// boroughs. At £600k ~51k postcodes pass in-frame, dropping to ~10k
// once the commute is dragged to 25 min — a visible prune that still
// leaves the zoom something to land on (verified via /api/filter-counts).
'Estimated current price': [0, 600000],
// Loose enough to keep the central-London map richly populated — a
// cap of 20 emptied the city centre and left the zoom with nothing
// to land on.
'Serious crime (avg/yr)': [0, 40],
[SCHOOL_GOOD_PRIMARY]: [1, 10],
'Noise (dB)': [0, 65],
@ -584,11 +593,13 @@ function createRecordingStoryboard(
'Max available download speed (Mbps)': [30, 1000],
},
// Travel-time filters returned by the AI stub. Slug matches the real
// /api/travel-destinations?mode=transit response.
// /api/travel-destinations?mode=transit response. Bank tube station is
// the central-London transit anchor served by the local stack (the
// older city-level "manchester" slug only exists on prod's fuller data).
stubbedTravelTimeFilters: [
{
mode: 'transit',
slug: 'manchester',
slug: 'bank-tube-station',
label: copy.travelTimeLabel,
max: TT_DRAG_FROM_MIN,
},