{
	"$schema": "https://ui.shadcn.com/schema/registry-item.json",
	"name": "ScrollVelocity-TS-CSS",
	"title": "ScrollVelocity",
	"description": "Text marquee animatio - speed and distortion scale with user's scroll velocity.",
	"type": "registry:component",
	"files": [
		{
			"type": "registry:file",
			"path": "ScrollVelocity.css",
			"target": "@components/ScrollVelocity.css",
			"content": ".parallax {\n  position: relative;\n  overflow: hidden;\n}\n\n.scroller {\n  display: flex;\n  white-space: nowrap;\n  text-align: center;\n  font-family: sans-serif;\n  font-size: 2.25rem;\n  font-weight: bold;\n  letter-spacing: -0.02em;\n  filter: drop-shadow(0 1px 1px rgba(0, 0, 0, 0.1));\n}\n\n.scroller span {\n  flex-shrink: 0;\n}\n\n@media (min-width: 768px) {\n  .scroller {\n    font-size: 5rem;\n    line-height: 5rem;\n  }\n}\n"
		},
		{
			"type": "registry:component",
			"path": "ScrollVelocity.tsx",
			"content": "import React, { useRef, useLayoutEffect, useState } from 'react';\nimport {\n  motion,\n  useScroll,\n  useSpring,\n  useTransform,\n  useMotionValue,\n  useVelocity,\n  useAnimationFrame\n} from 'motion/react';\nimport './ScrollVelocity.css';\n\ninterface VelocityMapping {\n  input: [number, number];\n  output: [number, number];\n}\n\ninterface VelocityTextProps {\n  children: React.ReactNode;\n  baseVelocity: number;\n  scrollContainerRef?: React.RefObject<HTMLElement>;\n  className?: string;\n  damping?: number;\n  stiffness?: number;\n  numCopies?: number;\n  velocityMapping?: VelocityMapping;\n  parallaxClassName?: string;\n  scrollerClassName?: string;\n  parallaxStyle?: React.CSSProperties;\n  scrollerStyle?: React.CSSProperties;\n}\n\ninterface ScrollVelocityProps {\n  scrollContainerRef?: React.RefObject<HTMLElement>;\n  texts: React.ReactNode[];\n  velocity?: number;\n  className?: string;\n  damping?: number;\n  stiffness?: number;\n  numCopies?: number;\n  velocityMapping?: VelocityMapping;\n  parallaxClassName?: string;\n  scrollerClassName?: string;\n  parallaxStyle?: React.CSSProperties;\n  scrollerStyle?: React.CSSProperties;\n}\n\nfunction useElementWidth<T extends HTMLElement>(ref: React.RefObject<T | null>): number {\n  const [width, setWidth] = useState(0);\n\n  useLayoutEffect(() => {\n    function updateWidth() {\n      if (ref.current) {\n        setWidth(ref.current.offsetWidth);\n      }\n    }\n    updateWidth();\n    window.addEventListener('resize', updateWidth);\n    return () => window.removeEventListener('resize', updateWidth);\n  }, [ref]);\n\n  return width;\n}\n\nexport const ScrollVelocity: React.FC<ScrollVelocityProps> = ({\n  scrollContainerRef,\n  texts = [],\n  velocity = 100,\n  className = '',\n  damping = 50,\n  stiffness = 400,\n  numCopies = 6,\n  velocityMapping = { input: [0, 1000], output: [0, 5] },\n  parallaxClassName = 'parallax',\n  scrollerClassName = 'scroller',\n  parallaxStyle,\n  scrollerStyle\n}) => {\n  function VelocityText({\n    children,\n    baseVelocity = velocity,\n    scrollContainerRef,\n    className = '',\n    damping,\n    stiffness,\n    numCopies,\n    velocityMapping,\n    parallaxClassName,\n    scrollerClassName,\n    parallaxStyle,\n    scrollerStyle\n  }: VelocityTextProps) {\n    const baseX = useMotionValue(0);\n    const scrollOptions = scrollContainerRef ? { container: scrollContainerRef } : {};\n    const { scrollY } = useScroll(scrollOptions);\n    const scrollVelocity = useVelocity(scrollY);\n    const smoothVelocity = useSpring(scrollVelocity, {\n      damping: damping ?? 50,\n      stiffness: stiffness ?? 400\n    });\n    const velocityFactor = useTransform(\n      smoothVelocity,\n      velocityMapping?.input || [0, 1000],\n      velocityMapping?.output || [0, 5],\n      { clamp: false }\n    );\n\n    const copyRef = useRef<HTMLSpanElement>(null);\n    const copyWidth = useElementWidth(copyRef);\n\n    function wrap(min: number, max: number, v: number): number {\n      const range = max - min;\n      const mod = (((v - min) % range) + range) % range;\n      return mod + min;\n    }\n\n    const x = useTransform(baseX, v => {\n      if (copyWidth === 0) return '0px';\n      return `${wrap(-copyWidth, 0, v)}px`;\n    });\n\n    const directionFactor = useRef<number>(1);\n    useAnimationFrame((t, delta) => {\n      let moveBy = directionFactor.current * baseVelocity * (delta / 1000);\n\n      if (velocityFactor.get() < 0) {\n        directionFactor.current = -1;\n      } else if (velocityFactor.get() > 0) {\n        directionFactor.current = 1;\n      }\n\n      moveBy += directionFactor.current * moveBy * velocityFactor.get();\n      baseX.set(baseX.get() + moveBy);\n    });\n\n    const spans = [];\n    for (let i = 0; i < (numCopies ?? 6); i++) {\n      spans.push(\n        <span className={className} key={i} ref={i === 0 ? copyRef : null}>\n          {children}&nbsp;\n        </span>\n      );\n    }\n\n    return (\n      <div className={parallaxClassName} style={parallaxStyle}>\n        <motion.div className={scrollerClassName} style={{ x, ...scrollerStyle }}>\n          {spans}\n        </motion.div>\n      </div>\n    );\n  }\n\n  return (\n    <section>\n      {texts.map((text, index) => (\n        <VelocityText\n          key={index}\n          className={className}\n          baseVelocity={index % 2 !== 0 ? -velocity : velocity}\n          scrollContainerRef={scrollContainerRef}\n          damping={damping}\n          stiffness={stiffness}\n          numCopies={numCopies}\n          velocityMapping={velocityMapping}\n          parallaxClassName={parallaxClassName}\n          scrollerClassName={scrollerClassName}\n          parallaxStyle={parallaxStyle}\n          scrollerStyle={scrollerStyle}\n        >\n          {text}\n        </VelocityText>\n      ))}\n    </section>\n  );\n};\n\nexport default ScrollVelocity;\n"
		}
	],
	"registryDependencies": [],
	"dependencies": [
		"motion@^12.23.12"
	]
}