{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "video-playlist-sidebar",
  "title": "Video Playlist Sidebar",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/video-playlist-sidebar/VideoPlaylistSidebar.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref } from 'vue'\nimport type { HTMLAttributes } from 'vue'\nimport {\n  Check,\n  CheckCircle2,\n  Clock,\n  Download,\n  FileCode2,\n  ListVideo,\n  Play,\n  PlayCircle,\n  Repeat,\n  Repeat1,\n  Search,\n  Shuffle,\n  SkipForward,\n} from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Progress } from '@/components/ui/progress'\nimport { Switch } from '@/components/ui/switch'\n\nexport interface VideoResource {\n  id: string\n  name: string\n  size: string\n  type: 'code' | 'figma' | 'pdf'\n}\n\nexport interface PlaylistItem {\n  id: string\n  index: number\n  title: string\n  instructor: string\n  instructorRole: string\n  instructorAvatar: string\n  duration: string\n  durationSec: number\n  category: string\n  summary: string\n  isCompleted: boolean\n  thumbnailGradient: string\n  resources: VideoResource[]\n}\n\nconst props = withDefaults(\n  defineProps<{\n    class?: HTMLAttributes['class']\n  }>(),\n  {},\n)\n\nconst autoPlay = ref(true)\nconst isLooping = ref(false)\nconst isShuffled = ref(false)\nconst searchQuery = ref('')\nconst activeVideoId = ref('vid-03')\nconst downloadedAssets = ref<Record<string, boolean>>({})\n\nconst playlistItems = ref<PlaylistItem[]>([\n  {\n    id: 'vid-01',\n    index: 1,\n    title: '01. Introduction to Component Registries',\n    instructor: 'Marcus Vance',\n    instructorRole: 'Principal Design Engineer',\n    instructorAvatar: 'MV',\n    duration: '14:20',\n    durationSec: 860,\n    category: 'Architecture',\n    summary: 'Understanding unbundled component distribution, registry manifests, and own-your-code paradigms.',\n    isCompleted: true,\n    thumbnailGradient: 'from-slate-800 to-zinc-950',\n    resources: [\n      { id: 'res-1a', name: 'registry-schema-spec.pdf', size: '1.2 MB', type: 'pdf' },\n      { id: 'res-1b', name: 'starter-registry.json', size: '48 KB', type: 'code' },\n    ],\n  },\n  {\n    id: 'vid-02',\n    index: 2,\n    title: '02. Headless Reka UI Primitives',\n    instructor: 'Elena Rostova',\n    instructorRole: 'Staff Frontend Engineer',\n    instructorAvatar: 'ER',\n    duration: '18:45',\n    durationSec: 1125,\n    category: 'Accessibility',\n    summary: 'Composition with asChild, polymorphic slot mechanics, and ARIA state tree management.',\n    isCompleted: true,\n    thumbnailGradient: 'from-zinc-800 to-slate-950',\n    resources: [\n      { id: 'res-2a', name: 'reka-composition-cheatsheet.pdf', size: '2.4 MB', type: 'pdf' },\n      { id: 'res-2b', name: 'accessible-dialog.vue', size: '14 KB', type: 'code' },\n    ],\n  },\n  {\n    id: 'vid-03',\n    index: 3,\n    title: '03. Building with OKLCH Tokens',\n    instructor: 'Sarah Connor',\n    instructorRole: 'Design Systems Architect',\n    instructorAvatar: 'SC',\n    duration: '22:10',\n    durationSec: 1330,\n    category: 'Color Systems',\n    summary: 'Perceptually uniform color spaces, wide-gamut Display P3 gamut mapping, and dark mode contrast.',\n    isCompleted: false,\n    thumbnailGradient: 'from-slate-850 to-zinc-950',\n    resources: [\n      { id: 'res-3a', name: 'tokens.config.css', size: '14.2 KB', type: 'code' },\n      { id: 'res-3b', name: 'oklch-palette-matrix.fig', size: '4.8 MB', type: 'figma' },\n      { id: 'res-3c', name: 'apca-contrast-guide.pdf', size: '1.1 MB', type: 'pdf' },\n    ],\n  },\n  {\n    id: 'vid-04',\n    index: 4,\n    title: '04. Micro-Interactions & Spring Physics',\n    instructor: 'Alex Chen',\n    instructorRole: 'Motion Design Lead',\n    instructorAvatar: 'AC',\n    duration: '19:35',\n    durationSec: 1175,\n    category: 'Motion & Physics',\n    summary: 'Crafting tactile UI states, gesture dampening, spring curves, and 120fps hardware acceleration.',\n    isCompleted: false,\n    thumbnailGradient: 'from-zinc-800 to-zinc-950',\n    resources: [\n      { id: 'res-4a', name: 'spring-curves-preset.ts', size: '8.4 KB', type: 'code' },\n      { id: 'res-4b', name: 'gesture-lab.fig', size: '3.6 MB', type: 'figma' },\n    ],\n  },\n  {\n    id: 'vid-05',\n    index: 5,\n    title: '05. Multi-Framework Parity Testing',\n    instructor: 'Marcus Vance',\n    instructorRole: 'Principal Design Engineer',\n    instructorAvatar: 'MV',\n    duration: '16:50',\n    durationSec: 1010,\n    category: 'Testing & Quality',\n    summary: 'Automating visual regression tests and unit tests across dual Vue 3 and React 19 packages.',\n    isCompleted: false,\n    thumbnailGradient: 'from-slate-800 to-slate-950',\n    resources: [\n      { id: 'res-5a', name: 'parity-test-matrix.ts', size: '22.0 KB', type: 'code' },\n      { id: 'res-5b', name: 'playwright.config.ts', size: '6.5 KB', type: 'code' },\n    ],\n  },\n  {\n    id: 'vid-06',\n    index: 6,\n    title: '06. CI/CD Deployment to Cloudflare',\n    instructor: 'David Kim',\n    instructorRole: 'DevOps & Cloud Engineer',\n    instructorAvatar: 'DK',\n    duration: '25:15',\n    durationSec: 1515,\n    category: 'DevOps & Edge',\n    summary: 'Automating multi-framework build chains, registry sync, and edge deployment via Wrangler.',\n    isCompleted: false,\n    thumbnailGradient: 'from-zinc-850 to-zinc-950',\n    resources: [\n      { id: 'res-6a', name: 'wrangler.toml', size: '3.4 KB', type: 'code' },\n      { id: 'res-6b', name: 'deploy-pipeline.yml', size: '5.1 KB', type: 'code' },\n    ],\n  },\n])\n\nconst activeVideo = computed(() => {\n  return playlistItems.value.find((item) => item.id === activeVideoId.value) || playlistItems.value[0]\n})\n\nconst completedCount = computed(() => {\n  return playlistItems.value.filter((item) => item.isCompleted).length\n})\n\nconst totalCount = computed(() => playlistItems.value.length)\n\nconst progressPercent = computed(() => {\n  if (totalCount.value === 0) return 0\n  return Math.round((completedCount.value / totalCount.value) * 100)\n})\n\nconst filteredVideos = computed(() => {\n  const query = searchQuery.value.trim().toLowerCase()\n  if (!query) return playlistItems.value\n  return playlistItems.value.filter(\n    (item) =>\n      item.title.toLowerCase().includes(query) ||\n      item.instructor.toLowerCase().includes(query) ||\n      item.category.toLowerCase().includes(query),\n  )\n})\n\nfunction selectVideo(id: string) {\n  activeVideoId.value = id\n}\n\nfunction toggleComplete(id: string) {\n  const video = playlistItems.value.find((item) => item.id === id)\n  if (video) {\n    video.isCompleted = !video.isCompleted\n  }\n}\n\nfunction nextVideo() {\n  const currentIndex = playlistItems.value.findIndex((item) => item.id === activeVideoId.value)\n  if (currentIndex === -1) return\n\n  if (isShuffled.value) {\n    const remaining = playlistItems.value.filter((item) => item.id !== activeVideoId.value)\n    if (remaining.length > 0) {\n      const randomIndex = Math.floor(Math.random() * remaining.length)\n      activeVideoId.value = remaining[randomIndex].id\n    }\n    return\n  }\n\n  if (currentIndex < playlistItems.value.length - 1) {\n    activeVideoId.value = playlistItems.value[currentIndex + 1].id\n  } else if (isLooping.value) {\n    activeVideoId.value = playlistItems.value[0].id\n  }\n}\n\nfunction handleDownload(assetId: string) {\n  downloadedAssets.value[assetId] = true\n  setTimeout(() => {\n    downloadedAssets.value[assetId] = false\n  }, 2000)\n}\n</script>\n\n<template>\n  <div\n    data-slot=\"video-playlist-sidebar\"\n    :class=\"cn('text-foreground mx-auto w-full max-w-2xl space-y-4', props.class)\"\n  >\n    <!-- Main Playlist Control Card -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardHeader class=\"space-y-3 pb-3 sm:pb-4\">\n        <!-- Badge row -->\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div class=\"flex items-center gap-2\">\n            <Badge variant=\"secondary\" class=\"gap-1.5 text-xs font-semibold\">\n              <ListVideo class=\"text-primary size-3.5\" />\n              <span>Video Playlist</span>\n            </Badge>\n            <Badge variant=\"outline\" class=\"text-muted-foreground gap-1 font-mono text-xs\">\n              <Clock class=\"size-3\" />\n              <span>1h 56m Total</span>\n            </Badge>\n          </div>\n          <span class=\"text-muted-foreground text-xs font-medium\">UIPKGE Masterclass Series</span>\n        </div>\n\n        <!-- Title & Subtitle -->\n        <div class=\"space-y-1\">\n          <CardTitle class=\"text-foreground text-lg font-bold tracking-tight sm:text-xl\">\n            Mastering Full-Stack UI Engineering\n          </CardTitle>\n          <CardDescription class=\"text-xs\">\n            Curated playlist on headless primitives, OKLCH token systems, fluid motion physics, and registry CI\n            pipelines.\n          </CardDescription>\n        </div>\n\n        <!-- Progress bar and counter -->\n        <div class=\"bg-muted/40 border-border/70 space-y-2 rounded-lg border p-3\">\n          <div class=\"flex items-center justify-between text-xs\">\n            <span class=\"text-foreground font-semibold\">\n              <span class=\"font-mono tabular-nums\">{{ completedCount }}</span> /\n              <span class=\"font-mono tabular-nums\">{{ totalCount }}</span> Videos Completed\n            </span>\n            <span class=\"text-primary font-mono font-bold tabular-nums\">{{ progressPercent }}%</span>\n          </div>\n          <Progress :model-value=\"progressPercent\" class=\"h-2\" />\n        </div>\n\n        <!-- Toolbar: Autoplay switch, Loop & Shuffle toggles -->\n        <div class=\"flex flex-wrap items-center justify-between gap-3 pt-1\">\n          <!-- Autoplay next switch -->\n          <div class=\"flex items-center gap-2.5\">\n            <Switch id=\"autoplay-switch\" :model-value=\"autoPlay\" size=\"sm\" @update:model-value=\"autoPlay = $event\" />\n            <label\n              for=\"autoplay-switch\"\n              class=\"text-foreground flex cursor-pointer items-center gap-1.5 text-xs font-medium select-none\"\n            >\n              <Play class=\"text-primary fill-primary size-3.5\" />\n              <span>Autoplay Next Video</span>\n            </label>\n          </div>\n\n          <!-- Loop and Shuffle Buttons -->\n          <div class=\"flex items-center gap-1.5\">\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              :class=\"\n                cn(\n                  'h-7.5 gap-1.5 px-2.5 text-xs font-medium',\n                  isLooping\n                    ? 'border-primary/30 bg-primary/10 text-primary border font-semibold'\n                    : 'text-muted-foreground hover:text-foreground',\n                )\n              \"\n              :aria-pressed=\"isLooping\"\n              aria-label=\"Toggle loop playlist\"\n              @click=\"isLooping = !isLooping\"\n            >\n              <Repeat1 v-if=\"isLooping\" class=\"size-3.5\" />\n              <Repeat v-else class=\"size-3.5\" />\n              <span>Loop</span>\n            </Button>\n\n            <Button\n              variant=\"ghost\"\n              size=\"sm\"\n              :class=\"\n                cn(\n                  'h-7.5 gap-1.5 px-2.5 text-xs font-medium',\n                  isShuffled\n                    ? 'border-primary/30 bg-primary/10 text-primary border font-semibold'\n                    : 'text-muted-foreground hover:text-foreground',\n                )\n              \"\n              :aria-pressed=\"isShuffled\"\n              aria-label=\"Toggle shuffle playlist\"\n              @click=\"isShuffled = !isShuffled\"\n            >\n              <Shuffle class=\"size-3.5\" />\n              <span>Shuffle</span>\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n    </Card>\n\n    <!-- Currently Active Video Card -->\n    <Card\n      class=\"border-primary/40 from-primary/5 via-card to-card relative overflow-hidden bg-gradient-to-br shadow-xs\"\n    >\n      <div class=\"bg-primary absolute top-0 right-0 left-0 h-0.5\" />\n      <CardHeader class=\"pb-3\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div class=\"flex items-center gap-2\">\n            <Badge variant=\"default\" class=\"gap-1.5 text-xs font-semibold\">\n              <span class=\"size-1.5 animate-pulse rounded-full bg-white\" />\n              <span>Now Playing</span>\n            </Badge>\n            <Badge variant=\"outline\" class=\"font-mono text-xs font-medium\">\n              Lesson {{ activeVideo.index }} of {{ totalCount }}\n            </Badge>\n          </div>\n          <span class=\"text-muted-foreground font-mono text-xs font-semibold tabular-nums\">\n            {{ activeVideo.duration }}\n          </span>\n        </div>\n\n        <div class=\"space-y-1 pt-1\">\n          <CardTitle class=\"text-foreground text-base font-bold tracking-tight sm:text-lg\">\n            {{ activeVideo.title }}\n          </CardTitle>\n          <CardDescription class=\"text-xs leading-relaxed\">\n            {{ activeVideo.summary }}\n          </CardDescription>\n        </div>\n\n        <!-- Instructor and Metadata -->\n        <div class=\"flex flex-wrap items-center justify-between gap-3 pt-2\">\n          <div class=\"flex items-center gap-2\">\n            <div\n              class=\"border-primary/30 bg-primary/15 text-primary flex size-7 items-center justify-center rounded-full border text-xs font-bold\"\n            >\n              {{ activeVideo.instructorAvatar }}\n            </div>\n            <div>\n              <p class=\"text-foreground text-xs font-semibold\">{{ activeVideo.instructor }}</p>\n              <p class=\"text-muted-foreground text-xs\">{{ activeVideo.instructorRole }} · {{ activeVideo.category }}</p>\n            </div>\n          </div>\n\n          <!-- Video status controls -->\n          <div class=\"flex items-center gap-2\">\n            <Button\n              :variant=\"activeVideo.isCompleted ? 'secondary' : 'outline'\"\n              size=\"sm\"\n              class=\"h-7.5 gap-1.5 px-2.5 text-xs font-medium\"\n              @click=\"toggleComplete(activeVideo.id)\"\n            >\n              <CheckCircle2 v-if=\"activeVideo.isCompleted\" class=\"text-success size-3.5\" />\n              <Check v-else class=\"size-3.5\" />\n              <span>{{ activeVideo.isCompleted ? 'Completed' : 'Mark as Watched' }}</span>\n            </Button>\n\n            <Button size=\"sm\" class=\"h-7.5 gap-1.5 px-2.5 text-xs font-semibold shadow-xs\" @click=\"nextVideo\">\n              <span>Next</span>\n              <SkipForward class=\"size-3.5\" />\n            </Button>\n          </div>\n        </div>\n      </CardHeader>\n\n      <!-- Downloadable Lesson Exercise Files -->\n      <CardContent class=\"border-border/60 bg-muted/20 border-t pt-3 pb-3.5\">\n        <div class=\"space-y-2\">\n          <div class=\"flex items-center justify-between\">\n            <p class=\"text-foreground flex items-center gap-1.5 text-xs font-bold tracking-tight uppercase\">\n              <FileCode2 class=\"text-primary size-3.5\" />\n              <span>Lesson Exercise Files</span>\n            </p>\n            <span class=\"text-muted-foreground font-mono text-xs\">{{ activeVideo.resources.length }} Files</span>\n          </div>\n\n          <div class=\"grid grid-cols-1 gap-2 sm:grid-cols-2\">\n            <div\n              v-for=\"res in activeVideo.resources\"\n              :key=\"res.id\"\n              class=\"border-border/80 bg-card hover:bg-muted/40 flex items-center justify-between gap-2 rounded-lg border p-2 text-xs transition-colors\"\n            >\n              <div class=\"flex min-w-0 items-center gap-2\">\n                <span\n                  class=\"border-primary/20 bg-primary/10 text-primary flex size-6 shrink-0 items-center justify-center rounded font-mono text-xs font-bold\"\n                >\n                  {{ res.type === 'code' ? 'TS' : res.type === 'figma' ? 'FIG' : 'PDF' }}\n                </span>\n                <div class=\"min-w-0\">\n                  <p class=\"text-foreground truncate text-xs font-medium\">{{ res.name }}</p>\n                  <p class=\"text-muted-foreground font-mono text-xs\">{{ res.size }}</p>\n                </div>\n              </div>\n\n              <Button\n                aria-label=\"Download attachment\"\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-6.5 shrink-0 gap-1 px-2 text-xs font-medium\"\n                @click=\"handleDownload(res.id)\"\n              >\n                <Check v-if=\"downloadedAssets[res.id]\" class=\"text-success size-3\" />\n                <Download v-else class=\"size-3\" />\n                <span>{{ downloadedAssets[res.id] ? 'Saved' : 'Get' }}</span>\n              </Button>\n            </div>\n          </div>\n        </div>\n      </CardContent>\n    </Card>\n\n    <!-- Playlist Queue & List Header -->\n    <Card class=\"border-border bg-card shadow-xs\">\n      <CardHeader class=\"space-y-3 pb-3\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <div class=\"flex items-center gap-2\">\n            <CardTitle class=\"text-foreground text-sm font-bold tracking-tight\">Playlist Queue</CardTitle>\n            <Badge variant=\"secondary\" class=\"font-mono text-xs\">{{ totalCount }} Videos</Badge>\n          </div>\n          <span class=\"text-muted-foreground text-xs font-medium\">Click any lesson to play</span>\n        </div>\n\n        <!-- Search / Filter input -->\n        <div class=\"relative\">\n          <Search class=\"text-muted-foreground absolute top-2.5 left-2.5 size-3.5\" />\n          <input\n            v-model=\"searchQuery\"\n            type=\"text\"\n            placeholder=\"Filter playlist by title, instructor, or topic...\"\n            class=\"border-input bg-background placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 h-8 w-full rounded-md border pr-3 pl-8 text-xs shadow-xs outline-none focus-visible:ring-[3px]\"\n          />\n        </div>\n      </CardHeader>\n\n      <!-- Video Items List -->\n      <CardContent class=\"divide-border/60 divide-y p-0\">\n        <div\n          v-for=\"video in filteredVideos\"\n          :key=\"video.id\"\n          :class=\"\n            cn(\n              'group relative flex cursor-pointer items-center justify-between gap-3 p-3 transition-colors duration-150',\n              activeVideoId === video.id\n                ? 'border-primary/50 bg-primary/10 ring-primary/20 shadow-xs ring-1'\n                : 'hover:bg-muted/40',\n            )\n          \"\n          tabindex=\"0\"\n          role=\"button\"\n          :aria-label=\"`Play ${video.title}`\"\n          :aria-current=\"activeVideoId === video.id ? 'true' : undefined\"\n          @click=\"selectVideo(video.id)\"\n          @keydown.enter=\"selectVideo(video.id)\"\n          @keydown.space.prevent=\"selectVideo(video.id)\"\n        >\n          <!-- Left active highlight bar -->\n          <div\n            v-if=\"activeVideoId === video.id\"\n            class=\"bg-primary absolute top-2 bottom-2 left-0 w-1 rounded-r shadow-xs\"\n          />\n\n          <!-- Left Section: Index / Status Indicator + Video Thumbnail -->\n          <div class=\"flex min-w-0 items-center gap-3\">\n            <!-- Play / Equalizer / Completed Status Icon -->\n            <div class=\"flex size-6 shrink-0 items-center justify-center\">\n              <!-- If active: Animated Equalizer Sound Bars -->\n              <div\n                v-if=\"activeVideoId === video.id\"\n                class=\"flex h-3.5 w-3.5 items-end justify-center gap-0.5\"\n                title=\"Playing\"\n              >\n                <span class=\"bg-primary h-3 w-0.5 animate-pulse rounded-full\" />\n                <span class=\"bg-primary h-4 w-0.5 animate-pulse rounded-full\" style=\"animation-delay: 150ms\" />\n                <span class=\"bg-primary h-2 w-0.5 animate-pulse rounded-full\" style=\"animation-delay: 300ms\" />\n              </div>\n\n              <!-- If completed: Checkmark -->\n              <CheckCircle2 v-else-if=\"video.isCompleted\" class=\"text-success size-4 shrink-0\" aria-hidden=\"true\" />\n\n              <!-- If unwatched: Index Number -->\n              <span v-else class=\"text-muted-foreground font-mono text-xs font-semibold tabular-nums\">\n                {{ video.index.toString().padStart(2, '0') }}\n              </span>\n            </div>\n\n            <!-- 16:9 Video Thumbnail Preview -->\n            <div\n              :class=\"\n                cn(\n                  'relative aspect-video w-24 shrink-0 overflow-hidden rounded-md border border-zinc-800 bg-zinc-950 bg-gradient-to-br shadow-xs sm:w-28',\n                  video.thumbnailGradient,\n                )\n              \"\n            >\n              <!-- Center Play Watermark -->\n              <div class=\"absolute inset-0 flex items-center justify-center\">\n                <PlayCircle\n                  :class=\"\n                    cn(\n                      'size-5 transition-transform group-hover:scale-110',\n                      activeVideoId === video.id ? 'text-primary fill-primary/20' : 'text-muted-foreground/80',\n                    )\n                  \"\n                />\n              </div>\n\n              <!-- Duration Badge (Bottom-Right) -->\n              <div\n                class=\"absolute right-1 bottom-1 rounded bg-black/85 px-1 py-0.5 font-mono text-xs font-semibold text-white tabular-nums backdrop-blur-xs\"\n              >\n                {{ video.duration }}\n              </div>\n            </div>\n\n            <!-- Video Title & Instructor Info -->\n            <div class=\"min-w-0 space-y-0.5\">\n              <p\n                :class=\"\n                  cn(\n                    'truncate text-xs font-semibold transition-colors sm:text-sm',\n                    activeVideoId === video.id ? 'text-primary font-bold' : 'text-foreground group-hover:text-primary',\n                  )\n                \"\n              >\n                {{ video.title }}\n              </p>\n              <p class=\"text-muted-foreground truncate text-xs\">\n                <span>{{ video.instructor }}</span>\n                <span class=\"mx-1.5\">·</span>\n                <span>{{ video.category }}</span>\n              </p>\n            </div>\n          </div>\n\n          <!-- Right Section: Status Badge & Quick Watch Toggle -->\n          <div class=\"flex shrink-0 items-center gap-2\">\n            <Badge\n              v-if=\"activeVideoId === video.id\"\n              variant=\"default\"\n              class=\"hidden px-2 py-0.5 text-xs font-semibold sm:inline-flex\"\n            >\n              Playing\n            </Badge>\n            <Badge\n              v-else-if=\"video.isCompleted\"\n              variant=\"outline\"\n              class=\"text-success text-success hidden gap-1 px-1.5 py-0.5 text-xs font-medium sm:inline-flex\"\n            >\n              <Check class=\"size-3\" />\n              <span>Watched</span>\n            </Badge>\n\n            <!-- Toggle complete button -->\n            <Button\n              variant=\"ghost\"\n              size=\"icon\"\n              class=\"text-muted-foreground hover:text-foreground size-7 shrink-0\"\n              :aria-label=\"video.isCompleted ? 'Mark as unwatched' : 'Mark as watched'\"\n              @click.stop=\"toggleComplete(video.id)\"\n            >\n              <CheckCircle2 v-if=\"video.isCompleted\" class=\"text-success size-4\" />\n              <Play v-else-if=\"activeVideoId === video.id\" class=\"text-primary size-3.5 fill-current\" />\n              <Check v-else class=\"text-muted-foreground/60 hover:text-foreground size-3.5\" />\n            </Button>\n          </div>\n        </div>\n\n        <div v-if=\"filteredVideos.length === 0\" class=\"text-muted-foreground p-8 text-center text-xs\">\n          No lessons match your search criteria.\n        </div>\n      </CardContent>\n\n      <CardFooter class=\"border-border/60 bg-muted/20 flex items-center justify-between border-t p-3 text-xs\">\n        <span class=\"text-muted-foreground\">Mastering Full-Stack UI Engineering</span>\n        <span class=\"text-muted-foreground font-mono font-medium tabular-nums\">\n          {{ completedCount }}/{{ totalCount }} Completed ({{ progressPercent }}%)\n        </span>\n      </CardFooter>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/VideoPlaylistSidebar.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/progress.json",
    "https://uipkge.dev/r/vue/switch.json"
  ],
  "description": "YouTube and Vimeo style video playlist drawer with active playing indicator, durations, auto-play next switch, loop/shuffle controls, and active lesson summary card.",
  "categories": [
    "media",
    "app",
    "education"
  ]
}