{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "particles",
  "type": "registry:ui",
  "title": "Particles",
  "description": "Particles are a fun way to add some visual flair to your website. They can be used to create a sense of depth, movement, and interactivity.",
  "files": [
    {
      "path": "registry/magicui/particles.tsx",
      "content": "\"use client\"\n\nimport React, {\n  useEffect,\n  useRef,\n  useState,\n  type ComponentPropsWithoutRef,\n} from \"react\"\n\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 extends ComponentPropsWithoutRef<\"div\"> {\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}\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 = 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\ntype 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\nexport const 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  ...props\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  const rafID = useRef<number | null>(null)\n  const resizeTimeout = useRef<NodeJS.Timeout | null>(null)\n  const initCanvasRef = useRef<() => void>(() => {})\n  const onMouseMoveRef = useRef<() => void>(() => {})\n  const animateRef = useRef<() => void>(() => {})\n\n  useEffect(() => {\n    if (canvasRef.current) {\n      context.current = canvasRef.current.getContext(\"2d\")\n    }\n    initCanvasRef.current()\n    animateRef.current()\n\n    const handleResize = () => {\n      if (resizeTimeout.current) {\n        clearTimeout(resizeTimeout.current)\n      }\n      resizeTimeout.current = setTimeout(() => {\n        initCanvasRef.current()\n      }, 200)\n    }\n\n    window.addEventListener(\"resize\", handleResize)\n\n    return () => {\n      if (rafID.current != null) {\n        window.cancelAnimationFrame(rafID.current)\n      }\n      if (resizeTimeout.current) {\n        clearTimeout(resizeTimeout.current)\n      }\n      window.removeEventListener(\"resize\", handleResize)\n    }\n  }, [color])\n\n  useEffect(() => {\n    onMouseMoveRef.current()\n  }, [mousePosition.x, mousePosition.y])\n\n  useEffect(() => {\n    initCanvasRef.current()\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  const resizeCanvas = () => {\n    if (canvasContainerRef.current && canvasRef.current && context.current) {\n      canvasSize.current.w = canvasContainerRef.current.offsetWidth\n      canvasSize.current.h = canvasContainerRef.current.offsetHeight\n\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      // Clear existing particles and create new ones with exact quantity\n      circles.current = []\n      for (let i = 0; i < quantity; i++) {\n        const circle = circleParams()\n        drawCircle(circle)\n      }\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 = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1))\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 = 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      }\n    })\n    rafID.current = window.requestAnimationFrame(animateRef.current)\n  }\n\n  initCanvasRef.current = initCanvas\n  onMouseMoveRef.current = onMouseMove\n  animateRef.current = animate\n\n  return (\n    <div\n      className={cn(\"pointer-events-none\", className)}\n      ref={canvasContainerRef}\n      aria-hidden=\"true\"\n      {...props}\n    >\n      <canvas ref={canvasRef} className=\"size-full\" />\n    </div>\n  )\n}\n",
      "type": "registry:ui"
    }
  ]
}