{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "flickering-grid",
  "type": "registry:ui",
  "title": "Flickering Grid",
  "description": "A flickering grid background made with SVGs, fully customizable using Tailwind CSS.",
  "files": [
    {
      "path": "registry/magicui/flickering-grid.tsx",
      "content": "\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface FlickeringGridProps extends React.HTMLAttributes<HTMLDivElement> {\n  squareSize?: number\n  gridGap?: number\n  flickerChance?: number\n  color?: string\n  width?: number\n  height?: number\n  className?: string\n  maxOpacity?: number\n}\n\nexport const FlickeringGrid: React.FC<FlickeringGridProps> = ({\n  squareSize = 4,\n  gridGap = 6,\n  flickerChance = 0.3,\n  color = \"rgb(0, 0, 0)\",\n  width,\n  height,\n  className,\n  maxOpacity = 0.3,\n  ...props\n}) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null)\n  const containerRef = useRef<HTMLDivElement>(null)\n  const [isInView, setIsInView] = useState(false)\n  const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 })\n\n  const memoizedColor = useMemo(() => {\n    const toRGBA = (color: string) => {\n      if (typeof window === \"undefined\") {\n        return `rgba(0, 0, 0,`\n      }\n      const canvas = document.createElement(\"canvas\")\n      canvas.width = canvas.height = 1\n      const ctx = canvas.getContext(\"2d\")\n      if (!ctx) return \"rgba(255, 0, 0,\"\n      ctx.fillStyle = color\n      ctx.fillRect(0, 0, 1, 1)\n      const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data)\n      return `rgba(${r}, ${g}, ${b},`\n    }\n    return toRGBA(color)\n  }, [color])\n\n  const setupCanvas = useCallback(\n    (canvas: HTMLCanvasElement, width: number, height: number) => {\n      const dpr = window.devicePixelRatio || 1\n      canvas.width = width * dpr\n      canvas.height = height * dpr\n      canvas.style.width = `${width}px`\n      canvas.style.height = `${height}px`\n      const cols = Math.ceil(width / (squareSize + gridGap))\n      const rows = Math.ceil(height / (squareSize + gridGap))\n\n      const squares = new Float32Array(cols * rows)\n      for (let i = 0; i < squares.length; i++) {\n        squares[i] = Math.random() * maxOpacity\n      }\n\n      return { cols, rows, squares, dpr }\n    },\n    [squareSize, gridGap, maxOpacity]\n  )\n\n  const updateSquares = useCallback(\n    (squares: Float32Array, deltaTime: number) => {\n      for (let i = 0; i < squares.length; i++) {\n        if (Math.random() < flickerChance * deltaTime) {\n          squares[i] = Math.random() * maxOpacity\n        }\n      }\n    },\n    [flickerChance, maxOpacity]\n  )\n\n  const drawGrid = useCallback(\n    (\n      ctx: CanvasRenderingContext2D,\n      width: number,\n      height: number,\n      cols: number,\n      rows: number,\n      squares: Float32Array,\n      dpr: number\n    ) => {\n      ctx.clearRect(0, 0, width, height)\n      ctx.fillStyle = \"transparent\"\n      ctx.fillRect(0, 0, width, height)\n\n      for (let i = 0; i < cols; i++) {\n        for (let j = 0; j < rows; j++) {\n          const opacity = squares[i * rows + j]\n          ctx.fillStyle = `${memoizedColor}${opacity})`\n          ctx.fillRect(\n            i * (squareSize + gridGap) * dpr,\n            j * (squareSize + gridGap) * dpr,\n            squareSize * dpr,\n            squareSize * dpr\n          )\n        }\n      }\n    },\n    [memoizedColor, squareSize, gridGap]\n  )\n\n  useEffect(() => {\n    const canvas = canvasRef.current\n    const container = containerRef.current\n    const ctx = canvas?.getContext(\"2d\") ?? null\n    let animationFrameId: number | null = null\n    let resizeObserver: ResizeObserver | null = null\n    let intersectionObserver: IntersectionObserver | null = null\n    let gridParams: ReturnType<typeof setupCanvas> | null = null\n\n    if (canvas && container && ctx) {\n      const updateCanvasSize = () => {\n        const newWidth = width || container.clientWidth\n        const newHeight = height || container.clientHeight\n        setCanvasSize({ width: newWidth, height: newHeight })\n        gridParams = setupCanvas(canvas, newWidth, newHeight)\n      }\n\n      updateCanvasSize()\n\n      let lastTime = 0\n      const animate = (time: number) => {\n        if (!isInView || !gridParams) return\n\n        const deltaTime = (time - lastTime) / 1000\n        lastTime = time\n\n        updateSquares(gridParams.squares, deltaTime)\n        drawGrid(\n          ctx,\n          canvas.width,\n          canvas.height,\n          gridParams.cols,\n          gridParams.rows,\n          gridParams.squares,\n          gridParams.dpr\n        )\n        animationFrameId = requestAnimationFrame(animate)\n      }\n\n      resizeObserver = new ResizeObserver(() => {\n        updateCanvasSize()\n      })\n      resizeObserver.observe(container)\n\n      intersectionObserver = new IntersectionObserver(\n        ([entry]) => {\n          setIsInView(entry.isIntersecting)\n        },\n        { threshold: 0 }\n      )\n      intersectionObserver.observe(canvas)\n\n      if (isInView) {\n        animationFrameId = requestAnimationFrame(animate)\n      }\n    }\n\n    return () => {\n      if (animationFrameId !== null) {\n        cancelAnimationFrame(animationFrameId)\n      }\n      if (resizeObserver) {\n        resizeObserver.disconnect()\n      }\n      if (intersectionObserver) {\n        intersectionObserver.disconnect()\n      }\n    }\n  }, [setupCanvas, updateSquares, drawGrid, width, height, isInView])\n\n  return (\n    <div\n      ref={containerRef}\n      className={cn(`h-full w-full ${className}`)}\n      {...props}\n    >\n      <canvas\n        ref={canvasRef}\n        className=\"pointer-events-none\"\n        style={{\n          width: canvasSize.width,\n          height: canvasSize.height,\n        }}\n      />\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}