{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "shiny-button",
  "type": "registry:ui",
  "title": "Shiny Button",
  "description": "Interactive button with spotlight effect that follows mouse cursor and highlights borders on hover.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": [
    "motion",
    "@radix-ui/react-slot",
    "class-variance-authority",
    "clsx",
    "tailwind-merge"
  ],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/shiny-button.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { motion, useMotionValue } from 'motion/react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'class-variance-authority';\nimport { cn } from '@/lib/utils';\n\nconst shinyButtonStyles = cva(\n  'relative inline-flex items-center justify-center overflow-hidden rounded-md text-sm font-medium transition-colors focus-visible:outline-hidden focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',\n  {\n    variants: {\n      size: {\n        default: 'h-9 px-4 py-2',\n        sm: 'h-8 rounded-md px-3 text-xs',\n        lg: 'h-10 rounded-md px-8',\n        icon: 'h-9 w-9',\n      },\n    },\n    defaultVariants: {\n      size: 'default',\n    },\n  },\n);\n\nexport interface ShinyButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof shinyButtonStyles> {\n  asChild?: boolean;\n  gradientFrom?: string;\n  gradientTo?: string;\n  gradientOpacity?: number;\n  gradientAngle?: number;\n}\n\nexport const ShinyButton = React.forwardRef<\n  HTMLButtonElement,\n  ShinyButtonProps\n>(\n  (\n    {\n      asChild = false,\n      size = 'default',\n      className,\n      children,\n      gradientFrom = '#9E7AFF',\n      gradientTo = '#FE8BBB',\n      gradientOpacity = 0.8,\n      gradientAngle = 0,\n      ...props\n    },\n    ref,\n  ) => {\n    const Comp = asChild ? Slot : 'button';\n    const [isHovered, setIsHovered] = React.useState(false);\n    const [currentAngle, setCurrentAngle] = React.useState(gradientAngle);\n    const [isTouch, setIsTouch] = React.useState(false);\n    const angle = useMotionValue(gradientAngle);\n\n    const reset = React.useCallback(() => {\n      angle.set(gradientAngle);\n      setCurrentAngle(gradientAngle);\n      setIsHovered(false);\n    }, [angle, gradientAngle]);\n\n    const handlePointerMove = React.useCallback(\n      (e: React.PointerEvent<HTMLButtonElement>) => {\n        if (e.pointerType === 'touch') {\n          setIsTouch(true);\n          return;\n        }\n        setIsTouch(false);\n        const rect = e.currentTarget.getBoundingClientRect();\n        const x = e.clientX - rect.left;\n        const y = e.clientY - rect.top;\n        const dx = x - rect.width / 2;\n        const dy = y - rect.height / 2;\n        const radians = Math.atan2(dy, dx);\n        const deg = (radians * 180) / Math.PI;\n        angle.set(deg);\n        setCurrentAngle(deg);\n      },\n      [angle],\n    );\n\n    React.useEffect(() => {\n      reset();\n    }, [reset]);\n\n    const gradientStyle = React.useMemo(\n      () => ({\n        background: `linear-gradient(${currentAngle}deg, ${gradientFrom}, ${gradientTo} 30%, transparent 80%)`,\n        opacity: gradientOpacity,\n      }),\n      [currentAngle, gradientFrom, gradientTo, gradientOpacity],\n    );\n\n    return (\n      <Comp\n        ref={ref}\n        className={cn(shinyButtonStyles({ size }))}\n        onPointerEnter={() => setIsHovered(true)}\n        onPointerMove={handlePointerMove}\n        onPointerLeave={reset}\n        {...props}\n      >\n        {isTouch ? (\n          <div\n            className='absolute inset-0 rounded-[inherit]'\n            style={{\n              background: `linear-gradient(${gradientAngle}deg, ${gradientFrom}, ${gradientTo} 30%, transparent 80%)`,\n              opacity: gradientOpacity,\n            }}\n          />\n        ) : (\n          <motion.div\n            className='pointer-events-none absolute inset-0 rounded-[inherit]'\n            style={gradientStyle}\n            animate={{ opacity: gradientOpacity }}\n            transition={{\n              type: 'spring',\n              stiffness: 150,\n              damping: 20,\n              mass: 0.5,\n            }}\n          />\n        )}\n\n        <div\n          className={cn(\n            'absolute inset-px rounded-[inherit] bg-neutral-100 dark:bg-neutral-900',\n            className,\n          )}\n        />\n        <div\n          className={cn(\n            'absolute inset-px rounded-[inherit] bg-neutral-200/40 dark:bg-neutral-800/60 transition-opacity duration-300',\n            isHovered ? 'opacity-100' : 'opacity-0',\n          )}\n        />\n        <span className='relative z-10'>{children}</span>\n      </Comp>\n    );\n  },\n);\nShinyButton.displayName = 'ShinyButton';\n\nexport { shinyButtonStyles };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
