import React from 'react'; import { AbsoluteFill, Easing, interpolate, useCurrentFrame } from 'remotion'; import { FakeDashboard, G } from '../../_fixtures/Fixtures'; // split-flap-flip: airport split-flap display. Each character is a dark flap cell (top/bottom halves); // each cell flips through 3 garbled intermediate states then clicks to a stop on the target character, cascading left→right 4f apart like a wave. // Beat: 0–21 build-up (whole row of garbled characters held still) → cascade flips start at 22 → all stopped by 78 → still to 140. const TEXT = 'SHIP FASTER'; const CHARSET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#$%&'; const START = 22; // cascade start frame const STAGGER = 4; // cascade delay between characters const FLIP = 5; // duration of a single flap const NFLIP = 3; // 3 flips per character (2 garbled intermediate states + 1 landing on the target) const CELL_W = 118; const CELL_H = 156; // Seeded sine hash (no Math.random) const rnd = (a: number) => { const x = Math.sin(a * 127.3) * 43758.5453; return x - Math.floor(x); }; const garble = (i: number, k: number) => CHARSET[Math.floor(rnd(i * 7.13 + k * 3.71 + 1) * CHARSET.length)]; const FLAP_BG = '#262624'; const FLAP_INK = '#f4f4f2'; // Half cell: top/bottom halves each overflow hidden; the full glyph inside is offset by half a cell to expose the matching half const Half: React.FC<{ ch: string; part: 'top' | 'bottom' }> = ({ ch, part }) => (
{ch}
); const FlapCell: React.FC<{ target: string; i: number; frame: number }> = ({ target, i, frame, }) => { // This cell's character sequence: 2 garbled → 1 garbled → target (the initial state is also garbled, visible during build-up) const seq = [garble(i, 0), garble(i, 1), garble(i, 2), target]; const local = frame - (START + i * STAGGER); const done = local >= NFLIP * FLIP; // Settle click: the whole cell dips and rebounds (1px is imperceptible; scaled to 6px so the "click" reads) const clickY = done ? interpolate(local, [15, 17, 19, 22], [0, 6, -1.5, 0], { extrapolateLeft: 'clamp', extrapolateRight: 'clamp', easing: Easing.out(Easing.quad), }) : 0; let topCh = seq[0]; let bottomCh = seq[0]; let flap: React.ReactNode = null; if (done) { topCh = target; bottomCh = target; } else if (local > 0) { const k = Math.min(NFLIP - 1, Math.floor(local / FLIP)); const from = seq[k]; const to = seq[k + 1]; const p = Easing.in(Easing.quad)((local - k * FLIP) / FLIP); // gravity feel: falls faster over time topCh = to; // top half static: once the flap opens, the next character's top half is exposed bottomCh = from; // bottom half static: keeps the old character until the active leaf covers it if (p < 0.5) { // First half: the old character's top leaf drops 0→-90 const deg = p * 2 * 90; flap = (
); } else { // Second half: the new character's bottom leaf slaps 90→0 over the old bottom half const deg = 90 - (p - 0.5) * 2 * 90; flap = (
); } } return (
{flap} {/* Center hinge line */}
); }; export const SplitFlapFlip: React.FC = () => { const frame = useCurrentFrame(); let letterIdx = 0; return ( {/* Dimmed fake background page to highlight the flap board */}
{TEXT.split('').map((ch, idx) => { if (ch === ' ') { return
; } const i = letterIdx++; return ; })}
); };