{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "moving-gradient-button",
  "title": "Moving Gradient Button",
  "description": "A button that reveals a spinning gradient border and flowing gradient text on hover.",
  "dependencies": [
    "motion",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "path": "registry/wise-ui/components/moving-gradient-button.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { motion, useMotionValue, useTransform, animate } from \"motion/react\"\nimport { cn } from \"@/registry/wise-ui/lib/utils\"\n\ninterface MovingGradientButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  animateBorder?: boolean\n  animateText?: boolean\n  gradientFrom?: string\n  gradientTo?: string\n  startIcon?: React.ReactNode\n  endIcon?: React.ReactNode\n  children: React.ReactNode\n}\n\nconst MovingGradientButton = React.forwardRef<\n  HTMLButtonElement,\n  MovingGradientButtonProps\n>(\n  (\n    {\n      animateBorder = true,\n      animateText = true,\n      gradientFrom = \"#00a896\",\n      gradientTo = \"#c084fc\",\n      startIcon,\n      endIcon,\n      children,\n      disabled,\n      className,\n      ...props\n    },\n    ref\n  ) => {\n    const [isHovered, setIsHovered] = React.useState(false)\n    const rotation = useMotionValue(0)\n    const controlRef = React.useRef<ReturnType<typeof animate> | null>(null)\n\n    React.useEffect(() => {\n      if (isHovered && (animateBorder || animateText) && !disabled) {\n        controlRef.current = animate(\n          rotation,\n          rotation.get() + 360 * 10000,\n          { duration: 3 * 10000, ease: \"linear\" }\n        )\n      } else {\n        controlRef.current?.stop()\n      }\n      return () => {\n        controlRef.current?.stop()\n      }\n    }, [isHovered, animateBorder, animateText, disabled, rotation])\n\n    // Shift –100% per 360° - with backgroundRepeat:\"repeat\" this scrolls\n    // the from→to→from gradient infinitely without any visible seam\n    const backgroundPosition = useTransform(\n      rotation,\n      (v) => `${-(v * 100) / 360}% 0%`\n    )\n\n    const showBorderAnimation = isHovered && animateBorder && !disabled\n    const showTextAnimation = isHovered && animateText && !disabled\n\n    return (\n      <div\n        className={cn(\n          \"group relative inline-flex rounded-md\",\n          disabled && \"pointer-events-none opacity-50\"\n        )}\n        onMouseEnter={() => setIsHovered(true)}\n        onMouseLeave={() => setIsHovered(false)}\n      >\n        {/* Static border - visible at rest */}\n        <div\n          className=\"pointer-events-none absolute inset-0 rounded-md border border-border transition-opacity duration-300\"\n          style={{ opacity: showBorderAnimation ? 0 : 1 }}\n        />\n\n        {/* Spinning conic gradient - visible on hover */}\n        <div\n          className=\"pointer-events-none absolute inset-0 overflow-hidden rounded-md transition-opacity duration-300\"\n          style={{ opacity: showBorderAnimation ? 1 : 0 }}\n        >\n          <motion.div\n            style={{\n              position: \"absolute\",\n              width: \"200%\",\n              aspectRatio: \"1\",\n              left: \"50%\",\n              top: \"50%\",\n              x: \"-50%\",\n              y: \"-50%\",\n              background: `conic-gradient(from 0deg, ${gradientFrom}, ${gradientTo}, ${gradientFrom})`,\n              rotate: rotation,\n            }}\n          />\n        </div>\n\n        {/* Inner button */}\n        <button\n          ref={ref}\n          disabled={disabled}\n          className={cn(\n            \"relative z-10 m-[1.5px] inline-flex cursor-pointer items-center gap-2 bg-background px-4 py-2 text-sm font-medium text-foreground transition-colors\",\n            \"rounded-[calc(var(--radius-md)-1.5px)]\",\n            \"disabled:cursor-not-allowed\",\n            className\n          )}\n          {...props}\n        >\n          {/* Start icon */}\n          {startIcon && (\n            <span\n              className=\"inline-flex shrink-0 transition-colors duration-300\"\n              style={{\n                color: showBorderAnimation || (isHovered && !disabled)\n                  ? gradientFrom\n                  : undefined,\n              }}\n            >\n              {startIcon}\n            </span>\n          )}\n\n          {/* Text */}\n          {showTextAnimation ? (\n            <motion.span\n              style={{\n                backgroundImage: `linear-gradient(90deg, ${gradientFrom}, ${gradientTo}, ${gradientFrom})`,\n                backgroundSize: \"200% 100%\",\n                backgroundRepeat: \"repeat\",\n                backgroundPosition,\n                WebkitBackgroundClip: \"text\",\n                WebkitTextFillColor: \"transparent\",\n                backgroundClip: \"text\",\n              }}\n            >\n              {children}\n            </motion.span>\n          ) : (\n            <span>{children}</span>\n          )}\n\n          {/* End icon */}\n          {endIcon && (\n            <span\n              className=\"inline-flex shrink-0 transition-colors duration-300\"\n              style={{\n                color: showBorderAnimation || (isHovered && !disabled)\n                  ? gradientTo\n                  : undefined,\n              }}\n            >\n              {endIcon}\n            </span>\n          )}\n        </button>\n      </div>\n    )\n  }\n)\nMovingGradientButton.displayName = \"MovingGradientButton\"\n\nexport { MovingGradientButton }\nexport type { MovingGradientButtonProps }\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}