From a9e5a8ad965b922ecae8fc9df16bc569a734b9a8 Mon Sep 17 00:00:00 2001 From: Andras Schmelczer Date: Tue, 12 May 2026 07:44:47 +0100 Subject: [PATCH] Update storyboard --- frontend/src/i18n/locales/de.ts | 12 ++--- frontend/src/i18n/locales/en.ts | 12 ++--- frontend/src/i18n/locales/fr.ts | 12 ++--- frontend/src/i18n/locales/hi.ts | 10 ++-- frontend/src/i18n/locales/hu.ts | 12 ++--- frontend/src/i18n/locales/zh.ts | 12 ++--- video/src/storyboard.ts | 82 ++++++--------------------------- 7 files changed, 49 insertions(+), 103 deletions(-) diff --git a/frontend/src/i18n/locales/de.ts b/frontend/src/i18n/locales/de.ts index 008395e..7a00ff6 100644 --- a/frontend/src/i18n/locales/de.ts +++ b/frontend/src/i18n/locales/de.ts @@ -202,7 +202,7 @@ const de: Translations = { clearAllSavePrompt: 'Möchtest du deine aktuellen Filter vor dem Löschen speichern?', saveAndClear: 'Speichern & löschen', clearWithoutSaving: 'Ohne Speichern löschen', - withoutThisFilter: '+{{value}} ohne diesen Filter', + filtersOut: 'filtert {{value}} heraus', schoolType: 'Schultyp', schoolRating: 'Schulbewertung', schoolDistance: 'Schulentfernung', @@ -898,7 +898,7 @@ const de: Translations = { Properties: 'Immobilien', Transport: 'Verkehr', Education: 'Bildung', - 'Area characteristics': 'Gebietsmerkmale', + 'Area development': 'Gebietsentwicklung', Crime: 'Kriminalität', Neighbours: 'Nachbarn', Amenities: 'Infrastruktur', @@ -939,7 +939,7 @@ const de: Translations = { 'Hervorragende weiterführende Schulen im Umkreis von 5 km', 'Education, Skills and Training Score': 'Score für Bildung, Kompetenzen und Ausbildung', - // ─ Feature names (Area characteristics) ─ + // ─ Feature names (Area development) ─ 'Income Score': 'Einkommensscore', 'Employment Score': 'Beschäftigungsscore', 'Health Deprivation and Disability Score': 'Score für Gesundheit und Behinderung', @@ -995,9 +995,9 @@ const de: Translations = { Schools: 'Schulen', 'Specific crimes': 'Einzelne Delikte', Ethnicities: 'Ethnien', - 'POI distance': 'POI-Entfernung', - 'POIs within 2km': 'POIs innerhalb von 2 km', - 'POIs within 5km': 'POIs innerhalb von 5 km', + 'Amenity distance': 'Entfernung zu Infrastruktur', + 'Amenities within 2km': 'Infrastruktur im Umkreis von 2 km', + 'Amenities within 5km': 'Infrastruktur im Umkreis von 5 km', // ─ Enum values ─ Detached: 'Freistehend', diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index bc8dbd7..8f49814 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -196,7 +196,7 @@ const en = { clearAllSavePrompt: 'Would you like to save your current filters before clearing?', saveAndClear: 'Save & Clear', clearWithoutSaving: 'Clear without saving', - withoutThisFilter: '+{{value}} without this filter', + filtersOut: 'filters out {{value}}', schoolType: 'School type', schoolRating: 'School rating', schoolDistance: 'School distance', @@ -882,7 +882,7 @@ const en = { Properties: 'Properties', Transport: 'Transport', Education: 'Education', - 'Area characteristics': 'Area characteristics', + 'Area development': 'Area development', Crime: 'Crime', Neighbours: 'Neighbours', Amenities: 'Amenities', @@ -921,7 +921,7 @@ const en = { 'Outstanding secondary schools within 5km': 'Outstanding secondary schools within 5km', 'Education, Skills and Training Score': 'Education, Skills and Training Score', - // ─ Feature names (Area characteristics) ─ + // ─ Feature names (Area development) ─ 'Income Score': 'Income Score', 'Employment Score': 'Employment Score', 'Health Deprivation and Disability Score': 'Health Deprivation and Disability Score', @@ -975,9 +975,9 @@ const en = { Schools: 'Schools', 'Specific crimes': 'Specific crimes', Ethnicities: 'Ethnicities', - 'POI distance': 'POI distance', - 'POIs within 2km': 'POIs within 2km', - 'POIs within 5km': 'POIs within 5km', + 'Amenity distance': 'Amenity distance', + 'Amenities within 2km': 'Amenities within 2km', + 'Amenities within 5km': 'Amenities within 5km', // ─ Enum values ─ Detached: 'Detached', diff --git a/frontend/src/i18n/locales/fr.ts b/frontend/src/i18n/locales/fr.ts index 3ee698e..c3274ce 100644 --- a/frontend/src/i18n/locales/fr.ts +++ b/frontend/src/i18n/locales/fr.ts @@ -203,7 +203,7 @@ const fr: Translations = { clearAllSavePrompt: 'Souhaitez-vous sauvegarder vos filtres actuels avant de les effacer ?', saveAndClear: 'Sauvegarder et effacer', clearWithoutSaving: 'Effacer sans sauvegarder', - withoutThisFilter: '+{{value}} sans ce filtre', + filtersOut: 'exclut {{value}}', schoolType: 'Type d’école', schoolRating: 'Note de l’école', schoolDistance: 'Distance de l’école', @@ -901,7 +901,7 @@ const fr: Translations = { Properties: 'Propriétés', Transport: 'Transports', Education: 'Éducation', - 'Area characteristics': 'Caractéristiques du quartier', + 'Area development': 'Développement du quartier', Crime: 'Criminalité', Neighbours: 'Voisins', Amenities: 'Commodités', @@ -940,7 +940,7 @@ const fr: Translations = { 'Outstanding secondary schools within 5km': 'Collèges/lycées Excellent dans un rayon de 5 km', 'Education, Skills and Training Score': 'Score éducation, compétences et formation', - // ─ Feature names (Area characteristics) ─ + // ─ Feature names (Area development) ─ 'Income Score': 'Score de revenu', 'Employment Score': 'Score d’emploi', 'Health Deprivation and Disability Score': 'Score de santé et handicap', @@ -994,9 +994,9 @@ const fr: Translations = { Schools: 'Écoles', 'Specific crimes': 'Crimes spécifiques', Ethnicities: 'Origines ethniques', - 'POI distance': 'Distance aux POI', - 'POIs within 2km': 'POI à moins de 2 km', - 'POIs within 5km': 'POI à moins de 5 km', + 'Amenity distance': 'Distance aux commodités', + 'Amenities within 2km': 'Commodités à moins de 2 km', + 'Amenities within 5km': 'Commodités à moins de 5 km', // ─ Enum values ─ Detached: 'Individuelle', diff --git a/frontend/src/i18n/locales/hi.ts b/frontend/src/i18n/locales/hi.ts index a7dbd64..5d97adb 100644 --- a/frontend/src/i18n/locales/hi.ts +++ b/frontend/src/i18n/locales/hi.ts @@ -188,7 +188,7 @@ const hi: Translations = { clearAllSavePrompt: 'क्या साफ करने से पहले आप अपने मौजूदा फिल्टर सहेजना चाहेंगे?', saveAndClear: 'सहेजें और साफ करें', clearWithoutSaving: 'बिना सहेजे साफ करें', - withoutThisFilter: '+{{value}} इस फिल्टर के बिना', + filtersOut: '{{value}} को फिल्टर करता है', schoolType: 'स्कूल प्रकार', schoolRating: 'स्कूल रेटिंग', schoolDistance: 'स्कूल दूरी', @@ -833,7 +833,7 @@ const hi: Translations = { Properties: 'संपत्तियां', Transport: 'परिवहन', Education: 'शिक्षा', - 'Area characteristics': 'क्षेत्र की विशेषताएँ', + 'Area development': 'क्षेत्र विकास', Crime: 'अपराध', Neighbours: 'पड़ोसी', Amenities: 'सुविधाएं', @@ -911,9 +911,9 @@ const hi: Translations = { Schools: 'स्कूल', 'Specific crimes': 'विशिष्ट अपराध', Ethnicities: 'जातीय समूह', - 'POI distance': 'POI दूरी', - 'POIs within 2km': '2 किमी के अंदर POI', - 'POIs within 5km': '5 किमी के अंदर POI', + 'Amenity distance': 'सुविधा दूरी', + 'Amenities within 2km': '2 किमी के अंदर सुविधाएं', + 'Amenities within 5km': '5 किमी के अंदर सुविधाएं', Detached: 'अलग मकान', 'Semi-Detached': 'अर्ध-स्वतंत्र मकान', Terraced: 'कतारबद्ध मकान', diff --git a/frontend/src/i18n/locales/hu.ts b/frontend/src/i18n/locales/hu.ts index 9ef85c6..6fe1cd6 100644 --- a/frontend/src/i18n/locales/hu.ts +++ b/frontend/src/i18n/locales/hu.ts @@ -200,7 +200,7 @@ const hu: Translations = { clearAllSavePrompt: 'Szeretnéd menteni a jelenlegi szűrőket a törlés előtt?', saveAndClear: 'Mentés és törlés', clearWithoutSaving: 'Törlés mentés nélkül', - withoutThisFilter: '+{{value}} e szűrő nélkül', + filtersOut: '{{value}} elemet kiszűr', schoolType: 'Iskolatípus', schoolRating: 'Iskolai értékelés', schoolDistance: 'Iskolatávolság', @@ -893,7 +893,7 @@ const hu: Translations = { Properties: 'Ingatlanok', Transport: 'Közlekedés', Education: 'Oktatás', - 'Area characteristics': 'Területi jellemzők', + 'Area development': 'Területi fejlődés', Crime: 'Bűnözés', Neighbours: 'Szomszédok', Amenities: 'Szolgáltatások', @@ -932,7 +932,7 @@ const hu: Translations = { 'Outstanding secondary schools within 5km': 'Kiemelkedő középiskolák 5 km-en belül', 'Education, Skills and Training Score': 'Oktatás, készségek és képzés pontszám', - // ─ Feature names (Area characteristics) ─ + // ─ Feature names (Area development) ─ 'Income Score': 'Jövedelmi pontszám', 'Employment Score': 'Foglalkoztatottsági pontszám', 'Health Deprivation and Disability Score': 'Egészségügyi depriváció és fogyatékosság pontszám', @@ -986,9 +986,9 @@ const hu: Translations = { Schools: 'Iskolák', 'Specific crimes': 'Konkrét bűncselekmények', Ethnicities: 'Etnikai csoportok', - 'POI distance': 'POI-távolság', - 'POIs within 2km': 'POI-k 2 km-en belül', - 'POIs within 5km': 'POI-k 5 km-en belül', + 'Amenity distance': 'Szolgáltatás-távolság', + 'Amenities within 2km': 'Szolgáltatások 2 km-en belül', + 'Amenities within 5km': 'Szolgáltatások 5 km-en belül', // ─ Enum values ─ Detached: 'Különálló', diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index e5100fe..4f28b59 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -195,7 +195,7 @@ const zh: Translations = { clearAllSavePrompt: '是否要在清除前保存当前的筛选条件?', saveAndClear: '保存并清除', clearWithoutSaving: '不保存直接清除', - withoutThisFilter: '+{{value}} 不使用此筛选条件', + filtersOut: '筛除 {{value}}', schoolType: '学校类型', schoolRating: '学校评级', schoolDistance: '学校距离', @@ -863,7 +863,7 @@ const zh: Translations = { Properties: '房产', Transport: '交通', Education: '教育', - 'Area characteristics': '区域特征', + 'Area development': '区域发展', Crime: '犯罪', Neighbours: '邻居', Amenities: '配套设施', @@ -901,7 +901,7 @@ const zh: Translations = { 'Outstanding secondary schools within 5km': '5公里内优秀中学数量', 'Education, Skills and Training Score': '教育、技能和培训得分', - // ─ Feature names (Area characteristics) ─ + // ─ Feature names (Area development) ─ 'Income Score': '收入得分', 'Employment Score': '就业得分', 'Health Deprivation and Disability Score': '健康与残障得分', @@ -955,9 +955,9 @@ const zh: Translations = { Schools: '学校', 'Specific crimes': '具体犯罪', Ethnicities: '族裔', - 'POI distance': 'POI 距离', - 'POIs within 2km': '2 公里内 POI', - 'POIs within 5km': '5 公里内 POI', + 'Amenity distance': '配套设施距离', + 'Amenities within 2km': '2 公里内配套设施', + 'Amenities within 5km': '5 公里内配套设施', // ─ Enum values ─ Detached: '独立式住宅', diff --git a/video/src/storyboard.ts b/video/src/storyboard.ts index ca2e1f3..4bdc855 100644 --- a/video/src/storyboard.ts +++ b/video/src/storyboard.ts @@ -21,23 +21,9 @@ import { el, type Storyboard } from './script.js'; * spacing is controlled here via `gapBeforeMs` (silence in audio) plus * optional `tail` activities (visual movement after the caption hides, * before the next cue's gap). - * - * Sum of `during` declared durations MUST be ≤ measured cue duration. If - * synth comes back tighter than the activities can fit, the runner throws - * with a pointer to the offending cue — bump that cue's text, lengthen its - * gapBefore, or trim a during step. - * - * Reference durations (Qwen3-TTS / British male narrator, 2026-05-09): - * cue 0 1920ms "Describe the life you want." - * cue 1 2720ms "Every matching neighbourhood, side by side." - * cue 2 2160ms "Tighten the commute to 20 minutes." - * cue 3 1840ms "Drill into a single block." - * cue 4 4480ms "Stats, listings, Street View, price history…" - * cue 5 1760ms "Take the shortlist into Excel." - * cue 6 4400ms "Perfect Postcode. Find where you actually want to live." */ -const PROMPT_TEXT = 'Flats or terraces <£450k, 35 min to Manchester, low crime'; +const PROMPT_TEXT = 'Flats <£300k, 35 min to commute Manchester close to an outstanding school in a quite low crime area'; const BRAND = { name: 'Perfect Postcode', @@ -45,59 +31,42 @@ const BRAND = { url: 'https://perfect-postcode.co.uk', }; -// 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. + const AI_ZOOM_SCALE = 2.4; -// 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. const TT_CARD_SELECTOR = '[data-filter-name="tt_0"]'; const TT_SLIDER_MAX = 120; -const TT_DRAG_FROM_MIN = 35; // matches AI stub max below +const TT_DRAG_FROM_MIN = 35; const TT_DRAG_TO_MIN = 20; -// Calm British male narrator. Matches what tts/synth.py used to default to; -// kept identical so existing audio caches don't invalidate on first run. const BRITISH_MALE_NARRATOR = - 'Calm, professional middle-aged Chinese male narrator with a ' + - 'strong Chinese accent. Even, measured pace; warm but ' + - 'understated; product-demo register. Do not laugh, sigh, gasp, or add ' + + 'Calm but cheerful, professional middle-aged British male narrator from the North with a ' + + 'strong Manchester accent. Even, measured pace; warm but and smiling voice; product-demo register. Do not laugh, sigh, gasp, or add ' + 'filler sounds; no audible breaths between sentences.'; const DEFAULT_CUES: Storyboard['cues'] = [ - // -- Scene 1: AI prompt ---------------------------------------------- - // Cue 0 is short (1920ms) — caption shows alone, then typing + submit - // happen silently in the tail. The natural beat is: viewer hears the - // brief, then watches the prompt being typed. { - text: 'Describe the life you want.', + text: 'Start by describing the type of place you\'re looking for', gapBeforeMs: 0, tail: [ - { kind: 'wait', durationMs: 140 }, { kind: 'type', selector: '[data-tutorial="ai-filters"] textarea', text: PROMPT_TEXT, durationMs: 3000, }, - { kind: 'wait', durationMs: 140 }, { kind: 'submitForm', formSelector: '[data-tutorial="ai-filters"] form', durationMs: 1700 }, - { kind: 'wait', durationMs: 700 }, ], }, - - // -- Scene 2: zoom out reveal --------------------------------------- { - text: 'Every matching neighbourhood, side by side.', + text: 'The dashboard will show you the likeliest places that will meet your expectations', gapBeforeMs: 400, during: [{ kind: 'zoomReset', durationMs: 1400 }], - tail: [{ kind: 'wait', durationMs: 1200 }], + tail: [{ kind: 'wait', durationMs: 500 }], }, - // -- Scene 3: travel-time slider ------------------------------------ { - text: `Tighten the commute to ${TT_DRAG_TO_MIN} minutes.`, + text: `Adjust the filters to narrow down to the best candidates`, gapBeforeMs: 500, during: [ { @@ -105,17 +74,14 @@ const DEFAULT_CUES: Storyboard['cues'] = [ thumbSelector: `${TT_CARD_SELECTOR} [role="slider"] >> nth=1`, trackSelector: `${TT_CARD_SELECTOR} [data-orientation="horizontal"] >> nth=0`, toFraction: TT_DRAG_TO_MIN / TT_SLIDER_MAX, - durationMs: 1400, + durationMs: 1000, }, ], - tail: [{ kind: 'wait', durationMs: 1200 }], + tail: [{ kind: 'wait', durationMs: 400 }], }, - // -- Scene 4a: deep zoom into a hexagon ----------------------------- - // The mapZoom barely fits (1500ms vs cue 1840ms); cursor prep happens - // earlier in this cue's during, the click + payoff dwell are in tail. { - text: 'Drill into a single block.', + text: 'And now it\'s time to dig into the details. Looks good to me!', gapBeforeMs: 500, during: [ { kind: 'cursorScale', scale: 1.4, durationMs: 200 }, @@ -130,7 +96,7 @@ const DEFAULT_CUES: Storyboard['cues'] = [ // Wait for the post-zoom /api/postcodes response and a redraw // before the click — otherwise the click can fire on a stale // frame and miss the polygon. - { kind: 'wait', durationMs: 1200 }, + { kind: 'wait', durationMs: 500 }, { kind: 'click', target: { kind: 'point', x: 1140, y: 605 }, @@ -142,19 +108,8 @@ const DEFAULT_CUES: Storyboard['cues'] = [ ], }, - // -- Scene 4b: right-pane payoff ----------------------------------- - // 4480ms cue, no during — the camera holds on the populated right pane - // for the whole climax line. Tail dwells before the export beat. { - text: 'Stats, listings, Street View, price history — all in one pane.', - gapBeforeMs: 0, - tail: [{ kind: 'wait', durationMs: 1200 }], - }, - - // -- Scene 5: export ------------------------------------------------ - // 1760ms cue. zoomReset + click together fit (1700ms); 60ms padding. - { - text: 'Take the shortlist into Excel.', + text: 'Now you can take your shortlist and start looking for your next home in your perfect postcode.', gapBeforeMs: 500, during: [ { kind: 'zoomReset', durationMs: 900 }, @@ -167,7 +122,6 @@ const DEFAULT_CUES: Storyboard['cues'] = [ tail: [{ kind: 'wait', durationMs: 800 }], }, - // -- Scene 6: outro ------------------------------------------------- { text: `${BRAND.name}. ${BRAND.tagline}`, gapBeforeMs: 600, @@ -185,8 +139,6 @@ const DEFAULT_CUES: Storyboard['cues'] = [ ]; const DEFAULT_PRE: Storyboard['pre'] = [ - // Camera push-in to the AI box happens before the first caption — silent - // setup keeps the cold open from feeling rushed. { kind: 'clearVignette', durationMs: 0 }, { kind: 'wait', durationMs: 200 }, { @@ -219,10 +171,6 @@ export const storyboards: Storyboard[] = [ voice: { instruct: BRITISH_MALE_NARRATOR, language: 'English', - // Sampling pinned for cue-to-cue consistency. Lower temp/top_p make - // the decoder less likely to sample non-speech tokens (laughter, - // random noise) at the cost of slightly flatter intonation. Seed - // makes runs reproducible. temperature: 0.6, topP: 0.9, seed: 42, @@ -230,8 +178,6 @@ export const storyboards: Storyboard[] = [ content: { promptText: PROMPT_TEXT, aiZoomScale: AI_ZOOM_SCALE, - // Initial map view used while we navigate. The AI scene zooms in on - // the sidebar so this only matters once we zoom out. initialMapView: { lat: 53.4795, lon: -2.2451, zoom: 11.5 }, // Filters returned by the AI stub. Keys MUST match real feature names // from /api/features (verified against the running server's schema).