{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "podcast-audio-player",
  "title": "Podcast Audio Player",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/podcast-audio-player/podcast-audio-player.component.ts",
      "content": "// Spotify / Apple Podcasts style audio player: episode hero card, 32-bar visual SVG\n// waveform scrubber with chapter markers, transport controls with speed cycling,\n// chapter navigation grid, and show-notes accordion. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, DestroyRef, Input, computed, inject, signal } from '@angular/core'\nimport {\n  BookOpen,\n  Bookmark,\n  BookmarkCheck,\n  Check,\n  Clock,\n  Copy,\n  Download,\n  ExternalLink,\n  FastForward,\n  Headphones,\n  Heart,\n  ListMusic,\n  LucideAngularModule,\n  Mic,\n  Pause,\n  Play,\n  Radio,\n  RotateCcw,\n  RotateCw,\n  Share2,\n  SkipBack,\n  SkipForward,\n  Volume1,\n  Volume2,\n  VolumeX,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\nimport {\n  UiAccordionComponent,\n  UiAccordionContentComponent,\n  UiAccordionItemComponent,\n  UiAccordionTriggerComponent,\n} from '@/ui/accordion/accordion.component'\nimport { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'\nimport { UiBadgeComponent } from '@/ui/badge/badge.component'\nimport { UiButtonComponent } from '@/ui/button/button.component'\nimport {\n  UiCardComponent,\n  UiCardContentComponent,\n  UiCardDescriptionComponent,\n  UiCardFooterComponent,\n  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiSliderComponent } from '@/ui/slider/slider.component'\n\nexport interface PodcastChapter {\n  id: number\n  title: string\n  subtitle: string\n  start: number\n  end: number\n  durationFormatted: string\n}\n\nexport interface PodcastPanelist {\n  name: string\n  role: string\n  company: string\n  avatar: string\n  fallback: string\n  bio: string\n  handle: string\n}\n\nexport interface PodcastReferenceLink {\n  title: string\n  description: string\n  url: string\n  tag: string\n}\n\nconst waveformBars = [\n  32, 48, 65, 88, 60, 42, 75, 96, 85, 62, 50, 78, 92, 100, 84, 60, 38, 70, 88, 95, 80, 54, 42, 68, 85, 92, 76, 60, 46,\n  64, 82, 45,\n]\n\nconst chapters: PodcastChapter[] = [\n  {\n    id: 1,\n    title: 'Intro & State of Tooling',\n    subtitle: 'Why traditional CSS workflows break at scale',\n    start: 0,\n    end: 495,\n    durationFormatted: '08:15',\n  },\n  {\n    id: 2,\n    title: 'The Monorepo Pivot',\n    subtitle: 'Moving away from monolithic npm component packages',\n    start: 495,\n    end: 1360,\n    durationFormatted: '14:25',\n  },\n  {\n    id: 3,\n    title: 'OKLCH Math & Contrast Engines',\n    subtitle: 'Perceptual uniformity, APCA contrast & wide-gamut P3',\n    start: 1360,\n    end: 2290,\n    durationFormatted: '15:30',\n  },\n  {\n    id: 4,\n    title: 'Q&A & Future Standards',\n    subtitle: 'Audience questions, migration playbooks & CSS Color 5',\n    start: 2290,\n    end: 2912,\n    durationFormatted: '10:22',\n  },\n]\n\nconst panelists: PodcastPanelist[] = [\n  {\n    name: 'Elena Rostova',\n    role: 'Staff Design Engineer',\n    company: 'Linear',\n    avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',\n    fallback: 'ER',\n    bio: 'Pioneered Linear dark mode tokens, high-density keyboard workflows, and micro-interactions.',\n    handle: '@erostova',\n  },\n  {\n    name: 'Marcus Vance',\n    role: 'Head of UI Architecture',\n    company: 'Vercel',\n    avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80',\n    fallback: 'MV',\n    bio: 'Core architect on headless design systems, token compilers, and zero-runtime CSS workflows.',\n    handle: '@marcusvance',\n  },\n  {\n    name: 'Dr. Aris Thorne',\n    role: 'Color Science Lead',\n    company: 'W3C CSS Working Group',\n    avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80',\n    fallback: 'AT',\n    bio: 'Author on CSS Color Module 4 & 5, APCA contrast metric integration, and gamut mapping algorithms.',\n    handle: '@aristhorne',\n  },\n]\n\nconst referenceLinks: PodcastReferenceLink[] = [\n  {\n    title: 'OKLCH Color Space Visualizer',\n    description: 'Interactive gamut mapper, lightness ramp generator, and P3 inspector.',\n    url: 'https://oklch.com',\n    tag: 'Tooling',\n  },\n  {\n    title: 'W3C CSS Color Module Level 4',\n    description: 'Official W3C specification defining oklch(), oklab(), and wide-gamut coordinates.',\n    url: 'https://www.w3.org/TR/css-color-4/',\n    tag: 'Specification',\n  },\n  {\n    title: 'UIPKGE Component Registry Architecture',\n    description: 'The unbundled registry distribution model for Vue and React design systems.',\n    url: 'https://uipkge.dev',\n    tag: 'Architecture',\n  },\n  {\n    title: 'APCA Accessible Perceptual Contrast Algorithm',\n    description: 'Next-generation readability standard replacing legacy WCAG 2 ratio formulas.',\n    url: 'https://git.apcacontrast.com',\n    tag: 'Accessibility',\n  },\n]\n\nconst speedOptions = [1.0, 1.25, 1.5, 2.0]\n\nexport function formatPodcastTime(seconds: number): string {\n  const m = Math.floor(seconds / 60)\n  const s = Math.floor(seconds % 60)\n  return `${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-podcast-audio-player, [ui-podcast-audio-player]',\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    UiAccordionComponent,\n    UiAccordionContentComponent,\n    UiAccordionItemComponent,\n    UiAccordionTriggerComponent,\n    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiAvatarImageComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiSeparatorComponent,\n    UiSliderComponent,\n  ],\n  template: `\n    <div data-slot=\"podcast-audio-player\" [class]=\"rootClass\">\n      <!-- EPISODE HERO CARD -->\n      <div ui-card class=\"overflow-hidden border shadow-xs\">\n        <div ui-card-content class=\"p-5 sm:p-7\">\n          <div class=\"flex flex-col gap-6 lg:flex-row lg:items-center lg:justify-between\">\n            <div class=\"flex flex-col gap-5 sm:flex-row sm:items-center\">\n              <div\n                class=\"from-primary/20 via-primary/10 to-background ring-border/80 group relative flex size-28 shrink-0 items-center justify-center overflow-hidden rounded-2xl border bg-gradient-to-br shadow-md ring-1 sm:size-32\"\n              >\n                <div\n                  class=\"border-primary/30 absolute inset-2 rounded-xl border border-dashed opacity-60 transition-transform duration-700 group-hover:rotate-45\"\n                ></div>\n                <div\n                  class=\"bg-card/90 relative z-10 flex size-14 items-center justify-center rounded-xl border shadow-xs\"\n                >\n                  <lucide-icon [img]=\"Radio\" class=\"text-primary size-7\" />\n                </div>\n                <div\n                  class=\"bg-background/90 text-foreground absolute right-2 bottom-2 rounded-md px-1.5 py-0.5 font-mono text-xs font-semibold shadow-xs\"\n                >\n                  EP #48\n                </div>\n              </div>\n\n              <div class=\"space-y-2\">\n                <div class=\"flex flex-wrap items-center gap-2\">\n                  <span ui-badge variant=\"outline\" class=\"gap-1 text-xs font-semibold\">\n                    <lucide-icon [img]=\"Mic\" class=\"text-primary size-3\" />\n                    <span>The Design Systems Podcast</span>\n                  </span>\n                  <span ui-badge variant=\"secondary\" class=\"font-mono text-xs\"> Season 4 · Episode #48 </span>\n                  <span\n                    ui-badge\n                    variant=\"outline\"\n                    class=\"border-success/30 bg-success/10 text-success font-mono text-xs\"\n                  >\n                    Lossless 320kbps\n                  </span>\n                </div>\n\n                <h1 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl lg:text-3xl\">\n                  Deconstructing OKLCH & Zero-Dependency Component Registries\n                </h1>\n\n                <div class=\"text-muted-foreground flex flex-wrap items-center gap-3 text-xs sm:text-sm\">\n                  <div class=\"flex items-center gap-2\">\n                    <div class=\"flex shrink-0 -space-x-2 overflow-hidden\">\n                      @for (person of panelists; track person.name) {\n                        <ui-avatar class=\"border-background ring-border size-6 border-2 ring-1\">\n                          <ui-avatar-image [src]=\"person.avatar\" [alt]=\"person.name\" />\n                          <ui-avatar-fallback>{{ person.fallback }}</ui-avatar-fallback>\n                        </ui-avatar>\n                      }\n                    </div>\n                    <span class=\"text-foreground font-medium\">Elena Rostova, Marcus Vance & Dr. Aris Thorne</span>\n                  </div>\n                  <span>·</span>\n                  <div class=\"flex items-center gap-1 font-mono text-xs tabular-nums\">\n                    <lucide-icon [img]=\"Clock\" class=\"size-3.5\" />\n                    <span>48 mins</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <div class=\"flex flex-wrap items-center gap-2 border-t pt-4 lg:border-t-0 lg:pt-0\">\n              <button\n                ui-button\n                [variant]=\"isLiked() ? 'default' : 'outline'\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs font-semibold shadow-xs\"\n                (click)=\"toggleLike()\"\n              >\n                <lucide-icon\n                  [img]=\"Heart\"\n                  [class]=\"isLiked() ? 'size-4 fill-current' : 'text-muted-foreground size-4'\"\n                />\n                <span class=\"font-mono tabular-nums\">{{ likeCount() }}</span>\n              </button>\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"icon\"\n                class=\"size-9 shadow-xs\"\n                [attr.aria-label]=\"isBookmarked() ? 'Remove bookmark' : 'Bookmark episode'\"\n                (click)=\"isBookmarked.set(!isBookmarked())\"\n              >\n                @if (isBookmarked()) {\n                  <lucide-icon [img]=\"BookmarkCheck\" class=\"text-primary size-4\" />\n                } @else {\n                  <lucide-icon [img]=\"Bookmark\" class=\"text-muted-foreground size-4\" />\n                }\n              </button>\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"h-9 gap-1.5 text-xs font-semibold shadow-xs\"\n                (click)=\"handleCopyLink()\"\n              >\n                @if (isCopied()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-4\" />\n                } @else {\n                  <lucide-icon [img]=\"Share2\" class=\"size-4\" />\n                }\n                <span>{{ isCopied() ? 'Link Copied' : 'Share' }}</span>\n              </button>\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"icon\"\n                class=\"size-9 shadow-xs\"\n                [attr.aria-label]=\"isDownloaded() ? 'Downloaded' : 'Download episode'\"\n                (click)=\"handleDownload()\"\n              >\n                @if (isDownloaded()) {\n                  <lucide-icon [img]=\"Check\" class=\"text-success size-4\" />\n                } @else {\n                  <lucide-icon [img]=\"Download\" class=\"size-4\" />\n                }\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- AUDIO PLAYER CONTROLS (CENTERPIECE) -->\n      <div ui-card class=\"border shadow-xs\">\n        <div ui-card-content class=\"space-y-6 p-5 sm:p-7\">\n          <div class=\"flex flex-col justify-between gap-2 sm:flex-row sm:items-center\">\n            <div class=\"space-y-1\">\n              <div class=\"flex items-center gap-2\">\n                <span class=\"bg-primary size-2 animate-pulse rounded-full\"></span>\n                <span class=\"text-muted-foreground font-mono text-xs font-semibold tracking-wider uppercase\">\n                  Now Playing · Chapter {{ currentChapter().id }} of {{ chapters.length }}\n                </span>\n              </div>\n              <h2 class=\"text-foreground text-base font-bold tracking-tight sm:text-lg\">\n                {{ currentChapter().title }}\n              </h2>\n              <p class=\"text-muted-foreground text-xs\">\n                {{ currentChapter().subtitle }}\n              </p>\n            </div>\n\n            <div class=\"flex items-center gap-2 self-start font-mono text-xs font-semibold tabular-nums sm:self-auto\">\n              <span ui-badge variant=\"outline\" class=\"border-primary/30 bg-primary/5 gap-1 px-2.5 py-1\">\n                <span class=\"text-primary font-bold\">{{ formattedCurrentTime() }}</span>\n                <span class=\"text-muted-foreground\">/</span>\n                <span class=\"text-muted-foreground\">{{ formattedTotalTime() }}</span>\n              </span>\n              <span class=\"text-muted-foreground text-xs\">{{ remainingTime() }}</span>\n            </div>\n          </div>\n\n          <!-- 32-BAR VISUAL WAVEFORM SCRUBBER -->\n          <div class=\"space-y-2\">\n            <div\n              class=\"group/wave bg-muted/30 hover:bg-muted/50 border-border/80 relative flex h-24 w-full cursor-pointer items-center justify-between rounded-xl border p-4 transition-colors select-none\"\n              role=\"slider\"\n              aria-label=\"Audio Waveform Scrubber\"\n              [attr.aria-valuenow]=\"currentTime()\"\n              [attr.aria-valuemin]=\"0\"\n              [attr.aria-valuemax]=\"totalDuration()\"\n              (click)=\"handleWaveformClick($event)\"\n            >\n              <div class=\"flex h-full w-full items-center justify-between gap-1 sm:gap-1.5\">\n                @for (barHeight of waveformBars; track $index) {\n                  <div class=\"flex h-full flex-1 items-center justify-center\">\n                    <div [class]=\"waveformBarClass($index)\" [style.height]=\"barHeight + '%'\"></div>\n                  </div>\n                }\n              </div>\n\n              <div\n                class=\"bg-foreground pointer-events-none absolute top-0 bottom-0 z-10 w-0.5 transition-colors\"\n                [style.left]=\"progressPercent() + '%'\"\n              >\n                <div\n                  class=\"bg-primary ring-background absolute -top-1 left-1/2 size-3.5 -translate-x-1/2 rounded-full shadow-md ring-2 transition-transform group-hover/wave:scale-125\"\n                ></div>\n              </div>\n\n              <div class=\"pointer-events-none absolute right-4 bottom-1.5 left-4 flex justify-between\">\n                @for (ch of chapters; track ch.id) {\n                  <span\n                    class=\"absolute flex flex-col items-center\"\n                    [style.left]=\"(ch.start / totalDuration()) * 100 + '%'\"\n                  >\n                    <span class=\"bg-muted-foreground/50 h-2 w-0.5\"></span>\n                  </span>\n                }\n              </div>\n            </div>\n\n            <div class=\"text-muted-foreground flex items-center justify-between font-mono text-xs tabular-nums\">\n              <div class=\"flex items-center gap-1.5\">\n                <span class=\"text-foreground font-semibold\">{{ formattedCurrentTime() }}</span>\n                <span>(Chapter progress: {{ currentChapterProgressRounded() }}%)</span>\n              </div>\n              <span>{{ formattedTotalTime() }}</span>\n            </div>\n          </div>\n\n          <!-- MAIN PLAYBACK CONTROLS BAR -->\n          <div class=\"flex flex-col items-center justify-between gap-4 pt-2 md:flex-row\">\n            <div class=\"flex items-center gap-2\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"sm\"\n                class=\"hover:bg-accent h-9 gap-1 px-2.5 font-mono text-xs font-bold shadow-xs\"\n                aria-label=\"Cycle Playback Speed\"\n                (click)=\"cycleSpeed()\"\n              >\n                <lucide-icon [img]=\"FastForward\" class=\"text-primary size-3.5\" />\n                <span>{{ speedLabel() }}</span>\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-9\"\n                aria-label=\"Previous Chapter\"\n                (click)=\"skipToPrevChapter()\"\n              >\n                <lucide-icon [img]=\"SkipBack\" class=\"size-4\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-9\"\n                aria-label=\"Next Chapter\"\n                (click)=\"skipToNextChapter()\"\n              >\n                <lucide-icon [img]=\"SkipForward\" class=\"size-4\" />\n              </button>\n            </div>\n\n            <div class=\"flex items-center gap-3\">\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"icon\"\n                class=\"size-10 rounded-full shadow-xs\"\n                aria-label=\"Rewind 15 seconds\"\n                (click)=\"skipSeconds(-15)\"\n              >\n                <lucide-icon [img]=\"RotateCcw\" class=\"size-4.5\" />\n              </button>\n\n              <button\n                ui-button\n                variant=\"default\"\n                size=\"icon\"\n                class=\"bg-primary text-primary-foreground hover:bg-primary/90 focus-visible:ring-ring size-13 rounded-full shadow-lg transition-[color,background-color,border-color,box-shadow,opacity,transform,scale,translate,rotate] hover:scale-105 focus-visible:ring-2 active:scale-95\"\n                [attr.aria-label]=\"isPlaying() ? 'Pause episode' : 'Play episode'\"\n                (click)=\"togglePlay()\"\n              >\n                @if (isPlaying()) {\n                  <lucide-icon [img]=\"Pause\" class=\"size-6 fill-current\" />\n                } @else {\n                  <lucide-icon [img]=\"Play\" class=\"ml-0.5 size-6 fill-current\" />\n                }\n              </button>\n\n              <button\n                ui-button\n                variant=\"outline\"\n                size=\"icon\"\n                class=\"size-10 rounded-full shadow-xs\"\n                aria-label=\"Fast forward 15 seconds\"\n                (click)=\"skipSeconds(15)\"\n              >\n                <lucide-icon [img]=\"RotateCw\" class=\"size-4.5\" />\n              </button>\n            </div>\n\n            <div class=\"flex items-center gap-2.5\">\n              <button\n                ui-button\n                variant=\"ghost\"\n                size=\"icon\"\n                class=\"text-muted-foreground hover:text-foreground size-9\"\n                [attr.aria-label]=\"isMuted() ? 'Unmute' : 'Mute'\"\n                (click)=\"toggleMute()\"\n              >\n                @if (isMuted() || volume() === 0) {\n                  <lucide-icon [img]=\"VolumeX\" class=\"text-destructive size-4.5\" />\n                } @else if (volume() < 50) {\n                  <lucide-icon [img]=\"Volume1\" class=\"size-4.5\" />\n                } @else {\n                  <lucide-icon [img]=\"Volume2\" class=\"size-4.5\" />\n                }\n              </button>\n\n              <div class=\"w-24 sm:w-28\">\n                <ui-slider\n                  [value]=\"[volume()]\"\n                  [min]=\"0\"\n                  [max]=\"100\"\n                  [step]=\"1\"\n                  [disabled]=\"isMuted()\"\n                  class=\"cursor-pointer\"\n                  (valueChange)=\"volume.set($event[0] ?? volume())\"\n                />\n              </div>\n              <span class=\"text-muted-foreground w-8 text-right font-mono text-xs tabular-nums\">\n                {{ isMuted() ? '0%' : volume() + '%' }}\n              </span>\n            </div>\n          </div>\n\n          <ui-separator />\n\n          <!-- CHAPTER NAVIGATION BAR -->\n          <div class=\"space-y-3\">\n            <div class=\"flex items-center justify-between\">\n              <div class=\"flex items-center gap-2\">\n                <lucide-icon [img]=\"ListMusic\" class=\"text-primary size-4\" />\n                <h3 class=\"text-foreground text-xs font-bold tracking-tight uppercase\">Chapter Navigation</h3>\n              </div>\n              <span class=\"text-muted-foreground font-mono text-xs\"> 4 Chapters · 48m 32s Total </span>\n            </div>\n\n            <div class=\"grid grid-cols-1 gap-2.5 sm:grid-cols-2 lg:grid-cols-4\">\n              @for (ch of chapters; track ch.id) {\n                <div [class]=\"chapterCardClass(ch)\" (click)=\"seekTo(ch.start)\">\n                  <div class=\"space-y-1\">\n                    <div class=\"flex items-center justify-between\">\n                      <span\n                        ui-badge\n                        [variant]=\"currentChapter().id === ch.id ? 'default' : 'outline'\"\n                        class=\"h-5 px-1.5 font-mono text-xs font-semibold\"\n                      >\n                        CH {{ ch.id }}\n                      </span>\n                      <span class=\"text-muted-foreground font-mono text-xs tabular-nums\">\n                        {{ formatTime(ch.start) }}\n                      </span>\n                    </div>\n                    <div class=\"text-foreground line-clamp-1 font-semibold\">\n                      {{ ch.title }}\n                    </div>\n                    <div class=\"text-muted-foreground line-clamp-1 text-xs\">\n                      {{ ch.subtitle }}\n                    </div>\n                  </div>\n\n                  <div class=\"mt-2.5 pt-1\">\n                    @if (currentChapter().id === ch.id) {\n                      <ui-progress [value]=\"currentChapterProgress()\" class=\"h-1\" />\n                    } @else {\n                      <div\n                        [class]=\"\n                          currentTime() >= ch.end\n                            ? 'bg-primary/40 h-1 w-full rounded-full'\n                            : 'bg-muted h-1 w-full rounded-full'\n                        \"\n                      ></div>\n                    }\n                  </div>\n                </div>\n              }\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- EPISODE SHOW NOTES & GUEST LINKS ACCORDION -->\n      <div ui-card class=\"border shadow-xs\">\n        <div ui-card-header class=\"pb-3\">\n          <div class=\"flex items-center justify-between\">\n            <div class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"BookOpen\" class=\"text-primary size-4\" />\n              <h3 ui-card-title class=\"text-base font-bold tracking-tight sm:text-lg\">\n                Episode Show Notes & Comprehensive Guide\n              </h3>\n            </div>\n            <span ui-badge variant=\"outline\" class=\"font-mono text-xs\"> Full Notes </span>\n          </div>\n          <p ui-card-description class=\"text-xs sm:text-sm\">\n            Deep dive references, color math formulas, guest bios, and resource bookmarks from this episode.\n          </p>\n        </div>\n\n        <div ui-card-content class=\"pt-1\">\n          <ui-accordion type=\"single\" collapsible defaultValue=\"summary\" class=\"w-full\">\n            <ui-accordion-item value=\"summary\">\n              <button ui-accordion-trigger class=\"text-xs font-semibold hover:no-underline sm:text-sm\">\n                <span class=\"flex items-center gap-2\">\n                  <span\n                    class=\"bg-primary/10 text-primary flex size-5 items-center justify-center rounded-full text-xs font-bold\"\n                    >1</span\n                  >\n                  <span>Executive Summary & Key Takeaways</span>\n                </span>\n              </button>\n              <ui-accordion-content class=\"space-y-3 pt-2 text-xs leading-relaxed\">\n                <p class=\"text-muted-foreground\">\n                  In this episode, we break down why traditional HSL and sRGB color models fall short when building\n                  modern, accessible multi-theme design systems. We explore how the OKLCH color space decouples\n                  perceptual lightness from chroma and hue, eliminating contrast inversion bugs when shifting from light\n                  to OLED dark mode.\n                </p>\n\n                <div class=\"grid grid-cols-1 gap-3 pt-1 sm:grid-cols-3\">\n                  <div class=\"bg-muted/30 space-y-1 rounded-lg border p-3\">\n                    <div class=\"text-foreground font-semibold\">Perceptual Uniformity</div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Lightness (L) in OKLCH remains consistent regardless of hue angle, unlike HSL where yellow appears\n                      far brighter than blue.\n                    </p>\n                  </div>\n\n                  <div class=\"bg-muted/30 space-y-1 rounded-lg border p-3\">\n                    <div class=\"text-foreground font-semibold\">Zero-Dependency Registries</div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Unbundled component distribution gives engineering teams 100% source code ownership with zero\n                      semantic version drift.\n                    </p>\n                  </div>\n\n                  <div class=\"bg-muted/30 space-y-1 rounded-lg border p-3\">\n                    <div class=\"text-foreground font-semibold\">Tailwind v4 &#64;theme</div>\n                    <p class=\"text-muted-foreground text-xs\">\n                      Direct CSS variable binding with OKLCH tokens unlocks dynamic color-mix tints without JavaScript\n                      runtime overhead.\n                    </p>\n                  </div>\n                </div>\n              </ui-accordion-content>\n            </ui-accordion-item>\n\n            <ui-accordion-item value=\"chapters\">\n              <button ui-accordion-trigger class=\"text-xs font-semibold hover:no-underline sm:text-sm\">\n                <span class=\"flex items-center gap-2\">\n                  <span\n                    class=\"bg-primary/10 text-primary flex size-5 items-center justify-center rounded-full text-xs font-bold\"\n                    >2</span\n                  >\n                  <span>Timestamped Chapter Breakdown & Discussion Topics</span>\n                </span>\n              </button>\n              <ui-accordion-content class=\"space-y-2.5 pt-2 text-xs\">\n                @for (ch of chapters; track ch.id) {\n                  <div\n                    class=\"bg-muted/20 hover:bg-muted/40 flex items-center justify-between rounded-lg border p-3 transition-colors\"\n                  >\n                    <div class=\"space-y-0.5\">\n                      <div class=\"flex items-center gap-2\">\n                        <span class=\"text-foreground font-semibold\">Chapter {{ ch.id }}: {{ ch.title }}</span>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs\">{{ ch.subtitle }}</p>\n                    </div>\n\n                    <button\n                      ui-button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-7 gap-1 px-2.5 font-mono text-xs font-bold tabular-nums\"\n                      (click)=\"seekTo(ch.start)\"\n                    >\n                      <lucide-icon [img]=\"Play\" class=\"text-primary size-2.5 fill-current\" />\n                      <span>{{ formatTime(ch.start) }}</span>\n                    </button>\n                  </div>\n                }\n              </ui-accordion-content>\n            </ui-accordion-item>\n\n            <ui-accordion-item value=\"panelists\">\n              <button ui-accordion-trigger class=\"text-xs font-semibold hover:no-underline sm:text-sm\">\n                <span class=\"flex items-center gap-2\">\n                  <span\n                    class=\"bg-primary/10 text-primary flex size-5 items-center justify-center rounded-full text-xs font-bold\"\n                    >3</span\n                  >\n                  <span>Featured Hosts & Guest Panel</span>\n                </span>\n              </button>\n              <ui-accordion-content class=\"space-y-3 pt-2 text-xs\">\n                <div class=\"grid grid-cols-1 gap-3 md:grid-cols-3\">\n                  @for (person of panelists; track person.name) {\n                    <div class=\"bg-muted/25 flex flex-col justify-between space-y-3 rounded-xl border p-3.5\">\n                      <div class=\"flex items-start gap-3\">\n                        <ui-avatar class=\"ring-border size-10 border ring-1\">\n                          <ui-avatar-image [src]=\"person.avatar\" [alt]=\"person.name\" />\n                          <ui-avatar-fallback>{{ person.fallback }}</ui-avatar-fallback>\n                        </ui-avatar>\n                        <div class=\"min-w-0 space-y-0.5\">\n                          <div class=\"text-foreground truncate font-bold\">{{ person.name }}</div>\n                          <div class=\"text-muted-foreground truncate text-xs\">{{ person.role }}</div>\n                          <span ui-badge variant=\"secondary\" class=\"h-4 px-1 text-xs\">\n                            {{ person.company }}\n                          </span>\n                        </div>\n                      </div>\n                      <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                        {{ person.bio }}\n                      </p>\n                      <div class=\"text-primary font-mono text-xs\">\n                        {{ person.handle }}\n                      </div>\n                    </div>\n                  }\n                </div>\n              </ui-accordion-content>\n            </ui-accordion-item>\n\n            <ui-accordion-item value=\"resources\">\n              <button ui-accordion-trigger class=\"text-xs font-semibold hover:no-underline sm:text-sm\">\n                <span class=\"flex items-center gap-2\">\n                  <span\n                    class=\"bg-primary/10 text-primary flex size-5 items-center justify-center rounded-full text-xs font-bold\"\n                    >4</span\n                  >\n                  <span>Mentioned Resources & Specifications</span>\n                </span>\n              </button>\n              <ui-accordion-content class=\"space-y-2.5 pt-2 text-xs\">\n                <div class=\"grid grid-cols-1 gap-2.5 sm:grid-cols-2\">\n                  @for (link of referenceLinks; track link.title) {\n                    <a\n                      [href]=\"link.url\"\n                      target=\"_blank\"\n                      rel=\"noreferrer\"\n                      class=\"bg-muted/25 hover:bg-muted/50 group flex flex-col justify-between rounded-xl border p-3.5 transition-colors\"\n                    >\n                      <div class=\"space-y-1\">\n                        <div class=\"flex items-center justify-between\">\n                          <span\n                            class=\"text-foreground group-hover:text-primary flex items-center gap-1.5 font-semibold transition-colors\"\n                          >\n                            {{ link.title }}\n                            <lucide-icon\n                              [img]=\"ExternalLink\"\n                              class=\"text-muted-foreground group-hover:text-primary size-3 transition-colors\"\n                            />\n                          </span>\n                          <span ui-badge variant=\"outline\" class=\"font-mono text-xs\">\n                            {{ link.tag }}\n                          </span>\n                        </div>\n                        <p class=\"text-muted-foreground text-xs leading-relaxed\">\n                          {{ link.description }}\n                        </p>\n                      </div>\n                      <div class=\"text-muted-foreground pt-2 font-mono text-xs\">\n                        {{ link.url }}\n                      </div>\n                    </a>\n                  }\n                </div>\n              </ui-accordion-content>\n            </ui-accordion-item>\n          </ui-accordion>\n        </div>\n\n        <div ui-card-footer class=\"bg-muted/20 flex flex-wrap items-center justify-between gap-3 border-t p-4 text-xs\">\n          <div class=\"text-muted-foreground flex items-center gap-2\">\n            <lucide-icon [img]=\"Headphones\" class=\"text-primary size-4\" />\n            <span>Produced by The Design Systems Guild · All rights reserved</span>\n          </div>\n\n          <div class=\"flex items-center gap-2\">\n            <button ui-button variant=\"ghost\" size=\"sm\" class=\"h-7 text-xs\" (click)=\"handleCopyLink()\">\n              <lucide-icon [img]=\"Copy\" class=\"mr-1 size-3.5\" />\n              <span>Copy Transcript Link</span>\n            </button>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiPodcastAudioPlayerComponent {\n  protected readonly BookOpen = BookOpen\n  protected readonly Bookmark = Bookmark\n  protected readonly BookmarkCheck = BookmarkCheck\n  protected readonly Check = Check\n  protected readonly Clock = Clock\n  protected readonly Copy = Copy\n  protected readonly Download = Download\n  protected readonly ExternalLink = ExternalLink\n  protected readonly FastForward = FastForward\n  protected readonly Headphones = Headphones\n  protected readonly Heart = Heart\n  protected readonly ListMusic = ListMusic\n  protected readonly Mic = Mic\n  protected readonly Pause = Pause\n  protected readonly Play = Play\n  protected readonly Radio = Radio\n  protected readonly RotateCcw = RotateCcw\n  protected readonly RotateCw = RotateCw\n  protected readonly Share2 = Share2\n  protected readonly SkipBack = SkipBack\n  protected readonly SkipForward = SkipForward\n  protected readonly Volume1 = Volume1\n  protected readonly Volume2 = Volume2\n  protected readonly VolumeX = VolumeX\n\n  protected readonly waveformBars = waveformBars\n  protected readonly chapters = chapters\n  protected readonly panelists = panelists\n  protected readonly referenceLinks = referenceLinks\n\n  @Input('class') className?: string\n  @Input() set initialTime(v: number | undefined) {\n    if (v !== undefined) this.currentTime.set(v)\n  }\n  @Input() set initialPlaying(v: boolean) {\n    if (v) this.startPlayback()\n    else this.stopPlayback()\n  }\n\n  readonly isPlaying = signal(false)\n  readonly currentTime = signal(860)\n  readonly totalDuration = signal(2912)\n  readonly volume = signal(80)\n  readonly isMuted = signal(false)\n  readonly playbackSpeed = signal(1.0)\n  readonly isBookmarked = signal(false)\n  readonly isLiked = signal(false)\n  readonly likeCount = signal(342)\n  readonly isCopied = signal(false)\n  readonly isDownloaded = signal(false)\n\n  private playInterval: ReturnType<typeof setInterval> | null = null\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n  private downloadTimer: ReturnType<typeof setTimeout> | undefined\n\n  constructor() {\n    inject(DestroyRef).onDestroy(() => {\n      if (this.playInterval) clearInterval(this.playInterval)\n      clearTimeout(this.copyTimer)\n      clearTimeout(this.downloadTimer)\n    })\n  }\n\n  readonly formattedCurrentTime = computed(() => formatPodcastTime(this.currentTime()))\n  readonly formattedTotalTime = computed(() => formatPodcastTime(this.totalDuration()))\n  readonly remainingTime = computed(() => `-${formatPodcastTime(this.totalDuration() - this.currentTime())}`)\n  readonly progressPercent = computed(() => (this.currentTime() / this.totalDuration()) * 100)\n\n  readonly currentChapter = computed<PodcastChapter>(() => {\n    const t = this.currentTime()\n    return chapters.find((c) => t >= c.start && t < c.end) ?? chapters[chapters.length - 1]\n  })\n\n  readonly currentChapterProgress = computed(() => {\n    const ch = this.currentChapter()\n    const elapsed = this.currentTime() - ch.start\n    const total = ch.end - ch.start\n    return Math.min(100, Math.max(0, (elapsed / total) * 100))\n  })\n\n  readonly currentChapterProgressRounded = computed(() => Math.round(this.currentChapterProgress()))\n\n  readonly speedLabel = computed(() => {\n    const s = this.playbackSpeed()\n    return `${s.toFixed(s % 1 === 0 ? 1 : 2)}x`\n  })\n\n  get rootClass(): string {\n    return cn('bg-background text-foreground w-full space-y-6', this.className)\n  }\n\n  waveformBarClass(index: number): string {\n    const active = (index / waveformBars.length) * 100 <= this.progressPercent()\n    const live = this.isPlaying() && Math.floor((this.progressPercent() / 100) * waveformBars.length) === index\n    return cn(\n      'w-full max-w-[8px] rounded-full transition-[height] duration-150',\n      active ? 'bg-primary' : 'bg-muted-foreground/25 group-hover/wave:bg-muted-foreground/35',\n      live && 'ring-primary/40 scale-y-110 ring-2',\n    )\n  }\n\n  chapterCardClass(ch: PodcastChapter): string {\n    return cn(\n      'group flex cursor-pointer flex-col justify-between rounded-xl border p-3 text-xs transition-colors duration-200',\n      this.currentChapter().id === ch.id\n        ? 'border-primary/50 bg-primary/5 ring-primary/20 shadow-xs ring-1'\n        : 'bg-card hover:bg-muted/40 border-border/70',\n    )\n  }\n\n  formatTime(seconds: number): string {\n    return formatPodcastTime(seconds)\n  }\n\n  togglePlay(): void {\n    if (this.isPlaying()) this.stopPlayback()\n    else this.startPlayback()\n  }\n\n  private startPlayback(): void {\n    this.isPlaying.set(true)\n    if (this.playInterval) clearInterval(this.playInterval)\n    this.playInterval = setInterval(() => {\n      if (this.currentTime() < this.totalDuration()) {\n        this.currentTime.update((t) => t + 1)\n      } else {\n        this.stopPlayback()\n      }\n    }, 1000 / this.playbackSpeed())\n  }\n\n  private stopPlayback(): void {\n    this.isPlaying.set(false)\n    if (this.playInterval) {\n      clearInterval(this.playInterval)\n      this.playInterval = null\n    }\n  }\n\n  seekTo(seconds: number): void {\n    this.currentTime.set(Math.max(0, Math.min(seconds, this.totalDuration())))\n  }\n\n  handleWaveformClick(event: MouseEvent): void {\n    const target = event.currentTarget as HTMLElement | null\n    if (!target) return\n    const rect = target.getBoundingClientRect()\n    if (rect.width === 0) return\n    const ratio = Math.max(0, Math.min(1, (event.clientX - rect.left) / rect.width))\n    this.seekTo(Math.round(ratio * this.totalDuration()))\n  }\n\n  skipSeconds(delta: number): void {\n    this.seekTo(this.currentTime() + delta)\n  }\n\n  cycleSpeed(): void {\n    const idx = speedOptions.indexOf(this.playbackSpeed())\n    const next = speedOptions[(idx + 1) % speedOptions.length]\n    this.playbackSpeed.set(next)\n    if (this.isPlaying()) this.startPlayback()\n  }\n\n  skipToNextChapter(): void {\n    const idx = chapters.findIndex((c) => c.id === this.currentChapter().id)\n    if (idx < chapters.length - 1) this.seekTo(chapters[idx + 1].start)\n  }\n\n  skipToPrevChapter(): void {\n    const ch = this.currentChapter()\n    if (this.currentTime() - ch.start > 4) {\n      this.seekTo(ch.start)\n    } else {\n      const idx = chapters.findIndex((c) => c.id === ch.id)\n      if (idx > 0) this.seekTo(chapters[idx - 1].start)\n      else this.seekTo(0)\n    }\n  }\n\n  toggleMute(): void {\n    this.isMuted.update((m) => !m)\n  }\n\n  toggleLike(): void {\n    const next = !this.isLiked()\n    this.isLiked.set(next)\n    this.likeCount.update((c) => c + (next ? 1 : -1))\n  }\n\n  handleCopyLink(): void {\n    try {\n      void navigator.clipboard?.writeText(window.location.href)\n    } catch {\n      // Clipboard unavailable (jsdom) — still show feedback.\n    }\n    this.isCopied.set(true)\n    clearTimeout(this.copyTimer)\n    this.copyTimer = setTimeout(() => this.isCopied.set(false), 2000)\n  }\n\n  handleDownload(): void {\n    this.isDownloaded.set(true)\n    clearTimeout(this.downloadTimer)\n    this.downloadTimer = setTimeout(() => this.isDownloaded.set(false), 3000)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/podcast-audio-player/podcast-audio-player.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/podcast-audio-player/index.ts",
      "content": "export {\n  UiPodcastAudioPlayerComponent,\n  formatPodcastTime,\n  type PodcastChapter,\n  type PodcastPanelist,\n  type PodcastReferenceLink,\n} from './podcast-audio-player.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/podcast-audio-player/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/accordion.json",
    "https://uipkge.dev/r/angular/avatar.json",
    "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/progress.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/slider.json"
  ],
  "description": "Spotify and Apple Podcasts style audio player with 32-bar visual SVG waveform scrubber, chapter markers, playback speed controls, and show notes accordion.",
  "categories": [
    "media",
    "app",
    "audio"
  ]
}