The homepage read as sections to scroll past. The automation chapter now opens the way the social media films do: one continuous shot on a dark stage that stays in place while the visitor scrolls. Scrolling moves the camera (close on a dot hopping between two squares, pull back to a whole office carrying things from tool to tool, freeze, lines take over) and changes the statement; a second clock keeps the dots moving when the visitor stops. The statements are the approved ones from the film. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
56 lines
1.9 KiB
TypeScript
56 lines
1.9 KiB
TypeScript
/**
|
|
* Motion helpers for the scroll films. All pure functions of a frame or a time,
|
|
* the same ones the social media films use, so both move alike.
|
|
*/
|
|
|
|
export type Point = { readonly x: number; readonly y: number };
|
|
|
|
const clamp01 = (value: number): number => Math.min(1, Math.max(0, value));
|
|
|
|
/** 0 before `from`, 1 after `to`, linear in between. */
|
|
export const progress = (frame: number, from: number, to: number): number =>
|
|
clamp01((frame - from) / (to - from));
|
|
|
|
export const mix = (from: number, to: number, t: number): number =>
|
|
from + (to - from) * t;
|
|
|
|
export const inOut = (t: number): number =>
|
|
t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
|
|
|
|
export const out = (t: number): number => 1 - (1 - t) ** 3;
|
|
|
|
const easeInOut = (t: number): number =>
|
|
t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2;
|
|
|
|
const round = (value: number): number => Math.round(value * 100) / 100;
|
|
|
|
const wrap = (index: number, length: number): number =>
|
|
((index % length) + length) % length;
|
|
|
|
/**
|
|
* Where something is that hops from stop to stop in an arc, round and round.
|
|
* It rests on `stops[0]` at time 0 and reaches the next stop every
|
|
* `framesPerHop`. The arc rises by `arcRatio` times the distance covered.
|
|
*/
|
|
export const hopAt = (
|
|
time: number,
|
|
stops: readonly Point[],
|
|
framesPerHop: number,
|
|
arcRatio: number,
|
|
): Point => {
|
|
const hop = Math.floor(time / framesPerHop);
|
|
const from = stops[wrap(hop, stops.length)];
|
|
const to = stops[wrap(hop + 1, stops.length)];
|
|
if (!from || !to) return { x: 0, y: 0 };
|
|
const t = easeInOut(time / framesPerHop - hop);
|
|
const distance = Math.hypot(to.x - from.x, to.y - from.y);
|
|
// Rounded: sine and square root differ in the last digit between the server
|
|
// and the browser, and the first frame is rendered by both.
|
|
return {
|
|
x: round(mix(from.x, to.x, t)),
|
|
y: round(
|
|
mix(from.y, to.y, t) - Math.sin(Math.PI * t) * distance * arcRatio,
|
|
),
|
|
};
|
|
};
|