{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-texts-typewriter",
  "title": "Typewriter",
  "description": "Animated typewriter effect with multi-sequence support, natural variance, delete animation, and auto-loop.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/components/texts/typewriter/index.tsx",
      "content": "'use client';\n\nimport { motion } from 'motion/react';\nimport {\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n  useCallback,\n} from 'react';\n\ntype TypewriterSequence = {\n  text: string;\n  deleteAfter?: boolean;\n  pauseAfter?: number;\n};\n\ninterface TypewriterProps {\n  sequences?: TypewriterSequence[];\n  typingSpeed?: number;\n  deleteSpeed?: number;\n  startDelay?: number;\n  pauseBeforeDelete?: number;\n  loopDelay?: number;\n  autoLoop?: boolean;\n  naturalVariance?: boolean;\n  className?: string;\n}\n\nconst DEFAULT_SEQUENCES: TypewriterSequence[] = [\n  { text: 'Typewriter', deleteAfter: true },\n  { text: 'Multiple Words', deleteAfter: true },\n  { text: 'Auto Loop', deleteAfter: false },\n];\n\nconst getRandomTypingDelay = (baseSpeed: number): number => {\n  if (Math.random() < 0.1) return baseSpeed * 2;\n  if (Math.random() > 0.9) return baseSpeed * 0.5;\n  const variance = 0.4;\n  const min = baseSpeed * (1 - variance);\n  const max = baseSpeed * (1 + variance);\n  return Math.random() * (max - min) + min;\n};\n\nexport function Typewriter({\n  sequences = DEFAULT_SEQUENCES,\n  typingSpeed = 50,\n  deleteSpeed = 30,\n  startDelay = 200,\n  pauseBeforeDelete = 1000,\n  loopDelay = 1000,\n  autoLoop = true,\n  naturalVariance = true,\n  className,\n}: TypewriterProps) {\n  const [displayText, setDisplayText] = useState('');\n\n  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const sequenceIndexRef = useRef(0);\n  const charIndexRef = useRef(0);\n  const sequencesRef = useRef(sequences);\n  const typeNextCharRef = useRef<() => void>(() => {});\n  const deleteCharRef = useRef<() => void>(() => {});\n\n  useLayoutEffect(() => {\n    sequencesRef.current = sequences;\n  });\n\n  const schedule = useCallback((fn: () => void, delay: number) => {\n    if (timeoutRef.current) clearTimeout(timeoutRef.current);\n    timeoutRef.current = setTimeout(fn, delay);\n  }, []);\n\n  const typeNextChar = useCallback(() => {\n    const seq = sequencesRef.current[sequenceIndexRef.current];\n    if (!seq) return;\n\n    if (charIndexRef.current < seq.text.length) {\n      charIndexRef.current += 1;\n      setDisplayText(seq.text.slice(0, charIndexRef.current));\n\n      const delay = naturalVariance\n        ? getRandomTypingDelay(typingSpeed)\n        : typingSpeed;\n\n      schedule(typeNextCharRef.current, delay);\n    } else {\n      const pause = seq.pauseAfter ?? pauseBeforeDelete;\n\n      if (seq.deleteAfter !== false) {\n        schedule(() => deleteCharRef.current(), pause);\n      } else {\n        schedule(() => {\n          const isLast =\n            sequenceIndexRef.current === sequencesRef.current.length - 1;\n\n          if (isLast && autoLoop) {\n            sequenceIndexRef.current = 0;\n            charIndexRef.current = 0;\n            setDisplayText('');\n            typeNextCharRef.current();\n          } else if (!isLast) {\n            sequenceIndexRef.current += 1;\n            charIndexRef.current = 0;\n            setDisplayText('');\n            typeNextCharRef.current();\n          }\n        }, loopDelay);\n      }\n    }\n  }, [\n    typingSpeed,\n    naturalVariance,\n    pauseBeforeDelete,\n    autoLoop,\n    loopDelay,\n    schedule,\n  ]);\n\n  const deleteChar = useCallback(() => {\n    if (charIndexRef.current > 0) {\n      charIndexRef.current -= 1;\n      const text = sequencesRef.current[sequenceIndexRef.current]?.text ?? '';\n      setDisplayText(text.slice(0, charIndexRef.current));\n      schedule(deleteCharRef.current, deleteSpeed);\n    } else {\n      const isLast =\n        sequenceIndexRef.current === sequencesRef.current.length - 1;\n\n      if (isLast && autoLoop) {\n        schedule(() => {\n          sequenceIndexRef.current = 0;\n          typeNextCharRef.current();\n        }, loopDelay);\n      } else if (!isLast) {\n        schedule(() => {\n          sequenceIndexRef.current += 1;\n          typeNextCharRef.current();\n        }, 80);\n      }\n    }\n  }, [deleteSpeed, autoLoop, loopDelay, schedule]);\n\n  useLayoutEffect(() => {\n    typeNextCharRef.current = typeNextChar;\n    deleteCharRef.current = deleteChar;\n  });\n\n  const startAnimation = useCallback(() => {\n    sequenceIndexRef.current = 0;\n    charIndexRef.current = 0;\n    schedule(() => {\n      setDisplayText('');\n      typeNextCharRef.current();\n    }, startDelay);\n  }, [startDelay, schedule]);\n\n  useEffect(() => {\n    startAnimation();\n    return () => {\n      if (timeoutRef.current) clearTimeout(timeoutRef.current);\n    };\n  }, [sequences, typingSpeed, deleteSpeed, naturalVariance, startAnimation]);\n\n  return (\n    <motion.span\n      initial={{ opacity: 0 }}\n      animate={{ opacity: 1 }}\n      transition={{ duration: 0.5 }}\n      className={className}\n    >\n      <span className=\"inline-block min-h-[1.2em] min-w-[0.5em]\">\n        {displayText}\n      </span>\n    </motion.span>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/odysseyui/typewriter.tsx"
    }
  ],
  "type": "registry:ui"
}