{
  "name": "confetti-burst",
  "type": "registry:ui",
  "description": "Frame-driven confetti burst overlay",
  "dependencies": [
    "remotion"
  ],
  "registryDependencies": [
    "motion-tokens"
  ],
  "files": [
    {
      "path": "registry/bases/default/primitives/confetti-burst.tsx",
      "type": "registry:ui",
      "content": "import { interpolate, random, useCurrentFrame, useVideoConfig } from \"remotion\";\nimport { DURATION, EASING } from \"@/remotion/lib/motion-tokens\";\n\nexport type ConfettiBurstProps = {\n  count?: number;\n  originX?: number;\n  originY?: number;\n  spread?: number;\n  seed?: string;\n  colors?: string[];\n  durationInFrames?: number;\n};\n\nconst DEFAULT_COLORS = [\"#e8b86d\", \"#2dd4bf\", \"#f472b6\", \"#f59e0b\", \"#fafafa\"];\n\ntype Particle = {\n  angle: number;\n  speed: number;\n  spin: number;\n  size: number;\n  color: string;\n  wobble: number;\n  shape: \"rect\" | \"circle\";\n  popDelay: number;\n};\n\nfunction createParticles(count: number, spread: number, colors: string[], seed: string): Particle[] {\n  return Array.from({ length: count }, (_, index) => {\n    const angle = -Math.PI / 2 + (random(`${seed}-angle-${index}`) - 0.5) * spread;\n    const speed = 220 + random(`${seed}-speed-${index}`) * 380;\n\n    return {\n      angle,\n      speed,\n      spin: (random(`${seed}-spin-${index}`) - 0.5) * 720,\n      size: 6 + random(`${seed}-size-${index}`) * 10,\n      color: colors[index % colors.length],\n      wobble: random(`${seed}-wobble-${index}`) * 40,\n      shape: random(`${seed}-shape-${index}`) > 0.5 ? \"circle\" : \"rect\",\n      popDelay: random(`${seed}-pop-${index}`) * 3,\n    };\n  });\n}\n\nexport const ConfettiBurst: React.FC<ConfettiBurstProps> = ({\n  count = 48,\n  originX = 50,\n  originY = 42,\n  spread = Math.PI * 0.9,\n  seed = \"confetti\",\n  colors = DEFAULT_COLORS,\n  durationInFrames = DURATION.slow,\n}) => {\n  const frame = useCurrentFrame();\n  const { width, height, fps } = useVideoConfig();\n  const particles = createParticles(count, spread, colors, seed);\n  const gravity = 680;\n  const drag = 1.6;\n  const time = frame / fps;\n  const fade = interpolate(frame, [durationInFrames * 0.55, durationInFrames], [1, 0], {\n    extrapolateLeft: \"clamp\",\n    extrapolateRight: \"clamp\",\n    easing: EASING.exit,\n  });\n\n  const originPxX = (originX / 100) * width;\n  const originPxY = (originY / 100) * height;\n\n  return (\n    <svg\n      style={{\n        position: \"absolute\",\n        inset: 0,\n        overflow: \"visible\",\n        pointerEvents: \"none\",\n      }}\n    >\n      {particles.map((particle, index) => {\n        const vx = Math.cos(particle.angle) * particle.speed;\n        const vy = Math.sin(particle.angle) * particle.speed;\n        // Air drag decays outward velocity over time; gravity still integrates linearly.\n        const settle = (1 - Math.exp(-drag * time)) / drag;\n        const x = originPxX + vx * settle + Math.sin(frame / 6 + particle.wobble) * particle.wobble * 0.15;\n        const y = originPxY + vy * settle + 0.5 * gravity * time * time;\n        const rotation = particle.spin * time;\n\n        const pop = interpolate(frame, [particle.popDelay, particle.popDelay + 5], [0, 1], {\n          extrapolateLeft: \"clamp\",\n          extrapolateRight: \"clamp\",\n          easing: EASING.pop,\n        });\n        const opacity = y > height + 40 ? 0 : fade * pop * (0.55 + (index % 3) * 0.12);\n        const size = particle.size * pop;\n\n        if (particle.shape === \"circle\") {\n          return (\n            <circle\n              key={index}\n              cx={x}\n              cy={y}\n              r={size * 0.5}\n              fill={particle.color}\n              opacity={opacity}\n            />\n          );\n        }\n\n        return (\n          <rect\n            key={index}\n            x={x}\n            y={y}\n            width={size}\n            height={size * 0.55}\n            fill={particle.color}\n            opacity={opacity}\n            transform={`rotate(${rotation} ${x} ${y})`}\n            rx={1}\n          />\n        );\n      })}\n    </svg>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "atoms",
    "drive": "time",
    "tier": "advanced"
  }
}