{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "audio",
	"title": "Audio",
	"type": "registry:ui",
	"description": "A modern audio player with progress scrubbing and volume controls.",
	"dependencies": [
		"@lucide/svelte@^0.554.0"
	],
	"devDependencies": [
		"@lucide/svelte@^0.544.0"
	],
	"registryDependencies": [
		"button",
		"slider",
		"hover-card"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport { setAudioPlayerContext } from './ctx';\n\timport type { Snippet } from 'svelte';\n\n\tlet {\n\t\tsrc,\n\t\tclass: className,\n\t\tchildren\n\t}: {\n\t\tsrc: string;\n\t\tclass?: string;\n\t\tchildren: Snippet;\n\t} = $props();\n\n\tlet audio: HTMLAudioElement;\n\n\tlet isPaused = $state(true);\n\tlet duration = $state(0);\n\tlet currentTime = $state(0);\n\tlet volume = $state(1);\n\tlet isMuted = $state(false);\n\n\tfunction togglePlay() {\n\t\tif (!audio) return;\n\t\tif (audio.paused) {\n\t\t\taudio.play();\n\t\t} else {\n\t\t\taudio.pause();\n\t\t}\n\t}\n\n\tfunction seek(time: number) {\n\t\tif (!audio) return;\n\t\taudio.currentTime = time;\n\t\tcurrentTime = time;\n\t}\n\n\tfunction setVolume(v: number) {\n\t\tif (!audio) return;\n\t\taudio.volume = v;\n\t\tvolume = v;\n\t}\n\n\tfunction toggleMute() {\n\t\tisMuted = !isMuted;\n\t}\n\n\tsetAudioPlayerContext({\n\t\tget isPlaying() {\n\t\t\treturn { value: !isPaused };\n\t\t},\n\t\tget duration() {\n\t\t\treturn { value: duration };\n\t\t},\n\t\tget currentTime() {\n\t\t\treturn { value: currentTime };\n\t\t},\n\t\tget volume() {\n\t\t\treturn { value: volume };\n\t\t},\n\t\tget isMuted() {\n\t\t\treturn { value: isMuted };\n\t\t},\n\t\ttogglePlay,\n\t\tseek,\n\t\tsetVolume,\n\t\ttoggleMute\n\t});\n</script>\n\n<div\n\tclass={cn(\n\t\t'relative overflow-hidden rounded-xl border bg-card text-card-foreground shadow-sm',\n\t\tclassName\n\t)}\n>\n\t<audio\n\t\tbind:this={audio}\n\t\t{src}\n\t\tbind:paused={isPaused}\n\t\tbind:currentTime\n\t\tbind:duration\n\t\tbind:volume\n\t\tbind:muted={isMuted}\n\t></audio>\n\t{@render children()}\n</div>\n",
			"type": "registry:ui",
			"target": "audio/audio.svelte"
		},
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { Button } from '$lib/components/ui/button';\r\n\timport { Play, Pause } from '@lucide/svelte';\r\n\timport { getAudioPlayerContext } from './ctx';\r\n\r\n\tlet { class: className }: { class?: string } = $props();\r\n\tconst ctx = getAudioPlayerContext();\r\n</script>\r\n\r\n<Button\r\n\tvariant=\"secondary\"\r\n\tsize=\"icon\"\r\n\tclass={cn('h-12 w-12 rounded-full shadow-sm', className)}\r\n\tonclick={ctx.togglePlay}\r\n>\r\n\t{#if ctx.isPlaying.value}\r\n\t\t<Pause class=\"h-5 w-5 fill-current\" />\r\n\t{:else}\r\n\t\t<Play class=\"h-5 w-5 fill-current ml-0.5\" />\r\n\t{/if}\r\n</Button>\r\n",
			"type": "registry:ui",
			"target": "audio/audio-play-button.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport { getAudioPlayerContext } from './ctx';\n\n\tlet { class: className }: { class?: string } = $props();\n\tconst ctx = getAudioPlayerContext();\n\n\tlet progress = $derived(\n\t\tctx.duration.value > 0 ? (ctx.currentTime.value / ctx.duration.value) * 100 : 0\n\t);\n</script>\n\n<div class={cn('relative h-1.5 w-full bg-secondary overflow-hidden', className)}>\n\t<div class=\"absolute left-0 top-0 h-full bg-primary\" style=\"width: {progress}%\"></div>\n\t<input\n\t\ttype=\"range\"\n\t\tmin=\"0\"\n\t\tmax={ctx.duration.value}\n\t\tstep=\"0.01\"\n\t\tvalue={ctx.currentTime.value}\n\t\toninput={(e) => ctx.seek(parseFloat(e.currentTarget.value))}\n\t\tclass=\"absolute inset-0 h-full w-full cursor-pointer opacity-0\"\n\t/>\n</div>\n",
			"type": "registry:ui",
			"target": "audio/audio-slider.svelte"
		},
		{
			"content": "<script lang=\"ts\">\n\timport { cn } from '$UTILS$';\n\timport { getAudioPlayerContext } from './ctx';\n\n\tlet { class: className }: { class?: string } = $props();\n\tconst ctx = getAudioPlayerContext();\n\n\tfunction formatTime(seconds: number) {\n\t\tconst mins = Math.floor(seconds / 60);\n\t\tconst secs = Math.floor(seconds % 60);\n\t\treturn `${mins}:${secs.toString().padStart(2, '0')}`;\n\t}\n</script>\n\n<span class={cn('text-xs font-medium tabular-nums text-muted-foreground', className)}>\n\t{formatTime(ctx.currentTime.value)} / {formatTime(ctx.duration.value)}\n</span>\n",
			"type": "registry:ui",
			"target": "audio/audio-time.svelte"
		},
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { Button } from '$lib/components/ui/button';\r\n\timport { Volume2, VolumeX, Volume1 } from '@lucide/svelte';\r\n\timport * as HoverCard from '$lib/components/ui/hover-card';\r\n\timport { getAudioPlayerContext } from './ctx';\r\n\r\n\tlet { class: className }: { class?: string } = $props();\r\n\tconst ctx = getAudioPlayerContext();\r\n\r\n\tfunction handleVolumeChange(e: Event & { currentTarget: HTMLInputElement }) {\r\n\t\tconst val = parseFloat(e.currentTarget.value);\r\n\t\tctx.setVolume(val);\r\n\t}\r\n</script>\r\n\r\n<HoverCard.Root openDelay={0} closeDelay={150}>\r\n\t<HoverCard.Trigger>\r\n\t\t<Button\r\n\t\t\tvariant=\"ghost\"\r\n\t\t\tsize=\"icon\"\r\n\t\t\tclass={cn('h-8 w-8 text-muted-foreground hover:text-foreground', className)}\r\n\t\t\tonclick={ctx.toggleMute}\r\n\t\t>\r\n\t\t\t{#if ctx.isMuted.value || ctx.volume.value === 0}\r\n\t\t\t\t<VolumeX class=\"h-4 w-4\" />\r\n\t\t\t{:else if ctx.volume.value < 0.5}\r\n\t\t\t\t<Volume1 class=\"h-4 w-4\" />\r\n\t\t\t{:else}\r\n\t\t\t\t<Volume2 class=\"h-4 w-4\" />\r\n\t\t\t{/if}\r\n\t\t</Button>\r\n\t</HoverCard.Trigger>\r\n\t<HoverCard.Content side=\"top\" align=\"center\" class=\"w-2 p-0 shadow-none\" sideOffset={10}>\r\n\t\t<div class=\"flex h-24 flex-col items-center justify-center gap-3\">\r\n\t\t\t<div class=\"relative h-full w-1.5 rounded-full bg-secondary\">\r\n\t\t\t\t<div\r\n\t\t\t\t\tclass=\"absolute bottom-0 left-0 w-full rounded-full bg-primary\"\r\n\t\t\t\t\tstyle=\"height: {ctx.isMuted.value ? 0 : ctx.volume.value * 100}%\"\r\n\t\t\t\t></div>\r\n\r\n\t\t\t\t<input\r\n\t\t\t\t\ttype=\"range\"\r\n\t\t\t\t\tmin=\"0\"\r\n\t\t\t\t\tmax=\"1\"\r\n\t\t\t\t\tstep=\"0.01\"\r\n\t\t\t\t\torient=\"vertical\"\r\n\t\t\t\t\tvalue={ctx.isMuted.value ? 0 : ctx.volume.value}\r\n\t\t\t\t\toninput={handleVolumeChange}\r\n\t\t\t\t\tclass=\"absolute inset-0 h-full w-full cursor-pointer opacity-0\"\r\n\t\t\t\t\tstyle=\"appearance: slider-vertical; -webkit-appearance: slider-vertical; width: 32px; left: -13px;\"\r\n\t\t\t\t/>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</HoverCard.Content>\r\n</HoverCard.Root>\r\n",
			"type": "registry:ui",
			"target": "audio/audio-volume.svelte"
		},
		{
			"content": "import { getContext, setContext } from 'svelte';\n\nconst AUDIO_PLAYER_KEY = Symbol('audio-player');\n\ntype AudioPlayerContext = {\n\tisPlaying: { value: boolean };\n\tduration: { value: number };\n\tcurrentTime: { value: number };\n\tvolume: { value: number };\n\tisMuted: { value: boolean };\n\ttogglePlay: () => void;\n\tseek: (time: number) => void;\n\ttoggleMute: () => void;\n\tsetVolume: (v: number) => void;\n};\n\nexport function setAudioPlayerContext(props: AudioPlayerContext) {\n\tsetContext(AUDIO_PLAYER_KEY, props);\n}\n\nexport function getAudioPlayerContext() {\n\treturn getContext<AudioPlayerContext>(AUDIO_PLAYER_KEY);\n}\n",
			"type": "registry:ui",
			"target": "audio/ctx.ts"
		},
		{
			"content": "import Root from './audio.svelte';\nimport PlayButton from './audio-play-button.svelte';\nimport Slider from './audio-slider.svelte';\nimport Time from './audio-time.svelte';\nimport Volume from './audio-volume.svelte';\n\nexport {\n\tRoot,\n\tPlayButton,\n\tSlider,\n\tTime,\n\tVolume,\n\t// Aliases\n\tRoot as AudioPlayer,\n\tPlayButton as AudioPlayButton,\n\tSlider as AudioSlider,\n\tTime as AudioTime,\n\tVolume as AudioVolume\n};\n",
			"type": "registry:ui",
			"target": "audio/index.ts"
		}
	]
}