{
  "name": "audio-pulse",
  "type": "registry:ui",
  "description": "Audio-reactive pulse rings",
  "dependencies": [
    "remotion",
    "@remotion/media-utils"
  ],
  "registryDependencies": [
    "audio-viz-utils"
  ],
  "files": [
    {
      "path": "registry/bases/default/primitives/audio-pulse.tsx",
      "type": "registry:ui",
      "content": "import { bandEnergy, useAudioBands } from \"@/remotion/lib/audio-viz-utils\";\n\nexport type AudioPulseProps = {\n  src: string;\n  size?: number;\n  color?: string;\n  ringCount?: number;\n  sensitivity?: number;\n  /**\n   * Optional frame override.\n   * Pass a parent `frame` when using inside `<Sequence from={...}>` to avoid discontinuities.\n   */\n  frame?: number;\n};\n\nconst clamp01 = (value: number) => Math.min(1, Math.max(0, value));\n\nconst smoothstep = (t: number) => {\n  const x = clamp01(t);\n  return x * x * (3 - 2 * x);\n};\n\nconst lerp = (a: number, b: number, t: number) => a + (b - a) * t;\n\nexport const AudioPulse: React.FC<AudioPulseProps> = ({\n  src,\n  size = 240,\n  color = \"#e8b86d\",\n  ringCount = 3,\n  sensitivity = 1,\n  frame: frameOverride,\n}) => {\n  // 24 bands keeps the low end resolved enough to separate a kick from the\n  // body of the mix; the pulse only reads the bottom third of them.\n  const { bands, frame, fps } = useAudioBands({\n    src,\n    bandCount: 24,\n    frame: frameOverride,\n  });\n\n  const low = bandEnergy(bands, 0, 8);\n  const full = bandEnergy(bands);\n  // Weighted toward the bass so the orb pumps on the beat rather than on hats.\n  const raw = clamp01((low * 0.72 + full * 0.28) * sensitivity);\n\n  // Compress: keeps quiet passages visible without flattening the transients.\n  const intensity = smoothstep(Math.pow(raw, 0.78));\n  const idle = 0.5 + 0.5 * Math.sin((frame / fps) * Math.PI * 2 * 0.18);\n  const breathe = 0.03 * idle;\n\n  return (\n    <div style={{ width: size, height: size, position: \"relative\" }}>\n      <div\n        style={{\n          position: \"absolute\",\n          inset: \"-14%\",\n          borderRadius: \"50%\",\n          background: `radial-gradient(circle at 50% 50%, ${color}14 0%, transparent 62%)`,\n          filter: \"blur(10px)\",\n          opacity: 0.78 + intensity * 0.28,\n          scale: 0.96 + intensity * 0.06,\n          pointerEvents: \"none\",\n        }}\n      />\n      {Array.from({ length: ringCount }).map((_, index) => {\n        const offset = index / Math.max(1, ringCount);\n        const baseScale = 0.56 + offset * 0.25;\n        const phase = (frame / fps) * Math.PI * 2 * 0.42 + index * 1.7;\n        const wobble = Math.sin(phase) * (0.012 + offset * 0.01);\n        const scale =\n          baseScale + intensity * (0.28 + offset * 0.22) + breathe + wobble;\n        const ringOpacity = lerp(0.72, 0.22, offset) * (0.65 + intensity * 0.35);\n        const ringWidth = lerp(2.3, 1.35, offset);\n        const glow = lerp(10, 46, intensity) * (1 - offset * 0.55);\n        return (\n          <div\n            key={index}\n            style={{\n              position: \"absolute\",\n              inset: 0,\n              borderRadius: \"50%\",\n              border: `${ringWidth}px solid ${color}`,\n              opacity: ringOpacity,\n              scale,\n              boxShadow: `0 0 ${Math.round(glow)}px ${color}3a, 0 0 ${Math.round(\n                glow * 0.55,\n              )}px ${color}24`,\n              filter: `blur(${lerp(0, 0.55, offset)}px) saturate(1.05)`,\n            }}\n          />\n        );\n      })}\n      <div\n        style={{\n          position: \"absolute\",\n          inset: \"32%\",\n          borderRadius: \"50%\",\n          background: [\n            `radial-gradient(circle at 32% 30%, ${color} 0%, ${color}cc 48%, ${color}66 100%)`,\n            `conic-gradient(from ${Math.round(frame * 1.2)}deg, ${color}20, ${color}55, ${color}12, ${color}33, ${color}20)`,\n          ].join(\", \"),\n          opacity: 0.92 + intensity * 0.05,\n          scale: 0.78 + intensity * 0.34 + breathe * 0.75,\n          boxShadow: `0 0 ${Math.round(58 * intensity)}px ${color}55, inset 0 0 24px ${color}2b`,\n          filter: `saturate(${1.05 + intensity * 0.35})`,\n        }}\n      />\n      <div\n        style={{\n          position: \"absolute\",\n          inset: \"32%\",\n          borderRadius: \"50%\",\n          background:\n            \"radial-gradient(circle at 36% 32%, rgba(255,255,255,0.46) 0%, rgba(255,255,255,0) 52%)\",\n          opacity: 0.5 + intensity * 0.18,\n          scale: 0.92 + intensity * 0.08,\n          mixBlendMode: \"screen\",\n          pointerEvents: \"none\",\n        }}\n      />\n    </div>\n  );\n};\n"
    }
  ],
  "atlas": {
    "lane": "signals",
    "drive": "media",
    "tier": "advanced",
    "tags": [
      "audio"
    ]
  }
}