{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "backgrounds-fireworks",
  "type": "registry:ui",
  "title": "Fireworks Background",
  "description": "A background component that displays a fireworks animation.",
  "dependencies": [
    "@animate-ui/react",
    "cn",
    "motion"
  ],
  "files": [
    {
      "path": "registry/backgrounds/fireworks/index.tsx",
      "type": "registry:ui",
      "target": "@components/animate-ui/backgrounds/fireworks.tsx",
      "content": "'use client';\n\nimport * as React from 'react';\nimport { useInView } from 'motion/react';\n\nimport { usePageVisible } from '@animate-ui/react/utils/use-page-visible';\nimport {\n  ReducedMotionConfig,\n  useShouldReduceMotion,\n} from '@animate-ui/react/utils/motion';\nimport { cn } from 'cn';\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.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    },\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 FireworksBackgroundContent({\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  const inView = useInView(containerRef);\n  const pageVisible = usePageVisible();\n  const reduce = useShouldReduceMotion();\n  const playing = inView && pageVisible && !reduce;\n  const stillKey = reduce\n    ? JSON.stringify([color, particleSpeed, particleSize])\n    : null;\n\n  const optionsRef = React.useRef({\n    population,\n    color,\n    fireworkSpeed,\n    fireworkSize,\n    particleSpeed,\n    particleSize,\n  });\n  React.useEffect(() => {\n    optionsRef.current = {\n      population,\n      color,\n      fireworkSpeed,\n      fireworkSize,\n      particleSpeed,\n      particleSize,\n    };\n  });\n\n  const engineRef = React.useRef<{\n    start: () => void;\n    stop: () => void;\n    still: () => void;\n  } | null>(null);\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 = 0;\n    let maxY = 0;\n    let scale = 1;\n\n    const explosions: ParticleType[] = [];\n    const fireworks: FireworkType[] = [];\n\n    let running = false;\n    let frameId = 0;\n    let launchTimeoutId: ReturnType<typeof setTimeout> | undefined;\n\n    const clear = () => {\n      ctx.setTransform(1, 0, 0, 1, 0, 0);\n      ctx.clearRect(0, 0, canvas.width, canvas.height);\n      ctx.setTransform(scale, 0, 0, scale, 0, 0);\n      ctx.globalAlpha = 1;\n    };\n\n    const render = () => {\n      clear();\n      for (let i = fireworks.length - 1; i >= 0; i--) fireworks[i]?.draw(ctx);\n      for (let i = explosions.length - 1; i >= 0; i--) explosions[i]?.draw(ctx);\n    };\n\n    const setCanvasSize = () => {\n      const width = container.offsetWidth;\n      const height = container.offsetHeight;\n      if (!width || !height) return;\n      const dpr = Math.min(window.devicePixelRatio || 1, 2);\n      maxX = window.innerWidth;\n      maxY = maxX * (height / width);\n      canvas.width = Math.max(1, Math.round(width * dpr));\n      canvas.height = Math.max(1, Math.round(height * dpr));\n      scale = canvas.width / maxX;\n      if (!running) render();\n    };\n    setCanvasSize();\n    window.addEventListener('resize', setCanvasSize);\n    const resizeObserver = new ResizeObserver(setCanvasSize);\n    resizeObserver.observe(container);\n\n    const handleExplosion = (particles: ParticleType[]) => {\n      explosions.push(...particles);\n    };\n\n    const addFirework = (x: number, targetY: number) => {\n      const options = optionsRef.current;\n      fireworks.push(\n        createFirework(\n          x,\n          maxY,\n          targetY,\n          getColor(options.color),\n          getValueByRange(options.fireworkSpeed),\n          getValueByRange(options.fireworkSize),\n          options.particleSpeed,\n          options.particleSize,\n          handleExplosion,\n        ),\n      );\n    };\n\n    const launchFirework = () => {\n      const { population } = optionsRef.current;\n      if (population > 0)\n        addFirework(rand(maxX * 0.1, maxX * 0.9), rand(maxY * 0.1, maxY * 0.4));\n      const timeout = rand(300, 800) / (population > 0 ? population : 1);\n      launchTimeoutId = setTimeout(launchFirework, timeout);\n    };\n\n    const animate = () => {\n      clear();\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      frameId = requestAnimationFrame(animate);\n    };\n\n    const start = () => {\n      if (running) return;\n      running = true;\n      launchFirework();\n      animate();\n    };\n\n    const stop = () => {\n      running = false;\n      clearTimeout(launchTimeoutId);\n      cancelAnimationFrame(frameId);\n    };\n\n    const still = () => {\n      if (running) return;\n      fireworks.length = 0;\n      explosions.length = 0;\n      const options = optionsRef.current;\n      for (let i = 0; i < 3; i++) {\n        const firework = createFirework(\n          maxX * (0.25 + i * 0.25),\n          maxY * rand(0.25, 0.45),\n          0,\n          getColor(options.color),\n          0,\n          0,\n          options.particleSpeed,\n          options.particleSize,\n          handleExplosion,\n        );\n        firework.explode();\n      }\n      for (let i = 0; i < 25; i++) {\n        for (const particle of explosions) particle.update();\n      }\n      render();\n    };\n\n    const handleClick = (event: MouseEvent) => {\n      if (!running) return;\n      const rect = container.getBoundingClientRect();\n      const unit = maxX / rect.width;\n      addFirework(\n        (event.clientX - rect.left) * unit,\n        (event.clientY - rect.top) * unit,\n      );\n    };\n    container.addEventListener('click', handleClick);\n\n    engineRef.current = { start, stop, still };\n\n    return () => {\n      stop();\n      engineRef.current = null;\n      window.removeEventListener('resize', setCanvasSize);\n      resizeObserver.disconnect();\n      container.removeEventListener('click', handleClick);\n    };\n  }, []);\n\n  React.useEffect(() => {\n    const engine = engineRef.current;\n    if (!engine) return;\n    if (reduce) {\n      engine.still();\n      return;\n    }\n    if (!playing) return;\n    engine.start();\n    return engine.stop;\n  }, [playing, reduce, stillKey]);\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        aria-hidden=\"true\"\n        {...canvasProps}\n        ref={canvasRef}\n        className={cn('absolute inset-0 size-full', canvasProps?.className)}\n      />\n    </div>\n  );\n}\n\nfunction FireworksBackground(props: FireworksBackgroundProps) {\n  return (\n    <ReducedMotionConfig>\n      <FireworksBackgroundContent {...props} />\n    </ReducedMotionConfig>\n  );\n}\n\nexport { FireworksBackground, type FireworksBackgroundProps };\n"
    }
  ]
}