{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "live-waveform",
	"title": "Live Waveform",
	"type": "registry:ui",
	"description": "A real-time audio waveform visualizer driven by a frequency array.",
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\n\texport type LiveWaveformProps = HTMLAttributes<HTMLDivElement> & {\n\t\t/**\n\t\t * When `true`, requests microphone access and drives the waveform from\n\t\t * live audio input. Toggling off stops the stream and closes the\n\t\t * audio context.\n\t\t * @default false\n\t\t */\n\t\tactive?: boolean;\n\t\t/**\n\t\t * When `true` (and `active` is `false`), renders an animated placeholder\n\t\t * wave pattern. Use this to signal a processing or awaiting state.\n\t\t * @default false\n\t\t */\n\t\tprocessing?: boolean;\n\t\t/**\n\t\t * Specific `MediaDeviceInfo.deviceId` to capture from. Omit to use the\n\t\t * default microphone.\n\t\t */\n\t\tdeviceId?: string;\n\t\t/**\n\t\t * Width of each bar in pixels.\n\t\t * @default 3\n\t\t */\n\t\tbarWidth?: number;\n\t\t/**\n\t\t * Minimum bar height in pixels. Bars are drawn at least this tall even\n\t\t * when their value is near zero.\n\t\t * @default 4\n\t\t */\n\t\tbarHeight?: number;\n\t\t/**\n\t\t * Gap between bars in pixels.\n\t\t * @default 1\n\t\t */\n\t\tbarGap?: number;\n\t\t/**\n\t\t * Corner radius applied to each bar.\n\t\t * @default 1.5\n\t\t */\n\t\tbarRadius?: number;\n\t\t/**\n\t\t * Custom bar color. Falls back to the canvas's computed `color` when\n\t\t * unset.\n\t\t */\n\t\tbarColor?: string;\n\t\t/**\n\t\t * Fade the left and right edges of the waveform via a destination-out\n\t\t * gradient mask.\n\t\t * @default true\n\t\t */\n\t\tfadeEdges?: boolean;\n\t\t/**\n\t\t * Width of the edge fade region in pixels.\n\t\t * @default 24\n\t\t */\n\t\tfadeWidth?: number;\n\t\t/**\n\t\t * Height of the waveform container. Numbers are treated as pixels;\n\t\t * strings are passed through as a CSS length.\n\t\t * @default 64\n\t\t */\n\t\theight?: string | number;\n\t\t/**\n\t\t * Amplitude multiplier applied to normalized frequency data before\n\t\t * rendering. Higher values make quiet sounds visible.\n\t\t * @default 1\n\t\t */\n\t\tsensitivity?: number;\n\t\t/**\n\t\t * Smoothing factor forwarded to the underlying `AnalyserNode` in\n\t\t * `[0, 1]`. Higher values produce smoother transitions.\n\t\t * @default 0.8\n\t\t */\n\t\tsmoothingTimeConstant?: number;\n\t\t/**\n\t\t * FFT size forwarded to the underlying `AnalyserNode`. Must be a power\n\t\t * of two.\n\t\t * @default 256\n\t\t */\n\t\tfftSize?: number;\n\t\t/**\n\t\t * Maximum number of samples retained in scrolling mode.\n\t\t * @default 60\n\t\t */\n\t\thistorySize?: number;\n\t\t/**\n\t\t * Minimum interval in milliseconds between audio samples.\n\t\t * @default 30\n\t\t */\n\t\tupdateRate?: number;\n\t\t/**\n\t\t * `\"static\"` renders a symmetric frequency-band visualization; `\"scrolling\"`\n\t\t * renders the volume average as a timeline that scrolls right-to-left.\n\t\t * @default \"static\"\n\t\t */\n\t\tmode?: \"scrolling\" | \"static\";\n\t\t/** Called when microphone setup fails (e.g. permission denied). */\n\t\tonError?: (error: Error) => void;\n\t\t/** Called with the captured `MediaStream` once the microphone is ready. */\n\t\tonStreamReady?: (stream: MediaStream) => void;\n\t\t/** Called when the stream is stopped or `active` flips back to `false`. */\n\t\tonStreamEnd?: () => void;\n\t};\n\n\tlet {\n\t\tactive = false,\n\t\tprocessing = false,\n\t\tdeviceId,\n\t\tbarWidth = 3,\n\t\tbarGap = 1,\n\t\tbarRadius = 1.5,\n\t\tbarColor,\n\t\tfadeEdges = true,\n\t\tfadeWidth = 24,\n\t\tbarHeight: baseBarHeight = 4,\n\t\theight = 64,\n\t\tsensitivity = 1,\n\t\tsmoothingTimeConstant = 0.8,\n\t\tfftSize = 256,\n\t\thistorySize = 60,\n\t\tupdateRate = 30,\n\t\tmode = \"static\",\n\t\tonError,\n\t\tonStreamReady,\n\t\tonStreamEnd,\n\t\tclass: className,\n\t\t...restProps\n\t}: LiveWaveformProps = $props();\n\n\tlet canvasEl: HTMLCanvasElement | null = $state(null);\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\n\t// Non-reactive imperative refs (match React `useRef` usage).\n\tlet historyRef: number[] = [];\n\tlet analyserRef: AnalyserNode | null = null;\n\tlet audioContextRef: AudioContext | null = null;\n\tlet streamRef: MediaStream | null = null;\n\tlet animationRef: number | null = null;\n\tlet lastUpdateRef = 0;\n\tlet processingAnimationRef: number | null = null;\n\tlet fadeAnimationRef: number | null = null;\n\tlet lastActiveDataRef: number[] = [];\n\tlet transitionProgressRef = 0;\n\tlet staticBarsRef: number[] = [];\n\tlet needsRedrawRef = true;\n\tlet gradientCacheRef: CanvasGradient | null = null;\n\tlet lastWidthRef = 0;\n\n\tconst heightStyle = $derived(typeof height === \"number\" ? `${height}px` : height);\n\n\t// Effect 1: Canvas resize observer (runs once on mount).\n\t$effect(() => {\n\t\tconst canvas = canvasEl;\n\t\tconst container = containerEl;\n\t\tif (!canvas || !container) return;\n\n\t\tconst resizeObserver = new ResizeObserver(() => {\n\t\t\tconst rect = container.getBoundingClientRect();\n\t\t\tconst dpr = window.devicePixelRatio || 1;\n\n\t\t\tcanvas.width = rect.width * dpr;\n\t\t\tcanvas.height = rect.height * dpr;\n\t\t\tcanvas.style.width = `${rect.width}px`;\n\t\t\tcanvas.style.height = `${rect.height}px`;\n\n\t\t\tconst ctx = canvas.getContext(\"2d\");\n\t\t\tif (ctx) {\n\t\t\t\tctx.scale(dpr, dpr);\n\t\t\t}\n\n\t\t\tgradientCacheRef = null;\n\t\t\tlastWidthRef = rect.width;\n\t\t\tneedsRedrawRef = true;\n\t\t});\n\n\t\tresizeObserver.observe(container);\n\t\treturn () => resizeObserver.disconnect();\n\t});\n\n\t// Effect 2: Processing animation / idle fade-out.\n\t$effect(() => {\n\t\tconst _processing = processing;\n\t\tconst _active = active;\n\t\tconst _barWidth = barWidth;\n\t\tconst _barGap = barGap;\n\t\tconst _mode = mode;\n\n\t\tif (_processing && !_active) {\n\t\t\tlet time = 0;\n\t\t\ttransitionProgressRef = 0;\n\n\t\t\tconst animateProcessing = () => {\n\t\t\t\ttime += 0.03;\n\t\t\t\ttransitionProgressRef = Math.min(1, transitionProgressRef + 0.02);\n\n\t\t\t\tconst processingData: number[] = [];\n\t\t\t\tconst barCount = Math.floor(\n\t\t\t\t\t(containerEl?.getBoundingClientRect().width || 200) / (_barWidth + _barGap)\n\t\t\t\t);\n\n\t\t\t\tif (_mode === \"static\") {\n\t\t\t\t\tconst halfCount = Math.floor(barCount / 2);\n\n\t\t\t\t\tfor (let i = 0; i < barCount; i++) {\n\t\t\t\t\t\tconst normalizedPosition = (i - halfCount) / halfCount;\n\t\t\t\t\t\tconst centerWeight = 1 - Math.abs(normalizedPosition) * 0.4;\n\n\t\t\t\t\t\tconst wave1 = Math.sin(time * 1.5 + normalizedPosition * 3) * 0.25;\n\t\t\t\t\t\tconst wave2 = Math.sin(time * 0.8 - normalizedPosition * 2) * 0.2;\n\t\t\t\t\t\tconst wave3 = Math.cos(time * 2 + normalizedPosition) * 0.15;\n\t\t\t\t\t\tconst combinedWave = wave1 + wave2 + wave3;\n\t\t\t\t\t\tconst processingValue = (0.2 + combinedWave) * centerWeight;\n\n\t\t\t\t\t\tlet finalValue = processingValue;\n\t\t\t\t\t\tif (lastActiveDataRef.length > 0 && transitionProgressRef < 1) {\n\t\t\t\t\t\t\tconst lastDataIndex = Math.min(i, lastActiveDataRef.length - 1);\n\t\t\t\t\t\t\tconst lastValue = lastActiveDataRef[lastDataIndex] || 0;\n\t\t\t\t\t\t\tfinalValue =\n\t\t\t\t\t\t\t\tlastValue * (1 - transitionProgressRef) + processingValue * transitionProgressRef;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tprocessingData.push(Math.max(0.05, Math.min(1, finalValue)));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tfor (let i = 0; i < barCount; i++) {\n\t\t\t\t\t\tconst normalizedPosition = (i - barCount / 2) / (barCount / 2);\n\t\t\t\t\t\tconst centerWeight = 1 - Math.abs(normalizedPosition) * 0.4;\n\n\t\t\t\t\t\tconst wave1 = Math.sin(time * 1.5 + i * 0.15) * 0.25;\n\t\t\t\t\t\tconst wave2 = Math.sin(time * 0.8 - i * 0.1) * 0.2;\n\t\t\t\t\t\tconst wave3 = Math.cos(time * 2 + i * 0.05) * 0.15;\n\t\t\t\t\t\tconst combinedWave = wave1 + wave2 + wave3;\n\t\t\t\t\t\tconst processingValue = (0.2 + combinedWave) * centerWeight;\n\n\t\t\t\t\t\tlet finalValue = processingValue;\n\t\t\t\t\t\tif (lastActiveDataRef.length > 0 && transitionProgressRef < 1) {\n\t\t\t\t\t\t\tconst lastDataIndex = Math.floor((i / barCount) * lastActiveDataRef.length);\n\t\t\t\t\t\t\tconst lastValue = lastActiveDataRef[lastDataIndex] || 0;\n\t\t\t\t\t\t\tfinalValue =\n\t\t\t\t\t\t\t\tlastValue * (1 - transitionProgressRef) + processingValue * transitionProgressRef;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tprocessingData.push(Math.max(0.05, Math.min(1, finalValue)));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (_mode === \"static\") {\n\t\t\t\t\tstaticBarsRef = processingData;\n\t\t\t\t} else {\n\t\t\t\t\thistoryRef = processingData;\n\t\t\t\t}\n\n\t\t\t\tneedsRedrawRef = true;\n\t\t\t\tprocessingAnimationRef = requestAnimationFrame(animateProcessing);\n\t\t\t};\n\n\t\t\tanimateProcessing();\n\n\t\t\treturn () => {\n\t\t\t\tif (processingAnimationRef !== null) {\n\t\t\t\t\tcancelAnimationFrame(processingAnimationRef);\n\t\t\t\t\tprocessingAnimationRef = null;\n\t\t\t\t}\n\t\t\t};\n\t\t} else if (!_active && !_processing) {\n\t\t\tconst hasData = _mode === \"static\" ? staticBarsRef.length > 0 : historyRef.length > 0;\n\n\t\t\tif (hasData) {\n\t\t\t\tlet fadeProgress = 0;\n\t\t\t\tconst fadeToIdle = () => {\n\t\t\t\t\tfadeProgress += 0.03;\n\t\t\t\t\tif (fadeProgress < 1) {\n\t\t\t\t\t\tif (_mode === \"static\") {\n\t\t\t\t\t\t\tstaticBarsRef = staticBarsRef.map((value) => value * (1 - fadeProgress));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thistoryRef = historyRef.map((value) => value * (1 - fadeProgress));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tneedsRedrawRef = true;\n\t\t\t\t\t\tfadeAnimationRef = requestAnimationFrame(fadeToIdle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (_mode === \"static\") {\n\t\t\t\t\t\t\tstaticBarsRef = [];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\thistoryRef = [];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfadeAnimationRef = null;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tfadeAnimationRef = requestAnimationFrame(fadeToIdle);\n\n\t\t\t\treturn () => {\n\t\t\t\t\tif (fadeAnimationRef !== null) {\n\t\t\t\t\t\tcancelAnimationFrame(fadeAnimationRef);\n\t\t\t\t\t\tfadeAnimationRef = null;\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t});\n\n\t// Effect 3: Microphone setup/teardown.\n\t$effect(() => {\n\t\tconst _active = active;\n\t\tconst _deviceId = deviceId;\n\t\tconst _fftSize = fftSize;\n\t\tconst _smoothingTimeConstant = smoothingTimeConstant;\n\t\tconst _onError = onError;\n\t\tconst _onStreamReady = onStreamReady;\n\t\tconst _onStreamEnd = onStreamEnd;\n\n\t\tif (!_active) {\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t\tstreamRef = null;\n\t\t\t\t_onStreamEnd?.();\n\t\t\t}\n\t\t\tif (audioContextRef && audioContextRef.state !== \"closed\") {\n\t\t\t\taudioContextRef.close();\n\t\t\t\taudioContextRef = null;\n\t\t\t}\n\t\t\tif (animationRef !== null) {\n\t\t\t\tcancelAnimationFrame(animationRef);\n\t\t\t\tanimationRef = null;\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tlet cancelled = false;\n\n\t\tconst setupMicrophone = async () => {\n\t\t\ttry {\n\t\t\t\tconst stream = await navigator.mediaDevices.getUserMedia({\n\t\t\t\t\taudio: _deviceId\n\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\tdeviceId: { exact: _deviceId },\n\t\t\t\t\t\t\t\techoCancellation: true,\n\t\t\t\t\t\t\t\tnoiseSuppression: true,\n\t\t\t\t\t\t\t\tautoGainControl: true,\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\techoCancellation: true,\n\t\t\t\t\t\t\t\tnoiseSuppression: true,\n\t\t\t\t\t\t\t\tautoGainControl: true,\n\t\t\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tif (cancelled) {\n\t\t\t\t\tstream.getTracks().forEach((track) => track.stop());\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tstreamRef = stream;\n\t\t\t\t_onStreamReady?.(stream);\n\n\t\t\t\tconst AudioContextCtor =\n\t\t\t\t\twindow.AudioContext ||\n\t\t\t\t\t(window as unknown as { webkitAudioContext: typeof AudioContext }).webkitAudioContext;\n\t\t\t\tconst audioContext = new AudioContextCtor();\n\t\t\t\tconst analyser = audioContext.createAnalyser();\n\t\t\t\tanalyser.fftSize = _fftSize;\n\t\t\t\tanalyser.smoothingTimeConstant = _smoothingTimeConstant;\n\n\t\t\t\tconst source = audioContext.createMediaStreamSource(stream);\n\t\t\t\tsource.connect(analyser);\n\n\t\t\t\taudioContextRef = audioContext;\n\t\t\t\tanalyserRef = analyser;\n\n\t\t\t\t// Clear history when starting.\n\t\t\t\thistoryRef = [];\n\t\t\t} catch (error) {\n\t\t\t\t_onError?.(error as Error);\n\t\t\t}\n\t\t};\n\n\t\tsetupMicrophone();\n\n\t\treturn () => {\n\t\t\tcancelled = true;\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t\tstreamRef = null;\n\t\t\t\t_onStreamEnd?.();\n\t\t\t}\n\t\t\tif (audioContextRef && audioContextRef.state !== \"closed\") {\n\t\t\t\taudioContextRef.close();\n\t\t\t\taudioContextRef = null;\n\t\t\t}\n\t\t\tif (animationRef !== null) {\n\t\t\t\tcancelAnimationFrame(animationRef);\n\t\t\t\tanimationRef = null;\n\t\t\t}\n\t\t};\n\t});\n\n\t// Effect 4: Main render RAF loop.\n\t$effect(() => {\n\t\tconst canvas = canvasEl;\n\t\tif (!canvas) return;\n\n\t\tconst ctx = canvas.getContext(\"2d\");\n\t\tif (!ctx) return;\n\n\t\tconst _active = active;\n\t\tconst _processing = processing;\n\t\tconst _sensitivity = sensitivity;\n\t\tconst _updateRate = updateRate;\n\t\tconst _historySize = historySize;\n\t\tconst _barWidth = barWidth;\n\t\tconst _baseBarHeight = baseBarHeight;\n\t\tconst _barGap = barGap;\n\t\tconst _barRadius = barRadius;\n\t\tconst _barColor = barColor;\n\t\tconst _fadeEdges = fadeEdges;\n\t\tconst _fadeWidth = fadeWidth;\n\t\tconst _mode = mode;\n\t\t// Reference processing so auto-tracking re-runs when it flips.\n\t\tvoid _processing;\n\n\t\tlet rafId: number;\n\n\t\tconst animate = (currentTime: number) => {\n\t\t\tconst rect = canvas.getBoundingClientRect();\n\n\t\t\t// Update audio data if active.\n\t\t\tif (_active && currentTime - lastUpdateRef > _updateRate) {\n\t\t\t\tlastUpdateRef = currentTime;\n\n\t\t\t\tif (analyserRef) {\n\t\t\t\t\tconst dataArray = new Uint8Array(analyserRef.frequencyBinCount);\n\t\t\t\t\tanalyserRef.getByteFrequencyData(dataArray);\n\n\t\t\t\t\tif (_mode === \"static\") {\n\t\t\t\t\t\t// Static mode: symmetric frequency bands, fixed positions.\n\t\t\t\t\t\tconst startFreq = Math.floor(dataArray.length * 0.05);\n\t\t\t\t\t\tconst endFreq = Math.floor(dataArray.length * 0.4);\n\t\t\t\t\t\tconst relevantData = dataArray.slice(startFreq, endFreq);\n\n\t\t\t\t\t\tconst barCount = Math.floor(rect.width / (_barWidth + _barGap));\n\t\t\t\t\t\tconst halfCount = Math.floor(barCount / 2);\n\t\t\t\t\t\tconst newBars: number[] = [];\n\n\t\t\t\t\t\t// Mirror the data for symmetric display.\n\t\t\t\t\t\tfor (let i = halfCount - 1; i >= 0; i--) {\n\t\t\t\t\t\t\tconst dataIndex = Math.floor((i / halfCount) * relevantData.length);\n\t\t\t\t\t\t\tconst value = Math.min(1, (relevantData[dataIndex] / 255) * _sensitivity);\n\t\t\t\t\t\t\tnewBars.push(Math.max(0.05, value));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfor (let i = 0; i < halfCount; i++) {\n\t\t\t\t\t\t\tconst dataIndex = Math.floor((i / halfCount) * relevantData.length);\n\t\t\t\t\t\t\tconst value = Math.min(1, (relevantData[dataIndex] / 255) * _sensitivity);\n\t\t\t\t\t\t\tnewBars.push(Math.max(0.05, value));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstaticBarsRef = newBars;\n\t\t\t\t\t\tlastActiveDataRef = newBars;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Scrolling mode: running average, scrolls right-to-left.\n\t\t\t\t\t\tlet sum = 0;\n\t\t\t\t\t\tconst startFreq = Math.floor(dataArray.length * 0.05);\n\t\t\t\t\t\tconst endFreq = Math.floor(dataArray.length * 0.4);\n\t\t\t\t\t\tconst relevantData = dataArray.slice(startFreq, endFreq);\n\n\t\t\t\t\t\tfor (let i = 0; i < relevantData.length; i++) {\n\t\t\t\t\t\t\tsum += relevantData[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t\tconst average = (sum / relevantData.length / 255) * _sensitivity;\n\n\t\t\t\t\t\thistoryRef.push(Math.min(1, Math.max(0.05, average)));\n\t\t\t\t\t\tlastActiveDataRef = [...historyRef];\n\n\t\t\t\t\t\tif (historyRef.length > _historySize) {\n\t\t\t\t\t\t\thistoryRef.shift();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tneedsRedrawRef = true;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Skip redraw if nothing changed and we're idle.\n\t\t\tif (!needsRedrawRef && !_active) {\n\t\t\t\trafId = requestAnimationFrame(animate);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tneedsRedrawRef = _active;\n\t\t\tctx.clearRect(0, 0, rect.width, rect.height);\n\n\t\t\tconst computedBarColor =\n\t\t\t\t_barColor ||\n\t\t\t\t(() => {\n\t\t\t\t\tconst style = getComputedStyle(canvas);\n\t\t\t\t\tconst color = style.color;\n\t\t\t\t\treturn color || \"#000\";\n\t\t\t\t})();\n\n\t\t\tconst step = _barWidth + _barGap;\n\t\t\tconst barCount = Math.floor(rect.width / step);\n\t\t\tconst centerY = rect.height / 2;\n\n\t\t\tif (_mode === \"static\") {\n\t\t\t\t// Static mode — bars in fixed positions.\n\t\t\t\tconst dataToRender = staticBarsRef;\n\n\t\t\t\tfor (let i = 0; i < barCount && i < dataToRender.length; i++) {\n\t\t\t\t\tconst value = dataToRender[i] || 0.1;\n\t\t\t\t\tconst x = i * step;\n\t\t\t\t\tconst barHeightPx = Math.max(_baseBarHeight, value * rect.height * 0.8);\n\t\t\t\t\tconst y = centerY - barHeightPx / 2;\n\n\t\t\t\t\tctx.fillStyle = computedBarColor;\n\t\t\t\t\tctx.globalAlpha = 0.4 + value * 0.6;\n\n\t\t\t\t\tif (_barRadius > 0) {\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.roundRect(x, y, _barWidth, barHeightPx, _barRadius);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tctx.fillRect(x, y, _barWidth, barHeightPx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Scrolling mode — bars drawn right-to-left from history tail.\n\t\t\t\tfor (let i = 0; i < barCount && i < historyRef.length; i++) {\n\t\t\t\t\tconst dataIndex = historyRef.length - 1 - i;\n\t\t\t\t\tconst value = historyRef[dataIndex] || 0.1;\n\t\t\t\t\tconst x = rect.width - (i + 1) * step;\n\t\t\t\t\tconst barHeightPx = Math.max(_baseBarHeight, value * rect.height * 0.8);\n\t\t\t\t\tconst y = centerY - barHeightPx / 2;\n\n\t\t\t\t\tctx.fillStyle = computedBarColor;\n\t\t\t\t\tctx.globalAlpha = 0.4 + value * 0.6;\n\n\t\t\t\t\tif (_barRadius > 0) {\n\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\tctx.roundRect(x, y, _barWidth, barHeightPx, _barRadius);\n\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tctx.fillRect(x, y, _barWidth, barHeightPx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply edge fading via cached gradient + destination-out.\n\t\t\tif (_fadeEdges && _fadeWidth > 0 && rect.width > 0) {\n\t\t\t\tif (!gradientCacheRef || lastWidthRef !== rect.width) {\n\t\t\t\t\tconst gradient = ctx.createLinearGradient(0, 0, rect.width, 0);\n\t\t\t\t\tconst fadePercent = Math.min(0.3, _fadeWidth / rect.width);\n\n\t\t\t\t\tgradient.addColorStop(0, \"rgba(255,255,255,1)\");\n\t\t\t\t\tgradient.addColorStop(fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\t\tgradient.addColorStop(1 - fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\t\tgradient.addColorStop(1, \"rgba(255,255,255,1)\");\n\n\t\t\t\t\tgradientCacheRef = gradient;\n\t\t\t\t\tlastWidthRef = rect.width;\n\t\t\t\t}\n\n\t\t\t\tctx.globalCompositeOperation = \"destination-out\";\n\t\t\t\tctx.fillStyle = gradientCacheRef;\n\t\t\t\tctx.fillRect(0, 0, rect.width, rect.height);\n\t\t\t\tctx.globalCompositeOperation = \"source-over\";\n\t\t\t}\n\n\t\t\tctx.globalAlpha = 1;\n\n\t\t\trafId = requestAnimationFrame(animate);\n\t\t};\n\n\t\trafId = requestAnimationFrame(animate);\n\n\t\treturn () => {\n\t\t\tif (rafId) {\n\t\t\t\tcancelAnimationFrame(rafId);\n\t\t\t}\n\t\t};\n\t});\n</script>\n\n<div\n\tbind:this={containerEl}\n\t{...restProps}\n\tdata-slot=\"live-waveform\"\n\tclass={cn(\"relative h-full w-full\", className)}\n\tstyle:height={heightStyle}\n\taria-label={active\n\t\t? \"Live audio waveform\"\n\t\t: processing\n\t\t\t? \"Processing audio\"\n\t\t\t: \"Audio waveform idle\"}\n\trole=\"img\"\n>\n\t{#if !active && !processing}\n\t\t<div\n\t\t\tclass=\"border-muted-foreground/20 absolute top-1/2 right-0 left-0 -translate-y-1/2 border-t-2 border-dotted\"\n\t\t></div>\n\t{/if}\n\t<canvas bind:this={canvasEl} class=\"block h-full w-full\" aria-hidden=\"true\"></canvas>\n</div>\n",
			"type": "registry:ui",
			"target": "live-waveform/live-waveform.svelte"
		},
		{
			"content": "import Root from \"./live-waveform.svelte\";\n\nexport {\n\tRoot,\n\t//\n\tRoot as LiveWaveform,\n};\nexport type { LiveWaveformProps } from \"./live-waveform.svelte\";\n",
			"type": "registry:ui",
			"target": "live-waveform/index.ts"
		}
	]
}