{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typewriter",
  "type": "registry:ui",
  "registryDependencies": [],
  "dependencies": ["motion"],
  "devDependencies": [],
  "files": [
    {
      "path": "typewriter.tsx",
      "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useEffect, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport interface TextTypewriterProps {\n  children: string;\n  className?: string;\n  duration?: number;\n}\n\nconst WRONG_CHARS = \"!@#$%^&*()QWERTY\";\n\nfunction randomWrongChar() {\n  return WRONG_CHARS[Math.floor(Math.random() * WRONG_CHARS.length)];\n}\n\nexport default function TextTypewriter({\n  children,\n  className,\n  duration = 3,\n}: TextTypewriterProps) {\n  const [text, setText] = useState(\"\");\n  const [showCursor, setShowCursor] = useState(false);\n\n  useEffect(() => {\n    const timeouts = new Set<ReturnType<typeof setTimeout>>();\n    const speed = duration / 3;\n\n    const schedule = (callback: () => void, ms: number) => {\n      const id = setTimeout(callback, ms * speed);\n      timeouts.add(id);\n    };\n\n    const runAnimation = () => {\n      let currentText = \"\";\n      let targetIndex = 0;\n      const finalText = children;\n\n      const typeChar = () => {\n        if (targetIndex >= finalText.length) {\n          setText(finalText);\n          setShowCursor(false);\n          schedule(() => {\n            setShowCursor(true);\n            runAnimation();\n          }, 1000);\n          return;\n        }\n\n        const targetChar = finalText[targetIndex];\n        const shouldGlitch = Math.random() > 0.6 && targetChar !== \" \";\n\n        if (shouldGlitch) {\n          currentText += randomWrongChar();\n          setText(currentText);\n\n          schedule(\n            () => {\n              currentText = currentText.slice(0, -1);\n              setText(currentText);\n\n              schedule(() => {\n                if (Math.random() > 0.5) {\n                  currentText += randomWrongChar();\n                  setText(currentText);\n\n                  schedule(() => {\n                    currentText = currentText.slice(0, -1);\n                    setText(currentText);\n\n                    schedule(() => {\n                      currentText += targetChar;\n                      setText(currentText);\n                      targetIndex++;\n                      schedule(typeChar, 50 + Math.random() * 100);\n                    }, 80);\n                  }, 120);\n                } else {\n                  currentText += targetChar;\n                  setText(currentText);\n                  targetIndex++;\n                  schedule(typeChar, 50 + Math.random() * 100);\n                }\n              }, 80);\n            },\n            100 + Math.random() * 150\n          );\n        } else {\n          currentText += targetChar;\n          setText(currentText);\n          targetIndex++;\n          schedule(typeChar, 40 + Math.random() * 80);\n        }\n      };\n\n      setText(\"\");\n      setShowCursor(true);\n      schedule(typeChar, 500);\n    };\n\n    runAnimation();\n\n    return () => {\n      for (const id of timeouts) {\n        clearTimeout(id);\n      }\n      timeouts.clear();\n    };\n  }, [children, duration]);\n\n  return (\n    <div className={cn(className)}>\n      <span aria-live=\"polite\">\n        {text}\n        {showCursor ? (\n          <motion.span\n            animate={{ opacity: [1, 0, 1] }}\n            aria-hidden\n            transition={{\n              duration: 0.8,\n              ease: \"linear\",\n              repeat: Number.POSITIVE_INFINITY,\n            }}\n          >\n            |\n          </motion.span>\n        ) : null}\n      </span>\n    </div>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "title": "Typewriter",
  "description": "Looping typewriter text effect with brief glitch substitutions and a blinking cursor."
}
