{
  "$schema": "https://uipkge.dev/schema/registry-item.json",
  "name": "loan-calculator",
  "title": "Loan Calculator",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-angular/blocks/loan-calculator/loan-calculator.component.ts",
      "content": "// Interactive mortgage and commercial loan amortization schedule calculator with dynamic\n// sliders, loan type presets, extra payment impact simulation, principal vs interest visual\n// ratio bar, and year-by-year amortization breakdown table. Port of the React block 1:1.\nimport { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'\nimport {\n  ArrowRight,\n  Building2,\n  Calculator,\n  Calendar,\n  Car,\n  CheckCircle2,\n  DollarSign,\n  FileSpreadsheet,\n  Home,\n  LucideAngularModule,\n  PiggyBank,\n  Wallet,\n} from 'lucide-angular'\nimport { cn } from '@/lib/utils'\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 { UiInputComponent } from '@/ui/input/input.component'\nimport { UiSeparatorComponent } from '@/ui/separator/separator.component'\nimport { UiSliderComponent } from '@/ui/slider/slider.component'\nimport {\n  UiTableBodyComponent,\n  UiTableCellComponent,\n  UiTableComponent,\n  UiTableFooterComponent,\n  UiTableHeadComponent,\n  UiTableHeaderComponent,\n  UiTableRowComponent,\n} from '@/ui/table/table.component'\nimport { UiTabsComponent, UiTabsListComponent, UiTabsTriggerComponent } from '@/ui/tabs/tabs.component'\n\nexport type LoanType = 'mortgage-30' | 'commercial-10' | 'auto-5' | 'personal-3'\n\nexport interface LoanPreset {\n  id: LoanType\n  name: string\n  amount: number\n  rate: number\n  termYears: number\n  extraPayment: number\n  description: string\n}\n\nexport const LOAN_PRESETS: Record<LoanType, LoanPreset> = {\n  'mortgage-30': {\n    id: 'mortgage-30',\n    name: 'Mortgage 30Y',\n    amount: 450_000,\n    rate: 6.5,\n    termYears: 30,\n    extraPayment: 200,\n    description: '30-year fixed home mortgage',\n  },\n  'commercial-10': {\n    id: 'commercial-10',\n    name: 'Commercial 10Y',\n    amount: 1_200_000,\n    rate: 7.25,\n    termYears: 10,\n    extraPayment: 500,\n    description: '10-year commercial real estate loan',\n  },\n  'auto-5': {\n    id: 'auto-5',\n    name: 'Auto Loan 5Y',\n    amount: 45_000,\n    rate: 5.9,\n    termYears: 5,\n    extraPayment: 50,\n    description: '60-month standard vehicle loan',\n  },\n  'personal-3': {\n    id: 'personal-3',\n    name: 'Personal Loan',\n    amount: 25_000,\n    rate: 9.5,\n    termYears: 3,\n    extraPayment: 25,\n    description: '36-month fixed unsecured personal loan',\n  },\n}\n\nexport const LOAN_AMOUNT_MARKS: Record<number, string> = {\n  100000: '$100K',\n  500000: '$500K',\n  1000000: '$1M',\n  2000000: '$2M',\n}\n\nexport const INTEREST_RATE_MARKS: Record<number, string> = {\n  1: '1%',\n  5: '5%',\n  10: '10%',\n  15: '15%',\n}\n\nexport const LOAN_TERM_MARKS: Record<number, string> = {\n  5: '5Y',\n  10: '10Y',\n  15: '15Y',\n  20: '20Y',\n  30: '30Y',\n}\n\nexport const EXTRA_PAYMENT_MARKS: Record<number, string> = {\n  0: '$0',\n  500: '$500',\n  1000: '$1K',\n  2000: '$2K',\n}\n\nexport const TERM_PRESETS = [5, 10, 15, 20, 30]\n\nexport interface MonthRecord {\n  month: number\n  principalPaid: number\n  interestPaid: number\n  extraPaid: number\n  totalPaid: number\n  remainingBalance: number\n}\n\nexport interface YearRecord {\n  year: number\n  principalPaid: number\n  interestPaid: number\n  extraPaid: number\n  totalPaid: number\n  remainingBalance: number\n}\n\nexport function fmtCurrency(val: number): string {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD',\n    minimumFractionDigits: 2,\n    maximumFractionDigits: 2,\n  }).format(val || 0)\n}\n\nexport function fmtCurrencyWhole(val: number): string {\n  return new Intl.NumberFormat('en-US', {\n    style: 'currency',\n    currency: 'USD',\n    minimumFractionDigits: 0,\n    maximumFractionDigits: 0,\n  }).format(val || 0)\n}\n\nexport function baseMonthlyPaymentFor(principal: number, annualRate: number, termYears: number): number {\n  const totalMonths = termYears * 12\n  if (principal <= 0 || totalMonths <= 0) return 0\n  if (annualRate <= 0) return principal / totalMonths\n  const monthlyRate = annualRate / 100 / 12\n  const factor = Math.pow(1 + monthlyRate, totalMonths)\n  if (!isFinite(factor) || factor <= 1) return principal / totalMonths\n  return (principal * (monthlyRate * factor)) / (factor - 1)\n}\n\nexport interface AmortizationResult {\n  monthlySchedule: MonthRecord[]\n  yearlySchedule: YearRecord[]\n  actualMonths: number\n  actualYears: string\n  monthsSaved: number\n  yearsSaved: string\n  totalInterestPaid: number\n  totalLoanCost: number\n  totalExtraPaid: number\n  interestSavings: number\n}\n\nexport function amortize(\n  loanAmount: number,\n  interestRate: number,\n  loanTermYears: number,\n  extraMonthlyPayment: number,\n  basePayment: number,\n  totalInterestBase: number,\n): AmortizationResult {\n  const principal = Math.max(0, loanAmount)\n  const annualRate = Math.max(0, interestRate)\n  const termYears = Math.max(1, loanTermYears)\n  const extra = Math.max(0, extraMonthlyPayment)\n  const monthlyRate = annualRate / 100 / 12\n  const standardMonths = termYears * 12\n\n  let balance = principal\n  const monthlySchedule: MonthRecord[] = []\n  let totalInterest = 0\n  let totalPaid = 0\n  let totalExtra = 0\n  let month = 0\n  const maxMonths = 1200\n\n  while (balance > 0.005 && month < maxMonths) {\n    month++\n    const interestForMonth = annualRate > 0 ? balance * monthlyRate : 0\n    let scheduledTotal = basePayment + extra\n    if (balance + interestForMonth < scheduledTotal) scheduledTotal = balance + interestForMonth\n    const principalForMonth = Math.min(balance, Math.max(0, scheduledTotal - interestForMonth))\n    const extraForMonth = Math.max(0, scheduledTotal - basePayment)\n    balance = Math.max(0, balance - principalForMonth)\n    totalInterest += interestForMonth\n    totalPaid += scheduledTotal\n    totalExtra += extraForMonth\n    monthlySchedule.push({\n      month,\n      principalPaid: principalForMonth,\n      interestPaid: interestForMonth,\n      extraPaid: extraForMonth,\n      totalPaid: scheduledTotal,\n      remainingBalance: balance,\n    })\n  }\n\n  const yearlySchedule: YearRecord[] = []\n  const totalYearsActual = Math.ceil(monthlySchedule.length / 12)\n  for (let y = 1; y <= totalYearsActual; y++) {\n    const yearMonths = monthlySchedule.slice((y - 1) * 12, Math.min(y * 12, monthlySchedule.length))\n    if (yearMonths.length === 0) continue\n    yearlySchedule.push({\n      year: y,\n      principalPaid: yearMonths.reduce((s, m) => s + m.principalPaid, 0),\n      interestPaid: yearMonths.reduce((s, m) => s + m.interestPaid, 0),\n      extraPaid: yearMonths.reduce((s, m) => s + m.extraPaid, 0),\n      totalPaid: yearMonths.reduce((s, m) => s + m.totalPaid, 0),\n      remainingBalance: yearMonths[yearMonths.length - 1]?.remainingBalance ?? 0,\n    })\n  }\n\n  const monthsSaved = Math.max(0, standardMonths - monthlySchedule.length)\n  return {\n    monthlySchedule,\n    yearlySchedule,\n    actualMonths: monthlySchedule.length,\n    actualYears: (monthlySchedule.length / 12).toFixed(1),\n    monthsSaved,\n    yearsSaved: (monthsSaved / 12).toFixed(1),\n    totalInterestPaid: totalInterest,\n    totalLoanCost: totalPaid,\n    totalExtraPaid: totalExtra,\n    interestSavings: Math.max(0, totalInterestBase - totalInterest),\n  }\n}\n\n@Component({\n  changeDetection: ChangeDetectionStrategy.Eager,\n  selector: 'ui-loan-calculator, [ui-loan-calculator]',\n  standalone: true,\n  host: { '[attr.class]': '\"contents\"' },\n  imports: [\n    LucideAngularModule,\n    UiBadgeComponent,\n    UiButtonComponent,\n    UiCardComponent,\n    UiCardContentComponent,\n    UiCardDescriptionComponent,\n    UiCardFooterComponent,\n    UiCardHeaderComponent,\n    UiCardTitleComponent,\n    UiInputComponent,\n    UiSeparatorComponent,\n    UiSliderComponent,\n    UiTableBodyComponent,\n    UiTableCellComponent,\n    UiTableComponent,\n    UiTableFooterComponent,\n    UiTableHeadComponent,\n    UiTableHeaderComponent,\n    UiTableRowComponent,\n    UiTabsComponent,\n    UiTabsListComponent,\n    UiTabsTriggerComponent,\n  ],\n  template: `\n    <div data-slot=\"loan-calculator\" [class]=\"rootClass\">\n      <!-- Header -->\n      <div class=\"flex flex-col items-center gap-4 text-center\">\n        <span ui-badge variant=\"outline\" class=\"gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase\">\n          <lucide-icon [img]=\"Calculator\" class=\"text-primary size-3.5\" />\n          Loan Amortization Engine\n        </span>\n        <div class=\"space-y-2\">\n          <h2 class=\"text-foreground text-2xl font-bold tracking-tight sm:text-3xl lg:text-4xl\">\n            Loan &amp; Mortgage Amortization Calculator\n          </h2>\n          <p class=\"text-muted-foreground mx-auto max-w-2xl text-sm sm:text-base\">\n            Estimate monthly payments, interest vs principal breakdown, and payoff timelines with extra payments.\n          </p>\n        </div>\n\n        <div class=\"mt-2 flex w-full justify-center\">\n          <ui-tabs [value]=\"selectedLoanType()\" (valueChange)=\"handleTabChange($event)\" class=\"w-full sm:w-auto\">\n            <ui-tabs-list variant=\"segmented\" class=\"grid w-full grid-cols-2 sm:flex sm:w-auto\">\n              <button ui-tabs-trigger value=\"mortgage-30\" class=\"gap-1.5\">\n                <lucide-icon [img]=\"Home\" class=\"size-3.5\" />\n                Mortgage 30Y\n              </button>\n              <button ui-tabs-trigger value=\"commercial-10\" class=\"gap-1.5\">\n                <lucide-icon [img]=\"Building2\" class=\"size-3.5\" />\n                Commercial 10Y\n              </button>\n              <button ui-tabs-trigger value=\"auto-5\" class=\"gap-1.5\">\n                <lucide-icon [img]=\"Car\" class=\"size-3.5\" />\n                Auto Loan 5Y\n              </button>\n              <button ui-tabs-trigger value=\"personal-3\" class=\"gap-1.5\">\n                <lucide-icon [img]=\"Wallet\" class=\"size-3.5\" />\n                Personal Loan\n              </button>\n            </ui-tabs-list>\n          </ui-tabs>\n        </div>\n      </div>\n\n      <!-- 2-Column Calculator Layout -->\n      <div class=\"grid grid-cols-1 gap-8 lg:grid-cols-12 lg:items-start\">\n        <div class=\"space-y-6 lg:col-span-7\">\n          <div ui-card>\n            <div ui-card-header>\n              <h3 ui-card-title class=\"flex items-center gap-2\">\n                <lucide-icon [img]=\"DollarSign\" class=\"text-primary size-5\" />\n                Loan Parameters\n              </h3>\n              <p ui-card-description>\n                Customize loan principal, interest rate, term duration, and extra monthly contributions.\n              </p>\n            </div>\n            <div ui-card-content class=\"space-y-8\">\n              <div class=\"space-y-4\">\n                <div class=\"flex items-center justify-between gap-4\">\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Loan Amount (Principal)</span>\n                    <p class=\"text-muted-foreground text-xs\">Total borrowed loan amount.</p>\n                  </div>\n                  <div class=\"w-36 sm:w-44\">\n                    <ui-input\n                      type=\"number\"\n                      [value]=\"loanAmount()\"\n                      (valueChange)=\"loanAmount.set(Number($event) || 0)\"\n                      placeholder=\"$450,000\"\n                      class=\"text-xs\"\n                    />\n                  </div>\n                </div>\n                <div class=\"pb-6\">\n                  <ui-slider\n                    [value]=\"[loanAmount()]\"\n                    [min]=\"10000\"\n                    [max]=\"2000000\"\n                    [step]=\"5000\"\n                    [marks]=\"amountMarks\"\n                    (valueChange)=\"loanAmount.set($event[0] ?? loanAmount())\"\n                  />\n                </div>\n                <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span class=\"text-muted-foreground text-xs\">Quick select:</span>\n                  @for (amt of [150000, 300000, 450000, 750000, 1200000]; track amt) {\n                    <button\n                      ui-button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-6 px-2 text-xs\"\n                      (click)=\"loanAmount.set(amt)\"\n                    >\n                      {{ amt >= 1000000 ? (amt / 1000000).toFixed(1) + 'M' : amt / 1000 + 'K' }}\n                    </button>\n                  }\n                </div>\n              </div>\n\n              <div ui-separator></div>\n\n              <div class=\"space-y-4\">\n                <div class=\"flex items-center justify-between gap-4\">\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Annual Interest Rate (APR)</span>\n                    <p class=\"text-muted-foreground text-xs\">Fixed annual percentage rate.</p>\n                  </div>\n                  <div class=\"w-32 sm:w-36\">\n                    <ui-input\n                      type=\"number\"\n                      [value]=\"interestRate()\"\n                      (valueChange)=\"interestRate.set(Number($event) || 0)\"\n                      placeholder=\"6.5%\"\n                      class=\"text-xs\"\n                    />\n                  </div>\n                </div>\n                <div class=\"pb-6\">\n                  <ui-slider\n                    [value]=\"[interestRate()]\"\n                    [min]=\"1\"\n                    [max]=\"15\"\n                    [step]=\"0.1\"\n                    [marks]=\"rateMarks\"\n                    (valueChange)=\"interestRate.set($event[0] ?? interestRate())\"\n                  />\n                </div>\n                <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span class=\"text-muted-foreground text-xs\">Market benchmarks:</span>\n                  @for (rate of [4.5, 5.5, 6.5, 7.5, 9.5]; track rate) {\n                    <button\n                      ui-button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-6 px-2 text-xs\"\n                      (click)=\"interestRate.set(rate)\"\n                    >\n                      {{ rate }}%\n                    </button>\n                  }\n                </div>\n              </div>\n\n              <div ui-separator></div>\n\n              <div class=\"space-y-4\">\n                <div class=\"flex items-center justify-between gap-4\">\n                  <div>\n                    <span class=\"text-foreground text-sm font-medium\">Loan Term</span>\n                    <p class=\"text-muted-foreground text-xs\">Amortization period in years.</p>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <span ui-badge variant=\"secondary\" class=\"font-mono text-xs font-semibold\">\n                      {{ loanTermYears() }} Years ({{ loanTermYears() * 12 }} mo)\n                    </span>\n                  </div>\n                </div>\n                <div class=\"pb-6\">\n                  <ui-slider\n                    [value]=\"[loanTermYears()]\"\n                    [min]=\"1\"\n                    [max]=\"30\"\n                    [step]=\"1\"\n                    [marks]=\"termMarks\"\n                    (valueChange)=\"loanTermYears.set($event[0] ?? loanTermYears())\"\n                  />\n                </div>\n                <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span class=\"text-muted-foreground text-xs\">Standard terms:</span>\n                  @for (term of termPresets; track term) {\n                    <button\n                      ui-button\n                      [variant]=\"loanTermYears() === term ? 'default' : 'outline'\"\n                      size=\"sm\"\n                      class=\"h-6 px-2 text-xs\"\n                      (click)=\"loanTermYears.set(term)\"\n                    >\n                      {{ term }} Yrs\n                    </button>\n                  }\n                </div>\n              </div>\n\n              <div ui-separator></div>\n\n              <div class=\"space-y-4\">\n                <div class=\"flex flex-wrap items-center justify-between gap-4\">\n                  <div>\n                    <div class=\"flex items-center gap-2\">\n                      <span class=\"text-foreground text-sm font-medium\">Extra Monthly Payment</span>\n                      <span\n                        ui-badge\n                        variant=\"outline\"\n                        class=\"border-success/20 bg-success/10 text-success text-xs font-normal\"\n                      >\n                        Accelerates Payoff\n                      </span>\n                    </div>\n                    <p class=\"text-muted-foreground text-xs\">Directly reduces principal balance each month.</p>\n                  </div>\n                  <div class=\"w-36 sm:w-44\">\n                    <ui-input\n                      type=\"number\"\n                      [value]=\"extraMonthlyPayment()\"\n                      (valueChange)=\"extraMonthlyPayment.set(Number($event) || 0)\"\n                      placeholder=\"$200/mo\"\n                      class=\"text-xs\"\n                    />\n                  </div>\n                </div>\n                <div class=\"pb-6\">\n                  <ui-slider\n                    [value]=\"[extraMonthlyPayment()]\"\n                    [min]=\"0\"\n                    [max]=\"2000\"\n                    [step]=\"25\"\n                    [marks]=\"extraMarks\"\n                    (valueChange)=\"extraMonthlyPayment.set($event[0] ?? extraMonthlyPayment())\"\n                  />\n                </div>\n                <div class=\"flex flex-wrap items-center gap-1.5 pt-1\">\n                  <span class=\"text-muted-foreground text-xs\">Quick extra:</span>\n                  @for (extra of [0, 100, 200, 500, 1000]; track extra) {\n                    <button\n                      ui-button\n                      variant=\"outline\"\n                      size=\"sm\"\n                      class=\"h-6 px-2 text-xs\"\n                      (click)=\"extraMonthlyPayment.set(extra)\"\n                    >\n                      +&#36;{{ extra }}/mo\n                    </button>\n                  }\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <!-- Right Results Card -->\n        <div class=\"lg:sticky lg:top-8 lg:col-span-5\">\n          <div ui-card class=\"border-border bg-card shadow-xs\">\n            <div ui-card-header class=\"pb-4\">\n              <div class=\"flex items-center justify-between\">\n                <span class=\"text-muted-foreground text-xs font-semibold tracking-wider uppercase\">\n                  Estimated Monthly Payment\n                </span>\n                <span\n                  ui-badge\n                  variant=\"secondary\"\n                  class=\"bg-primary/10 text-primary border-primary/20 text-xs font-semibold\"\n                >\n                  Amortized\n                </span>\n              </div>\n              <div class=\"mt-4\">\n                <div class=\"flex items-baseline gap-1.5\">\n                  <span class=\"text-foreground text-4xl font-bold tracking-tight tabular-nums sm:text-5xl\">\n                    {{ fmt(baseMonthlyPayment()) }}\n                  </span>\n                  <span class=\"text-muted-foreground text-sm font-normal\"> / month</span>\n                </div>\n                @if (extraMonthlyPayment() > 0) {\n                  <p class=\"text-muted-foreground mt-2 text-xs\">\n                    Total monthly cash outlay:\n                    <span class=\"text-foreground font-semibold tabular-nums\">\n                      {{ fmt(baseMonthlyPayment() + extraMonthlyPayment()) }}/mo\n                    </span>\n                    ({{ fmt(baseMonthlyPayment()) }} base + {{ fmt(extraMonthlyPayment()) }} extra)\n                  </p>\n                } @else {\n                  <p class=\"text-muted-foreground mt-2 text-xs\">\n                    Standard fixed payment based on {{ interestRate() }}% APR over {{ loanTermYears() }} years.\n                  </p>\n                }\n              </div>\n            </div>\n\n            <div ui-card-content class=\"space-y-5\">\n              <div ui-separator></div>\n\n              @if (extraMonthlyPayment() > 0 && amortization().interestSavings > 0) {\n                <div class=\"border-success/20 bg-success/10 text-success rounded-lg border p-3.5 text-xs\">\n                  <div class=\"flex items-start gap-2.5\">\n                    <lucide-icon [img]=\"PiggyBank\" class=\"mt-0.5 size-4 shrink-0\" />\n                    <div class=\"space-y-1\">\n                      <p class=\"text-sm leading-snug font-semibold\">\n                        Saves {{ fmtWhole(amortization().interestSavings) }} and cuts\n                        {{ amortization().yearsSaved }} years off payoff!\n                      </p>\n                      <p class=\"text-success/80 leading-normal\">\n                        You'll be debt-free in {{ amortization().actualYears }} years ({{\n                          amortization().actualMonths\n                        }}\n                        payments) instead of {{ loanTermYears() }} years.\n                      </p>\n                    </div>\n                  </div>\n                </div>\n              }\n\n              <div class=\"space-y-2.5\">\n                <div class=\"flex items-center justify-between text-sm\">\n                  <span class=\"text-muted-foreground\">Total Loan Principal</span>\n                  <span class=\"font-medium tabular-nums\">{{ fmt(loanAmount()) }}</span>\n                </div>\n                <div class=\"flex items-center justify-between text-sm\">\n                  <span class=\"text-muted-foreground\">Total Interest Paid</span>\n                  <span class=\"text-warning font-medium tabular-nums\">{{ fmt(amortization().totalInterestPaid) }}</span>\n                </div>\n                <div class=\"flex items-center justify-between text-sm\">\n                  <span class=\"text-muted-foreground\">Total Loan Cost</span>\n                  <span class=\"text-foreground font-semibold tabular-nums\">{{\n                    fmt(amortization().totalLoanCost)\n                  }}</span>\n                </div>\n                <div class=\"flex items-center justify-between text-sm\">\n                  <span class=\"text-muted-foreground\">Effective Payoff Timeline</span>\n                  <span class=\"font-medium tabular-nums\">\n                    {{ amortization().actualYears }} Years ({{ amortization().actualMonths }} mo)\n                  </span>\n                </div>\n              </div>\n\n              <div ui-separator></div>\n\n              <div class=\"space-y-2.5\">\n                <div class=\"flex items-center justify-between text-xs font-medium\">\n                  <span class=\"text-foreground\">Principal vs. Total Interest</span>\n                  <span class=\"text-muted-foreground tabular-nums\"\n                    >{{ principalPercent() }}% / {{ interestPercent() }}%</span\n                  >\n                </div>\n                <div class=\"bg-muted relative flex h-3 w-full overflow-hidden rounded-full shadow-inner\">\n                  <div\n                    class=\"bg-primary h-full transition-[width] duration-300\"\n                    [style.width.%]=\"principalPercent()\"\n                  ></div>\n                  <div\n                    class=\"bg-warning h-full transition-[width] duration-300\"\n                    [style.width.%]=\"interestPercent()\"\n                  ></div>\n                </div>\n                <div\n                  class=\"text-muted-foreground flex flex-col gap-1.5 text-xs sm:flex-row sm:items-center sm:justify-between\"\n                >\n                  <div class=\"flex items-center gap-1.5\">\n                    <span class=\"bg-primary size-2 shrink-0 rounded-full\"></span>\n                    <span>Principal: {{ fmtWhole(loanAmount()) }} ({{ principalPercent() }}%)</span>\n                  </div>\n                  <div class=\"flex items-center gap-1.5\">\n                    <span class=\"bg-warning size-2 shrink-0 rounded-full\"></span>\n                    <span>Interest: {{ fmtWhole(amortization().totalInterestPaid) }} ({{ interestPercent() }}%)</span>\n                  </div>\n                </div>\n              </div>\n\n              <div ui-separator></div>\n\n              <ul class=\"text-muted-foreground space-y-2 text-xs\">\n                <li class=\"flex items-center gap-2\">\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"text-primary size-3.5 shrink-0\" />\n                  <span>Real-time compound interest calculations</span>\n                </li>\n                <li class=\"flex items-center gap-2\">\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"text-primary size-3.5 shrink-0\" />\n                  <span>Principal reduction extra payment model</span>\n                </li>\n                <li class=\"flex items-center gap-2\">\n                  <lucide-icon [img]=\"CheckCircle2\" class=\"text-primary size-3.5 shrink-0\" />\n                  <span>Standard fixed-rate banking amortization schedule</span>\n                </li>\n              </ul>\n            </div>\n\n            <div ui-card-footer class=\"flex flex-col gap-2.5 pt-2\">\n              <button ui-button class=\"w-full gap-2 font-semibold shadow-xs\" size=\"lg\">\n                Apply for Pre-Approval\n                <lucide-icon [img]=\"ArrowRight\" class=\"size-4\" />\n              </button>\n              <button ui-button aria-label=\"Download attachment\" variant=\"outline\" class=\"w-full gap-2\" size=\"default\">\n                <lucide-icon [img]=\"FileSpreadsheet\" class=\"size-4\" />\n                Download Schedule (CSV)\n              </button>\n            </div>\n          </div>\n        </div>\n      </div>\n\n      <!-- Amortization Schedule Table -->\n      <div ui-card class=\"mt-8\">\n        <div ui-card-header class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n          <div>\n            <h3 ui-card-title class=\"flex items-center gap-2\">\n              <lucide-icon [img]=\"Calendar\" class=\"text-primary size-5\" />\n              Amortization Schedule\n            </h3>\n            <p ui-card-description>\n              Annual breakdown of principal reduction, interest paid, extra contributions, and ending balance.\n            </p>\n          </div>\n          <div class=\"flex items-center gap-2\">\n            <button ui-button variant=\"outline\" size=\"sm\" class=\"text-xs\" (click)=\"showAllYears.set(!showAllYears())\">\n              {{\n                showAllYears() ? 'Show First 5 Years' : 'Show All ' + amortization().yearlySchedule.length + ' Years'\n              }}\n            </button>\n          </div>\n        </div>\n        <div ui-card-content>\n          <div class=\"overflow-x-auto\">\n            <table ui-table density=\"cozy\">\n              <thead ui-table-header>\n                <tr ui-table-row>\n                  <th ui-table-head>Year</th>\n                  <th ui-table-head class=\"text-right\">Principal Paid</th>\n                  <th ui-table-head class=\"text-right\">Interest Paid</th>\n                  <th ui-table-head class=\"text-right\">Extra Principal</th>\n                  <th ui-table-head class=\"text-right\">Total Payment</th>\n                  <th ui-table-head class=\"text-right\">Ending Balance</th>\n                </tr>\n              </thead>\n              <tbody ui-table-body>\n                @for (row of displayedSchedule(); track row.year) {\n                  <tr ui-table-row>\n                    <td ui-table-cell class=\"font-medium\">\n                      <div class=\"flex items-center gap-2\">\n                        <span>Year {{ row.year }}</span>\n                        @if (row.year === 1) {\n                          <span ui-badge variant=\"outline\" class=\"text-xs font-normal\">Initial</span>\n                        }\n                      </div>\n                    </td>\n                    <td ui-table-cell class=\"text-foreground text-right font-medium tabular-nums\">\n                      {{ fmt(row.principalPaid) }}\n                    </td>\n                    <td ui-table-cell class=\"text-warning text-right tabular-nums\">{{ fmt(row.interestPaid) }}</td>\n                    <td ui-table-cell class=\"text-success text-right tabular-nums\">\n                      {{ row.extraPaid > 0 ? '+' + fmt(row.extraPaid) : '—' }}\n                    </td>\n                    <td ui-table-cell class=\"text-right font-semibold tabular-nums\">{{ fmt(row.totalPaid) }}</td>\n                    <td ui-table-cell class=\"text-right font-semibold tabular-nums\">\n                      {{ fmt(row.remainingBalance) }}\n                    </td>\n                  </tr>\n                }\n              </tbody>\n              <tfoot ui-table-footer>\n                <tr ui-table-row>\n                  <td ui-table-cell class=\"font-bold\">\n                    Total ({{ displayedSchedule().length }} {{ displayedSchedule().length === 1 ? 'Year' : 'Years' }})\n                  </td>\n                  <td ui-table-cell class=\"text-foreground text-right font-bold tabular-nums\">\n                    {{ fmt(displayedTotals().principal) }}\n                  </td>\n                  <td ui-table-cell class=\"text-warning text-right font-bold tabular-nums\">\n                    {{ fmt(displayedTotals().interest) }}\n                  </td>\n                  <td ui-table-cell class=\"text-success text-right font-bold tabular-nums\">\n                    {{ fmt(displayedTotals().extra) }}\n                  </td>\n                  <td ui-table-cell class=\"text-right font-bold tabular-nums\">{{ fmt(displayedTotals().total) }}</td>\n                  <td ui-table-cell class=\"text-right font-bold tabular-nums\">\n                    {{ fmt(displayedSchedule()[displayedSchedule().length - 1]?.remainingBalance ?? 0) }}\n                  </td>\n                </tr>\n              </tfoot>\n            </table>\n          </div>\n        </div>\n      </div>\n    </div>\n  `,\n})\nexport class UiLoanCalculatorComponent {\n  protected readonly ArrowRight = ArrowRight\n  protected readonly Building2 = Building2\n  protected readonly Calculator = Calculator\n  protected readonly Calendar = Calendar\n  protected readonly Car = Car\n  protected readonly CheckCircle2 = CheckCircle2\n  protected readonly DollarSign = DollarSign\n  protected readonly FileSpreadsheet = FileSpreadsheet\n  protected readonly Home = Home\n  protected readonly PiggyBank = PiggyBank\n  protected readonly Wallet = Wallet\n\n  protected readonly amountMarks = LOAN_AMOUNT_MARKS\n  protected readonly rateMarks = INTEREST_RATE_MARKS\n  protected readonly termMarks = LOAN_TERM_MARKS\n  protected readonly extraMarks = EXTRA_PAYMENT_MARKS\n  protected readonly termPresets = TERM_PRESETS\n\n  @Input('class') className?: string\n\n  readonly selectedLoanType = signal<LoanType>('mortgage-30')\n  readonly loanAmount = signal(450_000)\n  readonly interestRate = signal(6.5)\n  readonly loanTermYears = signal(30)\n  readonly extraMonthlyPayment = signal(200)\n  readonly showAllYears = signal(false)\n\n  readonly baseMonthlyPayment = computed(() =>\n    baseMonthlyPaymentFor(this.loanAmount(), this.interestRate(), this.loanTermYears()),\n  )\n  readonly totalCostBase = computed(() => this.baseMonthlyPayment() * this.loanTermYears() * 12)\n  readonly totalInterestBase = computed(() => Math.max(0, this.totalCostBase() - this.loanAmount()))\n\n  readonly amortization = computed(() =>\n    amortize(\n      this.loanAmount(),\n      this.interestRate(),\n      this.loanTermYears(),\n      this.extraMonthlyPayment(),\n      this.baseMonthlyPayment(),\n      this.totalInterestBase(),\n    ),\n  )\n\n  readonly principalPercent = computed(() => {\n    const total = this.amortization().totalLoanCost\n    if (total <= 0) return 100\n    return Math.min(100, Math.max(0, Math.round((this.loanAmount() / total) * 100)))\n  })\n  readonly interestPercent = computed(() => Math.max(0, 100 - this.principalPercent()))\n\n  readonly displayedSchedule = computed(() =>\n    this.showAllYears() ? this.amortization().yearlySchedule : this.amortization().yearlySchedule.slice(0, 5),\n  )\n  readonly displayedTotals = computed(() => {\n    const list = this.displayedSchedule()\n    return {\n      principal: list.reduce((s, r) => s + r.principalPaid, 0),\n      interest: list.reduce((s, r) => s + r.interestPaid, 0),\n      extra: list.reduce((s, r) => s + r.extraPaid, 0),\n      total: list.reduce((s, r) => s + r.totalPaid, 0),\n    }\n  })\n\n  get rootClass(): string {\n    return cn('mx-auto w-full max-w-6xl space-y-10 p-4 sm:p-6 lg:p-8', this.className)\n  }\n\n  fmt(v: number): string {\n    return fmtCurrency(v)\n  }\n\n  fmtWhole(v: number): string {\n    return fmtCurrencyWhole(v)\n  }\n\n  handleTabChange(value: string): void {\n    const type = value as LoanType\n    this.selectedLoanType.set(type)\n    const preset = LOAN_PRESETS[type]\n    if (preset) {\n      this.loanAmount.set(preset.amount)\n      this.interestRate.set(preset.rate)\n      this.loanTermYears.set(preset.termYears)\n      this.extraMonthlyPayment.set(preset.extraPayment)\n    }\n  }\n}\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/loan-calculator/loan-calculator.component.ts"
    },
    {
      "path": "packages/registry-angular/blocks/loan-calculator/index.ts",
      "content": "export {\n  UiLoanCalculatorComponent,\n  LOAN_PRESETS,\n  LOAN_AMOUNT_MARKS,\n  INTEREST_RATE_MARKS,\n  LOAN_TERM_MARKS,\n  EXTRA_PAYMENT_MARKS,\n  TERM_PRESETS,\n  fmtCurrency,\n  fmtCurrencyWhole,\n  baseMonthlyPaymentFor,\n  amortize,\n  type LoanType,\n  type LoanPreset,\n  type MonthRecord,\n  type YearRecord,\n  type AmortizationResult,\n} from './loan-calculator.component'\n",
      "type": "registry:block",
      "target": "~/src/app/components/blocks/loan-calculator/index.ts"
    }
  ],
  "dependencies": [
    "lucide-angular"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/angular/badge.json",
    "https://uipkge.dev/r/angular/button.json",
    "https://uipkge.dev/r/angular/card.json",
    "https://uipkge.dev/r/angular/input.json",
    "https://uipkge.dev/r/angular/separator.json",
    "https://uipkge.dev/r/angular/slider.json",
    "https://uipkge.dev/r/angular/table.json",
    "https://uipkge.dev/r/angular/tabs.json"
  ],
  "description": "Interactive mortgage and commercial loan amortization schedule calculator with dynamic sliders, loan type presets, extra payment impact simulation, principal vs interest visual ratio bar, and year-by-year amortization breakdown table.",
  "categories": [
    "finance",
    "billing"
  ]
}