Files
SCD-Bond-Calculator/calc.html
kawaiipunk d2c3f981fd Fix simple-at-maturity payout bug and minor issues
- Fix: simple-at-maturity final payout now includes total interest
  (was only paying one year's interest due to dead ternary)
- Fix: add type='button' to prevent form submission in WordPress
- Fix: add radix 10 to parseInt
- Fix: remove misleading tilde on exact average income calculation
2026-06-15 00:31:35 +01:00

337 lines
21 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!-- =================================================================== -->
<!-- SCD Bond Calculator — WordPress Custom HTML Block -->
<!-- Self-contained widget for Stirchley Community Development's -->
<!-- bond investment calculator. All styles are inline to remain -->
<!-- portable across WordPress theme environments. -->
<!-- =================================================================== -->
<div id="scd-calc" style="max-width:640px;margin:0 auto;padding:28px 24px;font-family:inherit;color:#2d2d2d;line-height:1.7;">
<!-- Heading -->
<h3 style="margin-bottom:12px;font-size:22px;">🧮 Estimate Your Bond Return</h3>
<!-- Mandatory risk disclaimer — required for FCA compliance -->
<p style="margin-bottom:28px;font-size:16px;line-height:1.7;color:#2d2d2d;"><em>Illustrative only — not financial advice. Bonds are not FSCS-protected. You may lose some or all of your investment.</em></p>
<!-- =============================================================== -->
<!-- INPUT FIELDS -->
<!-- Three core parameters: amount, interest rate, term. Each has -->
<!-- realistic min/max constraints enforced by the HTML attributes -->
<!-- as well as by JS validation in run(). -->
<!-- =============================================================== -->
<div style="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:32px;">
<div style="flex:1;min-width:180px;">
<label for="scd-amt" style="font-size:16px;font-weight:600;display:block;margin-bottom:8px;color:#2d2d2d;">💰 Amount (£)</label>
<input type="number" id="scd-amt" min="20000" max="10000000" step="1000" value="75000"
style="width:100%;padding:12px 14px;border:2px solid #9a9a9a;border-radius:8px;font-size:18px;font-family:monospace;background:#fff;box-sizing:border-box;color:#2d2d2d;" />
<span style="font-size:14px;color:#595959;display:block;margin-top:6px;">Min £20,000</span>
</div>
<div style="flex:1;min-width:140px;">
<label for="scd-rate" style="font-size:16px;font-weight:600;display:block;margin-bottom:8px;color:#2d2d2d;">💹 Rate (%)</label>
<input type="number" id="scd-rate" min="0" max="3.5" step="0.25" value="2.75"
style="width:100%;padding:12px 14px;border:2px solid #9a9a9a;border-radius:8px;font-size:18px;font-family:monospace;background:#fff;box-sizing:border-box;color:#2d2d2d;" />
<span style="font-size:14px;color:#595959;display:block;margin-top:6px;">0 3.5%</span>
</div>
<div style="flex:1;min-width:120px;">
<label for="scd-term" style="font-size:16px;font-weight:600;display:block;margin-bottom:8px;color:#2d2d2d;">📅 Term (yrs)</label>
<input type="number" id="scd-term" min="5" max="30" step="1" value="10"
style="width:100%;padding:12px 14px;border:2px solid #9a9a9a;border-radius:8px;font-size:18px;font-family:monospace;background:#fff;box-sizing:border-box;color:#2d2d2d;" />
<span style="font-size:14px;color:#595959;display:block;margin-top:6px;">Min 5</span>
</div>
</div>
<!-- =============================================================== -->
<!-- INTEREST PAYOUT METHOD SELECTOR -->
<!-- Four radio buttons covering the two binary choices: -->
<!-- Compound (cm/ca) vs Simple (sm/sa) -->
<!-- At maturity (cm/sm) vs Paid yearly (ca/sa) -->
<!-- Values are two-letter codes: first letter = type (c/s), -->
<!-- second letter = schedule (m/a). -->
<!-- =============================================================== -->
<p style="font-size:16px;font-weight:600;margin-bottom:12px;color:#2d2d2d;">📈 Interest payout</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:36px;">
<label style="cursor:pointer;padding:12px 18px;border:2px solid #9a9a9a;border-radius:8px;font-size:16px;background:#fff;display:flex;align-items:center;gap:8px;color:#2d2d2d;">
<input type="radio" name="scd-m" value="cm" checked style="accent-color:#e66d00;width:20px;height:20px;flex-shrink:0;" />
<span>Compound at maturity</span>
</label>
<label style="cursor:pointer;padding:12px 18px;border:2px solid #9a9a9a;border-radius:8px;font-size:16px;background:#fff;display:flex;align-items:center;gap:8px;color:#2d2d2d;">
<input type="radio" name="scd-m" value="ca" style="accent-color:#e66d00;width:20px;height:20px;flex-shrink:0;" />
<span>Compound paid yearly</span>
</label>
<label style="cursor:pointer;padding:12px 18px;border:2px solid #9a9a9a;border-radius:8px;font-size:16px;background:#fff;display:flex;align-items:center;gap:8px;color:#2d2d2d;">
<input type="radio" name="scd-m" value="sm" style="accent-color:#e66d00;width:20px;height:20px;flex-shrink:0;" />
<span>Simple at maturity</span>
</label>
<label style="cursor:pointer;padding:12px 18px;border:2px solid #9a9a9a;border-radius:8px;font-size:16px;background:#fff;display:flex;align-items:center;gap:8px;color:#2d2d2d;">
<input type="radio" name="scd-m" value="sa" style="accent-color:#e66d00;width:20px;height:20px;flex-shrink:0;" />
<span>Simple paid yearly</span>
</label>
</div>
<!-- =============================================================== -->
<!-- CALCULATE BUTTON -->
<!-- Triggers the run() function which validates, computes, and -->
<!-- renders the results. -->
<!-- =============================================================== -->
<button type="button" id="scd-btn"
style="display:block;width:100%;padding:18px;background:#e66d00;color:#fff;font-size:20px;font-weight:700;border:none;border-radius:10px;cursor:pointer;margin-bottom:8px;letter-spacing:0.3px;">
Calculate
</button>
<!-- =============================================================== -->
<!-- ERROR MESSAGE CONTAINER -->
<!-- Hidden by default; shown by run() when validation fails. -->
<!-- Uses warm orange/red palette to draw attention. -->
<!-- =============================================================== -->
<div id="scd-err" style="display:none;margin-top:20px;padding:16px 20px;background:#fff8f0;border:2px solid #c45500;border-radius:8px;font-size:16px;color:#3d1f00;line-height:1.7;"></div>
<!-- =============================================================== -->
<!-- RESULTS SECTION -->
<!-- Hidden by default; revealed after a successful calculation. -->
<!-- Contains: key-figure cards, security tier, tax note, -->
<!-- year-by-year breakdown table, and contextual footnotes. -->
<!-- =============================================================== -->
<div id="scd-out" style="display:none;">
<hr style="margin:36px 0;border:none;border-top:2px solid #d9d5cc;" />
<h4 style="margin-bottom:20px;font-size:20px;color:#2d2d2d;">📊 Your Estimated Return</h4>
<!-- Key figures -->
<div style="display:grid;grid-template-columns:1fr;gap:14px;margin-bottom:24px;">
<div style="padding:20px 24px;background:#fff8f0;border-left:4px solid #e66d00;border-radius:10px;text-align:center;">
<div style="font-size:14px;color:#6b3500;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;">Total Repayment</div>
<div id="scd-r-total" style="font-size:30px;font-weight:700;color:#5a2c00;margin-top:8px;"></div>
</div>
<div style="padding:20px 24px;background:#fff8f0;border-left:4px solid #e66d00;border-radius:10px;text-align:center;">
<div style="font-size:14px;color:#6b3500;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;">Total Interest</div>
<div id="scd-r-interest" style="font-size:30px;font-weight:700;color:#5a2c00;margin-top:8px;"></div>
</div>
<div style="padding:20px 24px;background:#fff8f0;border-left:4px solid #e66d00;border-radius:10px;text-align:center;">
<div style="font-size:14px;color:#6b3500;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;">Per-Year Income</div>
<div id="scd-r-annual" style="font-size:30px;font-weight:700;color:#5a2c00;margin-top:8px;"></div>
</div>
<div style="padding:20px 24px;background:#eaf2ec;border-left:4px solid #2d6a4f;border-radius:10px;text-align:center;">
<div style="font-size:14px;color:#1a3c2e;font-weight:700;text-transform:uppercase;letter-spacing:0.5px;">Effective Rate</div>
<div id="scd-r-eff" style="font-size:30px;font-weight:700;color:#1a3c2e;margin-top:8px;"></div>
</div>
</div>
<!-- Security -->
<div id="scd-r-sec" style="margin:24px 0;padding:16px 20px;border-radius:8px;font-size:16px;line-height:1.7;color:#2d2d2d;">
<strong>🔒 Security:</strong> <span id="scd-r-sec-text"></span>
</div>
<!-- UK Tax note -->
<blockquote style="margin:24px 0;padding:16px 20px;background:#f5f5f0;border-left:4px solid #e66d00;font-size:15px;color:#595959;line-height:1.7;">
<strong style="color:#2d2d2d;">🇬🇧 UK tax:</strong> Interest is your responsibility to report. SCD notifies HMRC if any investor receives &gt;£250 interest in a year. <span id="scd-r-tax"></span>
</blockquote>
<!-- Year-by-year -->
<details style="margin:28px 0;font-size:16px;color:#2d2d2d;">
<summary style="cursor:pointer;font-weight:700;color:#6b3500;padding:10px 0;font-size:16px;">▾ Year-by-year breakdown</summary>
<div style="overflow-x:auto;margin-top:14px;-webkit-overflow-scrolling:touch;">
<table style="width:100%;border-collapse:collapse;font-size:15px;min-width:400px;">
<thead>
<tr style="background:#f5f5f0;">
<th style="padding:12px 14px;text-align:left;border-bottom:2px solid #9a9a9a;color:#2d2d2d;">Year</th>
<th style="padding:12px 14px;text-align:right;border-bottom:2px solid #9a9a9a;color:#2d2d2d;">Balance</th>
<th style="padding:12px 14px;text-align:right;border-bottom:2px solid #9a9a9a;color:#2d2d2d;">Interest</th>
<th style="padding:12px 14px;text-align:right;border-bottom:2px solid #9a9a9a;color:#2d2d2d;">Paid Out</th>
</tr>
</thead>
<tbody id="scd-r-table"></tbody>
</table>
</div>
</details>
<!-- Context -->
<hr style="margin:32px 0;border:none;border-top:2px solid #d9d5cc;" />
<div style="font-size:15px;color:#595959;line-height:1.8;">
<p style="margin-bottom:8px;">🏠 At <strong style="color:#2d2d2d;">0%</strong> — a pure solidarity loan, maximising SCD's impact.</p>
<p style="margin-bottom:8px;">📈 At <strong style="color:#2d2d2d;">3.5%</strong> — a competitive return while supporting community housing.</p>
<p style="margin-bottom:8px;">🏦 Best Cash ISA rates (~4.04.7%) are FSCS-protected and instantly accessible — bonds are neither.</p>
<p style="margin-bottom:8px;">🔒 Bonds are non-transferable; early repayment possible but not guaranteed.</p>
<p style="margin-bottom:0;">💷 Denominated in GBP — international investors bear FX risk.</p>
</div>
<p style="font-size:15px;color:#595959;margin-top:24px;line-height:1.7;">
Refer to the <a href="https://www.stirchley.coop/wp-content/uploads/2026/05/SCD-Bond-Offer-2026-25-May-2026.pdf" style="color:#9c4d00;font-weight:600;text-decoration:underline;">Bond Offer Document</a> for full terms and risks. SCD is a Co-operative &amp; Community Benefit Society (Reg. 4496), regulated by the Regulator of Social Housing.
</p>
</div>
</div>
<!-- =================================================================== -->
<!-- RESPONSIVE OVERRIDE -->
<!-- On screens wider than 480px (i.e. most phones in landscape or -->
<!-- larger), switch the four key-figure cards from a single column -->
<!-- to a two-column grid for better use of horizontal space. -->
<!-- =================================================================== -->
<style>
@media(min-width:480px){
#scd-out>div:first-of-type{grid-template-columns:1fr 1fr !important;}
}
</style>
<!-- =================================================================== -->
<!-- JAVASCRIPT — CALCULATOR LOGIC -->
<!-- All computation, validation, and DOM manipulation lives inside -->
<!-- an IIFE to avoid polluting the global scope. -->
<!-- =================================================================== -->
<script>
(function(){
/* ------------------------------------------------------------------ */
/* g(n) — Format a number as GBP (e.g. 75000 → £75,000) */
/* Uses UK locale grouping with no decimal places for readability. */
/* ------------------------------------------------------------------ */
function g(n){return'\u00a3'+n.toLocaleString('en-GB',{minimumFractionDigits:0,maximumFractionDigits:0});}
/* ------------------------------------------------------------------ */
/* run() — Main calculation entry point */
/* 1. Read & validate inputs */
/* 2. Iterate year-by-year computing interest under the chosen model */
/* 3. Populate summary cards (total, interest, annual, effective) */
/* 4. Determine security tier from investment amount */
/* 5. Estimate UK tax at three marginal rates */
/* 6. Render the year-by-year breakdown table + totals row */
/* ------------------------------------------------------------------ */
function run(){
/* --- Read raw inputs --- */
var a=parseFloat(document.getElementById('scd-amt').value), /* Investment amount (£) */
r=parseFloat(document.getElementById('scd-rate').value), /* Annual interest rate (%) */
t=parseInt(document.getElementById('scd-term').value,10), /* Bond term (years) */
mEl=document.querySelector('input[name="scd-m"]:checked'), /* Selected payout method */
m=mEl?mEl.value:'cm', /* Default to compound-at-maturity */
e=document.getElementById('scd-err'), /* Error container */
o=document.getElementById('scd-out'); /* Results container */
/* --- Clear previous state --- */
e.style.display='none';e.textContent='';o.style.display='none';
/* --- Input validation --- */
if(isNaN(a)||a<20000){e.textContent='\u26a0 Minimum investment is \u00a320,000.';e.style.display='block';return;}
if(isNaN(r)||r<0||r>3.5){e.textContent='\u26a0 Rate must be 0\u20133.5%.';e.style.display='block';return;}
if(isNaN(t)||t<5){e.textContent='\u26a0 Minimum term is 5 years.';e.style.display='block';return;}
/* --- Decode method code --- */
/* m[0] === 'c' → compound; 's' → simple */
/* m[1] === 'a' → paid annually; 'm' → at maturity */
var cmp=m[0]==='c', /* compound? */
ann=m[1]==='a', /* annual payout? */
bal=a, /* running balance for compound calc */
totI=0, /* accumulated interest across all years */
totP=0, /* accumulated payout across all years */
rows=[]; /* year-by-year data for the table */
/* --- Year-by-year interest computation --- */
for(var y=1;y<=t;y++){
/* Interest for this year: compound uses running balance; simple always uses principal */
var i=cmp?bal*(r/100):a*(r/100);
totI+=i;
/* Payout: annual modes pay out each year; at-maturity pays everything in the final year */
var p=0;
if(ann){
p=i; /* yearly payout = this year's interest */
}else if(y===t){ /* final year for at-maturity modes */
p=cmp?bal+i:a+totI; /* cm: full accumulated balance; sm: principal + total simple interest */
}
totP+=p;
/* Compound: add this year's interest to the running balance */
if(cmp){
bal+=i;
}
rows.push({
y:y, /* year number */
i:i, /* interest earned in this year */
p:p, /* amount paid out this year */
cb:cmp?bal:(ann?a:bal) /* closing balance displayed in table */
});
}
/* Simple at maturity: final balance is just the original principal */
if(!cmp&&!ann) bal=a;
/* --- Summary calculations --- */
var total=ann?a+totP:(cmp?bal:a+totI), /* total repayment to investor */
netI=ann?totP:(cmp?bal-a:totI), /* net interest earned */
yrInc=ann?a*(r/100):netI/t, /* per-year income (actual or average) */
eff=r===0?0:(ann?r:(Math.pow(total/a,1/t)-1)*100); /* effective annual rate (CAGR) */
/* --- Security tier based on investment amount --- */
var sec=a<150000?'<strong>Unsecured</strong> \u2014 no legal charge (under \u00a3150,000)'
:a<500000?'<strong>Tertiary charge</strong> \u2014 behind primary &amp; secondary (\u00a3150k\u2013\u00a3500k)'
:'<strong>Secondary charge</strong> \u2014 behind primary mortgage only (over \u00a3500,000)',
secBg=a<150000?'#fff8f0':a<500000?'#eaf2ec':'#eaf2ec',
secBd=a<150000?'#c45500':a<500000?'#2d6a4f':'#2d6a4f';
/* --- Populate summary cards --- */
document.getElementById('scd-r-total').textContent=g(total);
document.getElementById('scd-r-interest').textContent=g(netI);
document.getElementById('scd-r-annual').textContent=g(yrInc)+(ann?'/yr':'/yr avg');
document.getElementById('scd-r-eff').textContent=eff.toFixed(2)+'%';
/* --- Update security tier display --- */
var sb=document.getElementById('scd-r-sec');
sb.style.background=secBg;sb.style.border='2px solid '+secBd;sb.style.color='#2d2d2d';
document.getElementById('scd-r-sec-text').innerHTML=sec;
/* --- Estimate UK tax at three marginal rates --- */
/* Uses the annual income figure: actual for annual-payout modes, average for at-maturity. */
var taxI=ann?yrInc:netI;
document.getElementById('scd-r-tax').textContent='On '+g(taxI)+' interest: ~'+g(taxI*0.2)+' (basic), ~'+g(taxI*0.4)+' (higher), ~'+g(taxI*0.45)+' (additional rate) \u2014 before Personal Savings Allowance.';
/* --- Render year-by-year table --- */
var tb=document.getElementById('scd-r-table');tb.innerHTML='';
for(var j=0;j<rows.length;j++){
var d=rows[j],
isFinal=(j===rows.length-1), /* highlight the maturity row */
tr=document.createElement('tr');
tr.style.background=isFinal?'#fff8f0':(j%2?'#f5f5f0':'#fff'); /* alternate row colours */
tr.innerHTML='<td style="padding:10px 14px;border-bottom:1px solid #d9d5cc;color:#2d2d2d;font-weight:'+(isFinal?'700':'400')+';">'+d.y+(isFinal?' (maturity)':'')+'</td>'
+'<td style="padding:10px 14px;border-bottom:1px solid #d9d5cc;text-align:right;font-family:monospace;color:#2d2d2d;">'+g(d.cb)+'</td>'
+'<td style="padding:10px 14px;border-bottom:1px solid #d9d5cc;text-align:right;font-family:monospace;color:#2d2d2d;">'+g(d.i)+'</td>'
+'<td style="padding:10px 14px;border-bottom:1px solid #d9d5cc;text-align:right;font-family:monospace;color:#2d2d2d;font-weight:'+(d.p>0?'700':'400')+';">'+(d.p>0?g(d.p):'\u2014')+'</td>';
tb.appendChild(tr);
}
/* --- Totals row appended after the per-year rows --- */
var ttr=document.createElement('tr');
ttr.style.background='#f5f5f0';
ttr.innerHTML='<td style="padding:10px 14px;border-top:2px solid #9a9a9a;font-weight:700;color:#2d2d2d;">Total</td>'
+'<td style="padding:10px 14px;border-top:2px solid #9a9a9a;text-align:right;font-family:monospace;color:#2d2d2d;">\u2014</td>'
+'<td style="padding:10px 14px;border-top:2px solid #9a9a9a;text-align:right;font-family:monospace;font-weight:700;color:#2d2d2d;">'+g(totI)+'</td>'
+'<td style="padding:10px 14px;border-top:2px solid #9a9a9a;text-align:right;font-family:monospace;font-weight:700;color:#2d2d2d;">'+g(totP)+'</td>';
tb.appendChild(ttr);
/* --- Reveal results section --- */
o.style.display='block';
}
/* ------------------------------------------------------------------ */
/* Event binding — wire the Calculate button to run() */
/* Handles both synchronous (DOM already loaded) and async cases. */
/* ------------------------------------------------------------------ */
if(document.readyState==='loading'){
document.addEventListener('DOMContentLoaded',function(){
document.getElementById('scd-btn').addEventListener('click',run);
});
}else{
document.getElementById('scd-btn').addEventListener('click',run);
}
})();
</script>