{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glass",
  "type": "registry:ui",
  "title": "Glass",
  "description": "glass surface with interactive ripple and dynamic cursor effects.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["motion", "clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/glass.tsx",
      "content": "'use client';\nimport { useRef, useEffect, useState, ReactNode, CSSProperties } from 'react';\nimport {\n  motion,\n  useMotionValue,\n  useSpring,\n  AnimatePresence,\n} from 'motion/react';\nimport { cn } from '@/lib/utils';\n\ntype GlassProps = {\n  children?: ReactNode;\n  width?: number | string;\n  height?: number | string;\n  borderRadius?: number;\n  tintOpacity?: number;\n  blur?: number;\n  ripple?: boolean;\n  followMouse?: boolean;\n  className?: string;\n  style?: CSSProperties;\n  onClick?: (e: React.MouseEvent) => void;\n};\n\ntype Ripple = {\n  id: number;\n  x: number;\n  y: number;\n};\n\nexport const Glass = ({\n  children,\n  width = 120,\n  height = 120,\n  borderRadius = 12,\n  tintOpacity = 0.1,\n  blur = 2,\n  ripple = false,\n  followMouse = false,\n  className = '',\n  style = {},\n  onClick,\n}: GlassProps) => {\n  const glassRef = useRef<HTMLDivElement>(null);\n  const left = useMotionValue(0);\n  const top = useMotionValue(0);\n  const [ripples, setRipples] = useState<Ripple[]>([]);\n\n  const smoothLeft = useSpring(left, { damping: 30, stiffness: 200 });\n  const smoothTop = useSpring(top, { damping: 30, stiffness: 200 });\n\n  useEffect(() => {\n    if (!followMouse) return;\n\n    const glass = glassRef.current;\n    const parent = glass?.parentElement;\n    if (!glass || !parent) return;\n\n    const w = typeof width === 'number' ? width : glass.offsetWidth;\n    const h = typeof height === 'number' ? height : glass.offsetHeight;\n\n    const handleTouch = (e: TouchEvent) => {\n      const touch = e.touches[0];\n      if (!touch) return;\n      const parentRect = parent.getBoundingClientRect();\n      left.set(touch.clientX - parentRect.left - w / 2);\n      top.set(touch.clientY - parentRect.top - h / 2);\n    };\n\n    const handleMouse = (e: MouseEvent) => {\n      const parentRect = parent.getBoundingClientRect();\n      left.set(e.clientX - parentRect.left - w / 2);\n      top.set(e.clientY - parentRect.top - h / 2);\n    };\n\n    window.addEventListener('mousemove', handleMouse);\n    window.addEventListener('touchmove', handleTouch);\n    window.addEventListener('touchstart', handleTouch);\n\n    return () => {\n      window.removeEventListener('mousemove', handleMouse);\n      window.removeEventListener('touchmove', handleTouch);\n      window.removeEventListener('touchstart', handleTouch);\n    };\n  }, [width, height, left, top, followMouse]);\n\n  useEffect(() => {\n    if (!ripple) return;\n\n    const handleClick = (e: MouseEvent) => {\n      if (!glassRef.current) return;\n      const rect = glassRef.current.getBoundingClientRect();\n      const x = e.clientX - rect.left;\n      const y = e.clientY - rect.top;\n      const newRipple = { id: Date.now(), x, y };\n      setRipples((prev) => [...prev, newRipple]);\n      setTimeout(\n        () => setRipples((prev) => prev.filter((r) => r.id !== newRipple.id)),\n        1000,\n      );\n    };\n\n    window.addEventListener('click', handleClick);\n    return () => window.removeEventListener('click', handleClick);\n  }, [ripple]);\n\n  const baseStyles: CSSProperties = {\n    '--lg-border-radius': `${borderRadius}px`,\n    '--lg-tint-opacity': tintOpacity,\n    '--lg-blur': `${blur}px`,\n    width,\n    height,\n    borderRadius: `${borderRadius}px`,\n    ...(followMouse ? { left: smoothLeft, top: smoothTop } : {}),\n    ...style,\n  } as CSSProperties;\n\n  const baseClasses = cn(\n    followMouse ? 'absolute' : 'relative',\n    'isolate z-40 shadow-lg',\n    \"before:absolute before:inset-0 before:z-0 before:rounded-(--lg-border-radius) before:bg-[rgba(255,255,255,var(--lg-tint-opacity))] before:shadow-[inset_0_0_20px_-5px_rgba(255,255,255,0.7)] before:content-['']\",\n    \"after:absolute after:inset-0 after:isolate after:-z-1 after:rounded-(--lg-border-radius) after:filter-[url(#glass-distortion)] after:backdrop-blur-(--lg-blur) after:content-['']\",\n    className,\n  );\n  useEffect(() => {\n    if (\n      typeof document !== 'undefined' &&\n      !document.getElementById('glass-distortion-filter')\n    ) {\n      const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');\n      svg.setAttribute('width', '0');\n      svg.setAttribute('height', '0');\n      svg.style.position = 'absolute';\n      svg.style.overflow = 'hidden';\n      svg.id = 'glass-distortion-filter';\n      svg.innerHTML = `\n        <defs>\n          <filter id=\"glass-distortion\" x=\"0%\" y=\"0%\" width=\"100%\" height=\"100%\">\n            <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.008 0.008\" numOctaves=\"2\" seed=\"92\" result=\"noise\"/>\n            <feGaussianBlur in=\"noise\" stdDeviation=\"2\" result=\"blurred\"/>\n            <feDisplacementMap in=\"SourceGraphic\" in2=\"blurred\" scale=\"80\" xChannelSelector=\"R\" yChannelSelector=\"G\"/>\n          </filter>\n        </defs>\n      `;\n      document.body.appendChild(svg);\n    }\n  }, []);\n\n  return (\n    <>\n      <motion.div\n        ref={glassRef}\n        className={baseClasses}\n        style={baseStyles}\n        onClick={onClick}\n      >\n        <div className='relative z-10 w-full h-full'>{children}</div>\n\n        {ripple && (\n          <AnimatePresence>\n            {ripples.map((ripple) => (\n              <motion.div\n                key={ripple.id}\n                className='absolute rounded-full bg-white/30 pointer-events-none'\n                style={{ left: ripple.x, top: ripple.y }}\n                initial={{ width: 0, height: 0, x: 0, y: 0, opacity: 1 }}\n                animate={{\n                  width: typeof width === 'number' ? width * 2 : 200,\n                  height: typeof width === 'number' ? width * 2 : 200,\n                  x: typeof width === 'number' ? -width : -100,\n                  y: typeof width === 'number' ? -width : -100,\n                  opacity: 0,\n                }}\n                exit={{ opacity: 0 }}\n                transition={{ duration: 1, ease: 'easeOut' }}\n              />\n            ))}\n          </AnimatePresence>\n        )}\n      </motion.div>\n    </>\n  );\n};\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}"
    }
  ]
}
