/* ── Google Fonts ── */
@import url(‘https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=DM+Sans:wght@400;500;600&display=swap’);
/* ── CSS Variables (palette matching the existing page) ── */
:root {
–green: #2e7d32;
–green-mid: #388e3c;
–green-light:#e8f5e9;
–green-pale: #f1f8f2;
–accent: #1b5e20;
–gold: #f9a825;
–text: #1a1a1a;
–muted: #555;
–border: #d0e4d1;
–card-bg: #ffffff;
–radius: 12px;
–shadow: 0 2px 14px rgba(0,0,0,.08);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: ‘DM Sans’, sans-serif;
background: var(–green-pale);
color: var(–text);
line-height: 1.6;
}
/* ── Wrapper ── */
.calc-wrapper {
max-width: 860px;
margin: 0 auto;
padding: 28px 16px 48px;
}
/* ── Hero header ── */
.calc-hero {
text-align: center;
margin-bottom: 28px;
}
.calc-hero .badge {
display: inline-block;
background: var(–green);
color: #fff;
font-size: .72rem;
font-weight: 600;
letter-spacing: .08em;
text-transform: uppercase;
padding: 4px 14px;
border-radius: 20px;
margin-bottom: 12px;
}
.calc-hero h1 {
font-family: ‘Playfair Display’, serif;
font-size: clamp(1.7rem, 4vw, 2.4rem);
color: var(–accent);
line-height: 1.2;
margin-bottom: 10px;
}
.calc-hero h1 em { font-style: italic; color: var(–green-mid); }
.calc-hero p {
color: var(–muted);
font-size: .97rem;
max-width: 520px;
margin: 0 auto;
}
/* ── Two-column layout ── */
.calc-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media(max-width: 660px) {
.calc-grid { grid-template-columns: 1fr; }
}
/* ── Card ── */
.card {
background: var(–card-bg);
border: 1px solid var(–border);
border-radius: var(–radius);
padding: 22px 20px;
box-shadow: var(–shadow);
}
.card-title {
font-size: .78rem;
font-weight: 600;
letter-spacing: .07em;
text-transform: uppercase;
color: var(–green);
margin-bottom: 18px;
padding-bottom: 10px;
border-bottom: 2px solid var(–green-light);
}
/* ── Field ── */
.field { margin-bottom: 16px; }
.field label {
display: block;
font-size: .88rem;
font-weight: 500;
color: var(–text);
margin-bottom: 6px;
}
.field label span {
font-weight: 400;
color: var(–muted);
font-size: .82rem;
}
.input-wrap {
position: relative;
}
.input-wrap .sym {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
font-size: .95rem;
color: var(–muted);
pointer-events: none;
}
.input-wrap .suf {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
font-size: .95rem;
color: var(–muted);
pointer-events: none;
}
.input-wrap input {
width: 100%;
padding: 10px 36px 10px 30px;
border: 1.5px solid var(–border);
border-radius: 8px;
font-size: .97rem;
font-family: ‘DM Sans’, sans-serif;
color: var(–text);
background: #fff;
transition: border-color .2s;
-moz-appearance: textfield;
}
.input-wrap input::-webkit-outer-spin-button,
.input-wrap input::-webkit-inner-spin-button { -webkit-appearance: none; }
.input-wrap input:focus {
outline: none;
border-color: var(–green);
}
.input-wrap.no-sym input { padding-left: 12px; }
/* ── Slider ── */
.slider-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 8px;
}
.slider-row input[type=range] {
flex: 1;
-webkit-appearance: none;
height: 5px;
border-radius: 3px;
background: linear-gradient(to right, var(–green) 0%, var(–green) var(–pct,50%), var(–border) var(–pct,50%), var(–border) 100%);
cursor: pointer;
}
.slider-row input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px; height: 18px;
border-radius: 50%;
background: var(–green);
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,.25);
}
.slider-val {
min-width: 44px;
text-align: right;
font-size: .9rem;
font-weight: 600;
color: var(–accent);
}
/* ── Compounding select ── */
select {
width: 100%;
padding: 10px 12px;
border: 1.5px solid var(–border);
border-radius: 8px;
font-size: .97rem;
font-family: ‘DM Sans’, sans-serif;
color: var(–text);
background: #fff;
cursor: pointer;
}
select:focus { outline: none; border-color: var(–green); }
/* ── CTA button ── */
.btn-calc {
width: 100%;
margin-top: 8px;
padding: 14px;
background: var(–green);
color: #fff;
border: none;
border-radius: 9px;
font-family: ‘DM Sans’, sans-serif;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
letter-spacing: .02em;
transition: background .2s, transform .1s;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-calc:hover { background: var(–accent); }
.btn-calc:active { transform: scale(.98); }
.btn-calc svg { flex-shrink: 0; }
/* ── Results ── */
#results { display: none; }
#results.visible { display: block; }
.results-banner {
background: linear-gradient(135deg, var(–green) 0%, var(–accent) 100%);
border-radius: var(–radius);
padding: 22px 24px;
color: #fff;
margin-bottom: 18px;
position: relative;
overflow: hidden;
}
.results-banner::after {
content: ‘💰’;
position: absolute;
right: 20px; top: 50%;
transform: translateY(-50%);
font-size: 3rem;
opacity: .18;
}
.results-banner .label {
font-size: .8rem;
text-transform: uppercase;
letter-spacing: .08em;
opacity: .85;
margin-bottom: 4px;
}
.results-banner .big {
font-family: ‘Playfair Display’, serif;
font-size: clamp(1.9rem, 5vw, 2.7rem);
font-weight: 700;
line-height: 1;
}
.results-banner .sub {
font-size: .86rem;
opacity: .8;
margin-top: 6px;
}
/* ── Breakdown grid ── */
.breakdown-grid {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 12px;
margin-bottom: 18px;
}
@media(max-width:520px){ .breakdown-grid { grid-template-columns: 1fr 1fr; } }
.bk-item {
background: #fff;
border: 1px solid var(–border);
border-radius: 10px;
padding: 14px 14px 12px;
box-shadow: var(–shadow);
}
.bk-item .bk-label {
font-size: .75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: .07em;
color: var(–muted);
margin-bottom: 4px;
}
.bk-item .bk-val {
font-size: 1.15rem;
font-weight: 700;
color: var(–accent);
}
.bk-item .bk-val.gold { color: var(–gold); }
/* ── Donut chart (pure CSS) ── */
.chart-section {
display: grid;
grid-template-columns: 220px 1fr;
gap: 20px;
align-items: center;
background: #fff;
border: 1px solid var(–border);
border-radius: var(–radius);
padding: 20px;
box-shadow: var(–shadow);
margin-bottom: 18px;
}
@media(max-width:540px){ .chart-section { grid-template-columns: 1fr; } }
.donut-wrap {
position: relative;
width: 160px;
height: 160px;
margin: 0 auto;
}
.donut-wrap svg { width:100%; height:100%; transform:rotate(-90deg); }
.donut-wrap .donut-center {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: .72rem;
color: var(–muted);
text-transform: uppercase;
letter-spacing: .05em;
text-align: center;
}
.donut-wrap .donut-center strong {
font-size: 1.05rem;
color: var(–accent);
display: block;
line-height: 1.2;
}
.legend { list-style: none; }
.legend li {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
font-size: .92rem;
}
.legend .dot {
width: 13px; height: 13px;
border-radius: 3px;
flex-shrink: 0;
}
.legend .val { font-weight: 700; margin-left: auto; }
/* ── Bar chart (canvas) ── */
.bar-section {
background: #fff;
border: 1px solid var(–border);
border-radius: var(–radius);
padding: 20px;
box-shadow: var(–shadow);
margin-bottom: 18px;
}
.bar-section h3 {
font-size: .9rem;
font-weight: 600;
color: var(–accent);
margin-bottom: 14px;
}
#growthCanvas { width: 100%; display: block; }
/* ── Scenarios ── */
.scenarios {
display: grid;
grid-template-columns: repeat(3,1fr);
gap: 12px;
margin-bottom: 18px;
}
@media(max-width:520px){ .scenarios { grid-template-columns: 1fr; } }
.sc-card {
border-radius: 10px;
padding: 14px;
text-align: center;
border: 1.5px solid var(–border);
background: #fff;
}
.sc-card.highlight {
border-color: var(–green);
background: var(–green-light);
}
.sc-card .sc-label { font-size: .8rem; color: var(–muted); margin-bottom: 4px; }
.sc-card .sc-val { font-size: 1.1rem; font-weight: 700; color: var(–accent); }
.sc-card .sc-sub { font-size: .78rem; color: var(–muted); margin-top: 2px; }
/* ── Next step / crosslink ── */
.next-step {
background: var(–green-light);
border: 1px solid var(–border);
border-left: 4px solid var(–green);
border-radius: var(–radius);
padding: 16px 18px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 18px;
flex-wrap: wrap;
}
.next-step p { font-size: .92rem; color: var(–text); font-weight: 500; }
.next-step a {
background: var(–green);
color: #fff;
padding: 8px 16px;
border-radius: 7px;
font-size: .88rem;
font-weight: 600;
text-decoration: none;
white-space: nowrap;
transition: background .2s;
}
.next-step a:hover { background: var(–accent); }
/* ── Share buttons ── */
.share-row {
display: flex;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 28px;
}
.share-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 9px 16px;
border-radius: 8px;
font-size: .85rem;
font-weight: 600;
cursor: pointer;
border: 1.5px solid var(–border);
background: #fff;
color: var(–text);
transition: background .15s;
}
.share-btn:hover { background: var(–green-light); }
.share-btn.copied { color: var(–green); border-color: var(–green); }
/* ── Disclaimer ── */
.disclaimer {
font-size: .8rem;
color: var(–muted);
border-top: 1px solid var(–border);
padding-top: 14px;
line-height: 1.5;
}
/* ── Formula box ── */
.formula-box {
background: var(–green-pale);
border: 1px solid var(–border);
border-radius: 10px;
padding: 16px;
margin-bottom: 18px;
font-size: .85rem;
color: var(–muted);
}
.formula-box strong { color: var(–accent); }
.formula-box code {
display: block;
margin: 8px 0;
font-size: .9rem;
background: #fff;
padding: 8px 12px;
border-radius: 6px;
border: 1px solid var(–border);
color: var(–text);
font-family: monospace;
}
Monthly Savings Calculator
Enter your savings details and see exactly how your money grows over time — with interest working for you every month.
%
Monthly
Quarterly
Semi-annually
Annually
Daily
No adjustment
~3% (typical)
~5% (high)
~2% (low)
-
Contributions (initial + monthly)
$0 -
Interest earned
$0
FV = I×(1+r/n)^(n×t) + P × [((1+r/n)^(n×t) - 1) / (r/n)]Where I = initial deposit, P = monthly deposit, r = APY as decimal, n = compounding periods/year, t = years.
/* ── Helpers ── */
const fmt = v => ‘$’ + Math.round(v).toLocaleString(‘en-US’);
const pct = v => (v * 100).toFixed(1) + ‘%’;
/* ── Sync sliders ── */
function syncSlider(inputId, sliderId, displayId, suffix) {
const inp = document.getElementById(inputId);
const sl = document.getElementById(sliderId);
const dp = document.getElementById(displayId);
const upd = () => {
sl.value = inp.value;
dp.textContent = inp.value + suffix;
updateSliderFill(sl);
};
inp.addEventListener(‘input’, upd);
sl.addEventListener(‘input’, () => {
inp.value = sl.value;
dp.textContent = sl.value + suffix;
updateSliderFill(sl);
});
updateSliderFill(sl);
}
function updateSliderFill(sl) {
const min = +sl.min, max = +sl.max, val = +sl.value;
const p = ((val – min) / (max – min) * 100).toFixed(1);
sl.style.setProperty(‘–pct’, p + ‘%’);
}
syncSlider(‘apy’, ‘apySlider’, ‘apyDisplay’, ‘%’);
syncSlider(‘years’, ‘yearsSlider’, ‘yearsDisplay’, ‘ yrs’);
/* ── Core calculation ── */
function calcFV(initial, monthly, apyDec, years, n) {
const t = years;
const r = apyDec;
const fvInitial = initial * Math.pow(1 + r / n, n * t);
const fvMonthly = monthly * ((Math.pow(1 + r / n, n * t) – 1) / (r / n));
return fvInitial + fvMonthly;
}
function calcFVNoInterest(initial, monthly, years) {
return initial + monthly * 12 * years;
}
/* ── Main calculate ── */
function calculate() {
const initial = +document.getElementById(‘initial’).value || 0;
const monthly = +document.getElementById(‘monthly’).value || 0;
const apy = +document.getElementById(‘apy’).value / 100;
const years = +document.getElementById(‘years’).value || 1;
const n = +document.getElementById(‘compound’).value;
const inf = +document.getElementById(‘inflation’).value;
// nominal future value
let fv;
if (apy === 0) {
fv = calcFVNoInterest(initial, monthly, years);
} else {
fv = calcFV(initial, monthly, apy, years, n);
}
const totalContrib = initial + monthly * 12 * years;
const interest = fv – totalContrib;
// inflation-adjusted
const realFV = fv / Math.pow(1 + inf, years);
// ── Display banner ──
document.getElementById(‘rYears’).textContent = years;
document.getElementById(‘rTotal’).textContent = fmt(fv);
document.getElementById(‘rInitial’).textContent = fmt(initial);
document.getElementById(‘rContribs’).textContent = fmt(totalContrib);
document.getElementById(‘rInterest’).textContent = fmt(interest);
document.getElementById(‘lContribs’).textContent = fmt(totalContrib);
document.getElementById(‘lInterest’).textContent = fmt(interest);
if (inf > 0) {
document.getElementById(‘rInflationNote’).textContent =
‘Inflation-adjusted value: ‘ + fmt(realFV) + ‘ (in today’s dollars)’;
} else {
document.getElementById(‘rInflationNote’).textContent = ”;
}
// ── Donut chart ──
const intPct = fv > 0 ? (interest / fv) : 0;
const contribPct = 1 – intPct;
const circ = 100;
const contribLen = contribPct * circ;
const interestLen = intPct * circ;
const dc = document.getElementById(‘donutContrib’);
const di = document.getElementById(‘donutInterest’);
dc.setAttribute(‘stroke-dasharray’, contribLen.toFixed(2) + ‘ ‘ + (circ – contribLen).toFixed(2));
di.setAttribute(‘stroke-dasharray’, interestLen.toFixed(2) + ‘ ‘ + (circ – interestLen).toFixed(2));
// offset interest so it starts after contrib arc
di.setAttribute(‘stroke-dashoffset’, -(contribLen).toFixed(2));
document.getElementById(‘donutPct’).textContent = (intPct * 100).toFixed(1) + ‘%’;
// ── Bar chart ──
drawBarChart(initial, monthly, apy, n, years);
// ── Scenarios ──
const lo = apy === 0
? calcFVNoInterest(initial, monthly * 0.5, years)
: calcFV(initial, monthly * 0.5, apy, years, n);
const hi = apy === 0
? calcFVNoInterest(initial, monthly * 1.5, years)
: calcFV(initial, monthly * 1.5, apy, years, n);
const loInt = lo – (initial + monthly * 0.5 * 12 * years);
const hiInt = hi – (initial + monthly * 1.5 * 12 * years);
document.getElementById(‘scLow’).textContent = fmt(lo);
document.getElementById(‘scLowInt’).textContent = fmt(monthly * 0.5) + ‘/mo · ‘ + fmt(loInt) + ‘ interest’;
document.getElementById(‘scMid’).textContent = fmt(fv);
document.getElementById(‘scMidInt’).textContent = fmt(monthly) + ‘/mo · ‘ + fmt(interest) + ‘ interest’;
document.getElementById(‘scHigh’).textContent = fmt(hi);
document.getElementById(‘scHighInt’).textContent = fmt(monthly * 1.5) + ‘/mo · ‘ + fmt(hiInt) + ‘ interest’;
// ── Show results ──
const res = document.getElementById(‘results’);
res.classList.add(‘visible’);
res.scrollIntoView({ behavior: ‘smooth’, block: ‘start’ });
}
/* ── Bar chart (canvas, no library) ── */
function drawBarChart(initial, monthly, apy, n, totalYears) {
const canvas = document.getElementById(‘growthCanvas’);
const W = canvas.offsetWidth || 600;
const H = 220;
canvas.width = W * window.devicePixelRatio;
canvas.height = H * window.devicePixelRatio;
canvas.style.height = H + ‘px’;
const ctx = canvas.getContext(‘2d’);
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
// Build data points
const points = [];
for (let y = 0; y p.fv));
const PAD_L = 64, PAD_R = 18, PAD_T = 14, PAD_B = 34;
const chartW = W – PAD_L – PAD_R;
const chartH = H – PAD_T – PAD_B;
const barW = Math.max(6, (chartW / points.length) * 0.62);
ctx.clearRect(0, 0, W, H);
// Grid lines
ctx.strokeStyle = ‘#e8f0e9’;
ctx.lineWidth = 1;
for (let i = 0; i {
const x = PAD_L + (i / (points.length – 1 || 1)) * chartW – barW / 2;
const fvH = (pt.fv / maxVal) * chartH;
const cH = (pt.contrib / maxVal) * chartH;
const intH = fvH – cH;
// contribution part (green)
ctx.fillStyle = ‘#2e7d32’;
ctx.fillRect(x, PAD_T + chartH – cH, barW, cH);
// interest part (gold)
ctx.fillStyle = ‘#f9a825’;
ctx.fillRect(x, PAD_T + chartH – fvH, barW, intH);
// x-axis label
if (i % Math.max(1, Math.floor(points.length / 8)) === 0 || i === points.length – 1) {
ctx.fillStyle = ‘#666′;
ctx.font = ’10px DM Sans, sans-serif’;
ctx.textAlign = ‘center’;
ctx.fillText(‘Yr ‘ + i, x + barW / 2, H – PAD_B + 14);
}
});
// Legend
ctx.fillStyle = ‘#2e7d32’;
ctx.fillRect(PAD_L, H – 10, 12, 8);
ctx.fillStyle = ‘#555′;
ctx.font = ’10px DM Sans, sans-serif’;
ctx.textAlign = ‘left’;
ctx.fillText(‘Contributions’, PAD_L + 16, H – 3);
ctx.fillStyle = ‘#f9a825’;
ctx.fillRect(PAD_L + 110, H – 10, 12, 8);
ctx.fillStyle = ‘#555’;
ctx.fillText(‘Interest’, PAD_L + 126, H – 3);
}
/* ── Copy link ── */
function copyLink(btn) {
navigator.clipboard.writeText(window.location.href).then(() => {
btn.textContent = ‘✓ Copied!’;
btn.classList.add(‘copied’);
setTimeout(() => {
btn.innerHTML = ‘ Copy link’;
btn.classList.remove(‘copied’);
}, 2000);
});
}
/* ── Auto-calculate on load ── */
window.addEventListener(‘load’, calculate);
window.addEventListener(‘resize’, () => {
const res = document.getElementById(‘results’);
if (res.classList.contains(‘visible’)) calculate();
});
