{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "type-writer",
  "type": "registry:component",
  "title": "Typewriter",
  "description": "Looping typewriter title with natural typing variance and a blinking caret (KokonutUI, MIT), bridged to contract tokens.",
  "dependencies": [
    "framer-motion"
  ],
  "registryDependencies": [
    "https://design.subconscious.ai/r/utils.json"
  ],
  "files": [
    {
      "path": "registry/components/type-writer.tsx",
      "type": "registry:component",
      "content": "\"use client\";\n\n/**\n * @author: @dorianbaffier\n * @description: Typewriter\n * @version: 1.0.0\n * @date: 2025-06-26\n * @license: MIT\n * @website: https://kokonutui.com\n * @github: https://github.com/kokonut-labs/kokonutui\n *\n * Bridged to the Subconscious design contract. Root is a headless <span> with\n * no layout/typography opinions — callers wrap with their own heading/spacing.\n * Color binds to `var(--color)` so the cursor + caret theme-track via\n * data-theme / data-mode.\n */\n\nimport { motion } from \"framer-motion\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/registry/lib/utils\";\n\ntype TypewriterSequence = {\n  text: string;\n  deleteAfter?: boolean;\n  pauseAfter?: number;\n};\n\ntype TypewriterTitleProps = Omit<\n  React.HTMLAttributes<HTMLSpanElement>,\n  \"children\"\n> & {\n  sequences?: TypewriterSequence[];\n  typingSpeed?: number;\n  startDelay?: number;\n  autoLoop?: boolean;\n  loopDelay?: number;\n  deleteSpeed?: number;\n  pauseBeforeDelete?: number;\n  naturalVariance?: boolean;\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\nexport default function TypewriterTitle({\n  sequences = DEFAULT_SEQUENCES,\n  typingSpeed = 50,\n  startDelay = 200,\n  autoLoop = true,\n  loopDelay = 1000,\n  deleteSpeed = 30,\n  pauseBeforeDelete = 1000,\n  naturalVariance = true,\n  className,\n  style,\n  ...rest\n}: TypewriterTitleProps) {\n  const [displayText, setDisplayText] = useState(\"\");\n  const sequenceIndexRef = useRef(0);\n  const charIndexRef = useRef(0);\n  const isDeletingRef = useRef(false);\n  const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n  // Initialize with the sequences provided\n  const sequencesRef = useRef(sequences);\n  useEffect(() => {\n    sequencesRef.current = sequences;\n  }, [sequences]);\n\n  useEffect(() => {\n    const getTypingDelay = () => {\n      if (!naturalVariance) {\n        return typingSpeed;\n      }\n\n      // More natural human typing pattern\n      const random = Math.random();\n\n      // 10% chance of a longer pause (thinking/hesitation)\n      if (random < 0.1) {\n        return typingSpeed * 2;\n      }\n\n      // 10% chance of a burst (fast typing)\n      if (random > 0.9) {\n        return typingSpeed * 0.5;\n      }\n\n      // Standard variance (+/- 40%)\n      const variance = 0.4;\n      const min = typingSpeed * (1 - variance);\n      const max = typingSpeed * (1 + variance);\n      return Math.random() * (max - min) + min;\n    };\n\n    const runTypewriter = () => {\n      const currentSequence = sequencesRef.current[sequenceIndexRef.current];\n      if (!currentSequence) {\n        return;\n      }\n\n      if (isDeletingRef.current) {\n        if (charIndexRef.current > 0) {\n          charIndexRef.current -= 1;\n          setDisplayText(currentSequence.text.slice(0, charIndexRef.current));\n          timeoutRef.current = setTimeout(runTypewriter, deleteSpeed);\n        } else {\n          isDeletingRef.current = false;\n          const isLastSequence =\n            sequenceIndexRef.current === sequencesRef.current.length - 1;\n\n          if (isLastSequence && autoLoop) {\n            timeoutRef.current = setTimeout(() => {\n              sequenceIndexRef.current = 0;\n              runTypewriter();\n            }, loopDelay);\n          } else if (!isLastSequence) {\n            timeoutRef.current = setTimeout(() => {\n              sequenceIndexRef.current += 1;\n              runTypewriter();\n            }, 100); // Quick transition to next word\n          }\n        }\n      } else if (charIndexRef.current < currentSequence.text.length) {\n        charIndexRef.current += 1;\n        setDisplayText(currentSequence.text.slice(0, charIndexRef.current));\n        timeoutRef.current = setTimeout(runTypewriter, getTypingDelay());\n      } else {\n        const pauseDuration = currentSequence.pauseAfter ?? pauseBeforeDelete;\n\n        if (currentSequence.deleteAfter) {\n          timeoutRef.current = setTimeout(() => {\n            isDeletingRef.current = true;\n            runTypewriter();\n          }, pauseDuration);\n        } else {\n          const isLastSequence =\n            sequenceIndexRef.current === sequencesRef.current.length - 1;\n\n          if (isLastSequence && autoLoop) {\n            timeoutRef.current = setTimeout(() => {\n              sequenceIndexRef.current = 0;\n              charIndexRef.current = 0;\n              setDisplayText(\"\");\n              runTypewriter();\n            }, loopDelay);\n          } else if (!isLastSequence) {\n            timeoutRef.current = setTimeout(() => {\n              sequenceIndexRef.current += 1;\n              charIndexRef.current = 0;\n              setDisplayText(\"\");\n              runTypewriter();\n            }, pauseDuration);\n          }\n        }\n      }\n    };\n\n    // Start the loop\n    timeoutRef.current = setTimeout(runTypewriter, startDelay);\n\n    return () => {\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n    };\n  }, [\n    // Only restart effect if timing configs change.\n    // We use sequencesRef for content to avoid restarting on array reference change.\n    typingSpeed,\n    deleteSpeed,\n    pauseBeforeDelete,\n    autoLoop,\n    loopDelay,\n    startDelay,\n    naturalVariance,\n  ]);\n\n  return (\n    <motion.span\n      animate={{ opacity: 1 }}\n      className={cn(\"inline-flex items-center gap-1\", className)}\n      initial={{ opacity: 0 }}\n      style={{ color: \"var(--color)\", ...style }}\n      transition={{ duration: 0.5 }}\n      {...rest}\n    >\n      <span\n        style={{ display: \"inline-block\", minWidth: \"0.5em\", minHeight: \"1.2em\" }}\n      >\n        {displayText}\n      </span>\n      <motion.span\n        animate={{\n          opacity: [1, 1, 0, 0],\n        }}\n        className=\"inline-block h-[1em] w-[3px]\"\n        style={{ background: \"var(--color)\" }}\n        transition={{\n          duration: 1,\n          repeat: Number.POSITIVE_INFINITY,\n          repeatType: \"loop\",\n          ease: \"linear\",\n        }}\n      />\n    </motion.span>\n  );\n}\n"
    }
  ]
}
