{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particles",
  "title": "Particles",
  "description": "A simple particles",
  "files": [
    {
      "path": "src/components/ui/particles.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport { useEffect, useRef, useState } from \"react\";\nimport { cn } from \"@/lib/utils\";\n\ninterface MousePosition {\n  x: number;\n  y: number;\n}\n\nfunction MousePosition(): MousePosition {\n  const [mousePosition, setMousePosition] = useState<MousePosition>({\n    x: 0,\n    y: 0,\n  });\n\n  useEffect(() => {\n    const handleMouseMove = (event: MouseEvent) => {\n      setMousePosition({ x: event.clientX, y: event.clientY });\n    };\n\n    window.addEventListener(\"mousemove\", handleMouseMove);\n\n    return () => {\n      window.removeEventListener(\"mousemove\", handleMouseMove);\n    };\n  }, []);\n\n  return mousePosition;\n}\n\ninterface ParticlesProps {\n  className?: string;\n  quantity?: number;\n  staticity?: number;\n  ease?: number;\n  size?: number;\n  refresh?: boolean;\n  color?: string;\n  vx?: number;\n  vy?: number;\n}\nfunction hexToRgb(hex: string): number[] {\n  hex = hex.replace(\"#\", \"\");\n\n  if (hex.length === 3) {\n    hex = hex\n      .split(\"\")\n      .map((char) => char + char)\n      .join(\"\");\n  }\n\n  const hexInt = Number.parseInt(hex, 16);\n  const red = (hexInt >> 16) & 255;\n  const green = (hexInt >> 8) & 255;\n  const blue = hexInt & 255;\n  return [red, green, blue];\n}\n\nconst Particles: React.FC<ParticlesProps> = ({\n  className = \"\",\n  quantity = 100,\n  staticity = 50,\n  ease = 50,\n  size = 0.4,\n  refresh = false,\n  color = \"#ffffff\",\n  vx = 0,\n  vy = 0,\n}) => {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const canvasContainerRef = useRef<HTMLDivElement>(null);\n  const context = useRef<CanvasRenderingContext2D | null>(null);\n  const circles = useRef<Circle[]>([]);\n  const mousePosition = MousePosition();\n  const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n  const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 });\n  const dpr = typeof window !== \"undefined\" ? window.devicePixelRatio : 1;\n\n  useEffect(() => {\n    if (canvasRef.current) {\n      context.current = canvasRef.current.getContext(\"2d\");\n    }\n    initCanvas();\n    animate();\n    window.addEventListener(\"resize\", initCanvas);\n\n    return () => {\n      window.removeEventListener(\"resize\", initCanvas);\n    };\n  }, [color]);\n\n  useEffect(() => {\n    onMouseMove();\n  }, [mousePosition.x, mousePosition.y]);\n\n  useEffect(() => {\n    initCanvas();\n  }, [refresh]);\n\n  const initCanvas = () => {\n    resizeCanvas();\n    drawParticles();\n  };\n\n  const onMouseMove = () => {\n    if (canvasRef.current) {\n      const rect = canvasRef.current.getBoundingClientRect();\n      const { w, h } = canvasSize.current;\n      const x = mousePosition.x - rect.left - w / 2;\n      const y = mousePosition.y - rect.top - h / 2;\n      const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;\n      if (inside) {\n        mouse.current.x = x;\n        mouse.current.y = y;\n      }\n    }\n  };\n\n  type Circle = {\n    x: number;\n    y: number;\n    translateX: number;\n    translateY: number;\n    size: number;\n    alpha: number;\n    targetAlpha: number;\n    dx: number;\n    dy: number;\n    magnetism: number;\n  };\n\n  const resizeCanvas = () => {\n    if (canvasContainerRef.current && canvasRef.current && context.current) {\n      circles.current.length = 0;\n      canvasSize.current.w = canvasContainerRef.current.offsetWidth;\n      canvasSize.current.h = canvasContainerRef.current.offsetHeight;\n      canvasRef.current.width = canvasSize.current.w * dpr;\n      canvasRef.current.height = canvasSize.current.h * dpr;\n      canvasRef.current.style.width = `${canvasSize.current.w}px`;\n      canvasRef.current.style.height = `${canvasSize.current.h}px`;\n      context.current.scale(dpr, dpr);\n    }\n  };\n\n  const circleParams = (): Circle => {\n    const x = Math.floor(Math.random() * canvasSize.current.w);\n    const y = Math.floor(Math.random() * canvasSize.current.h);\n    const translateX = 0;\n    const translateY = 0;\n    const pSize = Math.floor(Math.random() * 2) + size;\n    const alpha = 0;\n    const targetAlpha = Number.parseFloat(\n      (Math.random() * 0.6 + 0.1).toFixed(1)\n    );\n    const dx = (Math.random() - 0.5) * 0.1;\n    const dy = (Math.random() - 0.5) * 0.1;\n    const magnetism = 0.1 + Math.random() * 4;\n    return {\n      x,\n      y,\n      translateX,\n      translateY,\n      size: pSize,\n      alpha,\n      targetAlpha,\n      dx,\n      dy,\n      magnetism,\n    };\n  };\n\n  const rgb = hexToRgb(color);\n\n  const drawCircle = (circle: Circle, update = false) => {\n    if (context.current) {\n      const { x, y, translateX, translateY, size, alpha } = circle;\n      context.current.translate(translateX, translateY);\n      context.current.beginPath();\n      context.current.arc(x, y, size, 0, 2 * Math.PI);\n      context.current.fillStyle = `rgba(${rgb.join(\", \")}, ${alpha})`;\n      context.current.fill();\n      context.current.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n      if (!update) {\n        circles.current.push(circle);\n      }\n    }\n  };\n\n  const clearContext = () => {\n    if (context.current) {\n      context.current.clearRect(\n        0,\n        0,\n        canvasSize.current.w,\n        canvasSize.current.h\n      );\n    }\n  };\n\n  const drawParticles = () => {\n    clearContext();\n    const particleCount = quantity;\n    for (let i = 0; i < particleCount; i++) {\n      const circle = circleParams();\n      drawCircle(circle);\n    }\n  };\n\n  const remapValue = (\n    value: number,\n    start1: number,\n    end1: number,\n    start2: number,\n    end2: number\n  ): number => {\n    const remapped =\n      ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;\n    return remapped > 0 ? remapped : 0;\n  };\n\n  const animate = () => {\n    clearContext();\n    circles.current.forEach((circle: Circle, i: number) => {\n      // Handle the alpha value\n      const edge = [\n        circle.x + circle.translateX - circle.size, // distance from left edge\n        canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge\n        circle.y + circle.translateY - circle.size, // distance from top edge\n        canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge\n      ];\n      const closestEdge = edge.reduce((a, b) => Math.min(a, b));\n      const remapClosestEdge = Number.parseFloat(\n        remapValue(closestEdge, 0, 20, 0, 1).toFixed(2)\n      );\n      if (remapClosestEdge > 1) {\n        circle.alpha += 0.02;\n        if (circle.alpha > circle.targetAlpha) {\n          circle.alpha = circle.targetAlpha;\n        }\n      } else {\n        circle.alpha = circle.targetAlpha * remapClosestEdge;\n      }\n      circle.x += circle.dx + vx;\n      circle.y += circle.dy + vy;\n      circle.translateX +=\n        (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) /\n        ease;\n      circle.translateY +=\n        (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) /\n        ease;\n\n      drawCircle(circle, true);\n\n      // circle gets out of the canvas\n      if (\n        circle.x < -circle.size ||\n        circle.x > canvasSize.current.w + circle.size ||\n        circle.y < -circle.size ||\n        circle.y > canvasSize.current.h + circle.size\n      ) {\n        // remove the circle from the array\n        circles.current.splice(i, 1);\n        // create a new circle\n        const newCircle = circleParams();\n        drawCircle(newCircle);\n        // update the circle position\n      }\n    });\n    window.requestAnimationFrame(animate);\n  };\n\n  return (\n    <div\n      aria-hidden=\"true\"\n      className={cn(\"pointer-events-none\", className)}\n      ref={canvasContainerRef}\n    >\n      <canvas className=\"size-full\" ref={canvasRef} />\n    </div>\n  );\n};\n\nexport default Particles;\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}