{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "waveform",
	"title": "Waveform",
	"type": "registry:ui",
	"description": "A family of audio waveform renderers: static, recording, scrolling, scrubber, and microphone variants.",
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport type { HTMLAttributes } from \"svelte/elements\";\n\timport { cn } from \"$UTILS$.js\";\n\timport { getComputedBarColor, heightToCssSize } from \"./utils.js\";\n\n\texport type WaveformProps = HTMLAttributes<HTMLDivElement> & {\n\t\t/**\n\t\t * Array of normalized bar values in `[0, 1]`. The component samples from\n\t\t * this array to fill the available width.\n\t\t * @default []\n\t\t */\n\t\tdata?: number[];\n\t\t/**\n\t\t * Width of each bar in pixels.\n\t\t * @default 4\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 2\n\t\t */\n\t\tbarGap?: number;\n\t\t/**\n\t\t * Corner radius applied to each bar. Set to `0` for square bars.\n\t\t * @default 2\n\t\t */\n\t\tbarRadius?: number;\n\t\t/**\n\t\t * Custom bar color. Falls back to the canvas's computed `--foreground`\n\t\t * CSS variable when 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 128\n\t\t */\n\t\theight?: string | number;\n\t\t/**\n\t\t * Marks the waveform as actively capturing or rendering audio. Rendered\n\t\t * as `data-active` on the root element for CSS styling hooks.\n\t\t */\n\t\tactive?: boolean;\n\t\t/** Called when a bar is clicked with the data index and its value. */\n\t\tonBarClick?: (index: number, value: number) => void;\n\t};\n\n\tlet {\n\t\tdata = [],\n\t\tbarWidth = 4,\n\t\tbarHeight: baseBarHeight = 4,\n\t\tbarGap = 2,\n\t\tbarRadius = 2,\n\t\tbarColor,\n\t\tfadeEdges = true,\n\t\tfadeWidth = 24,\n\t\theight = 128,\n\t\tactive,\n\t\tonBarClick,\n\t\tclass: className,\n\t\t...restProps\n\t}: WaveformProps = $props();\n\n\tlet canvasEl: HTMLCanvasElement | null = $state(null);\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\n\tconst heightStyle = $derived(heightToCssSize(height));\n\n\t$effect(() => {\n\t\tconst canvas = canvasEl;\n\t\tconst container = containerEl;\n\t\tif (!canvas || !container) return;\n\n\t\t// Reactive dependency reads — re-runs the effect when any of these change.\n\t\tconst _data = data;\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\n\t\tconst renderWaveform = () => {\n\t\t\tconst ctx = canvas.getContext(\"2d\");\n\t\t\tif (!ctx) return;\n\n\t\t\tconst rect = canvas.getBoundingClientRect();\n\t\t\tctx.clearRect(0, 0, rect.width, rect.height);\n\n\t\t\tconst computedBarColor = getComputedBarColor(canvas, _barColor);\n\n\t\t\tconst barCount = Math.floor(rect.width / (_barWidth + _barGap));\n\t\t\tconst centerY = rect.height / 2;\n\n\t\t\tfor (let i = 0; i < barCount; i++) {\n\t\t\t\tconst dataIndex = Math.floor((i / barCount) * _data.length);\n\t\t\t\tconst value = _data[dataIndex] || 0;\n\t\t\t\tconst barHeightPx = Math.max(_baseBarHeight, value * rect.height * 0.8);\n\t\t\t\tconst x = i * (_barWidth + _barGap);\n\t\t\t\tconst y = centerY - barHeightPx / 2;\n\n\t\t\t\tctx.fillStyle = computedBarColor;\n\t\t\t\tctx.globalAlpha = 0.3 + value * 0.7;\n\n\t\t\t\tif (_barRadius > 0) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.roundRect(x, y, _barWidth, barHeightPx, _barRadius);\n\t\t\t\t\tctx.fill();\n\t\t\t\t} else {\n\t\t\t\t\tctx.fillRect(x, y, _barWidth, barHeightPx);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (_fadeEdges && _fadeWidth > 0 && rect.width > 0) {\n\t\t\t\tconst gradient = ctx.createLinearGradient(0, 0, rect.width, 0);\n\t\t\t\tconst fadePercent = Math.min(0.2, _fadeWidth / rect.width);\n\n\t\t\t\tgradient.addColorStop(0, \"rgba(255,255,255,1)\");\n\t\t\t\tgradient.addColorStop(fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1 - fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1, \"rgba(255,255,255,1)\");\n\n\t\t\t\tctx.globalCompositeOperation = \"destination-out\";\n\t\t\t\tctx.fillStyle = gradient;\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\t\t};\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\trenderWaveform();\n\t\t\t}\n\t\t});\n\n\t\tresizeObserver.observe(container);\n\t\trenderWaveform();\n\n\t\treturn () => resizeObserver.disconnect();\n\t});\n\n\tfunction handleClick(e: MouseEvent) {\n\t\tif (!onBarClick || !canvasEl) return;\n\n\t\tconst rect = canvasEl.getBoundingClientRect();\n\t\tconst x = e.clientX - rect.left;\n\t\tconst barIndex = Math.floor(x / (barWidth + barGap));\n\t\tconst dataIndex = Math.floor(\n\t\t\t(barIndex * data.length) / Math.floor(rect.width / (barWidth + barGap))\n\t\t);\n\n\t\tif (dataIndex >= 0 && dataIndex < data.length) {\n\t\t\tonBarClick(dataIndex, data[dataIndex]);\n\t\t}\n\t}\n</script>\n\n<div\n\tbind:this={containerEl}\n\tdata-slot=\"waveform\"\n\tdata-active={active ? \"\" : undefined}\n\tclass={cn(\"relative\", className)}\n\tstyle:height={heightStyle}\n\t{...restProps}\n>\n\t<canvas bind:this={canvasEl} class=\"block h-full w-full\" onclick={handleClick}></canvas>\n</div>\n",
			"type": "registry:ui",
			"target": "waveform/waveform.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from \"$UTILS$.js\";\n\timport { getComputedBarColor, heightToCssSize } from \"./utils.js\";\n\timport type { ScrollingWaveformProps } from \"./waveform-scrolling.svelte\";\n\n\texport type LiveMicrophoneWaveformProps = Omit<ScrollingWaveformProps, \"barCount\"> & {\n\t\tactive?: boolean;\n\t\tfftSize?: number;\n\t\tsmoothingTimeConstant?: number;\n\t\tsensitivity?: number;\n\t\tonError?: (error: Error) => void;\n\t\thistorySize?: number;\n\t\tupdateRate?: number;\n\t\tsavedHistoryRef?: { current: number[] };\n\t\tdragOffset?: number;\n\t\tsetDragOffset?: (offset: number) => void;\n\t\tenableAudioPlayback?: boolean;\n\t\tplaybackRate?: number;\n\t};\n\n\tlet {\n\t\tactive = false,\n\t\tfftSize = 256,\n\t\tsmoothingTimeConstant = 0.8,\n\t\tsensitivity = 1,\n\t\tonError,\n\t\thistorySize = 150,\n\t\tupdateRate = 50,\n\t\tbarWidth = 3,\n\t\tbarHeight: baseBarHeight = 4,\n\t\tbarGap = 1,\n\t\tbarRadius = 1,\n\t\tbarColor,\n\t\tfadeEdges = true,\n\t\tfadeWidth = 24,\n\t\theight = 128,\n\t\tclass: className,\n\t\tsavedHistoryRef,\n\t\tdragOffset: externalDragOffset,\n\t\tsetDragOffset: externalSetDragOffset,\n\t\tenableAudioPlayback = true,\n\t\tplaybackRate = 1,\n\t\t...restProps\n\t}: LiveMicrophoneWaveformProps = $props();\n\n\t// Reactive state\n\tlet internalDragOffset = $state(0);\n\tlet playbackPosition = $state<number | null>(null);\n\n\t// Element bindings\n\tlet canvasEl: HTMLCanvasElement | null = $state(null);\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\n\t// Non-reactive refs — plain objects/lets, not $state.\n\tconst internalHistoryRef = { current: [] as 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 dragStartXRef = 0;\n\tlet dragStartOffsetRef = 0;\n\tlet playbackStartTimeRef = 0;\n\n\tlet mediaRecorderRef: MediaRecorder | null = null;\n\tlet audioChunksRef: Blob[] = [];\n\tlet audioBufferRef: AudioBuffer | null = null;\n\tlet sourceNodeRef: AudioBufferSourceNode | null = null;\n\tlet scrubSourceRef: AudioBufferSourceNode | null = null;\n\n\t// Derived ref aliases — match React's `savedHistoryRef || internalHistoryRef` pattern.\n\tconst historyRef = $derived(savedHistoryRef ?? internalHistoryRef);\n\tconst dragOffset = $derived(externalDragOffset ?? internalDragOffset);\n\tconst setDragOffset = (offset: number) => {\n\t\tif (externalSetDragOffset) externalSetDragOffset(offset);\n\t\telse internalDragOffset = offset;\n\t};\n\n\tconst heightStyle = $derived(heightToCssSize(height));\n\n\t// --- Helpers ---\n\n\tasync function processAudioBlob(blob: Blob) {\n\t\ttry {\n\t\t\tconst arrayBuffer = await blob.arrayBuffer();\n\t\t\tif (audioContextRef) {\n\t\t\t\tconst audioBuffer = await audioContextRef.decodeAudioData(arrayBuffer);\n\t\t\t\taudioBufferRef = audioBuffer;\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"Error processing audio:\", error);\n\t\t}\n\t}\n\n\tfunction playScrubSound(position: number, direction: number) {\n\t\tif (!enableAudioPlayback || !audioBufferRef || !audioContextRef) return;\n\n\t\tif (scrubSourceRef) {\n\t\t\ttry {\n\t\t\t\tscrubSourceRef.stop();\n\t\t\t} catch {\n\t\t\t\t// ignore — source may already be stopped\n\t\t\t}\n\t\t}\n\n\t\tconst source = audioContextRef.createBufferSource();\n\t\tsource.buffer = audioBufferRef;\n\n\t\tconst speed = Math.abs(direction);\n\t\tconst rate = direction > 0 ? Math.min(3, 1 + speed * 0.1) : Math.max(-3, -1 - speed * 0.1);\n\n\t\tsource.playbackRate.value = rate;\n\n\t\tconst filter = audioContextRef.createBiquadFilter();\n\t\tfilter.type = \"lowpass\";\n\t\tfilter.frequency.value = Math.max(200, 2000 - speed * 100);\n\n\t\tsource.connect(filter);\n\t\tfilter.connect(audioContextRef.destination);\n\n\t\tconst startTime = Math.max(0, Math.min(position, audioBufferRef.duration - 0.1));\n\t\tsource.start(0, startTime, 0.1);\n\t\tscrubSourceRef = source;\n\t}\n\n\tfunction playFromPosition(position: number) {\n\t\tif (!enableAudioPlayback || !audioBufferRef || !audioContextRef) return;\n\n\t\tif (sourceNodeRef) {\n\t\t\ttry {\n\t\t\t\tsourceNodeRef.stop();\n\t\t\t} catch {\n\t\t\t\t// ignore — source may already be stopped\n\t\t\t}\n\t\t}\n\n\t\tconst source = audioContextRef.createBufferSource();\n\t\tsource.buffer = audioBufferRef;\n\t\tsource.playbackRate.value = playbackRate;\n\t\tsource.connect(audioContextRef.destination);\n\n\t\tconst startTime = Math.max(0, Math.min(position, audioBufferRef.duration));\n\t\tsource.start(0, startTime);\n\t\tsourceNodeRef = source;\n\n\t\tplaybackStartTimeRef = audioContextRef.currentTime - startTime;\n\t\tplaybackPosition = startTime;\n\n\t\tsource.onended = () => {\n\t\t\tplaybackPosition = null;\n\t\t};\n\t}\n\n\t// --- Effects ---\n\n\t// 1. ResizeObserver — 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\t\t});\n\n\t\tresizeObserver.observe(container);\n\t\treturn () => resizeObserver.disconnect();\n\t});\n\n\t// 2. Mic setup + MediaRecorder.\n\t$effect(() => {\n\t\tconst _active = active;\n\t\tconst _fftSize = fftSize;\n\t\tconst _smoothingTimeConstant = smoothingTimeConstant;\n\t\tconst _onError = onError;\n\t\tconst _enableAudioPlayback = enableAudioPlayback;\n\t\tconst _historyRef = historyRef;\n\n\t\tif (!_active) {\n\t\t\tif (mediaRecorderRef && mediaRecorderRef.state !== \"inactive\") {\n\t\t\t\tmediaRecorderRef.stop();\n\t\t\t}\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t}\n\t\t\tif (_enableAudioPlayback && audioChunksRef.length > 0) {\n\t\t\t\tconst audioBlob = new Blob(audioChunksRef, { type: \"audio/webm\" });\n\t\t\t\tprocessAudioBlob(audioBlob);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tsetDragOffset(0);\n\t\t_historyRef.current = [];\n\t\taudioChunksRef = [];\n\t\taudioBufferRef = null;\n\t\tplaybackPosition = null;\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({ audio: true });\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\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\tif (_enableAudioPlayback) {\n\t\t\t\t\tconst mediaRecorder = new MediaRecorder(stream);\n\t\t\t\t\tmediaRecorderRef = mediaRecorder;\n\n\t\t\t\t\tmediaRecorder.ondataavailable = (event) => {\n\t\t\t\t\t\tif (event.data.size > 0) {\n\t\t\t\t\t\t\taudioChunksRef.push(event.data);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tmediaRecorder.start(100);\n\t\t\t\t}\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 (mediaRecorderRef && mediaRecorderRef.state !== \"inactive\") {\n\t\t\t\tmediaRecorderRef.stop();\n\t\t\t}\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t}\n\t\t\tif (sourceNodeRef) {\n\t\t\t\ttry {\n\t\t\t\t\tsourceNodeRef.stop();\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (scrubSourceRef) {\n\t\t\t\ttry {\n\t\t\t\t\tscrubSourceRef.stop();\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n\n\t// 3. Playback visual sync — keyed on playbackPosition.\n\t$effect(() => {\n\t\tif (playbackPosition === null || !audioBufferRef) return;\n\n\t\tconst _playbackPosition = playbackPosition;\n\t\tconst _playbackRate = playbackRate;\n\t\tconst _barWidth = barWidth;\n\t\tconst _barGap = barGap;\n\t\tconst _historyRef = historyRef;\n\n\t\tlet animationId: number | null = null;\n\n\t\tconst updatePlaybackVisual = () => {\n\t\t\tif (audioContextRef && sourceNodeRef && audioBufferRef) {\n\t\t\t\tconst elapsed = audioContextRef.currentTime - playbackStartTimeRef;\n\t\t\t\tconst currentPos = _playbackPosition + elapsed * _playbackRate;\n\n\t\t\t\tif (currentPos < audioBufferRef.duration) {\n\t\t\t\t\tconst progressRatio = currentPos / audioBufferRef.duration;\n\t\t\t\t\tconst currentBarIndex = Math.floor(progressRatio * _historyRef.current.length);\n\t\t\t\t\tconst step = _barWidth + _barGap;\n\n\t\t\t\t\tconst containerWidth = containerEl?.getBoundingClientRect().width || 0;\n\t\t\t\t\tconst viewBars = Math.floor(containerWidth / step);\n\t\t\t\t\tconst targetOffset = -(currentBarIndex - (_historyRef.current.length - viewBars)) * step;\n\t\t\t\t\tconst clampedOffset = Math.max(\n\t\t\t\t\t\t-(_historyRef.current.length - viewBars) * step,\n\t\t\t\t\t\tMath.min(0, targetOffset)\n\t\t\t\t\t);\n\n\t\t\t\t\tsetDragOffset(clampedOffset);\n\t\t\t\t\tanimationId = requestAnimationFrame(updatePlaybackVisual);\n\t\t\t\t} else {\n\t\t\t\t\tplaybackPosition = null;\n\t\t\t\t\tconst step = _barWidth + _barGap;\n\t\t\t\t\tconst containerWidth = containerEl?.getBoundingClientRect().width || 0;\n\t\t\t\t\tconst viewBars = Math.floor(containerWidth / step);\n\t\t\t\t\tsetDragOffset(-(_historyRef.current.length - viewBars) * step);\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\n\t\tanimationId = requestAnimationFrame(updatePlaybackVisual);\n\n\t\treturn () => {\n\t\t\tif (animationId !== null) cancelAnimationFrame(animationId);\n\t\t};\n\t});\n\n\t// 4. Canvas render RAF loop.\n\t$effect(() => {\n\t\tconst canvas = canvasEl;\n\t\tif (!canvas) return;\n\n\t\tconst _active = active;\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 _dragOffset = dragOffset;\n\t\tconst _playbackPosition = playbackPosition;\n\t\tconst _historyRef = historyRef;\n\n\t\tif (!_active && _historyRef.current.length === 0 && _playbackPosition === null) return;\n\n\t\tconst ctx = canvas.getContext(\"2d\");\n\t\tif (!ctx) return;\n\n\t\tconst animate = (currentTime: number) => {\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\tlet sum = 0;\n\t\t\t\t\tfor (let i = 0; i < dataArray.length; i++) {\n\t\t\t\t\t\tsum += dataArray[i];\n\t\t\t\t\t}\n\t\t\t\t\tconst average = (sum / dataArray.length / 255) * _sensitivity;\n\n\t\t\t\t\t_historyRef.current.push(Math.min(1, Math.max(0.05, average)));\n\n\t\t\t\t\tif (_historyRef.current.length > _historySize) {\n\t\t\t\t\t\t_historyRef.current.shift();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rect = canvas.getBoundingClientRect();\n\t\t\tctx.clearRect(0, 0, rect.width, rect.height);\n\n\t\t\tconst computedBarColor = getComputedBarColor(canvas, _barColor);\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\tconst dataToRender = _historyRef.current;\n\n\t\t\tif (dataToRender.length > 0) {\n\t\t\t\tconst offsetInBars = Math.floor(_dragOffset / step);\n\n\t\t\t\tfor (let i = 0; i < barCount; i++) {\n\t\t\t\t\tlet dataIndex: number;\n\n\t\t\t\t\tif (_active) {\n\t\t\t\t\t\tdataIndex = dataToRender.length - 1 - i;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdataIndex = Math.max(\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tMath.min(\n\t\t\t\t\t\t\t\tdataToRender.length - 1,\n\t\t\t\t\t\t\t\tdataToRender.length - 1 - i - Math.floor(offsetInBars)\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (dataIndex >= 0 && dataIndex < dataToRender.length) {\n\t\t\t\t\t\tconst value = dataToRender[dataIndex];\n\t\t\t\t\t\tif (value !== undefined) {\n\t\t\t\t\t\t\tconst x = rect.width - (i + 1) * step;\n\t\t\t\t\t\t\tconst barHeightPx = Math.max(_baseBarHeight, value * rect.height * 0.7);\n\t\t\t\t\t\t\tconst y = centerY - barHeightPx / 2;\n\n\t\t\t\t\t\t\tctx.fillStyle = computedBarColor;\n\t\t\t\t\t\t\tctx.globalAlpha = 0.3 + value * 0.7;\n\n\t\t\t\t\t\t\tif (_barRadius > 0) {\n\t\t\t\t\t\t\t\tctx.beginPath();\n\t\t\t\t\t\t\t\tctx.roundRect(x, y, _barWidth, barHeightPx, _barRadius);\n\t\t\t\t\t\t\t\tctx.fill();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tctx.fillRect(x, y, _barWidth, barHeightPx);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (_fadeEdges && _fadeWidth > 0) {\n\t\t\t\tconst gradient = ctx.createLinearGradient(0, 0, rect.width, 0);\n\t\t\t\tconst fadePercent = Math.min(0.2, _fadeWidth / rect.width);\n\n\t\t\t\tgradient.addColorStop(0, \"rgba(255,255,255,1)\");\n\t\t\t\tgradient.addColorStop(fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1 - fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1, \"rgba(255,255,255,1)\");\n\n\t\t\t\tctx.globalCompositeOperation = \"destination-out\";\n\t\t\t\tctx.fillStyle = gradient;\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\tanimationRef = requestAnimationFrame(animate);\n\t\t};\n\n\t\tif (_active || _historyRef.current.length > 0 || _playbackPosition !== null) {\n\t\t\tanimationRef = requestAnimationFrame(animate);\n\t\t}\n\n\t\treturn () => {\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// --- Drag handling (replaces the isDragging useEffect) ---\n\n\tfunction handlePointerDown(event: PointerEvent) {\n\t\tif (active || historyRef.current.length === 0) return;\n\n\t\tevent.preventDefault();\n\t\tdragStartXRef = event.clientX;\n\t\tdragStartOffsetRef = dragOffset;\n\n\t\tlet lastScrubTime = 0;\n\t\tlet lastMouseX = dragStartXRef;\n\n\t\tconst handleMove = (moveEvent: PointerEvent) => {\n\t\t\tconst deltaX = moveEvent.clientX - dragStartXRef;\n\t\t\tconst newOffset = dragStartOffsetRef - deltaX * 0.5; // reduce sensitivity\n\n\t\t\tconst step = barWidth + barGap;\n\t\t\tconst maxBars = historyRef.current.length;\n\t\t\tconst viewWidth = canvasEl?.getBoundingClientRect().width || 0;\n\t\t\tconst viewBars = Math.floor(viewWidth / step);\n\n\t\t\tconst maxOffset = Math.max(0, (maxBars - viewBars) * step);\n\t\t\tconst minOffset = 0;\n\t\t\tconst clampedOffset = Math.max(minOffset, Math.min(maxOffset, newOffset));\n\n\t\t\tsetDragOffset(clampedOffset);\n\n\t\t\tconst now = Date.now();\n\t\t\tif (enableAudioPlayback && audioBufferRef && now - lastScrubTime > 50) {\n\t\t\t\tlastScrubTime = now;\n\t\t\t\tconst offsetBars = Math.floor(clampedOffset / step);\n\t\t\t\tconst rightmostBarIndex = Math.max(0, Math.min(maxBars - 1, maxBars - 1 - offsetBars));\n\t\t\t\tconst audioPosition = (rightmostBarIndex / maxBars) * audioBufferRef.duration;\n\t\t\t\tconst direction = moveEvent.clientX - lastMouseX;\n\t\t\t\tlastMouseX = moveEvent.clientX;\n\t\t\t\tplayScrubSound(\n\t\t\t\t\tMath.max(0, Math.min(audioBufferRef.duration - 0.1, audioPosition)),\n\t\t\t\t\tdirection\n\t\t\t\t);\n\t\t\t}\n\t\t};\n\n\t\tconst handleUp = () => {\n\t\t\tif (enableAudioPlayback && audioBufferRef) {\n\t\t\t\tconst step = barWidth + barGap;\n\t\t\t\tconst maxBars = historyRef.current.length;\n\t\t\t\tconst offsetBars = Math.floor(dragOffset / step);\n\t\t\t\tconst rightmostBarIndex = Math.max(0, Math.min(maxBars - 1, maxBars - 1 - offsetBars));\n\t\t\t\tconst audioPosition = (rightmostBarIndex / maxBars) * audioBufferRef.duration;\n\t\t\t\tplayFromPosition(Math.max(0, Math.min(audioBufferRef.duration - 0.1, audioPosition)));\n\t\t\t}\n\n\t\t\tif (scrubSourceRef) {\n\t\t\t\ttry {\n\t\t\t\t\tscrubSourceRef.stop();\n\t\t\t\t} catch {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twindow.removeEventListener(\"pointermove\", handleMove);\n\t\t\twindow.removeEventListener(\"pointerup\", handleUp);\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", handleMove);\n\t\twindow.addEventListener(\"pointerup\", handleUp, { once: true });\n\t}\n</script>\n\n<!-- svelte-ignore a11y_no_noninteractive_tabindex -->\n<div\n\tbind:this={containerEl}\n\tdata-slot=\"live-microphone-waveform\"\n\tclass={cn(\n\t\t\"relative flex items-center\",\n\t\t!active && historyRef.current.length > 0 && \"cursor-pointer\",\n\t\tclassName\n\t)}\n\trole={!active && historyRef.current.length > 0 ? \"slider\" : undefined}\n\taria-label={!active && historyRef.current.length > 0\n\t\t? \"Drag to scrub through recording\"\n\t\t: undefined}\n\taria-valuenow={!active && historyRef.current.length > 0 ? Math.abs(dragOffset) : undefined}\n\taria-valuemin={!active && historyRef.current.length > 0 ? 0 : undefined}\n\taria-valuemax={!active && historyRef.current.length > 0 ? historyRef.current.length : undefined}\n\ttabindex={!active && historyRef.current.length > 0 ? 0 : undefined}\n\tstyle:height={heightStyle}\n\tonpointerdown={handlePointerDown}\n\t{...restProps}\n>\n\t<canvas bind:this={canvasEl} class=\"block h-full w-full\"></canvas>\n</div>\n",
			"type": "registry:ui",
			"target": "waveform/waveform-live-microphone.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { untrack } from \"svelte\";\n\timport Waveform, { type WaveformProps } from \"./waveform.svelte\";\n\n\texport type MicrophoneWaveformProps = WaveformProps & {\n\t\tactive?: boolean;\n\t\tprocessing?: boolean;\n\t\tfftSize?: number;\n\t\tsmoothingTimeConstant?: number;\n\t\tsensitivity?: number;\n\t\tonError?: (error: Error) => void;\n\t};\n\n\tlet {\n\t\tactive = false,\n\t\tprocessing = false,\n\t\tfftSize = 256,\n\t\tsmoothingTimeConstant = 0.8,\n\t\tsensitivity = 1,\n\t\tonError,\n\t\t...restProps\n\t}: MicrophoneWaveformProps = $props();\n\n\tlet data: number[] = $state([]);\n\n\t// Non-reactive refs (match React `useRef` usage).\n\tlet analyserRef: AnalyserNode | null = null;\n\tlet audioContextRef: AudioContext | null = null;\n\tlet streamRef: MediaStream | null = null;\n\tlet animationIdRef: number | null = null;\n\tlet processingAnimationRef: number | null = null;\n\tlet lastActiveDataRef: number[] = [];\n\tlet transitionProgressRef = 0;\n\n\t// Processing / idle fade animation — keyed on [processing, active] in React.\n\t$effect(() => {\n\t\tconst _processing = processing;\n\t\tconst _active = active;\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 = 45;\n\n\t\t\t\tfor (let i = 0; i < barCount; i++) {\n\t\t\t\t\tconst normalizedPosition = (i - barCount / 2) / (barCount / 2);\n\t\t\t\t\tconst centerWeight = 1 - Math.abs(normalizedPosition) * 0.4;\n\n\t\t\t\t\tconst wave1 = Math.sin(time * 1.5 + i * 0.15) * 0.25;\n\t\t\t\t\tconst wave2 = Math.sin(time * 0.8 - i * 0.1) * 0.2;\n\t\t\t\t\tconst wave3 = Math.cos(time * 2 + i * 0.05) * 0.15;\n\t\t\t\t\tconst combinedWave = wave1 + wave2 + wave3;\n\t\t\t\t\tconst processingValue = (0.2 + combinedWave) * centerWeight;\n\n\t\t\t\t\tlet finalValue = processingValue;\n\t\t\t\t\tif (lastActiveDataRef.length > 0 && transitionProgressRef < 1) {\n\t\t\t\t\t\tconst lastDataIndex = Math.floor((i / barCount) * lastActiveDataRef.length);\n\t\t\t\t\t\tconst lastValue = lastActiveDataRef[lastDataIndex] || 0;\n\t\t\t\t\t\tfinalValue =\n\t\t\t\t\t\t\tlastValue * (1 - transitionProgressRef) + processingValue * transitionProgressRef;\n\t\t\t\t\t}\n\n\t\t\t\t\tprocessingData.push(Math.max(0.05, Math.min(1, finalValue)));\n\t\t\t\t}\n\n\t\t\t\tdata = processingData;\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 startData = untrack(() => data);\n\t\t\tif (startData.length > 0) {\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\tdata = startData.map((value) => value * (1 - fadeProgress));\n\t\t\t\t\t\trequestAnimationFrame(fadeToIdle);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdata = [];\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\tfadeToIdle();\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\t});\n\n\t// Mic capture — keyed on [active, fftSize, smoothingTimeConstant, sensitivity, onError].\n\t$effect(() => {\n\t\tconst _active = active;\n\t\tconst _fftSize = fftSize;\n\t\tconst _smoothingTimeConstant = smoothingTimeConstant;\n\t\tconst _sensitivity = sensitivity;\n\t\tconst _onError = onError;\n\n\t\tif (!_active) {\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t}\n\t\t\tif (audioContextRef && audioContextRef.state !== \"closed\") {\n\t\t\t\taudioContextRef.close();\n\t\t\t}\n\t\t\tif (animationIdRef !== null) {\n\t\t\t\tcancelAnimationFrame(animationIdRef);\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({ audio: true });\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\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\tconst dataArray = new Uint8Array(analyser.frequencyBinCount);\n\n\t\t\t\tconst updateData = () => {\n\t\t\t\t\tif (!analyserRef || cancelled) return;\n\n\t\t\t\t\tanalyserRef.getByteFrequencyData(dataArray);\n\n\t\t\t\t\tconst startFreq = Math.floor(dataArray.length * 0.05);\n\t\t\t\t\tconst endFreq = Math.floor(dataArray.length * 0.4);\n\t\t\t\t\tconst relevantData = dataArray.slice(startFreq, endFreq);\n\n\t\t\t\t\tconst halfLength = Math.floor(relevantData.length / 2);\n\t\t\t\t\tconst normalizedData: number[] = [];\n\n\t\t\t\t\tfor (let i = halfLength - 1; i >= 0; i--) {\n\t\t\t\t\t\tconst value = Math.min(1, (relevantData[i] / 255) * _sensitivity);\n\t\t\t\t\t\tnormalizedData.push(value);\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (let i = 0; i < halfLength; i++) {\n\t\t\t\t\t\tconst value = Math.min(1, (relevantData[i] / 255) * _sensitivity);\n\t\t\t\t\t\tnormalizedData.push(value);\n\t\t\t\t\t}\n\n\t\t\t\t\tdata = normalizedData;\n\t\t\t\t\tlastActiveDataRef = normalizedData;\n\n\t\t\t\t\tanimationIdRef = requestAnimationFrame(updateData);\n\t\t\t\t};\n\n\t\t\t\tupdateData();\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}\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 (animationIdRef !== null) {\n\t\t\t\tcancelAnimationFrame(animationIdRef);\n\t\t\t\tanimationIdRef = null;\n\t\t\t}\n\t\t};\n\t});\n</script>\n\n<Waveform {data} {...restProps} />\n",
			"type": "registry:ui",
			"target": "waveform/waveform-microphone.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from \"$UTILS$.js\";\n\timport { getComputedBarColor, heightToCssSize } from \"./utils.js\";\n\timport type { WaveformProps } from \"./waveform.svelte\";\n\n\texport type RecordingWaveformProps = Omit<WaveformProps, \"data\" | \"onBarClick\"> & {\n\t\trecording?: boolean;\n\t\tfftSize?: number;\n\t\tsmoothingTimeConstant?: number;\n\t\tsensitivity?: number;\n\t\tonError?: (error: Error) => void;\n\t\tonRecordingComplete?: (data: number[]) => void;\n\t\tupdateRate?: number;\n\t\tshowHandle?: boolean;\n\t};\n\n\tlet {\n\t\trecording = false,\n\t\tfftSize = 256,\n\t\tsmoothingTimeConstant = 0.8,\n\t\tsensitivity = 1,\n\t\tonError,\n\t\tonRecordingComplete,\n\t\tupdateRate = 50,\n\t\tshowHandle = true,\n\t\tbarWidth = 3,\n\t\tbarHeight: baseBarHeight = 4,\n\t\tbarGap = 1,\n\t\tbarRadius = 1,\n\t\tbarColor,\n\t\theight = 128,\n\t\tclass: className,\n\t\t...restProps\n\t}: RecordingWaveformProps = $props();\n\n\tlet recordedData: number[] = $state([]);\n\tlet viewPosition = $state(1);\n\tlet isRecordingComplete = $state(false);\n\n\tlet canvasEl: HTMLCanvasElement | null = $state(null);\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\n\t// Non-reactive refs.\n\tlet recordingDataRef: 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\n\tconst heightStyle = $derived(heightToCssSize(height));\n\n\t// ResizeObserver — 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\t\t});\n\n\t\tresizeObserver.observe(container);\n\t\treturn () => resizeObserver.disconnect();\n\t});\n\n\t// Mic setup — keyed on [recording, fftSize, smoothingTimeConstant, onError, onRecordingComplete].\n\t$effect(() => {\n\t\tconst _recording = recording;\n\t\tconst _fftSize = fftSize;\n\t\tconst _smoothingTimeConstant = smoothingTimeConstant;\n\t\tconst _onError = onError;\n\t\tconst _onRecordingComplete = onRecordingComplete;\n\n\t\tif (!_recording) {\n\t\t\tif (streamRef) {\n\t\t\t\tstreamRef.getTracks().forEach((track) => track.stop());\n\t\t\t}\n\t\t\tif (audioContextRef && audioContextRef.state !== \"closed\") {\n\t\t\t\taudioContextRef.close();\n\t\t\t}\n\n\t\t\tif (recordingDataRef.length > 0) {\n\t\t\t\trecordedData = [...recordingDataRef];\n\t\t\t\tisRecordingComplete = true;\n\t\t\t\t_onRecordingComplete?.(recordingDataRef);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tisRecordingComplete = false;\n\t\trecordingDataRef = [];\n\t\trecordedData = [];\n\t\tviewPosition = 1;\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({ audio: true });\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\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\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}\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\tanalyserRef = null;\n\t\t};\n\t});\n\n\t// Render RAF loop — re-runs on many deps.\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 _recording = recording;\n\t\tconst _recordedData = recordedData;\n\t\tconst _viewPosition = viewPosition;\n\t\tconst _isRecordingComplete = isRecordingComplete;\n\t\tconst _sensitivity = sensitivity;\n\t\tconst _updateRate = updateRate;\n\t\tconst _showHandle = showHandle;\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\n\t\tconst animate = (currentTime: number) => {\n\t\t\tif (_recording && 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\tlet sum = 0;\n\t\t\t\t\tfor (let i = 0; i < dataArray.length; i++) {\n\t\t\t\t\t\tsum += dataArray[i];\n\t\t\t\t\t}\n\t\t\t\t\tconst average = (sum / dataArray.length / 255) * _sensitivity;\n\n\t\t\t\t\trecordingDataRef.push(Math.min(1, Math.max(0.05, average)));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst rect = canvas.getBoundingClientRect();\n\t\t\tctx.clearRect(0, 0, rect.width, rect.height);\n\n\t\t\tconst computedBarColor = getComputedBarColor(canvas, _barColor);\n\n\t\t\tconst dataToRender = _recording ? recordingDataRef : _recordedData;\n\n\t\t\tif (dataToRender.length > 0) {\n\t\t\t\tconst step = _barWidth + _barGap;\n\t\t\t\tconst barsVisible = Math.floor(rect.width / step);\n\t\t\t\tconst centerY = rect.height / 2;\n\n\t\t\t\tlet startIndex = 0;\n\t\t\t\tif (!_recording && _isRecordingComplete) {\n\t\t\t\t\tconst totalBars = dataToRender.length;\n\t\t\t\t\tif (totalBars > barsVisible) {\n\t\t\t\t\t\tstartIndex = Math.floor((totalBars - barsVisible) * _viewPosition);\n\t\t\t\t\t}\n\t\t\t\t} else if (_recording) {\n\t\t\t\t\tstartIndex = Math.max(0, dataToRender.length - barsVisible);\n\t\t\t\t}\n\n\t\t\t\tfor (let i = 0; i < barsVisible && startIndex + i < dataToRender.length; i++) {\n\t\t\t\t\tconst value = dataToRender[startIndex + 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.7);\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.3 + value * 0.7;\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\n\t\t\t\tif (!_recording && _isRecordingComplete && _showHandle) {\n\t\t\t\t\tconst indicatorX = rect.width * _viewPosition;\n\n\t\t\t\t\tctx.strokeStyle = computedBarColor;\n\t\t\t\t\tctx.globalAlpha = 0.5;\n\t\t\t\t\tctx.lineWidth = 2;\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(indicatorX, 0);\n\t\t\t\t\tctx.lineTo(indicatorX, rect.height);\n\t\t\t\t\tctx.stroke();\n\t\t\t\t\tctx.fillStyle = computedBarColor;\n\t\t\t\t\tctx.globalAlpha = 1;\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(indicatorX, centerY, 6, 0, Math.PI * 2);\n\t\t\t\t\tctx.fill();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.globalAlpha = 1;\n\n\t\t\tanimationRef = requestAnimationFrame(animate);\n\t\t};\n\n\t\tanimationRef = requestAnimationFrame(animate);\n\n\t\treturn () => {\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\tfunction handleScrub(clientX: number) {\n\t\tif (!containerEl || recording || !isRecordingComplete) return;\n\t\tconst rect = containerEl.getBoundingClientRect();\n\t\tconst x = Math.max(0, Math.min(clientX - rect.left, rect.width));\n\t\tviewPosition = x / rect.width;\n\t}\n\n\tfunction handlePointerDown(event: PointerEvent) {\n\t\tif (recording || !isRecordingComplete) return;\n\t\tevent.preventDefault();\n\t\thandleScrub(event.clientX);\n\n\t\tconst handleMove = (moveEvent: PointerEvent) => {\n\t\t\thandleScrub(moveEvent.clientX);\n\t\t};\n\n\t\tconst handleUp = () => {\n\t\t\twindow.removeEventListener(\"pointermove\", handleMove);\n\t\t\twindow.removeEventListener(\"pointerup\", handleUp);\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", handleMove);\n\t\twindow.addEventListener(\"pointerup\", handleUp, { once: true });\n\t}\n</script>\n\n<!-- svelte-ignore a11y_no_noninteractive_tabindex -->\n<div\n\tbind:this={containerEl}\n\tdata-slot=\"recording-waveform\"\n\taria-label={isRecordingComplete && !recording ? \"Drag to scrub through recording\" : undefined}\n\taria-valuenow={isRecordingComplete && !recording ? viewPosition * 100 : undefined}\n\taria-valuemin={isRecordingComplete && !recording ? 0 : undefined}\n\taria-valuemax={isRecordingComplete && !recording ? 100 : undefined}\n\trole={isRecordingComplete && !recording ? \"slider\" : undefined}\n\ttabindex={isRecordingComplete && !recording ? 0 : undefined}\n\tclass={cn(\n\t\t\"relative flex items-center\",\n\t\tisRecordingComplete && !recording && \"cursor-pointer\",\n\t\tclassName\n\t)}\n\tstyle:height={heightStyle}\n\tonpointerdown={handlePointerDown}\n\t{...restProps}\n>\n\t<canvas bind:this={canvasEl} class=\"block h-full w-full\"></canvas>\n</div>\n",
			"type": "registry:ui",
			"target": "waveform/waveform-recording.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from \"$UTILS$.js\";\n\timport { getComputedBarColor, heightToCssSize } from \"./utils.js\";\n\timport type { WaveformProps } from \"./waveform.svelte\";\n\n\texport type ScrollingWaveformProps = Omit<WaveformProps, \"data\" | \"onBarClick\"> & {\n\t\tspeed?: number;\n\t\tbarCount?: number;\n\t\tdata?: number[];\n\t};\n\n\tlet {\n\t\tspeed = 50,\n\t\tbarCount = 60,\n\t\tbarWidth = 4,\n\t\tbarHeight: baseBarHeight = 4,\n\t\tbarGap = 2,\n\t\tbarRadius = 2,\n\t\tbarColor,\n\t\tfadeEdges = true,\n\t\tfadeWidth = 24,\n\t\theight = 128,\n\t\tdata,\n\t\tclass: className,\n\t\t...restProps\n\t}: ScrollingWaveformProps = $props();\n\n\tlet canvasEl: HTMLCanvasElement | null = $state(null);\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\n\t// Non-reactive mutable refs — plain `let`, not $state.\n\tlet barsRef: Array<{ x: number; height: number }> = [];\n\tlet animationRef: number | null = null;\n\tlet lastTimeRef = 0;\n\tconst seedRef = Math.random();\n\tlet dataIndexRef = 0;\n\n\tconst heightStyle = $derived(heightToCssSize(height));\n\n\t// ResizeObserver — re-runs when barWidth/barGap change (React deps [barWidth, barGap]).\n\t$effect(() => {\n\t\tconst canvas = canvasEl;\n\t\tconst container = containerEl;\n\t\tif (!canvas || !container) return;\n\n\t\tconst _barWidth = barWidth;\n\t\tconst _barGap = barGap;\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\tif (barsRef.length === 0) {\n\t\t\t\tconst step = _barWidth + _barGap;\n\t\t\t\tlet currentX = rect.width;\n\t\t\t\tlet index = 0;\n\t\t\t\tconst seeded = (i: number) => {\n\t\t\t\t\tconst x = Math.sin(seedRef * 10000 + i) * 10000;\n\t\t\t\t\treturn x - Math.floor(x);\n\t\t\t\t};\n\t\t\t\twhile (currentX > -step) {\n\t\t\t\t\tbarsRef.push({\n\t\t\t\t\t\tx: currentX,\n\t\t\t\t\t\theight: 0.2 + seeded(index++) * 0.6,\n\t\t\t\t\t});\n\t\t\t\t\tcurrentX -= step;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tresizeObserver.observe(container);\n\t\treturn () => resizeObserver.disconnect();\n\t});\n\n\t// RAF loop — re-runs when any render prop changes (matches React deps).\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\t// Reactive dependency reads.\n\t\tconst _speed = speed;\n\t\tconst _barCount = barCount;\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 _data = data;\n\n\t\tconst animate = (currentTime: number) => {\n\t\t\tconst deltaTime = lastTimeRef ? (currentTime - lastTimeRef) / 1000 : 0;\n\t\t\tlastTimeRef = currentTime;\n\n\t\t\tconst rect = canvas.getBoundingClientRect();\n\t\t\tctx.clearRect(0, 0, rect.width, rect.height);\n\n\t\t\tconst computedBarColor = getComputedBarColor(canvas, _barColor);\n\n\t\t\tconst step = _barWidth + _barGap;\n\t\t\tfor (let i = 0; i < barsRef.length; i++) {\n\t\t\t\tbarsRef[i].x -= _speed * deltaTime;\n\t\t\t}\n\n\t\t\tbarsRef = barsRef.filter((bar) => bar.x + _barWidth > -step);\n\n\t\t\twhile (barsRef.length === 0 || barsRef[barsRef.length - 1].x < rect.width) {\n\t\t\t\tconst lastBar = barsRef[barsRef.length - 1];\n\t\t\t\tconst nextX = lastBar ? lastBar.x + step : rect.width;\n\n\t\t\t\tlet newHeight: number;\n\t\t\t\tif (_data && _data.length > 0) {\n\t\t\t\t\tnewHeight = _data[dataIndexRef % _data.length] || 0.1;\n\t\t\t\t\tdataIndexRef = (dataIndexRef + 1) % _data.length;\n\t\t\t\t} else {\n\t\t\t\t\tconst time = Date.now() / 1000;\n\t\t\t\t\tconst uniqueIndex = barsRef.length + time * 0.01;\n\t\t\t\t\tconst seeded = (idx: number) => {\n\t\t\t\t\t\tconst x = Math.sin(seedRef * 10000 + idx * 137.5) * 10000;\n\t\t\t\t\t\treturn x - Math.floor(x);\n\t\t\t\t\t};\n\t\t\t\t\tconst wave1 = Math.sin(uniqueIndex * 0.1) * 0.2;\n\t\t\t\t\tconst wave2 = Math.cos(uniqueIndex * 0.05) * 0.15;\n\t\t\t\t\tconst randomComponent = seeded(uniqueIndex) * 0.4;\n\t\t\t\t\tnewHeight = Math.max(0.1, Math.min(0.9, 0.3 + wave1 + wave2 + randomComponent));\n\t\t\t\t}\n\n\t\t\t\tbarsRef.push({\n\t\t\t\t\tx: nextX,\n\t\t\t\t\theight: newHeight,\n\t\t\t\t});\n\t\t\t\tif (barsRef.length > _barCount * 2) break;\n\t\t\t}\n\n\t\t\tconst centerY = rect.height / 2;\n\t\t\tfor (const bar of barsRef) {\n\t\t\t\tif (bar.x < rect.width && bar.x + _barWidth > 0) {\n\t\t\t\t\tconst barHeightPx = Math.max(_baseBarHeight, bar.height * rect.height * 0.6);\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.3 + bar.height * 0.7;\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(bar.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(bar.x, y, _barWidth, barHeightPx);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (_fadeEdges && _fadeWidth > 0) {\n\t\t\t\tconst gradient = ctx.createLinearGradient(0, 0, rect.width, 0);\n\t\t\t\tconst fadePercent = Math.min(0.2, _fadeWidth / rect.width);\n\n\t\t\t\tgradient.addColorStop(0, \"rgba(255,255,255,1)\");\n\t\t\t\tgradient.addColorStop(fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1 - fadePercent, \"rgba(255,255,255,0)\");\n\t\t\t\tgradient.addColorStop(1, \"rgba(255,255,255,1)\");\n\n\t\t\t\tctx.globalCompositeOperation = \"destination-out\";\n\t\t\t\tctx.fillStyle = gradient;\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\tanimationRef = requestAnimationFrame(animate);\n\t\t};\n\n\t\tanimationRef = requestAnimationFrame(animate);\n\n\t\treturn () => {\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</script>\n\n<div\n\tbind:this={containerEl}\n\tdata-slot=\"scrolling-waveform\"\n\tclass={cn(\"relative flex items-center\", className)}\n\tstyle:height={heightStyle}\n\t{...restProps}\n>\n\t<canvas bind:this={canvasEl} class=\"block h-full w-full\"></canvas>\n</div>\n",
			"type": "registry:ui",
			"target": "waveform/waveform-scrolling.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from \"$UTILS$.js\";\n\timport { heightToCssSize, seededRandom } from \"./utils.js\";\n\timport Waveform, { type WaveformProps } from \"./waveform.svelte\";\n\n\texport type AudioScrubberProps = WaveformProps & {\n\t\tcurrentTime?: number;\n\t\tduration?: number;\n\t\tonSeek?: (time: number) => void;\n\t\tshowHandle?: boolean;\n\t};\n\n\tlet {\n\t\tdata = [],\n\t\tcurrentTime = 0,\n\t\tduration = 100,\n\t\tonSeek,\n\t\tshowHandle = true,\n\t\tbarWidth = 3,\n\t\tbarHeight,\n\t\tbarGap = 1,\n\t\tbarRadius = 1,\n\t\tbarColor,\n\t\theight = 128,\n\t\tclass: className,\n\t\t...restProps\n\t}: AudioScrubberProps = $props();\n\n\tlet containerEl: HTMLDivElement | null = $state(null);\n\tlet isDragging = $state(false);\n\tlet localProgress = $state(0);\n\n\t// Stable fallback seed per component instance — diverges from React's per-render\n\t// `Math.random()` (which is unstable anyway). Deterministic filler bars when no\n\t// data is provided.\n\tconst fallbackSeed = Math.random();\n\n\tconst waveformData = $derived(\n\t\tdata.length > 0\n\t\t\t? data\n\t\t\t: Array.from({ length: 100 }, (_, i) => 0.2 + seededRandom(fallbackSeed * 10000 + i) * 0.6)\n\t);\n\n\tconst heightStyle = $derived(heightToCssSize(height));\n\n\t$effect(() => {\n\t\tif (!isDragging && duration > 0) {\n\t\t\tlocalProgress = currentTime / duration;\n\t\t}\n\t});\n\n\tfunction handleScrub(clientX: number) {\n\t\tif (!containerEl) return;\n\t\tconst rect = containerEl.getBoundingClientRect();\n\t\tconst x = Math.max(0, Math.min(clientX - rect.left, rect.width));\n\t\tconst progress = x / rect.width;\n\t\tlocalProgress = progress;\n\t\tonSeek?.(progress * duration);\n\t}\n\n\tfunction handlePointerDown(event: PointerEvent) {\n\t\tevent.preventDefault();\n\t\tisDragging = true;\n\t\thandleScrub(event.clientX);\n\n\t\tconst handleMove = (moveEvent: PointerEvent) => {\n\t\t\thandleScrub(moveEvent.clientX);\n\t\t};\n\n\t\tconst handleUp = () => {\n\t\t\tisDragging = false;\n\t\t\twindow.removeEventListener(\"pointermove\", handleMove);\n\t\t\twindow.removeEventListener(\"pointerup\", handleUp);\n\t\t};\n\n\t\twindow.addEventListener(\"pointermove\", handleMove);\n\t\twindow.addEventListener(\"pointerup\", handleUp, { once: true });\n\t}\n</script>\n\n<div\n\tbind:this={containerEl}\n\tdata-slot=\"audio-scrubber\"\n\taria-label=\"Audio waveform scrubber\"\n\taria-valuemax={duration}\n\taria-valuemin={0}\n\taria-valuenow={currentTime}\n\trole=\"slider\"\n\ttabindex={0}\n\tclass={cn(\"relative cursor-pointer select-none\", className)}\n\tstyle:height={heightStyle}\n\tonpointerdown={handlePointerDown}\n\t{...restProps}\n>\n\t<Waveform\n\t\t{barColor}\n\t\t{barGap}\n\t\t{barRadius}\n\t\t{barWidth}\n\t\t{barHeight}\n\t\tdata={waveformData}\n\t\tfadeEdges={false}\n\t/>\n\n\t<div\n\t\tclass=\"bg-primary/20 pointer-events-none absolute inset-y-0 left-0\"\n\t\tstyle:width=\"{localProgress * 100}%\"\n\t></div>\n\n\t<div\n\t\tclass=\"bg-primary pointer-events-none absolute top-0 bottom-0 w-0.5\"\n\t\tstyle:left=\"{localProgress * 100}%\"\n\t></div>\n\n\t{#if showHandle}\n\t\t<div\n\t\t\tclass=\"border-background bg-primary pointer-events-none absolute top-1/2 h-4 w-4 -translate-x-1/2 -translate-y-1/2 rounded-full border-2 shadow-lg transition-transform hover:scale-110\"\n\t\t\tstyle:left=\"{localProgress * 100}%\"\n\t\t></div>\n\t{/if}\n</div>\n",
			"type": "registry:ui",
			"target": "waveform/waveform-scrubber.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport Waveform, { type WaveformProps } from \"./waveform.svelte\";\n\timport { seededRandom } from \"./utils.js\";\n\n\texport type StaticWaveformProps = WaveformProps & {\n\t\tbars?: number;\n\t\tseed?: number;\n\t};\n\n\tlet { bars = 40, seed = 42, ...restProps }: StaticWaveformProps = $props();\n\n\tconst data = $derived(Array.from({ length: bars }, (_, i) => 0.2 + seededRandom(seed + i) * 0.6));\n</script>\n\n<Waveform {data} {...restProps} />\n",
			"type": "registry:ui",
			"target": "waveform/waveform-static.svelte"
		},
		{
			"content": "export function seededRandom(seed: number): number {\n\tconst x = Math.sin(seed) * 10000;\n\treturn x - Math.floor(x);\n}\n\nexport function heightToCssSize(height: string | number): string {\n\treturn typeof height === \"number\" ? `${height}px` : height;\n}\n\nexport function getComputedBarColor(\n\tcanvas: HTMLCanvasElement,\n\toverride: string | undefined\n): string {\n\treturn override || getComputedStyle(canvas).getPropertyValue(\"--foreground\") || \"#000\";\n}\n",
			"type": "registry:ui",
			"target": "waveform/utils.ts"
		},
		{
			"content": "import Root from \"./waveform.svelte\";\nimport Scrolling from \"./waveform-scrolling.svelte\";\nimport Scrubber from \"./waveform-scrubber.svelte\";\nimport Microphone from \"./waveform-microphone.svelte\";\nimport Static from \"./waveform-static.svelte\";\nimport LiveMicrophone from \"./waveform-live-microphone.svelte\";\nimport Recording from \"./waveform-recording.svelte\";\n\nexport {\n\tRoot,\n\tScrolling,\n\tScrubber,\n\tMicrophone,\n\tStatic,\n\tLiveMicrophone,\n\tRecording,\n\t//\n\tRoot as Waveform,\n\tScrolling as ScrollingWaveform,\n\tScrubber as AudioScrubber,\n\tMicrophone as MicrophoneWaveform,\n\tStatic as StaticWaveform,\n\tLiveMicrophone as LiveMicrophoneWaveform,\n\tRecording as RecordingWaveform,\n};\nexport type { WaveformProps } from \"./waveform.svelte\";\nexport type { ScrollingWaveformProps } from \"./waveform-scrolling.svelte\";\nexport type { AudioScrubberProps } from \"./waveform-scrubber.svelte\";\nexport type { MicrophoneWaveformProps } from \"./waveform-microphone.svelte\";\nexport type { StaticWaveformProps } from \"./waveform-static.svelte\";\nexport type { LiveMicrophoneWaveformProps } from \"./waveform-live-microphone.svelte\";\nexport type { RecordingWaveformProps } from \"./waveform-recording.svelte\";\nexport { seededRandom, heightToCssSize, getComputedBarColor } from \"./utils.js\";\n",
			"type": "registry:ui",
			"target": "waveform/index.ts"
		}
	]
}