File size: 1,641 Bytes
ef4c36f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
"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>
  );
}