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,
}) => (
);
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 */}
{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 (
);
})}
{/* 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 */}
);
};
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 (
);
};
export const BenchmarkVideo: React.FC = () => {
return (
);
};