import React from "react"; import { AbsoluteFill, Easing, Sequence, interpolate, spring, useCurrentFrame, useVideoConfig, } from "remotion"; import { palette } from "./contract"; const FONT = "Archivo, sans-serif"; const ci = ( v: number, input: number[], output: number[], easing?: (t: number) => number ) => interpolate(v, input, output, { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing, }); const alpha = (hex: string, a: number) => { const h = hex.replace("#", ""); const r = parseInt(h.slice(0, 2), 16); const g = parseInt(h.slice(2, 4), 16); const b = parseInt(h.slice(4, 6), 16); return `rgba(${r},${g},${b},${a})`; }; const Mark: React.FC<{ size: number; delay?: number }> = ({ size, delay = 0 }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const bars = [ { x: 0, c: palette.accent }, { x: 0.19, c: palette.secondary }, { x: 0.38, c: palette.foreground }, ]; const h = size * 0.2; const gap = size * 0.1; return (
{bars.map((b, i) => { const p = spring({ frame: frame - delay - i * 5, fps, config: { damping: 200, mass: 0.7 }, }); return (
); })}
); }; const Background: React.FC = () => { const frame = useCurrentFrame(); const dx = Math.sin(frame / 90) * 60; const dy = Math.cos(frame / 110) * 40; return ( ); }; const Intro: React.FC = () => { const f = useCurrentFrame(); const { fps } = useVideoConfig(); const letters = "Relay".split(""); const tagO = ci(f, [30, 48], [0, 1]); const tagY = ci(f, [30, 48], [24, 0], Easing.out(Easing.cubic)); const line = ci(f, [40, 66], [0, 1], Easing.inOut(Easing.cubic)); const exitO = ci(f, [76, 90], [1, 0]); const exitY = ci(f, [76, 90], [0, -40], Easing.in(Easing.cubic)); return (
{letters.map((l, i) => { const p = spring({ frame: f - 8 - i * 3, fps, config: { damping: 200 } }); return ( {l} ); })}
Make room for focused work
); }; const WIN_W = 1080; const WIN_H = 780; const PAD = 36; const GAP = 16; const COL_W = (WIN_W - PAD * 2 - GAP * 4) / 5; const COL_TOP = 176; const SLOT_Y = [0, 106, 212, 396]; const CARD_H = 92; const FOCUS_H = 170; const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri"]; type CardDef = { c: number; s: number; stripe: string; lw: number; done: number }; const CARDS: CardDef[] = [ { c: 0, s: 0, stripe: palette.secondary, lw: 0.72, done: 0 }, { c: 0, s: 1, stripe: palette.foreground, lw: 0.55, done: 1 }, { c: 0, s: 3, stripe: palette.muted, lw: 0.64, done: -1 }, { c: 1, s: 0, stripe: palette.foreground, lw: 0.6, done: 2 }, { c: 1, s: 1, stripe: palette.secondary, lw: 0.75, done: 3 }, { c: 2, s: 0, stripe: palette.secondary, lw: 0.5, done: 4 }, { c: 2, s: 1, stripe: palette.muted, lw: 0.7, done: 5 }, { c: 2, s: 3, stripe: palette.foreground, lw: 0.58, done: -1 }, { c: 3, s: 0, stripe: palette.muted, lw: 0.66, done: 6 }, { c: 3, s: 1, stripe: palette.secondary, lw: 0.52, done: -1 }, { c: 3, s: 3, stripe: palette.secondary, lw: 0.7, done: -1 }, { c: 4, s: 0, stripe: palette.foreground, lw: 0.62, done: -1 }, { c: 4, s: 1, stripe: palette.muted, lw: 0.48, done: -1 }, { c: 4, s: 3, stripe: palette.foreground, lw: 0.68, done: -1 }, ]; const AVATARS = [ { c: palette.secondary, t: "A" }, { c: palette.accent, t: "M" }, { c: palette.foreground, t: "J" }, { c: palette.muted, t: "K" }, ]; const colX = (c: number) => PAD + c * (COL_W + GAP); const Cursor: React.FC<{ x: number; y: number; color: string; label: string; opacity: number }> = ({ x, y, color, label, opacity, }) => (
{label}
); const Product: React.FC = () => { const f = useCurrentFrame(); const { fps } = useVideoConfig(); const enter = spring({ frame: f, fps, config: { damping: 200 } }); const exitO = ci(f, [166, 180], [1, 0]); const exitS = ci(f, [166, 180], [1, 0.96]); const act = [ ci(f, [52, 68], [1, 0]), ci(f, [52, 68, 112, 128], [0, 1, 1, 0]), ci(f, [112, 128], [0, 1]), ]; const captions = ["Plan together", "Protect focus time", "See progress"]; const capIn = ci(f, [4, 22], [0, 1]); const dim = ci(f, [62, 76, 116, 130], [1, 0.5, 0.5, 1]); const cursorO = ci(f, [14, 24, 50, 60], [0, 1, 1, 0]); const cm = ci(f, [14, 52], [0, 1], Easing.inOut(Easing.cubic)); const progress = ci(f, [124, 168], [0, 0.68], Easing.inOut(Easing.cubic)); const stripO = ci(f, [112, 128], [0.4, 1]); const ringR = 20; const ringC = 2 * Math.PI * ringR; return (
{captions.map((c, i) => { const a = act[i]; return (
0.5 ? palette.accent : palette.muted, }} > {`0${i + 1}`}
{c}
); })}
{/* Header */}
Relay
This week
{AVATARS.map((av, i) => { const p = spring({ frame: f - 10 - i * 4, fps, config: { damping: 12, mass: 0.6 } }); return (
{av.t}
); })}
{/* Day labels */} {DAYS.map((d, c) => { const p = ci(f, [6 + c * 3, 18 + c * 3], [0, 1]); return (
{d}
); })} {/* Focus slots */} {DAYS.map((d, c) => { const p = spring({ frame: f - 62 - c * 5, fps, config: { damping: 200 } }); const glow = ci(f, [70, 90, 120, 140], [0, 1, 1, 0.3]); const outlineO = ci(f, [20, 34], [0, 1]); return (
Focus
); })} {/* Cards */} {CARDS.map((card, idx) => { const delay = 8 + card.c * 4 + (card.s === 3 ? 2 : card.s) * 7; const p = spring({ frame: f - delay, fps, config: { damping: 18, mass: 0.6 } }); const check = card.done >= 0 ? spring({ frame: f - 128 - card.done * 4, fps, config: { damping: 12, mass: 0.5 } }) : 0; const stripe = check > 0.5 ? palette.accent : card.stripe; return (
); })} {/* Progress strip */}
Progress
); }; const Outro: React.FC = () => { const f = useCurrentFrame(); const { fps } = useVideoConfig(); const l1 = spring({ frame: f - 3, fps, config: { damping: 200 } }); const l2 = spring({ frame: f - 8, fps, config: { damping: 200 } }); const bar = ci(f, [14, 34], [0, 1], Easing.out(Easing.cubic)); const inO = ci(f, [0, 10], [0, 1]); return (
Start your next week
with Relay
); }; export const BenchmarkVideo: React.FC = () => { return ( ); };