import React from 'react'; import { AbsoluteFill, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig } from 'remotion'; import { palette } from './contract'; const FONT = 'Archivo'; const CLAMP = { extrapolateLeft: 'clamp', extrapolateRight: 'clamp' } as const; const rgba = (hex: string, alpha: number): string => { const n = parseInt(hex.replace('#', ''), 16); return `rgba(${(n >> 16) & 255}, ${(n >> 8) & 255}, ${n & 255}, ${alpha})`; }; const ease = (frame: number, from: number, to: number, start: number, end: number): number => interpolate(frame, [start, end], [from, to], CLAMP); const fade = (frame: number, inStart: number, inEnd: number, outStart: number, outEnd: number): number => Math.min(ease(frame, 0, 1, inStart, inEnd), ease(frame, 1, 0, outStart, outEnd)); const captions = [ { text: 'Plan together', start: 0, end: 60 }, { text: 'Protect focus time', start: 60, end: 120 }, { text: 'See progress', start: 120, end: 180 }, ]; const days = ['MON', 'TUE', 'WED', 'THU', 'FRI']; const blocks = [ { c: 0, r: 0, t: 'Kickoff' }, { c: 0, r: 1, t: 'Roadmap' }, { c: 1, r: 0, t: 'Design review' }, { c: 2, r: 0, t: 'Build sprint' }, { c: 3, r: 0, t: 'Client sync' }, { c: 4, r: 0, t: 'Retro' }, { c: 1, r: 1, t: 'Handoff' }, { c: 3, r: 1, t: 'Hiring' }, ]; const meters = [ { label: 'Scope', value: 72, color: palette.accent }, { label: 'Reviews', value: 48, color: palette.secondary }, { label: 'Launch', value: 90, color: palette.accent }, ]; const Backdrop: React.FC = () => { const frame = useCurrentFrame(); const gx = ease(frame, 30, 70, 0, 360); const gy = ease(frame, 35, 65, 0, 360); const line = rgba(palette.foreground, 0.035); return ( ); }; const IntroScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const title = spring({ frame, fps, config: { damping: 200 } }); const rule = ease(frame, 0, 180, 20, 48); const tag = spring({ frame: Math.max(0, frame - 30), fps, config: { damping: 200 } }); return (
Relay
Make room for focused work
); }; const Captions: React.FC<{ frame: number }> = ({ frame }) => { const active = frame < 60 ? 0 : frame < 120 ? 1 : 2; return ( <> {captions.map((c, i) => { const o = fade(frame, c.start, c.start + 12, c.end - 10, c.end); const y = ease(frame, 28, 0, c.start, c.start + 12); return (
{`0${i + 1}`}
{c.text}
); })}
{captions.map((c, i) => (
))}
); }; const Interface: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => { const enter = spring({ frame, fps, config: { damping: 200 } }); const focusH = ease(frame, 0, 190, 62, 92); const focusO = ease(frame, 0, 1, 60, 66); const focusTextO = ease(frame, 0, 1, 84, 100); const panelO = ease(frame, 0, 1, 112, 124); return (
This week
{[palette.accent, palette.secondary, palette.muted].map((col, i) => (
))}
{days.map((d, i) => (
{d}
))} {blocks.map((b, k) => { const p = spring({ frame: Math.max(0, frame - (6 + k * 6)), fps, config: { damping: 200 } }); return (
{b.t}
); })}
Focus time
{meters.map((m, i) => { const p = ease(frame, 0, 1, 118 + i * 10, 168 + i * 10); const pct = Math.round(m.value * p); return (
{m.label} {`${pct}%`}
); })}
); }; const ProductScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); return ( ); }; const FinalScene: React.FC = () => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const s1 = spring({ frame: Math.max(0, frame - 4), fps, config: { damping: 200 } }); const s2 = spring({ frame: Math.max(0, frame - 14), fps, config: { damping: 200 } }); const opacity = ease(frame, 0, 1, 0, 12); return (
Start your next week
with Relay
Relay
); }; export const BenchmarkVideo: React.FC = () => { return ( ); };