{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "typography-effects",
  "title": "Typography Effects",
  "description": "Motion typography utilities for shimmer text, cycling loading messages, and lyric-style active word emphasis.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "button"
  ],
  "files": [
    {
      "path": "registry/corr/components/typography-effects.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { AnimatePresence, motion, useReducedMotion } from \"motion/react\"\n\nimport { ShimmeringText } from \"@/components/animate-ui/primitives/texts/shimmering\"\nimport { cn } from \"@/lib/utils\"\n\nexport type ShimmerTextProps = {\n  children: React.ReactNode\n  duration?: number\n  className?: string\n  disabled?: boolean\n}\n\nexport function ShimmerText({\n  children,\n  duration = 1.4,\n  className,\n  disabled,\n}: ShimmerTextProps) {\n  const reduceMotion = useReducedMotion()\n  const shouldAnimate = !disabled && !reduceMotion\n  const text = React.Children.toArray(children).join(\"\")\n\n  if (!shouldAnimate) {\n    return (\n      <span className={cn(\"inline-block overflow-visible pb-1\", className)}>\n        {children}\n      </span>\n    )\n  }\n\n  return (\n    <ShimmeringText\n      text={text}\n      duration={duration}\n      color=\"var(--muted-foreground)\"\n      shimmeringColor=\"var(--foreground)\"\n      className={cn(\n        \"overflow-visible pb-1 [&>span]:overflow-visible\",\n        className\n      )}\n    />\n  )\n}\n\nexport type LoadingTextProps = {\n  messages: string[]\n  interval?: number\n  className?: string\n  textClassName?: string\n  reserveWidth?: number | string\n}\n\nexport function LoadingText({\n  messages,\n  interval = 1800,\n  className,\n  textClassName,\n  reserveWidth,\n}: LoadingTextProps) {\n  const reduceMotion = useReducedMotion()\n  const [index, setIndex] = React.useState(0)\n  const safeMessages = messages.length ? messages : [\"Loading\"]\n  const message = safeMessages[index % safeMessages.length]\n\n  React.useEffect(() => {\n    if (safeMessages.length <= 1) return\n    const timer = window.setInterval(\n      () => setIndex((current) => current + 1),\n      interval\n    )\n\n    return () => window.clearInterval(timer)\n  }, [interval, safeMessages.length])\n\n  return (\n    <span\n      className={cn(\n        \"inline-flex min-h-6 items-center overflow-hidden text-sm font-medium text-muted-foreground\",\n        className\n      )}\n      style={reserveWidth ? { width: reserveWidth } : undefined}\n      aria-live=\"polite\"\n    >\n      <AnimatePresence mode=\"wait\" initial={false}>\n        <motion.span\n          key={message}\n          className={cn(\"inline-block\", textClassName)}\n          initial={{\n            opacity: 0,\n            y: reduceMotion ? 0 : 4,\n            filter: reduceMotion ? \"blur(0px)\" : \"blur(6px)\",\n          }}\n          animate={{ opacity: 1, y: 0, filter: \"blur(0px)\" }}\n          exit={{\n            opacity: 0,\n            y: reduceMotion ? 0 : -4,\n            filter: reduceMotion ? \"blur(0px)\" : \"blur(6px)\",\n          }}\n          transition={{ duration: reduceMotion ? 0 : 0.22, ease: \"easeOut\" }}\n        >\n          {message}\n        </motion.span>\n      </AnimatePresence>\n    </span>\n  )\n}\n\nexport type LyricTextWord = {\n  text: string\n  active?: boolean\n}\n\nexport type LyricTextLine = {\n  words: Array<string | LyricTextWord>\n  active?: boolean\n}\n\nexport type LyricTextProps = {\n  lines: LyricTextLine[]\n  className?: string\n  lineClassName?: string\n  activeWordClassName?: string\n  activeIndex?: number\n  autoPlay?: boolean\n  interval?: number\n}\n\nfunction normalizeWord(word: string | LyricTextWord): LyricTextWord {\n  return typeof word === \"string\" ? { text: word } : word\n}\n\nfunction LyricWord({\n  text,\n  active,\n  passed,\n  reduceMotion,\n  className,\n}: {\n  text: string\n  active: boolean\n  passed: boolean\n  reduceMotion: boolean | null\n  className?: string\n}) {\n  return (\n    <motion.span\n      className={cn(\n        \"relative inline-grid overflow-visible transition-colors\",\n        passed && \"text-foreground\",\n        className\n      )}\n      initial={false}\n      animate={{ y: passed && !reduceMotion ? -3 : 0 }}\n      transition={{ duration: active ? 0.2 : 0.18, ease: \"easeOut\" }}\n    >\n      <span className=\"col-start-1 row-start-1 invisible whitespace-pre\">\n        {text}\n      </span>\n      <span\n        aria-hidden=\"true\"\n        className=\"col-start-1 row-start-1 whitespace-pre\"\n      >\n        {active && !reduceMotion\n          ? text.split(\"\").map((character, index) => (\n              <motion.span\n                key={`${character}-${index}`}\n                className=\"inline-block whitespace-pre\"\n                initial={{\n                  color: \"var(--muted-foreground)\",\n                  opacity: 0.55,\n                  y: 4,\n                  filter: \"blur(5px)\",\n                }}\n                animate={{\n                  color: [\n                    \"var(--muted-foreground)\",\n                    \"var(--foreground)\",\n                    \"var(--foreground)\",\n                  ],\n                  opacity: [0.55, 1, 1],\n                  y: [4, -2, 0],\n                  filter: [\"blur(5px)\", \"blur(0px)\", \"blur(0px)\"],\n                }}\n                transition={{\n                  duration: 0.7,\n                  delay: index * 0.075,\n                  ease: \"easeOut\",\n                }}\n              >\n                {character}\n              </motion.span>\n            ))\n          : text}\n      </span>\n    </motion.span>\n  )\n}\n\nexport function LyricText({\n  lines,\n  className,\n  lineClassName,\n  activeWordClassName,\n  activeIndex,\n  autoPlay = true,\n  interval = 620,\n}: LyricTextProps) {\n  const reduceMotion = useReducedMotion()\n  const words = React.useMemo(\n    () => lines.flatMap((line) => line.words.map(normalizeWord)),\n    [lines]\n  )\n  const [internalIndex, setInternalIndex] = React.useState(0)\n  const currentIndex = activeIndex ?? internalIndex\n\n  React.useEffect(() => {\n    if (!autoPlay || reduceMotion || typeof activeIndex === \"number\") return\n    if (words.length <= 1) return\n\n    const timer = window.setInterval(() => {\n      setInternalIndex((index) => (index + 1) % words.length)\n    }, interval)\n\n    return () => window.clearInterval(timer)\n  }, [activeIndex, autoPlay, interval, reduceMotion, words.length])\n\n  let wordCursor = -1\n\n  return (\n    <div className={cn(\"space-y-5 overflow-visible py-1\", className)}>\n      {lines.map((line, lineIndex) => (\n        <p\n          key={lineIndex}\n          className={cn(\n            \"overflow-visible text-3xl leading-[1.18] font-semibold tracking-tight text-muted-foreground/40\",\n            line.active && \"text-muted-foreground/55\",\n            lineClassName\n          )}\n        >\n          {line.words.map((word, wordIndex) => {\n            const normalized = normalizeWord(word)\n            wordCursor += 1\n            const passed = wordCursor <= currentIndex\n            const active =\n              wordCursor === currentIndex ||\n              Boolean(!autoPlay && line.active && normalized.active)\n\n            return (\n              <React.Fragment key={`${lineIndex}-${wordIndex}-${normalized.text}`}>\n                <LyricWord\n                  text={normalized.text}\n                  active={active}\n                  passed={passed}\n                  reduceMotion={reduceMotion}\n                  className={activeWordClassName}\n                />\n                {wordIndex < line.words.length - 1 ? \" \" : null}\n              </React.Fragment>\n            )\n          })}\n        </p>\n      ))}\n    </div>\n  )\n}\n",
      "type": "registry:component"
    },
    {
      "path": "src/components/animate-ui/primitives/texts/shimmering.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, type HTMLMotionProps } from 'motion/react';\n\ntype ShimmeringTextProps = Omit<HTMLMotionProps<'span'>, 'children'> & {\n  text: string;\n  duration?: number;\n  wave?: boolean;\n  color?: string;\n  shimmeringColor?: string;\n};\n\nfunction ShimmeringText({\n  text,\n  duration = 1,\n  transition,\n  wave = false,\n  color = 'var(--color-neutral-500)',\n  shimmeringColor = 'var(--color-neutral-300)',\n  ...props\n}: ShimmeringTextProps) {\n  return (\n    <motion.span\n      style={\n        {\n          '--shimmering-color': shimmeringColor,\n          '--color': color,\n          color: 'var(--color)',\n          position: 'relative',\n          display: 'inline-block',\n          perspective: '500px',\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {text?.split('')?.map((char, i) => (\n        <motion.span\n          key={i}\n          style={{\n            display: 'inline-block',\n            whiteSpace: 'pre',\n            transformStyle: 'preserve-3d',\n          }}\n          initial={{\n            ...(wave\n              ? {\n                  scale: 1,\n                  rotateY: 0,\n                }\n              : {}),\n            color: 'var(--color)',\n          }}\n          animate={{\n            ...(wave\n              ? {\n                  x: [0, 5, 0],\n                  y: [0, -5, 0],\n                  scale: [1, 1.1, 1],\n                  rotateY: [0, 15, 0],\n                }\n              : {}),\n            color: ['var(--color)', 'var(--shimmering-color)', 'var(--color)'],\n          }}\n          transition={{\n            duration,\n            repeat: Infinity,\n            repeatType: 'loop',\n            repeatDelay: text.length * 0.05,\n            delay: (i * duration) / text.length,\n            ease: 'easeInOut',\n            ...transition,\n          }}\n        >\n          {char}\n        </motion.span>\n      ))}\n    </motion.span>\n  );\n}\n\nexport { ShimmeringText, type ShimmeringTextProps };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}