Catalog

Edit Props (live)

TEXT
config
config
config
config

Source

222 lines
import 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_cta

Comment Cta — Andy's hand-crafted Remotion scene. 5 props. ctaSubtitle:TEXT.

Workflow Inputs (1)

  • Cta Subtitle
    ctaSubtitle
    TEXT

Config Fields (4)

  • ctaAccent
  • ctaKeyword
  • ctaSecondary
  • backgroundColor

Meta

Updated
4/21/2026