{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "caption-slot",
  "title": "Caption Slot",
  "description": "One short line at a time, each new phrase rolling up into place as the previous one rolls out the top.",
  "dependencies": ["remotion", "@remotion/captions"],
  "registryDependencies": ["@remocn/caption-core"],
  "files": [
    {
      "path": "registry/remocn/caption-slot/index.tsx",
      "content": "\"use client\";\n\nimport { type CSSProperties, Fragment } from \"react\";\nimport { Easing, interpolate } from \"remotion\";\nimport {\n  type CaptionBaseProps,\n  type CaptionTokenState,\n  DEFAULT_HOLD_MS,\n  getExitWindowMs,\n  groupCaptionWords,\n  useCaptions,\n} from \"@/lib/remocn/caption-core\";\n\nexport interface CaptionSlotProps extends CaptionBaseProps {\n  activeColor?: string;\n  shadow?: boolean;\n}\n\nconst LINE_HEIGHT = 1.3;\nconst CLIP_MARGIN_EM = 0.2;\nconst TRAVEL_EM = LINE_HEIGHT + CLIP_MARGIN_EM * 2;\nconst ROLL_MS = 260;\nconst EXIT_MS = 220;\nconst LEGIBILITY_SHADOW =\n  \"0 2px 3px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.35)\";\n\nconst rollIn = Easing.bezier(0.16, 1, 0.3, 1);\nconst rollOut = Easing.bezier(0.7, 0, 0.84, 0);\n\nfunction SlotLine({\n  tokens,\n  activeIndex,\n  activeColor,\n  style,\n}: {\n  tokens: CaptionTokenState[];\n  activeIndex: number;\n  activeColor?: string;\n  style: CSSProperties;\n}) {\n  return (\n    <div style={{ whiteSpace: \"nowrap\", ...style }}>\n      {groupCaptionWords(tokens).map((word, w) => (\n        <Fragment key={word[0].index}>\n          {w > 0 && \" \"}\n          {word.map(({ index, token }) => (\n            <span\n              key={index}\n              style={{\n                color:\n                  activeColor !== undefined && index === activeIndex\n                    ? activeColor\n                    : undefined,\n              }}\n            >\n              {token.text}\n            </span>\n          ))}\n        </Fragment>\n      ))}\n    </div>\n  );\n}\n\nexport function CaptionSlot({\n  captions,\n  combineTokensWithinMilliseconds = 800,\n  holdMs = DEFAULT_HOLD_MS,\n  fontSize = 64,\n  fontWeight = 800,\n  color = \"#ffffff\",\n  activeColor,\n  shadow = true,\n  className,\n}: CaptionSlotProps) {\n  const state = useCaptions({\n    captions,\n    combineTokensWithinMilliseconds,\n    holdMs,\n  });\n  if (!state) return null;\n\n  const roll = interpolate(state.sinceStartMs, [0, ROLL_MS], [0, 1], {\n    easing: rollIn,\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n  const exitProgress = Math.min(\n    1,\n    Math.max(0, 1 - state.untilEndMs / getExitWindowMs(state, EXIT_MS)),\n  );\n  const exit = state.endsInSilence ? rollOut(exitProgress) : 0;\n\n  return (\n    <div\n      className={className}\n      style={{\n        position: \"relative\",\n        fontSize,\n        fontWeight,\n        color,\n        lineHeight: LINE_HEIGHT,\n        height: `${LINE_HEIGHT}em`,\n        textAlign: \"center\",\n        textShadow: shadow ? LEGIBILITY_SHADOW : undefined,\n        clipPath: `inset(-${CLIP_MARGIN_EM}em -100000px)`,\n      }}\n    >\n      {state.previous && roll < 1 ? (\n        <SlotLine\n          key={`page-${state.previous.pageIndex}`}\n          tokens={state.previous.tokens}\n          activeIndex={-1}\n          style={{\n            position: \"absolute\",\n            top: 0,\n            left: \"50%\",\n            transform: `translate(-50%, ${-roll * TRAVEL_EM}em)`,\n          }}\n        />\n      ) : null}\n      <SlotLine\n        key={`page-${state.pageIndex}`}\n        tokens={state.tokens}\n        activeIndex={state.activeIndex}\n        activeColor={activeColor}\n        style={{\n          transform: `translateY(${(1 - roll - exit) * TRAVEL_EM}em)`,\n        }}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/caption-slot.tsx"
    }
  ],
  "type": "registry:component"
}
