{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "caption-handwrite",
  "title": "Caption Handwrite",
  "description": "Each word is inked in by hand from left to right at the pace it is spoken, every word sitting at its own slight angle.",
  "dependencies": ["remotion", "@remotion/captions"],
  "registryDependencies": ["@remocn/caption-core"],
  "files": [
    {
      "path": "registry/remocn/caption-handwrite/index.tsx",
      "content": "\"use client\";\n\nimport { Fragment } from \"react\";\nimport { Easing, interpolate, random } from \"remotion\";\nimport {\n  type CaptionBaseProps,\n  captionWordText,\n  DEFAULT_HOLD_MS,\n  getExitWindowMs,\n  groupCaptionWords,\n  useCaptions,\n} from \"@/lib/remocn/caption-core\";\n\nexport interface CaptionHandwriteProps extends CaptionBaseProps {\n  wobble?: number;\n  shadow?: boolean;\n}\n\nconst ENTER_MS = 160;\nconst EXIT_MS = 220;\nconst MIN_WRITE_MS = 200;\nconst EDGE_PERCENT = 22;\nconst MASK_PAD_EM = 0.4;\nconst LEGIBILITY_SHADOW =\n  \"0 2px 3px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.35)\";\n\nconst enterEasing = Easing.bezier(0.2, 0.8, 0.2, 1);\n\nconst clamp = {\n  extrapolateLeft: \"clamp\",\n  extrapolateRight: \"clamp\",\n} as const;\n\nexport function CaptionHandwrite({\n  captions,\n  combineTokensWithinMilliseconds = 1200,\n  holdMs = DEFAULT_HOLD_MS,\n  fontSize = 64,\n  fontWeight = 800,\n  color = \"#ffffff\",\n  wobble = 1,\n  shadow = true,\n  className,\n}: CaptionHandwriteProps) {\n  const state = useCaptions({\n    captions,\n    combineTokensWithinMilliseconds,\n    holdMs,\n  });\n  if (!state) return null;\n\n  const enter = interpolate(\n    state.previous ? ENTER_MS : state.sinceStartMs,\n    [0, ENTER_MS],\n    [0, 1],\n    { ...clamp, easing: enterEasing },\n  );\n  const exit = state.endsInSilence\n    ? interpolate(\n        state.untilEndMs,\n        [0, getExitWindowMs(state, EXIT_MS)],\n        [0, 1],\n        clamp,\n      )\n    : 1;\n  const textShadow = shadow ? LEGIBILITY_SHADOW : undefined;\n\n  return (\n    <div\n      className={className}\n      style={{\n        fontSize,\n        fontWeight,\n        color,\n        lineHeight: 1.2,\n        textAlign: \"center\",\n        opacity: Math.min(enter, exit),\n      }}\n    >\n      {groupCaptionWords(state.tokens).map((word, w) => {\n        const text = captionWordText(word);\n        const fromMs = word[0].token.fromMs;\n        const toMs = word[word.length - 1].token.toMs;\n        const writeMs = Math.max(toMs - fromMs, MIN_WRITE_MS);\n        const written = interpolate(\n          state.timeMs - fromMs,\n          [0, writeMs],\n          [0, 1],\n          clamp,\n        );\n        const seed = `caption-handwrite-${state.pageIndex}-${word[0].index}`;\n        const tilt = (random(`${seed}-tilt`) * 3 - 1.5) * wobble;\n        const lift = (random(`${seed}-lift`) * 0.08 - 0.04) * wobble;\n        const head = written * (100 + EDGE_PERCENT);\n        const stop = (percent: number): string =>\n          `calc(${MASK_PAD_EM}em + (100% - ${MASK_PAD_EM * 2}em) * ${percent / 100})`;\n        const mask =\n          written > 0 && written < 1\n            ? `linear-gradient(90deg, #000 ${stop(head - EDGE_PERCENT)}, transparent ${stop(head)})`\n            : undefined;\n        return (\n          <Fragment key={`${state.pageIndex}-${word[0].index}`}>\n            {w > 0 && \" \"}\n            <span\n              style={{\n                display: \"inline-block\",\n                whiteSpace: \"nowrap\",\n                opacity: written > 0 ? 1 : 0,\n                padding: `${MASK_PAD_EM}em`,\n                margin: `-${MASK_PAD_EM}em`,\n                transform: `translateY(${lift}em) rotate(${tilt}deg)`,\n                WebkitMaskImage: mask,\n                maskImage: mask,\n                textShadow,\n              }}\n            >\n              {Array.from(text).map((char, c) => (\n                <span\n                  key={c}\n                  style={{\n                    display: \"inline-block\",\n                    whiteSpace: \"pre\",\n                    transform: `rotate(${(random(`${seed}-${c}`) * 6 - 3) * wobble}deg)`,\n                  }}\n                >\n                  {char}\n                </span>\n              ))}\n            </span>\n          </Fragment>\n        );\n      })}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/caption-handwrite.tsx"
    }
  ],
  "type": "registry:component"
}
