{
  "name": "dither-gradient",
  "type": "registry:ui",
  "dependencies": [],
  "registryDependencies": [],
  "description": "An animated dithered gradient background effect using canvas with Bayer matrix dithering.",
  "files": [
    {
      "path": "components/ui/dither-gradient.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef } from \"react\"\nimport { cn } from \"@/lib/utils\"\n\ninterface DitherGradientProps {\n  className?: string\n  colorFrom?: string\n  colorTo?: string\n  colorMid?: string\n  intensity?: number\n  speed?: number\n  angle?: number\n}\n\nexport function DitherGradient({\n  className,\n  colorFrom = \"#4f46e5\",\n  colorTo = \"#ec4899\",\n  colorMid = \"#a855f7\",\n  intensity = 0.15,\n  speed = 3,\n  angle = 45,\n}: DitherGradientProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const animationRef = useRef<number>(0)\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    if (!canvas) return\n\n    const ctx = canvas.getContext(\"2d\")\n    if (!ctx) return\n\n    const resize = () => {\n      const rect = canvas.getBoundingClientRect()\n      canvas.width = rect.width\n      canvas.height = rect.height\n    }\n\n    resize()\n    window.addEventListener(\"resize\", resize)\n\n    let time = 0\n    const bayerMatrix = [\n      [0, 8, 2, 10],\n      [12, 4, 14, 6],\n      [3, 11, 1, 9],\n      [15, 7, 13, 5],\n    ]\n\n    const hexToRgb = (hex: string) => {\n      const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex)\n      if (!result) return { r: 0, g: 0, b: 0 }\n      return {\n        r: parseInt(result[1]!, 16),\n        g: parseInt(result[2]!, 16),\n        b: parseInt(result[3]!, 16),\n      }\n    }\n\n    const smoothstep = (t: number) => t * t * (3 - 2 * t)\n\n    const animate = () => {\n      const { width, height } = canvas\n      const imageData = ctx.createImageData(width, height)\n      const data = imageData.data\n\n      const from = hexToRgb(colorFrom)\n      const mid = hexToRgb(colorMid)\n      const to = hexToRgb(colorTo)\n      const rad = (angle * Math.PI) / 180\n\n      for (let y = 0; y < height; y++) {\n        for (let x = 0; x < width; x++) {\n          const normalizedX = x / width\n          const normalizedY = y / height\n          \n          const gradientPos = \n            (normalizedX * Math.cos(rad) + normalizedY * Math.sin(rad)) * 0.8 + \n            0.1 + \n            Math.sin(time * speed * 0.0008) * 0.1\n\n          const clampedPos = Math.max(0, Math.min(1, gradientPos))\n\n          let r: number, g: number, b: number\n          if (clampedPos < 0.5) {\n            const t = smoothstep(clampedPos * 2)\n            r = from.r + (mid.r - from.r) * t\n            g = from.g + (mid.g - from.g) * t\n            b = from.b + (mid.b - from.b) * t\n          } else {\n            const t = smoothstep((clampedPos - 0.5) * 2)\n            r = mid.r + (to.r - mid.r) * t\n            g = mid.g + (to.g - mid.g) * t\n            b = mid.b + (to.b - mid.b) * t\n          }\n\n          const threshold = (bayerMatrix[y % 4]![x % 4]! / 16 - 0.5) * intensity * 180\n          const noise = (Math.random() - 0.5) * intensity * 60\n\n          const idx = (y * width + x) * 4\n          data[idx] = Math.min(255, Math.max(0, r + threshold + noise))\n          data[idx + 1] = Math.min(255, Math.max(0, g + threshold + noise))\n          data[idx + 2] = Math.min(255, Math.max(0, b + threshold + noise))\n          data[idx + 3] = 255\n        }\n      }\n\n      ctx.putImageData(imageData, 0, 0)\n      time += 16\n      animationRef.current = requestAnimationFrame(animate)\n    }\n\n    animate()\n\n    return () => {\n      window.removeEventListener(\"resize\", resize)\n      cancelAnimationFrame(animationRef.current)\n    }\n  }, [colorFrom, colorTo, colorMid, intensity, speed, angle])\n\n  return (\n    <canvas\n      ref={canvasRef}\n      className={cn(\"absolute inset-0 h-full w-full\", className)}\n    />\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}
