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 withAlpha = (hex: string, alpha: number): string => {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};
const LogoMark: React.FC<{ size: number }> = ({ size }) => (
);
const PulseRings: React.FC<{ base: number; color: string }> = ({ base, color }) => {
const frame = useCurrentFrame();
return (
<>
{[0, 22].map((off) => {
const t = (frame + off) % 44;
const scale = interpolate(t, [0, 44], [0.8, 1.6]);
const op = interpolate(t, [0, 32, 44], [0.4, 0.1, 0], CLAMP);
return (
);
})}
>
);
};
const Intro: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const exitOp = interpolate(frame, [74, 89], [1, 0], CLAMP);
const logoS = spring({ frame, fps, delay: 2, config: { damping: 13, stiffness: 170 } });
const logoScale = interpolate(logoS, [0, 1], [0.4, 1]);
const logoOp = interpolate(logoS, [0, 0.4], [0, 1], CLAMP);
const titleS = spring({ frame, fps, delay: 9, config: { damping: 16, stiffness: 120 } });
const titleY = interpolate(titleS, [0, 1], [48, 0]);
const titleOp = interpolate(titleS, [0, 0.5], [0, 1], CLAMP);
const subOp = interpolate(frame, [30, 46], [0, 1], CLAMP);
const subY = interpolate(frame, [30, 48], [22, 0], CLAMP);
const lineW = interpolate(frame, [38, 60], [0, 230], CLAMP);
return (
Relay
Make room for focused work
);
};
type CardSpec = {
chip: number;
color: string;
lines: number[];
avatars: number;
done: boolean;
};
const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri"];
const AV = [palette.accent, palette.secondary, palette.muted];
const PROGRESS = [0.72, 0.9, 0.55, 0.8, 0.64];
const BOARD: CardSpec[][] = [
[
{ chip: 0.8, color: palette.secondary, lines: [0.85, 0.55], avatars: 2, done: true },
{ chip: 0.6, color: palette.accent, lines: [0.7], avatars: 1, done: false },
{ chip: 0.5, color: palette.muted, lines: [0.6], avatars: 1, done: false },
],
[
{ chip: 0.7, color: palette.accent, lines: [0.8, 0.5], avatars: 2, done: true },
{ chip: 0.55, color: palette.secondary, lines: [0.65], avatars: 2, done: false },
],
[
{ chip: 0.9, color: palette.secondary, lines: [0.6], avatars: 3, done: false },
{ chip: 0.5, color: palette.muted, lines: [0.75, 0.4], avatars: 1, done: true },
],
[
{ chip: 0.65, color: palette.muted, lines: [0.8], avatars: 1, done: false },
{ chip: 0.8, color: palette.secondary, lines: [0.55, 0.7], avatars: 2, done: true },
],
[
{ chip: 0.75, color: palette.accent, lines: [0.7, 0.45], avatars: 2, done: false },
{ chip: 0.5, color: palette.secondary, lines: [0.6], avatars: 1, done: true },
],
];
const FEATURES = ["Plan together", "Protect focus time", "See progress"];
const BoardCard: React.FC<{ card: CardSpec; ci: number; ri: number; frame: number; fps: number }> = ({
card,
ci,
ri,
frame,
fps,
}) => {
const d = 8 + ci * 5 + ri * 7;
const s = spring({ frame, fps, delay: d, config: { damping: 16, stiffness: 150 } });
const op = interpolate(s, [0, 0.5], [0, 1], CLAMP);
const sc = interpolate(s, [0, 1], [0.7, 1]);
const cd = 128 + ci * 4 + ri * 6;
const cs = spring({ frame, fps, delay: cd, config: { damping: 12, stiffness: 220 } });
return (
{card.lines.map((w, li) => (
))}
{Array.from({ length: card.avatars }).map((_, ai) => {
const avS = spring({ frame, fps, delay: d + 16 + ai * 4, config: { damping: 11, stiffness: 220 } });
const avOp = interpolate(avS, [0, 0.5], [0, 1], CLAMP);
return (
);
})}
{card.done ? (
) : null}
);
};
const FocusBlock: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const op =
interpolate(frame, [62, 74], [0, 1], CLAMP) * interpolate(frame, [112, 124], [1, 0], CLAMP);
const s = spring({ frame, fps, delay: 62, config: { damping: 14, stiffness: 130 } });
const sc = interpolate(s, [0, 1], [0.92, 1]);
const pulse = 1 + 0.018 * Math.sin(Math.max(0, frame - 74) / 5);
const glow = 0.22 + 0.14 * Math.sin(Math.max(0, frame - 74) / 6);
if (op <= 0) {
return null;
}
return (
);
};
const ProductWindow: React.FC<{ frame: number; fps: number }> = ({ frame, fps }) => {
const dim = interpolate(frame, [62, 74, 112, 124], [1, 0.4, 0.4, 1], CLAMP);
const pct = Math.round(interpolate(frame, [126, 158], [0, 68], CLAMP));
const pillOp = interpolate(frame, [122, 132], [0, 1], CLAMP);
const trackOp = interpolate(frame, [120, 130], [0, 1], CLAMP);
return (
{[withAlpha(palette.muted, 0.5), palette.secondary, palette.accent].map((c, i) => (
))}
Team week
{`${pct}% this week`}
{[64, 52, 70, 44, 58].map((w, i) => (
))}
{BOARD.map((cards, ci) => {
const p = interpolate(frame, [126 + ci * 5, 152 + ci * 5], [0, PROGRESS[ci]], CLAMP);
return (
{DAYS[ci]}
{cards.map((card, ri) => (
))}
);
})}
);
};
const FeaturePanel: React.FC<{ frame: number }> = ({ frame }) => {
return (
{FEATURES.map((title, i) => {
const t = frame - i * 60;
const inOp = interpolate(t, [0, 14], [0, 1], CLAMP);
const outOp = i < 2 ? interpolate(t, [48, 60], [1, 0], CLAMP) : 1;
const y =
interpolate(t, [0, 14], [26, 0], CLAMP) +
(i < 2 ? interpolate(t, [48, 60], [0, -18], CLAMP) : 0);
const ulW = interpolate(t, [10, 28], [0, 76], CLAMP);
return (
{`0${i + 1}`}
/ 03
{title}
);
})}
{FEATURES.map((f, i) => {
const end = i === 2 ? 1 : 0;
const amt = interpolate(
frame,
[i * 60, i * 60 + 10, (i + 1) * 60, (i + 1) * 60 + 10],
[0, 1, 1, end],
CLAMP
);
return (
);
})}
);
};
const ProductScene: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const enterOp = interpolate(frame, [0, 12], [0, 1], CLAMP);
const exitOp = interpolate(frame, [166, 179], [1, 0], CLAMP);
const winS = spring({ frame, fps, delay: 2, config: { damping: 18, stiffness: 90 } });
const winY = interpolate(winS, [0, 1], [56, 0]);
return (
);
};
const Outro: React.FC = () => {
const frame = useCurrentFrame();
const { fps } = useVideoConfig();
const logoS = spring({ frame, fps, config: { damping: 13, stiffness: 160 } });
const logoScale = interpolate(logoS, [0, 1], [0.45, 1]);
const logoOp = interpolate(logoS, [0, 0.4], [0, 1], CLAMP);
const textS = spring({ frame, fps, delay: 6, config: { damping: 17, stiffness: 110 } });
const textY = interpolate(textS, [0, 1], [30, 0]);
const textOp = interpolate(textS, [0, 0.55], [0, 1], CLAMP);
const lineW = interpolate(frame, [16, 34], [0, 130], CLAMP);
return (
Start your next week
with Relay
);
};
export const BenchmarkVideo: React.FC = () => {
return (
);
};