{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-backgrounds-gravity-stars",
  "title": "Gravity Stars Background",
  "description": "A background component featuring a subtle yet engaging animated gravity stars effect.",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "registry/components/backgrounds/gravity-stars/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\ntype MouseGravity = 'attract' | 'repel';\ntype GlowAnimation = 'instant' | 'ease' | 'spring';\ntype StarsInteractionType = 'bounce' | 'merge';\n\ntype GravityStarsProps = {\n  starsCount?: number;\n  starsSize?: number;\n  starsOpacity?: number;\n  glowIntensity?: number;\n  glowAnimation?: GlowAnimation;\n  movementSpeed?: number;\n  mouseInfluence?: number;\n  mouseGravity?: MouseGravity;\n  gravityStrength?: number;\n  starsInteraction?: boolean;\n  starsInteractionType?: StarsInteractionType;\n} & React.ComponentProps<'div'>;\n\ntype Particle = {\n  x: number;\n  y: number;\n  vx: number;\n  vy: number;\n  size: number;\n  opacity: number;\n  baseOpacity: number;\n  mass: number;\n  glowMultiplier?: number;\n  glowVelocity?: number;\n};\n\nfunction GravityStarsBackground({\n  starsCount = 75,\n  starsSize = 2,\n  starsOpacity = 0.75,\n  glowIntensity = 15,\n  glowAnimation = 'ease',\n  movementSpeed = 0.3,\n  mouseInfluence = 100,\n  mouseGravity = 'attract',\n  gravityStrength = 75,\n  starsInteraction = false,\n  starsInteractionType = 'bounce',\n  className,\n  ...props\n}: GravityStarsProps) {\n  const containerRef = React.useRef<HTMLDivElement | null>(null);\n  const canvasRef = React.useRef<HTMLCanvasElement | null>(null);\n  const animRef = React.useRef<number | null>(null);\n  const starsRef = React.useRef<Particle[]>([]);\n  const mouseRef = React.useRef<{ x: number; y: number }>({ x: 0, y: 0 });\n  const [dpr, setDpr] = React.useState(1);\n  const [canvasSize, setCanvasSize] = React.useState({\n    width: 800,\n    height: 600,\n  });\n\n  const readColor = React.useCallback(() => {\n    const el = containerRef.current;\n    if (!el) return '#ffffff';\n    const cs = getComputedStyle(el);\n    return cs.color || '#ffffff';\n  }, []);\n\n  const initStars = React.useCallback(\n    (w: number, h: number) => {\n      starsRef.current = Array.from({ length: starsCount }).map(() => {\n        const angle = Math.random() * Math.PI * 2;\n        const speed = movementSpeed * (0.5 + Math.random() * 0.5);\n        return {\n          x: Math.random() * w,\n          y: Math.random() * h,\n          vx: Math.cos(angle) * speed,\n          vy: Math.sin(angle) * speed,\n          size: Math.random() * starsSize + 1,\n          opacity: starsOpacity,\n          baseOpacity: starsOpacity,\n          mass: Math.random() * 0.5 + 0.5,\n          glowMultiplier: 1,\n          glowVelocity: 0,\n        };\n      });\n    },\n    [starsCount, movementSpeed, starsOpacity, starsSize],\n  );\n\n  const redistributeStars = React.useCallback((w: number, h: number) => {\n    starsRef.current.forEach((p) => {\n      p.x = Math.random() * w;\n      p.y = Math.random() * h;\n    });\n  }, []);\n\n  const resizeCanvas = React.useCallback(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n    const rect = container.getBoundingClientRect();\n    const nextDpr = Math.max(1, Math.min(window.devicePixelRatio || 1, 2));\n    setDpr(nextDpr);\n    canvas.width = Math.max(1, Math.floor(rect.width * nextDpr));\n    canvas.height = Math.max(1, Math.floor(rect.height * nextDpr));\n    canvas.style.width = `${rect.width}px`;\n    canvas.style.height = `${rect.height}px`;\n    setCanvasSize({ width: rect.width, height: rect.height });\n    if (starsRef.current.length === 0) {\n      initStars(rect.width, rect.height);\n    } else {\n      redistributeStars(rect.width, rect.height);\n    }\n  }, [initStars, redistributeStars]);\n\n  const handlePointerMove = React.useCallback(\n    (e: React.MouseEvent | React.TouchEvent) => {\n      const canvas = canvasRef.current;\n      if (!canvas) return;\n      const rect = canvas.getBoundingClientRect();\n      let clientX = 0;\n      let clientY = 0;\n      if ('touches' in e) {\n        const t = e.touches[0];\n        if (!t) return;\n        clientX = t.clientX;\n        clientY = t.clientY;\n      } else {\n        clientX = e.clientX;\n        clientY = e.clientY;\n      }\n      mouseRef.current = { x: clientX - rect.left, y: clientY - rect.top };\n    },\n    [],\n  );\n\n  const updateStars = React.useCallback(() => {\n    const w = canvasSize.width;\n    const h = canvasSize.height;\n    const mouse = mouseRef.current;\n\n    for (let i = 0; i < starsRef.current.length; i++) {\n      const p = starsRef.current[i];\n\n      const dx = mouse.x - p.x;\n      const dy = mouse.y - p.y;\n      const dist = Math.hypot(dx, dy);\n\n      if (dist < mouseInfluence && dist > 0) {\n        const force = (mouseInfluence - dist) / mouseInfluence;\n        const nx = dx / dist;\n        const ny = dy / dist;\n        const g = force * (gravityStrength * 0.001);\n\n        if (mouseGravity === 'attract') {\n          p.vx += nx * g;\n          p.vy += ny * g;\n        } else if (mouseGravity === 'repel') {\n          p.vx -= nx * g;\n          p.vy -= ny * g;\n        }\n\n        p.opacity = Math.min(1, p.baseOpacity + force * 0.4);\n\n        const targetGlow = 1 + force * 2;\n        const currentGlow = p.glowMultiplier || 1;\n\n        if (glowAnimation === 'instant') {\n          p.glowMultiplier = targetGlow;\n        } else if (glowAnimation === 'ease') {\n          const ease = 0.15;\n          p.glowMultiplier = currentGlow + (targetGlow - currentGlow) * ease;\n        } else {\n          const spring = (targetGlow - currentGlow) * 0.2;\n          const damping = 0.85;\n          p.glowVelocity = (p.glowVelocity || 0) * damping + spring;\n          p.glowMultiplier = currentGlow + (p.glowVelocity || 0);\n        }\n      } else {\n        p.opacity = Math.max(p.baseOpacity * 0.3, p.opacity - 0.02);\n        const targetGlow = 1;\n        const currentGlow = p.glowMultiplier || 1;\n        if (glowAnimation === 'instant') {\n          p.glowMultiplier = targetGlow;\n        } else if (glowAnimation === 'ease') {\n          const ease = 0.08;\n          p.glowMultiplier = Math.max(\n            1,\n            currentGlow + (targetGlow - currentGlow) * ease,\n          );\n        } else {\n          const spring = (targetGlow - currentGlow) * 0.15;\n          const damping = 0.9;\n          p.glowVelocity = (p.glowVelocity || 0) * damping + spring;\n          p.glowMultiplier = Math.max(1, currentGlow + (p.glowVelocity || 0));\n        }\n      }\n\n      if (starsInteraction) {\n        for (let j = i + 1; j < starsRef.current.length; j++) {\n          const o = starsRef.current[j];\n          const dx2 = o.x - p.x;\n          const dy2 = o.y - p.y;\n          const d = Math.hypot(dx2, dy2);\n          const minD = p.size + o.size + 5;\n          if (d < minD && d > 0) {\n            if (starsInteractionType === 'bounce') {\n              const nx = dx2 / d;\n              const ny = dy2 / d;\n              const rvx = p.vx - o.vx;\n              const rvy = p.vy - o.vy;\n              const speed = rvx * nx + rvy * ny;\n              if (speed < 0) continue;\n              const impulse = (2 * speed) / (p.mass + o.mass);\n              p.vx -= impulse * o.mass * nx;\n              p.vy -= impulse * o.mass * ny;\n              o.vx += impulse * p.mass * nx;\n              o.vy += impulse * p.mass * ny;\n              const overlap = minD - d;\n              const sx = nx * overlap * 0.5;\n              const sy = ny * overlap * 0.5;\n              p.x -= sx;\n              p.y -= sy;\n              o.x += sx;\n              o.y += sy;\n            } else {\n              const mergeForce = (minD - d) / minD;\n              p.glowMultiplier = (p.glowMultiplier || 1) + mergeForce * 0.5;\n              o.glowMultiplier = (o.glowMultiplier || 1) + mergeForce * 0.5;\n              const af = mergeForce * 0.01;\n              p.vx += dx2 * af;\n              p.vy += dy2 * af;\n              o.vx -= dx2 * af;\n              o.vy -= dy2 * af;\n            }\n          }\n        }\n      }\n\n      p.x += p.vx;\n      p.y += p.vy;\n\n      p.vx += (Math.random() - 0.5) * 0.001;\n      p.vy += (Math.random() - 0.5) * 0.001;\n\n      p.vx *= 0.999;\n      p.vy *= 0.999;\n\n      if (p.x < 0) p.x = w;\n      if (p.x > w) p.x = 0;\n      if (p.y < 0) p.y = h;\n      if (p.y > h) p.y = 0;\n    }\n  }, [\n    canvasSize.width,\n    canvasSize.height,\n    mouseInfluence,\n    mouseGravity,\n    gravityStrength,\n    glowAnimation,\n    starsInteraction,\n    starsInteractionType,\n  ]);\n\n  const drawStars = React.useCallback(\n    (ctx: CanvasRenderingContext2D) => {\n      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);\n      const color = readColor();\n      for (const p of starsRef.current) {\n        ctx.save();\n        ctx.shadowColor = color;\n        ctx.shadowBlur = glowIntensity * (p.glowMultiplier || 1) * 2;\n        ctx.globalAlpha = p.opacity;\n        ctx.fillStyle = color;\n        ctx.beginPath();\n        ctx.arc(p.x * dpr, p.y * dpr, p.size * dpr, 0, Math.PI * 2);\n        ctx.fill();\n        ctx.restore();\n      }\n    },\n    [dpr, glowIntensity, readColor],\n  );\n\n  const animate = React.useCallback(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n    updateStars();\n    drawStars(ctx);\n    animRef.current = requestAnimationFrame(animate);\n  }, [updateStars, drawStars]);\n\n  React.useEffect(() => {\n    resizeCanvas();\n    const container = containerRef.current;\n    const ro =\n      typeof ResizeObserver !== 'undefined'\n        ? new ResizeObserver(resizeCanvas)\n        : null;\n    if (container && ro) ro.observe(container);\n    const onResize = () => resizeCanvas();\n    window.addEventListener('resize', onResize);\n    return () => {\n      window.removeEventListener('resize', onResize);\n      if (ro && container) ro.disconnect();\n    };\n  }, [resizeCanvas]);\n\n  React.useEffect(() => {\n    if (starsRef.current.length === 0) {\n      initStars(canvasSize.width, canvasSize.height);\n    } else {\n      starsRef.current.forEach((p) => {\n        p.baseOpacity = starsOpacity;\n        p.opacity = starsOpacity;\n        const spd = Math.hypot(p.vx, p.vy);\n        if (spd > 0) {\n          const ratio = movementSpeed / spd;\n          p.vx *= ratio;\n          p.vy *= ratio;\n        }\n      });\n    }\n  }, [\n    starsCount,\n    starsOpacity,\n    movementSpeed,\n    canvasSize.width,\n    canvasSize.height,\n    initStars,\n  ]);\n\n  React.useEffect(() => {\n    if (animRef.current) cancelAnimationFrame(animRef.current);\n    animRef.current = requestAnimationFrame(animate);\n    return () => {\n      if (animRef.current) cancelAnimationFrame(animRef.current);\n      animRef.current = null;\n    };\n  }, [animate]);\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"gravity-stars-background\"\n      className={cn('relative size-full overflow-hidden', className)}\n      onMouseMove={(e) => handlePointerMove(e)}\n      onTouchMove={(e) => handlePointerMove(e)}\n      {...props}\n    >\n      <canvas ref={canvasRef} className=\"block w-full h-full\" />\n    </div>\n  );\n}\n\nexport { GravityStarsBackground, type GravityStarsProps };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/components/backgrounds/gravity-stars.tsx"
    }
  ],
  "type": "registry:ui"
}