{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scroll-based-velocity",
  "type": "registry:ui",
  "title": "Scroll Based Velocity",
  "description": "Scrolling text whose speed changes based on scroll speed",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/magicui/scroll-based-velocity.tsx",
      "content": "\"use client\"\n\nimport React, { useContext, useEffect, useRef, useState } from \"react\"\nimport {\n  motion,\n  useAnimationFrame,\n  useMotionValue,\n  useScroll,\n  useSpring,\n  useTransform,\n  useVelocity,\n} from \"motion/react\"\nimport type { MotionValue } from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface ScrollVelocityRowProps extends React.HTMLAttributes<HTMLDivElement> {\n  children: React.ReactNode\n  baseVelocity?: number\n  direction?: 1 | -1\n  scrollReactivity?: boolean\n}\n\nexport const wrap = (min: number, max: number, v: number) => {\n  const rangeSize = max - min\n  return ((((v - min) % rangeSize) + rangeSize) % rangeSize) + min\n}\n\nconst ScrollVelocityContext = React.createContext<MotionValue<number> | null>(\n  null\n)\n\nexport function ScrollVelocityContainer({\n  children,\n  className,\n  ...props\n}: React.HTMLAttributes<HTMLDivElement>) {\n  const { scrollY } = useScroll()\n  const scrollVelocity = useVelocity(scrollY)\n  const smoothVelocity = useSpring(scrollVelocity, {\n    damping: 50,\n    stiffness: 400,\n  })\n  const velocityFactor = useTransform(smoothVelocity, (v) => {\n    const sign = v < 0 ? -1 : 1\n    const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n    return sign * magnitude\n  })\n\n  return (\n    <ScrollVelocityContext.Provider value={velocityFactor}>\n      <div className={cn(\"relative w-full\", className)} {...props}>\n        {children}\n      </div>\n    </ScrollVelocityContext.Provider>\n  )\n}\n\nexport function ScrollVelocityRow(props: ScrollVelocityRowProps) {\n  const sharedVelocityFactor = useContext(ScrollVelocityContext)\n  if (sharedVelocityFactor) {\n    return (\n      <ScrollVelocityRowImpl {...props} velocityFactor={sharedVelocityFactor} />\n    )\n  }\n  return <ScrollVelocityRowLocal {...props} />\n}\n\ninterface ScrollVelocityRowImplProps extends ScrollVelocityRowProps {\n  velocityFactor: MotionValue<number>\n}\n\nfunction ScrollVelocityRowImpl({\n  children,\n  baseVelocity = 5,\n  direction = 1,\n  className,\n  velocityFactor,\n  scrollReactivity = true,\n  ...props\n}: ScrollVelocityRowImplProps) {\n  const containerRef = useRef<HTMLDivElement>(null)\n  const blockRef = useRef<HTMLDivElement>(null)\n  const [numCopies, setNumCopies] = useState(1)\n\n  const baseX = useMotionValue(0)\n  const baseDirectionRef = useRef<number>(direction >= 0 ? 1 : -1)\n  const currentDirectionRef = useRef<number>(direction >= 0 ? 1 : -1)\n  const unitWidth = useMotionValue(0)\n\n  const isInViewRef = useRef(true)\n  const isPageVisibleRef = useRef(true)\n  const prefersReducedMotionRef = useRef(false)\n\n  useEffect(() => {\n    const container = containerRef.current\n    const block = blockRef.current\n    let ro: ResizeObserver | null = null\n    let io: IntersectionObserver | null = null\n    let mq: MediaQueryList | null = null\n    const handleVisibility = () => {\n      isPageVisibleRef.current = document.visibilityState === \"visible\"\n    }\n    const handlePRM = () => {\n      if (mq) {\n        prefersReducedMotionRef.current = mq.matches\n      }\n    }\n\n    if (container && block) {\n      const updateSizes = () => {\n        const cw = container.offsetWidth || 0\n        const bw = block.scrollWidth || 0\n        unitWidth.set(bw)\n        const nextCopies = bw > 0 ? Math.max(3, Math.ceil(cw / bw) + 2) : 1\n        setNumCopies((prev) => (prev === nextCopies ? prev : nextCopies))\n      }\n\n      updateSizes()\n\n      ro = new ResizeObserver(updateSizes)\n      ro.observe(container)\n      ro.observe(block)\n\n      io = new IntersectionObserver(([entry]) => {\n        isInViewRef.current = entry.isIntersecting\n      })\n      io.observe(container)\n\n      document.addEventListener(\"visibilitychange\", handleVisibility, {\n        passive: true,\n      })\n      handleVisibility()\n\n      mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n      mq.addEventListener(\"change\", handlePRM)\n      handlePRM()\n    }\n\n    return () => {\n      if (ro) {\n        ro.disconnect()\n      }\n      if (io) {\n        io.disconnect()\n      }\n      document.removeEventListener(\"visibilitychange\", handleVisibility)\n      if (mq) {\n        mq.removeEventListener(\"change\", handlePRM)\n      }\n    }\n  }, [children, unitWidth])\n\n  const x = useTransform([baseX, unitWidth], ([v, bw]) => {\n    const width = Number(bw) || 1\n    const offset = Number(v) || 0\n    return `${-wrap(0, width, offset)}px`\n  })\n\n  useAnimationFrame((_, delta) => {\n    if (!isInViewRef.current || !isPageVisibleRef.current) return\n    const dt = delta / 1000\n    const vf = scrollReactivity ? velocityFactor.get() : 0\n    const absVf = Math.min(5, Math.abs(vf))\n    const speedMultiplier = prefersReducedMotionRef.current ? 1 : 1 + absVf\n\n    if (absVf > 0.1) {\n      const scrollDirection = vf >= 0 ? 1 : -1\n      currentDirectionRef.current = baseDirectionRef.current * scrollDirection\n    }\n\n    const bw = unitWidth.get() || 0\n    if (bw <= 0) return\n    const pixelsPerSecond = (bw * baseVelocity) / 100\n    const moveBy =\n      currentDirectionRef.current * pixelsPerSecond * speedMultiplier * dt\n    baseX.set(baseX.get() + moveBy)\n  })\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(\"w-full overflow-hidden whitespace-nowrap\", className)}\n      {...props}\n    >\n      <motion.div\n        className=\"inline-flex transform-gpu items-center will-change-transform select-none\"\n        style={{ x }}\n      >\n        {Array.from({ length: numCopies }).map((_, i) => (\n          <div\n            key={i}\n            ref={i === 0 ? blockRef : null}\n            aria-hidden={i !== 0}\n            className=\"inline-flex shrink-0 items-center\"\n          >\n            {children}\n          </div>\n        ))}\n      </motion.div>\n    </div>\n  )\n}\n\nfunction ScrollVelocityRowLocal(props: ScrollVelocityRowProps) {\n  const { scrollY } = useScroll()\n  const localVelocity = useVelocity(scrollY)\n  const localSmoothVelocity = useSpring(localVelocity, {\n    damping: 50,\n    stiffness: 400,\n  })\n  const localVelocityFactor = useTransform(localSmoothVelocity, (v) => {\n    const sign = v < 0 ? -1 : 1\n    const magnitude = Math.min(5, (Math.abs(v) / 1000) * 5)\n    return sign * magnitude\n  })\n  return (\n    <ScrollVelocityRowImpl {...props} velocityFactor={localVelocityFactor} />\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}