<!
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>APR Calculator</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2.0.0"></script>
<style>
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
body {
background: linear-gradient(135deg, #e6f0fa 0%, #f3f5f9 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.calculator-container {
background: white;
border-radius: 15px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
padding: 30px;
width: 100%;
max-width: 900px;
border: 1px solid #e0e4e8;
}
h1 {
color: #0a47b6;
text-align: center;
margin-bottom: 25px;
font-size: clamp(1.8rem, 5vw, 2.2rem);
font-weight: 600;
letter-spacing: 1px;
}
.main-content {
display: flex;
gap: 30px;
flex-wrap: wrap;
justify-content: center;
}
.input-section, .result-section {
flex: 1;
min-width: 300px;
background: #f8f9fa;
border-radius: 10px;
padding: 20px;
box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.05);
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
color: #333;
font-weight: 500;
font-size: clamp(0.9rem, 2.5vw, 1rem);
margin-bottom: 8px;
}
input, select {
width: 100%;
padding: 10px 15px;
border: 2px solid #d0d4d8;
border-radius: 8px;
font-size: clamp(0.9rem, 2.5vw, 1rem);
transition: border-color 0.3s ease, box-shadow 0.3s ease;
}
input:focus, select:focus {
outline: none;
border-color: #0a47b6;
box-shadow: 0 0 5px rgba(10, 71, 182, 0.2);
}
.input-row {
display: flex;
gap: 15px;
}
.input-row .input-group {
flex: 1;
}
.button-group {
display: flex;
gap: 15px;
justify-content: center;
margin-top: 20px;
}
.calculate-btn, .clear-btn {
padding: 12px 30px;
border: none;
border-radius: 8px;
font-size: clamp(0.9rem, 2.5vw, 1rem);
font-weight: bold;
cursor: pointer;
transition: transform 0.2s ease, background 0.3s ease, box-shadow 0.3s ease;
}
.calculate-btn {
background: #0a47b6;
color: #FFFFFF;
}
.calculate-btn:hover {
background: #083894;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(10, 71, 182, 0.3);
}
.clear-btn {
background: #e0e4e8;
color: #333;
}
.clear-btn:hover {
background: #d0d4d8;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
}
.result-section h2 {
color: #0a47b6;
font-size: clamp(1.2rem, 4vw, 1.5rem);
margin-bottom: 15px;
text-align: center;
}
.result-section h3 {
color: #2e7d32;
font-size: clamp(1.5rem, 5vw, 1.8rem);
margin-bottom: 20px;
text-align: center;
background: #e8f5e9;
padding: 10px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
.result-items {
background: white;
border-radius: 10px;
padding: 20px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
margin-bottom: 20px;
}
.result-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0;
border-bottom: 1px solid #e0e4e8;
font-size: clamp(0.9rem, 2.5vw, 1rem);
color: #333;
transition: background 0.3s ease;
}
.result-item:last-child {
border-bottom: none;
}
.result-item:hover {
background: #f1f5f9;
}
.result-item span {
color: #0a47b6;
font-weight: 600;
font-size: clamp(1rem, 2.5vw, 1.1rem);
}
.result-item .label {
font-weight: 500;
color: #555;
}
.chart-wrapper {
margin-top: 20px;
text-align: center;
}
canvas {
max-width: 200px;
margin: 0 auto;
}
.toggle-table {
display: block;
margin: 20px auto 0;
color: #0a47b6;
font-size: clamp(0.9rem, 2.5vw, 1rem);
text-decoration: underline;
cursor: pointer;
background: none;
border: none;
}
.table-container {
margin-top: 20px;
max-height: 300px;
overflow-y: auto;
display: none;
}
table {
width: 100%;
border-collapse: collapse;
font-size: clamp(0.8rem, 2vw, 0.9rem);
}
th, td {
padding: 10px;
text-align: center;
border: 1px solid #e0e4e8;
}
th {
background: #0a47b6;
color: #FFFFFF;
font-weight: bold;
position: sticky;
top: 0;
z-index: 1;
}
tr:nth-child(even) {
background: #f8f9fa;
}
tr:hover {
background: #e0e4e8;
}
@media (max-width: 768px) {
.main-content {
flex-direction: column;
align-items: center;
}
.input-section, .result-section {
min-width: 100%;
}
.input-row {
flex-direction: column;
gap: 10px;
}
.button-group {
flex-direction: column;
gap: 10px;
}
.calculate-btn, .clear-btn {
padding: 12px;
width: 100%;
max-width: 300px;
}
.table-container {
margin: 0 -20px;
padding: 0 20px;
}
.result-items {
padding: 15px;
}
.result-item {
flex-direction: column;
align-items: flex-start;
gap: 5px;
padding: 15px 0;
}
}
@media (max-width: 480px) {
.calculator-container {
padding: 20px;
}
h1 {
font-size: clamp(1.5rem, 5vw, 1.8rem);
}
.result-section h3 {
font-size: clamp(1.2rem, 4vw, 1.5rem);
}
th, td {
padding: 8px;
}
.result-item span {
font-size: clamp(0.9rem, 2vw, 1rem);
}
}
</style>
</head>
<body>
<div class="calculator-container">
<h1>General APR Calculator</h1>
<div class="main-content">
<div class="input-section">
<div class="input-group">
<label for="loanAmount">Loan Amount</label>
<input type="number" id="loanAmount" placeholder="Enter loan amount"
step="0.01">
</div>
<div class="input-row">
<div class="input-group">
<label for="years">Loan Term (Years)</label>
<input type="number" id="years" placeholder="Years" step="1">
</div>
<div class="input-group">
<label for="months">Loan Term (Months)</label>
<input type="number" id="months" placeholder="Months" step="1">
</div>
</div>
<div class="input-group">
<label for="interestRate">Interest Rate</label>
<input type="number" id="interestRate" placeholder="Enter interest rate"
step="0.01">
</div>
<div class="input-group">
<label for="compoundFrequency">Compound</label>
<select id="compoundFrequency">
<option value="daily">Daily</option>
<option value="bi-weekly">Bi-Weekly</option>
<option value="semi-monthly">Semi-Monthly</option>
<option value="monthly" selected>Monthly</option>
<option value="quarterly">Quarterly</option>
<option value="semi-annual">Semi-Annual</option>
<option value="annual">Annual</option>
<option value="continuous">Continuous</option>
</select>
</div>
<div class="input-group">
<label for="paymentFrequency">Pay Back</label>
<select id="paymentFrequency">
<option value="daily">Every Day</option>
<option value="bi-weekly">Every Bi-Week</option>
<option value="semi-monthly">Every Semi-Month</option>
<option value="monthly" selected>Every Month</option>
<option value="quarterly">Every Quarter</option>
<option value="semi-annual">Every Semi-Year</option>
<option value="annual">Every Year</option>
</select>
</div>
<div class="input-group">
<label for="loanedFees">Loaned Fees</label>
<input type="number" id="loanedFees" placeholder="Enter loaned fees"
step="0.01">
</div>
<div class="input-group">
<label for="upfrontFees">Upfront Fees</label>
<input type="number" id="upfrontFees" placeholder="Enter upfront fees"
step="0.01">
</div>
<div class="button-group">
<button class="calculate-btn" onclick="calculateAPR()">Calculate</button>
<button class="clear-btn" onclick="clearForm()">Clear</button>
</div>
</div>
<div class="result-section" id="results" style="display: none;">
<h3 id="realAPR">Real APR: 0.0000%</h3>
<div class="result-items">
<div class="result-item">
<span class="label">Amount Financed:</span>
<span id="amountFinanced">$0.00</span>
</div>
<div class="result-item">
<span class="label">Upfront Out-of-Pocket Fees:</span>
<span id="upfrontFeesResult">$0.00</span>
</div>
<div class="result-item">
<span class="label">Payment Every <span
id="paymentFrequencyResult"></span>:</span>
<span id="paymentAmount">$0.00</span>
</div>
<div class="result-item">
<span class="label">Total of <span id="totalPayments"></span>
Payments:</span>
<span id="totalPaymentsAmount">$0.00</span>
</div>
<div class="result-item">
<span class="label">Total Interest:</span>
<span id="totalInterest">$0.00</span>
</div>
<div class="result-item">
<span class="label">All Payments and Fees:</span>
<span id="allPaymentsFees">$0.00</span>
</div>
</div>
<div class="chart-wrapper">
<canvas id="breakdownChart"></canvas>
</div>
<button class="toggle-table" onclick="toggleTable()">View Amortization
Table</button>
<div class="table-container" id="amortizationTable">
<table>
<thead>
<tr>
<th>Payment #</th>
<th>Payment Amount ($)</th>
<th>Interest Paid ($)</th>
<th>Principal Paid ($)</th>
<th>Remaining Balance ($)</th>
</tr>
</thead>
<tbody id="amortizationTableBody"></tbody>
</table>
</div>
</div>
</div>
</div>
<script>
let breakdownChart;
function calculateAPR() {
// Get input values
const loanAmount = parseFloat(document.getElementById('loanAmount').value);
const years = parseInt(document.getElementById('years').value) || 0;
const months = parseInt(document.getElementById('months').value) || 0;
const interestRate = parseFloat(document.getElementById('interestRate').value);
const compoundFrequency = document.getElementById('compoundFrequency').value;
const paymentFrequency = document.getElementById('paymentFrequency').value;
const loanedFees = parseFloat(document.getElementById('loanedFees').value) || 0;
const upfrontFees = parseFloat(document.getElementById('upfrontFees').value) || 0;
// Calculate total months based on payment frequency
const periodsPerYear = {
'daily': 365,
'bi-weekly': 26,
'semi-monthly': 24,
'monthly': 12,
'quarterly': 4,
'semi-annual': 2,
'annual': 1
};
const paymentPeriodsPerYear = periodsPerYear[paymentFrequency];
const totalPayments = (years * paymentPeriodsPerYear) + (months *
(paymentPeriodsPerYear / 12));
// Validation
if (isNaN(loanAmount) || isNaN(interestRate) || isNaN(totalPayments) ||
loanAmount <= 0 || interestRate < 0 || totalPayments <= 0) {
alert('Please enter valid positive numbers for loan amount, interest rate, and loan
term');
return;
}
// Convert interest rate to match payment frequency
const compoundPeriodsPerYear = periodsPerYear[compoundFrequency] || Infinity;
let effectiveRatePerPeriod;
if (compoundFrequency === 'continuous') {
const ear = Math.exp(interestRate / 100) - 1;
effectiveRatePerPeriod = Math.pow(1 + ear, 1 / paymentPeriodsPerYear) - 1;
} else {
const ear = Math.pow(1 + (interestRate / 100) / compoundPeriodsPerYear,
compoundPeriodsPerYear) - 1;
effectiveRatePerPeriod = Math.pow(1 + ear, 1 / paymentPeriodsPerYear) - 1;
}
// Calculate monthly payment
const monthlyPayment = loanAmount * effectiveRatePerPeriod * Math.pow(1 +
effectiveRatePerPeriod, totalPayments) /
(Math.pow(1 + effectiveRatePerPeriod, totalPayments) - 1);
// Calculate total payments and interest
const totalPaid = monthlyPayment * totalPayments;
const totalInterest = totalPaid - loanAmount;
// Adjust for fees to calculate APR
const amountFinanced = loanAmount - upfrontFees;
const totalCost = totalPaid + loanedFees + upfrontFees;
// Calculate APR using binary search
let low = 0;
let high = 100;
let apr = 0;
const precision = 0.0001;
let calculatedPayment;
for (let i = 0; i < 100; i++) {
apr = (low + high) / 2;
const aprRatePerPeriod = apr / 100 / paymentPeriodsPerYear;
calculatedPayment = amountFinanced * aprRatePerPeriod * Math.pow(1 +
aprRatePerPeriod, totalPayments) /
(Math.pow(1 + aprRatePerPeriod, totalPayments) - 1);
const totalWithAPR = calculatedPayment * totalPayments + loanedFees +
upfrontFees;
if (Math.abs(totalWithAPR - totalCost) < precision) {
break;
}
if (totalWithAPR > totalCost) {
high = apr;
} else {
low = apr;
}
}
// Amortization schedule
let balance = loanAmount;
let cumulativeInterest = 0;
const amortizationData = [];
for (let i = 1; i <= totalPayments; i++) {
const interest = balance * effectiveRatePerPeriod;
const principal = monthlyPayment - interest;
balance -= principal;
cumulativeInterest += interest;
if (balance < 0) balance = 0;
amortizationData.push({
paymentNumber: i,
paymentAmount: monthlyPayment.toFixed(2),
interestPaid: interest.toFixed(2),
principalPaid: principal.toFixed(2),
remainingBalance: balance.toFixed(2)
});
}
// Display results
document.getElementById('realAPR').textContent = `Real APR: ${apr.toFixed(4)}%`;
document.getElementById('amountFinanced').textContent = `$$
{amountFinanced.toFixed(2)}`;
document.getElementById('upfrontFeesResult').textContent = `$$
{upfrontFees.toFixed(2)}`;
document.getElementById('paymentFrequencyResult').textContent =
paymentFrequency.charAt(0).toUpperCase() + paymentFrequency.slice(1);
document.getElementById('paymentAmount').textContent = `$$
{monthlyPayment.toFixed(2)}`;
document.getElementById('totalPayments').textContent = totalPayments;
document.getElementById('totalPaymentsAmount').textContent = `$$
{totalPaid.toFixed(2)}`;
document.getElementById('totalInterest').textContent = `$${totalInterest.toFixed(2)}`;
document.getElementById('allPaymentsFees').textContent = `$${totalCost.toFixed(2)}`;
// Populate amortization table
const tableBody = document.getElementById('amortizationTableBody');
tableBody.innerHTML = '';
amortizationData.forEach(row => {
const tr = document.createElement('tr');
tr.innerHTML = `
<td>${row.paymentNumber}</td>
<td>$${row.paymentAmount}</td>
<td>$${row.interestPaid}</td>
<td>$${row.principalPaid}</td>
<td>$${row.remainingBalance}</td>
`;
tableBody.appendChild(tr);
});
// Destroy previous chart if it exists
if (breakdownChart) breakdownChart.destroy();
// Create breakdown chart
const ctx = document.getElementById('breakdownChart').getContext('2d');
breakdownChart = new Chart(ctx, {
type: 'pie',
data: {
labels: ['Principal', 'Interest', 'Fees'],
datasets: [{
data: [loanAmount, totalInterest, loanedFees + upfrontFees],
backgroundColor: ['#1E90FF', '#2E7D32', '#D32F2F']
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'right'
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.raw || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: $${value.toFixed(2)} (${percentage}%)`;
}
}
},
datalabels: {
color: '#fff',
formatter: (value, context) => {
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${percentage}%`;
},
font: {
weight: 'bold',
size: 14
}
}
}
},
plugins: [ChartDataLabels]
});
// Show results
document.getElementById('results').style.display = 'block';
}
function toggleTable() {
const table = document.getElementById('amortizationTable');
const button = document.querySelector('.toggle-table');
if (table.style.display === 'none' || table.style.display === '') {
table.style.display = 'block';
button.textContent = 'Hide Amortization Table';
} else {
table.style.display = 'none';
button.textContent = 'View Amortization Table';
}
}
function clearForm() {
document.getElementById('loanAmount').value = '';
document.getElementById('years').value = '';
document.getElementById('months').value = '';
document.getElementById('interestRate').value = '';
document.getElementById('compoundFrequency').value = 'monthly';
document.getElementById('paymentFrequency').value = 'monthly';
document.getElementById('loanedFees').value = '';
document.getElementById('upfrontFees').value = '';
document.getElementById('results').style.display = 'none';
document.getElementById('amortizationTable').style.display = 'none';
document.querySelector('.toggle-table').textContent = 'View Amortization Table';
if (breakdownChart) breakdownChart.destroy();
}
</script>
</body>
</html>