{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typed-split-wipe",
  "title": "Typed Split Wipe",
  "description": "A centered cursorless type build holds, then exits through opposing fixed-layout glyph wipes that leave the anchor visible longest.",
  "dependencies": ["remotion"],
  "files": [
    {
      "path": "registry/remocn/typed-split-wipe/index.tsx",
      "content": "\"use client\";\n\nimport type { CSSProperties } from \"react\";\nimport { Easing, interpolate, useCurrentFrame } from \"remotion\";\n\nexport interface TypedSplitWipeProps {\n  prefix: string;\n  anchor: string;\n  suffix: string;\n  typeFrames?: number;\n  exitAt?: number;\n  exitFrames?: number;\n  wordStagger?: number;\n  anchorShift?: number;\n  fontSize?: number;\n  color?: string;\n  fontWeight?: number;\n  speed?: number;\n  className?: string;\n}\n\nconst clamp = {\n  extrapolateLeft: \"clamp\" as const,\n  extrapolateRight: \"clamp\" as const,\n};\nconst wipeEasing = Easing.inOut(Easing.quad);\n\nexport function getTypedSplitWipeText(\n  text: string,\n  frame: number,\n  typeFrames: number,\n) {\n  const characters = Array.from(text);\n  const typedLength = interpolate(\n    frame,\n    [0, Math.max(typeFrames, 0.001)],\n    [0, characters.length],\n    clamp,\n  );\n  const visibleLength = Math.min(\n    characters.length,\n    Math.max(frame >= 0 ? 1 : 0, Math.floor(typedLength) + 1),\n  );\n  return characters.slice(0, visibleLength).join(\"\");\n}\n\nexport function TypedSplitWipe({\n  prefix,\n  anchor,\n  suffix,\n  typeFrames = 30,\n  exitAt = 50,\n  exitFrames = 20,\n  wordStagger = 4,\n  anchorShift = -72,\n  fontSize = 42,\n  color = \"#e8e8e8\",\n  fontWeight = 400,\n  speed = 1,\n  className,\n}: TypedSplitWipeProps) {\n  const frame = useCurrentFrame() * speed;\n  const fullText = [prefix, anchor, suffix].filter(Boolean).join(\" \");\n  const safeExitFrames = Math.max(exitFrames, 0.001);\n  const typedText = getTypedSplitWipeText(fullText, frame, typeFrames);\n  const suffixWords = suffix.trim().split(/\\s+/).filter(Boolean);\n\n  const textStyle: CSSProperties = {\n    color,\n    fontFamily:\n      \"var(--font-geist-sans, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif)\",\n    fontSize,\n    fontWeight,\n    letterSpacing: \"-0.03em\",\n    lineHeight: 1,\n    whiteSpace: \"pre\",\n  };\n\n  const prefixExit = interpolate(\n    frame,\n    [exitAt, exitAt + safeExitFrames],\n    [0, 1],\n    { ...clamp, easing: wipeEasing },\n  );\n  const anchorExitStart = exitAt + Math.max(exitFrames - 6, 0);\n  const anchorExit = interpolate(\n    frame,\n    [anchorExitStart, anchorExitStart + 6],\n    [0, 1],\n    { ...clamp, easing: wipeEasing },\n  );\n\n  return (\n    <div\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        display: \"flex\",\n        alignItems: \"center\",\n        justifyContent: \"center\",\n        overflow: \"hidden\",\n        background: \"transparent\",\n      }}\n    >\n      {frame < exitAt ? (\n        <span className={className} style={textStyle}>\n          {typedText}\n        </span>\n      ) : (\n        <span\n          className={className}\n          style={{\n            ...textStyle,\n            display: \"inline-flex\",\n            alignItems: \"baseline\",\n            gap: \"0.25em\",\n          }}\n        >\n          {prefix && (\n            <span\n              style={{\n                display: \"inline-block\",\n                clipPath: `inset(0 ${prefixExit * 100}% 0 0)`,\n              }}\n            >\n              {prefix}\n            </span>\n          )}\n\n          {anchor && (\n            <span\n              style={{\n                display: \"inline-block\",\n                clipPath: `inset(0 ${anchorExit * 50}% 0 ${anchorExit * 50}%)`,\n                transform: `translateX(${anchorShift * prefixExit}px)`,\n              }}\n            >\n              {anchor}\n            </span>\n          )}\n\n          {suffixWords.map((word, index) => {\n            const wordExitStart = exitAt + 2 + index * wordStagger;\n            const wordExit = interpolate(\n              frame,\n              [wordExitStart, wordExitStart + safeExitFrames * 0.65],\n              [0, 1],\n              { ...clamp, easing: wipeEasing },\n            );\n\n            return (\n              <span\n                key={`${word}-${index}`}\n                style={{\n                  display: \"inline-block\",\n                  clipPath: `inset(0 0 0 ${wordExit * 100}%)`,\n                }}\n              >\n                {word}\n              </span>\n            );\n          })}\n        </span>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/remocn/typed-split-wipe.tsx"
    }
  ],
  "type": "registry:component"
}
