← Catalog
Edit Props (live)
config
config
Source
164 linesimport React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
spring,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["400", "500", "700", "900"] });
const FONT = "'Inter', sans-serif";
interface Card {
title: string;
body: string;
reveal_at: number; // seconds, scene-relative
accent?: string; // optional left-edge accent colour
}
interface FeatureCardsProps {
cards: Card[];
backgroundColor?: string;
}
// Hex → rgba helper for accent glow
const hex2rgba = (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})`;
};
export const FeatureCards: React.FC<FeatureCardsProps> = ({
cards,
backgroundColor = "#0c0d12",
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const N = cards.length;
const gap = 24;
// Live in the top 2/3 of the 1920 frame — that's where eyes focus.
// Top edge ~80px, stack ends ~1280px → cards occupy ~1200px vertical.
const stackTop = 80;
const stackBottom = Math.floor(height * 0.66); // 66% of frame
const stackH = stackBottom - stackTop;
const cardH = Math.floor((stackH - gap * (N - 1)) / N);
return (
<AbsoluteFill style={{
background: `linear-gradient(180deg, #0e0f15 0%, #0a0b10 100%)`,
}}>
<div style={{
position: "absolute",
top: stackTop,
left: 36,
right: 36,
display: "flex",
flexDirection: "column",
gap,
}}>
{cards.map((c, i) => {
// spring-based slide + fade
const sp = spring({
frame: Math.max(0, frame - Math.floor(c.reveal_at * fps)),
fps,
config: { mass: 0.55, damping: 14, stiffness: 130 },
durationInFrames: 22,
});
const enterX = interpolate(sp, [0, 1], [width + 80, 0]);
const op = interpolate(sp, [0, 1], [0, 1]);
const t = frame / fps;
if (t < c.reveal_at) return null;
const accent = c.accent || "#9aa3b2";
return (
<div key={i} style={{
position: "relative",
// Subtle inner gradient for depth — feels glassy not flat
background: "linear-gradient(180deg, rgba(28,30,38,0.92) 0%, rgba(20,22,28,0.92) 100%)",
backdropFilter: "blur(8px)",
border: "1px solid rgba(255,255,255,0.06)",
borderRadius: 24,
padding: "26px 32px",
height: cardH,
display: "flex",
flexDirection: "column",
justifyContent: "center",
transform: `translateX(${enterX}px)`,
opacity: op,
boxShadow: `0 24px 60px rgba(0,0,0,0.55), 0 1px 0 rgba(255,255,255,0.04) inset`,
overflow: "hidden",
}}>
{/* Soft accent glow — top-left wash, very subtle */}
<div style={{
position: "absolute",
top: -120, left: -60,
width: 320, height: 320,
background: `radial-gradient(circle, ${hex2rgba(accent, 0.18)} 0%, transparent 65%)`,
pointerEvents: "none",
filter: "blur(20px)",
}} />
{/* Top row — accent dot + kicker */}
<div style={{
display: "flex",
alignItems: "center",
gap: 14,
marginBottom: 18,
}}>
<div style={{
width: 10, height: 10,
borderRadius: "50%",
background: accent,
boxShadow: `0 0 12px ${hex2rgba(accent, 0.7)}`,
}} />
<div style={{
fontFamily: FONT,
fontSize: 20,
fontWeight: 500,
color: "rgba(230,230,240,0.55)",
letterSpacing: 2.4,
textTransform: "uppercase",
}}>
Skill
</div>
</div>
{/* Title — restrained weight, cleaner letterspacing */}
<div style={{
fontFamily: FONT,
fontSize: 50,
fontWeight: 700,
color: "#f4f5f8",
letterSpacing: -1.4,
lineHeight: 1.05,
marginBottom: 14,
}}>
{c.title}
</div>
{/* Body — lighter weight, subtler colour, readable */}
<div style={{
fontFamily: FONT,
fontSize: 26,
fontWeight: 400,
color: "rgba(210,212,222,0.72)",
lineHeight: 1.35,
letterSpacing: -0.2,
}}>
{c.body}
</div>
</div>
);
})}
</div>
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: FeatureCards
// 2 props detected · 0 port(s)
Feature Cards
feature_cardsFeature Cards — Andy's hand-crafted Remotion scene. 2 props. no ports detected — source-only preview.
Workflow Inputs (0)
This block has no workflow-connected inputs.
Config Fields (2)
cardsbackgroundColor
Meta
- Updated
- 4/21/2026