{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "live-waveform",
  "type": "registry:ui",
  "description": "Canvas-based real-time waveform driven by a volume getter",
  "files": [
    {
      "path": "components/ui/LiveWaveform.tsx",
      "content": "import { useRef, useEffect, useCallback } from \"react\";\n\nexport interface LiveWaveformProps {\n  /** One or more functions returning volume (0–1). When multiple are given, the max is used. */\n  getVolume: (() => number) | (() => number)[];\n  /** Whether to animate. When false, renders a flat line. */\n  active?: boolean;\n  /** Waveform color. Default: uses --dg-va-primary CSS variable. */\n  color?: string;\n  /** Line width. Default: 2 */\n  lineWidth?: number;\n  className?: string;\n}\n\n/**\n * Canvas-based real-time waveform visualization.\n * Renders a smooth oscillating line driven by a volume source.\n *\n * @example\n * ```tsx\n * const mic = useAgentMicrophone();\n * // Pass a function that returns volume 0–1\n * <LiveWaveform getVolume={() => mic.getInputVolume()} active={mic.micActive} />\n * ```\n */\nexport function LiveWaveform({\n  getVolume,\n  active = true,\n  color,\n  lineWidth = 2,\n  className,\n}: LiveWaveformProps) {\n  const canvasRef = useRef<HTMLCanvasElement>(null);\n  const rafRef = useRef<number>(0);\n  const phaseRef = useRef(0);\n\n  const draw = useCallback(() => {\n    const canvas = canvasRef.current;\n    if (!canvas) return;\n\n    const ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n\n    const dpr = window.devicePixelRatio || 1;\n    const rect = canvas.getBoundingClientRect();\n    canvas.width = rect.width * dpr;\n    canvas.height = rect.height * dpr;\n    ctx.scale(dpr, dpr);\n\n    ctx.clearRect(0, 0, rect.width, rect.height);\n\n    const fns = Array.isArray(getVolume) ? getVolume : [getVolume];\n    const volume = active ? Math.max(...fns.map((fn) => fn())) : 0;\n    const amplitude = Math.min(1, volume * 2) * rect.height * 0.35;\n    const midY = rect.height / 2;\n\n    const style = getComputedStyle(canvas);\n    const strokeColor = color || style.getPropertyValue(\"--dg-va-primary\").trim() || \"#13EF93\";\n\n    ctx.strokeStyle = strokeColor;\n    ctx.lineWidth = lineWidth;\n    ctx.lineCap = \"round\";\n    ctx.lineJoin = \"round\";\n    ctx.beginPath();\n\n    phaseRef.current += 0.06;\n\n    for (let x = 0; x < rect.width; x++) {\n      const t = x / rect.width;\n      // Blend two sine waves for organic feel\n      const y = midY\n        + Math.sin(t * Math.PI * 4 + phaseRef.current) * amplitude * 0.6\n        + Math.sin(t * Math.PI * 7 + phaseRef.current * 1.3) * amplitude * 0.4;\n\n      if (x === 0) ctx.moveTo(x, y);\n      else ctx.lineTo(x, y);\n    }\n\n    ctx.stroke();\n\n    rafRef.current = requestAnimationFrame(draw);\n  }, [active, getVolume, color, lineWidth]);\n\n  useEffect(() => {\n    rafRef.current = requestAnimationFrame(draw);\n    return () => cancelAnimationFrame(rafRef.current);\n  }, [draw]);\n\n  return (\n    <canvas\n      ref={canvasRef}\n      className={className}\n      data-agent-live-waveform\n      style={{ width: \"100%\", height: \"100%\", display: \"block\" }}\n      aria-hidden=\"true\"\n    />\n  );\n}\n",
      "type": "registry:ui",
      "target": ""
    }
  ]
}
