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;">
|
<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>
|
<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>
|
<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="display:flex;gap:16px;flex-wrap:wrap;margin-bottom:32px;">
|
||||||
|
|
||||||
<div style="flex:1;min-width:180px;">
|
<div style="flex:1;min-width:180px;">
|
||||||
@ -32,7 +44,14 @@
|
|||||||
|
|
||||||
</div>
|
</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>
|
<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;">
|
<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;">
|
<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>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Button -->
|
<!-- =============================================================== -->
|
||||||
|
<!-- CALCULATE BUTTON -->
|
||||||
|
<!-- Triggers the run() function which validates, computes, and -->
|
||||||
|
<!-- renders the results. -->
|
||||||
|
<!-- =============================================================== -->
|
||||||
<button id="scd-btn"
|
<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;">
|
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
|
Calculate
|
||||||
</button>
|
</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>
|
<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;">
|
<div id="scd-out" style="display:none;">
|
||||||
|
|
||||||
<hr style="margin:36px 0;border:none;border-top:2px solid #d9d5cc;" />
|
<hr style="margin:36px 0;border:none;border-top:2px solid #d9d5cc;" />
|
||||||
@ -140,92 +172,135 @@
|
|||||||
|
|
||||||
</div>
|
</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>
|
<style>
|
||||||
@media(min-width:480px){
|
@media(min-width:480px){
|
||||||
#scd-out>div:first-of-type{grid-template-columns:1fr 1fr !important;}
|
#scd-out>div:first-of-type{grid-template-columns:1fr 1fr !important;}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<!-- =================================================================== -->
|
||||||
|
<!-- JAVASCRIPT — CALCULATOR LOGIC -->
|
||||||
|
<!-- All computation, validation, and DOM manipulation lives inside -->
|
||||||
|
<!-- an IIFE to avoid polluting the global scope. -->
|
||||||
|
<!-- =================================================================== -->
|
||||||
<script>
|
<script>
|
||||||
(function(){
|
(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});}
|
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(){
|
function run(){
|
||||||
var a=parseFloat(document.getElementById('scd-amt').value),
|
/* --- Read raw inputs --- */
|
||||||
r=parseFloat(document.getElementById('scd-rate').value),
|
var a=parseFloat(document.getElementById('scd-amt').value), /* Investment amount (£) */
|
||||||
t=parseInt(document.getElementById('scd-term').value),
|
r=parseFloat(document.getElementById('scd-rate').value), /* Annual interest rate (%) */
|
||||||
mEl=document.querySelector('input[name="scd-m"]:checked'),
|
t=parseInt(document.getElementById('scd-term').value), /* Bond term (years) */
|
||||||
m=mEl?mEl.value:'cm',
|
mEl=document.querySelector('input[name="scd-m"]:checked'), /* Selected payout method */
|
||||||
e=document.getElementById('scd-err'),
|
m=mEl?mEl.value:'cm', /* Default to compound-at-maturity */
|
||||||
o=document.getElementById('scd-out');
|
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';
|
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(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(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;}
|
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',
|
/* --- Decode method code --- */
|
||||||
bal=a,totI=0,totP=0,rows=[];
|
/* 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++){
|
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);
|
var i=cmp?bal*(r/100):a*(r/100);
|
||||||
totI+=i;
|
totI+=i;
|
||||||
|
|
||||||
|
/* Payout: annual modes pay out each year; at-maturity pays everything in the final year */
|
||||||
var p=0;
|
var p=0;
|
||||||
if(ann){
|
if(ann){
|
||||||
/* Yearly payout: interest paid each year */
|
p=i; /* yearly payout = this year's interest */
|
||||||
p=i;
|
}else if(y===t){ /* final year for at-maturity modes */
|
||||||
}else if(y===t){
|
p=cmp?bal+i:bal+i; /* pay out current balance + final interest */
|
||||||
/* At-maturity: entire balance paid in final year */
|
|
||||||
p=cmp?bal+i:bal+i;
|
|
||||||
}
|
}
|
||||||
totP+=p;
|
totP+=p;
|
||||||
|
|
||||||
/* Accumulate balance for compound modes */
|
/* Compound: add this year's interest to the running balance */
|
||||||
if(cmp){
|
if(cmp){
|
||||||
bal+=i;
|
bal+=i;
|
||||||
}
|
}
|
||||||
|
|
||||||
rows.push({
|
rows.push({
|
||||||
y:y,
|
y:y, /* year number */
|
||||||
i:i,
|
i:i, /* interest earned in this year */
|
||||||
p:p,
|
p:p, /* amount paid out this year */
|
||||||
/* Balance column: running balance before any payout */
|
cb:cmp?bal:(ann?a:bal) /* closing balance displayed in table */
|
||||||
cb:cmp?bal:(ann?a:bal)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Simple at maturity: final balance is just the original principal */
|
||||||
if(!cmp&&!ann) bal=a;
|
if(!cmp&&!ann) bal=a;
|
||||||
|
|
||||||
var total=ann?a+totP:(cmp?bal:a+totI),
|
/* --- Summary calculations --- */
|
||||||
netI=ann?totP:(cmp?bal-a:totI),
|
var total=ann?a+totP:(cmp?bal:a+totI), /* total repayment to investor */
|
||||||
yrInc=ann?a*(r/100):netI/t,
|
netI=ann?totP:(cmp?bal-a:totI), /* net interest earned */
|
||||||
eff=r===0?0:(ann?r:(Math.pow(total/a,1/t)-1)*100);
|
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)'
|
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)'
|
: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)',
|
:'<strong>Secondary charge</strong> \u2014 behind primary mortgage only (over \u00a3500,000)',
|
||||||
secBg=a<150000?'#fff8f0':a<500000?'#eaf2ec':'#eaf2ec',
|
secBg=a<150000?'#fff8f0':a<500000?'#eaf2ec':'#eaf2ec',
|
||||||
secBd=a<150000?'#c45500':a<500000?'#2d6a4f':'#2d6a4f';
|
secBd=a<150000?'#c45500':a<500000?'#2d6a4f':'#2d6a4f';
|
||||||
|
|
||||||
|
/* --- Populate summary cards --- */
|
||||||
document.getElementById('scd-r-total').textContent=g(total);
|
document.getElementById('scd-r-total').textContent=g(total);
|
||||||
document.getElementById('scd-r-interest').textContent=g(netI);
|
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-annual').textContent=(ann?'':'~')+g(yrInc)+(ann?'/yr':'/yr avg');
|
||||||
document.getElementById('scd-r-eff').textContent=eff.toFixed(2)+'%';
|
document.getElementById('scd-r-eff').textContent=eff.toFixed(2)+'%';
|
||||||
|
|
||||||
|
/* --- Update security tier display --- */
|
||||||
var sb=document.getElementById('scd-r-sec');
|
var sb=document.getElementById('scd-r-sec');
|
||||||
sb.style.background=secBg;sb.style.border='2px solid '+secBd;sb.style.color='#2d2d2d';
|
sb.style.background=secBg;sb.style.border='2px solid '+secBd;sb.style.color='#2d2d2d';
|
||||||
document.getElementById('scd-r-sec-text').innerHTML=sec;
|
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;
|
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.';
|
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='';
|
var tb=document.getElementById('scd-r-table');tb.innerHTML='';
|
||||||
for(var j=0;j<rows.length;j++){
|
for(var j=0;j<rows.length;j++){
|
||||||
var d=rows[j],
|
var d=rows[j],
|
||||||
isFinal=(j===rows.length-1),
|
isFinal=(j===rows.length-1), /* highlight the maturity row */
|
||||||
tr=document.createElement('tr');
|
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>'
|
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.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;">'+g(d.i)+'</td>'
|
||||||
@ -233,7 +308,7 @@
|
|||||||
tb.appendChild(tr);
|
tb.appendChild(tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Add a totals row */
|
/* --- Totals row appended after the per-year rows --- */
|
||||||
var ttr=document.createElement('tr');
|
var ttr=document.createElement('tr');
|
||||||
ttr.style.background='#f5f5f0';
|
ttr.style.background='#f5f5f0';
|
||||||
ttr.innerHTML='<td style="padding:10px 14px;border-top:2px solid #9a9a9a;font-weight:700;color:#2d2d2d;">Total</td>'
|
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>';
|
+'<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);
|
tb.appendChild(ttr);
|
||||||
|
|
||||||
|
/* --- Reveal results section --- */
|
||||||
o.style.display='block';
|
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'){
|
if(document.readyState==='loading'){
|
||||||
document.addEventListener('DOMContentLoaded',function(){
|
document.addEventListener('DOMContentLoaded',function(){
|
||||||
document.getElementById('scd-btn').addEventListener('click',run);
|
document.getElementById('scd-btn').addEventListener('click',run);
|
||||||
|
|||||||
Reference in New Issue
Block a user