{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "caption-cube",
  "title": "Caption Cube",
  "description": "Each new phrase turns into view as the next face of a 3D cube, the previous phrase rotating up and away on a horizontal hinge.",
  "dependencies": ["remotion", "@remotion/captions"],
  "registryDependencies": ["@remocn/caption-core"],
  "files": [
    {
      "path": "registry/remocn/caption-cube/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 CaptionCubeProps extends CaptionBaseProps {\n  tintActive?: boolean;\n  activeColor?: string;\n  shadow?: boolean;\n}\n\nconst LINE_HEIGHT = 1.3;\nconst DEPTH_EM = LINE_HEIGHT / 2;\nconst TURN_MS = 320;\nconst EXIT_MS = 280;\nconst PERSPECTIVE_EM = 10;\nconst LEGIBILITY_SHADOW =\n  \"0 2px 3px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.35)\";\n\nconst turnEasing = Easing.bezier(0.65, 0, 0.35, 1);\nconst exitEasing = Easing.bezier(0.7, 0, 0.84, 0);\n\nfunction CubeFace({\n  tokens,\n  activeIndex,\n  activeColor,\n  angle,\n  before,\n  style,\n}: {\n  tokens: CaptionTokenState[];\n  activeIndex: number;\n  activeColor: string | undefined;\n  angle: number;\n  before: string;\n  style?: CSSProperties;\n}) {\n  const facing = Math.max(0, Math.cos((angle * Math.PI) / 180));\n  return (\n    <div\n      style={{\n        whiteSpace: \"nowrap\",\n        backfaceVisibility: \"hidden\",\n        WebkitBackfaceVisibility: \"hidden\",\n        opacity: 0.35 + 0.65 * facing,\n        transform: `${before}rotateX(${angle}deg) translateZ(${DEPTH_EM}em)`,\n        ...style,\n      }}\n    >\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 CaptionCube({\n  captions,\n  combineTokensWithinMilliseconds = 800,\n  holdMs = DEFAULT_HOLD_MS,\n  fontSize = 64,\n  fontWeight = 800,\n  color = \"#ffffff\",\n  tintActive = false,\n  activeColor = \"#facc15\",\n  shadow = true,\n  className,\n}: CaptionCubeProps) {\n  const state = useCaptions({\n    captions,\n    combineTokensWithinMilliseconds,\n    holdMs,\n  });\n  if (!state) return null;\n\n  const turnMs = Math.max(\n    1,\n    Math.min(TURN_MS, state.sinceStartMs + state.untilEndMs),\n  );\n  const turn = interpolate(state.sinceStartMs, [0, turnMs], [0, 1], {\n    easing: turnEasing,\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n  });\n  const exitProgress = state.endsInSilence\n    ? exitEasing(\n        Math.min(\n          1,\n          Math.max(0, 1 - state.untilEndMs / getExitWindowMs(state, EXIT_MS)),\n        ),\n      )\n    : 0;\n  const currentAngle = (turn - 1) * 90 + exitProgress * 90;\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        perspective: `${PERSPECTIVE_EM}em`,\n      }}\n    >\n      <div\n        style={{\n          position: \"relative\",\n          height: \"100%\",\n          transformStyle: \"preserve-3d\",\n          transform: `translateZ(${-DEPTH_EM}em)`,\n        }}\n      >\n        {state.previous && turn < 1 ? (\n          <CubeFace\n            key={`page-${state.previous.pageIndex}`}\n            tokens={state.previous.tokens}\n            activeIndex={-1}\n            activeColor={undefined}\n            angle={turn * 90}\n            before=\"translateX(-50%) \"\n            style={{ position: \"absolute\", top: 0, left: \"50%\" }}\n          />\n        ) : null}\n        <CubeFace\n          key={`page-${state.pageIndex}`}\n          tokens={state.tokens}\n          activeIndex={state.activeIndex}\n          activeColor={tintActive ? activeColor : undefined}\n          angle={currentAngle}\n          before=\"\"\n        />\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/caption-cube.tsx"
    }
  ],
  "type": "registry:component"
}
