{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pixel-icon",
  "type": "registry:ui",
  "title": "Pixel Icon",
  "description": "A pixel icon component for Next.js apps with next-themes and Tailwind CSS, supporting system, light, and dark modes.",
  "files": [
    {
      "path": "registry/bucharitesh/pixel-icon.tsx",
      "content": "'use client';\n\nimport type React from 'react';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\n\ninterface PixelIconProps {\n  icon: number[][];\n  baseColor: string;\n  flickerColor: string;\n  secondaryColor?: string;\n  size?: number;\n  flickerChance?: number;\n  pixelShape?: 'circle' | 'square';\n  className?: string;\n}\n\nconst PixelIcon: React.FC<PixelIconProps> = ({\n  icon,\n  baseColor,\n  flickerColor,\n  secondaryColor = 'gray',\n  size = 80,\n  flickerChance = 0.3,\n  pixelShape = 'circle',\n  className,\n}) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const [isHovering, setIsHovering] = useState(false);\n  const [isInView, setIsInView] = useState(false);\n\n  const memoizedColors = useMemo(() => {\n    const toRGB = (color: string): [number, number, number] => {\n      if (typeof window === 'undefined') {\n        return [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 [255, 0, 0];\n      ctx.fillStyle = color;\n      ctx.fillRect(0, 0, 1, 1);\n      const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;\n      return [r, g, b];\n    };\n\n    const createShades = (\n      color: [number, number, number],\n      isDarker: boolean\n    ) => {\n      const shades: any = [];\n      if (isDarker) {\n        // Create more distinct dark shades\n        const darkFactors = [0.7, 0.8, 0.9, 1.0];\n        for (const factor of darkFactors) {\n          shades.push(\n            color.map((c) => Math.floor(c * factor)) as [number, number, number]\n          );\n        }\n      } else {\n        // Keep the original logic for lighter shades\n        for (let i = 0; i < 4; i++) {\n          const factor = 0.7 + (0.6 * i) / 3; // Range from 0.7 to 1.3\n          shades.push(\n            color.map((c) => Math.min(255, Math.floor(c * factor))) as [\n              number,\n              number,\n              number,\n            ]\n          );\n        }\n      }\n      return shades;\n    };\n\n    const baseRGB = toRGB(baseColor);\n    const flickerRGB = toRGB(flickerColor);\n    const secondaryRGB = toRGB(secondaryColor);\n\n    return {\n      base: `rgb(${baseRGB.join(',')})`,\n      flickerDark: createShades(flickerRGB, true),\n      flickerLight: createShades(flickerRGB, false),\n      secondary: `rgb(${secondaryRGB.join(',')})`,\n    };\n  }, [baseColor, flickerColor, secondaryColor]);\n\n  const setupCanvas = useCallback(\n    (canvas: HTMLCanvasElement) => {\n      const dpr = window.devicePixelRatio || 1;\n      canvas.width = size * dpr;\n      canvas.height = size * dpr;\n      canvas.style.width = `${size}px`;\n      canvas.style.height = `${size}px`;\n\n      const pixelSize = size / icon.length;\n      const pixelStates = icon.map((row) =>\n        row.map((pixel) => ({\n          type: pixel,\n          shadeIndex: 3, // Start with the brightest shade\n        }))\n      );\n\n      return {\n        pixelSize,\n        pixelStates,\n        dpr,\n      };\n    },\n    [icon, size]\n  );\n\n  const updatePixels = useCallback(\n    (\n      pixelStates: { type: number; shadeIndex: number }[][],\n      deltaTime: number\n    ) => {\n      if (!isHovering) return;\n\n      pixelStates.forEach((row) => {\n        row.forEach((pixel) => {\n          if (pixel.type !== 0 && Math.random() < flickerChance * deltaTime) {\n            if (pixel.type === 1) {\n              // Darker shades for type 1\n              pixel.shadeIndex = Math.floor(Math.random() * 4); // 0 to 3\n            } else if (pixel.type === 2) {\n              // Lighter shades for type 2\n              pixel.shadeIndex = Math.floor(Math.random() * 4); // 0 to 3\n            }\n          }\n        });\n      });\n    },\n    [isHovering, flickerChance]\n  );\n\n  const drawIcon = useCallback(\n    (\n      ctx: CanvasRenderingContext2D,\n      pixelSize: number,\n      pixelStates: { type: number; shadeIndex: number }[][],\n      dpr: number\n    ) => {\n      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n\n      pixelStates.forEach((row, y) => {\n        row.forEach((pixel, x) => {\n          if (pixel.type === 0) return;\n\n          let color: any;\n\n          if (isHovering) {\n            if (pixel.type === 2) {\n              color = `rgb(${memoizedColors.flickerDark[pixel.shadeIndex].join(',')})`;\n            } else if (pixel.type === 1) {\n              color = `rgb(${memoizedColors.flickerLight[pixel.shadeIndex].join(',')})`;\n            }\n          } else {\n            color =\n              pixel.type === 1 ? memoizedColors.base : memoizedColors.secondary;\n          }\n\n          ctx.fillStyle = (color as string) ?? '';\n\n          if (pixelShape === 'circle') {\n            ctx.beginPath();\n            ctx.arc(\n              (x * pixelSize + pixelSize / 2) * dpr,\n              (y * pixelSize + pixelSize / 2) * dpr,\n              (pixelSize / 2) * dpr,\n              0,\n              Math.PI * 2\n            );\n            ctx.fill();\n          } else {\n            ctx.fillRect(\n              x * pixelSize * dpr,\n              y * pixelSize * dpr,\n              pixelSize * dpr,\n              pixelSize * dpr\n            );\n          }\n        });\n      });\n    },\n    [memoizedColors, isHovering, pixelShape]\n  );\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    let animationFrameId: number;\n    const { pixelSize, pixelStates, dpr } = setupCanvas(canvas);\n\n    let lastTime = 0;\n    const animate = (time: number) => {\n      if (!isInView) return;\n\n      const deltaTime = (time - lastTime) / 1000;\n      lastTime = time;\n\n      updatePixels(pixelStates, deltaTime);\n      drawIcon(ctx, pixelSize, pixelStates, dpr);\n      animationFrameId = requestAnimationFrame(animate);\n    };\n\n    const observer = new IntersectionObserver(\n      ([entry]) => {\n        setIsInView(entry.isIntersecting);\n      },\n      { threshold: 0 }\n    );\n\n    observer.observe(canvas);\n\n    if (isInView) {\n      animationFrameId = requestAnimationFrame(animate);\n    }\n\n    return () => {\n      cancelAnimationFrame(animationFrameId);\n      observer.disconnect();\n    };\n  }, [setupCanvas, updatePixels, drawIcon, isInView]);\n\n  return (\n    <div\n      role=\"img\"\n      aria-label=\"Pixel icon\"\n      className={`box-border cursor-pointer ${className}`}\n      style={{ width: `${size}px`, height: `${size}px` }}\n      onMouseEnter={() => setIsHovering(true)}\n      onMouseLeave={() => setIsHovering(false)}\n    >\n      <canvas\n        ref={canvasRef}\n        className=\"h-full w-full\"\n        width={size}\n        height={size}\n      />\n    </div>\n  );\n};\n\nexport default PixelIcon;\n",
      "type": "registry:ui"
    }
  ]
}