{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "marquee",
  "files": [
    {
      "path": "components/ui/marquee.tsx",
      "content": "\"use client\"\n\nimport type React from \"react\"\nimport { useRef, type RefObject } from \"react\"\nimport {\n  motion,\n  useAnimationFrame,\n  useMotionValue,\n  useScroll,\n  useSpring,\n  useTransform,\n  useVelocity,\n  type SpringOptions,\n} from \"motion/react\"\n\nimport { cn } from \"@/lib/utils\"\n\n// Custom wrap function for seamless looping\nconst wrap = (min: number, max: number, value: number): number => {\n  const range = max - min\n  return ((((value - min) % range) + range) % range) + min\n}\n\nexport interface MarqueeProps {\n  children: React.ReactNode\n  className?: string\n  /**\n   * Direction of the marquee animation\n   * @default \"right\"\n   */\n  direction?: \"left\" | \"right\" | \"up\" | \"down\"\n  /**\n   * Speed preset for the marquee animation\n   * @default \"default\"\n   */\n  speedPreset?: \"slow\" | \"default\" | \"fast\"\n  /**\n   * Speed of the marquee animation (lower is slower, higher is faster)\n   * @default 10\n   */\n  speed?: number\n  /**\n   * Whether to pause animation on hover\n   * @default false\n   */\n  pauseOnHover?: boolean\n  /**\n   * Whether to slow down the animation on hover\n   * @default false\n   */\n  slowdownOnHover?: boolean\n  /**\n   * The factor to slow down the animation on hover\n   * @default 0.3\n   */\n  slowDownFactor?: number\n  /**\n   * The spring config for the slow down animation\n   */\n  slowDownSpringConfig?: SpringOptions\n  /**\n   * Whether to show fade effect on edges\n   * @default false\n   */\n  showFade?: boolean\n  /**\n   * Fade intensity (0-100)\n   * @default 12.5\n   */\n  fadeIntensity?: number\n  /**\n   * Number of times to repeat the children\n   * @default 3\n   */\n  repeat?: number\n  /**\n   * Whether to use the scroll velocity to control the marquee speed\n   * @default false\n   */\n  useScrollVelocity?: boolean\n  /**\n   * Whether to adjust the direction based on the scroll direction\n   * @default false\n   */\n  scrollAwareDirection?: boolean\n  /**\n   * The spring config for the scroll velocity-based direction adjustment\n   */\n  scrollSpringConfig?: SpringOptions\n  /**\n   * The container to use for the scroll velocity\n   */\n  scrollContainer?: RefObject<HTMLElement | null> | HTMLElement | null\n  /**\n   * Whether to allow dragging of the marquee\n   * @default false\n   */\n  draggable?: boolean\n  /**\n   * The sensitivity of the drag movement\n   * @default 0.2\n   */\n  dragSensitivity?: number\n  /**\n   * The decay of the drag velocity\n   * @default 0.96\n   */\n  dragVelocityDecay?: number\n  /**\n   * Whether to adjust the direction based on the drag velocity\n   * @default false\n   */\n  dragAwareDirection?: boolean\n  /**\n   * The angle of the drag movement in degrees\n   * @default 0\n   */\n  dragAngle?: number\n  /**\n   * Whether to change the cursor to grabbing when dragging\n   * @default true\n   */\n  grabCursor?: boolean\n  /**\n   * Custom easing function for the animation\n   */\n  easing?: (value: number) => number\n}\n\nexport function Marquee({\n  children,\n  className,\n  direction = \"right\",\n  speedPreset = \"default\",\n  speed,\n  pauseOnHover = false,\n  slowdownOnHover = false,\n  slowDownFactor = 0.3,\n  slowDownSpringConfig = { damping: 50, stiffness: 400 },\n  showFade = false,\n  fadeIntensity = 12.5,\n  repeat = 3,\n  useScrollVelocity = false,\n  scrollAwareDirection = false,\n  scrollSpringConfig = { damping: 50, stiffness: 400 },\n  scrollContainer,\n  draggable = false,\n  dragSensitivity = 0.2,\n  dragVelocityDecay = 0.96,\n  dragAwareDirection = false,\n  dragAngle = 0,\n  grabCursor = true,\n  easing,\n}: MarqueeProps) {\n  const speedMap = {\n    slow: 5,\n    default: 10,\n    fast: 20,\n  }\n\n  const actualSpeed = speed ?? speedMap[speedPreset]\n\n  const baseX = useMotionValue(0)\n  const baseY = useMotionValue(0)\n\n  const { scrollY } = useScroll({\n    ...(scrollContainer && {\n      container: scrollContainer as RefObject<HTMLDivElement>,\n    }),\n  })\n\n  const scrollVelocity = useVelocity(scrollY)\n  const smoothVelocity = useSpring(scrollVelocity, scrollSpringConfig)\n\n  const hoverFactorValue = useMotionValue(1)\n  const defaultVelocity = useMotionValue(1)\n\n  const isDragging = useRef(false)\n  const dragVelocity = useRef(0)\n  const isPausedRef = useRef(false)\n\n  const smoothHoverFactor = useSpring(hoverFactorValue, slowDownSpringConfig)\n\n  const velocityFactor = useTransform(\n    useScrollVelocity ? smoothVelocity : defaultVelocity,\n    [0, 1000],\n    [0, 5],\n    {\n      clamp: false,\n    }\n  )\n\n  const isHorizontal = direction === \"left\" || direction === \"right\"\n\n  const actualBaseVelocity =\n    direction === \"left\" || direction === \"up\" ? -actualSpeed : actualSpeed\n\n  const isHovered = useRef(false)\n  const directionFactor = useRef(1)\n\n  const x = useTransform(baseX, (v) => {\n    const wrappedValue = wrap(0, -100, v)\n    return `${easing ? easing(wrappedValue / -100) * -100 : wrappedValue}%`\n  })\n  const y = useTransform(baseY, (v) => {\n    const wrappedValue = wrap(0, -100, v)\n    return `${easing ? easing(wrappedValue / -100) * -100 : wrappedValue}%`\n  })\n\n  useAnimationFrame((t, delta) => {\n    if (isPausedRef.current) {\n      return\n    }\n\n    if (isDragging.current && draggable) {\n      if (isHorizontal) {\n        baseX.set(baseX.get() + dragVelocity.current)\n      } else {\n        baseY.set(baseY.get() + dragVelocity.current)\n      }\n\n      dragVelocity.current *= 0.9\n\n      if (Math.abs(dragVelocity.current) < 0.01) {\n        dragVelocity.current = 0\n      }\n\n      return\n    }\n\n    if (isHovered.current) {\n      hoverFactorValue.set(slowdownOnHover ? slowDownFactor : 1)\n    } else {\n      hoverFactorValue.set(1)\n    }\n\n    let moveBy =\n      directionFactor.current *\n      actualBaseVelocity *\n      (delta / 1000) *\n      smoothHoverFactor.get()\n\n    if (scrollAwareDirection && !isDragging.current) {\n      if (velocityFactor.get() < 0) {\n        directionFactor.current = -1\n      } else if (velocityFactor.get() > 0) {\n        directionFactor.current = 1\n      }\n    }\n\n    moveBy += directionFactor.current * moveBy * velocityFactor.get()\n\n    if (draggable) {\n      moveBy += dragVelocity.current\n\n      if (dragAwareDirection && Math.abs(dragVelocity.current) > 0.1) {\n        directionFactor.current = Math.sign(dragVelocity.current)\n      }\n\n      if (!isDragging.current && Math.abs(dragVelocity.current) > 0.01) {\n        dragVelocity.current *= dragVelocityDecay\n      } else if (!isDragging.current) {\n        dragVelocity.current = 0\n      }\n    }\n\n    if (isHorizontal) {\n      baseX.set(baseX.get() + moveBy)\n    } else {\n      baseY.set(baseY.get() + moveBy)\n    }\n  })\n\n  const lastPointerPosition = useRef({ x: 0, y: 0 })\n\n  const handlePointerDown = (e: React.PointerEvent) => {\n    if (!draggable) return\n    ;(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId)\n\n    if (grabCursor) {\n      ;(e.currentTarget as HTMLElement).style.cursor = \"grabbing\"\n    }\n\n    isDragging.current = true\n    lastPointerPosition.current = { x: e.clientX, y: e.clientY }\n    dragVelocity.current = 0\n  }\n\n  const handlePointerMove = (e: React.PointerEvent) => {\n    if (!draggable || !isDragging.current) return\n\n    const currentPosition = { x: e.clientX, y: e.clientY }\n    const deltaX = currentPosition.x - lastPointerPosition.current.x\n    const deltaY = currentPosition.y - lastPointerPosition.current.y\n\n    const angleInRadians = (dragAngle * Math.PI) / 180\n    const directionX = Math.cos(angleInRadians)\n    const directionY = Math.sin(angleInRadians)\n\n    const projectedDelta = deltaX * directionX + deltaY * directionY\n    dragVelocity.current = projectedDelta * dragSensitivity\n\n    lastPointerPosition.current = currentPosition\n  }\n\n  const handlePointerUp = (e: React.PointerEvent) => {\n    if (!draggable) return\n    ;(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId)\n\n    if (grabCursor) {\n      ;(e.currentTarget as HTMLElement).style.cursor = \"grab\"\n    }\n\n    isDragging.current = false\n  }\n\n  return (\n    <div\n      className={cn(\"relative w-full overflow-hidden\", className)}\n      style={{ transform: \"translateZ(0)\" }}\n    >\n      {showFade && isHorizontal && (\n        <>\n          <div\n            className=\"from-background pointer-events-none absolute top-0 left-0 z-10 h-full bg-gradient-to-r to-transparent select-none\"\n            style={{\n              width: `${fadeIntensity}%`,\n              transform: \"translateZ(0)\",\n            }}\n          />\n          <div\n            className=\"from-background pointer-events-none absolute top-0 right-0 z-10 h-full bg-gradient-to-l to-transparent select-none\"\n            style={{\n              width: `${fadeIntensity}%`,\n              transform: \"translateZ(0)\",\n            }}\n          />\n        </>\n      )}\n      {showFade && !isHorizontal && (\n        <>\n          <div\n            className=\"from-background pointer-events-none absolute top-0 left-0 z-10 w-full bg-gradient-to-b to-transparent select-none\"\n            style={{\n              height: `${fadeIntensity}%`,\n              transform: \"translateZ(0)\",\n            }}\n          />\n          <div\n            className=\"from-background pointer-events-none absolute bottom-0 left-0 z-10 w-full bg-gradient-to-t to-transparent select-none\"\n            style={{\n              height: `${fadeIntensity}%`,\n              transform: \"translateZ(0)\",\n            }}\n          />\n        </>\n      )}\n      <motion.div\n        className={cn(\"flex\", isHorizontal ? \"flex-row\" : \"flex-col\")}\n        onHoverStart={() => {\n          isHovered.current = true\n          if (pauseOnHover) {\n            isPausedRef.current = true\n          }\n        }}\n        onHoverEnd={() => {\n          isHovered.current = false\n          if (pauseOnHover) {\n            isPausedRef.current = false\n          }\n        }}\n        onPointerDown={handlePointerDown}\n        onPointerMove={handlePointerMove}\n        onPointerUp={handlePointerUp}\n        onPointerCancel={handlePointerUp}\n      >\n        {Array.from({ length: repeat }, (_, i) => i).map((i) => (\n          <motion.div\n            key={i}\n            className={cn(\n              \"shrink-0\",\n              isHorizontal && \"flex\",\n              draggable && grabCursor && \"cursor-grab\"\n            )}\n            style={isHorizontal ? { x } : { y }}\n            aria-hidden={i > 0}\n          >\n            {children}\n          </motion.div>\n        ))}\n      </motion.div>\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "hide": true
  },
  "type": "registry:ui"
}