{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "wavy-button",
  "type": "registry:ui",
  "title": " Wavy Button",
  "description": "Interactive wavybutton with animated text and customizable styles for modern uis.",
  "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/wavy-button.tsx",
      "content": "'use client';\nimport * as React from 'react';\nimport { motion } 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 buttonVariants = cva(\n  'relative inline-flex items-center justify-center gap-2 font-semibold transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 overflow-hidden [&_svg]:pointer-events-none',\n  {\n    variants: {\n      variant: {\n        default: 'bg-primary text-white hover:bg-primary/90',\n        destructive: 'bg-red-600 text-white hover:bg-red-500',\n        outline:\n          'border-2 border-gray-500 bg-transparent text-black dark:text-white hover:bg-gray-100 dark:hover:bg-gray-800',\n        secondary: 'bg-gray-500 text-white hover:bg-gray-400',\n        success: 'bg-green-600 text-white hover:bg-green-500',\n        warning: 'bg-yellow-400 text-black hover:bg-yellow-300',\n        info: 'bg-blue-600 text-white hover:bg-blue-500',\n        gradient: 'bg-linear-to-r from-purple-600 to-pink-500 text-white',\n        link: 'text-primary underline-offset-4 hover:underline bg-transparent shadow-none',\n      },\n      size: {\n        default: 'h-9 px-4 py-2 has-[>svg]:gap-2',\n        sm: 'h-8 rounded-md px-3 text-xs has-[>svg]:gap-1.5',\n        lg: 'h-10 rounded-md px-8 has-[>svg]:gap-2.5',\n        xl: 'h-24 px-20 text-2xl has-[>svg]:gap-3',\n        icon: 'h-9 w-9',\n        'icon-sm': 'h-12 w-12',\n        'icon-lg': 'h-20 w-20',\n      },\n      radius: {\n        default: 'rounded-full',\n        sm: 'rounded-lg',\n        lg: 'rounded-4xl',\n        none: 'rounded-none',\n      },\n    },\n    defaultVariants: {\n      variant: 'default',\n      size: 'default',\n      radius: 'default',\n    },\n  },\n);\n\ninterface VariantColorsType {\n  fromBg: string;\n  toBg: string;\n  stroke?: string;\n}\n\nconst variantColors: Record<\n  NonNullable<VariantProps<typeof buttonVariants>['variant']>,\n  VariantColorsType\n> = {\n  default: { fromBg: '#4a6b3f', toBg: '#d5e798', stroke: '#d5e798' },\n  destructive: { fromBg: '#dc2626', toBg: '#fca5a5', stroke: '#fca5a5' },\n  outline: {\n    fromBg: 'transparent',\n    toBg: 'transparent',\n    stroke: 'currentColor',\n  },\n  secondary: { fromBg: '#64748b', toBg: '#cbd5e1', stroke: '#cbd5e1' },\n  success: { fromBg: '#16a34a', toBg: '#86efac', stroke: '#86efac' },\n  warning: { fromBg: '#eab308', toBg: '#fde047', stroke: '#fde047' },\n  info: { fromBg: '#3b82f6', toBg: '#93c5fd', stroke: '#93c5fd' },\n  gradient: { fromBg: '#8b5cf6', toBg: '#ec4899', stroke: '#ec4899' },\n  link: { fromBg: 'transparent', toBg: 'transparent', stroke: 'currentColor' },\n};\n\ninterface WavyTextProps {\n  text: string;\n  isHovered: boolean;\n  className?: string;\n  duration: number;\n  delay: number;\n}\n\nconst WavyText: React.FC<WavyTextProps> = ({\n  text,\n  isHovered,\n  className = '',\n  duration,\n  delay,\n}) => {\n  const chars = text.split('');\n  return (\n    <span className='relative z-20 inline-flex'>\n      {chars.map((char, index) => (\n        <motion.span\n          key={index}\n          className={className}\n          animate={isHovered ? { y: [0, -8, 0] } : { y: 0 }}\n          transition={{\n            duration,\n            delay: index * delay,\n            ease: [0.4, 0, 0.2, 1],\n          }}\n          style={{\n            display: 'inline-block',\n            whiteSpace: char === ' ' ? 'pre' : 'normal',\n          }}\n        >\n          {char === ' ' ? '\\u00A0' : char}\n        </motion.span>\n      ))}\n    </span>\n  );\n};\n\ninterface WavyButtonProps\n  extends\n    React.ButtonHTMLAttributes<HTMLButtonElement>,\n    VariantProps<typeof buttonVariants> {\n  children: React.ReactNode;\n  animationDuration?: number;\n  strokeWidth?: number;\n  splitDelay?: number;\n  asChild?: boolean;\n  disableTextAnimation?: boolean;\n}\n\nconst WavyButton = React.forwardRef<HTMLButtonElement, WavyButtonProps>(\n  (\n    {\n      className,\n      variant = 'default',\n      size,\n      radius,\n      children,\n      animationDuration = 0.8,\n      strokeWidth = 30,\n      splitDelay = 0.04,\n      asChild = false,\n      disableTextAnimation = false,\n      ...props\n    },\n    ref,\n  ) => {\n    const [isHovered, setIsHovered] = React.useState(false);\n    const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);\n    const colors = variantColors[variant ?? 'default'];\n    const Component: React.ElementType = asChild ? Slot : motion.button;\n\n    const handleTouchStart = () => {\n      setIsHovered(true);\n      if (timeoutRef.current) {\n        clearTimeout(timeoutRef.current);\n      }\n      timeoutRef.current = setTimeout(() => {\n        setIsHovered(false);\n      }, 2000);\n    };\n\n    React.useEffect(() => {\n      return () => {\n        if (timeoutRef.current) {\n          clearTimeout(timeoutRef.current);\n        }\n      };\n    }, []);\n\n    return (\n      <Component\n        ref={ref}\n        className={cn(buttonVariants({ variant, size, radius, className }))}\n        onMouseEnter={() => setIsHovered(true)}\n        onMouseLeave={() => setIsHovered(false)}\n        onTouchStart={handleTouchStart}\n        animate={\n          !asChild\n            ? { backgroundColor: isHovered ? colors.toBg : colors.fromBg }\n            : undefined\n        }\n        transition={\n          !asChild\n            ? { duration: animationDuration, ease: [0.4, 0, 0.2, 1] }\n            : undefined\n        }\n        {...props}\n      >\n        <svg\n          viewBox='0 0 260 64'\n          fill='none'\n          xmlns='http://www.w3.org/2000/svg'\n          className='absolute inset-0 w-full h-full z-10 pointer-events-none'\n          preserveAspectRatio='none'\n        >\n          <defs>\n            <clipPath id='clip-wave'>\n              <rect width='260' height='64' fill='white' />\n            </clipPath>\n          </defs>\n          <g clipPath='url(#clip-wave)'>\n            <motion.path\n              d='M-11.7907 25.5948C-1.99079 7.39406 53.3086 -7.30655 91.8081 -10.8067C130.308 -14.3068 164.607 -12.2068 129.608 1.79383C94.6081 15.7944 37.9088 5.29517 -4.79076 43.0967C-47.4903 80.8983 1.50917 68.9978 11.3091 61.2975C21.1089 53.5972 55.4086 37.4965 79.2083 36.0965C103.008 34.6964 153.407 32.5939 174.407 1.79383C195.407 -29.0063 219.207 -29.0063 196.807 13.6955C174.407 56.3973 105.808 57.7985 84.8083 61.2975C63.8085 64.7965 44.9087 67.5966 32.3089 78.0971C19.709 88.5975 127.508 83.6962 157.607 72.4968C187.707 61.2975 218.507 24.8948 227.607 -1.00624C236.707 -26.9073 261.906 -7.3065 252.806 7.39411C243.706 22.0947 217.807 55.6961 207.307 66.8966C196.807 78.0971 219.207 96.9978 236.007 72.4968C252.806 47.9958 280.106 15.7945 285.706 7.39411'\n              stroke={colors.stroke}\n              strokeWidth={strokeWidth}\n              pathLength={1}\n              initial={{ pathLength: 0 }}\n              animate={isHovered ? { pathLength: 1 } : { pathLength: 0 }}\n              transition={{\n                duration: animationDuration,\n                ease: [0.4, 0, 0.2, 1],\n              }}\n            />\n          </g>\n        </svg>\n\n        <div\n          className={cn(\n            'relative z-20 inline-flex items-center',\n            isHovered\n              ? 'text-white dark:text-black'\n              : 'text-black dark:text-white',\n          )}\n        >\n          {typeof children === 'string' && !disableTextAnimation ? (\n            <WavyText\n              text={children}\n              isHovered={isHovered}\n              duration={animationDuration}\n              delay={splitDelay}\n            />\n          ) : (\n            children\n          )}\n        </div>\n      </Component>\n    );\n  },\n);\n\nWavyButton.displayName = 'WavyButton';\n\nexport default WavyButton;\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}"
    }
  ]
}
