`;
html += `
`; // end summary grid
// Accumulation Table
html += `Accumulation Phase Projection (Up to Age ${data.inputs.targetRetirementAge}):
`; if(data.accumulationTable.length > 0) { html += ``;
html += `
`;
} else {
html += `| Year | Age | Start Balance | Contributions | Growth | End Balance |
|---|---|---|---|---|---|
| ${row.year} | ${row.age} | ${formatCurrency(row.yearStartBalance)} | ${formatCurrency(row.savingsThisYear)} | ${formatCurrency(row.investmentGrowth)} | ${formatCurrency(row.endBalance)} |
No accumulation phase (retiring at current age or no accumulation years specified).
`; } // Decumulation Table if (data.decumulationTable.length > 0) { html += `Retirement (Decumulation) Phase Projection:
`; html += ``;
html += `
`;
} else {
html += `| Ret. Year | Age | Start Balance | Withdrawal | Growth | End Balance |
|---|---|---|---|---|---|
| ${row.year} | ${row.age} | ${formatCurrency(row.startBalance)} | ${formatCurrency(row.withdrawal)} | ${formatCurrency(row.growth)} | ${formatCurrency(row.endBalance)} ${row.notes ? `(${row.notes})` : ''} |
No decumulation phase to display (e.g., funds depleted immediately or retirement duration was 0).
`; } simulationResultsDiv.innerHTML = html; } if (runSimulationBtn) { runSimulationBtn.addEventListener('click', runFIRESimulation); // Still uses the same function name, logic inside is adapted } // --- PDF Download --- function loadJsPdfIfNeeded(callback) { if (jsPdfLoaded) { if (callback) callback(); return; } const script = document.createElement('script'); script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js'; script.onload = () => { jsPdfLoaded = true; console.log("jsPDF loaded dynamically."); if (callback) callback(); }; script.onerror = () => { console.error("Failed to load jsPDF."); alert("Error: Could not load PDF library."); }; document.head.appendChild(script); } function downloadResultsAsPdf() { if (!jsPdfLoaded) { alert("PDF library not loaded."); return; } if (!simulationDataForPdf) { alert("No simulation data to download. Please run the simulation first."); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF({ unit: 'pt', format: 'a4' }); const data = simulationDataForPdf; const pageMargin = 40; const pageWidth = doc.internal.pageSize.getWidth() - 2 * pageMargin; let y = pageMargin; function addMainTitle(text) { doc.setFontSize(18); doc.setFont(undefined, 'bold'); doc.setTextColor(26, 83, 92); // Primary doc.text(text, doc.internal.pageSize.getWidth() / 2, y, { align: 'center' }); y += 35; } function addSectionTitle(text) { if (y > doc.internal.pageSize.getHeight() - 80) { doc.addPage(); y = pageMargin; } doc.setFontSize(14); doc.setFont(undefined, 'bold'); doc.setTextColor(78, 205, 196); // Secondary doc.text(text, pageMargin, y); y += 25; } function addLine(key, value, keyColor = [33,37,41], valueColor = [33,37,41]) { if (y > doc.internal.pageSize.getHeight() - 40) { doc.addPage(); y = pageMargin; } doc.setFontSize(10); doc.setFont(undefined, 'bold'); doc.setTextColor(keyColor[0], keyColor[1], keyColor[2]); doc.text(key, pageMargin, y); doc.setFont(undefined, 'normal'); doc.setTextColor(valueColor[0], valueColor[1], valueColor[2]); const valueText = String(value); doc.text(valueText, pageMargin + 240, y, { align: 'left', maxWidth: pageWidth - 240 - 5 }); y += 18; } function addInfo(text, isError = false) { if (y > doc.internal.pageSize.getHeight() - 50) { doc.addPage(); y = pageMargin; } doc.setFontSize(8.5); doc.setFont(undefined, 'italic'); doc.setTextColor(isError ? 220 : 108, isError ? 53 : 117, isError ? 69 : 125); const splitText = doc.splitTextToSize(text, pageWidth); doc.text(splitText, pageMargin, y); y += (doc.getTextDimensions(splitText).h) + 10; } function addTable(headers, tableData, columnWidths) { // Removed highlight logic for this version if (y > doc.internal.pageSize.getHeight() - 60) { doc.addPage(); y = pageMargin; } doc.setFontSize(8); const headerFillColor = [78, 205, 196]; const headerTextColor = [26, 83, 92]; const rowTextColor = [33,37,41]; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); let currentX = pageMargin; headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 20, 'F'); doc.text(header, currentX + 5, y + 14); currentX += columnWidths[i]; }); y += 20; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); tableData.forEach((rowArray) => { if (y > doc.internal.pageSize.getHeight() - 35) { doc.addPage(); y = pageMargin; currentX = pageMargin; doc.setFillColor(headerFillColor[0], headerFillColor[1], headerFillColor[2]); doc.setTextColor(headerTextColor[0], headerTextColor[1], headerTextColor[2]); doc.setFont(undefined, 'bold'); headers.forEach((header, i) => { doc.rect(currentX, y, columnWidths[i], 20, 'F'); doc.text(header, currentX + 5, y + 14); currentX += columnWidths[i]; }); y += 20; doc.setTextColor(rowTextColor[0], rowTextColor[1], rowTextColor[2]); doc.setFont(undefined, 'normal'); } currentX = pageMargin; rowArray.forEach((cell, i) => { doc.rect(currentX, y, columnWidths[i], 18); const cellText = String(cell); const textLines = doc.splitTextToSize(cellText, columnWidths[i] - 8); doc.text(textLines, currentX + 4, y + 12); currentX += columnWidths[i]; }); y += 18; }); y += 10; } addMainTitle("Retirement Nest Egg Longevity Report"); addInfo(`Report Generated: ${new Date().toLocaleString()}`); y += 10; addSectionTitle("Input Parameters"); addLine("Current Age:", `${data.inputs.currentAge} years`); addLine("Target Retirement Age:", `${data.inputs.targetRetirementAge} years`); addLine("Current Nest Egg Value:", formatCurrency(data.inputs.currentPortfolioValue)); addLine("Annual Contributions (Pre-Retirement):", formatCurrency(data.inputs.annualSavings)); addLine("Desired Annual Income in Retirement:", formatCurrency(data.inputs.annualExpensesInRetirement)); // addLine("User Input SWR (Reference):", `${data.inputs.safeWithdrawalRatePct.toFixed(1)}%`); // Can be added if desired addLine("Avg. Annual Return (Pre-Retirement):", `${data.inputs.preRetirementReturnRatePct.toFixed(1)}%`); addLine("Avg. Annual Return (Post-Retirement):", `${data.inputs.postRetirementReturnRatePct.toFixed(1)}%`); addLine("Expected Avg. Annual Inflation:", `${data.inputs.inflationRatePct.toFixed(1)}%`); addLine("Retirement Duration Simulated:", `${data.inputs.retirementDuration} years`); addLine("Adjust Withdrawals for Inflation:", data.inputs.adjustExpensesForInflation ? "Yes" : "No"); y += 10; addSectionTitle("Nest Egg Longevity Summary"); addLine("Portfolio Value at Retirement (Age " + data.inputs.targetRetirementAge + "):", formatCurrency(data.accumulationSummary.portfolioAtRetirement)); addLine("Actual Initial Withdrawal Rate:", `${data.retirementSummary.actualInitialSWR.toFixed(2)}%`); if (data.retirementSummary.fundsDepletedYear !== null && data.retirementSummary.fundsDepletedYear <= data.inputs.retirementDuration) { addLine("Nest Egg Longevity:", `Depleted in Year ${data.retirementSummary.fundsDepletedYear} of retirement`,[220,53,69],[220,53,69]); addLine("Age at Depletion:", `${data.inputs.targetRetirementAge + data.retirementSummary.fundsDepletedYear -1} years old`); } else { addLine("Nest Egg Longevity:", `Lasted for simulated ${data.inputs.retirementDuration} years`,[40,167,69],[40,167,69]); addLine("Final Balance After "+data.inputs.retirementDuration+" Years:", formatCurrency(data.retirementSummary.finalBalancePostRetirement)); } addLine("Total Withdrawn During Retirement:", formatCurrency(data.retirementSummary.totalWithdrawnPostRetirement)); y += 10; addSectionTitle("Accumulation Phase Projection (Up to Age " + data.inputs.targetRetirementAge + ")"); const accHeaders = ["Year", "Age", "Start Balance", "Contributions", "Growth", "End Balance"]; const accColWidths = [40, 40, 110, 90, 90, 110]; const accTableFormattedData = data.accumulationTable.map(r => [ r.year, r.age, formatCurrency(r.yearStartBalance,0,0), formatCurrency(r.savingsThisYear,0,0), formatCurrency(r.investmentGrowth,0,0), formatCurrency(r.endBalance,0,0) ]); if (accTableFormattedData.length > 0) { addTable(accHeaders, accTableFormattedData, accColWidths); } else { addInfo("No accumulation phase (retiring at current age or target retirement age is current age)."); } if (data.decumulationTable.length > 0) { addSectionTitle("Retirement (Decumulation) Phase Projection"); const decHeaders = ["Ret. Year", "Age", "Start Balance", "Withdrawal", "Growth", "End Balance"]; const decColWidths = [50, 40, 110, 90, 90, 110]; const decTableFormattedData = data.decumulationTable.map(r => [ r.year, r.age, formatCurrency(r.startBalance,0,0), formatCurrency(r.withdrawal,0,0), formatCurrency(r.growth,0,0), formatCurrency(r.endBalance,0,0) + (r.notes ? ` (${r.notes.substring(0,10)}..)` : '') ]); addTable(decHeaders, decTableFormattedData, decColWidths); } addInfo("Disclaimer: This simulation uses fixed average assumptions and does not account for market volatility, sequence of returns risk, taxes, or fees. It is for illustrative and educational purposes only and should not be considered financial advice. Consult with a qualified financial advisor for personalized retirement planning."); // Footer const pageCount = doc.internal.getNumberOfPages(); for (let i = 1; i <= pageCount; i++) { doc.setPage(i); doc.setFontSize(8); doc.setTextColor(150); doc.text(`Page ${i} of ${pageCount} - Retirement Nest Egg Longevity Calculator`, pageMargin, doc.internal.pageSize.getHeight() - 20); } doc.save('Retirement_Nest_Egg_Longevity.pdf'); } if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', () => loadJsPdfIfNeeded(downloadResultsAsPdf)); } // --- Initialization --- showTab(0); loadJsPdfIfNeeded(); });