{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "currency-converter-widget",
  "title": "Currency Converter Widget",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/currency-converter-widget/currency-converter-widget.component.ts",
      "content": "// Wise and Revolut-style real-time multi-currency exchange calculator and transfer fee\n// estimator with live mid-market rates, transparent fee breakdown rail, bank savings\n// comparison, and quick-rate ticker. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, EventEmitter, Input, Output, computed, signal } from '@angular/core'\nimport {\n  ArrowDownRight,\n  ArrowRight,\n  ArrowUpDown,\n  ArrowUpRight,\n  Building2,\n  Coins,\n  Lock,\n  LucideAngularModule,\n  Minus,\n  RefreshCw,\n  ShieldCheck,\n  Zap,\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 {\n  UiSelectComponent,\n  UiSelectContentComponent,\n  UiSelectItemComponent,\n  UiSelectTriggerComponent,\n  UiSelectValueComponent,\n} from '@/ui/select/select.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiInputComponent } from '@/ui/input/input.component'\n\nexport interface CurrencyOption {\n  code: string\n  name: string\n  symbol: string\n  flag: string\n  rateToBase: number // USD base = 1.0\n  deliverySpeed: string\n  deliveryNetwork: string\n}\n\nexport interface PopularPair {\n  id: string\n  from: string\n  to: string\n  change24h: number\n  trend: 'up' | 'down'\n}\n\nexport interface TransferPayload {\n  sendAmount: number\n  fromCurrency: string\n  toCurrency: string\n  recipientGets: number\n  fee: number\n  rate: number\n}\n\nconst CURRENCIES: CurrencyOption[] = [\n  {\n    code: 'USD',\n    name: 'US Dollar',\n    symbol: '$',\n    flag: '🇺🇸',\n    rateToBase: 1.0,\n    deliverySpeed: 'in minutes',\n    deliveryNetwork: 'FedNow / RTP',\n  },\n  {\n    code: 'EUR',\n    name: 'Euro',\n    symbol: '€',\n    flag: '🇪🇺',\n    rateToBase: 0.9245,\n    deliverySpeed: 'in minutes',\n    deliveryNetwork: 'SEPA Instant',\n  },\n  {\n    code: 'GBP',\n    name: 'British Pound',\n    symbol: '£',\n    flag: '🇬🇧',\n    rateToBase: 0.7892,\n    deliverySpeed: 'in seconds',\n    deliveryNetwork: 'Faster Payments',\n  },\n  {\n    code: 'JPY',\n    name: 'Japanese Yen',\n    symbol: '¥',\n    flag: '🇯🇵',\n    rateToBase: 154.6,\n    deliverySpeed: 'within 4 hours',\n    deliveryNetwork: 'Zengin System',\n  },\n  {\n    code: 'CAD',\n    name: 'Canadian Dollar',\n    symbol: 'CA$',\n    flag: '🇨🇦',\n    rateToBase: 1.378,\n    deliverySpeed: 'in minutes',\n    deliveryNetwork: 'Interac Direct',\n  },\n  {\n    code: 'AUD',\n    name: 'Australian Dollar',\n    symbol: 'A$',\n    flag: '🇦🇺',\n    rateToBase: 1.523,\n    deliverySpeed: 'in minutes',\n    deliveryNetwork: 'NPP / PayID',\n  },\n  {\n    code: 'SGD',\n    name: 'Singapore Dollar',\n    symbol: 'S$',\n    flag: '🇸🇬',\n    rateToBase: 1.348,\n    deliverySpeed: 'instant',\n    deliveryNetwork: 'FAST Network',\n  },\n  {\n    code: 'CHF',\n    name: 'Swiss Franc',\n    symbol: 'CHF',\n    flag: '🇨🇭',\n    rateToBase: 0.898,\n    deliverySpeed: 'in minutes',\n    deliveryNetwork: 'SIC Instant',\n  },\n]\n\nconst POPULAR_PAIRS: PopularPair[] = [\n  { id: 'eur-usd', from: 'EUR', to: 'USD', change24h: 0.18, trend: 'up' },\n  { id: 'gbp-usd', from: 'GBP', to: 'USD', change24h: 0.24, trend: 'up' },\n  { id: 'usd-jpy', from: 'USD', to: 'JPY', change24h: -0.32, trend: 'down' },\n  { id: 'usd-cad', from: 'USD', to: 'CAD', change24h: 0.09, trend: 'up' },\n  { id: 'usd-eur', from: 'USD', to: 'EUR', change24h: -0.18, trend: 'down' },\n  { id: 'gbp-eur', from: 'GBP', to: 'EUR', change24h: 0.05, trend: 'up' },\n]\n\nfunction formatAmount(val: number, currencyCode: string): string {\n  const isZeroDecimal = currencyCode === 'JPY'\n  return new Intl.NumberFormat('en-US', {\n    minimumFractionDigits: isZeroDecimal ? 0 : 2,\n    maximumFractionDigits: isZeroDecimal ? 0 : 2,\n  }).format(val)\n}\n\nfunction getPairRate(fromCode: string, toCode: string): string {\n  const f = CURRENCIES.find((c) => c.code === fromCode)?.rateToBase || 1\n  const t = CURRENCIES.find((c) => c.code === toCode)?.rateToBase || 1\n  const rate = t / f\n  return rate >= 100 ? rate.toFixed(2) : rate.toFixed(4)\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-currency-converter-widget, [ui-currency-converter-widget]',\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    UiSelectComponent,\n    UiSelectContentComponent,\n    UiSelectItemComponent,\n    UiSelectTriggerComponent,\n    UiSelectValueComponent,\n    UiSeparatorComponent,\n  ],\n  template: `\n    <div data-slot=\"currency-converter-widget\" [class]=\"rootClass\">\n      <!-- Header Section -->\n      <div class=\"space-y-2\">\n        <div class=\"flex flex-wrap items-center justify-between gap-2\">\n          <h2 class=\"text-foreground text-xl font-bold tracking-tight sm:text-2xl\">\n            Currency Converter & International Transfer\n          </h2>\n          <span ui-badge variant=\"outline\" class=\"gap-1.5 py-1 text-xs font-normal\">\n            <span class=\"bg-success size-2 animate-pulse rounded-full\"></span>\n            Live Market Rates\n          </span>\n        </div>\n        <p class=\"text-muted-foreground text-sm\">\n          Real-time mid-market exchange rates with transparent fees. No hidden markups.\n        </p>\n\n        <!-- Refresh & Current Mid-market rate banner -->\n        <div\n          class=\"border-border bg-muted/40 text-muted-foreground flex flex-wrap items-center justify-between gap-2 rounded-lg border px-3.5 py-2 text-xs\"\n        >\n          <div class=\"flex items-center gap-2\">\n            <span class=\"text-foreground font-medium\">\n              Mid-market rate: 1 {{ fromCurrencyObj().code }} = {{ formattedExchangeRate() }}\n              {{ toCurrencyObj().code }}\n            </span>\n            <span>Updated 1m ago</span>\n          </div>\n          <button\n            type=\"button\"\n            aria-label=\"Refresh exchange rates\"\n            class=\"text-foreground hover:text-primary focus-visible:ring-ring inline-flex min-h-6 items-center gap-1 rounded px-1.5 py-0.5 font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none\"\n            (click)=\"handleRefresh()\"\n          >\n            <lucide-icon [img]=\"RefreshCw\" [class]=\"refreshIconClass\" />\n            <span>{{ refreshSuccess() ? 'Refreshed!' : 'Refresh' }}</span>\n          </button>\n        </div>\n      </div>\n\n      <!-- Main Converter Card -->\n      <div ui-card class=\"border-border shadow-xs\">\n        <div ui-card-content class=\"space-y-4 p-5 sm:p-6\">\n          <!-- Payment method tab pill -->\n          <div class=\"flex items-center justify-between gap-2\">\n            <div class=\"border-border bg-muted/50 inline-flex rounded-lg border p-1 text-xs\">\n              <button type=\"button\" [class]=\"methodTabClass('bank')\" (click)=\"paymentMethod.set('bank')\">\n                Bank Transfer (Lowest Fee)\n              </button>\n              <button type=\"button\" [class]=\"methodTabClass('card')\" (click)=\"paymentMethod.set('card')\">\n                Debit / Credit Card\n              </button>\n            </div>\n\n            <span class=\"text-muted-foreground hidden text-xs sm:inline-flex\"> Guaranteed rate for 24h </span>\n          </div>\n\n          <!-- \"You send\" Input Container -->\n          <div\n            class=\"border-input bg-card focus-within:border-ring focus-within:ring-ring/20 rounded-xl border p-3.5 shadow-2xs transition-colors focus-within:ring-2\"\n          >\n            <div class=\"text-muted-foreground mb-1.5 flex items-center justify-between text-xs\">\n              <label for=\"angular-send-amount-input\" class=\"text-foreground font-medium\">You send</label>\n              <span>Max limit: $100,000 / transfer</span>\n            </div>\n\n            <div class=\"flex flex-wrap items-center gap-3\">\n              <div class=\"flex flex-1 items-baseline gap-1\">\n                <span class=\"text-muted-foreground text-xl font-semibold select-none sm:text-2xl\">\n                  {{ fromCurrencyObj().symbol }}\n                </span>\n                <ui-input\n                  id=\"angular-send-amount-input\"\n                  type=\"number\"\n                  [min]=\"1\"\n                  [value]=\"sendAmount()\"\n                  (valueChange)=\"onAmountChange($event)\"\n                  placeholder=\"1,000.00\"\n                  class=\"text-foreground placeholder:text-muted-foreground/40 w-full bg-transparent text-2xl font-bold tracking-tight tabular-nums outline-none sm:text-3xl\"\n                />\n              </div>\n\n              <!-- From Currency Selector -->\n              <div class=\"w-36 shrink-0\">\n                <ui-select [value]=\"fromCurrency()\" (valueChange)=\"fromCurrency.set($event)\">\n                  <button ui-select-trigger class=\"bg-muted/40 h-11 w-full text-xs font-medium sm:text-sm\">\n                    <ui-select-value placeholder=\"Select currency\" />\n                  </button>\n                  <ui-select-content class=\"max-h-64\">\n                    @for (curr of currencies; track curr.code) {\n                      <ui-select-item [value]=\"curr.code\">\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-base\">{{ curr.flag }}</span>\n                          <span class=\"font-semibold\">{{ curr.code }}</span>\n                          <span class=\"text-muted-foreground truncate text-xs\">({{ curr.name }})</span>\n                        </div>\n                      </ui-select-item>\n                    }\n                  </ui-select-content>\n                </ui-select>\n              </div>\n            </div>\n          </div>\n\n          <!-- Transfer Breakdown Rail (Vertical Connector) -->\n          <div\n            class=\"before:bg-border relative my-1 space-y-3.5 py-2 pl-6 before:absolute before:top-2 before:bottom-2 before:left-2.5 before:w-px\"\n          >\n            <!-- Step 1: Fee -->\n            <div class=\"relative flex items-center justify-between text-xs\">\n              <div\n                class=\"border-border bg-card text-muted-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border\"\n              >\n                <lucide-icon [img]=\"Minus\" class=\"size-2.5\" />\n              </div>\n              <div class=\"flex items-center gap-1.5\">\n                <span class=\"text-foreground font-semibold tabular-nums\">\n                  -{{ fromCurrencyObj().symbol }}{{ formatAmount(totalFee(), fromCurrencyObj().code) }}\n                  {{ fromCurrencyObj().code }}\n                </span>\n                <span class=\"text-muted-foreground\">Transfer fee</span>\n                <span ui-badge variant=\"secondary\" class=\"text-muted-foreground h-4.5 px-1.5 text-xs font-normal\">\n                  {{ paymentMethod() === 'card' ? '0.65% + $1.20' : '0.35% + $1.20' }}\n                </span>\n              </div>\n              <span class=\"text-muted-foreground hidden sm:inline\">Transparent fixed rate</span>\n            </div>\n\n            <!-- Step 2: Amount to convert -->\n            <div class=\"relative flex items-center justify-between text-xs\">\n              <div\n                class=\"border-border bg-card text-muted-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border\"\n              >\n                <span class=\"text-xs leading-none font-bold\">=</span>\n              </div>\n              <div class=\"flex items-center gap-1.5\">\n                <span class=\"text-foreground font-semibold tabular-nums\">\n                  {{ fromCurrencyObj().symbol }}{{ formatAmount(amountToConvert(), fromCurrencyObj().code) }}\n                  {{ fromCurrencyObj().code }}\n                </span>\n                <span class=\"text-muted-foreground\">Amount to convert</span>\n              </div>\n              <span class=\"text-muted-foreground hidden sm:inline\">Net transfer base</span>\n            </div>\n\n            <!-- Step 3: Guaranteed Exchange Rate & Swap Button Line -->\n            <div class=\"relative flex flex-wrap items-center justify-between gap-2 text-xs\">\n              <div\n                class=\"border-primary/50 bg-primary text-primary-foreground absolute -left-[19px] flex size-3.5 items-center justify-center rounded-full border\"\n              >\n                <lucide-icon [img]=\"Lock\" class=\"size-2\" />\n              </div>\n              <div class=\"flex flex-wrap items-center gap-2\">\n                <span class=\"text-foreground font-semibold tabular-nums\"> × {{ formattedExchangeRate() }} </span>\n                <span class=\"text-muted-foreground\">Guaranteed mid-market rate</span>\n                <span\n                  ui-badge\n                  variant=\"outline\"\n                  class=\"border-success/30 bg-success/10 text-success gap-1 py-0.5 text-xs\"\n                >\n                  <lucide-icon [img]=\"Lock\" class=\"size-2.5\" />\n                  24h Guarantee\n                </span>\n              </div>\n\n              <!-- Swap Currencies Button -->\n              <button\n                type=\"button\"\n                aria-label=\"Swap currencies\"\n                class=\"border-border bg-card text-foreground hover:bg-accent hover:border-primary/50 focus-visible:ring-ring inline-flex min-h-6 items-center gap-1.5 rounded-full border px-2.5 py-1 text-xs font-medium shadow-2xs transition-[background-color,border-color,box-shadow,scale] focus-visible:ring-2 focus-visible:outline-none active:scale-95\"\n                (click)=\"handleSwap()\"\n              >\n                <lucide-icon [img]=\"ArrowUpDown\" class=\"text-primary size-3\" />\n                <span>Swap</span>\n              </button>\n            </div>\n          </div>\n\n          <!-- \"Recipient gets\" Input / Output Container -->\n          <div\n            class=\"border-input bg-card focus-within:border-ring focus-within:ring-ring/20 rounded-xl border p-3.5 shadow-2xs transition-colors focus-within:ring-2\"\n          >\n            <div class=\"text-muted-foreground mb-1.5 flex items-center justify-between text-xs\">\n              <label class=\"text-foreground font-medium\">Recipient gets</label>\n              <span class=\"text-success flex items-center gap-1 font-medium\">\n                <lucide-icon [img]=\"Zap\" class=\"size-3\" />\n                {{ toCurrencyObj().deliverySpeed }}\n              </span>\n            </div>\n\n            <div class=\"flex flex-wrap items-center gap-3\">\n              <div class=\"flex min-w-0 flex-1 items-baseline gap-1\">\n                <span class=\"text-muted-foreground text-xl font-semibold select-none sm:text-2xl\">\n                  {{ toCurrencyObj().symbol }}\n                </span>\n                <div\n                  class=\"text-foreground w-full truncate text-2xl font-bold tracking-tight tabular-nums select-all sm:text-3xl\"\n                >\n                  {{ formatAmount(recipientGets(), toCurrencyObj().code) }}\n                </div>\n              </div>\n\n              <!-- To Currency Selector -->\n              <div class=\"w-36 shrink-0\">\n                <ui-select [value]=\"toCurrency()\" (valueChange)=\"toCurrency.set($event)\">\n                  <button ui-select-trigger class=\"bg-muted/40 h-11 w-full text-xs font-medium sm:text-sm\">\n                    <ui-select-value placeholder=\"Select currency\" />\n                  </button>\n                  <ui-select-content class=\"max-h-64\">\n                    @for (curr of currencies; track curr.code) {\n                      <ui-select-item [value]=\"curr.code\">\n                        <div class=\"flex items-center gap-2\">\n                          <span class=\"text-base\">{{ curr.flag }}</span>\n                          <span class=\"font-semibold\">{{ curr.code }}</span>\n                          <span class=\"text-muted-foreground truncate text-xs\">({{ curr.name }})</span>\n                        </div>\n                      </ui-select-item>\n                    }\n                  </ui-select-content>\n                </ui-select>\n              </div>\n            </div>\n          </div>\n\n          <!-- Delivery Speed & Savings Breakdown Banner -->\n          <div class=\"border-border bg-muted/30 space-y-3 rounded-lg border p-4\">\n            <div class=\"flex flex-wrap items-center justify-between gap-2\">\n              <div class=\"flex items-center gap-2 text-xs\">\n                <span class=\"bg-primary/10 text-primary flex size-6 items-center justify-center rounded-full\">\n                  <lucide-icon [img]=\"Zap\" class=\"size-3.5\" />\n                </span>\n                <div>\n                  <p class=\"text-foreground font-semibold\">Estimated delivery: {{ toCurrencyObj().deliverySpeed }}</p>\n                  <p class=\"text-muted-foreground\">Via {{ toCurrencyObj().deliveryNetwork }}</p>\n                </div>\n              </div>\n\n              <span ui-badge variant=\"outline\" class=\"border-warning/30 bg-warning/10 text-warning gap-1 py-1 text-xs\">\n                <lucide-icon [img]=\"Coins\" class=\"size-3\" />\n                Save ~{{ fromCurrencyObj().symbol }}{{ formatAmount(estimatedSavings(), fromCurrencyObj().code) }}\n                {{ fromCurrencyObj().code }}\n              </span>\n            </div>\n\n            <ui-separator />\n\n            <!-- Comparison vs Standard High-Street Bank -->\n            <div class=\"grid grid-cols-1 gap-3 text-xs sm:grid-cols-2\">\n              <div class=\"bg-card/60 border-border flex items-start gap-2 rounded-md border p-2.5\">\n                <lucide-icon [img]=\"ShieldCheck\" class=\"text-success mt-0.5 size-4 shrink-0\" />\n                <div>\n                  <p class=\"text-foreground font-medium\">UIPKGE Transfer</p>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Fee: {{ fromCurrencyObj().symbol }}{{ formatAmount(totalFee(), fromCurrencyObj().code) }} · 0%\n                    exchange spread\n                  </p>\n                </div>\n              </div>\n\n              <div class=\"bg-card/60 border-border flex items-start gap-2 rounded-md border p-2.5 opacity-75\">\n                <lucide-icon [img]=\"Building2\" class=\"text-muted-foreground mt-0.5 size-4 shrink-0\" />\n                <div>\n                  <p class=\"text-foreground font-medium\">Standard Bank Wire</p>\n                  <p class=\"text-muted-foreground text-xs\">\n                    Fee: ~{{ fromCurrencyObj().symbol }}{{ formatAmount(bankTotalCost(), fromCurrencyObj().code) }} ·\n                    ~3.2% hidden spread\n                  </p>\n                </div>\n              </div>\n            </div>\n          </div>\n\n          <!-- Primary Action Button -->\n          <button ui-button class=\"h-11 w-full gap-2 text-base font-semibold shadow-sm\" (click)=\"handleTransfer()\">\n            <span\n              >Get Started / Send {{ fromCurrencyObj().symbol\n              }}{{ formatAmount(sendAmount(), fromCurrencyObj().code) }}</span\n            >\n            <lucide-icon [img]=\"ArrowRight\" class=\"size-4\" />\n          </button>\n\n          <p class=\"text-muted-foreground flex items-center justify-center gap-1.5 text-center text-xs\">\n            <lucide-icon [img]=\"Lock\" class=\"text-muted-foreground size-3\" />\n            <span>Rates locked for 24 hours. Licensed & regulated financial institution.</span>\n          </p>\n        </div>\n      </div>\n\n      <!-- Popular Currency Pairs Quick Rate Ticker -->\n      <div class=\"space-y-3\">\n        <div class=\"flex items-center justify-between\">\n          <h3 class=\"text-foreground text-sm font-semibold\">Popular Currency Pairs</h3>\n          <span class=\"text-muted-foreground text-xs\">Click pair to load</span>\n        </div>\n\n        <div class=\"grid grid-cols-2 gap-2.5 sm:grid-cols-3\">\n          @for (pair of popularPairs; track pair.id) {\n            <button type=\"button\" [class]=\"pairCardClass(pair)\" (click)=\"handleSelectPair(pair)\">\n              <div class=\"flex w-full items-center justify-between text-xs\">\n                <span class=\"text-foreground font-semibold\">{{ pair.from }} / {{ pair.to }}</span>\n                <span [class]=\"pairTrendClass(pair)\">\n                  @if (pair.trend === 'up') {\n                    <lucide-icon [img]=\"ArrowUpRight\" class=\"size-3\" />\n                  } @else {\n                    <lucide-icon [img]=\"ArrowDownRight\" class=\"size-3\" />\n                  }\n                  {{ pair.change24h > 0 ? '+' + pair.change24h + '%' : pair.change24h + '%' }}\n                </span>\n              </div>\n\n              <div class=\"text-foreground text-sm font-bold tabular-nums\">\n                {{ getPairRate(pair.from, pair.to) }}\n              </div>\n            </button>\n          }\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiCurrencyConverterWidgetComponent {\n  protected readonly ArrowDownRight = ArrowDownRight\n  protected readonly ArrowRight = ArrowRight\n  protected readonly ArrowUpDown = ArrowUpDown\n  protected readonly ArrowUpRight = ArrowUpRight\n  protected readonly Building2 = Building2\n  protected readonly Coins = Coins\n  protected readonly Lock = Lock\n  protected readonly Minus = Minus\n  protected readonly RefreshCw = RefreshCw\n  protected readonly ShieldCheck = ShieldCheck\n  protected readonly Zap = Zap\n\n  protected readonly currencies = CURRENCIES\n  protected readonly popularPairs = POPULAR_PAIRS\n\n  @Input() set initialSendAmount(v: number | undefined) {\n    if (v !== undefined) this.sendAmount.set(v)\n  }\n  @Input() set initialFromCurrency(v: string | undefined) {\n    if (v) this.fromCurrency.set(v)\n  }\n  @Input() set initialToCurrency(v: string | undefined) {\n    if (v) this.toCurrency.set(v)\n  }\n  @Input('class') className?: string\n  @Output() transfer = new EventEmitter<TransferPayload>()\n\n  readonly sendAmount = signal(1000)\n  readonly fromCurrency = signal('USD')\n  readonly toCurrency = signal('EUR')\n  readonly paymentMethod = signal<'bank' | 'card'>('bank')\n  readonly isRefreshing = signal(false)\n  readonly refreshSuccess = signal(false)\n\n  readonly fromCurrencyObj = computed(() => CURRENCIES.find((c) => c.code === this.fromCurrency()) || CURRENCIES[0]!)\n  readonly toCurrencyObj = computed(() => CURRENCIES.find((c) => c.code === this.toCurrency()) || CURRENCIES[1]!)\n\n  // Dynamic mid-market rate calculation\n  readonly exchangeRate = computed(() => {\n    const from = this.fromCurrencyObj()\n    const to = this.toCurrencyObj()\n    if (!from || !to) return 1\n    return to.rateToBase / from.rateToBase\n  })\n\n  readonly inverseExchangeRate = computed(() => {\n    if (this.exchangeRate() === 0) return 0\n    return 1 / this.exchangeRate()\n  })\n\n  readonly formattedExchangeRate = computed(() => {\n    const r = this.exchangeRate()\n    if (r >= 100) return r.toFixed(2)\n    if (r >= 1) return r.toFixed(4)\n    return r.toFixed(5)\n  })\n\n  // Fee breakdown logic\n  readonly fixedBaseFee = computed(() => {\n    // Base fixed fee equivalent to ~$1.20 USD\n    const fromRate = this.fromCurrencyObj().rateToBase\n    const base = 1.2 * fromRate\n    return this.paymentMethod() === 'card' ? base * 1.6 : base\n  })\n\n  readonly variableRate = computed(() => (this.paymentMethod() === 'card' ? 0.0065 : 0.0035))\n\n  readonly variableFee = computed(() => {\n    const amt = Number(this.sendAmount()) || 0\n    return amt * this.variableRate()\n  })\n\n  readonly totalFee = computed(() => this.fixedBaseFee() + this.variableFee())\n\n  readonly amountToConvert = computed(() => {\n    const amt = Number(this.sendAmount()) || 0\n    return Math.max(0, amt - this.totalFee())\n  })\n\n  readonly recipientGets = computed(() => this.amountToConvert() * this.exchangeRate())\n\n  // Bank comparison calculation\n  readonly bankWireFee = computed(() => 20 * this.fromCurrencyObj().rateToBase)\n  private readonly bankMarkupSpread = 0.032 // 3.2% average hidden bank margin\n  readonly bankTotalCost = computed(() => {\n    const amt = Number(this.sendAmount()) || 0\n    return this.bankWireFee() + amt * this.bankMarkupSpread\n  })\n\n  readonly estimatedSavings = computed(() => Math.max(0, this.bankTotalCost() - this.totalFee()))\n\n  get rootClass(): string {\n    return cn('mx-auto w-full max-w-2xl space-y-6', this.className)\n  }\n\n  get refreshIconClass(): string {\n    return cn('size-3.5 transition-transform duration-500', this.isRefreshing() ? 'text-primary animate-spin' : '')\n  }\n\n  methodTabClass(method: 'bank' | 'card'): string {\n    return cn(\n      'min-h-6 rounded-md px-3 py-1 font-medium transition-colors',\n      this.paymentMethod() === method\n        ? 'bg-card text-foreground shadow-xs'\n        : 'text-muted-foreground hover:text-foreground',\n    )\n  }\n\n  pairCardClass(pair: PopularPair): string {\n    const selected = this.fromCurrency() === pair.from && this.toCurrency() === pair.to\n    return cn(\n      'hover:bg-accent focus-visible:ring-ring flex flex-col items-start gap-1 rounded-lg border p-3 text-left transition-colors focus-visible:ring-2 focus-visible:outline-none',\n      selected ? 'border-primary bg-primary/5 ring-primary/20 ring-1' : 'border-border bg-card',\n    )\n  }\n\n  pairTrendClass(pair: PopularPair): string {\n    return cn('flex items-center text-xs font-medium', pair.trend === 'up' ? 'text-success' : 'text-destructive')\n  }\n\n  formatAmount(val: number, currencyCode: string): string {\n    return formatAmount(val, currencyCode)\n  }\n\n  getPairRate(fromCode: string, toCode: string): string {\n    return getPairRate(fromCode, toCode)\n  }\n\n  onAmountChange(raw: string): void {\n    this.sendAmount.set(Number.parseFloat(raw) || 0)\n  }\n\n  handleSwap(): void {\n    const temp = this.fromCurrency()\n    this.fromCurrency.set(this.toCurrency())\n    this.toCurrency.set(temp)\n  }\n\n  handleSelectPair(pair: PopularPair): void {\n    this.fromCurrency.set(pair.from)\n    this.toCurrency.set(pair.to)\n  }\n\n  handleRefresh(): void {\n    if (this.isRefreshing()) return\n    this.isRefreshing.set(true)\n    setTimeout(() => {\n      this.isRefreshing.set(false)\n      this.refreshSuccess.set(true)\n      setTimeout(() => {\n        this.refreshSuccess.set(false)\n      }, 2000)\n    }, 450)\n  }\n\n  handleTransfer(): void {\n    this.transfer.emit({\n      sendAmount: Number(this.sendAmount()) || 0,\n      fromCurrency: this.fromCurrency(),\n      toCurrency: this.toCurrency(),\n      recipientGets: this.recipientGets(),\n      fee: this.totalFee(),\n      rate: this.exchangeRate(),\n    })\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/currency-converter-widget/currency-converter-widget.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/currency-converter-widget/index.ts",
      "content": "export {\n  UiCurrencyConverterWidgetComponent,\n  type CurrencyOption,\n  type PopularPair,\n  type TransferPayload,\n} from './currency-converter-widget.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/currency-converter-widget/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/select.json",
    "https://uipkge.dev/r/angular/separator.json"
  ],
  "description": "Wise and Revolut-style real-time multi-currency exchange calculator and transfer fee estimator with live mid-market rates, transparent fee breakdown rail, bank savings comparison, and quick-rate ticker.",
  "categories": [
    "finance",
    "fintech",
    "billing",
    "commerce"
  ]
}