← Catalog
Edit Props (live)
TEXT
config
config
config
config
Source
222 linesimport React from "react";
import {
AbsoluteFill,
useCurrentFrame,
useVideoConfig,
interpolate,
Easing,
spring,
} from "remotion";
import { loadFont as loadInter } from "@remotion/google-fonts/Inter";
loadInter("normal", { weights: ["400", "600", "800", "900"] });
const FONT = "'Inter', sans-serif";
interface CommentCtaProps {
ctaKeyword?: string;
ctaSubtitle?: string;
ctaSecondary?: string;
ctaAccent?: string;
backgroundColor?: string;
}
export const CommentCta: React.FC<CommentCtaProps> = ({
ctaKeyword = "FLOW",
ctaSubtitle = "and I'll DM you the steps",
ctaSecondary = "+ my best use cases",
ctaAccent = "#D97757",
backgroundColor = "#0d0d0f",
}) => {
const frame = useCurrentFrame();
const { fps, width, height } = useVideoConfig();
const t = frame / fps;
// Stagger: chip → subtitle → secondary → icons
const chipSpring = spring({ frame: frame - 2, fps, config: { mass: 0.7, damping: 13, stiffness: 180 } });
const chipOp = interpolate(chipSpring, [0, 1], [0, 1]);
const chipScale = interpolate(chipSpring, [0, 1], [0.85, 1]);
const subOp = interpolate(frame, [12, 22], [0, 1], { extrapolateRight: "clamp" });
const subY = interpolate(frame, [12, 22], [16, 0], { extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) });
const secOp = interpolate(frame, [18, 28], [0, 1], { extrapolateRight: "clamp" });
const secY = interpolate(frame, [18, 28], [12, 0], { extrapolateRight: "clamp", easing: Easing.out(Easing.cubic) });
const iconOp = interpolate(frame, [22, 32], [0, 1], { extrapolateRight: "clamp" });
// Speech-bubble pulse — every ~0.8s
const pulse = 1 + Math.max(0, Math.sin(t * Math.PI * 1.25)) * 0.10;
const cx = width / 2;
const cy = height / 2;
return (
<AbsoluteFill style={{ backgroundColor, overflow: "hidden", fontFamily: FONT }}>
{/* Subtle radial glow behind the chip */}
<div
style={{
position: "absolute",
left: 0, right: 0, top: 0, bottom: 0,
background: `radial-gradient(ellipse at 50% 42%, ${hex2rgba(ctaAccent, 0.20)}, transparent 55%)`,
pointerEvents: "none",
}}
/>
{/* Chip — "Comment '<KEYWORD>'" */}
<div
style={{
position: "absolute",
left: 0, right: 0, top: cy - 220,
display: "flex",
justifyContent: "center",
opacity: chipOp,
transform: `scale(${chipScale})`,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 22,
padding: "30px 52px",
borderRadius: 999,
background: "rgba(255,255,255,0.05)",
border: `2px solid ${hex2rgba(ctaAccent, 0.55)}`,
boxShadow: `0 30px 80px ${hex2rgba(ctaAccent, 0.35)}, inset 0 1px 0 rgba(255,255,255,0.08)`,
color: "#fff",
fontSize: 78,
fontWeight: 800,
letterSpacing: -1.5,
}}
>
<span style={{ opacity: 0.85 }}>Comment</span>
<span
style={{
color: ctaAccent,
fontWeight: 900,
fontSize: 96,
letterSpacing: -2,
padding: "0 4px",
}}
>
"{ctaKeyword}"
</span>
</div>
</div>
{/* Subtitle */}
<div
style={{
position: "absolute",
left: 60, right: 60, top: cy + 20,
textAlign: "center",
fontSize: 46,
fontWeight: 600,
color: "#fff",
letterSpacing: -0.8,
opacity: subOp,
transform: `translateY(${subY}px)`,
}}
>
{ctaSubtitle}
</div>
{/* Secondary line */}
{ctaSecondary && (
<div
style={{
position: "absolute",
left: 60, right: 60, top: cy + 100,
textAlign: "center",
fontSize: 36,
fontWeight: 500,
color: "rgba(255,255,255,0.6)",
letterSpacing: -0.5,
opacity: secOp,
transform: `translateY(${secY}px)`,
}}
>
{ctaSecondary}
</div>
)}
{/* Instagram action stack */}
<div
style={{
position: "absolute",
right: 70,
bottom: 220,
display: "flex",
flexDirection: "column",
alignItems: "center",
gap: 36,
opacity: iconOp,
}}
>
<IgIcon size={90}>
<HeartSvg />
</IgIcon>
<IgIcon size={90} accent={ctaAccent} pulse={pulse}>
<BubbleSvg />
</IgIcon>
<IgIcon size={90}>
<PaperPlaneSvg />
</IgIcon>
</div>
</AbsoluteFill>
);
};
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})`;
};
const IgIcon: React.FC<{
size: number;
accent?: string;
pulse?: number;
children: React.ReactNode;
}> = ({ size, accent, pulse = 1, children }) => (
<div
style={{
width: size,
height: size,
borderRadius: "50%",
background: accent ? `${hex2rgba(accent, 0.18)}` : "rgba(255,255,255,0.08)",
border: `2px solid ${accent ? hex2rgba(accent, 0.7) : "rgba(255,255,255,0.45)"}`,
display: "flex",
alignItems: "center",
justifyContent: "center",
color: accent || "#fff",
transform: `scale(${pulse})`,
transition: "transform 0.12s ease-out",
}}
>
{children}
</div>
);
const HeartSvg = () => (
<svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
</svg>
);
const BubbleSvg = () => (
<svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
</svg>
);
const PaperPlaneSvg = () => (
<svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round">
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
);
Schema (Zod)
// Auto-extracted from Props interface — see source for authoritative types.
// Component: CommentCta
// 5 props detected · 1 port(s)
Comment Cta
comment_ctaComment Cta — Andy's hand-crafted Remotion scene. 5 props. ctaSubtitle:TEXT.
Workflow Inputs (1)
- Cta Subtitle
ctaSubtitleTEXT
Config Fields (4)
ctaAccentctaKeywordctaSecondarybackgroundColor
Meta
- Updated
- 4/21/2026