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 = () => ( );