diff --git a/links/templates/links/mini_apps/fire_planning.html b/links/templates/links/mini_apps/fire_planning.html index 1533545..a1e112e 100644 --- a/links/templates/links/mini_apps/fire_planning.html +++ b/links/templates/links/mini_apps/fire_planning.html @@ -236,22 +236,23 @@ table { width: 100%; border-collapse: collapse; - font-size: 0.8rem; + font-size: 0.75rem; } th { background: #f3f4f6; - padding: 0.5rem; + padding: 0.5rem 0.375rem; text-align: left; font-weight: 600; color: #374151; border-bottom: 2px solid #e5e7eb; - font-size: 0.75rem; + font-size: 0.7rem; } td { - padding: 0.375rem 0.5rem; + padding: 0.375rem; border-bottom: 1px solid #e5e7eb; + white-space: nowrap; } tr:hover { @@ -262,6 +263,140 @@ background: #d1fae5 !important; } + .net-worth-cell { + font-weight: 600; + color: #2563eb; + cursor: pointer; + text-decoration: underline; + text-decoration-style: dotted; + text-decoration-color: #93c5fd; + } + + .net-worth-cell:hover { + color: #1d4ed8; + background-color: #dbeafe; + } + + /* Modal Styles */ + .modal-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.6); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + } + + .modal-content { + background: white; + border-radius: 12px; + box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); + max-width: 600px; + width: 100%; + max-height: 90vh; + overflow-y: auto; + animation: modalSlideIn 0.3s ease-out; + } + + @keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + .modal-header { + padding: 1.25rem 1.5rem; + border-bottom: 2px solid #e5e7eb; + display: flex; + align-items: center; + justify-content: space-between; + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); + border-radius: 12px 12px 0 0; + } + + .modal-title { + font-size: 1.25rem; + font-weight: 700; + color: white; + margin: 0; + display: flex; + align-items: center; + } + + .modal-close { + background: rgba(255, 255, 255, 0.2); + border: none; + color: white; + width: 32px; + height: 32px; + border-radius: 6px; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: background 0.2s ease; + font-size: 1.25rem; + } + + .modal-close:hover { + background: rgba(255, 255, 255, 0.3); + } + + .modal-body { + padding: 1.5rem; + } + + .calculation-step { + background: #f9fafb; + border-left: 4px solid #3b82f6; + padding: 0.75rem 1rem; + margin-bottom: 0.75rem; + border-radius: 4px; + font-size: 0.95rem; + } + + .calculation-step.highlight { + background: #dbeafe; + border-left-color: #1d4ed8; + font-weight: 600; + } + + .calculation-label { + color: #6b7280; + font-size: 0.85rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; + } + + .calculation-value { + color: #1f2937; + font-size: 1.1rem; + font-weight: 700; + font-family: 'Courier New', monospace; + } + + .calculation-formula { + background: #fef3c7; + border: 1px solid #fbbf24; + padding: 0.75rem; + border-radius: 6px; + margin: 1rem 0; + font-family: 'Courier New', monospace; + font-size: 0.9rem; + color: #92400e; + } + {% endblock %} @@ -290,7 +425,7 @@
- +
@@ -300,7 +435,7 @@
- +
@@ -514,7 +649,8 @@

- Detailed breakdown of your journey to FIRE. + Detailed year-by-year breakdown showing your income, savings, investment returns, and net worth growth. + Click on any Net Worth value to see the detailed calculation with actual numbers. Green rows indicate years where you've achieved FIRE (≥100% progress).

@@ -523,10 +659,12 @@ Age Year - Net Worth + Annual Income + Annual Savings + Investment Return + Net Worth (End) FIRE Target Progress - Annual Savings @@ -537,6 +675,24 @@
+ + +
@@ -661,26 +817,39 @@ const currentYear = new Date().getFullYear() + year; const fireTarget = inputs.fireTarget * Math.pow(1 + inputs.inflationRate, year); const annualSavings = currentIncome * inputs.savingsRate; - const progress = (netWorth / fireTarget) * 100; + + // Store starting net worth for the year + const startingNetWorth = netWorth; + + // Calculate investment return for the year (return on starting net worth + savings) + const investmentReturn = (netWorth + annualSavings) * inputs.returnRate; + + // Calculate ending net worth for the year + const endingNetWorth = (netWorth + annualSavings) * (1 + inputs.returnRate); + + // Calculate progress based on ending net worth + const progress = (endingNetWorth / fireTarget) * 100; projections.push({ age: age, year: currentYear, - netWorth: netWorth, + netWorth: endingNetWorth, // Store ENDING net worth + startingNetWorth: startingNetWorth, // Keep starting for modal calculation fireTarget: fireTarget, progress: progress, - annualSavings: annualSavings + annualSavings: annualSavings, + investmentReturn: investmentReturn, + annualIncome: currentIncome, + returnRate: inputs.returnRate }); // Project next year's net worth and income - netWorth = (netWorth + annualSavings) * (1 + inputs.returnRate); + netWorth = endingNetWorth; // Next year starts with this year's ending currentIncome = currentIncome * (1 + inputs.incomeGrowthRate); } return projections; - } - - // Find the year FIRE is achieved + } // Find the year FIRE is achieved function findFIREYear(projections) { return projections.find(p => p.progress >= 100); } @@ -749,21 +918,27 @@ function updateTable(projections) { let tableHTML = ''; - projections.forEach(proj => { + projections.forEach((proj, index) => { const rowClass = proj.progress >= 100 ? 'fire-achieved' : ''; + tableHTML += ` ${proj.age} ${proj.year} - ${formatCurrency(proj.netWorth)} + ${formatCurrency(proj.annualIncome)} + ${formatCurrency(proj.annualSavings)} + ${formatCurrency(proj.investmentReturn)} + ${formatCurrency(proj.netWorth)} ${formatCurrency(proj.fireTarget)} ${formatPercentage(proj.progress)} - ${formatCurrency(proj.annualSavings)} `; }); document.getElementById('projectionTableBody').innerHTML = tableHTML; + + // Store projections globally for modal access + window.currentProjections = projections; } // Adjust value with quick buttons @@ -1132,13 +1307,100 @@ }); } + // Show Net Worth Calculation Modal + function showNetWorthCalculation(index) { + const proj = window.currentProjections[index]; + const modal = document.getElementById('netWorthModal'); + const content = document.getElementById('modalCalculationContent'); + + let calculationHTML = ''; + + // All years use the same calculation logic + const startNetWorth = proj.startingNetWorth; + const withSavings = startNetWorth + proj.annualSavings; + const endNetWorth = withSavings * (1 + proj.returnRate); + const returnPercent = (proj.returnRate * 100).toFixed(1); + const returnMultiplier = (1 + proj.returnRate).toFixed(2); + + calculationHTML = ` +
+
Age ${proj.age} - Year ${proj.year}
+
Showing net worth growth during this year
+
+ +
+
Step 1: Starting Net Worth
+
${formatCurrency(startNetWorth)}
+ ${index === 0 ? '
(Your initial net worth)
' : + `
(This is what you ended age ${proj.age - 1} with)
`} +
+ +
+
Step 2: Add Annual Savings
+
+ ${formatCurrency(proj.annualSavings)}
+
+ From income of ${formatCurrency(proj.annualIncome)} +
+
+ +
+
Step 3: Subtotal Before Returns
+
= ${formatCurrency(withSavings)}
+
+ +
+
Apply Investment Return:
+ ${formatCurrency(withSavings)} × ${returnMultiplier} (${returnPercent}% return) +
+ +
+
Final: Ending Net Worth
+
+ ${formatCurrency(endNetWorth)} +
+
+ (This becomes your starting net worth for age ${proj.age + 1}) +
+
+ +
+
💡 Understanding the Formula
+
+ Net Worth (End) = (Net Worth (Start) + Annual Savings) × (1 + Return Rate) +
+
+ `; + + content.innerHTML = calculationHTML; + modal.style.display = 'flex'; + + // Close modal when clicking outside + modal.onclick = function(event) { + if (event.target === modal) { + closeNetWorthModal(); + } + }; + } + + // Close Net Worth Calculation Modal + function closeNetWorthModal() { + document.getElementById('netWorthModal').style.display = 'none'; + } + + // Close modal with Escape key + document.addEventListener('keydown', function(event) { + if (event.key === 'Escape') { + closeNetWorthModal(); + } + }); + // Reset form function resetForm() { document.getElementById('currentAge').value = 35; document.getElementById('targetAge').value = 50; - document.getElementById('currentNetWorth').value = 300000; + document.getElementById('currentNetWorth').value = 200000; document.getElementById('totalNetWorth').value = 900000; - document.getElementById('annualExpenses').value = 150000; + document.getElementById('annualExpenses').value = 130000; document.getElementById('annualIncome').value = 300000; document.getElementById('savingsRate').value = 40; document.getElementById('returnRate').value = 8;