Auto Refinance Calculator
@import url('https://fonts.googleapis.com/css2?family=Roboto+Slab:wght@500;600&family=Montserrat:wght@400;500;600;700&display=swap');
:root{
--ink:#14213D;
--ink-soft:#1E2E52;
--cream:#FBF7EE;
--paper:#F3EEE1;
--amber:#85c045;
--green:#658f43;
--rust:#c8592d;
--slate:#5B6472;
--line:rgba(20,33,61,0.14);
--font-display:'Rockwell','Rockwell Nova','Roboto Slab',Georgia,serif;
--font-body:'Gotham','Gotham SSm A','Gotham SSm B',Montserrat,'Century Gothic',sans-serif;
}
.arc-widget, .arc-widget *{box-sizing:border-box;}
.arc-widget{
color:var(--ink);
font-family:var(--font-body);
-webkit-font-smoothing:antialiased;
padding:48px 20px 80px;
}
.wrap{max-width:920px;margin:0 auto;}
/* ---------- Lanes ---------- */
.lanes{
position:relative;
display:grid;
grid-template-columns:1fr 1fr;
gap:0;
background:var(--ink);
border-radius:18px;
overflow:hidden;
box-shadow:0 24px 60px -30px rgba(20,33,61,0.55);
}
.lane{
padding:30px 30px 34px;
color:var(--ink);
}
.lane.current{background:var(--paper);}
.lane.refi{background:var(--paper);}
.laneline{
position:absolute;
top:0; bottom:0; left:50%;
width:2px;
transform:translateX(-1px);
background-image:repeating-linear-gradient(to bottom, var(--amber) 0 14px, transparent 14px 28px);
opacity:0.65;
z-index:2;
}
.lane-label{
font-family:var(--font-display);
font-size:17px;
letter-spacing:0.02em;
text-transform:none;
color:var(--amber);
font-weight:600;
margin-bottom:18px;
display:flex;
align-items:center;
gap:8px;
}
.lane-label .dot{
width:6px;height:6px;border-radius:50%;
background:var(--amber);
display:inline-block;
}
.lane.current .lane-label{color:var(--rust);}
.lane.current .lane-label .dot{background:var(--rust);}
.lane.refi .lane-label{color:#698433;}
.lane.refi .lane-label .dot{background:#698433;}
.field{margin-bottom:20px;}
.field:last-child{margin-bottom:0;}
.field label{
display:block;
font-size:12.5px;
color:var(--slate);
margin-bottom:7px;
font-weight:500;
}
.field .inputrow{
display:flex;
align-items:baseline;
border-bottom:1px solid var(--line);
padding-bottom:6px;
transition:border-color .15s ease;
}
.field .inputrow:focus-within{border-color:var(--amber);}
.field .prefix, .field .suffix{
font-family:var(--font-body);
font-size:15px;
color:var(--slate);
}
.field input[type="number"]{
background:transparent;
border:none;
outline:none;
color:var(--ink);
font-family:var(--font-body);
font-size:22px;
font-weight:600;
width:100%;
padding:2px 6px;
-moz-appearance:textfield;
}
.field input[type="number"]::-webkit-outer-spin-button,
.field input[type="number"]::-webkit-inner-spin-button{
-webkit-appearance:none; margin:0;
}
.field input[type="range"]{
width:100%;
margin-top:10px;
accent-color:var(--amber);
cursor:pointer;
}
.lane.current .field input[type="range"]{accent-color:var(--rust);}
.lane.current .field .inputrow:focus-within{border-color:var(--rust);}
/* ---------- Readout / dashboard ---------- */
.readout{
margin-top:36px;
background:var(--paper);
border-radius:18px;
padding:32px 32px 28px;
border:1px solid var(--line);
}
.readout-head{
display:flex;
justify-content:space-between;
align-items:flex-end;
margin-bottom:22px;
gap:16px;
flex-wrap:wrap;
}
.readout-head h2{
font-family:var(--font-display);
font-weight:500;
font-size:21px;
margin:0;
}
.badge{
font-family:var(--font-body);
font-size:12px;
font-weight:600;
letter-spacing:0.02em;
padding:6px 12px;
border-radius:100px;
white-space:nowrap;
}
.badge.up{background:rgba(47,110,79,0.12); color:var(--green);}
.badge.down{background:rgba(176,65,62,0.12); color:var(--rust);}
.gauges{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:22px;
margin-bottom:26px;
}
.gauge{
background:var(--cream);
border-radius:12px;
padding:18px 18px 16px;
border:1px solid var(--line);
}
.gauge .g-label{
font-size:11.5px;
color:var(--slate);
text-transform:uppercase;
letter-spacing:0.08em;
font-weight:600;
margin-bottom:8px;
}
.gauge .g-value{
font-family:var(--font-body);
font-size:clamp(22px,3vw,30px);
font-weight:700;
line-height:1;
color:var(--ink);
}
.gauge .g-value.pos{color:var(--green);}
.gauge .g-value.neg{color:var(--rust);}
#curPaymentOut{color:var(--rust);}
#newPaymentOut{color:var(--green);}
.gauge .g-note{
font-size:12px;
color:var(--slate);
margin-top:6px;
}
.bar-row{margin-bottom:8px;}
.bar-row .bar-labels{
display:flex; justify-content:space-between;
font-size:12.5px; color:var(--slate); margin-bottom:6px;
}
#curInterestLabel{color:var(--rust); font-weight:600;}
#newInterestLabel{color:var(--green); font-weight:600;}
.bar-track{
height:10px;
background:rgba(20,33,61,0.08);
border-radius:100px;
overflow:hidden;
display:flex;
}
.bar-fill-current{background:var(--rust); height:100%;}
.bar-fill-new{background:var(--green); height:100%;}
.foot-note{
margin-top:22px;
font-size:12.5px;
color:var(--slate);
line-height:1.6;
border-top:1px solid var(--line);
padding-top:16px;
}
@media (max-width:640px){
.lanes{grid-template-columns:1fr;}
.laneline{
left:0; right:0; top:50%; bottom:auto; width:auto; height:2px;
transform:translateY(-1px);
background-image:repeating-linear-gradient(to right, var(--amber) 0 14px, transparent 14px 28px);
}
.gauges{grid-template-columns:1fr;}
}
function $(id){return document.getElementById(id);}
function monthlyPayment(principal, annualRatePct, months){
const r = (annualRatePct/100)/12;
if(months {
const num = $(numId), range = $(rangeId);
const allowDecimal = decimalFields.includes(numId);
num.addEventListener('input', ()=>{
const cursorFromEnd = num.value.length - num.selectionStart;
let raw, formatted;
if(allowDecimal){
raw = num.value.replace(/[^0-9.]/g,'');
const firstDot = raw.indexOf('.');
if(firstDot !== -1){
raw = raw.slice(0, firstDot+1) + raw.slice(firstDot+1).replace(/\./g,'');
}
const [intPart, decPart] = raw.split('.');
const intFormatted = formatIntWithCommas(intPart || '');
formatted = decPart !== undefined ? intFormatted + '.' + decPart : intFormatted;
} else {
raw = num.value.replace(/[^0-9]/g,'');
formatted = formatIntWithCommas(raw);
}
num.value = formatted;
const pos = Math.max(formatted.length - cursorFromEnd, 0);
num.setSelectionRange(pos,pos);
range.value = parseNum(raw) || 0;
calculate();
});
range.addEventListener('input', ()=>{
num.value = allowDecimal
? String(range.value)
: Number(range.value).toLocaleString('en-US');
calculate();
});
});
function parseNum(str){
return parseFloat(String(str).replace(/,/g,'')) || 0;
}
function calculate(){
const curBalance = parseNum($('curBalance').value);
const curRate = parseNum($('curRate').value);
const curTerm = parseNum($('curTerm').value) || 1;
const newRate = parseNum($('newRate').value);
const newTerm = parseNum($('newTerm').value) || 1;
const fees = parseNum($('fees').value);
const newPrincipal = curBalance + fees;
const curPayment = monthlyPayment(curBalance, curRate, curTerm);
const newPayment = monthlyPayment(newPrincipal, newRate, newTerm);
const curTotalPaid = curPayment * curTerm;
const newTotalPaid = newPayment * newTerm;
const curInterest = Math.max(curTotalPaid - curBalance, 0);
const newInterest = Math.max(newTotalPaid - newPrincipal, 0);
const monthlyDiff = curPayment - newPayment; // positive = savings
const lifetimeDiff = curTotalPaid - newTotalPaid; // positive = savings
$('curPaymentOut').textContent = fmt(curPayment);
$('newPaymentOut').textContent = fmt(newPayment);
const diffEl = $('monthlyDiffOut');
diffEl.textContent = (monthlyDiff >= 0 ? '−' : '+') + fmt(Math.abs(monthlyDiff)).replace('$','$');
diffEl.className = 'g-value ' + (monthlyDiff >= 0 ? 'pos' : 'neg');
$('monthlyDiffNote').textContent = monthlyDiff >= 0 ? 'less per month' : 'more per month';
$('curInterestLabel').textContent = fmt(curInterest);
$('newInterestLabel').textContent = fmt(newInterest);
const maxInterest = Math.max(curInterest, newInterest, 1);
$('barCurrent').style.width = (curInterest / maxInterest * 100) + '%';
$('barNew').style.width = (newInterest / maxInterest * 100) + '%';
const badge = $('headlineBadge');
if(lifetimeDiff >= 0){
badge.textContent = 'Saves ' + fmt(Math.abs(lifetimeDiff)) + ' over the loan';
badge.className = 'badge up';
} else {
badge.textContent = 'Costs ' + fmt(Math.abs(lifetimeDiff)) + ' more over the loan';
badge.className = 'badge down';
}
}
calculate();