/* Margin Money Debt Payoff Calculator v2: page shell + calculator */ (function () { var script = document.currentScript; var part = (script && script.getAttribute('data-part')) || 'bottom'; var holder = document.createElement('div'); if (script && script.parentNode) script.parentNode.insertBefore(holder, script); else document.body.appendChild(holder); if (!window.__mmDpcShell) { window.__mmDpcShell = fetch('/resources', { credentials: 'same-origin' }).then(function (r) { return r.text(); }).then(function (html) { var d = new DOMParser().parseFromString(html, 'text/html'); var cc = d.querySelector('.custom-code-container') || d; var styles = Array.prototype.map.call(cc.querySelectorAll('link[rel=stylesheet],link[rel=preconnect],style'), function (e) { return e.outerHTML; }).join(''); var header = cc.querySelector('.mm-header'); if (header) header.querySelectorAll('[aria-current]').forEach(function (a) { a.removeAttribute('aria-current'); }); var skip = cc.querySelector('.mm-skip'); var footer = cc.querySelector('footer.mm-f'); return { styles: styles, header: header ? header.outerHTML : '', skip: skip ? skip.outerHTML : '', footer: footer ? footer.outerHTML : '' }; }).catch(function () { return { styles: '', header: '', skip: '', footer: '' }; }); } var hero = '
' + '

Free debt payoff calculator

' + '

See the month your last debt is paid off.

' + '

Do you ever wonder when the payments will finally stop? List your debts, choose the snowball or avalanche method, and see your payoff order, the month each debt is gone, and how much money you get back as each one clears. When you are done, you can print your plan or save it as a PDF.

' + '

Open the free calculator

' + '
' + '

Enter your email to open the calculator

'; window.__mmDpcShell.then(function (sh) { if (part === 'top') { holder.innerHTML = '
' + sh.styles + sh.skip + sh.header + '
' + hero + '
'; if (window.__mmDpcAfterTop) window.__mmDpcAfterTop(); } else { holder.innerHTML = '
' + sh.footer + '
'; mmDpcInitCalc(holder.querySelector('#mm-dpc')); } }); })(); function mmDpcInitCalc(mount) { if (!mount || mount.getAttribute('data-ready')) return; mount.setAttribute('data-ready', '1'); var KEY = 'mm_dpc_access'; var STORE = 'mm_dpc_inputs'; function lsGet(k) { try { return window.localStorage.getItem(k); } catch (e) { return null; } } function lsSet(k, v) { try { window.localStorage.setItem(k, v); } catch (e) {} } var params = new URLSearchParams(window.location.search); if (params.get('access') === '1') lsSet(KEY, '1'); var unlocked = lsGet(KEY) === '1' || params.get('access') === '1'; /* ---------- styles ---------- */ var css = [ '.dpc{padding:48px 24px 96px}', '.dpc-shell{max-width:1200px;margin:0 auto}', '.dpc .dpc-card{background:#fff;border:1px solid #D9DDD6;border-radius:16px;box-shadow:0 1px 2px rgba(15,28,46,.06),0 4px 12px rgba(15,28,46,.04);padding:28px}', '.dpc h2{font:600 clamp(26px,3.4vw,34px)/1.2 "Playfair Display",serif;color:#0F1C2E;margin:0 0 8px;letter-spacing:-.3px}', '.dpc h3{font:600 20px/1.3 "Playfair Display",serif;color:#0F1C2E;margin:0 0 12px}', '.dpc p{font:400 16px/1.6 Inter,sans-serif;color:#22344B;margin:0 0 12px}', '.dpc .dpc-sub{color:#405066;font-size:15px}', '.dpc label,.dpc .dpc-lbl{display:block;font:600 14px/1.3 Inter,sans-serif;color:#0F1C2E;margin:0 0 6px}', '.dpc input[type=text],.dpc input[type=number],.dpc input[type=month]{width:100%;min-height:46px;border:1px solid #9AA5A0;border-radius:10px;padding:10px 12px;font:400 16px/1.2 Inter,sans-serif;color:#0F1C2E;background:#fff;box-sizing:border-box}', '.dpc input:focus-visible,.dpc button:focus-visible{outline:3px solid #66B8A5;outline-offset:2px}', '.dpc .dpc-money{position:relative}.dpc .dpc-money span{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:#405066;font:400 16px Inter,sans-serif;pointer-events:none}.dpc .dpc-money input{padding-left:26px}', '.dpc .dpc-pct{position:relative}.dpc .dpc-pct span{position:absolute;right:12px;top:50%;transform:translateY(-50%);color:#405066;font:400 16px Inter,sans-serif;pointer-events:none}.dpc .dpc-pct input{padding-right:30px}', '.dpc .dpc-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:16px}', '.dpc .dpc-debt{display:grid;grid-template-columns:minmax(140px,1.6fr) repeat(3,minmax(110px,1fr)) 48px;gap:12px;align-items:end;padding:16px 0;border-top:1px solid #E6E9E4}', '.dpc .dpc-debt:first-of-type{border-top:0}', '.dpc .dpc-remove{width:48px;height:46px;border:1px solid #D9DDD6;border-radius:10px;background:#fff;color:#8A2E1F;font:600 22px/1 Inter,sans-serif;cursor:pointer}', '.dpc .dpc-remove:hover{background:#FBEFEA}', '.dpc .dpc-btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;min-height:48px;padding:12px 24px;border-radius:9999px;font:600 16px/1.2 Inter,sans-serif;cursor:pointer;text-decoration:none;border:0}', '.dpc .dpc-primary{background:#006A54;color:#fff}.dpc .dpc-primary:hover{background:#005846}', '.dpc .dpc-ghost{background:#fff;color:#0F1C2E;border:1.5px solid #0F1C2E}.dpc .dpc-ghost:hover{background:#F5F1E8}', '.mm .dpc a.dpc-primary,.mm .dpc a.dpc-primary:hover,.mm .dpc a.dpc-primary:visited{color:#fff !important;text-decoration:none}.c-form,.c-form *{-webkit-hyphens:manual !important;hyphens:manual !important}', '.dpc .dpc-row{display:flex;flex-wrap:wrap;gap:12px;align-items:center}', '.dpc .dpc-toggle{display:inline-flex;background:#F5F1E8;border-radius:9999px;padding:4px;gap:4px;flex-wrap:wrap}', '.dpc .dpc-toggle button{min-height:44px;padding:10px 20px;border-radius:9999px;border:0;background:transparent;font:600 15px/1.2 Inter,sans-serif;color:#22344B;cursor:pointer}', '.dpc .dpc-toggle button[aria-pressed=true]{background:#0F1C2E;color:#fff}', '.dpc .dpc-compare{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px}', '.dpc .dpc-plan{border:1.5px solid #D9DDD6;border-radius:14px;padding:20px;background:#fff}', '.dpc .dpc-plan.is-on{border-color:#006A54;box-shadow:0 0 0 3px #E8F4F1}', '.dpc .dpc-plan .dpc-k{font:600 12px/1.3 Inter,sans-serif;letter-spacing:.48px;text-transform:uppercase;color:#405066;margin:0 0 6px}', '.dpc .dpc-big{font:600 28px/1.15 "Playfair Display",serif;color:#0F1C2E;margin:0 0 10px}', '.dpc .dpc-stats{display:grid;grid-template-columns:1fr 1fr;gap:8px 16px;margin:0}', '.dpc .dpc-stats dt{font:400 13px/1.3 Inter,sans-serif;color:#405066}.dpc .dpc-stats dd{margin:0;font:600 16px/1.3 Inter,sans-serif;color:#0F1C2E;font-variant-numeric:tabular-nums}', '.dpc .dpc-order{list-style:none;margin:0;padding:0;counter-reset:o}', '.dpc .dpc-order li{counter-increment:o;display:grid;grid-template-columns:40px 1fr auto;gap:12px;align-items:center;padding:14px 0;border-top:1px solid #E6E9E4}', '.dpc .dpc-order li:first-child{border-top:0}', '.dpc .dpc-order li:before{content:counter(o);width:32px;height:32px;border-radius:50%;background:#E8F4F1;color:#004739;font:600 14px/32px Inter,sans-serif;text-align:center}', '.dpc .dpc-order .dpc-n{font:600 16px/1.3 Inter,sans-serif;color:#0F1C2E}.dpc .dpc-order .dpc-d{font:400 14px/1.4 Inter,sans-serif;color:#405066}', '.dpc .dpc-order .dpc-free{font:600 14px/1.3 Inter,sans-serif;color:#004739;background:#E8F4F1;border-radius:9999px;padding:6px 10px;white-space:nowrap}', '.dpc .dpc-tablewrap{overflow-x:auto;border:1px solid #D9DDD6;border-radius:12px;max-height:560px}', '.dpc table{border-collapse:collapse;width:100%;font:400 14px/1.4 Inter,sans-serif;color:#0F1C2E;font-variant-numeric:tabular-nums}', '.dpc th,.dpc td{padding:10px 12px;text-align:right;white-space:nowrap;border-bottom:1px solid #EEF0EC}', '.dpc th:first-child,.dpc td:first-child{text-align:left;position:sticky;left:0;background:#fff}', '.dpc thead th{position:sticky;top:0;background:#0F1C2E;color:#fff;font-weight:600;z-index:1}', '.dpc thead th:first-child{z-index:2;background:#0F1C2E}', '.dpc tr.dpc-paid td{background:#F2F8F6}.dpc tr.dpc-paid td:first-child{background:#F2F8F6}', '.dpc .dpc-msg{border-radius:12px;padding:14px 16px;font:400 15px/1.5 Inter,sans-serif;margin:0 0 16px}', '.dpc .dpc-warn{background:#FBEFEA;color:#6B2416;border:1px solid #F0CDBF}', '.dpc .dpc-ok{background:#E8F4F1;color:#004739;border:1px solid #BFDFD6}', '.dpc .dpc-sec{margin-top:24px}', '.dpc .dpc-note{font:400 13px/1.6 Inter,sans-serif;color:#405066;margin:16px 0 0}', '.dpc .dpc-lock{text-align:center;max-width:640px;margin:0 auto;padding:40px 28px}', '.dpc .dpc-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}', '.dpc .dpc-printhead,.dpc .dpc-printonly{display:none}', '.dpc .dpc-rmtxt{display:none}', '@media (max-width:760px){.dpc .dpc-debt{grid-template-columns:1fr 1fr;}.dpc .dpc-debt .dpc-f-name{grid-column:1 / -1}.dpc .dpc-debt>div:nth-child(4){grid-column:1 / -1}.dpc .dpc-debt .dpc-remove{grid-column:1 / -1;justify-self:end;margin-top:-4px;width:auto;height:auto;min-height:40px;padding:6px 2px;border:0;background:none;font:600 15px/1.2 Inter,sans-serif;color:#8A2E1F;text-decoration:underline;text-underline-offset:3px}.dpc .dpc-debt .dpc-remove:hover{background:none}.dpc .dpc-rmx{display:none}.dpc .dpc-rmtxt{display:inline}.dpc{padding:32px 16px 72px}.dpc .dpc-card{padding:22px 18px}.dpc .dpc-row .dpc-btn{width:100%}.dpc .dpc-toggle{width:100%}.dpc .dpc-toggle button{flex:1}}', '@media print{', ' @page{margin:14mm}', ' body *{visibility:hidden !important}', ' #mm-dpc-print,#mm-dpc-print *{visibility:visible !important}', ' #mm-dpc-print{position:absolute;left:0;top:0;width:100%}', ' .dpc{padding:0}.dpc .dpc-noprint{display:none !important}', ' .dpc .dpc-printhead,.dpc .dpc-printonly{display:block;margin:0 0 16px}', ' .dpc .dpc-card.dpc-schedule{break-inside:auto}', ' .dpc .dpc-card{box-shadow:none;border:1px solid #ccc;padding:16px;break-inside:avoid}', ' .dpc .dpc-tablewrap{max-height:none;overflow:visible;border:0}', ' .dpc thead th{position:static;background:#0F1C2E !important;color:#fff !important;-webkit-print-color-adjust:exact;print-color-adjust:exact}', ' .dpc th:first-child,.dpc td:first-child{position:static}', ' .dpc table{font-size:10px}.dpc th,.dpc td{padding:4px 6px}', ' .dpc tr{break-inside:avoid}', '}' ].join('\n'); var st = document.createElement('style'); st.textContent = css; document.head.appendChild(st); /* ---------- helpers ---------- */ function esc(s) { return String(s).replace(/[&<>"']/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]; }); } function money(n) { return '$' + Math.round(n).toLocaleString('en-US'); } function money2(n) { return '$' + n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function num(v) { var n = parseFloat(String(v).replace(/[$,%\s]/g, '')); return isFinite(n) ? n : 0; } var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; var MON3 = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; function monthLabel(y, m, short) { return (short ? MON3 : MONTHS)[m] + ' ' + y; } function addMonths(y, m, k) { var t = y * 12 + m + k; return [Math.floor(t / 12), t % 12]; } function durText(n) { var y = Math.floor(n / 12), m = n % 12, out = []; if (y) out.push(y + (y === 1 ? ' year' : ' years')); if (m || !y) out.push(m + (m === 1 ? ' month' : ' months')); return out.join(', '); } /* ---------- simulation ---------- */ function simulate(debts, extra, method) { var list = debts.map(function (d, i) { return { i: i, name: d.name, bal: d.balance, rate: d.rate, min: d.min, paidMonth: null, interest: 0 }; }); var order = list.slice().sort(function (a, b) { if (method === 'avalanche') return (b.rate - a.rate) || (a.bal - b.bal) || (a.i - b.i); return (a.bal - b.bal) || (b.rate - a.rate) || (a.i - b.i); }); var budget = list.reduce(function (s, d) { return s + d.min; }, 0) + extra; var rows = [], month = 0, totalInterest = 0, totalPaid = 0, stuck = false; while (list.some(function (d) { return d.bal > 0.005; })) { if (month >= 600) { stuck = true; break; } month++; var pay = list.map(function () { return 0; }), intThis = 0; list.forEach(function (d) { if (d.bal > 0.005) { var it = d.bal * d.rate / 1200; d.bal += it; d.interest += it; intThis += it; } }); var left = budget; list.forEach(function (d) { if (d.bal > 0.005) { var p = Math.min(d.min, d.bal); d.bal -= p; pay[d.i] += p; left -= p; } }); for (var k = 0; k < order.length && left > 0.005; k++) { var d = order[k]; if (d.bal > 0.005) { var p2 = Math.min(left, d.bal); d.bal -= p2; pay[d.i] += p2; left -= p2; } } var cleared = []; list.forEach(function (d) { if (d.bal <= 0.005 && d.paidMonth === null) { d.bal = 0; d.paidMonth = month; cleared.push(d.name); } }); var paidSum = pay.reduce(function (s, v) { return s + v; }, 0); totalInterest += intThis; totalPaid += paidSum; rows.push({ month: month, pay: pay, bals: list.map(function (d) { return d.bal; }), paid: paidSum, interest: intThis, remaining: list.reduce(function (s, d) { return s + d.bal; }, 0), cleared: cleared }); } var payoff = list.filter(function (d) { return d.paidMonth !== null; }).sort(function (a, b) { return (a.paidMonth - b.paidMonth) || (order.indexOf(a) - order.indexOf(b)); }); return { months: month, rows: rows, totalInterest: totalInterest, totalPaid: totalPaid, budget: budget, payoff: payoff, stuck: stuck, order: order }; } /* ---------- state ---------- */ var now = new Date(); var state = { method: 'snowball', extra: '', start: now.getFullYear() + '-' + String(now.getMonth() + 1).padStart(2, '0'), debts: [ { name: '', balance: '', rate: '', min: '' }, { name: '', balance: '', rate: '', min: '' }, { name: '', balance: '', rate: '', min: '' } ] }; try { var saved = JSON.parse(lsGet(STORE) || 'null'); if (saved && saved.debts && saved.debts.length) state = saved; } catch (e) {} function save() { lsSet(STORE, JSON.stringify(state)); } /* ---------- locked view ---------- */ if (!unlocked) { mount.innerHTML = '
' + '

Your calculator is one step away.

' + '

Enter your email in the form above and the calculator opens right here on this page. You only need to do this once on this device.

' + '

Enter my email

'; return; } /* hide the email form and hero button once unlocked */ function hideGate() { var anchor = document.getElementById('get-calculator'); if (anchor) anchor.style.display = 'none'; document.querySelectorAll('.c-form').forEach(function (f) { f.style.display = 'none'; }); document.querySelectorAll('a[href="#get-calculator"]').forEach(function (a) { a.setAttribute('href', '#calculator'); a.textContent = 'Go to the calculator'; }); } hideGate(); setTimeout(hideGate, 800); setTimeout(hideGate, 2500); /* ---------- render ---------- */ mount.innerHTML = '
' + '

Margin Money Debt Payoff Plan

' + '
' + '

Debt Payoff Calculator

' + '

List each debt with its balance, interest rate, and minimum payment. Your numbers stay on this device and are not sent to us.

' + '
' + '
' + '
' + '

Money on top of your minimum payments.

' + '
' + '
' + '
Payoff method
' + '' + '
' + '
' + '
' + '
'; var debtsEl = document.getElementById('dpc-debts'); var extraEl = document.getElementById('dpc-extra'); var startEl = document.getElementById('dpc-start'); var outEl = document.getElementById('dpc-out'); extraEl.value = state.extra; startEl.value = state.start; function renderDebts() { debtsEl.innerHTML = state.debts.map(function (d, i) { var n = i + 1; return '
' + '
' + '
' + '
' + '
' + '' + '
'; }).join(''); } function setMethodButtons() { document.querySelectorAll('.dpc-toggle button').forEach(function (b) { b.setAttribute('aria-pressed', b.getAttribute('data-m') === state.method ? 'true' : 'false'); }); } var lastResult = null; function compute() { var debts = [], issues = []; state.debts.forEach(function (d, i) { var b = num(d.balance), r = num(d.rate), m = num(d.min), name = (d.name || '').trim() || ('Debt ' + (i + 1)); if (!b && !m && !(d.name || '').trim()) return; if (b <= 0) { issues.push(name + ' needs a balance.'); return; } if (m <= 0) { issues.push(name + ' needs a minimum payment.'); return; } if (r < 0 || r > 100) { issues.push(name + ' needs an interest rate between 0 and 100.'); return; } debts.push({ name: name, balance: b, rate: r, min: m }); }); var extra = Math.max(0, num(state.extra)); var sm = (state.start || '').split('-'), sy = parseInt(sm[0], 10), smo = parseInt(sm[1], 10) - 1; if (!(sy > 1900) || !(smo >= 0)) { sy = now.getFullYear(); smo = now.getMonth(); } if (!debts.length) { outEl.innerHTML = '

Add at least one debt with a balance and a minimum payment to see your plan.

' + (issues.length ? '

' + issues.map(esc).join('
') + '

' : '') + '
'; lastResult = null; return; } var snow = simulate(debts, extra, 'snowball'); var aval = simulate(debts, extra, 'avalanche'); var res = state.method === 'avalanche' ? aval : snow; lastResult = { res: res, debts: debts, sy: sy, smo: smo, extra: extra }; function endLabel(r) { var e = addMonths(sy, smo, r.months - 1); return monthLabel(e[0], e[1]); } function planCard(r, key, title, desc) { var on = state.method === key; return '

' + title + (on ? ' (showing below)' : '') + '

' + (r.stuck ? '

Not paid off in 50 years

' : '

Debt-free ' + endLabel(r) + '

') + '

' + desc + '

' + '
Time to debt-free
' + (r.stuck ? '50+ years' : durText(r.months)) + '
Total interest
' + money(r.totalInterest) + '
'; } var warn = ''; debts.forEach(function (d) { if (d.min <= d.balance * d.rate / 1200 + 0.005) warn += '

The minimum on ' + esc(d.name) + ' does not cover its monthly interest of ' + money2(d.balance * d.rate / 1200) + ', so it only shrinks once extra money reaches it.

'; }); if (issues.length) warn += '

Skipped: ' + issues.map(esc).join(' ') + '

'; var diff = Math.round(snow.totalInterest - aval.totalInterest); var cmp = diff > 0 ? 'Avalanche saves about ' + money(diff) + ' in interest. Snowball pays off your first debt sooner, which keeps many people motivated.' : (diff < 0 ? 'Snowball costs about ' + money(-diff) + ' less in interest with these numbers.' : 'Both methods cost about the same in interest with these numbers.'); var running = 0; var orderHtml = res.payoff.map(function (d) { running += d.min; var e = addMonths(sy, smo, d.paidMonth - 1); return '
  • ' + esc(d.name) + '
    Paid off ' + monthLabel(e[0], e[1]) + ', month ' + d.paidMonth + ', interest paid ' + money(d.interest) + '
    ' + 'Frees ' + money(d.min) + '/mo
  • '; }).join(''); var heads = ['Month']; debts.forEach(function (d) { heads.push(d.name + ' payment'); heads.push(d.name + ' balance'); }); heads.push('Total paid', 'Interest', 'Total remaining'); var body = res.rows.map(function (r) { var e = addMonths(sy, smo, r.month - 1); var cells = ['' + monthLabel(e[0], e[1], true) + (r.cleared.length ? ', paid off ' + esc(r.cleared.join(', ')) + '' : '') + '']; r.pay.forEach(function (p, i) { cells.push('' + money2(p) + '' + money2(r.bals[i]) + ''); }); cells.push('' + money2(r.paid) + '' + money2(r.interest) + '' + money2(r.remaining) + ''); return '' + cells.join('') + ''; }).join(''); var firstFree = res.payoff.length ? res.payoff[0] : null; var ff = firstFree ? addMonths(sy, smo, firstFree.paidMonth - 1) : null; var stl = monthLabel(sy, smo); var printDebts = '

    Your debts

    ' + debts.map(function (d) { return ''; }).join('') + '
    DebtBalanceInterest rateMinimum payment
    ' + esc(d.name) + '' + money2(d.balance) + '' + d.rate + '%' + money2(d.min) + '

    Extra each month: ' + money2(extra) + '. First payment month: ' + stl + '.

    '; outEl.innerHTML = printDebts + '

    Your two options

    ' + warn + '
    ' + planCard(snow, 'snowball', 'Snowball', 'Smallest balance first. Quick wins you can see.') + planCard(aval, 'avalanche', 'Avalanche', 'Highest interest rate first. Usually the least interest.') + '

    ' + cmp + '

    ' + '

    Your payoff order (' + (state.method === 'avalanche' ? 'avalanche' : 'snowball') + ')

    ' + '

    You put ' + money(res.budget) + ' toward debt every month: your minimums plus ' + money(extra) + ' extra. When a debt is paid off, its payment rolls into the next one, so your monthly amount stays the same while the debts disappear faster.

    ' + (firstFree ? '

    Your first debt is gone in ' + monthLabel(ff[0], ff[1]) + '. When the last one clears, ' + money(res.budget) + ' a month is margin you get back.

    ' : '') + '
      ' + orderHtml + '
    ' + (res.stuck ? '

    With these numbers some debt is still left after 50 years. Try adding a little extra each month.

    ' : '') + '
    ' + '

    Month by month

    ' + '
    ' + '

    ' + '
    ' + heads.map(function (h, i) { return ''; }).join('') + '' + body + '
    Monthly payments and balances for each debt
    ' + esc(h) + '
    ' + '

    This calculator is for education and planning. It assumes fixed interest rates charged monthly, no new charges, and payments made every month, so your real numbers will vary a little. It is not financial advice.

    ' + '

    Want help building this into a plan that runs on its own? Book a free Margin Strategy Call.

    ' + '
    '; document.getElementById('dpc-copy').addEventListener('click', copyTable); document.getElementById('dpc-print').addEventListener('click', function () { var pd = document.getElementById('dpc-printdate'); if (pd) pd.textContent = 'Prepared ' + now.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }) + ' at marginmoneycoaching.com'; window.print(); }); } function copyTable() { if (!lastResult) return; var r = lastResult, lines = []; var h = ['Month']; r.debts.forEach(function (d) { h.push(d.name + ' payment', d.name + ' balance'); }); h.push('Total paid', 'Interest', 'Total remaining', 'Paid off this month'); lines.push(h.join('\t')); r.res.rows.forEach(function (row) { var e = addMonths(r.sy, r.smo, row.month - 1), c = [monthLabel(e[0], e[1], true)]; row.pay.forEach(function (p, i) { c.push(p.toFixed(2), row.bals[i].toFixed(2)); }); c.push(row.paid.toFixed(2), row.interest.toFixed(2), row.remaining.toFixed(2), row.cleared.join(', ')); lines.push(c.join('\t')); }); var text = lines.join('\n'); var status = document.getElementById('dpc-copied'); var btn = document.getElementById('dpc-copy'); function done() { status.textContent = 'Table copied. Paste it into Excel or Google Sheets.'; btn.textContent = 'Copied'; setTimeout(function () { btn.textContent = 'Copy table'; }, 2500); } if (navigator.clipboard && window.isSecureContext) { navigator.clipboard.writeText(text).then(done, fallback); } else fallback(); function fallback() { var ta = document.createElement('textarea'); ta.value = text; ta.setAttribute('readonly', ''); ta.style.position = 'fixed'; ta.style.opacity = '0'; document.body.appendChild(ta); ta.select(); try { document.execCommand('copy'); done(); } catch (e) {} document.body.removeChild(ta); } } /* ---------- events ---------- */ var t; function schedule() { clearTimeout(t); t = setTimeout(function () { save(); compute(); }, 250); } debtsEl.addEventListener('input', function (e) { var row = e.target.closest('.dpc-debt'); if (!row) return; state.debts[+row.getAttribute('data-i')][e.target.getAttribute('data-f')] = e.target.value; schedule(); }); debtsEl.addEventListener('click', function (e) { var b = e.target.closest('[data-rm]'); if (!b) return; var i = +b.getAttribute('data-rm'); state.debts.splice(i, 1); if (!state.debts.length) state.debts.push({ name: '', balance: '', rate: '', min: '' }); renderDebts(); save(); compute(); var next = debtsEl.querySelector('[data-rm="' + Math.min(i, state.debts.length - 1) + '"]'); if (next) next.focus(); }); document.getElementById('dpc-add').addEventListener('click', function () { state.debts.push({ name: '', balance: '', rate: '', min: '' }); renderDebts(); save(); var inp = document.getElementById('dpc-n' + (state.debts.length - 1)); if (inp) inp.focus(); }); extraEl.addEventListener('input', function () { state.extra = extraEl.value; schedule(); }); startEl.addEventListener('input', function () { state.start = startEl.value; schedule(); }); startEl.addEventListener('change', function () { state.start = startEl.value; schedule(); }); document.querySelectorAll('.dpc-toggle button').forEach(function (b) { b.addEventListener('click', function () { state.method = b.getAttribute('data-m'); setMethodButtons(); save(); compute(); }); }); renderDebts(); setMethodButtons(); compute(); if (params.get('access') === '1') { setTimeout(function () { var c = document.getElementById('calculator'); if (c) c.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, 600); } }