{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "confetti-button", "type": "registry:ui", "title": "Confetti Button", "description": "iOS 26 liquid-glass pill that erupts spring-physics confetti particles on click.", "dependencies": [ "motion" ], "files": [ { "path": "registry/ruixenui/confetti-button.tsx", "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { motion, AnimatePresence } from \"motion/react\";\n\n/* ── sound ── */\nlet _a: AudioContext, _b: AudioBuffer;\nconst tick = () => {\n if (typeof window === \"undefined\") return;\n if (!_a) {\n _a = new AudioContext();\n _b = _a.createBuffer(1, (_a.sampleRate * 0.003) | 0, _a.sampleRate);\n const d = _b.getChannelData(0);\n for (let i = 0; i < d.length; i++)\n d[i] = (Math.random() * 2 - 1) * (1 - i / d.length) ** 4;\n }\n const s = _a.createBufferSource();\n s.buffer = _b;\n const g = _a.createGain();\n g.gain.value = 0.08;\n s.connect(g).connect(_a.destination);\n s.start();\n};\n\n/* ── theme ── */\nconst CSS = `\n.cf{\n --cf-glass:linear-gradient(180deg,rgba(255,255,255,0.78),rgba(255,255,255,0.62));\n --cf-border:rgba(0,0,0,0.06);\n --cf-shadow:0 0 1px rgba(0,0,0,0.04),0 2px 8px rgba(0,0,0,0.04),inset 0 1px 0 rgba(255,255,255,0.8);\n --cf-hi:rgba(0,0,0,0.88)\n}\n.dark .cf,[data-theme=\"dark\"] .cf{\n --cf-glass:linear-gradient(180deg,rgba(255,255,255,0.05),rgba(255,255,255,0.02));\n --cf-border:rgba(255,255,255,0.07);\n --cf-shadow:0 1px 3px rgba(0,0,0,0.08),inset 0 1px 0 rgba(255,255,255,0.04);\n --cf-hi:rgba(255,255,255,0.88)\n}`;\n\n/* ── confetti colors ── */\nconst COLORS = [\n \"#FF6B6B\",\n \"#4ECDC4\",\n \"#45B7D1\",\n \"#96CEB4\",\n \"#FFEAA7\",\n \"#DDA0DD\",\n];\n\ninterface Particle {\n id: number;\n x: number;\n y: number;\n rotate: number;\n color: string;\n size: number;\n shape: \"circle\" | \"square\";\n}\n\n/* ── component ── */\nexport interface ConfettiButtonProps {\n label?: string;\n onClick?: () => void;\n sound?: boolean;\n style?: React.CSSProperties;\n}\n\nexport default function ConfettiButton({\n label = \"Celebrate\",\n onClick,\n sound = true,\n style,\n}: ConfettiButtonProps) {\n const [particles, setParticles] = React.useState([]);\n\n const fire = () => {\n const batch: Particle[] = Array.from({ length: 24 }, (_, i) => ({\n id: Date.now() + i,\n x: (Math.random() - 0.5) * 140,\n y: -(40 + Math.random() * 80),\n rotate: Math.random() * 540 - 270,\n color: COLORS[Math.floor(Math.random() * COLORS.length)],\n size: 4 + Math.random() * 4,\n shape: Math.random() > 0.5 ? \"circle\" : \"square\",\n }));\n setParticles(batch);\n if (sound) tick();\n onClick?.();\n setTimeout(() => setParticles([]), 900);\n };\n\n return (\n <>\n