{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "special-text",
  "title": "Special Text",
  "description": "Animated text with scramble effect.",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/spell-ui/special-text.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\nimport { useInView } from \"motion/react\";\n\ninterface SpecialTextProps {\n  children: string;\n  speed?: number;\n  delay?: number;\n  className?: string;\n  inView?: boolean;\n  once?: boolean;\n}\n\nconst RANDOM_CHARS = \"_!X$0-+*#\";\n\nfunction getRandomChar(prevChar?: string): string {\n  let char: string;\n  do {\n    char = RANDOM_CHARS[Math.floor(Math.random() * RANDOM_CHARS.length)];\n  } while (char === prevChar);\n  return char;\n}\n\nexport function SpecialText({\n  children,\n  speed = 20,\n  delay = 0,\n  className = \"\",\n  inView = false,\n  once = true,\n}: SpecialTextProps) {\n  const containerRef = useRef<HTMLSpanElement>(null);\n  const isInView = useInView(containerRef, { once, margin: \"-100px\" });\n  const shouldAnimate = inView ? isInView : true;\n  const [hasStarted, setHasStarted] = useState(() => !inView && delay <= 0);\n  const text = children;\n  const [displayText, setDisplayText] = useState<string>(\n    \" \".repeat(text.length),\n  );\n  const [currentPhase, setCurrentPhase] = useState<\"phase1\" | \"phase2\">(\n    \"phase1\",\n  );\n  const [animationStep, setAnimationStep] = useState<number>(0);\n  const intervalRef = useRef<NodeJS.Timeout | null>(null);\n  const startTimeoutRef = useRef<number | null>(null);\n\n  function clearStartTimeout() {\n    if (startTimeoutRef.current === null) return;\n    window.clearTimeout(startTimeoutRef.current);\n    startTimeoutRef.current = null;\n  }\n\n  function startAnimation() {\n    setHasStarted(true);\n    setDisplayText(\" \".repeat(text.length));\n    setCurrentPhase(\"phase1\");\n    setAnimationStep(0);\n  }\n\n  const runPhase1 = () => {\n    const maxSteps = text.length * 2;\n    const currentLength = Math.min(animationStep + 1, text.length);\n\n    const chars: string[] = [];\n    for (let i = 0; i < currentLength; i++) {\n      const prevChar = i > 0 ? chars[i - 1] : undefined;\n      chars.push(getRandomChar(prevChar));\n    }\n\n    for (let i = currentLength; i < text.length; i++) {\n      chars.push(\"\\u00A0\");\n    }\n\n    setDisplayText(chars.join(\"\"));\n\n    if (animationStep < maxSteps - 1) {\n      setAnimationStep((prev) => prev + 1);\n    } else {\n      setCurrentPhase(\"phase2\");\n      setAnimationStep(0);\n    }\n  };\n\n  const runPhase2 = () => {\n    const revealedCount = Math.floor(animationStep / 2);\n    const chars: string[] = [];\n\n    for (let i = 0; i < revealedCount && i < text.length; i++) {\n      chars.push(text[i]);\n    }\n\n    if (revealedCount < text.length) {\n      if (animationStep % 2 === 0) {\n        chars.push(\"_\");\n      } else {\n        chars.push(getRandomChar());\n      }\n    }\n\n    for (let i = chars.length; i < text.length; i++) {\n      chars.push(getRandomChar());\n    }\n\n    setDisplayText(chars.join(\"\"));\n\n    if (animationStep < text.length * 2 - 1) {\n      setAnimationStep((prev) => prev + 1);\n    } else {\n      setDisplayText(text);\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n        intervalRef.current = null;\n      }\n    }\n  };\n\n  useEffect(() => {\n    if (shouldAnimate && !hasStarted) {\n      clearStartTimeout();\n      if (delay <= 0) {\n        startAnimation();\n        return;\n      }\n      startTimeoutRef.current = window.setTimeout(() => {\n        startTimeoutRef.current = null;\n        startAnimation();\n      }, delay * 1000);\n    }\n    return () => clearStartTimeout();\n  }, [shouldAnimate, hasStarted, delay, text.length]);\n\n  useEffect(() => {\n    if (!hasStarted) {\n      return;\n    }\n\n    if (intervalRef.current) {\n      clearInterval(intervalRef.current);\n    }\n\n    intervalRef.current = setInterval(() => {\n      if (currentPhase === \"phase1\") {\n        runPhase1();\n      } else {\n        runPhase2();\n      }\n    }, speed);\n\n    return () => {\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n      }\n    };\n  }, [currentPhase, animationStep, text, speed, hasStarted]);\n\n  useEffect(() => {\n    if (hasStarted) {\n      setDisplayText(\" \".repeat(text.length));\n      setCurrentPhase(\"phase1\");\n      setAnimationStep(0);\n    }\n\n    return () => {\n      clearStartTimeout();\n      if (intervalRef.current) {\n        clearInterval(intervalRef.current);\n      }\n    };\n  }, [text, hasStarted]);\n\n  return (\n    <span\n      ref={containerRef}\n      className={`h-4.5 leading-5 inline-flex font-mono font-medium ${className}`}\n    >\n      {displayText}\n    </span>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}