import React from "react"; import { AbsoluteFill, interpolate, spring, useCurrentFrame, useVideoConfig, Easing, } from "remotion"; import { zColor } from "@remotion/zod-types"; import { z } from "zod"; import { hexToRgba, mixHex } from "../lib/anim"; export const kineticQuoteSchema = z.object({ quote: z.string(), author: z.string(), accentColor: zColor(), secondaryColor: zColor(), backgroundColor: zColor(), }); type Props = z.infer; // ── Slowly rotating gradient sheen behind the text ─────────────────────────── const SheenBackground: React.FC<{ bg: string; accent: string; secondary: string; }> = ({ bg, accent, secondary }) => { const frame = useCurrentFrame(); const angle = (frame * 0.4) % 360; return ( {/* Soft top glow */} ); }; // ── Word-by-word reveal of the quote ───────────────────────────────────────── const Quote: React.FC<{ quote: string; accent: string; secondary: string }> = ({ quote, accent, secondary, }) => { const frame = useCurrentFrame(); const { fps } = useVideoConfig(); const words = quote.split(/\s+/).filter(Boolean); return (
{words.map((w, i) => { const start = 12 + i * 4; const s = spring({ frame: frame - start, fps, config: { damping: 18, mass: 0.7, stiffness: 110 }, }); const y = interpolate(s, [0, 1], [28, 0]); const op = interpolate(s, [0, 1], [0, 1]); return ( {w} ); })}
); }; export const KineticQuote: React.FC = ({ quote, author, accentColor, secondaryColor, backgroundColor, }) => { const frame = useCurrentFrame(); const words = quote.split(/\s+/).filter(Boolean); // The decorative rule + author appear once the quote has finished landing. const tail = 12 + words.length * 4 + 8; const ruleW = interpolate(frame, [tail, tail + 18], [0, 120], { extrapolateLeft: "clamp", extrapolateRight: "clamp", easing: Easing.out(Easing.cubic), }); const authorOp = interpolate(frame, [tail + 10, tail + 30], [0, 1], { extrapolateLeft: "clamp", extrapolateRight: "clamp", }); return ( {/* Opening quotation mark */}
{author}
); };