{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "slide-text",
  "type": "registry:ui",
  "title": "Slide Text",
  "description": "sliding text animation with smooth staggered motion and loop control.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/slide-text.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport { motion, type Variants, useReducedMotion } from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ninterface SlideTextProps {\n  text: string;\n  staggerDelay?: number;\n  staggerDuration?: number;\n  pauseDuration?: number;\n  className?: string;\n}\n\nconst SlideText = React.forwardRef<HTMLSpanElement, SlideTextProps>(\n  (\n    {\n      text,\n      staggerDelay = 0.025,\n      staggerDuration = 0.4,\n      pauseDuration = 1.5,\n      className,\n    },\n    ref,\n  ) => {\n    const [phase, setPhase] = React.useState<'initial' | 'down' | 'up'>(\n      'initial',\n    );\n    const chars = React.useMemo(() => Array.from(text), [text]);\n    const prefersReducedMotion = useReducedMotion();\n\n    const totalAnimationTime = React.useMemo(\n      () => (chars.length * staggerDelay + staggerDuration) * 1000,\n      [chars.length, staggerDelay, staggerDuration],\n    );\n\n    React.useEffect(() => {\n      if (prefersReducedMotion) return;\n\n      const down = setTimeout(() => {\n        setPhase('down');\n      }, pauseDuration * 1000);\n\n      return () => clearTimeout(down);\n    }, [pauseDuration, prefersReducedMotion]);\n\n    React.useEffect(() => {\n      if (prefersReducedMotion || phase !== 'down') return;\n\n      const up = setTimeout(\n        () => {\n          setPhase('up');\n        },\n        totalAnimationTime + pauseDuration * 1000,\n      );\n\n      return () => clearTimeout(up);\n    }, [phase, totalAnimationTime, pauseDuration, prefersReducedMotion]);\n\n    React.useEffect(() => {\n      if (prefersReducedMotion || phase !== 'up') return;\n\n      const reset = setTimeout(() => {\n        setPhase('initial');\n      }, totalAnimationTime);\n\n      return () => clearTimeout(reset);\n    }, [phase, totalAnimationTime, prefersReducedMotion]);\n\n    React.useEffect(() => {\n      if (prefersReducedMotion || phase !== 'initial') return;\n\n      const restart = setTimeout(() => {\n        setPhase('down');\n      }, pauseDuration * 1000);\n\n      return () => clearTimeout(restart);\n    }, [phase, pauseDuration, prefersReducedMotion]);\n\n    const containerVariants: Variants = {\n      initial: {},\n      down: {\n        transition: {\n          staggerChildren: prefersReducedMotion ? 0 : staggerDelay,\n        },\n      },\n      up: {\n        transition: {\n          staggerChildren: prefersReducedMotion ? 0 : staggerDelay,\n        },\n      },\n    };\n\n    const stackVariants: Variants = {\n      initial: { y: '0%' },\n      down: ({ index }: { index: number }) =>\n        prefersReducedMotion\n          ? { y: '0%' }\n          : {\n              y: '-100%',\n              transition: {\n                duration: staggerDuration,\n                delay: index * staggerDelay,\n                ease: 'easeOut',\n              },\n            },\n      up: ({ index, total }: { index: number; total: number }) =>\n        prefersReducedMotion\n          ? { y: '0%' }\n          : {\n              y: '0%',\n              transition: {\n                duration: staggerDuration,\n                delay: (total - 1 - index) * staggerDelay,\n                ease: 'easeOut',\n              },\n            },\n    };\n\n    return (\n      <span\n        ref={ref}\n        className={cn(\n          'relative inline-block leading-none select-none overflow-hidden',\n          className,\n        )}\n      >\n        <motion.span\n          className='relative h-fit leading-none inline-flex transform-gpu will-change-transform'\n          variants={containerVariants}\n          initial='initial'\n          animate={phase}\n          style={{ perspective: 1000 }}\n        >\n          {chars.map((char, index) => {\n            const isSpace = char === ' ';\n\n            return (\n              <span\n                key={index}\n                className='inline-block h-[1.2em] align-baseline overflow-hidden relative'\n                style={{ lineHeight: 1.2 }}\n              >\n                <motion.span\n                  className='block relative'\n                  variants={stackVariants}\n                  custom={{ index, total: chars.length }}\n                  style={{\n                    backfaceVisibility: 'hidden',\n                    transform: 'translateZ(0)',\n                    lineHeight: 1.2,\n                  }}\n                >\n                  <span className='flex h-[1.2em] leading-none relative items-center'>\n                    {isSpace ? '\\u00A0' : char}\n                  </span>\n                  <span className='flex h-[1.2em] leading-none absolute top-full left-0 items-center'>\n                    {isSpace ? '\\u00A0' : char}\n                  </span>\n                </motion.span>\n              </span>\n            );\n          })}\n        </motion.span>\n      </span>\n    );\n  },\n);\n\nSlideText.displayName = 'SlideText';\n\nexport { SlideText };\nexport type { SlideTextProps };\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}"
    }
  ]
}
