{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "virtual-card-manager",
  "title": "Virtual Card Manager",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/virtual-card-manager/virtual-card-manager.component.ts",
      "content": "// Mercury / Ramp style corporate virtual card manager: interactive dark credit card\n// visual hero with EMV chip, revealable credentials, live spending limit progress,\n// instant freeze toggles, limit adjusters, and an active virtual cards table.\n// Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  Check,\n  CheckCircle2,\n  Copy,\n  CreditCard,\n  Eye,\n  EyeOff,\n  LucideAngularModule,\n  Plus,\n  Radio,\n  ShieldCheck,\n  SlidersHorizontal,\n  Snowflake,\n  Trash2,\n  Zap,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\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  UiCardHeaderComponent,\n  UiCardTitleComponent,\n} from '@/ui/card/card.component'\nimport { UiProgressComponent } from '@/ui/progress/progress.component'\nimport { UiSwitchComponent } from '@/ui/switch/switch.component'\n\nexport interface VirtualCard {\n  id: string\n  name: string\n  brand: 'mastercard' | 'visa'\n  last4: string\n  fullNumber: string\n  holder: string\n  avatar: string\n  initials: string\n  expiry: string\n  cvv: string\n  spent: number\n  limit: number\n  type: 'Monthly Recurring' | 'Single-use'\n  status: 'Active' | 'Frozen'\n  billingCycle: string\n  purpose: string\n}\n\nconst initialCards: VirtualCard[] = [\n  {\n    id: 'vc-1',\n    name: 'AWS & Cloud Infrastructure',\n    brand: 'mastercard',\n    last4: '8492',\n    fullNumber: '5532 8920 4108 8492',\n    holder: 'Elena Rostova',\n    avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',\n    initials: 'ER',\n    expiry: '08/29',\n    cvv: '482',\n    spent: 4200,\n    limit: 5000,\n    type: 'Monthly Recurring',\n    status: 'Active',\n    billingCycle: 'Resets on 1st of month',\n    purpose: 'Cloud compute, S3 buckets, and RDS instances',\n  },\n  {\n    id: 'vc-2',\n    name: 'Marketing & Ad Spend',\n    brand: 'visa',\n    last4: '3190',\n    fullNumber: '4012 8831 9204 3190',\n    holder: 'Marcus Chen',\n    avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&auto=format&fit=crop&q=80',\n    initials: 'MC',\n    expiry: '11/28',\n    cvv: '915',\n    spent: 8750,\n    limit: 10000,\n    type: 'Monthly Recurring',\n    status: 'Active',\n    billingCycle: 'Resets on 1st of month',\n    purpose: 'Google Ads, LinkedIn campaigns, and Meta Ads',\n  },\n  {\n    id: 'vc-3',\n    name: 'Travel & Conferences',\n    brand: 'visa',\n    last4: '6021',\n    fullNumber: '4111 5900 1284 6021',\n    holder: 'Sarah Jenkins',\n    avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&auto=format&fit=crop&q=80',\n    initials: 'SJ',\n    expiry: '03/27',\n    cvv: '304',\n    spent: 1850,\n    limit: 2500,\n    type: 'Single-use',\n    status: 'Frozen',\n    billingCycle: 'Single event allowance',\n    purpose: 'Q3 Design Systems Summit flights and lodging',\n  },\n  {\n    id: 'vc-4',\n    name: 'SaaS Subscriptions',\n    brand: 'mastercard',\n    last4: '9455',\n    fullNumber: '5241 7719 3302 9455',\n    holder: 'Elena Rostova',\n    avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',\n    initials: 'ER',\n    expiry: '05/29',\n    cvv: '729',\n    spent: 620,\n    limit: 1200,\n    type: 'Monthly Recurring',\n    status: 'Active',\n    billingCycle: 'Resets on 1st of month',\n    purpose: 'GitHub Enterprise, Figma, and Slack licenses',\n  },\n]\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-virtual-card-manager, [ui-virtual-card-manager]',\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    UiAvatarComponent,\n    UiAvatarFallbackComponent,\n    UiAvatarImageComponent,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiProgressComponent,\n    UiSwitchComponent,\n  ],\n  template: `\n    <div data-slot=\"virtual-card-manager\" [class]=\"rootClass\">\n      <!-- Header -->\n      <div class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div class=\"space-y-1\">\n          <div class=\"flex items-center gap-2.5\">\n            <h2 class=\"text-foreground text-xl font-semibold tracking-tight sm:text-2xl\">Virtual Cards</h2>\n            <span ui-badge wrap variant=\"secondary\" class=\"font-mono text-xs tabular-nums\">\n              {{ cards().length }} Cards\n            </span>\n          </div>\n          <p class=\"text-muted-foreground text-sm\">\n            Generate instant corporate cards for subscriptions, vendors, and team expense limits.\n          </p>\n        </div>\n\n        <div class=\"flex items-center gap-2\">\n          <button ui-button size=\"sm\" class=\"gap-1.5 shadow-xs\" (click)=\"issueNewCard()\">\n            <lucide-icon [img]=\"Plus\" class=\"size-4\" />\n            Issue New Card\n          </button>\n        </div>\n      </div>\n\n      <!-- Notification / Action Toast feedback -->\n      @if (actionFeedback()) {\n        <div\n          class=\"bg-primary/10 text-primary border-primary/20 flex items-center justify-between rounded-lg border px-4 py-2.5 text-xs font-medium transition-colors\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <lucide-icon [img]=\"CheckCircle2\" class=\"size-4 shrink-0\" />\n            <span>{{ actionFeedback() }}</span>\n          </div>\n          <button\n            ui-button\n            aria-label=\"Dismiss notification\"\n            variant=\"ghost\"\n            size=\"xs\"\n            class=\"h-6 px-2 text-xs\"\n            (click)=\"actionFeedback.set(null)\"\n          >\n            Dismiss\n          </button>\n        </div>\n      }\n\n      <!-- Virtual Card Visual Hero -->\n      <div ui-card class=\"border-border bg-card shadow-xs\">\n        <div ui-card-header class=\"border-b pb-4\">\n          <div class=\"flex flex-wrap items-center justify-between gap-2\">\n            <div>\n              <div class=\"flex items-center gap-2\">\n                <h3 ui-card-title class=\"text-base font-semibold\">{{ selectedCard().name }}</h3>\n                <span\n                  ui-badge\n                  wrap\n                  [variant]=\"selectedCard().status === 'Active' ? 'success' : 'warning'\"\n                  class=\"text-xs whitespace-normal\"\n                >\n                  {{ selectedCard().status }}\n                </span>\n              </div>\n              <p ui-card-description class=\"mt-0.5 text-xs\">\n                {{ selectedCard().purpose }} · {{ selectedCard().billingCycle }}\n              </p>\n            </div>\n            <span ui-badge wrap variant=\"outline\" class=\"font-mono text-xs\">\n              Ending in {{ selectedCard().last4 }}\n            </span>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"pt-6\">\n          <div class=\"grid gap-6 lg:grid-cols-12 lg:items-center\">\n            <!-- Stylized Dark Credit Card Container -->\n            <div class=\"lg:col-span-6 xl:col-span-5\">\n              <div [class]=\"cardVisualClass\">\n                <!-- Ambient light accents -->\n                <div\n                  class=\"bg-chart-1/15 pointer-events-none absolute -top-16 -right-16 size-48 rounded-full blur-2xl\"\n                  aria-hidden=\"true\"\n                ></div>\n                <div\n                  class=\"bg-success/10 pointer-events-none absolute -bottom-16 -left-16 size-48 rounded-full blur-2xl\"\n                  aria-hidden=\"true\"\n                ></div>\n\n                <!-- Frozen watermark overlay -->\n                @if (isSelectedFrozen()) {\n                  <div\n                    class=\"absolute inset-0 z-20 flex flex-col items-center justify-center gap-2 rounded-2xl bg-zinc-950/70 backdrop-blur-xs\"\n                  >\n                    <div\n                      class=\"border-warning/40 bg-warning/20 text-warning inline-flex items-center gap-1.5 rounded-full border px-3 py-1 text-xs font-semibold tracking-wider uppercase\"\n                    >\n                      <lucide-icon [img]=\"Snowflake\" class=\"size-3.5\" />\n                      Card Frozen\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Transactions are currently blocked</p>\n                  </div>\n                }\n\n                <!-- Top Row: Chip & Contactless + Brand Logo -->\n                <div class=\"relative z-10 flex items-center justify-between\">\n                  <div class=\"flex items-center gap-3\">\n                    <!-- Metallic EMV Chip -->\n                    <div\n                      class=\"border-warning/20/40 relative flex h-8 w-11 items-center justify-center rounded-md border bg-gradient-to-br from-amber-200/30 via-amber-400/20 to-amber-600/30 shadow-inner\"\n                      aria-hidden=\"true\"\n                    >\n                      <div class=\"border-warning/20/30 absolute inset-0 grid grid-cols-3 grid-rows-2\">\n                        <div class=\"border-warning/30 border-r border-b\"></div>\n                        <div class=\"border-warning/30 border-r border-b\"></div>\n                        <div class=\"border-warning/30 border-b\"></div>\n                        <div class=\"border-warning/30 border-r\"></div>\n                        <div class=\"border-warning/30 border-r\"></div>\n                        <div></div>\n                      </div>\n                    </div>\n\n                    <!-- NFC Wave icon -->\n                    <div class=\"text-muted-foreground/80 rotate-90\" aria-hidden=\"true\">\n                      <lucide-icon [img]=\"Radio\" class=\"size-4\" />\n                    </div>\n                  </div>\n\n                  <!-- Brand Indicator -->\n                  <div class=\"flex items-center gap-2\">\n                    <span class=\"text-muted-foreground font-mono text-xs font-medium tracking-wider uppercase\"\n                      >Virtual</span\n                    >\n                    @if (selectedCard().brand === 'mastercard') {\n                      <div class=\"flex -space-x-2\" aria-label=\"Mastercard\">\n                        <div class=\"bg-destructive/90 size-6 rounded-full\"></div>\n                        <div class=\"bg-warning/90 size-6 rounded-full mix-blend-screen\"></div>\n                      </div>\n                    } @else {\n                      <div class=\"text-base font-bold tracking-widest text-zinc-100 italic\" aria-label=\"Visa\">VISA</div>\n                    }\n                  </div>\n                </div>\n\n                <!-- Middle Row: Card Number & Quick Actions -->\n                <div class=\"relative z-10 my-auto pt-4\">\n                  <div class=\"flex items-center justify-between gap-2\">\n                    <p\n                      class=\"font-mono text-lg font-medium tracking-[0.16em] text-zinc-100 tabular-nums drop-shadow-sm sm:text-xl\"\n                    >\n                      {{ showDetails() ? selectedCard().fullNumber : '•••• •••• •••• ' + selectedCard().last4 }}\n                    </p>\n                    <div class=\"flex items-center gap-1\">\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"icon-sm\"\n                        class=\"size-7 text-zinc-300 hover:bg-white/10 hover:text-white\"\n                        [attr.aria-label]=\"showDetails() ? 'Hide card number' : 'Reveal card number'\"\n                        (click)=\"showDetails.set(!showDetails())\"\n                      >\n                        @if (showDetails()) {\n                          <lucide-icon [img]=\"EyeOff\" class=\"size-3.5\" />\n                        } @else {\n                          <lucide-icon [img]=\"Eye\" class=\"size-3.5\" />\n                        }\n                      </button>\n                      <button\n                        ui-button\n                        variant=\"ghost\"\n                        size=\"icon-sm\"\n                        class=\"size-7 text-zinc-300 hover:bg-white/10 hover:text-white\"\n                        aria-label=\"Copy card number\"\n                        (click)=\"handleCopyNumber()\"\n                      >\n                        @if (copied()) {\n                          <lucide-icon [img]=\"Check\" class=\"text-success size-3.5\" />\n                        } @else {\n                          <lucide-icon [img]=\"Copy\" class=\"size-3.5\" />\n                        }\n                      </button>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Bottom Row: Cardholder, Expiry, CVV -->\n                <div class=\"relative z-10 flex items-end justify-between pt-2\">\n                  <div class=\"space-y-0.5\">\n                    <p class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">Cardholder</p>\n                    <p class=\"max-w-[140px] truncate text-xs font-semibold tracking-wide text-zinc-200 uppercase\">\n                      {{ selectedCard().holder }}\n                    </p>\n                  </div>\n\n                  <div class=\"space-y-0.5\">\n                    <p class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">Expires</p>\n                    <p class=\"font-mono text-xs font-medium text-zinc-200 tabular-nums\">\n                      {{ selectedCard().expiry }}\n                    </p>\n                  </div>\n\n                  <div class=\"space-y-0.5 text-right\">\n                    <p class=\"text-muted-foreground font-mono text-xs tracking-wider uppercase\">CVV</p>\n                    <p class=\"font-mono text-xs font-medium text-zinc-200 tabular-nums\">\n                      {{ showDetails() ? selectedCard().cvv : '•••' }}\n                    </p>\n                  </div>\n                </div>\n              </div>\n            </div>\n\n            <!-- Card Controls & Spending Breakdown -->\n            <div class=\"space-y-6 lg:col-span-6 xl:col-span-7\">\n              <!-- Spending Limit Meter -->\n              <div class=\"border-border bg-muted/30 space-y-3 rounded-lg border p-4\">\n                <div class=\"flex items-center justify-between\">\n                  <span class=\"text-sm font-medium\">Monthly Spending Limit</span>\n                  <span class=\"text-muted-foreground font-mono text-xs tabular-nums\">\n                    \\${{ selectedCard().spent.toLocaleString() }} / \\${{ selectedCard().limit.toLocaleString() }}\n                  </span>\n                </div>\n\n                <ui-progress [value]=\"selectedPercent()\" class=\"h-2\" />\n\n                <div class=\"text-muted-foreground flex items-center justify-between text-xs\">\n                  <span class=\"text-foreground font-medium tabular-nums\">\n                    \\${{ availableFunds().toLocaleString() }} available\n                  </span>\n                  <span class=\"font-medium tabular-nums\"> {{ selectedPercent() }}% utilized </span>\n                </div>\n              </div>\n\n              <!-- Card Controls Row -->\n              <div class=\"grid gap-3 sm:grid-cols-3\">\n                <!-- Freeze Card Toggle -->\n                <div\n                  class=\"border-border bg-card flex items-center justify-between rounded-lg border p-3 sm:flex-col sm:items-start sm:justify-between sm:gap-2\"\n                >\n                  <div class=\"space-y-0.5\">\n                    <p class=\"text-foreground text-xs font-medium\">Freeze Card</p>\n                    <p class=\"text-muted-foreground text-xs\">Block charges</p>\n                  </div>\n                  <ui-switch\n                    size=\"sm\"\n                    aria-label=\"Toggle card freeze status\"\n                    [checked]=\"isSelectedFrozen()\"\n                    (checkedChange)=\"toggleFreeze(selectedCard().id)\"\n                  />\n                </div>\n\n                <!-- Change Limit Button -->\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-auto flex-col items-start gap-1 p-3 text-left\"\n                  (click)=\"adjustLimit(selectedCard().id)\"\n                >\n                  <span class=\"flex w-full items-center justify-between\">\n                    <span class=\"text-foreground text-xs font-medium\">Adjust Limit</span>\n                    <lucide-icon [img]=\"SlidersHorizontal\" class=\"text-muted-foreground size-3.5\" />\n                  </span>\n                  <span class=\"text-muted-foreground text-xs font-normal\">Edit monthly cap</span>\n                </button>\n\n                <!-- Terminate Card Button -->\n                <button\n                  ui-button\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"text-destructive hover:text-destructive hover:bg-destructive/10 border-border h-auto flex-col items-start gap-1 p-3 text-left\"\n                  (click)=\"terminateCard(selectedCard().id)\"\n                >\n                  <span class=\"flex w-full items-center justify-between\">\n                    <span class=\"text-xs font-medium\">Terminate</span>\n                    <lucide-icon [img]=\"Trash2\" class=\"size-3.5\" />\n                  </span>\n                  <span class=\"text-muted-foreground text-xs font-normal\">Close permanently</span>\n                </button>\n              </div>\n\n              <!-- Card Meta Specs -->\n              <div class=\"grid grid-cols-2 gap-3 pt-1 sm:grid-cols-3\">\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground text-xs\">Cardholder</span>\n                  <div class=\"flex items-center gap-1.5 pt-0.5\">\n                    <ui-avatar size=\"xs\" class=\"size-4.5\">\n                      <ui-avatar-image [src]=\"selectedCard().avatar\" [alt]=\"selectedCard().holder\" />\n                      <ui-avatar-fallback class=\"text-xs\">{{ selectedCard().initials }}</ui-avatar-fallback>\n                    </ui-avatar>\n                    <span class=\"truncate text-xs font-medium\">{{ selectedCard().holder }}</span>\n                  </div>\n                </div>\n\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground text-xs\">Card Type</span>\n                  <p class=\"text-foreground text-xs font-medium\">{{ selectedCard().type }}</p>\n                </div>\n\n                <div class=\"space-y-0.5\">\n                  <span class=\"text-muted-foreground text-xs\">Fraud Protection</span>\n                  <div class=\"text-success flex items-center gap-1 text-xs font-medium\">\n                    <lucide-icon [img]=\"ShieldCheck\" class=\"size-3.5\" />\n                    <span>3D Secure Active</span>\n                  </div>\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Active Cards Grid / Table -->\n      <div ui-card class=\"border-border bg-card shadow-xs\">\n        <div ui-card-header class=\"border-b pb-4\">\n          <div class=\"flex items-center justify-between\">\n            <div>\n              <h3 ui-card-title class=\"text-base font-semibold\">Active Cards List</h3>\n              <p ui-card-description class=\"mt-0.5 text-xs\">\n                All company virtual cards currently assigned to team members and services.\n              </p>\n            </div>\n            <span ui-badge wrap variant=\"outline\" class=\"font-mono text-xs tabular-nums\">\n              {{ cards().length }} Issued\n            </span>\n          </div>\n        </div>\n\n        <div ui-card-content class=\"p-0\">\n          <div class=\"divide-border divide-y\">\n            @for (card of cards(); track card.id) {\n              <div [class]=\"rowClass(card)\">\n                <!-- Card Identity & Number -->\n                <div class=\"flex min-w-0 items-center gap-3.5 sm:w-1/3\">\n                  <div [class]=\"cardIconClass(card)\">\n                    <lucide-icon [img]=\"CreditCard\" class=\"size-5\" />\n                  </div>\n\n                  <div class=\"min-w-0 flex-1 space-y-0.5\">\n                    <div class=\"flex items-center gap-2\">\n                      <p class=\"text-foreground truncate text-sm font-medium\">{{ card.name }}</p>\n                    </div>\n                    <div class=\"text-muted-foreground flex items-center gap-2 text-xs\">\n                      <span class=\"font-mono tabular-nums\">•••• {{ card.last4 }}</span>\n                      <span>·</span>\n                      <span class=\"uppercase\">{{ card.brand }}</span>\n                    </div>\n                  </div>\n                </div>\n\n                <!-- Cardholder Avatar & Name -->\n                <div class=\"flex items-center gap-2 sm:w-1/5\">\n                  <ui-avatar size=\"sm\">\n                    <ui-avatar-image [src]=\"card.avatar\" [alt]=\"card.holder\" />\n                    <ui-avatar-fallback>{{ card.initials }}</ui-avatar-fallback>\n                  </ui-avatar>\n                  <div class=\"min-w-0\">\n                    <p class=\"text-foreground truncate text-xs font-medium\">{{ card.holder }}</p>\n                    <p class=\"text-muted-foreground truncate text-xs\">{{ card.type }}</p>\n                  </div>\n                </div>\n\n                <!-- Spending Limit & Progress -->\n                <div class=\"space-y-1.5 sm:w-1/4\">\n                  <div class=\"flex items-center justify-between text-xs\">\n                    <span class=\"text-foreground font-mono font-medium tabular-nums\">\n                      \\${{ card.spent.toLocaleString() }} / \\${{ card.limit.toLocaleString() }}\n                    </span>\n                    <span class=\"text-muted-foreground font-mono tabular-nums\"> {{ cardPercent(card) }}% </span>\n                  </div>\n                  <ui-progress [value]=\"cardPercent(card)\" class=\"h-1.5\" />\n                </div>\n\n                <!-- Status Badge & Actions -->\n                <div class=\"flex items-center justify-between gap-3 sm:justify-end\">\n                  <span\n                    ui-badge\n                    wrap\n                    [variant]=\"card.status === 'Active' ? 'success' : 'warning'\"\n                    class=\"text-xs whitespace-normal\"\n                  >\n                    {{ card.status }}\n                  </span>\n\n                  <div class=\"flex items-center gap-1.5\">\n                    <button\n                      ui-button\n                      [variant]=\"selectedId() === card.id ? 'secondary' : 'ghost'\"\n                      size=\"sm\"\n                      class=\"text-xs\"\n                      (click)=\"selectedId.set(card.id)\"\n                    >\n                      {{ selectedId() === card.id ? 'Viewing' : 'View Card' }}\n                    </button>\n\n                    <button\n                      ui-button\n                      variant=\"ghost\"\n                      size=\"icon-sm\"\n                      [attr.aria-label]=\"card.status === 'Active' ? 'Freeze card' : 'Unfreeze card'\"\n                      (click)=\"toggleFreeze(card.id)\"\n                    >\n                      @if (card.status === 'Active') {\n                        <lucide-icon [img]=\"Snowflake\" class=\"text-muted-foreground size-4\" />\n                      } @else {\n                        <lucide-icon [img]=\"Zap\" class=\"text-warning size-4\" />\n                      }\n                    </button>\n                  </div>\n                </div>\n              </div>\n            }\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiVirtualCardManagerComponent {\n  protected readonly Check = Check\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly Copy = Copy\n  protected readonly CreditCard = CreditCard\n  protected readonly Eye = Eye\n  protected readonly EyeOff = EyeOff\n  protected readonly Plus = Plus\n  protected readonly Radio = Radio\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly SlidersHorizontal = SlidersHorizontal\n  protected readonly Snowflake = Snowflake\n  protected readonly Trash2 = Trash2\n  protected readonly Zap = Zap\n\n  @Input('class') className?: string\n  @Input() set initialCards(v: VirtualCard[] | undefined) {\n    if (v) this.cards.set(v.map((c) => ({ ...c })))\n  }\n\n  readonly cards = signal<VirtualCard[]>(initialCards.map((c) => ({ ...c })))\n  readonly selectedId = signal<string>('vc-1')\n  readonly showDetails = signal<boolean>(false)\n  readonly copied = signal<boolean>(false)\n  readonly actionFeedback = signal<string | null>(null)\n  private copyTimer: ReturnType<typeof setTimeout> | undefined\n  private feedbackTimer: ReturnType<typeof setTimeout> | undefined\n\n  readonly selectedCard = computed(() => this.cards().find((c) => c.id === this.selectedId()) ?? this.cards()[0])\n\n  readonly selectedPercent = computed(() => {\n    const card = this.selectedCard()\n    if (!card || card.limit === 0) return 0\n    return Math.min(100, Math.round((card.spent / card.limit) * 100))\n  })\n\n  readonly availableFunds = computed(() => {\n    const card = this.selectedCard()\n    return Math.max(0, card.limit - card.spent)\n  })\n\n  readonly isSelectedFrozen = computed(() => this.selectedCard()?.status === 'Frozen')\n\n  get rootClass(): string {\n    return cn('mx-auto w-full max-w-6xl space-y-6', this.className)\n  }\n\n  get cardVisualClass(): string {\n    return cn(\n      'relative aspect-[1.586/1] w-full max-w-[420px] rounded-2xl p-6 text-white transition-[filter] duration-300 lg:max-w-full',\n      'border border-zinc-700/60 bg-gradient-to-br from-zinc-900 via-neutral-900 to-zinc-950 shadow-xl',\n      this.isSelectedFrozen() && 'contrast-95 grayscale filter',\n    )\n  }\n\n  rowClass(card: VirtualCard): string {\n    return cn(\n      'group flex flex-col gap-4 p-4 transition-colors sm:flex-row sm:items-center sm:justify-between',\n      this.selectedId() === card.id ? 'bg-muted/40' : 'hover:bg-muted/20',\n    )\n  }\n\n  cardIconClass(card: VirtualCard): string {\n    return cn(\n      'border-border flex size-10 shrink-0 items-center justify-center rounded-lg border shadow-xs transition-transform',\n      card.status === 'Frozen' ? 'bg-muted/60 text-muted-foreground' : 'bg-primary/10 text-primary',\n    )\n  }\n\n  cardPercent(card: VirtualCard): number {\n    if (card.limit === 0) return 0\n    return Math.min(100, Math.round((card.spent / card.limit) * 100))\n  }\n\n  toggleFreeze(id: string): void {\n    this.cards.update((prev) =>\n      prev.map((c) => {\n        if (c.id === id) {\n          const nextStatus = c.status === 'Active' ? 'Frozen' : 'Active'\n          return { ...c, status: nextStatus }\n        }\n        return c\n      }),\n    )\n  }\n\n  handleCopyNumber(): void {\n    const card = this.selectedCard()\n    if (!card) return\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      void navigator.clipboard.writeText(card.fullNumber.replace(/\\s+/g, ''))\n    }\n    this.copied.set(true)\n    clearTimeout(this.copyTimer)\n    this.copyTimer = setTimeout(() => this.copied.set(false), 2000)\n  }\n\n  adjustLimit(cardId: string): void {\n    const current = this.cards().find((c) => c.id === cardId)\n    const newLimit = !current\n      ? 0\n      : current.limit === 5000\n        ? 7500\n        : current.limit === 10000\n          ? 12000\n          : current.limit + 1000\n    this.cards.update((prev) =>\n      prev.map((c) => {\n        if (c.id === cardId) {\n          const next = c.limit === 5000 ? 7500 : c.limit === 10000 ? 12000 : c.limit + 1000\n          return { ...c, limit: next }\n        }\n        return c\n      }),\n    )\n    this.showFeedback(`Limit updated to $${newLimit.toLocaleString()}`)\n  }\n\n  terminateCard(cardId: string): void {\n    if (this.cards().length <= 1) {\n      this.showFeedback('Cannot terminate the last remaining card')\n      return\n    }\n    const remaining = this.cards().filter((c) => c.id !== cardId)\n    this.cards.set(remaining)\n    if (this.selectedId() === cardId) {\n      this.selectedId.set(remaining[0].id)\n    }\n    this.showFeedback('Card terminated permanently')\n  }\n\n  issueNewCard(): void {\n    const newId = `vc-${Date.now().toString().slice(-4)}`\n    const newLast4 = Math.floor(1000 + Math.random() * 9000).toString()\n    const newCard: VirtualCard = {\n      id: newId,\n      name: 'AI & API Workloads',\n      brand: 'visa',\n      last4: newLast4,\n      fullNumber: `4400 1284 9912 ${newLast4}`,\n      holder: 'Elena Rostova',\n      avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=100&auto=format&fit=crop&q=80',\n      initials: 'ER',\n      expiry: '09/30',\n      cvv: '814',\n      spent: 0,\n      limit: 3000,\n      type: 'Monthly Recurring',\n      status: 'Active',\n      billingCycle: 'Resets on 1st of month',\n      purpose: 'OpenAI, Anthropic, and Vector DB infrastructure',\n    }\n    this.cards.update((prev) => [newCard, ...prev])\n    this.selectedId.set(newId)\n    this.showFeedback('New virtual card issued successfully')\n  }\n\n  showFeedback(msg: string): void {\n    this.actionFeedback.set(msg)\n    clearTimeout(this.feedbackTimer)\n    this.feedbackTimer = setTimeout(() => {\n      if (this.actionFeedback() === msg) this.actionFeedback.set(null)\n    }, 3000)\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/virtual-card-manager/virtual-card-manager.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/virtual-card-manager/index.ts",
      "content": "export { UiVirtualCardManagerComponent, type VirtualCard } from './virtual-card-manager.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/virtual-card-manager/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "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/switch.json"
  ],
  "description": "Mercury/Ramp-style corporate virtual card manager: interactive dark credit card visual hero with EMV chip, revealable credentials, live spending limit progress bar, instant freeze toggles, limit adjusters, and an active virtual cards table.",
  "categories": [
    "finance",
    "app",
    "billing"
  ]
}