scrape / components /caption-word.tsx
kjbytes's picture
push code
ef4c36f
Raw
History Blame Contribute Delete
1.64 kB
"use client";
import { motion } from "framer-motion";
import type { AnimationSettings, Caption, CaptionStyle } from "@/types";
interface CaptionWordProps {
caption: Caption;
currentMs: number;
style: CaptionStyle;
animation: AnimationSettings;
}
export function CaptionWord({ caption, style, animation }: CaptionWordProps) {
// parent filters to the active cue, so no active/past branching needed here
const text = style.uppercase ? caption.text.toUpperCase() : caption.text;
const textStyle: React.CSSProperties = {
fontFamily: `"${style.fontFamily}", "Arial Black", sans-serif`,
// ponytail: cqw = % of the preview's width, so 58px-at-1080 previews true to export
fontSize: `${(style.fontSize / 1080) * 100}cqw`,
color: style.fillColor,
WebkitTextStroke: `${(style.strokeWidth / 1080) * 100}cqw ${style.strokeColor}`,
paintOrder: "stroke fill",
letterSpacing: `${(style.letterSpacing / 1080) * 100}cqw`,
textShadow: style.shadow
? `0 ${(style.shadow.offsetY / 1080) * 100}cqw ${(style.shadow.blur / 1080) * 100}cqw ${style.shadow.color}`
: undefined,
};
return (
<motion.span
style={textStyle}
className="inline-block font-black leading-none"
// calm entrance matching the export renderer: fade + small rise, then hold still
initial={{ scale: animation.style === "pop" ? 0.92 : 0.97, opacity: 0, y: 10 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{
duration: 0.24 / animation.speed,
ease: animation.style === "pop" ? [0.34, 1.3, 0.64, 1] : "easeOut",
}}
>
{text}
</motion.span>
);
}