import React from 'react';
import {Img, interpolate, staticFile, useCurrentFrame} from 'remotion';
import {S, C, FONT, MONO} from '../lib/tokens';
import {cl, easeOut, ramp, spr, mix} from '../lib/anim';
import {McBox, useDebug} from '../lib/debug';
import {useFormat} from '../lib/format-context';
import {geometry} from '../lib/format.mjs';
const src = (s: string) => (s.startsWith('http') ? s : staticFile(s));
// Card that rises in with a slight 3D tilt. Optional mac title bar.
export const Card: React.FC<{at: number; w: number; h: number; title?: string; out?: number; children?: React.ReactNode; style?: React.CSSProperties; allowOverlap?: boolean}> = ({at, w, h, title, out, children, style, allowOverlap}) => {
const f = useCurrentFrame(); const M = S.motion.card; const p = ramp(f, at, M.frames, easeOut);
const q = out !== undefined ? ramp(f, out, 12) : 0;
return
{title !== undefined &&
{['#ff5f57', '#febc2e', '#28c840'].map((c) =>
)}
{title}
}
{children}
;
};
export const Pill: React.FC<{at: number; children: React.ReactNode; out?: number; style?: React.CSSProperties; size?: number}> = ({at, children, out, style, size = 24}) => {
const f = useCurrentFrame(); const s = spr(f, at, S.motion.pill.spring); const p = ramp(f, at, 10);
const q = out !== undefined ? ramp(f, out, 10) : 0;
return {children};
};
export const Keycap: React.FC<{at: number; children: React.ReactNode; size?: number}> = ({at, children, size = 30}) => {
const f = useCurrentFrame(); const s = spr(f, at, {damping: 12, stiffness: 180, mass: 0.5});
const press = interpolate(f - at, [8, 11, 16], [0, 1, 0], cl); const p = ramp(f, at, 8);
return {children}
;
};
// Number counting up to a REAL value. Always pair with for the source.
export const Counter: React.FC<{at: number; to: number; from?: number; dur?: number; size?: number; suffix?: string; decimals?: number; color?: string}> = ({at, to, from = 0, dur = S.motion.number.duration, size = 140, suffix = '', decimals = 0, color = C.ink}) => {
const f = useCurrentFrame(); const p = ramp(f, at, dur); const v = (from + (to - from) * p).toFixed(decimals); const dbg = useDebug();
return {Number(v).toLocaleString('en-US', {minimumFractionDigits: decimals})}{suffix};
};
// Strike a word, then show its replacement (pattern: "not X. Y.")
export const Strike: React.FC<{at: number; appear?: number; word: string; size?: number; color?: string}> = ({at, appear, word, size = 76, color = C.danger}) => {
const f = useCurrentFrame(); const p = ramp(f, at, 10); const dbg = useDebug(); const a = ramp(f, appear ?? at - 12, 12);
return
{word}
;
};
export const CheckItem: React.FC<{at: number; text: string; done?: number; size?: number; allowOverlap?: boolean}> = ({at, text, done, size = 22, allowOverlap}) => {
const f = useCurrentFrame(); const s = spr(f, at, {damping: 12, stiffness: 160, mass: 0.6}); const p = ramp(f, at, 8);
const d = done !== undefined ? ramp(f, done, 10) : 0; const dbg = useDebug();
return
0 ? C.success : C.cardBar, display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#fff', fontSize: size * 0.7, transform: `scale(${0.7 + 0.3 * d})`}}>{d > 0.5 ? '✓' : ''}
0.6 ? 'line-through' : 'none'}}>{text}
;
};
export const StepCards: React.FC<{at: number; steps: string[]; gap?: number; step?: number;vertical?:boolean}> = ({at, steps, gap = 48, step = 10,vertical=false}) => (
);
// Call to action badge, e.g. "Comment MAU"
export const Callout: React.FC<{at: number; label: string; big: string; note?: string}> = ({at, label, big, note}) => {
const f = useCurrentFrame(); const s = spr(f, at, {damping: 10, stiffness: 150, mass: 0.7}); const p = ramp(f, at, 10);
const pulse = 1 + Math.max(0, Math.sin((f - at - 20) / 9)) * 0.02 * (f > at + 20 ? 1 : 0);
return ;
};
// Source credit, bottom-left, small. Required for every real number or third-party example.
export const Credit: React.FC<{at: number; children: React.ReactNode}> = ({at, children}) => {
const f = useCurrentFrame(); const p = ramp(f, at, 12);const {format,platform}=useFormat();const g=geometry(format,platform);
return
{children}
;
};
// Real footage inside a player card. Use a JPG sequence (frames/001.jpg...) for bulletproof renders.
export const Frames: React.FC<{dir: string; count: number; at: number; fps?: number; w: number; h: number}> = ({dir, count, at, fps = 30, w, h}) => {
const f = useCurrentFrame(); const i = Math.max(0, Math.min(count - 1, Math.floor((f - at) * fps / 30)));
return
;
};