{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "TextType-JS-CSS",
	"title": "TextType",
	"description": "Typewriter effect with blinking cursor and adjustable typing cadence.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "TextType.css",
			"target": "@components/TextType.css",
			"content": ".text-type {\n  display: inline-block;\n  white-space: pre-wrap;\n}\n\n.text-type__cursor {\n  margin-left: 0.25rem;\n  display: inline-block;\n  opacity: 1;\n}\n\n.text-type__cursor--hidden {\n  display: none;\n}\n"
		},
		{
			"type": "registry:component",
			"path": "TextType.jsx",
			"content": "'use client';\n\nimport { useEffect, useRef, useState, createElement, useMemo, useCallback } from 'react';\nimport { gsap } from 'gsap';\nimport './TextType.css';\n\nconst TextType = ({\n  text,\n  as: Component = 'div',\n  typingSpeed = 50,\n  initialDelay = 0,\n  pauseDuration = 2000,\n  deletingSpeed = 30,\n  loop = true,\n  className = '',\n  showCursor = true,\n  hideCursorWhileTyping = false,\n  cursorCharacter = '|',\n  cursorClassName = '',\n  cursorBlinkDuration = 0.5,\n  textColors = [],\n  variableSpeed,\n  onSentenceComplete,\n  startOnVisible = false,\n  reverseMode = false,\n  ...props\n}) => {\n  const [displayedText, setDisplayedText] = useState('');\n  const [currentCharIndex, setCurrentCharIndex] = useState(0);\n  const [isDeleting, setIsDeleting] = useState(false);\n  const [currentTextIndex, setCurrentTextIndex] = useState(0);\n  const [isVisible, setIsVisible] = useState(!startOnVisible);\n  const cursorRef = useRef(null);\n  const containerRef = useRef(null);\n\n  const textArray = useMemo(() => (Array.isArray(text) ? text : [text]), [text]);\n\n  const getRandomSpeed = useCallback(() => {\n    if (!variableSpeed) return typingSpeed;\n    const { min, max } = variableSpeed;\n    return Math.random() * (max - min) + min;\n  }, [variableSpeed, typingSpeed]);\n\n  const getCurrentTextColor = () => {\n    if (textColors.length === 0) return 'inherit';\n    return textColors[currentTextIndex % textColors.length];\n  };\n\n  useEffect(() => {\n    if (!startOnVisible || !containerRef.current) return;\n\n    const observer = new IntersectionObserver(\n      entries => {\n        entries.forEach(entry => {\n          if (entry.isIntersecting) {\n            setIsVisible(true);\n          }\n        });\n      },\n      { threshold: 0.1 }\n    );\n\n    observer.observe(containerRef.current);\n    return () => observer.disconnect();\n  }, [startOnVisible]);\n\n  useEffect(() => {\n    if (showCursor && cursorRef.current) {\n      gsap.set(cursorRef.current, { opacity: 1 });\n      gsap.to(cursorRef.current, {\n        opacity: 0,\n        duration: cursorBlinkDuration,\n        repeat: -1,\n        yoyo: true,\n        ease: 'power2.inOut'\n      });\n    }\n  }, [showCursor, cursorBlinkDuration]);\n\n  useEffect(() => {\n    if (!isVisible) return;\n\n    let timeout;\n    const currentText = textArray[currentTextIndex];\n    const processedText = reverseMode ? currentText.split('').reverse().join('') : currentText;\n\n    const executeTypingAnimation = () => {\n      if (isDeleting) {\n        if (displayedText === '') {\n          setIsDeleting(false);\n          if (currentTextIndex === textArray.length - 1 && !loop) {\n            return;\n          }\n\n          if (onSentenceComplete) {\n            onSentenceComplete(textArray[currentTextIndex], currentTextIndex);\n          }\n\n          setCurrentTextIndex(prev => (prev + 1) % textArray.length);\n          setCurrentCharIndex(0);\n          timeout = setTimeout(() => {}, pauseDuration);\n        } else {\n          timeout = setTimeout(() => {\n            setDisplayedText(prev => prev.slice(0, -1));\n          }, deletingSpeed);\n        }\n      } else {\n        if (currentCharIndex < processedText.length) {\n          timeout = setTimeout(\n            () => {\n              setDisplayedText(prev => prev + processedText[currentCharIndex]);\n              setCurrentCharIndex(prev => prev + 1);\n            },\n            variableSpeed ? getRandomSpeed() : typingSpeed\n          );\n        } else if (textArray.length >= 1) {\n          if (!loop && currentTextIndex === textArray.length - 1) return;\n          timeout = setTimeout(() => {\n            setIsDeleting(true);\n          }, pauseDuration);\n        }\n      }\n    };\n\n    if (currentCharIndex === 0 && !isDeleting && displayedText === '') {\n      timeout = setTimeout(executeTypingAnimation, initialDelay);\n    } else {\n      executeTypingAnimation();\n    }\n\n    return () => clearTimeout(timeout);\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [\n    currentCharIndex,\n    displayedText,\n    isDeleting,\n    typingSpeed,\n    deletingSpeed,\n    pauseDuration,\n    textArray,\n    currentTextIndex,\n    loop,\n    initialDelay,\n    isVisible,\n    reverseMode,\n    variableSpeed,\n    onSentenceComplete\n  ]);\n\n  const shouldHideCursor =\n    hideCursorWhileTyping && (currentCharIndex < textArray[currentTextIndex].length || isDeleting);\n\n  return createElement(\n    Component,\n    {\n      ref: containerRef,\n      className: `text-type ${className}`,\n      ...props\n    },\n    <span className=\"text-type__content\" style={{ color: getCurrentTextColor() || 'inherit' }}>\n      {displayedText}\n    </span>,\n    showCursor && (\n      <span\n        ref={cursorRef}\n        className={`text-type__cursor ${cursorClassName} ${shouldHideCursor ? 'text-type__cursor--hidden' : ''}`}\n      >\n        {cursorCharacter}\n      </span>\n    )\n  );\n};\n\nexport default TextType;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"gsap@^3.13.0"
	]
}