diff --git a/Projects/EMI Loan Planner/README.md b/Projects/EMI Loan Planner/README.md
new file mode 100644
index 00000000..1fbf4c1f
--- /dev/null
+++ b/Projects/EMI Loan Planner/README.md
@@ -0,0 +1,53 @@
+# EMI Loan Planner
+
+An interactive personal finance utility and loan amortization planner. It calculates Equated Monthly Installments (EMI), models standard and custom prepayments (monthly, annual, and one-time), projects interest and tenure savings, and draws canvas-based visuals.
+
+---
+
+## Amortization Mathematics & Prepayments
+
+The calculator runs dual simulations (a baseline without prepayments vs. an active plan with prepayments) to evaluate savings.
+
+### 1. Equated Monthly Installment (EMI)
+Calculated using the standard compounding annuity formula:
+
+$$EMI = P \times r \times \frac{(1 + r)^n}{(1 + r)^n - 1}$$
+
+Where:
+- $P$: Loan principal amount.
+- $r$: Monthly interest rate ($R_{\text{annual}} / 12 / 100$).
+- $n$: Loan tenure in months ($\text{Years} \times 12$).
+
+### 2. Month-by-Month Amortization Schedule
+For each month $m = 1, 2, ...$:
+- **Interest Portion**:
+ $$Interest_m = Balance_{m-1} \times r$$
+- **Principal Portion**:
+ $$Principal\_Paid_m = \min(EMI - Interest_m, Balance_{m-1})$$
+- **Extra Prepayment ($PP_m$)**: Any extra principal prepaid at month $m$.
+- **Ending Balance**:
+ $$Balance_m = Balance_{m-1} - Principal\_Paid_m - PP_m$$
+
+The loop terminates when $Balance_m \le 0$ or when the max term is reached.
+
+### 3. Savings Calculations
+- **Interest Saved**:
+ $$\text{Interest Saved} = \sum Interest_{\text{baseline}} - \sum Interest_{\text{active}}$$
+- **Tenure Saved**:
+ $$\text{Tenure Saved} = Tenure_{\text{baseline}} - Tenure_{\text{active}} \quad (\text{months})$$
+
+---
+
+## Prepayment Modes Supported
+
+1. **Monthly Prepayment**: An extra fixed amount added to the principal repayment every month.
+2. **Annual Prepayment**: An extra lump sum paid at the end of each year (every 12th month).
+3. **One-Time Prepayment**: A single lump sum payment credited at a specific month/year.
+
+---
+
+## Standalone Execution
+
+1. Clone or download this directory.
+2. Open `index.html` in any modern web browser to launch.
+3. No server or internet connection required.
diff --git a/Projects/EMI Loan Planner/index.html b/Projects/EMI Loan Planner/index.html
new file mode 100644
index 00000000..565456d4
--- /dev/null
+++ b/Projects/EMI Loan Planner/index.html
@@ -0,0 +1,239 @@
+
+
+
+
+
+ EMI Loan Planner - Dynamic Amortization Workspace
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 💵 CORE LOAN DETAILS
+
+
+
+
Loan Amount (Principal)
+
+ $
+
+
+
+
+
Total borrowing amount (principal).
+
+
+
+
+
Interest Rate (p.a.)
+
+
+ %
+
+
+
+
Annual compounding rate percentage.
+
+
+
+
+
Tenure (Years)
+
+
+ Y
+
+
+
+
Loan duration in years.
+
+
+
+
+
+ ⏳ PREPAYMENT SAVINGS MODELER
+
+
+
+
Monthly Extra Payment
+
+ $
+
+
+
+
+
Additional amount paid every single month.
+
+
+
+
+
Annual Extra Payment
+
+ $
+
+
+
+
+
Additional lump sum paid at the end of each year.
+
+
+
+
+
One-Time Prepayment
+
+ $
+
+
+
+
+
A single one-off payment credited once.
+
+
+
+
+
One-Time Prepay Month
+
+
+ M
+
+
+
+
Month index when the one-time prepayment is made.
+
+
+
+
+
+
+
+
+
+
+ MONTHLY EMI PAYMENT
+ $0
+ Baseline EMI: $0
+
+
+ TOTAL INTEREST PAYABLE
+ $0
+ 0% of total payment
+
+
+ TOTAL INTEREST SAVED
+ $0
+ Total Prepayments: $0
+
+
+ TENURE TIME SAVED
+ 0 Months
+ Paid in 0 years
+
+
+
+
+
+
+
TOTAL LOAN COST DIVISION
+
+
+
+
+
Principal: 0.0%
+
Interest: 0.0%
+
Prepayments: 0.0%
+
+
+
+
+
AMORTIZATION TIMELINE & BALANCE CURVE
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Starting Balance
+ EMI Paid
+ Prepayment
+ Principal Paid
+ Interest Paid
+ Ending Balance
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Projects/EMI Loan Planner/project.json b/Projects/EMI Loan Planner/project.json
new file mode 100644
index 00000000..3248e722
--- /dev/null
+++ b/Projects/EMI Loan Planner/project.json
@@ -0,0 +1,17 @@
+{
+ "title": "EMI Loan Planner",
+ "description": "A premium personal finance workspace to calculate EMI, simulate regular/one-time prepayments, track interest savings, and export amortization schedules.",
+ "entry": "index.html",
+ "tags": [
+ "finance",
+ "calculator",
+ "loan-planner",
+ "data-visualization",
+ "canvas"
+ ],
+ "author": {
+ "name": "Sujal Kamate",
+ "github": "SujalKamate"
+ },
+ "thumbnail": "thumbnail.svg"
+}
diff --git a/Projects/EMI Loan Planner/script.js b/Projects/EMI Loan Planner/script.js
new file mode 100644
index 00000000..f438c250
--- /dev/null
+++ b/Projects/EMI Loan Planner/script.js
@@ -0,0 +1,717 @@
+/**
+ * EMI LOAN PLANNER
+ * Core Logic: Monthly Compounding, Extra Prepayments, Custom Charting & Amortization Scheduling
+ */
+
+// --- Global Simulation State ---
+const state = {
+ loanAmount: 250000,
+ interestRate: 6.5,
+ tenure: 30, // years
+
+ // Prepayment sliders
+ monthlyPrepay: 0,
+ annualPrepay: 0,
+ onetimePrepay: 0,
+ onetimeMonth: 12,
+
+ // Table view mode
+ viewMode: 'YEARLY', // 'YEARLY' or 'MONTHLY'
+
+ // Calculations
+ emi: 0,
+ baselineInterest: 0,
+ activeInterest: 0,
+ savedInterest: 0,
+ baselineMonths: 360,
+ activeMonths: 360,
+ savedTenureMonths: 0,
+ totalPrepayments: 0,
+
+ // Schedule matrices
+ monthlySchedule: [],
+ yearlySchedule: [],
+
+ // Canvas
+ donutCanvas: null,
+ donutCtx: null,
+ areaCanvas: null,
+ areaCtx: null,
+
+ // Hover indices
+ hoverTimeIndex: -1 // year or month index depending on viewMode
+};
+
+// Preset Profiles
+const presets = {
+ home: { loanAmount: 250000, interestRate: 6.5, tenure: 30 },
+ car: { loanAmount: 30000, interestRate: 5.0, tenure: 5 },
+ student: { loanAmount: 50000, interestRate: 4.5, tenure: 10 },
+ personal: { loanAmount: 15000, interestRate: 12.0, tenure: 3 }
+};
+
+// --- Initialization ---
+document.addEventListener('DOMContentLoaded', () => {
+ initDOMReferences();
+ setupSyncListeners();
+ setupCanvasElements();
+ loadPresetValues('home'); // load default mortgage preset
+ recalculateAll();
+});
+
+function initDOMReferences() {
+ // Preset Select
+ document.getElementById('presetSelect').addEventListener('change', (e) => {
+ if (e.target.value !== 'custom') {
+ loadPresetValues(e.target.value);
+ recalculateAll();
+ }
+ });
+
+ // Table View mode buttons
+ const viewAnnual = document.getElementById('viewAnnualBtn');
+ const viewMonthly = document.getElementById('viewMonthlyBtn');
+
+ viewAnnual.addEventListener('click', () => {
+ if (state.viewMode === 'YEARLY') return;
+ state.viewMode = 'YEARLY';
+ viewAnnual.classList.add('active');
+ viewMonthly.classList.remove('active');
+ document.getElementById('colTimeHeader').innerText = 'Year';
+ buildAmortizationTable();
+ drawAreaChart();
+ });
+
+ viewMonthly.addEventListener('click', () => {
+ if (state.viewMode === 'MONTHLY') return;
+ state.viewMode = 'MONTHLY';
+ viewMonthly.classList.add('active');
+ viewAnnual.classList.remove('active');
+ document.getElementById('colTimeHeader').innerText = 'Month';
+ buildAmortizationTable();
+ drawAreaChart();
+ });
+
+ // Export CSV
+ document.getElementById('downloadCsvBtn').addEventListener('click', exportCSV);
+
+ // Area Chart Interactivity
+ const areaChart = document.getElementById('areaChart');
+ areaChart.addEventListener('mousemove', handleChartHover);
+ areaChart.addEventListener('mouseleave', clearChartHover);
+}
+
+function setupCanvasElements() {
+ state.donutCanvas = document.getElementById('donutChart');
+ state.donutCtx = state.donutCanvas.getContext('2d');
+
+ state.areaCanvas = document.getElementById('areaChart');
+ state.areaCtx = state.areaCanvas.getContext('2d');
+}
+
+// Synchronize sliders and numeric input boxes
+function setupSyncListeners() {
+ const syncWidgets = (sliderId, boxId, stateKey, isFloat = false) => {
+ const slider = document.getElementById(sliderId);
+ const box = document.getElementById(boxId);
+
+ slider.addEventListener('input', (e) => {
+ const val = isFloat ? parseFloat(e.target.value) : parseInt(e.target.value);
+ state[stateKey] = val;
+ box.value = val;
+
+ document.getElementById('presetSelect').value = 'custom';
+ recalculateAll();
+ });
+
+ box.addEventListener('input', (e) => {
+ let val = isFloat ? parseFloat(e.target.value) : parseInt(e.target.value);
+ if (isNaN(val) || val < 0) val = 0;
+ state[stateKey] = val;
+ slider.value = Math.min(slider.max, val); // bound check slider
+
+ document.getElementById('presetSelect').value = 'custom';
+ recalculateAll();
+ });
+ };
+
+ syncWidgets('inputLoanAmount', 'boxLoanAmount');
+ syncWidgets('inputInterestRate', 'boxInterestRate', 'interestRate', true);
+ syncWidgets('inputTenure', 'boxTenure');
+
+ // Prepayments
+ syncWidgets('inputMonthlyPrepay', 'boxMonthlyPrepay');
+ syncWidgets('inputAnnualPrepay', 'boxAnnualPrepay');
+ syncWidgets('inputOnetimePrepay', 'boxOnetimePrepay');
+ syncWidgets('inputOnetimeMonth', 'boxOnetimeMonth');
+}
+
+function loadPresetValues(name) {
+ const p = presets[name];
+ if (!p) return;
+
+ state.loanAmount = p.loanAmount;
+ state.interestRate = p.interestRate;
+ state.tenure = p.tenure;
+
+ // Sync inputs
+ const syncValues = (sliderId, boxId, val) => {
+ document.getElementById(sliderId).value = val;
+ document.getElementById(boxId).value = val;
+ };
+
+ syncValues('inputLoanAmount', 'boxLoanAmount', p.loanAmount);
+ syncValues('inputInterestRate', 'boxInterestRate', p.interestRate);
+ syncValues('inputTenure', 'boxTenure', p.tenure);
+
+ // Set prepayments back to 0 by default when a preset is loaded
+ state.monthlyPrepay = 0;
+ state.annualPrepay = 0;
+ state.onetimePrepay = 0;
+ state.onetimeMonth = 12;
+
+ syncValues('inputMonthlyPrepay', 'boxMonthlyPrepay', 0);
+ syncValues('inputAnnualPrepay', 'boxAnnualPrepay', 0);
+ syncValues('inputOnetimePrepay', 'boxOnetimePrepay', 0);
+ syncValues('inputOnetimeMonth', 'boxOnetimeMonth', 12);
+}
+
+// --- Compounding Calculation Engine ---
+
+function recalculateAll() {
+ const monthlyRate = state.interestRate / 12 / 100;
+ const totalMonths = state.tenure * 12;
+
+ // 1. Calculate base EMI payment
+ if (monthlyRate === 0) {
+ state.emi = state.loanAmount / totalMonths;
+ } else {
+ state.emi = state.loanAmount * monthlyRate * Math.pow(1 + monthlyRate, totalMonths) / (Math.pow(1 + monthlyRate, totalMonths) - 1);
+ }
+
+ // 2. Perform Baseline Run (No Prepayments)
+ runBaselineSimulation(monthlyRate, totalMonths);
+
+ // 3. Perform Active Run (With Prepayments)
+ runActiveSimulation(monthlyRate, totalMonths);
+
+ // 4. Summarize yearly matrix
+ buildYearlySummary();
+
+ // 5. Update Indicators
+ updateUI();
+}
+
+function runBaselineSimulation(r, totalMonths) {
+ let balance = state.loanAmount;
+ let accumulatedInterest = 0;
+ let monthsCount = 0;
+
+ for (let m = 1; m <= totalMonths; m++) {
+ const interest = balance * r;
+ const principal = Math.min(state.emi - interest, balance);
+
+ accumulatedInterest += interest;
+ balance -= principal;
+ monthsCount++;
+
+ if (balance <= 0) break;
+ }
+
+ state.baselineInterest = accumulatedInterest;
+ state.baselineMonths = monthsCount;
+}
+
+function runActiveSimulation(r, totalMonths) {
+ state.monthlySchedule = [];
+ state.totalPrepayments = 0;
+
+ let balance = state.loanAmount;
+ let accumulatedInterest = 0;
+ let monthsCount = 0;
+
+ for (let m = 1; m <= totalMonths; m++) {
+ const startingBalance = balance;
+ const interest = balance * r;
+ const basePrincipal = Math.min(state.emi - interest, balance);
+
+ let prepayment = 0;
+
+ // Evaluate extra prepayments
+ if (balance - basePrincipal > 0) {
+ // Monthly extra
+ prepayment += Math.min(state.monthlyPrepay, balance - basePrincipal);
+
+ // Annual extra (ends of year)
+ if (m % 12 === 0) {
+ prepayment += Math.min(state.annualPrepay, balance - basePrincipal - prepayment);
+ }
+
+ // One-time extra
+ if (m === state.onetimeMonth) {
+ prepayment += Math.min(state.onetimePrepay, balance - basePrincipal - prepayment);
+ }
+ }
+
+ const principalPaid = basePrincipal + prepayment;
+ const endingBalance = Math.max(0, balance - principalPaid);
+
+ accumulatedInterest += interest;
+ state.totalPrepayments += prepayment;
+
+ state.monthlySchedule.push({
+ month: m,
+ startingBalance: startingBalance,
+ emiPaid: basePrincipal + interest,
+ prepayment: prepayment,
+ principalPaid: principalPaid,
+ interestPaid: interest,
+ endingBalance: endingBalance
+ });
+
+ balance = endingBalance;
+ monthsCount++;
+
+ if (balance <= 0) break;
+ }
+
+ state.activeInterest = accumulatedInterest;
+ state.activeMonths = monthsCount;
+
+ // Calculate final savings
+ state.savedInterest = Math.max(0, state.baselineInterest - state.activeInterest);
+ state.savedTenureMonths = Math.max(0, state.baselineMonths - state.activeMonths);
+}
+
+function buildYearlySummary() {
+ state.yearlySchedule = [];
+
+ let yearNum = 1;
+ let yearPrincipal = 0;
+ let yearInterest = 0;
+ let yearPrepay = 0;
+ let yearEmi = 0;
+ let startingBalance = state.loanAmount;
+
+ state.monthlySchedule.forEach((row, idx) => {
+ yearPrincipal += row.principalPaid;
+ yearInterest += row.interestPaid;
+ yearPrepay += row.prepayment;
+ yearEmi += row.emiPaid;
+
+ // End of year or last record
+ if (row.month % 12 === 0 || idx === state.monthlySchedule.length - 1) {
+ state.yearlySchedule.push({
+ year: yearNum,
+ startingBalance: startingBalance,
+ emiPaid: yearEmi,
+ prepayment: yearPrepay,
+ principalPaid: yearPrincipal,
+ interestPaid: yearInterest,
+ endingBalance: row.endingBalance
+ });
+
+ // Reset
+ yearNum++;
+ yearPrincipal = 0;
+ yearInterest = 0;
+ yearPrepay = 0;
+ yearEmi = 0;
+ startingBalance = row.endingBalance;
+ }
+ });
+}
+
+// --- UI Indicators ---
+
+function updateUI() {
+ const formatCurrency = (val) => '$' + Math.round(val).toLocaleString();
+
+ // Primary KPIs
+ document.getElementById('kpiEmi').innerText = formatCurrency(state.emi);
+ document.getElementById('kpiBaseEmi').innerText = `Baseline EMI: ${formatCurrency(state.emi)}`;
+
+ document.getElementById('kpiInterest').innerText = formatCurrency(state.activeInterest);
+ const totalPaid = state.loanAmount + state.activeInterest + state.totalPrepayments;
+ const interestPct = totalPaid > 0 ? (state.activeInterest / totalPaid * 100) : 0;
+ document.getElementById('kpiInterestPct').innerText = `${interestPct.toFixed(1)}% of total payment`;
+
+ document.getElementById('kpiSavedInterest').innerText = formatCurrency(state.savedInterest);
+ document.getElementById('kpiPrepaymentTotal').innerText = `Total Prepayments: ${formatCurrency(state.totalPrepayments)}`;
+
+ // Tenure saved
+ const yearsSaved = Math.floor(state.savedTenureMonths / 12);
+ const monthsSaved = state.savedTenureMonths % 12;
+ let savedString = '';
+ if (yearsSaved > 0) savedString += `${yearsSaved} Yr `;
+ savedString += `${monthsSaved} Mo`;
+ document.getElementById('kpiSavedTenure').innerText = state.savedTenureMonths > 0 ? savedString : '0 Months';
+
+ const activeY = (state.activeMonths / 12).toFixed(1);
+ document.getElementById('kpiActiveTenure').innerText = `Paid in ${activeY} years`;
+
+ // Donut Percentages
+ const principalPct = totalPaid > 0 ? (state.loanAmount / totalPaid * 100) : 0;
+ const prepayPct = totalPaid > 0 ? (state.totalPrepayments / totalPaid * 100) : 0;
+
+ document.getElementById('legendPctPrincipal').innerText = principalPct.toFixed(1) + '%';
+ document.getElementById('legendPctInterest').innerText = interestPct.toFixed(1) + '%';
+ document.getElementById('legendPctPrepay').innerText = prepayPct.toFixed(1) + '%';
+
+ // Build table view
+ buildAmortizationTable();
+
+ // Draw Charts
+ drawDonutChart(principalPct, interestPct, prepayPct);
+ drawAreaChart();
+}
+
+function buildAmortizationTable() {
+ const body = document.getElementById('tableBody');
+ body.innerHTML = '';
+
+ const formatCurrency = (val) => '$' + Math.round(val).toLocaleString();
+ const schedule = state.viewMode === 'YEARLY' ? state.yearlySchedule : state.monthlySchedule;
+
+ schedule.forEach(row => {
+ const tr = document.createElement('tr');
+ const label = state.viewMode === 'YEARLY' ? `Year ${row.year}` : `Month ${row.month}`;
+ tr.innerHTML = `
+ ${label}
+ ${formatCurrency(row.startingBalance)}
+ ${formatCurrency(row.emiPaid)}
+ ${formatCurrency(row.prepayment)}
+ ${formatCurrency(row.principalPaid)}
+ ${formatCurrency(row.interestPaid)}
+ ${formatCurrency(row.endingBalance)}
+ `;
+ body.appendChild(tr);
+ });
+}
+
+// --- Canvas Charts drawing ---
+
+function drawDonutChart(principalPct, interestPct, prepayPct) {
+ const ctx = state.donutCtx;
+ const w = state.donutCanvas.width;
+ const h = state.donutCanvas.height;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const cx = w / 2;
+ const cy = h / 2;
+ const radius = 80;
+ const thickness = 22;
+
+ const startAng = -Math.PI / 2;
+ const principalRad = (principalPct / 100) * (Math.PI * 2);
+ const interestRad = (interestPct / 100) * (Math.PI * 2);
+ const prepayRad = (prepayPct / 100) * (Math.PI * 2);
+
+ // 1. Principal Segment (Indigo Blue)
+ ctx.strokeStyle = '#3d5afe';
+ ctx.lineWidth = thickness;
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, startAng, startAng + principalRad);
+ ctx.stroke();
+
+ // 2. Interest Segment (Gold Amber)
+ ctx.strokeStyle = '#ffb300';
+ ctx.lineWidth = thickness;
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, startAng + principalRad, startAng + principalRad + interestRad);
+ ctx.stroke();
+
+ // 3. Prepayments Segment (Cyan)
+ if (prepayRad > 0) {
+ ctx.strokeStyle = '#00e5ff';
+ ctx.lineWidth = thickness;
+ ctx.beginPath();
+ ctx.arc(cx, cy, radius, startAng + principalRad + interestRad, startAng + Math.PI * 2);
+ ctx.stroke();
+ }
+
+ // Text center values
+ ctx.fillStyle = '#f8fafc';
+ ctx.font = 'bold 15px Outfit';
+ ctx.textAlign = 'center';
+ ctx.textBaseline = 'middle';
+
+ const totalCost = state.loanAmount + state.activeInterest + state.totalPrepayments;
+ ctx.fillText('$' + Math.round(totalCost / 1000).toLocaleString() + 'k', cx, cy - 8);
+
+ ctx.fillStyle = '#94a3b8';
+ ctx.font = '8px Outfit';
+ ctx.fillText('TOTAL COST OF LOAN', cx, cy + 12);
+}
+
+function drawAreaChart() {
+ const ctx = state.areaCtx;
+ const w = state.areaCanvas.width;
+ const h = state.areaCanvas.height;
+
+ ctx.clearRect(0, 0, w, h);
+
+ const paddingLeft = 60;
+ const paddingRight = 20;
+ const paddingTop = 20;
+ const paddingBottom = 30;
+
+ const chartW = w - paddingLeft - paddingRight;
+ const chartH = h - paddingTop - paddingBottom;
+
+ const schedule = state.viewMode === 'YEARLY' ? state.yearlySchedule : state.monthlySchedule;
+ const size = schedule.length;
+ if (size === 0) return;
+
+ const totalTerms = state.viewMode === 'YEARLY' ? state.tenure : state.tenure * 12;
+ const maxVal = state.loanAmount;
+
+ const getX = (t) => paddingLeft + (t / totalTerms) * chartW;
+ const getY = (v) => h - paddingBottom - (v / maxVal) * chartH;
+
+ // Grid Lines
+ ctx.strokeStyle = 'rgba(255,255,255,0.03)';
+ ctx.lineWidth = 1;
+ for (let i = 1; i <= 4; i++) {
+ const val = (maxVal / 4) * i;
+ const y = getY(val);
+ ctx.beginPath();
+ ctx.moveTo(paddingLeft, y);
+ ctx.lineTo(w - paddingRight, y);
+ ctx.stroke();
+
+ ctx.fillStyle = '#64748b';
+ ctx.font = '9.5px monospace';
+ ctx.textAlign = 'right';
+ ctx.fillText('$' + Math.round(val / 1000).toLocaleString() + 'k', paddingLeft - 8, y + 3);
+ }
+
+ // X-axis Markers
+ const step = Math.max(1, Math.round(totalTerms / 5));
+ for (let t = 0; t <= totalTerms; t += step) {
+ const x = getX(t);
+ ctx.beginPath();
+ ctx.moveTo(x, paddingTop);
+ ctx.lineTo(x, h - paddingBottom);
+ ctx.stroke();
+
+ ctx.fillStyle = '#94a3b8';
+ ctx.font = '10px Outfit';
+ ctx.textAlign = 'center';
+ const label = state.viewMode === 'YEARLY' ? `Yr ${t}` : `Mo ${t}`;
+ ctx.fillText(label, x, h - paddingBottom + 16);
+ }
+
+ // Draw Area for Declining Balance (Active Prepayment curve)
+ // Fill under active balance
+ const activePoints = [{ x: getX(0), y: getY(state.loanAmount) }];
+ schedule.forEach(row => {
+ const idx = state.viewMode === 'YEARLY' ? row.year : row.month;
+ activePoints.push({ x: getX(idx), y: getY(row.endingBalance) });
+ });
+
+ ctx.fillStyle = 'rgba(61, 90, 254, 0.15)';
+ ctx.beginPath();
+ ctx.moveTo(getX(0), getY(0));
+ activePoints.forEach(pt => ctx.lineTo(pt.x, pt.y));
+ ctx.lineTo(activePoints[activePoints.length - 1].x, getY(0));
+ ctx.closePath();
+ ctx.fill();
+
+ // Draw Stroke line curves
+ // 1. Baseline curve (No prepayment - dashed gray)
+ ctx.strokeStyle = '#475569';
+ ctx.lineWidth = 1.5;
+ ctx.setLineDash([3, 3]);
+ ctx.beginPath();
+ ctx.moveTo(getX(0), getY(state.loanAmount));
+
+ // Calculate simple baseline curve coordinates
+ const r = state.interestRate / 12 / 100;
+ const baseTotalMonths = state.tenure * 12;
+ let baseB = state.loanAmount;
+
+ if (state.viewMode === 'YEARLY') {
+ for (let y = 1; y <= state.tenure; y++) {
+ for (let m = 1; m <= 12; m++) {
+ const interest = baseB * r;
+ const principal = Math.min(state.emi - interest, baseB);
+ baseB = Math.max(0, baseB - principal);
+ }
+ ctx.lineTo(getX(y), getY(baseB));
+ }
+ } else {
+ for (let m = 1; m <= baseTotalMonths; m++) {
+ const interest = baseB * r;
+ const principal = Math.min(state.emi - interest, baseB);
+ baseB = Math.max(0, baseB - principal);
+ ctx.lineTo(getX(m), getY(baseB));
+ }
+ }
+ ctx.stroke();
+ ctx.setLineDash([]); // clear dash
+
+ // 2. Active prepayment curve (Solid Indigo Blue)
+ ctx.strokeStyle = '#3d5afe';
+ ctx.lineWidth = 3;
+ ctx.beginPath();
+ ctx.moveTo(activePoints[0].x, activePoints[0].y);
+ for (let i = 1; i < activePoints.length; i++) {
+ ctx.lineTo(activePoints[i].x, activePoints[i].y);
+ }
+ ctx.stroke();
+
+ // Border bounds
+ ctx.strokeStyle = 'rgba(63, 81, 181, 0.15)';
+ ctx.lineWidth = 1;
+ ctx.strokeRect(paddingLeft, paddingTop, chartW, chartH);
+
+ // If hovered crosshair is active
+ if (state.hoverTimeIndex !== -1 && state.hoverTimeIndex <= totalTerms) {
+ drawHoverLine(getX, getY, paddingLeft, paddingTop, chartH, totalTerms);
+ }
+}
+
+// --- Hover Details Line crosshairs ---
+
+function handleChartHover(e) {
+ const rect = state.areaCanvas.getBoundingClientRect();
+ const mx = e.clientX - rect.left;
+ const w = state.areaCanvas.width;
+
+ const paddingLeft = 60;
+ const paddingRight = 20;
+ const chartW = w - paddingLeft - paddingRight;
+
+ if (mx < paddingLeft || mx > w - paddingRight || state.monthlySchedule.length === 0) {
+ clearChartHover();
+ return;
+ }
+
+ const totalTerms = state.viewMode === 'YEARLY' ? state.tenure : state.tenure * 12;
+ const relativeX = (mx - paddingLeft) / chartW;
+ const timeHover = Math.round(relativeX * totalTerms);
+
+ if (timeHover !== state.hoverTimeIndex) {
+ state.hoverTimeIndex = timeHover;
+ drawAreaChart();
+ }
+}
+
+function drawHoverLine(getX, getY, paddingLeft, paddingTop, chartH, totalTerms) {
+ const ctx = state.areaCtx;
+ const w = state.areaCanvas.width;
+ const tIdx = state.hoverTimeIndex;
+
+ const x = getX(tIdx);
+
+ // vertical line
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.25)';
+ ctx.lineWidth = 1;
+ ctx.setLineDash([3, 3]);
+ ctx.beginPath();
+ ctx.moveTo(x, paddingTop);
+ ctx.lineTo(x, paddingTop + chartH);
+ ctx.stroke();
+ ctx.setLineDash([]);
+
+ // Fetch projection record for this index
+ const schedule = state.viewMode === 'YEARLY' ? state.yearlySchedule : state.monthlySchedule;
+ const key = state.viewMode === 'YEARLY' ? 'year' : 'month';
+
+ let record = schedule.find(r => r[key] === tIdx);
+ let balance = state.loanAmount;
+ let principalPaid = 0;
+ let interestPaid = 0;
+ let prepayment = 0;
+
+ if (tIdx > 0) {
+ if (record) {
+ balance = record.endingBalance;
+ principalPaid = record.principalPaid;
+ interestPaid = record.interestPaid;
+ prepayment = record.prepayment;
+ } else {
+ // Loan already paid off by this time
+ balance = 0;
+ }
+ }
+
+ // Draw dots
+ ctx.fillStyle = '#3d5afe';
+ ctx.beginPath();
+ ctx.arc(x, getY(balance), 4.5, 0, Math.PI * 2);
+ ctx.fill();
+
+ // Tooltip Box
+ const formatCurrency = (val) => '$' + Math.round(val).toLocaleString();
+
+ ctx.fillStyle = 'rgba(9, 14, 23, 0.95)';
+ ctx.strokeStyle = 'var(--color-border)';
+ ctx.lineWidth = 1.5;
+
+ const boxW = 125;
+ const boxH = 68;
+ const boxX = x > w / 2 ? x - boxW - 10 : x + 10;
+ const boxY = paddingTop + 10;
+
+ ctx.fillRect(boxX, boxY, boxW, boxH);
+ ctx.strokeRect(boxX, boxY, boxW, boxH);
+
+ ctx.font = 'bold 8.5px monospace';
+ ctx.fillStyle = 'var(--color-primary)';
+ ctx.textAlign = 'left';
+
+ const label = state.viewMode === 'YEARLY' ? `YEAR ${tIdx}` : `MONTH ${tIdx}`;
+ ctx.fillText(label, boxX + 8, boxY + 12);
+
+ ctx.font = '8px monospace';
+ ctx.fillStyle = 'var(--color-text-secondary)';
+ ctx.fillText(`Bal Remaining: ${formatCurrency(balance)}`, boxX + 8, boxY + 24);
+
+ if (record) {
+ ctx.fillStyle = 'var(--color-primary)';
+ ctx.fillText(`Principal Paid: ${formatCurrency(principalPaid)}`, boxX + 8, boxY + 35);
+ ctx.fillStyle = 'var(--color-interest)';
+ ctx.fillText(`Interest Paid: ${formatCurrency(interestPaid)}`, boxX + 8, boxY + 46);
+ ctx.fillStyle = 'var(--color-prepay)';
+ ctx.fillText(`Prepayment: ${formatCurrency(prepayment)}`, boxX + 8, boxY + 57);
+ } else {
+ ctx.fillStyle = 'var(--color-savings)';
+ ctx.fillText('LOAN FULLY PAID OFF', boxX + 8, boxY + 35);
+ }
+}
+
+function clearChartHover() {
+ state.hoverTimeIndex = -1;
+ drawAreaChart();
+}
+
+// --- CSV Exporter ---
+
+function exportCSV() {
+ if (state.monthlySchedule.length === 0) return;
+
+ let csv = 'Payment Month,Starting Balance,Regular EMI Paid,Extra Prepayment,Principal Component,Interest Component,Ending Balance\n';
+
+ state.monthlySchedule.forEach(row => {
+ csv += `${row.month},`;
+ csv += `${Math.round(row.startingBalance)},`;
+ csv += `${Math.round(row.emiPaid)},`;
+ csv += `${Math.round(row.prepayment)},`;
+ csv += `${Math.round(row.principalPaid)},`;
+ csv += `${Math.round(row.interestPaid)},`;
+ csv += `${Math.round(row.endingBalance)}\n`;
+ });
+
+ const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
+ const url = URL.createObjectURL(blob);
+ const link = document.createElement('a');
+ link.setAttribute('href', url);
+ link.setAttribute('download', `loan_amortization_schedule_${state.tenure}_years.csv`);
+ link.style.visibility = 'hidden';
+ document.body.appendChild(link);
+ link.click();
+ document.body.removeChild(link);
+}
diff --git a/Projects/EMI Loan Planner/style.css b/Projects/EMI Loan Planner/style.css
new file mode 100644
index 00000000..0d04fb94
--- /dev/null
+++ b/Projects/EMI Loan Planner/style.css
@@ -0,0 +1,623 @@
+/* Style Sheet: EMI Loan Planner */
+
+:root {
+ /* Colors */
+ --color-bg-dark: #080a0f;
+ --color-bg-panel: rgba(14, 21, 33, 0.82);
+ --color-bg-element: rgba(22, 34, 52, 0.9);
+ --color-border: rgba(63, 81, 181, 0.15);
+ --color-border-active: rgba(63, 81, 181, 0.45);
+
+ --color-text-primary: #f8fafc;
+ --color-text-secondary: #94a3b8;
+ --color-text-muted: #475569;
+
+ /* Financial Layout Theme Colors */
+ --color-primary: #3d5afe; /* Principal - Electric Indigo/Blue */
+ --color-primary-dim: rgba(61, 90, 254, 0.15);
+
+ --color-interest: #ffb300; /* Interest portion - Gold/Amber */
+ --color-interest-dim: rgba(255, 179, 0, 0.15);
+
+ --color-prepay: #00e5ff; /* Prepayments - Cyan */
+
+ --color-savings: #00e676; /* Savings - Hazard Green */
+ --color-savings-dim: rgba(0, 230, 118, 0.15);
+
+ /* Fonts */
+ --font-title: 'Outfit', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+ --font-body: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
+
+ /* Layout */
+ --radius-lg: 12px;
+ --radius-md: 8px;
+ --radius-sm: 4px;
+ --shadow-neon: 0 0 15px rgba(61, 90, 254, 0.15);
+}
+
+/* Base Settings */
+* {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+}
+
+body {
+ background-color: var(--color-bg-dark);
+ color: var(--color-text-primary);
+ font-family: var(--font-body);
+ font-size: 14px;
+ line-height: 1.5;
+ min-height: 100vh;
+ padding: 20px;
+ position: relative;
+ overflow-x: hidden;
+}
+
+/* Grid pattern behind elements */
+body::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background-image:
+ linear-gradient(rgba(61, 90, 254, 0.02) 1px, transparent 1px),
+ linear-gradient(90deg, rgba(61, 90, 254, 0.02) 1px, transparent 1px);
+ background-size: 30px 30px;
+ z-index: 0;
+ pointer-events: none;
+}
+
+.app-container {
+ position: relative;
+ z-index: 1;
+ display: flex;
+ flex-direction: column;
+ max-width: 1500px;
+ margin: 0 auto;
+ gap: 16px;
+}
+
+/* Header styling */
+.dashboard-header {
+ background: var(--color-bg-panel);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ padding: 16px 24px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ backdrop-filter: blur(12px);
+ box-shadow: var(--shadow-neon);
+}
+
+.header-logo-title {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+.loan-logo {
+ width: 42px;
+ height: 42px;
+ animation: pulse-glow 3s infinite ease-in-out;
+}
+
+.logo-svg {
+ width: 100%;
+ height: 100%;
+}
+
+.title-meta h1 {
+ font-family: var(--font-title);
+ font-weight: 800;
+ font-size: 20px;
+ letter-spacing: 1.5px;
+ color: var(--color-text-primary);
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.pro-badge {
+ background: linear-gradient(135deg, var(--color-primary), var(--color-prepay));
+ color: #020617;
+ font-size: 11px;
+ font-weight: 900;
+ padding: 2px 6px;
+ border-radius: 4px;
+ letter-spacing: 0.5px;
+ box-shadow: 0 0 8px var(--color-primary);
+}
+
+.subtitle {
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 2px;
+ color: var(--color-text-secondary);
+}
+
+/* Preset selector dropdown */
+.preset-selector {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.preset-selector label {
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 1px;
+ color: var(--color-text-secondary);
+}
+
+.styled-select {
+ background: var(--color-bg-element);
+ color: var(--color-text-primary);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ padding: 8px 12px;
+ font-family: var(--font-body);
+ font-weight: 500;
+ outline: none;
+ cursor: pointer;
+ transition: border-color 0.3s ease;
+}
+
+.styled-select:hover {
+ border-color: var(--color-primary);
+}
+
+/* Dashboard Columns Grid Layout */
+.dashboard-grid {
+ display: grid;
+ grid-template-columns: 360px 1fr;
+ gap: 16px;
+}
+
+/* Panel Containers */
+.panel {
+ background: var(--color-bg-panel);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-lg);
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ backdrop-filter: blur(12px);
+ box-shadow: 0 4px 30px rgba(0, 0, 0, 0.4);
+}
+
+.panel-header {
+ padding: 14px 20px;
+ background: rgba(4, 8, 15, 0.85);
+ border-bottom: 1px solid var(--color-border);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.panel-header h2 {
+ font-family: var(--font-title);
+ font-weight: 600;
+ font-size: 13px;
+ letter-spacing: 1.5px;
+ color: var(--color-text-secondary);
+}
+
+.scrollable-content {
+ padding: 20px;
+ display: flex;
+ flex-direction: column;
+ gap: 20px;
+ max-height: 520px;
+ overflow-y: auto;
+}
+
+.scrollable-content::-webkit-scrollbar {
+ width: 6px;
+}
+
+.scrollable-content::-webkit-scrollbar-track {
+ background: rgba(2, 6, 12, 0.2);
+}
+
+.scrollable-content::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+ border-radius: 3px;
+}
+
+/* Control groups sliders */
+.control-group {
+ display: flex;
+ flex-direction: column;
+ gap: 14px;
+ border-bottom: 1px dashed rgba(255, 255, 255, 0.03);
+ padding-bottom: 18px;
+}
+
+.control-group:last-child {
+ border-bottom: none;
+ padding-bottom: 0;
+}
+
+.control-group h3 {
+ font-family: var(--font-title);
+ font-weight: 700;
+ font-size: 11px;
+ letter-spacing: 1px;
+ color: var(--color-text-primary);
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.group-icon {
+ font-size: 12px;
+}
+
+.slider-wrapper {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+}
+
+.slider-labels {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.slider-labels label {
+ font-size: 12.5px;
+ font-weight: 500;
+ color: var(--color-text-secondary);
+}
+
+.input-val-container {
+ display: flex;
+ align-items: center;
+ background: var(--color-bg-element);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-sm);
+ padding: 2px 6px;
+ gap: 4px;
+}
+
+.input-val-container span {
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--color-primary);
+}
+
+.num-box {
+ width: 70px;
+ background: transparent;
+ border: none;
+ color: var(--color-text-primary);
+ font-family: monospace;
+ font-size: 12.5px;
+ font-weight: 700;
+ text-align: right;
+ outline: none;
+}
+
+.num-box::-webkit-inner-spin-button,
+.num-box::-webkit-outer-spin-button {
+ -webkit-appearance: none;
+ margin: 0;
+}
+
+input[type="range"] {
+ -webkit-appearance: none;
+ width: 100%;
+ height: 5px;
+ background: rgba(255,255,255,0.05);
+ border-radius: 3px;
+ outline: none;
+}
+
+input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ appearance: none;
+ width: 13px;
+ height: 13px;
+ border-radius: 50%;
+ background: var(--color-primary);
+ cursor: pointer;
+ box-shadow: 0 0 6px var(--color-primary);
+ transition: transform 0.1s ease;
+}
+
+input[type="range"]::-webkit-slider-thumb:hover {
+ transform: scale(1.3);
+}
+
+.slider-description {
+ font-size: 9.5px;
+ color: var(--color-text-muted);
+ line-height: 1.3;
+}
+
+/* Center Panel: Telemetry Dashboard */
+.workspace-container {
+ padding: 16px;
+ gap: 16px;
+}
+
+.telemetry-deck {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 14px;
+}
+
+.kpi-card {
+ background: var(--color-bg-element);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ padding: 14px;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ transition: all 0.3s ease;
+}
+
+.kpi-card:hover {
+ border-color: var(--color-border-active);
+ box-shadow: var(--shadow-neon);
+}
+
+.kpi-label {
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ color: var(--color-text-secondary);
+}
+
+.kpi-value {
+ font-family: 'Outfit', sans-serif;
+ font-weight: 800;
+ font-size: 20px;
+}
+
+.kpi-value.primary { color: var(--color-primary); }
+.kpi-value.interest { color: var(--color-interest); }
+.kpi-value.savings { color: var(--color-savings); }
+.kpi-value.tenure { color: var(--color-prepay); }
+
+.kpi-sub {
+ font-size: 9.5px;
+ color: var(--color-text-muted);
+}
+
+/* Visualization Workspace */
+.visualization-workspace {
+ display: grid;
+ grid-template-columns: 240px 1fr;
+ gap: 16px;
+ flex: 1;
+ min-height: 300px;
+}
+
+.chart-container {
+ background: rgba(4, 8, 15, 0.4);
+ border: 1px solid var(--color-border);
+ border-radius: var(--radius-md);
+ padding: 14px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+}
+
+.chart-container h3 {
+ font-family: var(--font-title);
+ font-size: 10.5px;
+ letter-spacing: 1px;
+ color: var(--color-text-secondary);
+ align-self: flex-start;
+ margin-bottom: 12px;
+}
+
+.canvas-wrapper {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+}
+
+.canvas-wrapper canvas {
+ max-width: 100%;
+ display: block;
+}
+
+/* Legend items for donut chart */
+.donut-legend {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ width: 100%;
+ padding-top: 10px;
+ border-top: 1px solid rgba(255,255,255,0.03);
+}
+
+.legend-item {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 11px;
+ color: var(--color-text-secondary);
+}
+
+.legend-item .bullet {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ display: inline-block;
+}
+
+.legend-item.principal .bullet { background: var(--color-primary); box-shadow: 0 0 6px var(--color-primary); }
+.legend-item.interest .bullet { background: var(--color-interest); box-shadow: 0 0 6px var(--color-interest); }
+.legend-item.prepay .bullet { background: var(--color-prepay); box-shadow: 0 0 6px var(--color-prepay); }
+.legend-item span {
+ font-family: monospace;
+ font-weight: 700;
+ margin-left: auto;
+}
+
+/* Bottom Panel: Amortization schedule */
+.projections-container {
+ flex: 1;
+ max-height: 400px;
+}
+
+.table-actions {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+}
+
+/* View toggle switch */
+.toggle-view-wrapper {
+ display: flex;
+ align-items: center;
+ background: rgba(2, 6, 12, 0.4);
+ padding: 3px;
+ border-radius: var(--radius-sm);
+ border: 1px solid var(--color-border);
+}
+
+.view-label {
+ font-size: 9px;
+ font-weight: 800;
+ color: var(--color-text-secondary);
+ margin-right: 6px;
+ margin-left: 4px;
+}
+
+.toggle-view-btn {
+ background: transparent;
+ border: none;
+ color: var(--color-text-secondary);
+ font-family: var(--font-title);
+ font-weight: 700;
+ font-size: 10px;
+ padding: 4px 10px;
+ border-radius: 4px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.toggle-view-btn.active {
+ background: var(--color-primary);
+ color: #020617;
+ box-shadow: 0 0 6px var(--color-primary);
+}
+
+.styled-btn {
+ background: var(--color-savings);
+ color: #020617;
+ border: 1px solid var(--color-savings);
+ border-radius: var(--radius-md);
+ padding: 8px 16px;
+ font-family: var(--font-title);
+ font-weight: 700;
+ font-size: 11.5px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ transition: all 0.25s ease;
+ box-shadow: 0 0 8px rgba(0, 230, 118, 0.2);
+}
+
+.styled-btn:hover {
+ background: #020617;
+ color: var(--color-savings);
+ box-shadow: 0 0 12px rgba(0, 230, 118, 0.4);
+}
+
+.table-wrapper {
+ overflow-y: auto;
+ max-height: 280px;
+}
+
+.table-wrapper::-webkit-scrollbar {
+ width: 6px;
+}
+
+.table-wrapper::-webkit-scrollbar-thumb {
+ background: var(--color-border);
+ border-radius: 3px;
+}
+
+table {
+ width: 100%;
+ border-collapse: collapse;
+ text-align: left;
+ font-size: 12px;
+}
+
+thead th {
+ position: sticky;
+ top: 0;
+ background: #090e15;
+ color: var(--color-text-secondary);
+ font-family: var(--font-title);
+ font-weight: 700;
+ padding: 12px 16px;
+ border-bottom: 1px solid var(--color-border);
+ z-index: 2;
+}
+
+tbody tr {
+ border-bottom: 1px solid rgba(255, 255, 255, 0.02);
+ transition: background 0.2s ease;
+}
+
+tbody tr:hover {
+ background: rgba(255, 255, 255, 0.02);
+}
+
+tbody td {
+ padding: 10px 16px;
+ color: var(--color-text-primary);
+ font-family: monospace;
+}
+
+tbody td:nth-child(2),
+tbody td:nth-child(3),
+tbody td:nth-child(4),
+tbody td:nth-child(5),
+tbody td:nth-child(6),
+tbody td:nth-child(7) {
+ font-weight: 600;
+}
+
+tbody td:first-child {
+ font-family: var(--font-body);
+ font-weight: 600;
+ color: var(--color-text-secondary);
+}
+
+tbody td:nth-child(3) { color: var(--color-text-primary); }
+tbody td:nth-child(4) { color: var(--color-prepay); }
+tbody td:nth-child(5) { color: var(--color-primary); }
+tbody td:nth-child(6) { color: var(--color-interest); }
+tbody td:nth-child(7) { color: var(--color-text-primary); }
+
+/* Keyframe animations */
+@keyframes pulse-glow {
+ 0% { transform: scale(1); filter: drop-shadow(0 0 1px rgba(61, 90, 254, 0.3)); }
+ 50% { transform: scale(1.05); filter: drop-shadow(0 0 6px rgba(61, 90, 254, 0.5)); }
+ 100% { transform: scale(1); filter: drop-shadow(0 0 1px rgba(61, 90, 254, 0.3)); }
+}
+
+/* Responsiveness */
+@media (max-width: 1000px) {
+ .dashboard-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .visualization-workspace {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/Projects/EMI Loan Planner/thumbnail.svg b/Projects/EMI Loan Planner/thumbnail.svg
new file mode 100644
index 00000000..1627aaa6
--- /dev/null
+++ b/Projects/EMI Loan Planner/thumbnail.svg
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MONTHLY EMI
+ $1,755
+
+
+ INTEREST SAVED
+ $73,420
+
+
+
+ EMI LOAN PLANNER
+
+
+
+
+
+
+
+