import type { FC } from "react";
import { useCurrentFrame } from "remotion";
import { COLOR, DRAW } from "../brand/tokens";
import { inOut, mix, out, progress } from "../illustrations/motion";
import { FilmStage } from "./FilmStage";
import { CAPTIONS, SHOT_FRAMES, SURFACE, T, cameraAt, glow, untilFold } from "./shot";
/**
* Close up, a block wobbles on another: Jenga. The camera pulls back: it is a whole leaning tower,
* the app, swaying with every change. It stops, a light rises through it, the blocks line up one
* by one, and two rails keep it straight.
*/
const BLOCK = { width: 230, height: 44, pitch: 52, base: 290 } as const;
/** How far each block sits off centre while the tower is crooked. */
const OFFSETS = [0, 14, -10, 22, -18, 26, -8, 30, -22] as const;
const TOP = OFFSETS.length - 1;
const SWAY_FRAMES = 46;
const SWAY_DEGREES = 2.2;
const ALIGN_EVERY = 8;
const RAIL_GAP = 42;
const blockY = (index: number): number => BLOCK.base - BLOCK.height / 2 - index * BLOCK.pitch;
const alignStart = (index: number): number => T.resolve + index * ALIGN_EVERY;
const LAST_ALIGN = alignStart(TOP) + 22;
const Drawing: FC = () => {
const frame = useCurrentFrame();
const camera = cameraAt(frame, 2.4, 1, { x: 0, y: blockY(TOP) + 30 });
const { zoom, hair, others } = camera;
const folded = frame >= T.fold;
/** How much the tower moves: it stops at the freeze and starts again for the loop. */
const restless = folded ? progress(frame, T.fold + 16, SHOT_FRAMES) : 1 - progress(frame, T.freeze, T.turn);
const wave = Math.sin((frame / SWAY_FRAMES) * Math.PI * 2);
const crooked = (index: number): number =>
folded ? inOut(progress(frame, T.fold, T.fold + 40)) : 1 - inOut(progress(frame, alignStart(index), alignStart(index) + 22));
const scan = progress(frame, T.turn, LAST_ALIGN);
const scanShown = Math.min(progress(frame, T.turn, T.turn + 10), 1 - progress(frame, LAST_ALIGN - 10, LAST_ALIGN));
const rails = out(progress(frame, T.calm, T.calm + 40)) * untilFold(frame);
const top = blockY(TOP) - BLOCK.height / 2 - 20;
return (
{[-1, 1].map((side) => (
0 ? 1 : 0}
style={glow(COLOR.done, 10, zoom)}
/>
))}
{OFFSETS.map((offset, index) => {
const off = crooked(index);
// The top block wobbles on its own: that is the opening picture.
const wobble = index === TOP ? Math.sin((frame / (SWAY_FRAMES / 2)) * Math.PI * 2) * 3.5 * restless : 0;
const justAligned = folded ? 0 : Math.sin(Math.PI * progress(frame, alignStart(index), alignStart(index) + 30));
const y = blockY(index);
return (
= TOP - 1 ? 1 : others}
transform={`translate(${offset * off} 0) rotate(${wobble} ${-BLOCK.width / 2} ${y + BLOCK.height / 2})`}
>
0.1 ? DRAW.mark : DRAW.fg}
strokeOpacity={SURFACE.strokeOpacity + justAligned * 0.25}
strokeWidth={hair * (1 + justAligned * 0.6)}
/>
);
})}
{scanShown > 0 && (
)}
);
};
/** Making an app reliable: from a leaning tower that sways with every change to one that stands. */
export const AppFilm: FC = () => (
);