{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glowy-button",
  "title": "Glowy Button",
  "author": "mrap10",
  "description": "Button with dynamic glow that follows the cursor on hover.",
  "dependencies": ["motion"],
  "files": [
    {
      "path": "registry/default/button/glowy-button.tsx",
      "content": "\"use client\";\n\nimport { motion, useMotionValue, useSpring, useTransform } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\nimport { ComponentProps, useRef } from \"react\";\n\ntype GlowVariant = \"primary\" | \"ghost\";\ntype GlowSize = \"sm\" | \"md\" | \"lg\";\n\nexport interface GlowyButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  variant?: GlowVariant;\n  size?: GlowSize;\n  colorFrom?: string;\n  colorTo?: string;\n  glowStrength?: number;\n  ref?: React.Ref<HTMLButtonElement>;\n}\n\nconst sizeClasses: Record<GlowSize, string> = {\n  sm: \"h-8 px-4 text-xs gap-1.5\",\n  md: \"h-10 px-[22px] text-sm gap-2\",\n  lg: \"h-12 px-7 text-[15px] gap-2\",\n};\n\nconst SPRING = { stiffness: 200, damping: 28, mass: 0.6 };\n\nconst colorMix = (color: string, pct: number) =>\n  `color-mix(in srgb, ${color} ${pct}%, transparent)`;\n\nexport function GlowyButton({\n  ref,\n  variant = \"primary\",\n  size = \"md\",\n  colorFrom = \"rgba(79,70,229,1)\",\n  colorTo = \"rgba(220,80,60,1)\",\n  glowStrength = 1,\n  className,\n  children,\n  onMouseMove,\n  onMouseEnter,\n  onMouseLeave,\n  ...props\n}: GlowyButtonProps) {\n  const internalRef = useRef<HTMLButtonElement>(null);\n  const mergedRef = (node: HTMLButtonElement | null) => {\n    internalRef.current = node;\n    if (typeof ref === \"function\") {\n      ref(node);\n    } else if (ref) {\n      ref.current = node;\n    }\n  };\n\n  const mouseX = useMotionValue(0.5);\n  const mouseY = useMotionValue(0.5);\n  const smoothX = useSpring(mouseX, SPRING);\n  const smoothY = useSpring(mouseY, SPRING);\n\n  const hovered = useMotionValue(0);\n  const hoverOpacity = useSpring(hovered, { stiffness: 200, damping: 24 });\n\n  const ambientOpacity = useTransform(\n    hoverOpacity,\n    [0, 1],\n    [variant === \"primary\" ? 0.45 : 0, 1]\n  );\n\n  const spotOpacity = useTransform(\n    hoverOpacity,\n    [0, 1],\n    [0, glowStrength * (variant === \"primary\" ? 1 : 0.85)]\n  );\n\n  const spotLeft = useTransform(smoothX, (v) => `${v * 100}%`);\n  const spotTop = useTransform(smoothY, (v) => `${v * 100}%`);\n\n  const ambientGradient = [\n    `radial-gradient(ellipse 130% 130% at 15% 65%, ${colorMix(colorFrom, 55)} 0%, transparent 55%)`,\n    `radial-gradient(ellipse 100% 100% at 88% 35%, ${colorMix(colorTo, 45)} 0%, transparent 55%)`,\n  ].join(\",\");\n\n  const spotGradient =\n    variant === \"primary\"\n      ? \"radial-gradient(circle 90px at 50% 50%, rgba(255,255,255,0.22) 0%, transparent 65%)\"\n      : `radial-gradient(circle 100px at 50% 50%, ${colorMix(colorFrom, 20)}, ${colorMix(colorTo, 14)} 45%, transparent 70%)`;\n\n  function handleMouseMove(e: React.MouseEvent<HTMLButtonElement>) {\n    if (internalRef.current) {\n      const { left, top, width, height } =\n        internalRef.current.getBoundingClientRect();\n      mouseX.set((e.clientX - left) / width);\n      mouseY.set((e.clientY - top) / height);\n    }\n    onMouseMove?.(e);\n  }\n\n  function handleMouseEnter(e: React.MouseEvent<HTMLButtonElement>) {\n    hovered.set(1);\n    onMouseEnter?.(e);\n  }\n\n  function handleMouseLeave(e: React.MouseEvent<HTMLButtonElement>) {\n    hovered.set(0);\n    mouseX.set(0.5);\n    mouseY.set(0.5);\n    onMouseLeave?.(e);\n  }\n\n  return (\n    <motion.button\n      ref={mergedRef}\n      whileTap={{ scale: 0.97 }}\n      transition={{ type: \"spring\", stiffness: 400, damping: 25 }}\n      onMouseMove={handleMouseMove}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      className={cn(\n        \"relative isolate inline-flex items-center justify-center rounded-full\",\n        \"cursor-pointer overflow-hidden select-none\",\n        \"leading-none font-medium tracking-[0.01em]\",\n        \"focus-visible:ring-2 focus-visible:ring-white/50 focus-visible:outline-none\",\n        \"disabled:pointer-events-none disabled:opacity-40\",\n        variant === \"primary\" && \"bg-[#0f0f10] text-[#f0eeea]\",\n        variant === \"ghost\" &&\n          \"text-foreground border border-neutral-200 bg-transparent dark:border-neutral-800\",\n        sizeClasses[size],\n        className\n      )}\n      style={{\n        boxShadow:\n          variant === \"primary\"\n            ? \"inset 0 0.6px 2px rgba(255,255,255,0.08), inset 0 -1px 0 rgba(0,0,0,0.4), 0 0 0 0.5px rgba(255,255,255,0.08)\"\n            : undefined,\n      }}\n      {...(props as ComponentProps<typeof motion.button>)}\n    >\n      {/* Layer 1: Ambient base gradient */}\n      <motion.span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute inset-0 rounded-[inherit]\"\n        style={{\n          background: ambientGradient,\n          opacity: ambientOpacity,\n        }}\n      />\n\n      {/* Layer 2: Travelling Spotlight effect */}\n      <motion.span\n        aria-hidden=\"true\"\n        className=\"pointer-events-none absolute size-40 -translate-x-1/2 -translate-y-1/2 rounded-full\"\n        style={{\n          left: spotLeft,\n          top: spotTop,\n          background: spotGradient,\n          opacity: spotOpacity,\n        }}\n      />\n\n      {/* Layer 3: Button content */}\n      <span className=\"relative z-2 inline-flex items-center gap-[inherit]\">\n        {children}\n      </span>\n    </motion.button>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": ["buttons"],
  "type": "registry:component"
}
