import React from 'react'; import { AbsoluteFill, Easing, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'; import { palette } from './contract'; const FONT = 'Archivo, sans-serif'; const clamp = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const; const easeOut = Easing.bezier(0.16, 1, 0.3, 1); const Mark: React.FC<{ size: number }> = ({ size }) => (
); const Background: React.FC = () => { const frame = useCurrentFrame(); const drift = Math.sin(frame / 60) * 40; const drift2 = Math.cos(frame / 75) * 50; const gridShift = (frame * 0.4) % 80; return (
); }; /* ---------- Scene 1 ---------- */ const Intro: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const out = interpolate(frame, [76, 90], [1, 0], clamp); const outY = interpolate(frame, [76, 90], [0, -24], { ...clamp, easing: Easing.in(Easing.cubic) }); const markS = spring({ frame, fps, config: { damping: 14, stiffness: 120 } }); const wordP = interpolate(frame, [8, 34], [0, 1], { ...clamp, easing: easeOut }); const line = interpolate(frame, [22, 60], [0, 1], { ...clamp, easing: easeOut }); const words = ['Make', 'room', 'for', 'focused', 'work']; // baton dot travels along the line const dotX = interpolate(frame, [30, 75], [0, 1], { ...clamp, easing: Easing.inOut(Easing.cubic) }); return (
Relay
28 ? 1 : 0, }} />
{words.map((w, i) => { const p = interpolate(frame, [30 + i * 5, 46 + i * 5], [0, 1], { ...clamp, easing: easeOut }); return ( {w} ); })}
); }; /* ---------- Interface pieces ---------- */ const Cursor: React.FC<{ x: number; y: number; color: string; name: string; opacity?: number }> = ({ x, y, color, name, opacity = 1, }) => (
{name}
); const Bar: React.FC<{ w: number | string; h?: number; color?: string; style?: React.CSSProperties }> = ({ w, h = 10, color = palette.muted, style, }) => (
); const PlanPanel: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const colX = (c: number) => 28 + c * 234; const slotY = (s: number) => 70 + s * 108; const heads = ['Ideas', 'This week', 'Next']; const cards: { c: number; s: number; accent?: boolean; moving?: boolean; w: number }[] = [ { c: 0, s: 0, w: 150 }, { c: 0, s: 1, w: 120 }, { c: 0, s: 2, w: 160, moving: true }, { c: 1, s: 0, w: 140 }, { c: 1, s: 1, w: 170, accent: true }, { c: 2, s: 0, w: 130 }, ]; const moveP = interpolate(frame, [64, 100], [0, 1], { ...clamp, easing: Easing.inOut(Easing.cubic) }); const mx = colX(0) + (colX(1) - colX(0)) * moveP; const cursorIn = interpolate(frame, [30, 60], [0, 1], { ...clamp, easing: easeOut }); const c1x = interpolate(cursorIn, [0, 1], [640, colX(0) + 150]) + (colX(1) - colX(0)) * moveP; const c1y = interpolate(cursorIn, [0, 1], [420, slotY(2) + 50]); const c2x = 520 + Math.sin(frame / 18) * 18; const c2y = 330 + Math.cos(frame / 14) * 14; return (
{heads.map((h, i) => { const p = interpolate(frame, [i * 5, i * 5 + 16], [0, 1], { ...clamp, easing: easeOut }); return (
{h}
); })} {cards.map((cd, i) => { const s = spring({ frame: frame - 8 - i * 5, fps, config: { damping: 16, stiffness: 140 } }); const x = cd.moving ? mx : colX(cd.c); const lift = cd.moving ? Math.sin(moveP * Math.PI) : 0; const landed = cd.moving && moveP >= 1; return (
0 ? `0 ${20 * lift}px ${40 * lift}px #00000066` : 'none', opacity: s, transform: `scale(${0.85 + 0.15 * s + lift * 0.04})`, padding: 16, boxSizing: 'border-box', }} >
); })}
); }; const FocusPanel: React.FC = () => { const frame = useCurrentFrame(); const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri']; const colW = 127; const colX = (i: number) => 28 + i * (colW + 12); const meetings = [ { c: 0, t: 20, h: 80 }, { c: 1, t: 150, h: 70 }, { c: 2, t: 20, h: 60 }, { c: 3, t: 250, h: 90 }, { c: 4, t: 120, h: 70 }, { c: 2, t: 330, h: 70 }, ]; const focus = [ { c: 0, t: 130, h: 170 }, { c: 1, t: 250, h: 170 }, { c: 2, t: 110, h: 200 }, { c: 3, t: 20, h: 190 }, { c: 4, t: 220, h: 190 }, ]; const top = 76; return (
{days.map((d, i) => (
{d}
))} {days.map((d, i) => (
))} {meetings.map((m, i) => { const p = interpolate(frame, [i * 3, i * 3 + 14], [0, 1], { ...clamp, easing: easeOut }); return (
); })} {focus.map((f, i) => { const p = interpolate(frame, [24 + i * 7, 24 + i * 7 + 22], [0, 1], { ...clamp, easing: easeOut }); return (
Focus
); })}
); }; const ProgressPanel: React.FC = () => { const frame = useCurrentFrame(); const R = 96; const C = 2 * Math.PI * R; const ring = interpolate(frame, [6, 56], [0, 0.72], { ...clamp, easing: easeOut }); const rows = [ { label: 'Launch plan', v: 0.85, color: palette.accent }, { label: 'Design review', v: 0.6, color: palette.secondary }, { label: 'Research', v: 0.95, color: palette.accent }, { label: 'Handoff', v: 0.35, color: palette.secondary }, ]; const ringP = interpolate(frame, [0, 14], [0, 1], clamp); return (
Progress
{rows.map((r, i) => { const p = interpolate(frame, [10 + i * 6, 50 + i * 6], [0, 1], { ...clamp, easing: easeOut }); const e = interpolate(frame, [i * 6, i * 6 + 14], [0, 1], clamp); return (
{r.label}
); })}
); }; const AppWindow: React.FC<{ step: number }> = ({ step }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const enter = spring({ frame, fps, config: { damping: 18, stiffness: 90 } }); const out = interpolate(frame, [166, 180], [1, 0], clamp); const navItems = ['Plan', 'Focus', 'Progress']; return (
{[palette.muted, palette.muted, palette.muted].map((c, i) => (
))}
Relay ยท Week plan
{navItems.map((n, i) => { const active = i === step; return (
{n}
); })}
); }; const PanelFade: React.FC<{ children: React.ReactNode; last?: boolean }> = ({ children, last }) => { const frame = useCurrentFrame(); const a = interpolate(frame, [0, 8], [0, 1], clamp); const b = last ? 1 : interpolate(frame, [52, 60], [1, 0], clamp); return
{children}
; }; const Caption: React.FC<{ index: number; title: string }> = ({ index, title }) => { const frame = useCurrentFrame(); const last = index === 2; const inP = interpolate(frame, [0, 20], [0, 1], { ...clamp, easing: easeOut }); const outP = last ? 1 : interpolate(frame, [50, 60], [1, 0], clamp); return (
0{index + 1}
{title}
); }; const Features: React.FC = () => { const frame = useCurrentFrame(); const out = interpolate(frame, [166, 180], [1, 0], clamp); const titles = ['Plan together', 'Protect focus time', 'See progress']; const step = Math.min(2, Math.floor(frame / 60)); return (
{titles.map((t, i) => ( ))}
{titles.map((t, i) => { const p = interpolate(frame, [i * 60, i * 60 + 60], [0, 1], clamp); return (
); })}
); }; /* ---------- Final ---------- */ const Outro: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const markS = spring({ frame, fps, config: { damping: 14, stiffness: 110 } }); const lines = [ [{ t: 'Start', a: false }, { t: 'your', a: false }, { t: 'next', a: false }, { t: 'week', a: false }], [{ t: 'with', a: false }, { t: 'Relay', a: true }], ]; const line = interpolate(frame, [26, 56], [0, 1], { ...clamp, easing: easeOut }); let idx = 0; return (
{lines.map((ln, li) => (
{ln.map((w) => { const i = idx++; const p = interpolate(frame, [4 + i * 4, 24 + i * 4], [0, 1], { ...clamp, easing: easeOut }); return ( {w.t} ); })}
))}
); }; export const BenchmarkVideo: React.FC = () => { return ( ); };