{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "animated-textgenerate",
  "type": "registry:ui",
  "title": "Animated TextGenerate",
  "description": "Generates animated text word-by-word with blur, highlights, links, and smooth effects.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/animated-textgenerate.tsx",
      "content": "'use client';\n\nimport Link from 'next/link';\nimport { useEffect, useState } from 'react';\nimport { motion } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ninterface AnimatedTextGenerateProps {\n  text: string;\n  className?: string;\n  textClassName?: string;\n  blurEffect?: boolean;\n  speed?: number;\n  highlightWords?: string[];\n  highlightClassName?: string;\n  linkWords?: string[];\n  linkHrefs?: string[];\n  linkClassNames?: string[];\n}\n\nexport const AnimatedTextGenerate = ({\n  text,\n  className,\n  textClassName,\n  blurEffect = true,\n  speed = 0.5,\n  highlightWords = [],\n  highlightClassName,\n  linkWords = [],\n  linkHrefs = [],\n  linkClassNames = [],\n}: AnimatedTextGenerateProps) => {\n  const [currentText, setCurrentText] = useState(text);\n  const [visibleCount, setVisibleCount] = useState(0);\n  const splitWords = text.split(' ');\n\n  if (currentText !== text) {\n    setCurrentText(text);\n    setVisibleCount(0);\n  }\n\n  useEffect(() => {\n    const intervalId = setInterval(\n      () => {\n        setVisibleCount((prev) => {\n          if (prev >= splitWords.length) {\n            clearInterval(intervalId);\n            return prev;\n          }\n          return prev + 1;\n        });\n      },\n      Math.max(speed * 200, 100),\n    );\n\n    return () => clearInterval(intervalId);\n  }, [speed, splitWords.length]);\n\n  const generateWords = () => {\n    return (\n      <div className='flex flex-wrap items-center gap-1'>\n        {splitWords.map((word, idx) => {\n          const isVisible = idx < visibleCount;\n          const remaining = splitWords.length - visibleCount;\n          let capsuleCount = 4;\n          if (remaining <= 2) capsuleCount = remaining;\n          else if (remaining <= 4) capsuleCount = Math.min(3, remaining);\n          else if (visibleCount === 0) capsuleCount = 2;\n          else if (visibleCount < 3) capsuleCount = 3;\n\n          const isUpcoming =\n            idx >= visibleCount && idx < visibleCount + capsuleCount;\n          const isHighlight =\n            highlightWords.length > 0 &&\n            highlightWords.some((hw) =>\n              word.toLowerCase().includes(hw.toLowerCase()),\n            );\n          const linkIndex = linkWords.findIndex((lw) =>\n            word.toLowerCase().includes(lw.toLowerCase()),\n          );\n          const isLink = linkIndex !== -1;\n\n          if (isVisible) {\n            const wordElement = (\n              <motion.span\n                key={`${word}-${idx}`}\n                initial={{\n                  opacity: 0,\n                  filter: blurEffect ? 'blur(10px)' : 'none',\n                }}\n                animate={{\n                  opacity: 1,\n                  filter: blurEffect ? 'blur(0px)' : 'none',\n                }}\n                transition={{\n                  duration: speed * 0.3,\n                  ease: 'easeOut',\n                }}\n                className={cn(\n                  'dark:text-white text-black',\n                  isHighlight && highlightClassName,\n                )}\n              >\n                {word}\n              </motion.span>\n            );\n\n            if (isLink && linkHrefs[linkIndex]) {\n              return (\n                <Link\n                  href={linkHrefs[linkIndex]}\n                  key={`link-${idx}`}\n                  className={cn(linkClassNames[linkIndex])}\n                >\n                  {wordElement}\n                </Link>\n              );\n            }\n            return wordElement;\n          }\n\n          if (isUpcoming) {\n            return (\n              <motion.div\n                key={`placeholder-${idx}`}\n                initial={{ opacity: 0, scale: 0.8 }}\n                animate={{ opacity: 0.4, scale: 1 }}\n                exit={{ opacity: 0, scale: 0.8 }}\n                transition={{ duration: 0.2 }}\n                className='bg-black dark:bg-gray-600 rounded-full'\n                style={{\n                  width: `${Math.max(word.length * 0.7, 2.5)}em`,\n                  height: '0.9em',\n                  display: 'inline-block',\n                }}\n              />\n            );\n          }\n\n          return null;\n        })}\n      </div>\n    );\n  };\n\n  return (\n    <div className={cn('font-bold', className)}>\n      <div className='mt-4'>\n        <div\n          className={cn(\n            'dark:text-white text-black text-2xl leading-snug tracking-wide',\n            textClassName,\n          )}\n        >\n          {generateWords()}\n        </div>\n      </div>\n    </div>\n  );\n};\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
