<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Tools on thenullpointer.net</title>
    <link>https://thenullpointer.net/tools/</link>
    <description>Recent content in Tools on thenullpointer.net</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <copyright>All Rights Reserved</copyright>
    <atom:link href="https://thenullpointer.net/tools/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Battery Tool Comparisons</title>
      <link>https://thenullpointer.net/tools/battery-tool-comparisons/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://thenullpointer.net/tools/battery-tool-comparisons/</guid>
      <description>&lt;div id=&#34;tool-comparison&#34; style=&#34;font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, sans-serif; max-width: 800px; margin: 0 auto;&#34;&gt;&#xA;&lt;style&gt;&#xA;  #tool-comparison { --green: #4CAF50; --yellow: #F5C518; --cyan: #00ACC1; --red: #E53935; --bg: #0f1117; --card: #1a1d27; --card2: #2a2f3e; --card3: #222639; color: #e0e0e0; background: var(--bg); padding: 20px 12px; border-radius: 12px; }&#xA;  #tool-comparison * { box-sizing: border-box; }&#xA;  #tool-comparison h2 { font-size: 20px; font-weight: 700; text-align: center; margin: 0 0 4px; color: #fff; }&#xA;  #tool-comparison .subtitle { text-align: center; font-size: 11px; color: #888; margin: 0 0 16px; }&#xA;  #tool-comparison .tabs { display: flex; gap: 3px; margin-bottom: 16px; background: var(--card); border-radius: 10px; padding: 3px; }&#xA;  #tool-comparison .tabs button { flex: 1; padding: 10px 0; border: none; border-radius: 8px; cursor: pointer; background: transparent; color: #666; font-weight: 400; font-size: 13px; font-family: inherit; transition: all 0.2s; }&#xA;  #tool-comparison .tabs button.active { background: var(--card2); color: #fff; font-weight: 700; }&#xA;  #tool-comparison .tool-card { background: var(--card); border-radius: 10px; padding: 12px; margin-bottom: 8px; }&#xA;  #tool-comparison .tool-card h3 { font-size: 13px; font-weight: 700; color: #fff; margin: 0 0 8px; }&#xA;  #tool-comparison .brand-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }&#xA;  #tool-comparison .brand-cell { background: var(--bg); border-radius: 8px; padding: 7px 9px; border-left: 3px solid; }&#xA;  #tool-comparison .brand-cell .brand-name { font-size: 11px; font-weight: 700; }&#xA;  #tool-comparison .brand-cell .model { font-size: 10px; color: #999; margin-top: 1px; }&#xA;  #tool-comparison .brand-cell .price { font-size: 13px; font-weight: 700; color: #fff; margin-top: 2px; }&#xA;  #tool-comparison .battery-card { border: 1px dashed #444; }&#xA;  #tool-comparison .battery-card .model { font-size: 9px; line-height: 1.3; }&#xA;  #tool-comparison .totals { background: var(--card3); border-radius: 10px; padding: 14px 12px; margin-top: 4px; }&#xA;  #tool-comparison .totals h3 { font-size: 13px; font-weight: 700; color: #fff; margin: 0 0 10px; }&#xA;  #tool-comparison .total-cell { background: var(--bg); border-radius: 8px; padding: 10px; border-left: 3px solid; }&#xA;  #tool-comparison .total-cell.lowest { outline: 2px solid; }&#xA;  #tool-comparison .total-cell .total-price { font-size: 18px; font-weight: 700; color: #fff; margin-top: 2px; }&#xA;  #tool-comparison .total-cell .coverage { font-size: 9px; color: var(--green); margin-top: 2px; }&#xA;  #tool-comparison .total-cell .lowest-tag { font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 1px; margin-bottom: 2px; }&#xA;  #tool-comparison .footnote { font-size: 10px; color: #555; margin-top: 8px; line-height: 1.5; }&#xA;  #tool-comparison .tab-content { display: none; }&#xA;  #tool-comparison .tab-content.active { display: block; }&#xA;   &#xA;  #tool-comparison .pc-card { background: var(--card); border-radius: 12px; padding: 14px; margin-bottom: 16px; border-left: 3px solid; }&#xA;  #tool-comparison .pc-card h3 { margin: 0 0 10px; font-size: 15px; font-weight: 700; }&#xA;  #tool-comparison .pc-card h3 span { font-size: 11px; font-weight: 400; color: #666; }&#xA;  #tool-comparison .pc-label { font-size: 10px; font-weight: 700; margin-bottom: 4px; text-transform: uppercase; letter-spacing: 1px; }&#xA;  #tool-comparison .pc-label.pro { color: var(--green); }&#xA;  #tool-comparison .pc-label.con { color: var(--red); }&#xA;  #tool-comparison .pc-item { font-size: 12px; padding: 3px 0; line-height: 1.5; }&#xA;  #tool-comparison .pc-item.pro { color: #ccc; }&#xA;  #tool-comparison .pc-item.con { color: #999; }&#xA;  #tool-comparison .pc-section { margin-bottom: 10px; }&#xA;   &#xA;  #tool-comparison .slider-group { background: var(--card); border-radius: 12px; padding: 14px; margin-bottom: 16px; }&#xA;  #tool-comparison .slider-row { margin-bottom: 12px; }&#xA;  #tool-comparison .slider-header { display: flex; justify-content: space-between; font-size: 13px; margin-bottom: 3px; }&#xA;  #tool-comparison .slider-header span:first-child { font-weight: 500; }&#xA;  #tool-comparison .slider-header span:last-child { color: #888; }&#xA;  #tool-comparison input[type=range] { width: 100%; accent-color: #6C63FF; }&#xA;  #tool-comparison .score-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }&#xA;  #tool-comparison .score-card { background: var(--card); border-radius: 12px; padding: 14px; border: 1px solid var(--card2); position: relative; }&#xA;  #tool-comparison .score-card.best { border-width: 2px; }&#xA;  #tool-comparison .score-card .best-tag { position: absolute; top: -8px; right: 10px; color: #000; font-size: 9px; font-weight: 700; padding: 2px 6px; border-radius: 4px; }&#xA;  #tool-comparison .score-card .score-val { font-size: 26px; font-weight: 700; }&#xA;  #tool-comparison .score-card .score-name { font-size: 13px; font-weight: 600; color: #fff; margin-top: 2px; }&#xA;  #tool-comparison .score-card .score-sub { font-size: 10px; color: #666; }&#xA;  #tool-comparison .score-bar-row { display: flex; align-items: center; gap: 5px; margin-bottom: 3px; }&#xA;  #tool-comparison .score-bar-label { font-size: 10px; color: #666; width: 56px; }&#xA;  #tool-comparison .score-bar-track { flex: 1; background: var(--bg); border-radius: 3px; height: 5px; }&#xA;  #tool-comparison .score-bar-fill { height: 100%; border-radius: 3px; opacity: 0.7; }&#xA;  #tool-comparison .score-bar-val { font-size: 10px; color: #888; width: 14px; text-align: right; }&#xA;  #tool-comparison .score-note { font-size: 12px; color: #888; margin-bottom: 14px; line-height: 1.5; }&#xA;&lt;/style&gt;&#xA;&#xA;&lt;h2&gt;Power Tool Brand Comparison&lt;/h2&gt;&#xA;&lt;p class=&#34;subtitle&#34;&gt;Upgrading your primary home toolkit · old Ryobi set goes to vacation property&lt;/p&gt;</description>
    </item>
    <item>
      <title>Large Payments Mortgage Amortization</title>
      <link>https://thenullpointer.net/tools/mortgage-large-payments-amortization/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://thenullpointer.net/tools/mortgage-large-payments-amortization/</guid>
      <description>&lt;style&gt;&#xA;:root {&#xA;  --accent: #e85d26;&#xA;  --bg: #0f1117;&#xA;  --card: #181b24;&#xA;  --border: #262a36;&#xA;  --text: #d4d4d8;&#xA;  --muted: #71717a;&#xA;  --green: #22c55e;&#xA;  --input-bg: #12151e;&#xA;  --indigo: #6366f1;&#xA;}&#xA;*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }&#xA;body { background: var(--bg); color: var(--text); font-family: &#39;DM Sans&#39;, &#39;Segoe UI&#39;, sans-serif; padding: 24px 16px; }&#xA;.wrap { max-width: 860px; margin: 0 auto; }&#xA;h1 { font-size: 22px; font-weight: 700; letter-spacing: -0.5px; }&#xA;.subtitle { color: var(--muted); font-size: 13px; margin-top: 4px; }&#xA;.card { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 18px; margin-bottom: 24px; }&#xA;.section-title { font-size: 15px; font-weight: 600; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }&#xA;.dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }&#xA;.dot-indigo { background: var(--indigo); }&#xA;.dot-accent { background: var(--accent); }&#xA;.row { display: flex; gap: 8px; flex-wrap: wrap; align-items: flex-end; }&#xA;.row + .row { margin-top: 12px; }&#xA;.field { flex: 1 1 90px; min-width: 70px; }&#xA;.field label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 3px; }&#xA;.field input { background: var(--input-bg); border: 1px solid var(--border); color: var(--text); border-radius: 6px; padding: 8px 10px; font-size: 14px; outline: none; width: 100%; font-family: inherit; }&#xA;.info { margin-top: 10px; font-size: 13px; color: var(--muted); }&#xA;.info span { color: var(--text); font-family: &#39;JetBrains Mono&#39;, monospace; }&#xA;.btn { background: var(--accent); color: #fff; border: none; border-radius: 6px; padding: 8px 16px; font-size: 14px; font-weight: 600; cursor: pointer; white-space: nowrap; font-family: inherit; }&#xA;.btn-outline { background: transparent; border: 1px solid var(--border); color: var(--text); }&#xA;.btn-pill { border-radius: 20px; padding: 6px 18px; font-size: 13px; }&#xA;.btn-sm { padding: 4px 10px; font-size: 12px; border-radius: 20px; }&#xA;.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: 12px; margin-bottom: 24px; }&#xA;.stat { background: var(--card); border: 1px solid var(--border); border-radius: 10px; padding: 14px 16px; }&#xA;.stat-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 1px; margin-bottom: 6px; }&#xA;.stat-value { font-size: 20px; font-weight: 700; font-family: &#39;JetBrains Mono&#39;, monospace; }&#xA;.stat-sub { font-size: 11px; color: var(--muted); margin-top: 2px; }&#xA;.divider { border-top: 1px solid var(--border); padding-top: 12px; margin-top: 12px; }&#xA;.divider-label { font-size: 12px; color: var(--muted); margin-bottom: 8px; }&#xA;.pills { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 6px; }&#xA;.pill { background: #1e2330; border: 1px solid var(--border); border-radius: 20px; padding: 4px 10px; font-size: 12px; font-family: &#39;JetBrains Mono&#39;, monospace; display: inline-flex; align-items: center; gap: 6px; }&#xA;.pill-x { cursor: pointer; color: var(--muted); font-weight: 700; font-size: 14px; line-height: 1; }&#xA;.tabs { display: flex; gap: 4px; margin-bottom: 16px; }&#xA;table { width: 100%; border-collapse: collapse; font-size: 13px; font-family: &#39;JetBrains Mono&#39;, monospace; }&#xA;th { padding: 10px 12px; text-align: right; font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.8px; font-weight: 500; white-space: nowrap; border-bottom: 1px solid var(--border); }&#xA;td { padding: 7px 12px; text-align: right; }&#xA;tr.extra-row { background: #162016; }&#xA;.tbl-wrap { overflow-x: auto; }&#xA;.tbl-footer { padding: 12px; text-align: center; }&#xA;svg text { font-family: &#39;JetBrains Mono&#39;, monospace; }&#xA;&lt;/style&gt;&#xA;&lt;/head&gt;&#xA;&lt;body&gt;&#xA;&lt;div class=&#34;wrap&#34;&gt;&#xA;  &lt;div style=&#34;margin-bottom:20px&#34;&gt;&#xA;    &lt;h1&gt;Amortization Calculator&lt;/h1&gt;&#xA;    &lt;p class=&#34;subtitle&#34; id=&#34;subtitle&#34;&gt;&lt;/p&gt;</description>
    </item>
    <item>
      <title>Mortgage Points Amortization</title>
      <link>https://thenullpointer.net/tools/mortgage-points-amortization/</link>
      <pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate>
      <guid>https://thenullpointer.net/tools/mortgage-points-amortization/</guid>
      <description>&lt;style&gt;&#xA;*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }&#xA;body {&#xA;    font-family: -apple-system, BlinkMacSystemFont, &#39;Segoe UI&#39;, Roboto, sans-serif;&#xA;    background: #f5f5f2;&#xA;    color: #1a1a18;&#xA;    padding: 2rem 1rem;&#xA;    min-height: 100vh;&#xA;}&#xA;h1 { font-size: 22px; font-weight: 500; margin-bottom: 1.5rem; }&#xA;.calc-wrap { max-width: 640px; margin: 0 auto; }&#xA;.section {&#xA;    background: #ffffff;&#xA;    border: 0.5px solid rgba(0,0,0,0.15);&#xA;    border-radius: 12px;&#xA;    padding: 1.25rem;&#xA;    margin-bottom: 1rem;&#xA;}&#xA;.row { display: flex; align-items: center; gap: 12px; margin-bottom: 1rem; }&#xA;.row:last-child { margin-bottom: 0; }&#xA;.row label { font-size: 13px; color: #5f5e5a; width: 160px; flex-shrink: 0; }&#xA;.row input[type=range] { flex: 1; accent-color: #1D9E75; }&#xA;.row .val { font-size: 14px; font-weight: 500; min-width: 90px; text-align: right; }&#xA;.metrics {&#xA;    display: grid;&#xA;    grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));&#xA;    gap: 10px;&#xA;    margin-bottom: 1rem;&#xA;}&#xA;.metric { background: #f1efe8; border-radius: 8px; padding: 0.85rem 1rem; }&#xA;.metric-label { font-size: 12px; color: #5f5e5a; margin-bottom: 4px; }&#xA;.metric-value { font-size: 20px; font-weight: 500; color: #1a1a18; }&#xA;.metric-value.good { color: #0f6e56; }&#xA;.metric-value.bad { color: #a32d2d; }&#xA;.verdict { border-radius: 8px; padding: 0.85rem 1rem; font-size: 14px; font-weight: 500; margin-bottom: 1rem; }&#xA;.verdict.good { background: #e1f5ee; color: #0f6e56; }&#xA;.verdict.bad { background: #fcebeb; color: #a32d2d; }&#xA;.chart-label { font-size: 12px; color: #5f5e5a; margin-bottom: 6px; }&#xA;.legend { display: flex; gap: 16px; margin-bottom: 8px; font-size: 12px; color: #5f5e5a; }&#xA;.legend span { display: flex; align-items: center; gap: 4px; }&#xA;.legend-dot { width: 10px; height: 10px; border-radius: 2px; }&#xA;&#xA;@media (prefers-color-scheme: dark) {&#xA;    body { background: #1a1a18; color: #e8e6df; }&#xA;    .section { background: #2c2c2a; border-color: rgba(255,255,255,0.12); }&#xA;    .metric { background: #3a3a38; }&#xA;    .metric-label { color: #a0a09a; }&#xA;    .metric-value { color: #e8e6df; }&#xA;    .metric-value.good { color: #5dcaa5; }&#xA;    .metric-value.bad { color: #f09595; }&#xA;    .verdict.good { background: #085041; color: #9fe1cb; }&#xA;    .verdict.bad { background: #501313; color: #f7c1c1; }&#xA;    .chart-label { color: #a0a09a; }&#xA;    .legend { color: #a0a09a; }&#xA;    .row label { color: #a0a09a; }&#xA;}&#xA;&lt;/style&gt;&#xA;&lt;/head&gt;&#xA;&lt;body&gt;&#xA;&lt;div class=&#34;calc-wrap&#34;&gt;&#xA;&lt;h1&gt;Mortgage points calculator&lt;/h1&gt;&#xA;&#xA;&lt;div class=&#34;section&#34;&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;    &lt;label&gt;Loan amount&lt;/label&gt;&#xA;    &lt;input type=&#34;range&#34; id=&#34;loanAmt&#34; min=&#34;50000&#34; max=&#34;1000000&#34; step=&#34;1000&#34; value=&#34;168000&#34;&gt;&#xA;    &lt;span class=&#34;val&#34; id=&#34;loanAmtVal&#34;&gt;$168,000&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;    &lt;label&gt;Default rate&lt;/label&gt;&#xA;    &lt;input type=&#34;range&#34; id=&#34;baseRate&#34; min=&#34;3&#34; max=&#34;12&#34; step=&#34;0.05&#34; value=&#34;7.1&#34;&gt;&#xA;    &lt;span class=&#34;val&#34; id=&#34;baseRateVal&#34;&gt;7.10%&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;    &lt;label&gt;Buydown rate&lt;/label&gt;&#xA;    &lt;input type=&#34;range&#34; id=&#34;buyRate&#34; min=&#34;3&#34; max=&#34;12&#34; step=&#34;0.05&#34; value=&#34;6.625&#34;&gt;&#xA;    &lt;span class=&#34;val&#34; id=&#34;buyRateVal&#34;&gt;6.625%&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;    &lt;label&gt;Cost of points&lt;/label&gt;&#xA;    &lt;input type=&#34;range&#34; id=&#34;pointCost&#34; min=&#34;500&#34; max=&#34;20000&#34; step=&#34;100&#34; value=&#34;2940&#34;&gt;&#xA;    &lt;span class=&#34;val&#34; id=&#34;pointCostVal&#34;&gt;$2,940&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div class=&#34;row&#34;&gt;&#xA;    &lt;label&gt;Planned payoff (mo.)&lt;/label&gt;&#xA;    &lt;input type=&#34;range&#34; id=&#34;payoff&#34; min=&#34;6&#34; max=&#34;360&#34; step=&#34;1&#34; value=&#34;36&#34;&gt;&#xA;    &lt;span class=&#34;val&#34; id=&#34;payoffVal&#34;&gt;36 mo.&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;metrics&#34;&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Default payment&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mBase&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Buydown payment&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mBuy&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Monthly savings&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mSave&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Break-even&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mBE&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Savings at payoff&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mSaveAt&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;    &lt;div class=&#34;metric&#34;&gt;&lt;div class=&#34;metric-label&#34;&gt;Net gain / loss&lt;/div&gt;&lt;div class=&#34;metric-value&#34; id=&#34;mNet&#34;&gt;—&lt;/div&gt;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;verdict&#34; id=&#34;verdict&#34;&gt;&lt;/div&gt;&#xA;&#xA;&lt;div class=&#34;section&#34;&gt;&#xA;    &lt;div class=&#34;chart-label&#34;&gt;Cumulative savings vs. cost of points over time&lt;/div&gt;&#xA;    &lt;div class=&#34;legend&#34;&gt;&#xA;    &lt;span&gt;&lt;span class=&#34;legend-dot&#34; style=&#34;background:#1D9E75;&#34;&gt;&lt;/span&gt; Cumulative savings&lt;/span&gt;&#xA;    &lt;span&gt;&lt;span class=&#34;legend-dot&#34; style=&#34;background:#E24B4A; opacity:0.7;&#34;&gt;&lt;/span&gt; Cost of points&lt;/span&gt;&#xA;    &lt;/div&gt;&#xA;    &lt;div style=&#34;position: relative; width: 100%; height: 220px;&#34;&gt;&#xA;    &lt;canvas id=&#34;chart&#34; role=&#34;img&#34; aria-label=&#34;Line chart showing cumulative savings versus cost of points over time&#34;&gt;&lt;/canvas&gt;&#xA;    &lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&lt;/div&gt;&#xA;&#xA;&lt;script src=&#34;https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.1/chart.umd.js&#34;&gt;&lt;/script&gt;&#xA;&lt;script&gt;&#xA;function monthlyPayment(principal, annualRate) {&#xA;    const r = annualRate / 100 / 12;&#xA;    const n = 360;&#xA;    if (r === 0) return principal / n;&#xA;    return principal * r * Math.pow(1 + r, n) / (Math.pow(1 + r, n) - 1);&#xA;}&#xA;&#xA;function fmt$(n) { return &#39;$&#39; + Math.abs(n).toLocaleString(&#39;en-US&#39;, {minimumFractionDigits: 0, maximumFractionDigits: 0}); }&#xA;function fmtSigned(n) { return (n &gt;= 0 ? &#39;+&#39; : &#39;-&#39;) + &#39;$&#39; + Math.abs(n).toLocaleString(&#39;en-US&#39;, {minimumFractionDigits: 0, maximumFractionDigits: 0}); }&#xA;&#xA;let chart;&#xA;&#xA;function compute() {&#xA;    const loan = +document.getElementById(&#39;loanAmt&#39;).value;&#xA;    const base = +document.getElementById(&#39;baseRate&#39;).value;&#xA;    const buy = +document.getElementById(&#39;buyRate&#39;).value;&#xA;    const cost = +document.getElementById(&#39;pointCost&#39;).value;&#xA;    const payoff = +document.getElementById(&#39;payoff&#39;).value;&#xA;&#xA;    const pmtBase = monthlyPayment(loan, base);&#xA;    const pmtBuy = monthlyPayment(loan, buy);&#xA;    const savingPerMonth = pmtBase - pmtBuy;&#xA;    const breakEven = savingPerMonth &gt; 0 ? Math.ceil(cost / savingPerMonth) : Infinity;&#xA;    const savingsAtPayoff = savingPerMonth * payoff;&#xA;    const net = savingsAtPayoff - cost;&#xA;&#xA;    document.getElementById(&#39;mBase&#39;).textContent = fmt$(pmtBase);&#xA;    document.getElementById(&#39;mBuy&#39;).textContent = fmt$(pmtBuy);&#xA;    document.getElementById(&#39;mSave&#39;).textContent = fmt$(savingPerMonth) + &#39;/mo&#39;;&#xA;    document.getElementById(&#39;mBE&#39;).textContent = isFinite(breakEven) ? breakEven + &#39; mo.&#39; : &#39;—&#39;;&#xA;&#xA;    const saveEl = document.getElementById(&#39;mSaveAt&#39;);&#xA;    saveEl.textContent = fmt$(savingsAtPayoff);&#xA;&#xA;    const netEl = document.getElementById(&#39;mNet&#39;);&#xA;    netEl.textContent = fmtSigned(net);&#xA;    netEl.className = &#39;metric-value &#39; + (net &gt;= 0 ? &#39;good&#39; : &#39;bad&#39;);&#xA;&#xA;    const verdictEl = document.getElementById(&#39;verdict&#39;);&#xA;    if (!isFinite(breakEven) || savingPerMonth &lt;= 0) {&#xA;    verdictEl.textContent = &#39;Buydown rate is not lower — no benefit to buying points.&#39;;&#xA;    verdictEl.className = &#39;verdict bad&#39;;&#xA;    } else if (net &gt;= 0) {&#xA;    verdictEl.textContent = `Worth it. You break even at ${breakEven} months and net ${fmtSigned(net)} by your planned payoff.`;&#xA;    verdictEl.className = &#39;verdict good&#39;;&#xA;    } else {&#xA;    verdictEl.textContent = `Not worth it. Break-even is ${breakEven} months — ${breakEven - payoff} months past your planned payoff. You&#39;d be ${fmt$(Math.abs(net))} short.`;&#xA;    verdictEl.className = &#39;verdict bad&#39;;&#xA;    }&#xA;&#xA;    const maxMo = Math.max(payoff + 12, isFinite(breakEven) ? breakEven + 12 : payoff + 12, 60);&#xA;    const labels = [];&#xA;    const cumSavings = [];&#xA;    const costLine = [];&#xA;    for (let m = 0; m &lt;= maxMo; m += Math.ceil(maxMo / 48)) {&#xA;    labels.push(&#39;Mo &#39; + m);&#xA;    cumSavings.push(+(savingPerMonth * m).toFixed(0));&#xA;    costLine.push(cost);&#xA;    }&#xA;&#xA;    const isDark = matchMedia(&#39;(prefers-color-scheme: dark)&#39;).matches;&#xA;    const gridColor = isDark ? &#39;rgba(255,255,255,0.08)&#39; : &#39;rgba(0,0,0,0.07)&#39;;&#xA;    const textColor = isDark ? &#39;#a0a09a&#39; : &#39;#888780&#39;;&#xA;&#xA;    if (chart) {&#xA;    chart.data.labels = labels;&#xA;    chart.data.datasets[0].data = cumSavings;&#xA;    chart.data.datasets[1].data = costLine;&#xA;    chart.update();&#xA;    } else {&#xA;    chart = new Chart(document.getElementById(&#39;chart&#39;), {&#xA;        type: &#39;line&#39;,&#xA;        data: {&#xA;        labels,&#xA;        datasets: [&#xA;            {&#xA;            label: &#39;Cumulative savings&#39;,&#xA;            data: cumSavings,&#xA;            borderColor: &#39;#1D9E75&#39;,&#xA;            backgroundColor: &#39;rgba(29,158,117,0.08)&#39;,&#xA;            borderWidth: 2,&#xA;            pointRadius: 0,&#xA;            fill: true,&#xA;            tension: 0.3&#xA;            },&#xA;            {&#xA;            label: &#39;Cost of points&#39;,&#xA;            data: costLine,&#xA;            borderColor: &#39;#E24B4A&#39;,&#xA;            borderWidth: 2,&#xA;            borderDash: [6, 4],&#xA;            pointRadius: 0,&#xA;            fill: false&#xA;            }&#xA;        ]&#xA;        },&#xA;        options: {&#xA;        responsive: true,&#xA;        maintainAspectRatio: false,&#xA;        interaction: { mode: &#39;index&#39;, intersect: false },&#xA;        plugins: {&#xA;            legend: { display: false },&#xA;            tooltip: {&#xA;            callbacks: {&#xA;                label: ctx =&gt; &#39; &#39; + ctx.dataset.label + &#39;: $&#39; + ctx.parsed.y.toLocaleString()&#xA;            }&#xA;            }&#xA;        },&#xA;        scales: {&#xA;            x: { ticks: { color: textColor, font: { size: 11 }, maxTicksLimit: 8 }, grid: { color: gridColor } },&#xA;            y: {&#xA;            ticks: { color: textColor, font: { size: 11 }, callback: v =&gt; &#39;$&#39; + (v / 1000).toFixed(1) + &#39;k&#39; },&#xA;            grid: { color: gridColor }&#xA;            }&#xA;        }&#xA;        }&#xA;    });&#xA;    }&#xA;}&#xA;&#xA;[&#39;loanAmt&#39;, &#39;baseRate&#39;, &#39;buyRate&#39;, &#39;pointCost&#39;, &#39;payoff&#39;].forEach(id =&gt; {&#xA;    const el = document.getElementById(id);&#xA;    el.addEventListener(&#39;input&#39;, () =&gt; {&#xA;    const v = +el.value;&#xA;    const labels = {&#xA;        loanAmt: v =&gt; &#39;$&#39; + v.toLocaleString(),&#xA;        baseRate: v =&gt; v.toFixed(2) + &#39;%&#39;,&#xA;        buyRate: v =&gt; v.toFixed(3) + &#39;%&#39;,&#xA;        pointCost: v =&gt; &#39;$&#39; + v.toLocaleString(),&#xA;        payoff: v =&gt; v + &#39; mo.&#39;&#xA;    };&#xA;    document.getElementById(id + &#39;Val&#39;).textContent = labels[id](v);&#xA;    compute();&#xA;    });&#xA;});&#xA;&#xA;compute();&#xA;&lt;/script&gt;</description>
    </item>
  </channel>
</rss>
