{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "media-asset-gallery",
  "title": "Media Asset Gallery",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/media-asset-gallery/media-asset-gallery.component.ts",
      "content": "// Figma/Unsplash style Digital Asset Manager (DAM) with resolution tags, aspect ratio\n// filters, tag chips, color palette inspector, multi-resolution download picker, and\n// slide-over inspector drawer. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\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  LucideAngularModule,\n  Palette,\n  Search,\n  Share2,\n  Shapes,\n  Tag,\n  Upload,\n  Video,\n  X,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport {\n  UiSheetCloseComponent,\n  UiSheetComponent,\n  UiSheetContentComponent,\n  UiSheetDescriptionComponent,\n  UiSheetFooterComponent,\n  UiSheetHeaderComponent,\n  UiSheetTitleComponent,\n} from '@/ui/sheet/sheet.component'\nimport { UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent } from '@/ui/tabs/tabs.component'\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: { name: string; role: string; avatarInitials: string }\n  uploadDate: string\n  license: string\n  colorSpace: string\n  palette: string[]\n  tags: string[]\n  previewUrl: string\n  downloadOptions: { id: string; label: string; resolution: string; size: string; badge?: string }[]\n}\n\nexport const DEFAULT_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: { name: 'Elena Vance', role: 'Brand Architecture Lead', avatarInitials: 'EV' },\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: { name: 'Marcus Chen', role: 'Principal Iconographer', avatarInitials: 'MC' },\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: { name: 'Sarah Jenkins', role: 'Motion Art Director', avatarInitials: 'SJ' },\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: { name: 'David Kim', role: 'Staff Photographer', avatarInitials: 'DK' },\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: { name: 'Aria Rodriguez', role: 'Mobile UX Architect', avatarInitials: 'AR' },\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: { name: 'Elena Vance', role: 'Brand Architecture Lead', avatarInitials: 'EV' },\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 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\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-media-asset-gallery, [ui-media-asset-gallery]',\n  standalone: true,\n  // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiInputComponent,\n    UiSeparatorComponent,\n    UiSheetCloseComponent,\n    UiSheetComponent,\n    UiSheetContentComponent,\n    UiSheetDescriptionComponent,\n    UiSheetFooterComponent,\n    UiSheetHeaderComponent,\n    UiSheetTitleComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n  ],\n  template: `\n    <div data-slot=\"media-asset-gallery\" [class]=\"rootClass\">\n      <!-- Top Floating Toast Notification -->\n      @if (notificationToast()) {\n        <div\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          <lucide-icon [img]=\"CheckCircle2\" class=\"text-primary size-4 shrink-0\" />\n          <span>{{ notificationToast() }}</span>\n        </div>\n      }\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              <lucide-icon [img]=\"Layers\" class=\"size-5\" />\n            </div>\n            <h1 class=\"text-xl font-bold tracking-tight sm:text-2xl\">Digital Asset Management (DAM)</h1>\n            <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\"> v2.4 </span>\n          </div>\n          <p class=\"text-muted-foreground flex flex-wrap items-center gap-2 text-xs\">\n            <lucide-icon [img]=\"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\"></span>\n          </p>\n        </div>\n\n        <div class=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n          <div class=\"relative w-full sm:w-64 lg:w-72\">\n            <lucide-icon\n              [img]=\"Search\"\n              class=\"text-muted-foreground pointer-events-none absolute top-1/2 left-3 size-4 -translate-y-1/2\"\n            />\n            <ui-input\n              [value]=\"searchQuery()\"\n              (valueChange)=\"searchQuery.set($event)\"\n              type=\"search\"\n              placeholder=\"Search assets or tags…\"\n              class=\"h-9 pr-8 pl-9 text-xs\"\n            />\n            @if (searchQuery()) {\n              <button\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.set('')\"\n              >\n                <lucide-icon [img]=\"X\" class=\"size-3.5\" />\n              </button>\n            }\n          </div>\n\n          <button\n            ui-button\n            size=\"sm\"\n            class=\"shrink-0 font-medium\"\n            (click)=\"showNotification('Upload dialog ready: Drop media files to ingest.')\"\n          >\n            <lucide-icon [img]=\"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        <div class=\"flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between\">\n          <ui-tabs [value]=\"selectedCategory()\" (valueChange)=\"setCategory($event)\" class=\"w-full lg:w-auto\">\n            <ui-tabs-list class=\"grid w-full grid-cols-2 gap-1 sm:grid-cols-5 lg:w-auto\">\n              <button ui-tabs-trigger value=\"all\" class=\"text-xs\">All Assets</button>\n              <button ui-tabs-trigger value=\"images\" class=\"text-xs\">\n                <lucide-icon [img]=\"ImageIcon\" class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n                Images\n              </button>\n              <button ui-tabs-trigger value=\"vectors\" class=\"text-xs\">\n                <lucide-icon [img]=\"FileCode\" class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n                Vectors & Icons\n              </button>\n              <button ui-tabs-trigger value=\"videos\" class=\"text-xs\">\n                <lucide-icon [img]=\"Video\" class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n                Videos\n              </button>\n              <button ui-tabs-trigger value=\"logos\" class=\"text-xs\">\n                <lucide-icon [img]=\"Shapes\" class=\"mr-1.5 hidden size-3.5 sm:inline\" />\n                Brand Logos\n              </button>\n            </ui-tabs-list>\n          </ui-tabs>\n\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            @for (opt of aspectOptions; track opt.value) {\n              <button type=\"button\" [class]=\"ratioPillClass(opt.value)\" (click)=\"selectedAspectRatio.set(opt.value)\">\n                {{ opt.label }}\n              </button>\n            }\n          </div>\n        </div>\n\n        <div ui-separator class=\"bg-border/60\"></div>\n\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              <lucide-icon [img]=\"Tag\" class=\"size-3.5\" />\n              Tags:\n            </span>\n            @for (tag of tags; track tag) {\n              <button type=\"button\" [class]=\"tagChipClass(tag)\" (click)=\"toggleTag(tag)\">{{ tag }}</button>\n            }\n            @if (isFiltered()) {\n              <button\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            }\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 -->\n      @if (filteredAssets().length > 0) {\n        <div class=\"grid grid-cols-1 gap-5 md:grid-cols-2 lg:grid-cols-3\">\n          @for (asset of filteredAssets(); track asset.id) {\n            <div\n              ui-card\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              <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                <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                ></div>\n                <div class=\"absolute top-2.5 left-2.5 flex flex-wrap items-center gap-1.5\">\n                  <span\n                    ui-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                  </span>\n                  <span\n                    ui-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                  </span>\n                </div>\n                <div class=\"absolute top-2.5 right-2.5\">\n                  <span ui-badge class=\"bg-primary/95 text-primary-foreground font-mono text-xs shadow-xs\">\n                    {{ asset.format }}\n                  </span>\n                </div>\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                    <lucide-icon [img]=\"Eye\" class=\"size-3.5\" />\n                    <span>Inspect</span>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <button\n                      ui-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)=\"copyAssetLink(asset, $event)\"\n                    >\n                      <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                    </button>\n                    <button\n                      ui-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)=\"triggerInstantDownload(asset, $event)\"\n                    >\n                      <lucide-icon [img]=\"ArrowDownToLine\" class=\"size-3.5\" />\n                    </button>\n                  </div>\n                </div>\n              </div>\n\n              <div ui-card-content 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\n                      class=\"group-hover:text-primary line-clamp-1 text-sm font-semibold tracking-tight transition-colors\"\n                    >\n                      {{ asset.title }}\n                    </h2>\n                  </div>\n                  <p class=\"text-muted-foreground truncate font-mono text-xs\">{{ asset.filename }}</p>\n                </div>\n                <div class=\"flex flex-wrap items-center gap-1.5\">\n                  @for (t of asset.tags; track t) {\n                    <span class=\"bg-muted/70 text-muted-foreground rounded-md px-2 py-0.5 text-xs font-medium\">\n                      {{ t }}\n                    </span>\n                  }\n                </div>\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\">{{ asset.dimensions }}</span>\n                  <span class=\"text-foreground font-semibold tabular-nums\"\n                    >{{ asset.fileSize }} · {{ asset.format }}</span\n                  >\n                </div>\n              </div>\n            </div>\n          }\n        </div>\n      } @else {\n        <div\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            <lucide-icon [img]=\"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 ui-button variant=\"outline\" size=\"sm\" class=\"mt-2 text-xs\" (click)=\"resetFilters()\">\n            Clear All Filters\n          </button>\n        </div>\n      }\n\n      <!-- Asset Detail Inspector Drawer (Sheet) -->\n      <div ui-sheet [open]=\"isDrawerOpen()\" (openChange)=\"isDrawerOpen.set($event)\">\n        <div\n          ui-sheet-content\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          @if (selectedAsset(); as asset) {\n            <div ui-sheet-header class=\"space-y-1 text-left\">\n              <div class=\"flex items-center gap-2\">\n                <span ui-badge class=\"bg-primary/95 text-primary-foreground font-mono text-xs\">{{ asset.format }}</span>\n                <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">{{ asset.aspectRatioLabel }}</span>\n                <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\">{{ asset.resolutionBadge }}</span>\n              </div>\n              <h2 ui-sheet-title class=\"text-lg font-bold tracking-tight\">{{ asset.title }}</h2>\n              <p ui-sheet-description class=\"text-muted-foreground truncate font-mono text-xs\">{{ asset.filename }}</p>\n            </div>\n\n            <div class=\"border-border/80 bg-muted/40 relative overflow-hidden rounded-xl border\">\n              <img [src]=\"asset.previewUrl\" [alt]=\"asset.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                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon-xs\"\n                  class=\"size-6 text-xs\"\n                  title=\"Copy Direct URL\"\n                  (click)=\"copyAssetLink(asset)\"\n                >\n                  <lucide-icon [img]=\"Copy\" class=\"size-3\" />\n                </button>\n                <a\n                  ui-button\n                  variant=\"ghost\"\n                  size=\"icon-xs\"\n                  class=\"size-6 text-xs\"\n                  title=\"Open preview in new tab\"\n                  [href]=\"asset.previewUrl\"\n                  target=\"_blank\"\n                  rel=\"noreferrer\"\n                >\n                  <lucide-icon [img]=\"ExternalLink\" class=\"size-3\" />\n                </a>\n              </div>\n            </div>\n\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                  <lucide-icon [img]=\"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                @for (color of asset.palette; track color) {\n                  <button\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                    ></span>\n                    <span class=\"text-muted-foreground group-hover/color:text-foreground font-mono text-xs uppercase\">\n                      {{ color }}\n                    </span>\n                  </button>\n                }\n              </div>\n            </div>\n\n            <div ui-separator class=\"bg-border/60\"></div>\n\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\">{{ asset.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\">{{ asset.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\">{{ asset.colorSpace }}</span>\n                </div>\n                <div>\n                  <span class=\"text-muted-foreground block text-xs\">License</span>\n                  <span class=\"text-foreground font-medium\">{{ asset.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                      {{ asset.uploadedBy.avatarInitials }}\n                    </span>\n                    <span class=\"text-foreground font-medium\">{{ asset.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\">{{ asset.uploadDate }}</span>\n                </div>\n              </div>\n            </div>\n\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: {{ asset.format }}</span>\n              </div>\n              <div class=\"space-y-2\">\n                @for (opt of asset.downloadOptions; track opt.id) {\n                  <button\n                    type=\"button\"\n                    [class]=\"downloadOptionClass(opt.id)\"\n                    (click)=\"selectedResolutionOption.set(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                        @if (opt.badge) {\n                          <span ui-badge variant=\"secondary\" class=\"px-1.5 py-0 text-xs font-normal\">\n                            {{ opt.badge }}\n                          </span>\n                        }\n                      </div>\n                      <span class=\"text-muted-foreground font-mono text-xs tabular-nums\">{{ opt.resolution }}</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 [class]=\"downloadRadioClass(opt.id)\">\n                        @if (selectedResolutionOption() === opt.id) {\n                          <lucide-icon [img]=\"Check\" class=\"size-2.5\" />\n                        }\n                      </span>\n                    </div>\n                  </button>\n                }\n              </div>\n            </div>\n\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                  <lucide-icon [img]=\"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(asset)\"\n                >\n                  <lucide-icon [img]=\"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=\"{{ asset.previewUrl }}\" alt=\"{{ asset.title }}\" loading=\"lazy\" /&gt;\n              </div>\n            </div>\n\n            <div\n              ui-sheet-footer\n              class=\"border-border/80 flex flex-col-reverse gap-2 border-t pt-4 sm:flex-row sm:justify-end\"\n            >\n              <div ui-sheet-close>\n                <button ui-button variant=\"outline\" size=\"sm\" class=\"text-xs font-medium\">Close</button>\n              </div>\n              <button ui-button variant=\"outline\" size=\"sm\" class=\"text-xs font-medium\" (click)=\"copyAssetLink(asset)\">\n                <lucide-icon [img]=\"Share2\" class=\"size-3.5\" />\n                Share Link\n              </button>\n              <button\n                ui-button\n                aria-label=\"Download attachment\"\n                size=\"sm\"\n                class=\"text-xs font-medium\"\n                (click)=\"triggerInstantDownload(asset)\"\n              >\n                <lucide-icon [img]=\"ArrowDownToLine\" class=\"size-3.5\" />\n                Download Asset\n              </button>\n            </div>\n          }\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiMediaAssetGalleryComponent {\n  protected readonly ArrowDownToLine = ArrowDownToLine\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Code2 = Code2\n  protected readonly Copy = Copy\n  protected readonly ExternalLink = ExternalLink\n  protected readonly Eye = Eye\n  protected readonly FileCode = FileCode\n  protected readonly HardDrive = HardDrive\n  protected readonly ImageIcon = ImageIcon\n  protected readonly Layers = Layers\n  protected readonly Palette = Palette\n  protected readonly Search = Search\n  protected readonly Share2 = Share2\n  protected readonly Shapes = Shapes\n  protected readonly Tag = Tag\n  protected readonly Upload = Upload\n  protected readonly Video = Video\n  protected readonly X = X\n\n  protected readonly aspectOptions = aspectFilterOptions\n  protected readonly tags = availableTags\n\n  @Input() set initialAssets(v: AssetItem[] | undefined) {\n    if (v) this.assetsSignal.set([...v])\n  }\n  @Input('class') className?: string\n\n  readonly assetsSignal = signal<AssetItem[]>([...DEFAULT_ASSETS])\n  get assets(): AssetItem[] {\n    return this.assetsSignal()\n  }\n\n  readonly searchQuery = signal('')\n  readonly selectedCategory = signal('all')\n  readonly selectedAspectRatio = signal('all')\n  readonly selectedTag = signal<string | null>(null)\n\n  readonly selectedAsset = signal<AssetItem | null>(null)\n  readonly isDrawerOpen = signal(false)\n  readonly selectedResolutionOption = signal('opt-1')\n\n  readonly notificationToast = signal<string | null>(null)\n  private toastTimeout: ReturnType<typeof setTimeout> | undefined\n\n  readonly copiedColor = signal<string | null>(null)\n  private colorTimeout: ReturnType<typeof setTimeout> | undefined\n\n  readonly copiedEmbed = signal(false)\n  private embedTimeout: ReturnType<typeof setTimeout> | undefined\n\n  readonly filteredAssets = computed(() => {\n    return this.assetsSignal().filter((item) => {\n      if (this.selectedCategory() !== 'all' && item.category !== this.selectedCategory()) return false\n      if (this.selectedAspectRatio() !== 'all' && item.aspectRatio !== this.selectedAspectRatio()) return false\n      if (this.selectedTag() && !item.tags.includes(this.selectedTag()!)) return false\n      if (this.searchQuery().trim() !== '') {\n        const q = this.searchQuery().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) return false\n      }\n      return true\n    })\n  })\n\n  readonly isFiltered = computed(\n    () =>\n      !!this.selectedTag() ||\n      this.selectedAspectRatio() !== 'all' ||\n      !!this.searchQuery() ||\n      this.selectedCategory() !== 'all',\n  )\n\n  get rootClass(): string {\n    return cn('w-full space-y-6', this.className)\n  }\n\n  setCategory(v: string): void {\n    this.selectedCategory.set(v)\n  }\n\n  ratioPillClass(value: string): string {\n    return 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      this.selectedAspectRatio() === 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\n  tagChipClass(tag: string): string {\n    return 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      this.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\n  downloadOptionClass(id: string): string {\n    return 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      this.selectedResolutionOption() === 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\n  downloadRadioClass(id: string): string {\n    return cn(\n      'flex size-4 items-center justify-center rounded-full border',\n      this.selectedResolutionOption() === id ? 'border-primary bg-primary text-primary-foreground' : 'border-border',\n    )\n  }\n\n  showNotification(msg: string): void {\n    if (this.toastTimeout) clearTimeout(this.toastTimeout)\n    this.notificationToast.set(msg)\n    this.toastTimeout = setTimeout(() => this.notificationToast.set(null), 2800)\n  }\n\n  copyHex(color: string): void {\n    try {\n      navigator.clipboard?.writeText(color)\n    } catch {\n      // Clipboard fallback\n    }\n    this.copiedColor.set(color)\n    this.showNotification(`Copied color hex ${color} to clipboard`)\n    if (this.colorTimeout) clearTimeout(this.colorTimeout)\n    this.colorTimeout = setTimeout(() => this.copiedColor.set(null), 2000)\n  }\n\n  copyEmbedSnippet(asset: AssetItem): void {\n    const code = `<img src=\"${asset.previewUrl}\" alt=\"${asset.title}\" loading=\"lazy\" />`\n    try {\n      navigator.clipboard?.writeText(code)\n    } catch {\n      // Clipboard fallback\n    }\n    this.copiedEmbed.set(true)\n    this.showNotification('Copied HTML embed snippet to clipboard')\n    if (this.embedTimeout) clearTimeout(this.embedTimeout)\n    this.embedTimeout = setTimeout(() => this.copiedEmbed.set(false), 2200)\n  }\n\n  copyAssetLink(asset: AssetItem, event?: Event): void {\n    event?.stopPropagation()\n    try {\n      navigator.clipboard?.writeText(`https://cdn.uipkge.dev/assets/${asset.filename}`)\n    } catch {\n      // Clipboard fallback\n    }\n    this.showNotification(`Link copied for \"${asset.title}\"`)\n  }\n\n  triggerInstantDownload(asset: AssetItem, event?: Event): void {\n    event?.stopPropagation()\n    this.showNotification(`Downloading ${asset.filename} (${asset.fileSize})...`)\n  }\n\n  openInspector(asset: AssetItem): void {\n    this.selectedAsset.set(asset)\n    this.selectedResolutionOption.set(asset.downloadOptions[0]?.id || 'opt-1')\n    this.isDrawerOpen.set(true)\n  }\n\n  toggleTag(tag: string): void {\n    this.selectedTag.set(this.selectedTag() === tag ? null : tag)\n  }\n\n  resetFilters(): void {\n    this.selectedTag.set(null)\n    this.selectedAspectRatio.set('all')\n    this.searchQuery.set('')\n    this.selectedCategory.set('all')\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/media-asset-gallery/media-asset-gallery.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/media-asset-gallery/index.ts",
      "content": "export { UiMediaAssetGalleryComponent, DEFAULT_ASSETS, type AssetItem } from './media-asset-gallery.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/media-asset-gallery/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/sheet.json",
    "https://uipkge.dev/r/angular/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"
  ]
}