{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "loan-calculator",
  "title": "Loan Calculator",
  "type": "registry:block",
  "files": [
    {
      "path": "packages/registry-vue/blocks/loan-calculator/LoanCalculator.vue",
      "content": "<script setup lang=\"ts\">\nimport { computed, ref, type HTMLAttributes } from 'vue'\nimport {\n  ArrowRight,\n  Building2,\n  Calculator,\n  Calendar,\n  Car,\n  CheckCircle2,\n  DollarSign,\n  FileSpreadsheet,\n  Home,\n  PiggyBank,\n  Wallet,\n} from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Badge } from '@/components/ui/badge'\nimport { Button } from '@/components/ui/button'\nimport { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'\nimport { Input } from '@/components/ui/input'\nimport { Separator } from '@/components/ui/separator'\nimport { Slider } from '@/components/ui/slider'\nimport { Table, TableBody, TableCell, TableFooter, TableHead, TableHeader, TableRow } from '@/components/ui/table'\nimport { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'\n\ninterface Props {\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\ntype LoanType = 'mortgage-30' | 'commercial-10' | 'auto-5' | 'personal-3'\n\ninterface LoanPreset {\n  id: LoanType\n  name: string\n  amount: number\n  rate: number\n  termYears: number\n  extraPayment: number\n  description: string\n}\n\nconst 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\nconst LOAN_AMOUNT_MARKS = {\n  100000: '$100K',\n  500000: '$500K',\n  1000000: '$1M',\n  2000000: '$2M',\n}\n\nconst INTEREST_RATE_MARKS = {\n  1: '1%',\n  5: '5%',\n  10: '10%',\n  15: '15%',\n}\n\nconst LOAN_TERM_MARKS = {\n  5: '5Y',\n  10: '10Y',\n  15: '15Y',\n  20: '20Y',\n  30: '30Y',\n}\n\nconst EXTRA_PAYMENT_MARKS = {\n  0: '$0',\n  500: '$500',\n  1000: '$1K',\n  2000: '$2K',\n}\n\nconst TERM_PRESETS = [5, 10, 15, 20, 30]\n\nconst selectedLoanType = ref<LoanType>('mortgage-30')\nconst loanAmount = ref(450_000)\nconst interestRate = ref(6.5)\nconst loanTermYears = ref(30)\nconst extraMonthlyPayment = ref(200)\nconst showAllYears = ref(false)\n\nfunction handleTabChange(value: string | number) {\n  const type = String(value) as LoanType\n  selectedLoanType.value = type\n  const preset = LOAN_PRESETS[type]\n  if (preset) {\n    loanAmount.value = preset.amount\n    interestRate.value = preset.rate\n    loanTermYears.value = preset.termYears\n    extraMonthlyPayment.value = preset.extraPayment\n  }\n}\n\n// Base monthly payment (standard amortization without extra payments)\nconst baseMonthlyPayment = computed(() => {\n  const principal = loanAmount.value\n  const annualRate = interestRate.value\n  const termYears = loanTermYears.value\n  const totalMonths = termYears * 12\n\n  if (principal <= 0 || totalMonths <= 0) return 0\n  if (annualRate <= 0) return principal / totalMonths\n\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\n// Total standard cost and interest without extra payment\nconst totalCostBase = computed(() => {\n  return baseMonthlyPayment.value * loanTermYears.value * 12\n})\n\nconst totalInterestBase = computed(() => {\n  return Math.max(0, totalCostBase.value - loanAmount.value)\n})\n\ninterface MonthRecord {\n  month: number\n  principalPaid: number\n  interestPaid: number\n  extraPaid: number\n  totalPaid: number\n  remainingBalance: number\n}\n\ninterface YearRecord {\n  year: number\n  principalPaid: number\n  interestPaid: number\n  extraPaid: number\n  totalPaid: number\n  remainingBalance: number\n}\n\nconst amortizationResult = computed(() => {\n  const principal = Math.max(0, loanAmount.value)\n  const annualRate = Math.max(0, interestRate.value)\n  const termYears = Math.max(1, loanTermYears.value)\n  const extra = Math.max(0, extraMonthlyPayment.value)\n  const basePayment = baseMonthlyPayment.value\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 // 100 years safeguard\n\n  while (balance > 0.005 && month < maxMonths) {\n    month++\n    const interestForMonth = annualRate > 0 ? balance * monthlyRate : 0\n    let scheduledTotal = basePayment + extra\n\n    if (balance + interestForMonth < scheduledTotal) {\n      scheduledTotal = balance + interestForMonth\n    }\n\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\n    totalInterest += interestForMonth\n    totalPaid += scheduledTotal\n    totalExtra += extraForMonth\n\n    monthlySchedule.push({\n      month,\n      principalPaid: principalForMonth,\n      interestPaid: interestForMonth,\n      extraPaid: extraForMonth,\n      totalPaid: scheduledTotal,\n      remainingBalance: balance,\n    })\n  }\n\n  // Aggregate by year\n  const yearlySchedule: YearRecord[] = []\n  const totalYearsActual = Math.ceil(monthlySchedule.length / 12)\n\n  for (let y = 1; y <= totalYearsActual; y++) {\n    const startIdx = (y - 1) * 12\n    const endIdx = Math.min(y * 12, monthlySchedule.length)\n    const yearMonths = monthlySchedule.slice(startIdx, endIdx)\n\n    if (yearMonths.length === 0) continue\n\n    const yearPrincipal = yearMonths.reduce((sum, m) => sum + m.principalPaid, 0)\n    const yearInterest = yearMonths.reduce((sum, m) => sum + m.interestPaid, 0)\n    const yearExtra = yearMonths.reduce((sum, m) => sum + m.extraPaid, 0)\n    const yearTotal = yearMonths.reduce((sum, m) => sum + m.totalPaid, 0)\n    const endingBalance = yearMonths[yearMonths.length - 1]?.remainingBalance ?? 0\n\n    yearlySchedule.push({\n      year: y,\n      principalPaid: yearPrincipal,\n      interestPaid: yearInterest,\n      extraPaid: yearExtra,\n      totalPaid: yearTotal,\n      remainingBalance: endingBalance,\n    })\n  }\n\n  const monthsSaved = Math.max(0, standardMonths - monthlySchedule.length)\n  const yearsSaved = monthsSaved / 12\n  const interestSaved = Math.max(0, totalInterestBase.value - totalInterest)\n\n  return {\n    monthlySchedule,\n    yearlySchedule,\n    actualMonths: monthlySchedule.length,\n    actualYears: (monthlySchedule.length / 12).toFixed(1),\n    monthsSaved,\n    yearsSaved: yearsSaved.toFixed(1),\n    totalInterestPaid: totalInterest,\n    totalLoanCost: totalPaid,\n    totalExtraPaid: totalExtra,\n    interestSavings: interestSaved,\n  }\n})\n\n// Visual breakdown percentages\nconst principalPercent = computed(() => {\n  const total = amortizationResult.value.totalLoanCost\n  if (total <= 0) return 100\n  return Math.min(100, Math.max(0, Math.round((loanAmount.value / total) * 100)))\n})\n\nconst interestPercent = computed(() => {\n  return Math.max(0, 100 - principalPercent.value)\n})\n\n// Filtered schedule for display\nconst displayedSchedule = computed(() => {\n  if (showAllYears.value) {\n    return amortizationResult.value.yearlySchedule\n  }\n  return amortizationResult.value.yearlySchedule.slice(0, 5)\n})\n\n// Totals for the displayed schedule\nconst displayedTotals = computed(() => {\n  const list = displayedSchedule.value\n  return {\n    principal: list.reduce((sum, r) => sum + r.principalPaid, 0),\n    interest: list.reduce((sum, r) => sum + r.interestPaid, 0),\n    extra: list.reduce((sum, r) => sum + r.extraPaid, 0),\n    total: list.reduce((sum, r) => sum + r.totalPaid, 0),\n  }\n})\n\n// Formatters\nconst fmtCurrency = (val: number) => {\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\nconst fmtCurrencyWhole = (val: number) => {\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</script>\n\n<template>\n  <div data-slot=\"loan-calculator\" :class=\"cn('mx-auto w-full max-w-6xl space-y-10 p-4 sm:p-6 lg:p-8', props.class)\">\n    <!-- Header -->\n    <div class=\"flex flex-col items-center gap-4 text-center\">\n      <Badge variant=\"outline\" class=\"gap-1.5 px-3 py-1 text-xs font-medium tracking-wider uppercase\">\n        <Calculator class=\"text-primary size-3.5\" />\n        Loan Amortization Engine\n      </Badge>\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      <!-- Loan Type Tabs -->\n      <div class=\"mt-2 flex w-full justify-center\">\n        <Tabs :model-value=\"selectedLoanType\" @update:model-value=\"handleTabChange\" class=\"w-full sm:w-auto\">\n          <TabsList variant=\"segmented\" class=\"grid w-full grid-cols-2 sm:flex sm:w-auto\">\n            <TabsTrigger value=\"mortgage-30\" class=\"gap-1.5\">\n              <Home class=\"size-3.5\" />\n              Mortgage 30Y\n            </TabsTrigger>\n            <TabsTrigger value=\"commercial-10\" class=\"gap-1.5\">\n              <Building2 class=\"size-3.5\" />\n              Commercial 10Y\n            </TabsTrigger>\n            <TabsTrigger value=\"auto-5\" class=\"gap-1.5\">\n              <Car class=\"size-3.5\" />\n              Auto Loan 5Y\n            </TabsTrigger>\n            <TabsTrigger value=\"personal-3\" class=\"gap-1.5\">\n              <Wallet class=\"size-3.5\" />\n              Personal Loan\n            </TabsTrigger>\n          </TabsList>\n        </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      <!-- Left Inputs Card -->\n      <div class=\"space-y-6 lg:col-span-7\">\n        <Card>\n          <CardHeader>\n            <CardTitle class=\"flex items-center gap-2\">\n              <DollarSign class=\"text-primary size-5\" />\n              Loan Parameters\n            </CardTitle>\n            <CardDescription>\n              Customize loan principal, interest rate, term duration, and extra monthly contributions.\n            </CardDescription>\n          </CardHeader>\n          <CardContent class=\"space-y-8\">\n            <!-- 1. Loan Amount -->\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                  <Input\n                    id=\"loan-amount-input\"\n                    type=\"number\"\n                    size=\"small\"\n                    :min=\"5000\"\n                    :max=\"2500000\"\n                    :step=\"5000\"\n                    prefix=\"$\"\n                    :model-value=\"loanAmount\"\n                    @update:model-value=\"(val) => (loanAmount = Number(val) || 0)\"\n                  />\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"loanAmount\"\n                  :min=\"10000\"\n                  :max=\"2000000\"\n                  :step=\"5000\"\n                  :marks=\"LOAN_AMOUNT_MARKS\"\n                  :tooltip=\"(val) => `$${val.toLocaleString('en-US')}`\"\n                  @update:model-value=\"(val) => (loanAmount = typeof val === 'number' ? val : val[0])\"\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                <Button\n                  v-for=\"amt in [150000, 300000, 450000, 750000, 1200000]\"\n                  :key=\"amt\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-6 px-2 text-xs\"\n                  @click=\"loanAmount = amt\"\n                >\n                  ${{ amt >= 1000000 ? `${(amt / 1000000).toFixed(1)}M` : `${amt / 1000}K` }}\n                </Button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- 2. Interest Rate -->\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                  <Input\n                    id=\"interest-rate-input\"\n                    type=\"number\"\n                    size=\"small\"\n                    :min=\"0.5\"\n                    :max=\"25\"\n                    :step=\"0.1\"\n                    suffix=\"%\"\n                    :model-value=\"interestRate\"\n                    @update:model-value=\"(val) => (interestRate = Number(val) || 0)\"\n                  />\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"interestRate\"\n                  :min=\"1\"\n                  :max=\"15\"\n                  :step=\"0.1\"\n                  :marks=\"INTEREST_RATE_MARKS\"\n                  :tooltip=\"(val) => `${val.toFixed(1)}%`\"\n                  @update:model-value=\"(val) => (interestRate = typeof val === 'number' ? val : val[0])\"\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                <Button\n                  v-for=\"rate in [4.5, 5.5, 6.5, 7.5, 9.5]\"\n                  :key=\"rate\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-6 px-2 text-xs\"\n                  @click=\"interestRate = rate\"\n                >\n                  {{ rate }}%\n                </Button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- 3. Loan Term -->\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                  <Badge variant=\"secondary\" class=\"font-mono text-xs font-semibold\">\n                    {{ loanTermYears }} Years ({{ loanTermYears * 12 }} mo)\n                  </Badge>\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"loanTermYears\"\n                  :min=\"1\"\n                  :max=\"30\"\n                  :step=\"1\"\n                  :marks=\"LOAN_TERM_MARKS\"\n                  :tooltip=\"(val) => `${val} Years`\"\n                  @update:model-value=\"(val) => (loanTermYears = typeof val === 'number' ? val : val[0])\"\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                <Button\n                  v-for=\"term in TERM_PRESETS\"\n                  :key=\"term\"\n                  :variant=\"loanTermYears === term ? 'default' : 'outline'\"\n                  size=\"sm\"\n                  class=\"h-6 px-2 text-xs\"\n                  @click=\"loanTermYears = term\"\n                >\n                  {{ term }} Yrs\n                </Button>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- 4. Extra Monthly Payment -->\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                    <Badge variant=\"outline\" class=\"border-success/20 bg-success/10 text-success text-xs font-normal\">\n                      Accelerates Payoff\n                    </Badge>\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                  <Input\n                    id=\"extra-payment-input\"\n                    type=\"number\"\n                    size=\"small\"\n                    :min=\"0\"\n                    :max=\"10000\"\n                    :step=\"25\"\n                    prefix=\"$\"\n                    suffix=\"/ mo\"\n                    :model-value=\"extraMonthlyPayment\"\n                    @update:model-value=\"(val) => (extraMonthlyPayment = Number(val) || 0)\"\n                  />\n                </div>\n              </div>\n              <div class=\"pb-6\">\n                <Slider\n                  :model-value=\"extraMonthlyPayment\"\n                  :min=\"0\"\n                  :max=\"2000\"\n                  :step=\"25\"\n                  :marks=\"EXTRA_PAYMENT_MARKS\"\n                  :tooltip=\"(val) => `$${val}/mo`\"\n                  @update:model-value=\"(val) => (extraMonthlyPayment = typeof val === 'number' ? val : val[0])\"\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                <Button\n                  v-for=\"extra in [0, 100, 200, 500, 1000]\"\n                  :key=\"extra\"\n                  variant=\"outline\"\n                  size=\"sm\"\n                  class=\"h-6 px-2 text-xs\"\n                  @click=\"extraMonthlyPayment = extra\"\n                >\n                  +${{ extra }}/mo\n                </Button>\n              </div>\n            </div>\n          </CardContent>\n        </Card>\n      </div>\n\n      <!-- Right Results Card (Sticky on desktop) -->\n      <div class=\"lg:sticky lg:top-8 lg:col-span-5\">\n        <Card class=\"border-border bg-card shadow-xs\">\n          <CardHeader 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              <Badge variant=\"secondary\" class=\"bg-primary/10 text-primary border-primary/20 text-xs font-semibold\">\n                Amortized\n              </Badge>\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                  {{ fmtCurrency(baseMonthlyPayment) }}\n                </span>\n                <span class=\"text-muted-foreground text-sm font-normal\"> / month</span>\n              </div>\n              <p v-if=\"extraMonthlyPayment > 0\" class=\"text-muted-foreground mt-2 text-xs\">\n                Total monthly cash outlay:\n                <span class=\"text-foreground font-semibold tabular-nums\">\n                  {{ fmtCurrency(baseMonthlyPayment + extraMonthlyPayment) }}/mo\n                </span>\n                ({{ fmtCurrency(baseMonthlyPayment) }} base + {{ fmtCurrency(extraMonthlyPayment) }} extra)\n              </p>\n              <p v-else class=\"text-muted-foreground mt-2 text-xs\">\n                Standard fixed payment based on {{ interestRate }}% APR over {{ loanTermYears }} years.\n              </p>\n            </div>\n          </CardHeader>\n\n          <CardContent class=\"space-y-5\">\n            <Separator />\n\n            <!-- Extra Payment Impact Box -->\n            <div\n              v-if=\"extraMonthlyPayment > 0 && amortizationResult.interestSavings > 0\"\n              class=\"border-success/20 bg-success/10 text-success rounded-lg border p-3.5 text-xs\"\n            >\n              <div class=\"flex items-start gap-2.5\">\n                <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 {{ fmtCurrencyWhole(amortizationResult.interestSavings) }} and cuts\n                    {{ amortizationResult.yearsSaved }} years off payoff!\n                  </p>\n                  <p class=\"text-success/80 leading-normal\">\n                    You'll be debt-free in {{ amortizationResult.actualYears }} years ({{\n                      amortizationResult.actualMonths\n                    }}\n                    payments) instead of {{ loanTermYears }} years.\n                  </p>\n                </div>\n              </div>\n            </div>\n\n            <!-- Summary Breakdown -->\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\">{{ fmtCurrency(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 text-warning font-medium tabular-nums\">\n                  {{ fmtCurrency(amortizationResult.totalInterestPaid) }}\n                </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                  {{ fmtCurrency(amortizationResult.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                  {{ amortizationResult.actualYears }} Years ({{ amortizationResult.actualMonths }} mo)\n                </span>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- Visual Principal vs Interest Bar -->\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\">{{ principalPercent }}% / {{ interestPercent }}%</span>\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                />\n                <div\n                  class=\"bg-warning h-full transition-[width] duration-300\"\n                  :style=\"{ width: `${interestPercent}%` }\"\n                />\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\" />\n                  <span>Principal: {{ fmtCurrencyWhole(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\" />\n                  <span\n                    >Interest: {{ fmtCurrencyWhole(amortizationResult.totalInterestPaid) }} ({{\n                      interestPercent\n                    }}%)</span\n                  >\n                </div>\n              </div>\n            </div>\n\n            <Separator />\n\n            <!-- Key Features / Trust Checklist -->\n            <ul class=\"text-muted-foreground space-y-2 text-xs\">\n              <li class=\"flex items-center gap-2\">\n                <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                <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                <CheckCircle2 class=\"text-primary size-3.5 shrink-0\" />\n                <span>Standard fixed-rate banking amortization schedule</span>\n              </li>\n            </ul>\n          </CardContent>\n\n          <CardFooter class=\"flex flex-col gap-2.5 pt-2\">\n            <Button class=\"w-full gap-2 font-semibold shadow-xs\" size=\"lg\">\n              Apply for Pre-Approval\n              <ArrowRight class=\"size-4\" />\n            </Button>\n            <Button aria-label=\"Download attachment\" variant=\"outline\" class=\"w-full gap-2\" size=\"default\">\n              <FileSpreadsheet class=\"size-4\" />\n              Download Schedule (CSV)\n            </Button>\n          </CardFooter>\n        </Card>\n      </div>\n    </div>\n\n    <!-- Amortization Schedule Table -->\n    <Card class=\"mt-8\">\n      <CardHeader class=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n        <div>\n          <CardTitle class=\"flex items-center gap-2\">\n            <Calendar class=\"text-primary size-5\" />\n            Amortization Schedule\n          </CardTitle>\n          <CardDescription>\n            Annual breakdown of principal reduction, interest paid, extra contributions, and ending balance.\n          </CardDescription>\n        </div>\n        <div class=\"flex items-center gap-2\">\n          <Button variant=\"outline\" size=\"sm\" class=\"text-xs\" @click=\"showAllYears = !showAllYears\">\n            {{ showAllYears ? 'Show First 5 Years' : `Show All ${amortizationResult.yearlySchedule.length} Years` }}\n          </Button>\n        </div>\n      </CardHeader>\n      <CardContent>\n        <div class=\"overflow-x-auto\">\n          <Table density=\"cozy\">\n            <TableHeader>\n              <TableRow>\n                <TableHead>Year</TableHead>\n                <TableHead class=\"text-right\">Principal Paid</TableHead>\n                <TableHead class=\"text-right\">Interest Paid</TableHead>\n                <TableHead class=\"text-right\">Extra Principal</TableHead>\n                <TableHead class=\"text-right\">Total Payment</TableHead>\n                <TableHead class=\"text-right\">Ending Balance</TableHead>\n              </TableRow>\n            </TableHeader>\n            <TableBody>\n              <TableRow v-for=\"row in displayedSchedule\" :key=\"row.year\">\n                <TableCell class=\"font-medium\">\n                  <div class=\"flex items-center gap-2\">\n                    <span>Year {{ row.year }}</span>\n                    <Badge v-if=\"row.year === 1\" variant=\"outline\" class=\"text-xs font-normal\"> Initial </Badge>\n                  </div>\n                </TableCell>\n                <TableCell class=\"text-foreground text-right font-medium tabular-nums\">\n                  {{ fmtCurrency(row.principalPaid) }}\n                </TableCell>\n                <TableCell class=\"text-warning text-warning text-right tabular-nums\">\n                  {{ fmtCurrency(row.interestPaid) }}\n                </TableCell>\n                <TableCell class=\"text-success text-success text-right tabular-nums\">\n                  {{ row.extraPaid > 0 ? `+${fmtCurrency(row.extraPaid)}` : '—' }}\n                </TableCell>\n                <TableCell class=\"text-right font-semibold tabular-nums\">\n                  {{ fmtCurrency(row.totalPaid) }}\n                </TableCell>\n                <TableCell class=\"text-right font-semibold tabular-nums\">\n                  {{ fmtCurrency(row.remainingBalance) }}\n                </TableCell>\n              </TableRow>\n            </TableBody>\n            <TableFooter>\n              <TableRow>\n                <TableCell class=\"font-bold\">\n                  Total ({{ displayedSchedule.length }} {{ displayedSchedule.length === 1 ? 'Year' : 'Years' }})\n                </TableCell>\n                <TableCell class=\"text-foreground text-right font-bold tabular-nums\">\n                  {{ fmtCurrency(displayedTotals.principal) }}\n                </TableCell>\n                <TableCell class=\"text-warning text-warning text-right font-bold tabular-nums\">\n                  {{ fmtCurrency(displayedTotals.interest) }}\n                </TableCell>\n                <TableCell class=\"text-success text-success text-right font-bold tabular-nums\">\n                  {{ fmtCurrency(displayedTotals.extra) }}\n                </TableCell>\n                <TableCell class=\"text-right font-bold tabular-nums\">\n                  {{ fmtCurrency(displayedTotals.total) }}\n                </TableCell>\n                <TableCell class=\"text-right font-bold tabular-nums\">\n                  {{ fmtCurrency(displayedSchedule[displayedSchedule.length - 1]?.remainingBalance ?? 0) }}\n                </TableCell>\n              </TableRow>\n            </TableFooter>\n          </Table>\n        </div>\n      </CardContent>\n    </Card>\n  </div>\n</template>\n",
      "type": "registry:block",
      "target": "~/app/components/blocks/LoanCalculator.vue"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/badge.json",
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/card.json",
    "https://uipkge.dev/r/vue/input.json",
    "https://uipkge.dev/r/vue/separator.json",
    "https://uipkge.dev/r/vue/slider.json",
    "https://uipkge.dev/r/vue/table.json",
    "https://uipkge.dev/r/vue/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"
  ]
}