Add comprehensive comments to HTML, CSS, and JS sections

This commit is contained in:
2026-06-15 00:24:25 +01:00
parent 1f2d758f70
commit 488fa83105

148
calc.html
View File

@ -1,12 +1,24 @@
<!-- SCD Bond Calculator — WordPress Custom HTML Block -->
<!-- =================================================================== -->
<!-- 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>
<!-- Inputs -->
<!-- =============================================================== -->
<!-- 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;">
@ -32,7 +44,14 @@
</div>
<!-- Interest method -->
<!-- =============================================================== -->
<!-- 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;">
@ -53,16 +72,29 @@
</label>
</div>
<!-- Button -->
<!-- =============================================================== -->
<!-- CALCULATE BUTTON -->
<!-- Triggers the run() function which validates, computes, and -->
<!-- renders the results. -->
<!-- =============================================================== -->
<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 -->
<!-- =============================================================== -->
<!-- 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 -->
<!-- =============================================================== -->
<!-- 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;" />
@ -140,92 +172,135 @@
</div>
<!-- Responsive override -->
<!-- =================================================================== -->
<!-- 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(){
var a=parseFloat(document.getElementById('scd-amt').value),
r=parseFloat(document.getElementById('scd-rate').value),
t=parseInt(document.getElementById('scd-term').value),
mEl=document.querySelector('input[name="scd-m"]:checked'),
m=mEl?mEl.value:'cm',
e=document.getElementById('scd-err'),
o=document.getElementById('scd-out');
/* --- 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), /* 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;}
var cmp=m[0]==='c',ann=m[1]==='a',
bal=a,totI=0,totP=0,rows=[];
/* --- 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){
/* Yearly payout: interest paid each year */
p=i;
}else if(y===t){
/* At-maturity: entire balance paid in final year */
p=cmp?bal+i:bal+i;
p=i; /* yearly payout = this year's interest */
}else if(y===t){ /* final year for at-maturity modes */
p=cmp?bal+i:bal+i; /* pay out current balance + final interest */
}
totP+=p;
/* Accumulate balance for compound modes */
/* Compound: add this year's interest to the running balance */
if(cmp){
bal+=i;
}
rows.push({
y:y,
i:i,
p:p,
/* Balance column: running balance before any payout */
cb:cmp?bal:(ann?a:bal)
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;
var total=ann?a+totP:(cmp?bal:a+totI),
netI=ann?totP:(cmp?bal-a:totI),
yrInc=ann?a*(r/100):netI/t,
eff=r===0?0:(ann?r:(Math.pow(total/a,1/t)-1)*100);
/* --- 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=(ann?'':'~')+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),
isFinal=(j===rows.length-1), /* highlight the maturity row */
tr=document.createElement('tr');
tr.style.background=isFinal?'#fff8f0':(j%2?'#f5f5f0':'#fff');
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>'
@ -233,7 +308,7 @@
tb.appendChild(tr);
}
/* Add a totals row */
/* --- 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>'
@ -242,9 +317,14 @@
+'<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);