{
	"$schema": "https://shadcn-svelte.com/schema/registry-item.json",
	"name": "video",
	"title": "Video",
	"type": "registry:ui",
	"description": "A responsive video player with controls and progress bar.",
	"dependencies": [
		"@lucide/svelte@^0.554.0"
	],
	"devDependencies": [
		"@lucide/svelte@^0.544.0"
	],
	"registryDependencies": [
		"button"
	],
	"files": [
		{
			"content": "<script lang=\"ts\">\r\n\timport { cn } from '$UTILS$';\r\n\timport { Play, Pause, Volume2, VolumeX, Maximize, Minimize, Loader2 } from '@lucide/svelte';\r\n\timport { onMount } from 'svelte';\r\n\timport { Slider } from '$lib/components/ui/slider';\r\n\r\n\tlet {\r\n\t\tsrc,\r\n\t\tposter,\r\n\t\tclass: className,\r\n\t\tautoplay = false,\r\n\t\tloop = false,\r\n\t\tmuted = false\r\n\t} = $props<{\r\n\t\tsrc: string;\r\n\t\tposter?: string;\r\n\t\tclass?: string;\r\n\t\tautoplay?: boolean;\r\n\t\tloop?: boolean;\r\n\t\tmuted?: boolean;\r\n\t}>();\r\n\r\n\tlet video: HTMLVideoElement;\r\n\tlet isPlaying = $state(false);\r\n\tlet currentTime = $state(0);\r\n\tlet duration = $state(0);\r\n\tlet volume = $state(1);\r\n\tlet isMuted = $state(muted);\r\n\tlet isFullscreen = $state(false);\r\n\tlet showControls = $state(false);\r\n\tlet controlsTimeout: number;\r\n\tlet isLoading = $state(true);\r\n\tlet lastVolume = $state(1);\r\n\r\n\tfunction togglePlay() {\r\n\t\tif (video.paused) {\r\n\t\t\tvideo.play();\r\n\t\t} else {\r\n\t\t\tvideo.pause();\r\n\t\t}\r\n\t}\r\n\r\n\tfunction handlePlay() {\r\n\t\tisPlaying = true;\r\n\t\tisLoading = false;\r\n\t\tshowControlsTemporarily();\r\n\t}\r\n\r\n\tfunction handlePause() {\r\n\t\tisPlaying = false;\r\n\t\tshowControls = true;\r\n\t}\r\n\r\n\tfunction handleTimeUpdate() {\r\n\t\tcurrentTime = video.currentTime;\r\n\t}\r\n\r\n\tfunction handleDurationChange() {\r\n\t\tduration = video.duration;\r\n\t}\r\n\r\n\tfunction handleSeek(e: Event) {\r\n\t\tconst target = e.target as HTMLInputElement;\r\n\t\tconst time = parseFloat(target.value);\r\n\t\tvideo.currentTime = time;\r\n\t\tcurrentTime = time;\r\n\t}\r\n\r\n\tfunction handleVolumeChange(val: number) {\r\n\t\tconst newVolume = val;\r\n\t\tvolume = newVolume;\r\n\t\tvideo.volume = newVolume;\r\n\t\tisMuted = newVolume === 0;\r\n\t\tif (newVolume > 0) lastVolume = newVolume;\r\n\t}\r\n\r\n\tfunction toggleMute() {\r\n\t\tif (isMuted) {\r\n\t\t\tisMuted = false;\r\n\t\t\tvolume = lastVolume > 0 ? lastVolume : 1;\r\n\t\t\tvideo.muted = false;\r\n\t\t\tvideo.volume = volume;\r\n\t\t} else {\r\n\t\t\tisMuted = true;\r\n\t\t\tlastVolume = volume;\r\n\t\t\tvolume = 0;\r\n\t\t\tvideo.muted = true;\r\n\t\t\tvideo.volume = 0;\r\n\t\t}\r\n\t}\r\n\r\n\tfunction toggleFullscreen() {\r\n\t\tif (!document.fullscreenElement) {\r\n\t\t\tvideo.parentElement?.requestFullscreen();\r\n\t\t\tisFullscreen = true;\r\n\t\t} else {\r\n\t\t\tdocument.exitFullscreen();\r\n\t\t\tisFullscreen = false;\r\n\t\t}\r\n\t}\r\n\r\n\tfunction formatTime(seconds: number) {\r\n\t\tconst mins = Math.floor(seconds / 60);\r\n\t\tconst secs = Math.floor(seconds % 60);\r\n\t\treturn `${mins}:${secs.toString().padStart(2, '0')}`;\r\n\t}\r\n\r\n\tfunction showControlsTemporarily() {\r\n\t\tshowControls = true;\r\n\t\tclearTimeout(controlsTimeout);\r\n\t\tcontrolsTimeout = setTimeout(() => {\r\n\t\t\tshowControls = false;\r\n\t\t}, 2500);\r\n\t}\r\n\r\n\tfunction handleMouseMove() {\r\n\t\tshowControlsTemporarily();\r\n\t}\r\n\r\n\tfunction handleWaiting() {\r\n\t\tisLoading = true;\r\n\t}\r\n\r\n\tfunction handleCanPlay() {\r\n\t\tisLoading = false;\r\n\t}\r\n\r\n\tonMount(() => {\r\n\t\tvideo.volume = volume;\r\n\t\tvideo.muted = isMuted;\r\n\r\n\t\tif (autoplay) {\r\n\t\t\tvideo.play().catch(() => {\r\n\t\t\t\tisMuted = true;\r\n\t\t\t\tvideo.muted = true;\r\n\t\t\t\tvideo.play();\r\n\t\t\t});\r\n\t\t}\r\n\t});\r\n</script>\r\n\r\n<div\r\n\tclass={cn(\r\n\t\t'group relative flex aspect-video w-full min-w-[300px] items-center justify-center overflow-hidden rounded-xl bg-black shadow-lg',\r\n\t\tclassName\r\n\t)}\r\n\tonmousemove={handleMouseMove}\r\n\tonmouseleave={() => isPlaying && (showControls = false)}\r\n>\r\n\t<video\r\n\t\tbind:this={video}\r\n\t\t{src}\r\n\t\t{poster}\r\n\t\t{loop}\r\n\t\tclass=\"h-full w-full object-cover\"\r\n\t\tonplay={handlePlay}\r\n\t\tonpause={handlePause}\r\n\t\tontimeupdate={handleTimeUpdate}\r\n\t\tondurationchange={handleDurationChange}\r\n\t\tonwaiting={handleWaiting}\r\n\t\toncanplay={handleCanPlay}\r\n\t\tonclick={togglePlay}\r\n\t></video>\r\n\r\n\t{#if isLoading}\r\n\t\t<div class=\"absolute inset-0 flex items-center justify-center bg-black/20 backdrop-blur-[1px]\">\r\n\t\t\t<Loader2 class=\"h-10 w-10 animate-spin text-white/80\" />\r\n\t\t</div>\r\n\t{/if}\r\n\r\n\t{#if !isPlaying && !isLoading}\r\n\t\t<button\r\n\t\t\tonclick={togglePlay}\r\n\t\t\tclass=\"absolute inset-0 flex items-center justify-center bg-black/10 transition-opacity hover:bg-black/20\"\r\n\t\t>\r\n\t\t\t<div\r\n\t\t\t\tclass=\"flex h-16 w-16 items-center justify-center rounded-full bg-white/10 text-white backdrop-blur-sm transition-transform hover:scale-110\"\r\n\t\t\t>\r\n\t\t\t\t<Play class=\"ml-1 h-8 w-8 fill-white\" />\r\n\t\t\t</div>\r\n\t\t</button>\r\n\t{/if}\r\n\r\n\t<div\r\n\t\tclass={cn(\r\n\t\t\t'absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent px-4 pb-4 pt-12 transition-opacity duration-300',\r\n\t\t\tshowControls ? 'opacity-100' : 'opacity-0'\r\n\t\t)}\r\n\t>\r\n\t\t<div class=\"group/slider relative mb-4 flex h-4 w-full items-center\">\r\n\t\t\t<input\r\n\t\t\t\ttype=\"range\"\r\n\t\t\t\tmin=\"0\"\r\n\t\t\t\tmax={duration || 0}\r\n\t\t\t\tbind:value={currentTime}\r\n\t\t\t\toninput={(e) => {\r\n\t\t\t\t\tconst el = e.currentTarget as HTMLInputElement;\r\n\t\t\t\t\tconst t = parseFloat(el.value);\r\n\t\t\t\t\tcurrentTime = t;\r\n\t\t\t\t\tvideo.currentTime = t;\r\n\t\t\t\t}}\r\n\t\t\t\tclass=\"absolute z-20 h-full w-full cursor-pointer opacity-0\"\r\n\t\t\t/>\r\n\t\t\t<div\r\n\t\t\t\tclass=\"absolute h-1 w-full rounded-full bg-white/20 transition-all group-hover/slider:h-1.5\"\r\n\t\t\t></div>\r\n\t\t\t<div\r\n\t\t\t\tclass=\"absolute h-1 rounded-full bg-white group-hover/slider:h-1.5\"\r\n\t\t\t\tstyle=\"width: {(currentTime / duration) * 100}%\"\r\n\t\t\t></div>\r\n\t\t\t<div\r\n\t\t\t\tclass=\"absolute h-3 w-3 rounded-full bg-white opacity-0 shadow transition-opacity group-hover/slider:opacity-100\"\r\n\t\t\t\tstyle=\"left: {(currentTime / duration) * 100}%; transform: translateX(-50%)\"\r\n\t\t\t></div>\r\n\t\t</div>\r\n\r\n\t\t<div class=\"flex items-center justify-between gap-4\">\r\n\t\t\t<div class=\"flex items-center gap-4\">\r\n\t\t\t\t<button\r\n\t\t\t\t\tonclick={togglePlay}\r\n\t\t\t\t\tclass=\"text-white/90 hover:text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\r\n\t\t\t\t>\r\n\t\t\t\t\t{#if isPlaying}\r\n\t\t\t\t\t\t<Pause class=\"h-5 w-5 fill-white/90\" />\r\n\t\t\t\t\t{:else}\r\n\t\t\t\t\t\t<Play class=\"h-5 w-5 fill-white/90\" />\r\n\t\t\t\t\t{/if}\r\n\t\t\t\t</button>\r\n\r\n\t\t\t\t<div class=\"group/volume flex items-center gap-2\">\r\n\t\t\t\t\t<button\r\n\t\t\t\t\t\tonclick={toggleMute}\r\n\t\t\t\t\t\tclass=\"text-white/90 hover:text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t{#if isMuted || volume === 0}\r\n\t\t\t\t\t\t\t<VolumeX class=\"h-5 w-5\" />\r\n\t\t\t\t\t\t{:else}\r\n\t\t\t\t\t\t\t<Volume2 class=\"h-5 w-5\" />\r\n\t\t\t\t\t\t{/if}\r\n\t\t\t\t\t</button>\r\n\t\t\t\t\t<div\r\n\t\t\t\t\t\tclass=\"flex items-center justify-center w-0 h-10 p-2 overflow-hidden transition-all duration-300 group-hover/volume:w-20\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<div class=\"group/slider relative flex h-4 w-full items-center\">\r\n\t\t\t\t\t\t\t<input\r\n\t\t\t\t\t\t\t\ttype=\"range\"\r\n\t\t\t\t\t\t\t\tmin={0}\r\n\t\t\t\t\t\t\t\tmax={1}\r\n\t\t\t\t\t\t\t\tstep={0.05}\r\n\t\t\t\t\t\t\t\tbind:value={volume}\r\n\t\t\t\t\t\t\t\toninput={(e) => {\r\n\t\t\t\t\t\t\t\t\tconst el = e.currentTarget as HTMLInputElement;\r\n\t\t\t\t\t\t\t\t\thandleVolumeChange(parseFloat(el.value));\r\n\t\t\t\t\t\t\t\t}}\r\n\t\t\t\t\t\t\t\tclass=\"absolute z-20 h-full w-full cursor-pointer opacity-0\"\r\n\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\tclass=\"absolute h-1 w-full rounded-full bg-white/20 transition-all group-hover/slider:h-1.5\"\r\n\t\t\t\t\t\t\t></div>\r\n\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\tclass=\"absolute h-1 rounded-full bg-white group-hover/slider:h-1.5\"\r\n\t\t\t\t\t\t\t\tstyle=\"width: {(volume / 1) * 100}%\"\r\n\t\t\t\t\t\t\t></div>\r\n\t\t\t\t\t\t\t<div\r\n\t\t\t\t\t\t\t\tclass=\"absolute h-3 w-3 rounded-full bg-white opacity-0 shadow transition-opacity group-hover/slider:opacity-100\"\r\n\t\t\t\t\t\t\t\tstyle=\"left: {(volume / 1) * 100}%; transform: translateX(-50%)\"\r\n\t\t\t\t\t\t\t></div>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\r\n\t\t\t\t<div class=\"text-xs font-medium text-white/90\">\r\n\t\t\t\t\t{formatTime(currentTime)} / {formatTime(duration || 0)}\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\r\n\t\t\t<button\r\n\t\t\t\tonclick={toggleFullscreen}\r\n\t\t\t\tclass=\"text-white/90 hover:text-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\r\n\t\t\t>\r\n\t\t\t\t{#if isFullscreen}\r\n\t\t\t\t\t<Minimize class=\"h-5 w-5\" />\r\n\t\t\t\t{:else}\r\n\t\t\t\t\t<Maximize class=\"h-5 w-5\" />\r\n\t\t\t\t{/if}\r\n\t\t\t</button>\r\n\t\t</div>\r\n\t</div>\r\n</div>\r\n\r\n<style>\r\n\tinput[type='range'] {\r\n\t\tappearance: none;\r\n\t}\r\n\r\n\tinput[type='range']::-webkit-slider-thumb {\r\n\t\tappearance: none;\r\n\t\theight: 0;\r\n\t\twidth: 0;\r\n\t\tborder: none;\r\n\t}\r\n\r\n\tinput[type='range']::-moz-range-thumb {\r\n\t\theight: 0;\r\n\t\twidth: 0;\r\n\t\tborder: none;\r\n\t\tbackground: transparent;\r\n\t}\r\n</style>\r\n",
			"type": "registry:ui",
			"target": "video/video.svelte"
		},
		{
			"content": "import Video from \"./video.svelte\";\r\n\r\nexport { Video };",
			"type": "registry:ui",
			"target": "video/index.ts"
		}
	]
}