loan-calculator / index.html
sombochea's picture
undefined - Initial Deployment
3e4cfcb verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loan Calculator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
/* Custom scrollbar for schedule */
.schedule-container::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.schedule-container::-webkit-scrollbar-track {
background: #f1f1f1;
}
.schedule-container::-webkit-scrollbar-thumb {
background: #888;
border-radius: 3px;
}
.schedule-container::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Animation for results */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
.fade-in {
animation: fadeIn 0.5s ease-out forwards;
}
</style>
</head>
<body class="bg-gray-100 min-h-screen">
<div class="flex items-center justify-center min-h-screen">
<div class="w-full max-w-2xl mx-4">
<div class="bg-white rounded-xl shadow-xl overflow-hidden">
<div class="text-center p-6 bg-indigo-700 text-white">
<h1 class="text-3xl font-bold mb-2">Loan Calculator</h1>
<p class="opacity-90">Calculate your EMI and payment schedule</p>
</div>
<div class="p-6">
<div class="mb-6">
<label for="loanAmount" class="block text-gray-700 font-medium mb-2">Loan Amount ($)</label>
<div class="relative">
<span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">$</span>
<input type="number" id="loanAmount" class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="10,000" min="1">
</div>
</div>
<div class="mb-6">
<label for="interestRate" class="block text-gray-700 font-medium mb-2">Interest Rate (% p.a.)</label>
<div class="relative">
<span class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500">%</span>
<input type="number" id="interestRate" class="w-full pl-8 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="8.5" step="0.01" min="0">
</div>
</div>
<div class="mb-6">
<label for="loanTerm" class="block text-gray-700 font-medium mb-2">Loan Term</label>
<div class="flex gap-4">
<div class="flex-1">
<input type="number" id="loanTerm" class="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500" placeholder="5" min="1">
</div>
<select id="termType" class="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500">
<option value="years">Years</option>
<option value="months">Months</option>
</select>
</div>
</div>
<div class="mb-6">
<label class="block text-gray-700 font-medium mb-2">Payment Type</label>
<div class="grid grid-cols-2 gap-3">
<button id="emiBtn" class="payment-type-btn bg-indigo-600 text-white py-2 px-4 rounded-lg shadow-sm hover:bg-indigo-700 transition active:bg-indigo-800">
<i class="fas fa-calendar-alt mr-2"></i> EMI
</button>
<button id="otherBtn" class="payment-type-btn bg-gray-200 text-gray-700 py-2 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition" disabled>
<i class="fas fa-clock mr-2"></i> Other (Coming Soon)
</button>
</div>
</div>
<button id="calculateBtn" class="w-full bg-indigo-600 text-white py-3 px-4 rounded-lg font-medium hover:bg-indigo-700 transition active:bg-indigo-800 shadow-md">
Calculate <i class="fas fa-calculator ml-2"></i>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Results Modal -->
<div id="resultsModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 hidden z-50">
<div class="bg-white rounded-xl shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col">
<div class="p-6 bg-indigo-700 text-white flex justify-between items-center">
<h2 class="text-2xl font-bold">Loan Calculation Results</h2>
<button id="closeModal" class="text-white hover:text-gray-200 text-2xl">&times;</button>
</div>
<div class="p-6 overflow-y-auto flex-1">
<div class="grid grid-cols-2 gap-4 mb-6">
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Monthly Payment</p>
<p id="monthlyPayment" class="text-2xl font-bold text-indigo-600">$0</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Total Interest</p>
<p id="totalInterest" class="text-2xl font-bold text-indigo-600">$0</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Total Payment</p>
<p id="totalPayment" class="text-2xl font-bold text-indigo-600">$0</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg">
<p class="text-sm text-gray-500">Loan Term</p>
<p id="loanTermResult" class="text-2xl font-bold text-indigo-600">0 Months</p>
</div>
</div>
<div class="border-t pt-4">
<div class="flex justify-between items-center mb-4">
<h3 class="text-lg font-semibold text-gray-800">Payment Schedule</h3>
<button id="downloadBtn" class="text-indigo-600 hover:text-indigo-800 flex items-center">
<i class="fas fa-download mr-1"></i> Export
</button>
</div>
<div class="schedule-container max-h-96 overflow-y-auto">
<table class="w-full text-sm">
<thead class="sticky top-0 bg-gray-100">
<tr class="text-left text-gray-600 border-b">
<th class="py-2 px-3">#</th>
<th class="py-2 px-3">Payment</th>
<th class="py-2 px-3">Principal</th>
<th class="py-2 px-3">Interest</th>
<th class="py-2 px-3">Balance</th>
</tr>
</thead>
<tbody id="scheduleBody" class="divide-y divide-gray-200">
<!-- Schedule will be populated here -->
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const calculateBtn = document.getElementById('calculateBtn');
const emiBtn = document.getElementById('emiBtn');
const otherBtn = document.getElementById('otherBtn');
const resultsSection = document.getElementById('resultsSection');
const downloadBtn = document.getElementById('downloadBtn');
// Format currency
const formatCurrency = (amount) => {
return new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 2
}).format(amount);
};
// Calculate EMI
const calculateEMI = (principal, annualRate, termYears, termType) => {
let termMonths;
if (termType === 'years') {
termMonths = termYears * 12;
} else {
termMonths = termYears;
}
const monthlyRate = annualRate / 100 / 12;
const emi = principal * monthlyRate * Math.pow(1 + monthlyRate, termMonths) / (Math.pow(1 + monthlyRate, termMonths) - 1);
return {
emi: emi,
termMonths: termMonths,
totalPayment: emi * termMonths,
totalInterest: (emi * termMonths) - principal
};
};
// Generate payment schedule
const generateSchedule = (principal, annualRate, termMonths, emi) => {
const monthlyRate = annualRate / 100 / 12;
let balance = principal;
const schedule = [];
for (let i = 1; i <= termMonths; i++) {
const interest = balance * monthlyRate;
const principalPayment = emi - interest;
balance -= principalPayment;
// Ensure balance doesn't go negative due to rounding
if (i === termMonths && Math.abs(balance) < 0.01) {
balance = 0;
}
schedule.push({
month: i,
payment: emi,
principal: principalPayment,
interest: interest,
balance: balance > 0 ? balance : 0
});
}
return schedule;
};
// Modal elements
const resultsModal = document.getElementById('resultsModal');
const closeModal = document.getElementById('closeModal');
// Close modal handler
closeModal.addEventListener('click', function() {
resultsModal.classList.add('hidden');
});
// Close modal when clicking outside
resultsModal.addEventListener('click', function(e) {
if (e.target === resultsModal) {
resultsModal.classList.add('hidden');
}
});
// Calculate button click handler
calculateBtn.addEventListener('click', function() {
const loanAmount = parseFloat(document.getElementById('loanAmount').value);
const interestRate = parseFloat(document.getElementById('interestRate').value);
const loanTerm = parseFloat(document.getElementById('loanTerm').value);
const termType = document.getElementById('termType').value;
// Validate inputs
if (isNaN(loanAmount) || isNaN(interestRate) || isNaN(loanTerm) || loanAmount <= 0 || loanTerm <= 0) {
alert('Please enter valid values for all fields');
return;
}
// Calculate EMI
const result = calculateEMI(loanAmount, interestRate, loanTerm, termType);
// Update summary
document.getElementById('monthlyPayment').textContent = formatCurrency(result.emi);
document.getElementById('totalInterest').textContent = formatCurrency(result.totalInterest);
document.getElementById('totalPayment').textContent = formatCurrency(result.totalPayment);
document.getElementById('loanTermResult').textContent = `${result.termMonths} Months`;
// Generate and display schedule
const schedule = generateSchedule(loanAmount, interestRate, result.termMonths, result.emi);
const scheduleBody = document.getElementById('scheduleBody');
scheduleBody.innerHTML = '';
schedule.forEach((payment, index) => {
const row = document.createElement('tr');
row.className = index % 2 === 0 ? 'bg-white' : 'bg-gray-50';
row.innerHTML = `
<td class="py-2 px-3">${payment.month}</td>
<td class="py-2 px-3">${formatCurrency(payment.payment)}</td>
<td class="py-2 px-3">${formatCurrency(payment.principal)}</td>
<td class="py-2 px-3">${formatCurrency(payment.interest)}</td>
<td class="py-2 px-3">${formatCurrency(payment.balance)}</td>
`;
scheduleBody.appendChild(row);
});
// Show modal with results
resultsModal.classList.remove('hidden');
});
// Payment type buttons
emiBtn.addEventListener('click', function() {
emiBtn.classList.remove('bg-gray-200', 'text-gray-700');
emiBtn.classList.add('bg-indigo-600', 'text-white');
otherBtn.classList.remove('bg-indigo-600', 'text-white');
otherBtn.classList.add('bg-gray-200', 'text-gray-700');
});
otherBtn.addEventListener('click', function() {
alert('Other payment types coming soon! Currently only EMI is available.');
});
// Export schedule
downloadBtn.addEventListener('click', function() {
const loanAmount = document.getElementById('loanAmount').value;
const interestRate = document.getElementById('interestRate').value;
const loanTerm = document.getElementById('loanTerm').value;
const termType = document.getElementById('termType').value;
if (!resultsSection.classList.contains('hidden')) {
// Create CSV content
let csv = 'Payment #,Payment,Principal,Interest,Balance\n';
const rows = document.querySelectorAll('#scheduleBody tr');
rows.forEach(row => {
const cols = row.querySelectorAll('td');
const rowData = [];
cols.forEach(col => {
// Remove currency symbols and commas for CSV
const text = col.textContent.replace(/[$,]/g, '');
rowData.push(text);
});
csv += rowData.join(',') + '\n';
});
// Create download link
const blob = new Blob([csv], { type: 'text/csv' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.setAttribute('hidden', '');
a.setAttribute('href', url);
a.setAttribute('download', `loan_schedule_${loanAmount}_${interestRate}_${loanTerm}${termType === 'years' ? 'y' : 'm'}.csv`);
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
} else {
alert('Please calculate a loan first');
}
});
// Add animation to results when they appear
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('fade-in');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('#resultsSection > div').forEach(el => {
observer.observe(el);
});
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=sombochea/loan-calculator" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>