{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pixel-background",
  "type": "registry:ui",
  "title": "Pixel Background",
  "description": "A flexible pixel animation background with customizable origins and smooth atmospheric motion.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": [""],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/pixel-background.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef } from 'react';\n\nclass Pixel {\n  width: number;\n  height: number;\n  ctx: CanvasRenderingContext2D;\n  x: number;\n  y: number;\n  color: string;\n  speed: number;\n  size: number;\n  sizeStep: number;\n  minSize: number;\n  maxSizeInteger: number;\n  maxSize: number;\n  delay: number;\n  counter: number;\n  counterStep: number;\n  isIdle: boolean;\n  isReverse: boolean;\n  isShimmer: boolean;\n\n  constructor(\n    canvas: HTMLCanvasElement,\n    context: CanvasRenderingContext2D,\n    x: number,\n    y: number,\n    color: string,\n    speed: number,\n    delay: number,\n  ) {\n    this.width = canvas.width;\n    this.height = canvas.height;\n    this.ctx = context;\n    this.x = x;\n    this.y = y;\n    this.color = color;\n    this.speed = this.getRandomValue(0.1, 0.9) * speed;\n    this.size = 0;\n    this.sizeStep = Math.random() * 0.4;\n    this.minSize = 0.5;\n    this.maxSizeInteger = 2;\n    this.maxSize = this.getRandomValue(this.minSize, this.maxSizeInteger);\n    this.delay = delay;\n    this.counter = 0;\n    this.counterStep = Math.random() * 4 + (this.width + this.height) * 0.01;\n    this.isIdle = false;\n    this.isReverse = false;\n    this.isShimmer = false;\n  }\n\n  getRandomValue(min: number, max: number) {\n    return Math.random() * (max - min) + min;\n  }\n\n  draw() {\n    const centerOffset = this.maxSizeInteger * 0.5 - this.size * 0.5;\n    this.ctx.fillStyle = this.color;\n    this.ctx.fillRect(\n      this.x + centerOffset,\n      this.y + centerOffset,\n      this.size,\n      this.size,\n    );\n  }\n\n  appear() {\n    this.isIdle = false;\n    if (this.counter <= this.delay) {\n      this.counter += this.counterStep;\n      return;\n    }\n    if (this.size >= this.maxSize) {\n      this.isShimmer = true;\n    }\n    if (this.isShimmer) {\n      this.shimmer();\n    } else {\n      this.size += this.sizeStep;\n    }\n    this.draw();\n  }\n\n  shimmer() {\n    if (this.size >= this.maxSize) {\n      this.isReverse = true;\n    } else if (this.size <= this.minSize) {\n      this.isReverse = false;\n    }\n    if (this.isReverse) {\n      this.size -= this.speed;\n    } else {\n      this.size += this.speed;\n    }\n  }\n}\n\nfunction getEffectiveSpeed(value: number, reducedMotion: boolean) {\n  const min = 0;\n  const max = 100;\n  const throttle = 0.001;\n  if (value <= min || reducedMotion) {\n    return min;\n  } else if (value >= max) {\n    return max * throttle;\n  } else {\n    return value * throttle;\n  }\n}\n\ninterface PixelBackgroundProps {\n  gap?: number;\n  speed?: number;\n  colors?: string;\n  opacity?: number;\n  direction?:\n    | 'center'\n    | 'top'\n    | 'bottom'\n    | 'left'\n    | 'right'\n    | 'top-left'\n    | 'top-right'\n    | 'bottom-left'\n    | 'bottom-right';\n  className?: string;\n  canvasClassName?: string;\n  children?: React.ReactNode;\n}\n\nexport default function PixelBackground({\n  gap = 6,\n  speed = 80,\n  colors = '#fecdd3,#fda4af,#e11d48',\n  opacity = 1,\n  direction = 'center',\n  className = '',\n  canvasClassName = '',\n  children,\n}: PixelBackgroundProps) {\n  const containerRef = useRef<HTMLDivElement>(null);\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const pixelsRef = useRef<Pixel[]>([]);\n  const animationRef = useRef<number | null>(null);\n  const timePreviousRef = useRef<number>(0);\n\n  useEffect(() => {\n    const reducedMotionValue = window.matchMedia(\n      '(prefers-reduced-motion: reduce)',\n    ).matches;\n    const getOriginPoint = (width: number, height: number) => {\n      switch (direction) {\n        case 'top':\n          return { x: width / 2, y: 0 };\n        case 'bottom':\n          return { x: width / 2, y: height };\n        case 'left':\n          return { x: 0, y: height / 2 };\n        case 'right':\n          return { x: width, y: height / 2 };\n        case 'top-left':\n          return { x: 0, y: 0 };\n        case 'top-right':\n          return { x: width, y: 0 };\n        case 'bottom-left':\n          return { x: 0, y: height };\n        case 'bottom-right':\n          return { x: width, y: height };\n        case 'center':\n        default:\n          return { x: width / 2, y: height / 2 };\n      }\n    };\n\n    const initPixels = () => {\n      if (!containerRef.current || !canvasRef.current) return;\n\n      const rect = containerRef.current.getBoundingClientRect();\n      const width = Math.floor(rect.width);\n      const height = Math.floor(rect.height);\n      const ctx = canvasRef.current.getContext('2d');\n\n      canvasRef.current.width = width;\n      canvasRef.current.height = height;\n      canvasRef.current.style.width = `${width}px`;\n      canvasRef.current.style.height = `${height}px`;\n\n      const origin = getOriginPoint(width, height);\n      const colorsArray = colors.split(',');\n      const pxs: Pixel[] = [];\n\n      for (let x = 0; x < width; x += parseInt(gap.toString(), 10)) {\n        for (let y = 0; y < height; y += parseInt(gap.toString(), 10)) {\n          const color =\n            colorsArray[Math.floor(Math.random() * colorsArray.length)];\n          const dx = x - origin.x;\n          const dy = y - origin.y;\n          const distance = Math.sqrt(dx * dx + dy * dy);\n          const delay = reducedMotionValue ? 0 : distance;\n\n          if (!ctx) return;\n          pxs.push(\n            new Pixel(\n              canvasRef.current,\n              ctx,\n              x,\n              y,\n              color,\n              getEffectiveSpeed(speed, reducedMotionValue),\n              delay,\n            ),\n          );\n        }\n      }\n\n      pixelsRef.current = pxs;\n    };\n\n    const doAnimate = (fnName: 'appear') => {\n      animationRef.current = requestAnimationFrame(() => doAnimate(fnName));\n\n      const timeNow = performance.now();\n      if (timePreviousRef.current === 0) timePreviousRef.current = timeNow;\n      const timePassed = timeNow - timePreviousRef.current;\n      const timeInterval = 1000 / 60;\n\n      if (timePassed < timeInterval) return;\n      timePreviousRef.current = timeNow - (timePassed % timeInterval);\n\n      const ctx = canvasRef.current?.getContext('2d');\n      if (!ctx || !canvasRef.current) return;\n\n      ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);\n\n      let allIdle = true;\n      for (let i = 0; i < pixelsRef.current.length; i++) {\n        const pixel = pixelsRef.current[i];\n        pixel[fnName]();\n        if (!pixel.isIdle) {\n          allIdle = false;\n        }\n      }\n\n      if (allIdle && animationRef.current !== null) {\n        cancelAnimationFrame(animationRef.current);\n      }\n    };\n\n    const handleAnimation = (name: 'appear') => {\n      if (animationRef.current !== null) {\n        cancelAnimationFrame(animationRef.current);\n      }\n      animationRef.current = requestAnimationFrame(() => doAnimate(name));\n    };\n\n    initPixels();\n    handleAnimation('appear');\n\n    return () => {\n      if (animationRef.current !== null) {\n        cancelAnimationFrame(animationRef.current);\n      }\n    };\n  }, [gap, speed, colors, direction]);\n\n  return (\n    <div ref={containerRef} className={`relative overflow-hidden ${className}`}>\n      <canvas\n        ref={canvasRef}\n        className={`absolute inset-0 z-0 pointer-events-none ${canvasClassName}`}\n        style={{ opacity }}\n      />\n\n      {children && <div className='relative z-10'>{children}</div>}\n    </div>\n  );\n}\n"
    }
  ]
}
