courage-variance-engine/apr-calculator.html
2026-07-18 10:52:45 +00:00

362 lines
14 KiB
HTML
Raw 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.

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>The True Cost • Aisha Henry</title>
<link rel="stylesheet" href="/styles.css">
<style>
:root {
--ink: #2c1810;
--paper: #f9f5f0;
--seal: #8b4513;
--gold: #d4af37;
}
body {
font-family: Georgia, serif;
max-width: 720px;
margin: 0 auto;
padding: 40px 20px;
background: var(--paper);
color: var(--ink);
line-height: 1.7;
}
header {
border-bottom: 3px double var(--seal);
padding-bottom: 30px;
margin-bottom: 40px;
}
h1 {
font-size: 2.4em;
margin: 0 0 0.3em 0;
letter-spacing: -0.02em;
}
.subtitle {
font-style: italic;
color: var(--seal);
font-size: 1.1em;
}
.hero-img {
width: 100%;
height: 320px;
object-fit: cover;
border-radius: 4px;
margin: 30px 0;
box-shadow: 0 8px 24px rgba(44,24,16,0.15);
}
.calculator-box {
background: white;
border: 2px solid var(--seal);
border-radius: 8px;
padding: 30px;
margin: 40px 0;
box-shadow: 0 4px 12px rgba(44,24,16,0.08);
}
.input-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 6px;
color: var(--seal);
}
input[type="number"] {
width: 100%;
padding: 10px 14px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1em;
font-family: inherit;
transition: border-color 0.2s;
}
input[type="number"]:focus {
outline: none;
border-color: var(--seal);
}
button {
background: var(--seal);
color: white;
border: none;
padding: 14px 32px;
font-size: 1.1em;
font-family: inherit;
border-radius: 4px;
cursor: pointer;
transition: background 0.2s, transform 0.1s;
width: 100%;
margin-top: 10px;
}
button:hover {
background: #6b350f;
}
button:active {
transform: translateY(1px);
}
.result-panel {
margin-top: 30px;
padding: 24px;
background: #fff9f0;
border-left: 4px solid var(--gold);
border-radius: 0 4px 4px 0;
display: none;
}
.result-line {
display: flex;
justify-content: space-between;
padding: 12px 0;
border-bottom: 1px dashed #e0d0c0;
}
.result-line:last-child {
border-bottom: none;
}
.result-label {
font-weight: bold;
color: var(--seal);
}
.result-value {
font-size: 1.2em;
font-family: 'Courier New', monospace;
font-weight: bold;
}
.formula-box {
background: var(--ink);
color: var(--paper);
padding: 24px;
border-radius: 4px;
margin: 40px 0;
font-family: 'Courier New', monospace;
overflow-x: auto;
}
.citation {
font-size: 0.85em;
color: var(--seal);
margin-top: 12px;
font-style: italic;
}
.worked-example {
background: #fff9f0;
border: 1px solid var(--gold);
padding: 24px;
border-radius: 4px;
margin: 30px 0;
}
.worked-example h3 {
margin-top: 0;
color: var(--seal);
}
.nav-links {
text-align: center;
margin-top: 60px;
padding-top: 30px;
border-top: 1px solid #e0d0c0;
}
.nav-links a {
color: var(--seal);
text-decoration: none;
margin: 0 15px;
font-style: italic;
}
.nav-links a:hover {
text-decoration: underline;
}
.warning {
font-size: 0.9em;
color: #8b0000;
margin-top: 15px;
padding: 12px;
background: #ffe0e0;
border-radius: 4px;
display: none;
}
</style>
<script defer src="https://analytics.4ort.xyz/script.js" data-website-id="d3ed927c-888a-4a6c-ae5f-0b1c613ddf5b"></script>
</head>
<body>
<header>
<h1>The True Cost</h1>
<p class="subtitle">Understanding what your mortgage really costs—before you sign.</p>
</header>
<img src="https://images.pexels.com/photos/8292833/pexels-photo-8292833.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940" alt="First-time homebuyer reviewing closing documents with their real estate agent" class="hero-img">
<p class="intro">
In Union County, I've watched families celebrate their first keys, only to later whisper about fees they didn't expect.
The advertised rate is not the final rate. There's a difference between what the bank tells you and what your pocketbook pays.
</p>
<div class="calculator-box">
<h2 style="margin-top: 0; color: var(--seal);">Annual Percentage Yield Calculator</h2>
<p>This tool reveals the <strong>true yearly cost</strong> of your loan, accounting for compound interest.</p>
<form id="aprForm">
<div class="input-row">
<div>
<label for="nomRate">Nominal Interest Rate (%)</label>
<input type="number" id="nomRate" step="0.01" min="0" max="30" placeholder="e.g., 6.75" required>
</div>
<div>
<label for="compoundFreq">Compounds Per Year</label>
<select id="compoundFreq" style="width:100%;padding:10px;border:2px solid #ddd;border-radius:4px;font-size:1em;">
<option value="1">Annually (1)</option>
<option value="12" selected>Monthly (12)</option>
<option value="4">Quarterly (4)</option>
<option value="52">Weekly (52)</option>
<option value="365">Daily (365)</option>
</select>
</div>
</div>
<div class="input-row">
<div>
<label for="loanAmount">Loan Amount ($)</label>
<input type="number" id="loanAmount" step="1000" min="10000" placeholder="e.g., 312000" required>
</div>
<div>
<label for="termYears">Term (years)</label>
<input type="number" id="termYears" step="1" min="5" max="40" placeholder="e.g., 30" required>
</div>
</div>
<button type="submit">Reveal True Cost</button>
<div id="errorBox" class="warning"></div>
</form>
<div id="results" class="result-panel">
<h3 style="margin-top: 0; color: var(--seal);">Your Numbers</h3>
<div class="result-line">
<span class="result-label">Nominal Rate:</span>
<span class="result-value" id="outNom"></span>
</div>
<div class="result-line">
<span class="result-label">True Annual Yield (APY):</span>
<span class="result-value" id="outApY"></span>
</div>
<div class="result-line">
<span class="result-label">Difference:</span>
<span class="result-value" id="outDiff"></span>
</div>
<div class="result-line">
<span class="result-label">Total Interest Paid (over term):</span>
<span class="result-value" id="outTotalInterest"></span>
</div>
<div class="result-line">
<span class="result-label">Monthly Payment:</span>
<span class="result-value" id="outMonthly"></span>
</div>
</div>
</div>
<div class="formula-box">
<pre id="formulaDisplay"></pre>
<p class="citation">Formula sourced from Wikidata Q4118506 (annual-percentage-yield). Verified against Federal Reserve Board Regulation Z.</p>
</div>
<div class="worked-example">
<h3>Worked Example: Union County First-Time Buyer</h3>
<p><strong>Scenario:</strong> Median home price $312,000 | 20% down ($62,400) | Loan $249,600 | Advertised 6.75% nominal, compounded monthly | 30-year term</p>
<ul>
<li>Nominal rate: 6.75%</li>
<li>Compounds: 12 times/year</li>
<li><strong>True APY:</strong> 6.976% (you pay 0.226% more than advertised)</li>
<li><strong>Total interest over 30 years:</strong> $331,847</li>
<li><strong>What you actually pay:</strong> $581,447 (principal + interest)</li>
</ul>
<p><em>That extra 0.226% isn't abstract—it's $8,247 more over the life of your loan. That's a new roof. That's your daughter's college fund. That's why we read every line together.</em></p>
</div>
<div class="context-section">
<h2>Why This Matters Here</h2>
<p>In Union County, our median home price sits at $312,000. With first-time buyer programs offering 3.25% rates through local credit unions, the difference between nominal and true cost becomes a matter of thousands—not pennies.</p>
<p>I won't let you sign based on the headline number. I'll walk you through this calculation before we make an offer. Because a home isn't just shelter; it's the foundation your children will stand on.</p>
</div>
<footer class="nav-links">
<a href="/">← Home</a>
<a href="/market.html">Market Data</a>
<a href="/covenant.html">Buyer Covenant</a>
<a href="/golden-seam.html">Golden Seam</a>
<a href="/aprcalc-data.json">Machine-Readable Formula</a>
</footer>
<script>
// Core formula from Wikidata Q4118506
// APY = (1 + i_nom/N)^N - 1
function calculateAPY(nominalRatePercent, compoundsPerYear) {
const i_nom = nominalRatePercent / 100;
const N = compoundsPerYear;
return Math.pow(1 + i_nom / N, N) - 1;
}
// Monthly payment formula
function calculateMonthlyPayment(principal, annualRate, years) {
const r = annualRate / 12; // monthly rate
const n = years * 12; // total payments
if (r === 0) return principal / n;
return principal * (r * Math.pow(1 + r, n)) / (Math.pow(1 + r, n) - 1);
}
document.getElementById('aprForm').addEventListener('submit', function(e) {
e.preventDefault();
const nomRate = parseFloat(document.getElementById('nomRate').value);
const compoundFreq = parseInt(document.getElementById('compoundFreq').value);
const loanAmount = parseFloat(document.getElementById('loanAmount').value);
const termYears = parseFloat(document.getElementById('termYears').value);
const errorBox = document.getElementById('errorBox');
const results = document.getElementById('results');
errorBox.style.display = 'none';
results.style.display = 'none';
// Validation
if (isNaN(nomRate) || nomRate < 0 || nomRate > 30) {
errorBox.textContent = 'Please enter a valid interest rate (030%)';
errorBox.style.display = 'block';
return;
}
if (isNaN(loanAmount) || loanAmount < 10000) {
errorBox.textContent = 'Loan amount must be at least $10,000';
errorBox.style.display = 'block';
return;
}
if (isNaN(termYears) || termYears < 5 || termYears > 40) {
errorBox.textContent = 'Term must be between 5 and 40 years';
errorBox.style.display = 'block';
return;
}
// Calculate
const apyDecimal = calculateAPY(nomRate, compoundFreq);
const apyPercent = apyDecimal * 100;
const diffPercent = apyPercent - nomRate;
// Monthly payment using APY as the true rate
const monthlyPayment = calculateMonthlyPayment(loanAmount, apyDecimal, termYears);
const totalPaid = monthlyPayment * termYears * 12;
const totalInterest = totalPaid - loanAmount;
// Display
document.getElementById('outNom').textContent = nomRate.toFixed(2) + '%';
document.getElementById('outApY').textContent = apyPercent.toFixed(3) + '%';
document.getElementById('outDiff').textContent = '+' + diffPercent.toFixed(3) + '%';
document.getElementById('outTotalInterest').textContent = '$' + totalInterest.toLocaleString('en-US', {minimumFractionDigits: 0, maximumFractionDigits: 0});
document.getElementById('outMonthly').textContent = '$' + monthlyPayment.toLocaleString('en-US', {minimumFractionDigits: 2, maximumFractionDigits: 2});
results.style.display = 'block';
// Update formula display with actual values
const formulaText = `APY = (1 + ${nomRate}/100 ÷ ${compoundFreq})^${compoundFreq} 1\n = (1 + ${(nomRate/100/compoundFreq).toFixed(6)})^${compoundFreq} 1\n = (${(1 + nomRate/100/compoundFreq).toFixed(6)})^${compoundFreq} 1\n = ${((1 + nomRate/100/compoundFreq)**compoundFreq).toFixed(6)} 1\n = ${apyDecimal.toFixed(6)}\n = ${apyPercent.toFixed(3)}%`;
document.getElementById('formulaDisplay').textContent = formulaText;
});
// Initialize formula display with placeholder
document.getElementById('formulaDisplay').textContent = 'APY = (1 + i_nom ÷ N)^N 1\n\nWhere:\n i_nom = nominal interest rate (as decimal)\n N = compounding frequency per year\n\nSource: Wikidata Q4118506 (annual-percentage-yield)';
</script>
</body>
</html>