{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hyper-text",
  "type": "registry:ui",
  "title": "Hyper Text",
  "description": "A text animation that scrambles letters before revealing the final text.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/magicui/hyper-text.tsx",
      "content": "\"use client\"\n\nimport {\n  useEffect,\n  useRef,\n  useState,\n  type ComponentType,\n  type RefAttributes,\n} from \"react\"\nimport {\n  AnimatePresence,\n  motion,\n  type DOMMotionComponents,\n  type HTMLMotionProps,\n  type MotionProps,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype CharacterSet = string[] | readonly string[]\n\nconst motionElements = {\n  article: motion.article,\n  div: motion.div,\n  h1: motion.h1,\n  h2: motion.h2,\n  h3: motion.h3,\n  h4: motion.h4,\n  h5: motion.h5,\n  h6: motion.h6,\n  li: motion.li,\n  p: motion.p,\n  section: motion.section,\n  span: motion.span,\n} as const\n\ntype MotionElementType = Extract<\n  keyof DOMMotionComponents,\n  keyof typeof motionElements\n>\ntype HyperTextMotionComponent = ComponentType<\n  Omit<HTMLMotionProps<\"div\">, \"ref\"> & RefAttributes<HTMLElement>\n>\n\ninterface HyperTextProps extends Omit<MotionProps, \"children\"> {\n  /** The text content to be animated */\n  children: string\n  /** Optional className for styling */\n  className?: string\n  /** Duration of the animation in milliseconds */\n  duration?: number\n  /** Delay before animation starts in milliseconds */\n  delay?: number\n  /** Component to render as - defaults to div */\n  as?: MotionElementType\n  /** Whether to start animation when element comes into view */\n  startOnView?: boolean\n  /** Whether to trigger animation on hover */\n  animateOnHover?: boolean\n  /** Custom character set for scramble effect. Defaults to uppercase alphabet */\n  characterSet?: CharacterSet\n}\n\nconst DEFAULT_CHARACTER_SET = Object.freeze(\n  \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\".split(\"\")\n) as readonly string[]\n\nconst getRandomInt = (max: number): number => Math.floor(Math.random() * max)\n\nexport function HyperText({\n  children,\n  className,\n  duration = 800,\n  delay = 0,\n  as: Component = \"div\",\n  startOnView = false,\n  animateOnHover = true,\n  characterSet = DEFAULT_CHARACTER_SET,\n  ...props\n}: HyperTextProps) {\n  const MotionComponent = motionElements[Component] as HyperTextMotionComponent\n\n  const [displayText, setDisplayText] = useState<string[]>(() =>\n    children.split(\"\")\n  )\n  const [isAnimating, setIsAnimating] = useState(false)\n  const iterationCount = useRef(0)\n  const elementRef = useRef<HTMLElement | null>(null)\n\n  const handleAnimationTrigger = () => {\n    if (animateOnHover && !isAnimating) {\n      iterationCount.current = 0\n      setIsAnimating(true)\n    }\n  }\n\n  // Handle animation start based on view or delay\n  useEffect(() => {\n    if (!startOnView) {\n      const startTimeout = setTimeout(() => {\n        setIsAnimating(true)\n      }, delay)\n      return () => clearTimeout(startTimeout)\n    }\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        if (entry.isIntersecting) {\n          setTimeout(() => {\n            setIsAnimating(true)\n          }, delay)\n          observer.disconnect()\n        }\n      },\n      { threshold: 0.1, rootMargin: \"-30% 0px -30% 0px\" }\n    )\n\n    if (elementRef.current) {\n      observer.observe(elementRef.current)\n    }\n\n    return () => observer.disconnect()\n  }, [delay, startOnView])\n\n  // Handle scramble animation\n  useEffect(() => {\n    let animationFrameId: number | null = null\n\n    if (isAnimating) {\n      const maxIterations = children.length\n      const startTime = performance.now()\n\n      const animate = (currentTime: number) => {\n        const elapsed = currentTime - startTime\n        const progress = Math.min(elapsed / duration, 1)\n\n        iterationCount.current = progress * maxIterations\n\n        setDisplayText((currentText) =>\n          currentText.map((letter, index) =>\n            letter === \" \"\n              ? letter\n              : index <= iterationCount.current\n                ? children[index]\n                : characterSet[getRandomInt(characterSet.length)]\n          )\n        )\n\n        if (progress < 1) {\n          animationFrameId = requestAnimationFrame(animate)\n        } else {\n          setIsAnimating(false)\n        }\n      }\n\n      animationFrameId = requestAnimationFrame(animate)\n    }\n\n    return () => {\n      if (animationFrameId !== null) {\n        cancelAnimationFrame(animationFrameId)\n      }\n    }\n  }, [children, duration, isAnimating, characterSet])\n\n  return (\n    <MotionComponent\n      ref={elementRef}\n      className={cn(\"overflow-hidden py-2 text-4xl font-bold\", className)}\n      onMouseEnter={handleAnimationTrigger}\n      {...props}\n    >\n      <AnimatePresence>\n        {displayText.map((letter, index) => (\n          <motion.span\n            key={index}\n            className={cn(\"font-mono\", letter === \" \" ? \"w-3\" : \"\")}\n          >\n            {letter.toUpperCase()}\n          </motion.span>\n        ))}\n      </AnimatePresence>\n    </MotionComponent>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}