{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typing-animation",
  "title": "Typing Animation",
  "description": "A minimal typing animation with smart backspacing, an optional cursor, and adjustable speed.",
  "dependencies": [],
  "files": [
    {
      "path": "components/animation/TypingAnimation.tsx",
      "content": "\"use client\";\n\nimport { useEffect, useRef, useState } from \"react\";\n\ntype TypingAnimationProps = {\n  children?: string;\n  speed?: number;\n  deleteSpeed?: number;\n  cursor?: boolean;\n  className?: string;\n};\n\nexport default function TypingAnimation({\n  children = \"Make something great.\",\n  speed = 65,\n  deleteSpeed = 30,\n  cursor = true,\n  className = \"\",\n}: TypingAnimationProps) {\n  const [displayed, setDisplayed] = useState(\"\");\n  const current = useRef<string[]>([]);\n\n  useEffect(() => {\n    const target = Array.from(\n      new Intl.Segmenter(undefined, { granularity: \"grapheme\" }).segment(children),\n      ({ segment }) => segment,\n    );\n    const preference = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    let timer: ReturnType<typeof setTimeout>;\n\n    function advance() {\n      clearTimeout(timer);\n      if (preference.matches) {\n        current.current = target;\n        setDisplayed(children);\n        return;\n      }\n\n      const value = current.current;\n      const matches = value.every((letter, index) => letter === target[index]);\n      if (matches && value.length === target.length) return;\n\n      timer = setTimeout(() => {\n        current.current = matches\n          ? target.slice(0, value.length + 1)\n          : value.slice(0, -1);\n        setDisplayed(current.current.join(\"\"));\n        advance();\n      }, Math.max(0, matches ? speed : deleteSpeed));\n    }\n\n    advance();\n    preference.addEventListener(\"change\", advance);\n    return () => {\n      clearTimeout(timer);\n      preference.removeEventListener(\"change\", advance);\n    };\n  }, [children, speed, deleteSpeed]);\n\n  return (\n    <span className={`whitespace-pre-wrap ${className}`}>\n      <span className=\"sr-only\">{children}</span>\n      <span aria-hidden=\"true\" className=\"motion-reduce:hidden\">\n        {displayed}\n        {cursor && (\n          <span className=\"ml-0.5 inline-block h-[0.9em] w-[2px] animate-pulse bg-current align-baseline\" />\n        )}\n      </span>\n      <span aria-hidden=\"true\" className=\"hidden motion-reduce:inline\">{children}</span>\n    </span>\n  );\n}\n",
      "type": "registry:component",
      "target": "@components/animation/TypingAnimation.tsx"
    }
  ],
  "categories": [
    "animation"
  ],
  "type": "registry:component"
}