{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "media-asset-gallery",
  "title": "Media Asset Gallery",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/media-asset-gallery/MediaAssetGallery.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, ref } from 'vue'\nimport {\n  ArrowDownToLine,\n  Check,\n  CheckCircle2,\n  Code2,\n  Copy,\n  ExternalLink,\n  Eye,\n  FileCode,\n  HardDrive,\n  Image as ImageIcon,\n  Layers,\n  Palette,\n  Search,\n  Shapes,\n  Share2,\n  Tag,\n  Upload,\n  Video,\n  X,\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 } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Separator } from '@/components/ui/separator'\nimport {\n  Sheet,\n  SheetClose,\n  SheetContent,\n  SheetDescription,\n  SheetFooter,\n  SheetHeader,\n  SheetTitle,\n} from '@/components/ui/sheet'\nimport { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'\n\nexport interface AssetItem {\n  id: string\n  title: string\n  filename: string\n  category: 'all' | 'images' | 'vectors' | 'videos' | 'logos'\n  type: 'image' | 'vector' | 'video' | 'logo'\n  format: string\n  aspectRatio: '16:9' | '1:1' | '9:16' | 'vector'\n  aspectRatioLabel: string\n  resolutionBadge: string\n  dimensions: string\n  fileSize: string\n  uploadedBy: {\n    name: string\n    role: string\n    avatarInitials: string\n  }\n  uploadDate: string\n  license: string\n  colorSpace: string\n  palette: string[]\n  tags: string[]\n  previewUrl: string\n  downloadOptions: {\n    id: string\n    label: string\n    resolution: string\n    size: string\n    badge?: string\n  }[]\n}\n\nconst props = defineProps<{\n  class?: HTMLAttributes['class']\n}>()\n\nconst ASSETS: AssetItem[] = [\n  {\n    id: 'dam-01',\n    title: 'Hyperion Cloud UI Design System',\n    filename: 'hyperion-cloud-design-system-4k.png',\n    category: 'images',\n    type: 'image',\n    format: 'PNG',\n    aspectRatio: '16:9',\n    aspectRatioLabel: '16:9 Landscape',\n    resolutionBadge: '4K',\n    dimensions: '3840 × 2160',\n    fileSize: '2.4 MB',\n    uploadedBy: {\n      name: 'Elena Vance',\n      role: 'Brand Architecture Lead',\n      avatarInitials: 'EV',\n    },\n    uploadDate: 'Aug 18, 2026',\n    license: 'Enterprise Commercial',\n    colorSpace: 'Display P3 · 300 DPI',\n    palette: ['#0F172A', '#2563EB', '#38BDF8', '#10B981', '#F8FAFC'],\n    tags: ['#marketing', '#product-ui'],\n    previewUrl: 'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      { id: 'opt-1', label: 'Original Master (4K)', resolution: '3840 × 2160', size: '2.4 MB', badge: 'Lossless' },\n      { id: 'opt-2', label: 'Web Optimized (1080p)', resolution: '1920 × 1080', size: '640 KB', badge: 'Recommended' },\n      { id: 'opt-3', label: 'Mobile Thumbnail', resolution: '640 × 360', size: '92 KB' },\n    ],\n  },\n  {\n    id: 'dam-02',\n    title: 'NeoCorp Brand Identity & Vector Glyphs',\n    filename: 'neocorp-brand-vector-glyphs.svg',\n    category: 'vectors',\n    type: 'vector',\n    format: 'SVG',\n    aspectRatio: 'vector',\n    aspectRatioLabel: 'Raw Vector',\n    resolutionBadge: 'Vector',\n    dimensions: 'Vector Bezier',\n    fileSize: '420 KB',\n    uploadedBy: {\n      name: 'Marcus Chen',\n      role: 'Principal Iconographer',\n      avatarInitials: 'MC',\n    },\n    uploadDate: 'Aug 15, 2026',\n    license: 'Internal Brand Core',\n    colorSpace: 'sRGB · Vector Paths',\n    palette: ['#4F46E5', '#7C3AED', '#EC4899', '#312E81', '#FFFFFF'],\n    tags: ['#brand-guidelines', '#marketing'],\n    previewUrl: 'https://images.unsplash.com/photo-1634017839464-5c339ebe3cb4?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      { id: 'opt-1', label: 'Master SVG Vector', resolution: 'Scalable Bezier', size: '420 KB', badge: 'Source' },\n      { id: 'opt-2', label: 'PNG Hi-Res Raster', resolution: '4096 × 4096', size: '1.1 MB' },\n      { id: 'opt-3', label: 'PDF Vector Spec', resolution: 'Vector PDF', size: '850 KB' },\n    ],\n  },\n  {\n    id: 'dam-03',\n    title: 'Solaris OS 2.0 Product Launch Teaser',\n    filename: 'solaris-os-launch-teaser-h265.mp4',\n    category: 'videos',\n    type: 'video',\n    format: 'MP4',\n    aspectRatio: '16:9',\n    aspectRatioLabel: '16:9 Landscape',\n    resolutionBadge: '4K',\n    dimensions: '3840 × 2160',\n    fileSize: '18.6 MB',\n    uploadedBy: {\n      name: 'Sarah Jenkins',\n      role: 'Motion Art Director',\n      avatarInitials: 'SJ',\n    },\n    uploadDate: 'Aug 12, 2026',\n    license: 'Global Broadcast Sync',\n    colorSpace: 'Rec. 709 · 60 FPS ProRes',\n    palette: ['#18181B', '#EA580C', '#F59E0B', '#06B6D4', '#FAFAFA'],\n    tags: ['#marketing', '#product-ui'],\n    previewUrl: 'https://images.unsplash.com/photo-1550745165-9bc0b252726f?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      {\n        id: 'opt-1',\n        label: 'Master 4K Video (ProRes)',\n        resolution: '3840 × 2160 · 60fps',\n        size: '18.6 MB',\n        badge: 'Cinema',\n      },\n      { id: 'opt-2', label: 'Web Stream MP4 (1080p)', resolution: '1920 × 1080 · 30fps', size: '4.8 MB', badge: 'Web' },\n      { id: 'opt-3', label: 'Poster Frame Keyframe', resolution: '3840 × 2160 PNG', size: '1.9 MB' },\n    ],\n  },\n  {\n    id: 'dam-04',\n    title: 'Executive Leadership — Studio Headshot 01',\n    filename: 'alex-morgan-executive-portrait.jpg',\n    category: 'images',\n    type: 'image',\n    format: 'JPG',\n    aspectRatio: '1:1',\n    aspectRatioLabel: '1:1 Square',\n    resolutionBadge: '2K',\n    dimensions: '2048 × 2048',\n    fileSize: '1.8 MB',\n    uploadedBy: {\n      name: 'David Kim',\n      role: 'Staff Photographer',\n      avatarInitials: 'DK',\n    },\n    uploadDate: 'Aug 10, 2026',\n    license: 'Press Kit Editorial',\n    colorSpace: 'Adobe RGB (1998) · 300 DPI',\n    palette: ['#0F172A', '#334155', '#94A3B8', '#0EA5E9', '#F8FAFC'],\n    tags: ['#headshots', '#brand-guidelines'],\n    previewUrl: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      { id: 'opt-1', label: 'Full Resolution (2K RAW)', resolution: '2048 × 2048', size: '1.8 MB', badge: 'Print' },\n      { id: 'opt-2', label: 'Square Avatar Web', resolution: '800 × 800', size: '210 KB', badge: 'Web' },\n      { id: 'opt-3', label: 'Compressed WebP', resolution: '400 × 400', size: '45 KB' },\n    ],\n  },\n  {\n    id: 'dam-05',\n    title: 'Mobile App Onboarding Carousel — Portrait',\n    filename: 'mobile-onboarding-mockup-9-16.png',\n    category: 'images',\n    type: 'image',\n    format: 'PNG',\n    aspectRatio: '9:16',\n    aspectRatioLabel: '9:16 Portrait',\n    resolutionBadge: '1080p',\n    dimensions: '1080 × 1920',\n    fileSize: '1.2 MB',\n    uploadedBy: {\n      name: 'Aria Rodriguez',\n      role: 'Mobile UX Architect',\n      avatarInitials: 'AR',\n    },\n    uploadDate: 'Aug 08, 2026',\n    license: 'App Store / Marketing',\n    colorSpace: 'Display P3 · 460 PPI',\n    palette: ['#09090B', '#7C3AED', '#C026D3', '#38BDF8', '#FFFFFF'],\n    tags: ['#product-ui', '#marketing'],\n    previewUrl: 'https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      { id: 'opt-1', label: 'High Density 9:16 PNG', resolution: '1080 × 1920', size: '1.2 MB', badge: 'Retina' },\n      { id: 'opt-2', label: 'Standard Mobile Display', resolution: '720 × 1280', size: '420 KB' },\n      { id: 'opt-3', label: 'Story Template JPEG', resolution: '1080 × 1920', size: '280 KB' },\n    ],\n  },\n  {\n    id: 'dam-06',\n    title: 'UIPKGE Monogram & Wordmark Master Kit',\n    filename: 'uipkge-monogram-symbol-master.svg',\n    category: 'logos',\n    type: 'logo',\n    format: 'SVG',\n    aspectRatio: '1:1',\n    aspectRatioLabel: '1:1 Square',\n    resolutionBadge: 'Vector',\n    dimensions: '1024 × 1024',\n    fileSize: '180 KB',\n    uploadedBy: {\n      name: 'Elena Vance',\n      role: 'Brand Architecture Lead',\n      avatarInitials: 'EV',\n    },\n    uploadDate: 'Aug 04, 2026',\n    license: 'Master Trademark',\n    colorSpace: 'Pantone Solid Coated · Vector',\n    palette: ['#000000', '#059669', '#047857', '#34D399', '#FFFFFF'],\n    tags: ['#brand-guidelines'],\n    previewUrl: 'https://images.unsplash.com/photo-1620641788421-7a1c342ea42e?w=1200&auto=format&fit=crop&q=80',\n    downloadOptions: [\n      { id: 'opt-1', label: 'SVG Master Vector', resolution: 'Scalable Vector', size: '180 KB', badge: 'Primary' },\n      { id: 'opt-2', label: 'Transparent PNG Kit', resolution: '2048 × 2048', size: '340 KB' },\n      { id: 'opt-3', label: 'Favicon & App Icon Bundle', resolution: 'Multi-Res .ICO', size: '95 KB' },\n    ],\n  },\n]\n\nconst searchQuery = ref('')\nconst selectedCategory = ref('all')\nconst selectedAspectRatio = ref<string>('all')\nconst selectedTag = ref<string | null>(null)\n\nconst selectedAsset = ref<AssetItem | null>(null)\nconst isDrawerOpen = ref(false)\nconst selectedResolutionOption = ref<string>('opt-1')\n\nconst notificationToast = ref<string | null>(null)\nlet toastTimeout: ReturnType<typeof setTimeout> | null = null\n\nfunction showNotification(msg: string) {\n  if (toastTimeout) clearTimeout(toastTimeout)\n  notificationToast.value = msg\n  toastTimeout = setTimeout(() => {\n    notificationToast.value = null\n  }, 2800)\n}\n\nconst copiedColor = ref<string | null>(null)\nlet colorTimeout: ReturnType<typeof setTimeout> | null = null\n\nfunction copyHex(color: string) {\n  navigator.clipboard?.writeText(color)\n  copiedColor.value = color\n  showNotification(`Copied color hex ${color} to clipboard`)\n  if (colorTimeout) clearTimeout(colorTimeout)\n  colorTimeout = setTimeout(() => {\n    copiedColor.value = null\n  }, 2000)\n}\n\nconst copiedEmbed = ref(false)\nlet embedTimeout: ReturnType<typeof setTimeout> | null = null\n\nfunction copyEmbedSnippet(asset: AssetItem) {\n  const code = `<img src=\"${asset.previewUrl}\" alt=\"${asset.title}\" loading=\"lazy\" />`\n  navigator.clipboard?.writeText(code)\n  copiedEmbed.value = true\n  showNotification('Copied HTML embed snippet to clipboard')\n  if (embedTimeout) clearTimeout(embedTimeout)\n  embedTimeout = setTimeout(() => {\n    copiedEmbed.value = false\n  }, 2200)\n}\n\nfunction copyAssetLink(asset: AssetItem, event?: MouseEvent) {\n  if (event) event.stopPropagation()\n  navigator.clipboard?.writeText(`https://cdn.uipkge.dev/assets/${asset.filename}`)\n  showNotification(`Link copied for \"${asset.title}\"`)\n}\n\nfunction triggerInstantDownload(asset: AssetItem, event?: MouseEvent) {\n  if (event) event.stopPropagation()\n  showNotification(`Downloading ${asset.filename} (${asset.fileSize})...`)\n}\n\nfunction openInspector(asset: AssetItem) {\n  selectedAsset.value = asset\n  selectedResolutionOption.value = asset.downloadOptions[0]?.id || 'opt-1'\n  isDrawerOpen.value = true\n}\n\nfunction toggleTag(tag: string) {\n  if (selectedTag.value === tag) {\n    selectedTag.value = null\n  } else {\n    selectedTag.value = tag\n  }\n}\n\nfunction resetFilters() {\n  selectedTag.value = null\n  selectedAspectRatio.value = 'all'\n  searchQuery.value = ''\n  selectedCategory.value = 'all'\n}\n\nconst filteredAssets = computed(() => {\n  return ASSETS.filter((item) => {\n    // Category match\n    if (selectedCategory.value !== 'all' && item.category !== selectedCategory.value) {\n      return false\n    }\n\n    // Aspect ratio match\n    if (selectedAspectRatio.value !== 'all' && item.aspectRatio !== selectedAspectRatio.value) {\n      return false\n    }\n\n    // Tag chip match\n    if (selectedTag.value && !item.tags.includes(selectedTag.value)) {\n      return false\n    }\n\n    // Search query match\n    if (searchQuery.value.trim() !== '') {\n      const q = searchQuery.value.toLowerCase().trim()\n      const titleMatch = item.title.toLowerCase().includes(q)\n      const fileMatch = item.filename.toLowerCase().includes(q)\n      const tagMatch = item.tags.some((t) => t.toLowerCase().includes(q))\n      const formatMatch = item.format.toLowerCase().includes(q)\n      if (!titleMatch && !fileMatch && !tagMatch && !formatMatch) {\n        return false\n      }\n    }\n\n    return true\n  })\n})\n\nconst aspectFilterOptions = [\n  { value: 'all', label: 'All Ratios' },\n  { value: '16:9', label: '16:9 Landscape' },\n  { value: '1:1', label: '1:1 Square' },\n  { value: '9:16', label: '9:16 Portrait' },\n  { value: 'vector', label: 'Raw Vector' },\n]\n\nconst availableTags = ['#marketing', '#product-ui', '#brand-guidelines', '#headshots']\n</script>\n\n<template>\n  <div data-slot=\"media-asset-gallery\" :class=\"cn('w-full space-y-6', props.class)\">\n    <!-- Top Floating Toast Notification -->\n    <div\n      v-if=\"notificationToast\"\n      role=\"status\"\n      aria-live=\"polite\"\n      class=\"bg-foreground text-background animate-in fade-in slide-in-from-top-2 fixed top-6 right-6 z-50 flex items-center gap-2.5 rounded-lg px-4 py-2.5 text-xs font-medium shadow-lg transition-colors\"\n    >\n      <CheckCircle2 class=\"text-primary size-4 shrink-0\" />\n      <span>{{ notificationToast }}</span>\n    </div>\n\n    <!-- Header Section -->\n    <div\n      class=\"bg-card text-card-foreground border-border/80 flex flex-col gap-5 rounded-2xl border p-5 shadow-xs sm:flex-row sm:items-center sm:justify-between lg:p-6\"\n    >\n      <div class=\"space-y-1\">\n        <div class=\"flex flex-wrap items-center gap-2.5\">\n          <div class=\"bg-primary/10 text-primary grid size-9 place-items-center rounded-xl\">\n            <Layers class=\"size-5\" />\n          </div>\n          <h1 class=\"text-xl font-bold tracking-tight sm:text-2xl\">Digital Asset Management (DAM)</h1>\n          <Badge variant=\"secondary\" class=\"font-mono text-xs\"> v2.4 </Badge>\n        </div>\n        <p class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n          <HardDrive class=\"size-3.5\" />\n          <span class=\"font-medium\">Total Assets:</span>\n          <span class=\"text-foreground font-semibold tabular-nums\">1,420 media files · 24.8 GB</span>\n          <span class=\"text-border hidden sm:inline\">|</span>\n          <span class=\"text-muted-foreground hidden sm:inline\">Cloud Sync Active</span>\n          <span class=\"bg-success inline-block size-1.5 rounded-full\" />\n        </p>\n      </div>\n\n      <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n        <!-- Search Input -->\n        <div class=\"relative w-full sm:w-64 lg:w-72\">\n          <Search\n            aria-hidden=\"true\"\n            class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\"\n          />\n          <Input\n            v-model=\"searchQuery\"\n            type=\"search\"\n            placeholder=\"Search assets or tags…\"\n            class=\"h-9 pr-8 pl-9 text-xs\"\n          />\n          <button\n            v-if=\"searchQuery\"\n            type=\"button\"\n            class=\"text-muted-foreground hover:text-foreground absolute top-1/2 right-2.5 min-h-6 -translate-y-1/2 text-xs\"\n            aria-label=\"Clear search\"\n            @click=\"searchQuery = ''\"\n          >\n            <X class=\"size-3.5\" />\n          </button>\n        </div>\n\n        <!-- Upload Media Button -->\n        <Button\n          size=\"sm\"\n          class=\"shrink-0 font-medium\"\n          @click=\"showNotification('Upload dialog ready: Drop media files to ingest.')\"\n        >\n          <Upload class=\"size-4\" />\n          Upload Media\n        </Button>\n      </div>\n    </div>\n\n    <!-- Filter Toolbar -->\n    <div class=\"bg-card text-card-foreground border-border/80 space-y-4 rounded-2xl border p-4 shadow-xs sm:p-5\">\n      <!-- Category Tabs and Aspect Ratio Filters -->\n      <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n        <!-- Type Filter Tabs -->\n        <Tabs v-model=\"selectedCategory\" class=\"w-full lg:w-auto\">\n          <TabsList class=\"grid w-full grid-cols-2 gap-1 sm:grid-cols-5 lg:w-auto\">\n            <TabsTrigger value=\"all\" class=\"text-xs\"> All Assets </TabsTrigger>\n            <TabsTrigger value=\"images\" class=\"text-xs\">\n              <ImageIcon class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n              Images\n            </TabsTrigger>\n            <TabsTrigger value=\"vectors\" class=\"text-xs\">\n              <FileCode class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n              Vectors & Icons\n            </TabsTrigger>\n            <TabsTrigger value=\"videos\" class=\"text-xs\">\n              <Video class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n              Videos\n            </TabsTrigger>\n            <TabsTrigger value=\"logos\" class=\"text-xs\">\n              <Shapes class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n              Brand Logos\n            </TabsTrigger>\n          </TabsList>\n        </Tabs>\n\n        <!-- Aspect Ratio Filter Pills -->\n        <div class=\"flex flex-wrap items-center gap-1.5 overflow-x-auto pb-1 sm:pb-0\">\n          <span class=\"text-muted-foreground mr-1 text-xs font-medium\">Ratio:</span>\n          <button\n            v-for=\"opt in aspectFilterOptions\"\n            :key=\"opt.value\"\n            type=\"button\"\n            :class=\"\n              cn(\n                'focus-visible:ring-ring min-h-6 rounded-lg px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                selectedAspectRatio === opt.value\n                  ? 'bg-primary text-primary-foreground shadow-xs'\n                  : 'bg-muted/70 text-muted-foreground hover:bg-muted hover:text-foreground',\n              )\n            \"\n            @click=\"selectedAspectRatio = opt.value\"\n          >\n            {{ opt.label }}\n          </button>\n        </div>\n      </div>\n\n      <Separator class=\"bg-border/60\" />\n\n      <!-- Tag Chips and Result Meta -->\n      <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n        <div class=\"flex flex-wrap items-center gap-2\">\n          <span class=\"text-muted-foreground flex items-center gap-1 text-xs font-medium\">\n            <Tag class=\"size-3.5\" />\n            Tags:\n          </span>\n          <button\n            v-for=\"tag in availableTags\"\n            :key=\"tag\"\n            type=\"button\"\n            :class=\"\n              cn(\n                'focus-visible:ring-ring min-h-6 rounded-md border px-2.5 py-0.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',\n                selectedTag === tag\n                  ? 'border-primary bg-primary/10 text-primary font-semibold'\n                  : 'border-border bg-background text-muted-foreground hover:border-foreground/30 hover:text-foreground',\n              )\n            \"\n            @click=\"toggleTag(tag)\"\n          >\n            {{ tag }}\n          </button>\n          <button\n            v-if=\"selectedTag || selectedAspectRatio !== 'all' || searchQuery || selectedCategory !== 'all'\"\n            type=\"button\"\n            class=\"text-muted-foreground hover:text-foreground ml-1 min-h-6 text-xs underline underline-offset-2\"\n            @click=\"resetFilters\"\n          >\n            Clear filters\n          </button>\n        </div>\n\n        <div class=\"text-muted-foreground text-xs tabular-nums\">\n          Showing <span class=\"text-foreground font-semibold\">{{ filteredAssets.length }}</span> of\n          {{ ASSETS.length }} assets\n        </div>\n      </div>\n    </div>\n\n    <!-- Asset Grid (3-column, responsive) -->\n    <div v-if=\"filteredAssets.length > 0\" class=\"grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3\">\n      <Card\n        v-for=\"asset in filteredAssets\"\n        :key=\"asset.id\"\n        class=\"border-border/80 bg-card hover:border-border group relative flex flex-col overflow-hidden rounded-xl border p-0 shadow-xs transition-colors duration-200 hover:shadow-md\"\n        @click=\"openInspector(asset)\"\n      >\n        <!-- Preview Container with Aspect Ratio Tags and Overlay Actions -->\n        <div class=\"bg-muted/40 relative aspect-video w-full cursor-pointer overflow-hidden\">\n          <img\n            :src=\"asset.previewUrl\"\n            :alt=\"asset.title\"\n            loading=\"lazy\"\n            class=\"size-full object-cover transition-transform duration-300 group-hover:scale-105\"\n          />\n\n          <!-- Gradient Overlay on Hover -->\n          <div\n            class=\"absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-black/30 opacity-0 transition-opacity duration-200 group-hover:opacity-100\"\n          />\n\n          <!-- Top Aspect Ratio & Resolution Badges -->\n          <div class=\"absolute top-2.5 left-2.5 flex flex-wrap items-center gap-1.5\">\n            <Badge\n              variant=\"secondary\"\n              class=\"bg-background/90 text-foreground font-mono text-xs shadow-xs backdrop-blur-xs\"\n            >\n              {{ asset.aspectRatio }}\n            </Badge>\n            <Badge\n              variant=\"outline\"\n              class=\"bg-background/90 text-foreground font-mono text-xs shadow-xs backdrop-blur-xs\"\n            >\n              {{ asset.resolutionBadge }}\n            </Badge>\n          </div>\n\n          <!-- Top Right Format Pill -->\n          <div class=\"absolute top-2.5 right-2.5\">\n            <Badge class=\"bg-primary/95 text-primary-foreground font-mono text-xs shadow-xs\">\n              {{ asset.format }}\n            </Badge>\n          </div>\n\n          <!-- Bottom Hover Overlay Actions -->\n          <div\n            class=\"absolute right-2.5 bottom-2.5 left-2.5 flex items-center justify-between opacity-0 transition-opacity duration-200 group-hover:opacity-100\"\n          >\n            <div class=\"flex items-center gap-1 text-xs font-medium text-white drop-shadow-xs\">\n              <Eye class=\"size-3.5\" />\n              <span>Inspect</span>\n            </div>\n            <div class=\"flex items-center gap-1.5\">\n              <Button\n                variant=\"secondary\"\n                size=\"icon-xs\"\n                class=\"size-7 bg-white/90 text-zinc-900 shadow-xs hover:bg-white\"\n                title=\"Copy asset link\"\n                aria-label=\"Copy asset link\"\n                @click=\"(e: MouseEvent) => copyAssetLink(asset, e)\"\n              >\n                <Copy class=\"size-3.5\" />\n              </Button>\n              <Button\n                variant=\"default\"\n                size=\"icon-xs\"\n                class=\"size-7 shadow-xs\"\n                title=\"Instant download\"\n                aria-label=\"Instant download\"\n                @click=\"(e: MouseEvent) => triggerInstantDownload(asset, e)\"\n              >\n                <ArrowDownToLine class=\"size-3.5\" />\n              </Button>\n            </div>\n          </div>\n        </div>\n\n        <!-- Asset Card Content -->\n        <CardContent class=\"flex flex-1 flex-col justify-between gap-3 p-4\">\n          <div class=\"space-y-1.5\">\n            <div class=\"flex items-start justify-between gap-2\">\n              <h2 class=\"group-hover:text-primary line-clamp-1 text-sm font-semibold tracking-tight transition-colors\">\n                {{ asset.title }}\n              </h2>\n            </div>\n            <p class=\"text-muted-foreground truncate font-mono text-xs\">\n              {{ asset.filename }}\n            </p>\n          </div>\n\n          <!-- Tags -->\n          <div class=\"flex flex-wrap items-center gap-1.5\">\n            <span\n              v-for=\"t in asset.tags\"\n              :key=\"t\"\n              class=\"bg-muted/70 text-muted-foreground rounded-md px-2 py-0.5 text-xs font-medium\"\n            >\n              {{ t }}\n            </span>\n          </div>\n\n          <!-- Metadata Footer Row -->\n          <div class=\"border-border/60 flex items-center justify-between border-t pt-3 text-xs\">\n            <span class=\"text-muted-foreground font-mono tabular-nums\">\n              {{ asset.dimensions }}\n            </span>\n            <span class=\"text-foreground font-semibold tabular-nums\"> {{ asset.fileSize }} · {{ asset.format }} </span>\n          </div>\n        </CardContent>\n      </Card>\n    </div>\n\n    <!-- Empty State -->\n    <div\n      v-else\n      class=\"bg-card text-card-foreground border-border/80 flex flex-col items-center justify-center rounded-2xl border p-12 text-center shadow-xs\"\n    >\n      <div class=\"bg-muted/70 text-muted-foreground mb-3 grid size-12 place-items-center rounded-xl\">\n        <Search class=\"size-6\" />\n      </div>\n      <h2 class=\"text-base font-semibold\">No assets found</h2>\n      <p class=\"text-muted-foreground mt-1 max-w-sm text-xs\">\n        No media assets match your active category, aspect ratio, or search query. Try clearing filters to view all\n        assets.\n      </p>\n      <Button variant=\"outline\" size=\"sm\" class=\"mt-2 text-xs\" @click=\"resetFilters\"> Clear All Filters </Button>\n    </div>\n\n    <!-- Asset Detail Inspector Drawer (Sheet) -->\n    <Sheet v-model:open=\"isDrawerOpen\">\n      <SheetContent\n        side=\"right\"\n        class=\"border-border bg-card text-card-foreground w-full space-y-6 overflow-y-auto p-6 sm:max-w-md lg:max-w-lg\"\n      >\n        <div v-if=\"selectedAsset\" class=\"space-y-6\">\n          <!-- Drawer Header -->\n          <SheetHeader class=\"space-y-1 text-left\">\n            <div class=\"flex items-center gap-2\">\n              <Badge class=\"bg-primary/95 text-primary-foreground font-mono text-xs\">\n                {{ selectedAsset.format }}\n              </Badge>\n              <Badge variant=\"outline\" class=\"font-mono text-xs\">\n                {{ selectedAsset.aspectRatioLabel }}\n              </Badge>\n              <Badge variant=\"secondary\" class=\"font-mono text-xs\">\n                {{ selectedAsset.resolutionBadge }}\n              </Badge>\n            </div>\n            <SheetTitle class=\"text-lg font-bold tracking-tight\">\n              {{ selectedAsset.title }}\n            </SheetTitle>\n            <SheetDescription class=\"text-muted-foreground truncate font-mono text-xs\">\n              {{ selectedAsset.filename }}\n            </SheetDescription>\n          </SheetHeader>\n\n          <!-- Full Media Preview with Actions -->\n          <div class=\"border-border/80 bg-muted/40 relative overflow-hidden rounded-xl border\">\n            <img :src=\"selectedAsset.previewUrl\" :alt=\"selectedAsset.title\" class=\"aspect-video w-full object-cover\" />\n            <div\n              class=\"bg-card/90 border-border/80 absolute right-2 bottom-2 flex items-center gap-1 rounded-lg border p-1 backdrop-blur-xs\"\n            >\n              <Button\n                variant=\"ghost\"\n                size=\"icon-xs\"\n                class=\"size-6 text-xs\"\n                title=\"Copy Direct URL\"\n                @click=\"copyAssetLink(selectedAsset)\"\n              >\n                <Copy class=\"size-3\" />\n              </Button>\n              <Button\n                variant=\"ghost\"\n                size=\"icon-xs\"\n                class=\"size-6 text-xs\"\n                title=\"Open preview in new tab\"\n                as=\"a\"\n                :href=\"selectedAsset.previewUrl\"\n                target=\"_blank\"\n                rel=\"noreferrer\"\n              >\n                <ExternalLink class=\"size-3\" />\n              </Button>\n            </div>\n          </div>\n\n          <!-- Color Palette Swatches -->\n          <div class=\"space-y-2.5\">\n            <div class=\"flex items-center justify-between text-xs\">\n              <span class=\"text-foreground flex items-center gap-1.5 font-semibold\">\n                <Palette class=\"text-primary size-3.5\" />\n                Color Palette Swatches\n              </span>\n              <span class=\"text-muted-foreground text-xs\">Click swatch to copy HEX</span>\n            </div>\n            <div class=\"grid grid-cols-5 gap-2\">\n              <button\n                v-for=\"color in selectedAsset.palette\"\n                :key=\"color\"\n                type=\"button\"\n                class=\"group/color border-border/60 hover:border-primary focus-visible:ring-ring flex flex-col items-center gap-1.5 rounded-lg border p-2 text-center transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n                @click=\"copyHex(color)\"\n              >\n                <span\n                  class=\"size-7 rounded-md border border-black/10 shadow-inner transition-transform group-hover/color:scale-110\"\n                  :style=\"{ backgroundColor: color }\"\n                />\n                <span class=\"text-muted-foreground group-hover/color:text-foreground font-mono text-xs uppercase\">\n                  {{ color }}\n                </span>\n              </button>\n            </div>\n          </div>\n\n          <Separator class=\"bg-border/60\" />\n\n          <!-- Metadata Breakdown Grid -->\n          <div class=\"space-y-3\">\n            <h3 class=\"text-foreground text-xs font-semibold tracking-wider uppercase\">Asset Metadata</h3>\n            <div class=\"bg-muted/40 border-border/60 grid grid-cols-2 gap-3 rounded-xl border p-3.5 text-xs\">\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">Dimensions</span>\n                <span class=\"text-foreground font-mono font-medium tabular-nums\">{{ selectedAsset.dimensions }}</span>\n              </div>\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">File Size</span>\n                <span class=\"text-foreground font-semibold tabular-nums\">{{ selectedAsset.fileSize }}</span>\n              </div>\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">Color Space</span>\n                <span class=\"text-foreground font-medium\">{{ selectedAsset.colorSpace }}</span>\n              </div>\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">License</span>\n                <span class=\"text-foreground font-medium\">{{ selectedAsset.license }}</span>\n              </div>\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">Uploaded By</span>\n                <div class=\"mt-0.5 flex items-center gap-1.5\">\n                  <span\n                    class=\"bg-primary/10 text-primary grid size-4.5 place-items-center rounded-full text-xs font-semibold\"\n                  >\n                    {{ selectedAsset.uploadedBy.avatarInitials }}\n                  </span>\n                  <span class=\"text-foreground font-medium\">{{ selectedAsset.uploadedBy.name }}</span>\n                </div>\n              </div>\n              <div>\n                <span class=\"text-muted-foreground block text-xs\">Upload Date</span>\n                <span class=\"text-foreground font-medium tabular-nums\">{{ selectedAsset.uploadDate }}</span>\n              </div>\n            </div>\n          </div>\n\n          <!-- Resolution Download Choices -->\n          <div class=\"space-y-3\">\n            <div class=\"flex items-center justify-between\">\n              <h3 class=\"text-foreground text-xs font-semibold tracking-wider uppercase\">Download Resolution</h3>\n              <span class=\"text-muted-foreground font-mono text-xs\">Format: {{ selectedAsset.format }}</span>\n            </div>\n\n            <div class=\"space-y-2\">\n              <button\n                v-for=\"opt in selectedAsset.downloadOptions\"\n                :key=\"opt.id\"\n                type=\"button\"\n                :class=\"\n                  cn(\n                    'focus-visible:ring-ring flex w-full items-center justify-between rounded-xl border p-3 text-left transition-[background-color,box-shadow] focus-visible:ring-2 focus-visible:outline-none',\n                    selectedResolutionOption === opt.id\n                      ? 'border-primary bg-primary/5 text-foreground shadow-xs'\n                      : 'border-border/70 bg-card hover:bg-muted/40 text-muted-foreground',\n                  )\n                \"\n                @click=\"selectedResolutionOption = opt.id\"\n              >\n                <div class=\"space-y-0.5\">\n                  <div class=\"flex items-center gap-2\">\n                    <span class=\"text-foreground text-xs font-semibold\">{{ opt.label }}</span>\n                    <Badge v-if=\"opt.badge\" variant=\"secondary\" class=\"px-1.5 py-0 text-xs font-normal\">\n                      {{ opt.badge }}\n                    </Badge>\n                  </div>\n                  <span class=\"text-muted-foreground font-mono text-xs tabular-nums\">\n                    {{ opt.resolution }}\n                  </span>\n                </div>\n                <div class=\"flex items-center gap-3\">\n                  <span class=\"text-foreground text-xs font-semibold tabular-nums\">{{ opt.size }}</span>\n                  <span\n                    :class=\"\n                      cn(\n                        'flex size-4 items-center justify-center rounded-full border',\n                        selectedResolutionOption === opt.id\n                          ? 'border-primary bg-primary text-primary-foreground'\n                          : 'border-border',\n                      )\n                    \"\n                  >\n                    <Check v-if=\"selectedResolutionOption === opt.id\" class=\"size-2.5\" />\n                  </span>\n                </div>\n              </button>\n            </div>\n          </div>\n\n          <!-- Embed Code Copy -->\n          <div class=\"space-y-2\">\n            <div class=\"flex items-center justify-between text-xs\">\n              <span class=\"text-foreground flex items-center gap-1.5 font-semibold\">\n                <Code2 class=\"text-primary size-3.5\" />\n                HTML CDN Embed Snippet\n              </span>\n              <button\n                type=\"button\"\n                class=\"text-primary flex min-h-6 items-center gap-1 text-xs font-medium hover:underline\"\n                @click=\"copyEmbedSnippet(selectedAsset)\"\n              >\n                <Copy class=\"size-3\" />\n                <span>{{ copiedEmbed ? 'Copied!' : 'Copy Code' }}</span>\n              </button>\n            </div>\n            <div\n              class=\"bg-muted/60 border-border/80 text-muted-foreground overflow-x-auto rounded-xl border p-2.5 font-mono text-xs\"\n            >\n              &lt;img src=\"{{ selectedAsset.previewUrl }}\" alt=\"{{ selectedAsset.title }}\" loading=\"lazy\" /&gt;\n            </div>\n          </div>\n\n          <!-- Sheet Footer Actions -->\n          <SheetFooter class=\"border-border/80 flex flex-col-reverse gap-2 border-t pt-4 sm:flex-row sm:justify-end\">\n            <SheetClose as-child>\n              <Button variant=\"outline\" size=\"sm\" class=\"text-xs font-medium\"> Close </Button>\n            </SheetClose>\n            <Button variant=\"outline\" size=\"sm\" class=\"text-xs font-medium\" @click=\"copyAssetLink(selectedAsset)\">\n              <Share2 class=\"size-3.5\" />\n              Share Link\n            </Button>\n            <Button\n              aria-label=\"Download attachment\"\n              size=\"sm\"\n              class=\"text-xs font-medium\"\n              @click=\"triggerInstantDownload(selectedAsset)\"\n            >\n              <ArrowDownToLine class=\"size-3.5\" />\n              Download Asset\n            </Button>\n          </SheetFooter>\n        </div>\n      </SheetContent>\n    </Sheet>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/MediaAssetGallery.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/input.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/sheet.json",
    "https://uipkge.dev/r/vue/tabs.json"
  ],
  "description": "Figma/Unsplash style Digital Asset Manager (DAM) with resolution tags, aspect ratio filters, tag chips, color palette inspector, multi-resolution download picker, and slide-over inspector drawer.",
  "categories": [
    "media",
    "app",
    "dashboard"
  ]
}