{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-backgrounds-fireworks",
  "title": "Fireworks Background",
  "description": "A background component that displays a fireworks animation.",
  "files": [
    {
      "path": "registry/components/backgrounds/fireworks/index.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst rand = (min: number, max: number): number =>\n  Math.random() * (max - min) + min;\n\nconst randInt = (min: number, max: number): number =>\n  Math.floor(Math.random() * (max - min) + min);\n\nconst randColor = (): string => `hsl(${randInt(0, 360)}, 100%, 50%)`;\n\ntype ParticleType = {\n  x: number;\n  y: number;\n  color: string;\n  speed: number;\n  direction: number;\n  vx: number;\n  vy: number;\n  gravity: number;\n  friction: number;\n  alpha: number;\n  decay: number;\n  size: number;\n  update: () => void;\n  draw: (ctx: CanvasRenderingContext2D) => void;\n  isAlive: () => boolean;\n};\n\nfunction createParticle(\n  x: number,\n  y: number,\n  color: string,\n  speed: number,\n  direction: number,\n  gravity: number,\n  friction: number,\n  size: number,\n): ParticleType {\n  const vx = Math.cos(direction) * speed;\n  const vy = Math.sin(direction) * speed;\n  const alpha = 1;\n  const decay = rand(0.005, 0.02);\n\n  return {\n    x,\n    y,\n    color,\n    speed,\n    direction,\n    vx,\n    vy,\n    gravity,\n    friction,\n    alpha,\n    decay,\n    size,\n    update() {\n      this.vx *= this.friction;\n      this.vy *= this.friction;\n      this.vy += this.gravity;\n      this.x += this.vx;\n      this.y += this.vy;\n      this.alpha -= this.decay;\n    },\n    draw(ctx: CanvasRenderingContext2D) {\n      ctx.save();\n      ctx.globalAlpha = this.alpha;\n      ctx.beginPath();\n      ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);\n      ctx.fillStyle = this.color;\n      ctx.fill();\n      ctx.restore();\n    },\n    isAlive() {\n      return this.alpha > 0;\n    },\n  };\n}\n\ntype FireworkType = {\n  x: number;\n  y: number;\n  targetY: number;\n  color: string;\n  speed: number;\n  size: number;\n  angle: number;\n  vx: number;\n  vy: number;\n  trail: { x: number; y: number }[];\n  trailLength: number;\n  exploded: boolean;\n  update: () => boolean;\n  explode: () => void;\n  draw: (ctx: CanvasRenderingContext2D) => void;\n};\n\nfunction createFirework(\n  x: number,\n  y: number,\n  targetY: number,\n  color: string,\n  speed: number,\n  size: number,\n  particleSpeed: { min: number; max: number } | number,\n  particleSize: { min: number; max: number } | number,\n  onExplode: (particles: ParticleType[]) => void,\n): FireworkType {\n  const angle = -Math.PI / 2 + rand(-0.3, 0.3);\n  const vx = Math.cos(angle) * speed;\n  const vy = Math.sin(angle) * speed;\n  const trail: { x: number; y: number }[] = [];\n  const trailLength = randInt(10, 25);\n\n  return {\n    x,\n    y,\n    targetY,\n    color,\n    speed,\n    size,\n    angle,\n    vx,\n    vy,\n    trail,\n    trailLength,\n    exploded: false,\n    update() {\n      this.trail.push({ x: this.x, y: this.y });\n      if (this.trail.length > this.trailLength) {\n        this.trail.shift();\n      }\n      this.x += this.vx;\n      this.y += this.vy;\n      this.vy += 0.02;\n      if (this.vy >= 0 || this.y <= this.targetY) {\n        this.explode();\n        return false;\n      }\n      return true;\n    },\n    explode() {\n      const numParticles = randInt(50, 150);\n      const particles: ParticleType[] = [];\n      for (let i = 0; i < numParticles; i++) {\n        const particleAngle = rand(0, Math.PI * 2);\n        const localParticleSpeed = getValueByRange(particleSpeed);\n        const localParticleSize = getValueByRange(particleSize);\n        particles.push(\n          createParticle(\n            this.x,\n            this.y,\n            this.color,\n            localParticleSpeed,\n            particleAngle,\n            0.05,\n            0.98,\n            localParticleSize,\n          ),\n        );\n      }\n      onExplode(particles);\n    },\n    draw(ctx: CanvasRenderingContext2D) {\n      ctx.save();\n      ctx.beginPath();\n      if (this.trail.length > 1) {\n        ctx.moveTo(this.trail[0]?.x ?? this.x, this.trail[0]?.y ?? this.y);\n        for (const point of this.trail) {\n          ctx.lineTo(point.x, point.y);\n        }\n      } else {\n        ctx.moveTo(this.x, this.y);\n        ctx.lineTo(this.x, this.y);\n      }\n      ctx.strokeStyle = this.color;\n      ctx.lineWidth = this.size;\n      ctx.lineCap = 'round';\n      ctx.stroke();\n      ctx.restore();\n    },\n  };\n}\n\nfunction getValueByRange(range: { min: number; max: number } | number): number {\n  if (typeof range === 'number') {\n    return range;\n  }\n  return rand(range.min, range.max);\n}\n\nfunction getColor(color: string | string[] | undefined): string {\n  if (Array.isArray(color)) {\n    return color[randInt(0, color.length)] ?? randColor();\n  }\n  return color ?? randColor();\n}\n\ntype FireworksBackgroundProps = Omit<React.ComponentProps<'div'>, 'color'> & {\n  canvasProps?: React.ComponentProps<'canvas'>;\n  population?: number;\n  color?: string | string[];\n  fireworkSpeed?: { min: number; max: number } | number;\n  fireworkSize?: { min: number; max: number } | number;\n  particleSpeed?: { min: number; max: number } | number;\n  particleSize?: { min: number; max: number } | number;\n};\n\nfunction FireworksBackground({\n  ref,\n  className,\n  canvasProps,\n  population = 1,\n  color,\n  fireworkSpeed = { min: 4, max: 8 },\n  fireworkSize = { min: 2, max: 5 },\n  particleSpeed = { min: 2, max: 7 },\n  particleSize = { min: 1, max: 5 },\n  ...props\n}: FireworksBackgroundProps) {\n  const canvasRef = React.useRef<HTMLCanvasElement>(null);\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  React.useImperativeHandle(ref, () => containerRef.current as HTMLDivElement);\n\n  React.useEffect(() => {\n    const canvas = canvasRef.current;\n    const container = containerRef.current;\n    if (!canvas || !container) return;\n    const ctx = canvas.getContext('2d');\n    if (!ctx) return;\n\n    let maxX = window.innerWidth;\n    let ratio = container.offsetHeight / container.offsetWidth;\n    let maxY = maxX * ratio;\n    canvas.width = maxX;\n    canvas.height = maxY;\n\n    const setCanvasSize = () => {\n      maxX = window.innerWidth;\n      ratio = container.offsetHeight / container.offsetWidth;\n      maxY = maxX * ratio;\n      canvas.width = maxX;\n      canvas.height = maxY;\n    };\n    window.addEventListener('resize', setCanvasSize);\n\n    const explosions: ParticleType[] = [];\n    const fireworks: FireworkType[] = [];\n\n    const handleExplosion = (particles: ParticleType[]) => {\n      explosions.push(...particles);\n    };\n\n    const launchFirework = () => {\n      const x = rand(maxX * 0.1, maxX * 0.9);\n      const y = maxY;\n      const targetY = rand(maxY * 0.1, maxY * 0.4);\n      const fireworkColor = getColor(color);\n      const speed = getValueByRange(fireworkSpeed);\n      const size = getValueByRange(fireworkSize);\n      fireworks.push(\n        createFirework(\n          x,\n          y,\n          targetY,\n          fireworkColor,\n          speed,\n          size,\n          particleSpeed,\n          particleSize,\n          handleExplosion,\n        ),\n      );\n      const timeout = rand(300, 800) / population;\n      setTimeout(launchFirework, timeout);\n    };\n\n    launchFirework();\n\n    let animationFrameId: number;\n    const animate = () => {\n      ctx.clearRect(0, 0, maxX, maxY);\n\n      for (let i = fireworks.length - 1; i >= 0; i--) {\n        const firework = fireworks[i];\n        if (!firework?.update()) {\n          fireworks.splice(i, 1);\n        } else {\n          firework.draw(ctx);\n        }\n      }\n\n      for (let i = explosions.length - 1; i >= 0; i--) {\n        const particle = explosions[i];\n        particle?.update();\n        if (particle?.isAlive()) {\n          particle.draw(ctx);\n        } else {\n          explosions.splice(i, 1);\n        }\n      }\n\n      animationFrameId = requestAnimationFrame(animate);\n    };\n\n    animate();\n\n    const handleClick = (event: MouseEvent) => {\n      const x = event.clientX;\n      const y = maxY;\n      const targetY = event.clientY;\n      const fireworkColor = getColor(color);\n      const speed = getValueByRange(fireworkSpeed);\n      const size = getValueByRange(fireworkSize);\n      fireworks.push(\n        createFirework(\n          x,\n          y,\n          targetY,\n          fireworkColor,\n          speed,\n          size,\n          particleSpeed,\n          particleSize,\n          handleExplosion,\n        ),\n      );\n    };\n\n    container.addEventListener('click', handleClick);\n\n    return () => {\n      window.removeEventListener('resize', setCanvasSize);\n      container.removeEventListener('click', handleClick);\n      cancelAnimationFrame(animationFrameId);\n    };\n  }, [\n    population,\n    color,\n    fireworkSpeed,\n    fireworkSize,\n    particleSpeed,\n    particleSize,\n  ]);\n\n  return (\n    <div\n      ref={containerRef}\n      data-slot=\"fireworks-background\"\n      className={cn('relative size-full overflow-hidden', className)}\n      {...props}\n    >\n      <canvas\n        {...canvasProps}\n        ref={canvasRef}\n        className={cn('absolute inset-0 size-full', canvasProps?.className)}\n      />\n    </div>\n  );\n}\n\nexport { FireworksBackground, type FireworksBackgroundProps };\n",
      "type": "registry:ui",
      "target": "components/animate-ui/components/backgrounds/fireworks.tsx"
    }
  ],
  "type": "registry:ui"
}