← Catalog
Edit Props (live)
TEXT
IMAGE
TEXT
config
config
config
config
Source
211 linesimport React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
Img,
staticFile,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["400", "700", "900"] });
loadInter("italic", { weights: ["400", "900"] });
interface QuoteCardProps {
quote: string;
author?: string;
role?: string;
avatarSrc?: string;
backgroundColor?: string;
accentColor?: string;
textColor?: string;
}
const FONT = "'Inter', sans-serif";
export const QuoteCard: React.FC<QuoteCardProps> = ({
quote,
author,
role,
avatarSrc,
backgroundColor = "#f6f4ed",
accentColor = "#c94f3d",
textColor = "#1a1a1a",
}) => {
const frame = useCurrentFrame();
const { durationInFrames } = useVideoConfig();
// Split quote into words for stagger reveal
const words = quote.split(/\s+/);
// Big quote mark fades in first
const markOpacity = interpolate(frame, [0, 14], [0, 1], { extrapolateRight: "clamp" });
const markScale = interpolate(frame, [0, 18], [0.7, 1], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.back(1.5)),
});
// Author block reveals at the end
const authorAt = Math.max(60, Math.round(durationInFrames * 0.55));
const authorOp = interpolate(frame, [authorAt, authorAt + 14], [0, 1], {
extrapolateRight: "clamp",
});
const authorY = interpolate(frame, [authorAt, authorAt + 18], [20, 0], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
// Accent rule to left of author
const ruleW = interpolate(frame, [authorAt + 4, authorAt + 24], [0, 80], {
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
return (
<AbsoluteFill
style={{
backgroundColor,
color: textColor,
fontFamily: FONT,
display: "flex",
flexDirection: "column",
justifyContent: "center",
padding: "100px",
}}
>
{/* Big decorative curly quote mark — Georgia gives a proper typographic glyph */}
<div
style={{
position: "absolute",
top: -60,
left: 60,
fontSize: 480,
fontWeight: 700,
color: accentColor,
lineHeight: 1,
opacity: markOpacity * 0.18,
transform: `scale(${markScale})`,
transformOrigin: "top left",
fontFamily: "Georgia, 'Times New Roman', serif",
userSelect: "none",
}}
>
{"\u201C"}
</div>
{/* Quote body — word-by-word reveal, vertically centered in the card */}
<div
style={{
position: "relative",
zIndex: 2,
fontSize: 72,
fontWeight: 700,
fontStyle: "italic",
lineHeight: 1.2,
letterSpacing: -1.5,
color: textColor,
marginBottom: 80,
}}
>
{words.map((w, i) => {
const startF = 8 + i * 3;
const endF = startF + 14;
const wOp = interpolate(frame, [startF, endF], [0, 1], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
});
const wY = interpolate(frame, [startF, endF], [14, 0], {
extrapolateLeft: "clamp",
extrapolateRight: "clamp",
easing: Easing.out(Easing.cubic),
});
return (
<span
key={i}
style={{
display: "inline-block",
opacity: wOp,
transform: `translateY(${wY}px)`,
marginRight: 18,
}}
>
{w}
</span>
);
})}
</div>
{/* Author block — sits below the quote, aligned in the centered flex column */}
{author && (
<div
style={{
position: "relative",
zIndex: 2,
display: "flex",
alignItems: "center",
gap: 28,
opacity: authorOp,
transform: `translateY(${authorY}px)`,
}}
>
{/* Avatar */}
{avatarSrc && (
<div
style={{
width: 120,
height: 120,
borderRadius: "50%",
overflow: "hidden",
flexShrink: 0,
border: `4px solid ${accentColor}`,
}}
>
<Img
src={avatarSrc.startsWith("http") ? avatarSrc : staticFile(avatarSrc)}
style={{ width: "100%", height: "100%", objectFit: "cover" }}
/>
</div>
)}
{/* Accent rule (shown only when no avatar) */}
{!avatarSrc && (
<div
style={{
width: ruleW,
height: 6,
background: accentColor,
flexShrink: 0,
}}
/>
)}
<div style={{ display: "flex", flexDirection: "column" }}>
<div
style={{
fontSize: 44,
fontWeight: 900,
letterSpacing: -0.5,
lineHeight: 1.1,
}}
>
{author}
</div>
{role && (
<div
style={{
fontSize: 28,
fontWeight: 400,
opacity: 0.65,
marginTop: 6,
}}
>
{role}
</div>
)}
</div>
</div>
)}
</AbsoluteFill>
);
};
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: QuoteCard
// 7 props detected · 3 port(s)
Quote Card
quote_cardQuote Card — Andy's hand-crafted Remotion scene. 7 props. quote:TEXT, avatarSrc:IMAGE, textColor:TEXT.
Workflow Inputs (3)
- Quote
quoteTEXTRequired - Avatar Src
avatarSrcIMAGE - Text Color
textColorTEXT
Config Fields (4)
roleauthoraccentColorbackgroundColor
Meta
- Updated
- 4/21/2026