Add comprehensive comments to HTML, CSS, and JS sections
This commit is contained in:
148
calc.html
148
calc.html
@ -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 & 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);
|
||||
|
||||
Reference in New Issue
Block a user