{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "confetti",
  "title": "Confetti",
  "description": "Dependency-free confetti burst, fired from anywhere.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/confetti.tsx",
      "content": "\"use client\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface BurstOptions {\n  /** Origin in viewport px; defaults to screen center */\n  x?: number;\n  y?: number;\n  count?: number;\n  colors?: string[];\n}\n\nconst defaultColors = [\"#8b5cf6\", \"#d946ef\", \"#22d3ee\", \"#fbbf24\", \"#34d399\"];\n\n/**\n * Fire a confetti burst — no canvas library, ~2s, cleans up after itself.\n * No-op when the user prefers reduced motion.\n */\nexport function confettiBurst({\n  x = window.innerWidth / 2,\n  y = window.innerHeight / 2,\n  count = 90,\n  colors = defaultColors,\n}: BurstOptions = {}) {\n  if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n  const canvas = document.createElement(\"canvas\");\n  const dpr = Math.min(window.devicePixelRatio || 1, 2);\n  canvas.width = window.innerWidth * dpr;\n  canvas.height = window.innerHeight * dpr;\n  canvas.style.cssText =\n    \"position:fixed;inset:0;width:100vw;height:100vh;pointer-events:none;z-index:9999\";\n  document.body.appendChild(canvas);\n  const ctx = canvas.getContext(\"2d\")!;\n  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n\n  const particles = Array.from({ length: count }, () => {\n    const angle = Math.random() * Math.PI * 2;\n    const velocity = 6 + Math.random() * 8;\n    return {\n      x,\n      y,\n      vx: Math.cos(angle) * velocity,\n      vy: Math.sin(angle) * velocity - 4,\n      size: 4 + Math.random() * 5,\n      rotation: Math.random() * 360,\n      spin: (Math.random() - 0.5) * 20,\n      color: colors[Math.floor(Math.random() * colors.length)],\n      life: 1,\n    };\n  });\n\n  const start = performance.now();\n  const tick = (now: number) => {\n    const elapsed = (now - start) / 1000;\n    ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);\n    let alive = false;\n    for (const p of particles) {\n      p.x += p.vx;\n      p.y += p.vy;\n      p.vy += 0.25;\n      p.vx *= 0.99;\n      p.rotation += p.spin;\n      p.life = Math.max(0, 1 - elapsed / 1.8);\n      if (p.life <= 0) continue;\n      alive = true;\n      ctx.save();\n      ctx.translate(p.x, p.y);\n      ctx.rotate((p.rotation * Math.PI) / 180);\n      ctx.globalAlpha = p.life;\n      ctx.fillStyle = p.color;\n      ctx.fillRect(-p.size / 2, -p.size / 2, p.size, p.size * 0.6);\n      ctx.restore();\n    }\n    if (alive) {\n      requestAnimationFrame(tick);\n    } else {\n      canvas.remove();\n    }\n  };\n  requestAnimationFrame(tick);\n}\n\ninterface ConfettiButtonProps\n  extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n  children: React.ReactNode;\n}\n\nexport function ConfettiButton({\n  children,\n  className,\n  onClick,\n  ...props\n}: ConfettiButtonProps) {\n  return (\n    <button\n      data-slot=\"confetti-button\"\n      className={cn(\n        \"inline-flex h-11 cursor-pointer items-center justify-center gap-2 rounded-full border bg-card px-6 text-sm font-medium transition-transform hover:scale-[1.03] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring active:scale-[0.97]\",\n        className\n      )}\n      onClick={(e) => {\n        // Keyboard clicks (detail 0) have no pointer position; burst\n        // from the button's centre instead of the viewport corner\n        if (e.detail === 0) {\n          const rect = e.currentTarget.getBoundingClientRect();\n          confettiBurst({\n            x: rect.left + rect.width / 2,\n            y: rect.top + rect.height / 2,\n          });\n        } else {\n          confettiBurst({ x: e.clientX, y: e.clientY });\n        }\n        onClick?.(e);\n      }}\n      {...props}\n    >\n      {children}\n    </button>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/confetti.tsx"
    }
  ],
  "type": "registry:ui"
}