Improve FAQ & video rendering, tighten homepage and CSS
This commit is contained in:
parent
05a1f316e1
commit
c69bb0d614
48 changed files with 4689 additions and 1077 deletions
125
video/src/scenes.ts
Normal file
125
video/src/scenes.ts
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import type { Page } from 'playwright';
|
||||
import {
|
||||
PROMPT_TEXT,
|
||||
DRAG_FILTER_NAME,
|
||||
DRAG_TO_FRACTION,
|
||||
} from './config.js';
|
||||
import {
|
||||
clearVignette,
|
||||
hideCaption,
|
||||
showCaption,
|
||||
showOutro,
|
||||
} from './dom.js';
|
||||
import { fakeType, sleep, smoothMove, smoothDragSliderThumb } from './motion.js';
|
||||
|
||||
export interface SceneCtx {
|
||||
page: Page;
|
||||
cursor: { x: number; y: number };
|
||||
}
|
||||
|
||||
/** Cold open. Vignette fades; cursor parks at a "natural" rest position. */
|
||||
export async function sceneColdOpen(ctx: SceneCtx): Promise<void> {
|
||||
await clearVignette(ctx.page);
|
||||
await ctx.page.mouse.move(ctx.cursor.x, ctx.cursor.y);
|
||||
await sleep(1100);
|
||||
}
|
||||
|
||||
/**
|
||||
* AI prompt scene: click the collapsed AI box, type the prompt, submit,
|
||||
* watch the (stubbed) response apply.
|
||||
*/
|
||||
export async function sceneAiPrompt(ctx: SceneCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
|
||||
await showCaption(page, 'Describe the area you want.');
|
||||
|
||||
const aiButton = page.locator('[data-tutorial="ai-filters"] button').first();
|
||||
const btnBox = await aiButton.boundingBox();
|
||||
if (!btnBox) throw new Error('AI button not found');
|
||||
|
||||
const target = { x: btnBox.x + btnBox.width / 2, y: btnBox.y + btnBox.height / 2 };
|
||||
await smoothMove(page, ctx.cursor, target, { durationMs: 400 });
|
||||
ctx.cursor = target;
|
||||
|
||||
await page.mouse.click(target.x, target.y);
|
||||
|
||||
const textarea = page.locator('[data-tutorial="ai-filters"] textarea');
|
||||
await textarea.waitFor({ state: 'visible', timeout: 3000 });
|
||||
await sleep(120);
|
||||
|
||||
const taBox = await textarea.boundingBox();
|
||||
if (taBox) {
|
||||
const into = { x: taBox.x + 30, y: taBox.y + taBox.height / 2 };
|
||||
await smoothMove(page, ctx.cursor, into, { durationMs: 220 });
|
||||
ctx.cursor = into;
|
||||
}
|
||||
|
||||
// fakeType runs the typing animation inside the browser to avoid CDP
|
||||
// round-trip overhead per keystroke (which can quadruple total typing time).
|
||||
await fakeType(page, '[data-tutorial="ai-filters"] textarea', PROMPT_TEXT, 35);
|
||||
await sleep(180);
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await sleep(700);
|
||||
await hideCaption(page);
|
||||
await sleep(150);
|
||||
}
|
||||
|
||||
/**
|
||||
* Slider scene: pan to a numeric filter's right thumb and drag it inward.
|
||||
* The whole point: the user sees the map react in real time to a human action,
|
||||
* driving home that AI sets a starting point but you stay in control.
|
||||
*/
|
||||
export async function sceneSliderControl(ctx: SceneCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
await showCaption(page, 'You stay in control.');
|
||||
|
||||
const card = page.locator(`[data-filter-name="${DRAG_FILTER_NAME}"]`);
|
||||
await card.waitFor({ state: 'visible', timeout: 3000 });
|
||||
await card.scrollIntoViewIfNeeded();
|
||||
await sleep(120);
|
||||
|
||||
const thumbSelector = `[data-filter-name="${DRAG_FILTER_NAME}"] [role="slider"] >> nth=1`;
|
||||
const trackSelector = `[data-filter-name="${DRAG_FILTER_NAME}"] [data-orientation="horizontal"] >> nth=0`;
|
||||
|
||||
ctx.cursor = await smoothDragSliderThumb(
|
||||
page,
|
||||
thumbSelector,
|
||||
trackSelector,
|
||||
ctx.cursor,
|
||||
DRAG_TO_FRACTION,
|
||||
1100
|
||||
);
|
||||
|
||||
await sleep(550);
|
||||
await hideCaption(page);
|
||||
await sleep(150);
|
||||
}
|
||||
|
||||
/** Property reveal: click a postcode on the map to open the side pane with charts. */
|
||||
export async function scenePropertyReveal(ctx: SceneCtx): Promise<void> {
|
||||
const { page } = ctx;
|
||||
const viewport = page.viewportSize() ?? { width: 1920, height: 1080 };
|
||||
|
||||
const target = {
|
||||
x: 360 + (viewport.width - 360) * 0.55,
|
||||
y: viewport.height * 0.5,
|
||||
};
|
||||
|
||||
await smoothMove(page, ctx.cursor, target, { durationMs: 500 });
|
||||
ctx.cursor = target;
|
||||
|
||||
await page.mouse.click(target.x, target.y);
|
||||
await sleep(1300);
|
||||
}
|
||||
|
||||
/** Outro: full-screen logo card with brand + URL. */
|
||||
export async function sceneOutro(ctx: SceneCtx): Promise<void> {
|
||||
await showOutro(
|
||||
ctx.page,
|
||||
'Perfect Postcodes',
|
||||
'Find where you actually want to live.',
|
||||
'perfectpostcodes.com'
|
||||
);
|
||||
await sleep(1800);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue