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,
}) => (
);
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 (
);
})}
);
};
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 (
{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 (
);
})}
);
};
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 (
);
})}
);
};
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 (
);
};
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 (
);
};