Loss in Purchasing Power of Initial $${pv.toFixed(2)} (due to inflation): $${mainResults.lossOfPurchasingPowerInitialPV.toFixed(2)}
Net Change in Purchasing Power (Real FV vs. Total Nominal Invested $${mainResults.initialPrincipalTotal.toFixed(2)}):
$${mainResults.netGainLossReal.toFixed(2)}
`;
fvResultsAreaEl.style.display = 'block';
if (isSensitivityRun) {
const lowInf = getNum(lowInflationRateSavingsEl, mainInflation); // Default to main if empty
const highInf = getNum(highInflationRateSavingsEl, mainInflation);
let sensitivityHtml = `
Inflation Sensitivity on Real Future Value:
| Inflation Scenario | Inflation Rate | Real Future Value (Today's $) |
`;
const scenarios = [
{ name: "Main Scenario", rate: mainInflation, result: mainResults.realFv }
];
if (lowInf !== mainInflation) {
const lowResults = calculateForRate(lowInf);
scenarios.push({ name: "Low Inflation", rate: lowInf, result: lowResults.realFv });
lastFVSavingsResults.sensitivity.push({name: "Low Inflation", rate: lowInf, realFv: lowResults.realFv});
} else { // Add main again if low is same, so it appears in sensitivity list
lastFVSavingsResults.sensitivity.push({name: "Main (as Low)", rate: mainInflation, realFv: mainResults.realFv});
}
if (highInf !== mainInflation && highInf !== lowInf) {
const highResults = calculateForRate(highInf);
scenarios.push({ name: "High Inflation", rate: highInf, result: highResults.realFv });
lastFVSavingsResults.sensitivity.push({name: "High Inflation", rate: highInf, realFv: highResults.realFv});
} else if (highInf !== mainInflation && highInf === lowInf) { // If high is same as low, but different from main
// Already covered by low, but ensure it's in PDF results if distinct entry
} else if (highInf === mainInflation && !lastFVSavingsResults.sensitivity.find(s=>s.rate === mainInflation && s.name.includes("High"))) {
lastFVSavingsResults.sensitivity.push({name: "Main (as High)", rate: mainInflation, realFv: mainResults.realFv});
}
// Ensure main scenario is in sensitivity list for PDF if not already added as low/high
if (!lastFVSavingsResults.sensitivity.find(s => s.rate === mainInflation)) {
lastFVSavingsResults.sensitivity.unshift({name: "Main Scenario", rate: mainInflation, realFv: mainResults.realFv});
}
// Sort sensitivity scenarios for display consistency (e.g., by rate)
lastFVSavingsResults.sensitivity.sort((a,b) => a.rate - b.rate);
lastFVSavingsResults.sensitivity.forEach(scen => {
sensitivityHtml += `| ${scen.name} | ${scen.rate.toFixed(1)}% | $${scen.realFv.toFixed(2)} |
`;
});
sensitivityHtml += `
`;
fvSensitivityResultsAreaEl.innerHTML = sensitivityHtml;
fvSensitivityResultsAreaEl.style.display = 'block';
} else {
fvSensitivityResultsAreaEl.style.display = 'none';
lastFVSavingsResults.sensitivity = []; // Clear if not a sensitivity run
}
}
window.iist_calculateFutureValueOfSavings = iist_calculateFutureValueOfSavings;
// --- Tab 2: Savings Goal Planner ---
window.iist_calculateNominalGoal = function(isSensitivityRun = false) {
const targetPV = getNum(targetGoalTodayValueEl);
const years = getNum(yearsUntilGoalEl, 0);
const mainInflation = getNum(inflationRateGoalEl, 0);
if (targetPV <= 0 || years <= 0) {
alert("Please enter a valid target goal amount (in today's dollars) and number of years (>0).");
goalResultsAreaEl.style.display = 'none';
goalSensitivityResultsAreaEl.style.display = 'none';
reachGoalSectionEl.style.display = 'none';
return;
}
const calculateForRate = (inflationR) => {
return targetPV * Math.pow(1 + (inflationR / 100), years);
};
const nominalGoalMain = calculateForRate(mainInflation);
lastGoalResults = { // For PDF
inputs: { targetPV, years, mainInflation },
mainNominalGoal: nominalGoalMain,
sensitivity: []
};
goalResultsAreaEl.innerHTML = `
Nominal Goal Projection (at ${mainInflation.toFixed(1)}% avg. inflation)
To have the purchasing power of $${targetPV.toFixed(2)} today...
In ${years} years, you will need a nominal amount of: $${nominalGoalMain.toFixed(2)}
`;
goalResultsAreaEl.style.display = 'block';
reachGoalSectionEl.style.display = 'block'; // Show the next section
if (isSensitivityRun) {
const lowInf = getNum(lowInflationRateGoalEl, mainInflation);
const highInf = getNum(highInflationRateGoalEl, mainInflation);
let sensitivityHtml = `
Inflation Sensitivity on Nominal Goal Needed:
| Inflation Scenario | Inflation Rate | Nominal Amount Needed ($) |
`;
const scenarios = [
{ name: "Main Scenario", rate: mainInflation, result: nominalGoalMain }
];
if (lowInf !== mainInflation) {
const lowResult = calculateForRate(lowInf);
scenarios.push({ name: "Low Inflation", rate: lowInf, result: lowResult });
lastGoalResults.sensitivity.push({name: "Low Inflation", rate: lowInf, nominalGoal: lowResult});
} else {
lastGoalResults.sensitivity.push({name: "Main (as Low)", rate: mainInflation, nominalGoal: nominalGoalMain});
}
if (highInf !== mainInflation && highInf !== lowInf) {
const highResult = calculateForRate(highInf);
scenarios.push({ name: "High Inflation", rate: highInf, result: highResult });
lastGoalResults.sensitivity.push({name: "High Inflation", rate: highInf, nominalGoal: highResult});
} else if (highInf === mainInflation && !lastGoalResults.sensitivity.find(s=>s.rate === mainInflation && s.name.includes("High"))) {
lastGoalResults.sensitivity.push({name: "Main (as High)", rate: mainInflation, nominalGoal: nominalGoalMain});
}
if (!lastGoalResults.sensitivity.find(s => s.rate === mainInflation)) {
lastGoalResults.sensitivity.unshift({name: "Main Scenario", rate: mainInflation, nominalGoal: nominalGoalMain});
}
lastGoalResults.sensitivity.sort((a,b) => a.rate - b.rate);
lastGoalResults.sensitivity.forEach(scen => {
sensitivityHtml += `| ${scen.name} | ${scen.rate.toFixed(1)}% | $${scen.nominalGoal.toFixed(2)} |
`;
});
sensitivityHtml += `
`;
goalSensitivityResultsAreaEl.innerHTML = sensitivityHtml;
goalSensitivityResultsAreaEl.style.display = 'block';
} else {
goalSensitivityResultsAreaEl.style.display = 'none';
lastGoalResults.sensitivity = [];
}
}
window.iist_calculateNominalGoal = iist_calculateNominalGoal;
window.iist_projectSavingsToGoal = function() {
if (!lastGoalResults || !lastGoalResults.mainNominalGoal) {
alert("Please calculate the 'Nominal Amount Needed' for your goal first.");
return;
}
const nominalTarget = lastGoalResults.mainNominalGoal;
const years = getNum(yearsUntilGoalEl); // Should be same as used for nominalGoalMain
const currentSaved = getNum(currentSavingsForGoalEl);
const annualContrib = getNum(plannedAnnualContributionGoalEl);
const annualReturn = getNum(expectedReturnGoalSavingsEl);
if (years <= 0) { alert("Years until goal must be positive."); return; }
const fvFromCurrent = fvLumpSum(currentSaved, annualReturn, years);
const fvFromContrib = fvAnnuity(annualContrib, annualReturn, years);
const totalProjectedNominal = fvFromCurrent + fvFromContrib;
const shortfallSurplus = totalProjectedNominal - nominalTarget;
lastReachGoalResults = { // For PDF
inputs: { currentSaved, annualContrib, annualReturn, years, nominalTarget },
totalProjectedNominal,
shortfallSurplus
};
reachGoalResultsAreaEl.innerHTML = `
Projection Towards $${nominalTarget.toFixed(2)} Goal:
Current Savings for Goal: $${currentSaved.toFixed(2)}
Planned Annual Contribution: $${annualContrib.toFixed(2)}
Expected Annual Return on Savings: ${annualReturn.toFixed(1)}%
Total Projected Nominal Savings in ${years} years: $${totalProjectedNominal.toFixed(2)}
Result: You are projected to have a
${shortfallSurplus >= 0 ? 'SURPLUS of $' + shortfallSurplus.toFixed(2) : 'SHORTFALL of $' + Math.abs(shortfallSurplus).toFixed(2)}
for your nominal goal of $${nominalTarget.toFixed(2)}.
`;
reachGoalResultsAreaEl.style.display = 'block';
}
window.iist_projectSavingsToGoal = iist_projectSavingsToGoal;
// --- PDF Download ---
if(downloadPdfButton) {
downloadPdfButton.addEventListener('click', function() {
if (!lastFVSavingsResults && !lastGoalResults) {
alert("Please perform at least one calculation before downloading the PDF.");
return;
}
let pdfHtml = `
`;
pdfHtml += `
Inflation Impact on Savings Report
`;
pdfHtml += `
Generated on: ${new Date().toLocaleDateString()}
`;
if (lastFVSavingsResults) {
pdfHtml += `
Future Value of Current Savings
`;
pdfHtml += `
`;
pdfHtml += `
Nominal Future Value: $${lastFVSavingsResults.main.totalNominalFv.toFixed(2)}
Real Future Value (Today's $): $${lastFVSavingsResults.main.realFv.toFixed(2)}
Loss in Purchasing Power of Initial Principal: $${lastFVSavingsResults.main.lossOfPurchasingPowerInitialPV.toFixed(2)}
Net Change in Purchasing Power (vs. total invested $${lastFVSavingsResults.main.initialPrincipalTotal.toFixed(2)}): $${lastFVSavingsResults.main.netGainLossReal.toFixed(2)}
`;
if (lastFVSavingsResults.sensitivity && lastFVSavingsResults.sensitivity.length > 0) {
pdfHtml += `
Inflation Sensitivity on Real Future Value:
| Inflation Rate | Real Future Value ($) |
`;
lastFVSavingsResults.sensitivity.forEach(scen => {
pdfHtml += `| ${scen.rate.toFixed(1)}% (${scen.name}) | $${scen.realFv.toFixed(2)} |
`;
});
pdfHtml += `
`;
}
pdfHtml += `
`;
}
if (lastGoalResults) {
pdfHtml += `
Savings Goal Planner
`;
pdfHtml += `
`;
pdfHtml += `
Nominal Amount Needed in ${lastGoalResults.inputs.years} years: $${lastGoalResults.mainNominalGoal.toFixed(2)}
`;
if (lastGoalResults.sensitivity && lastGoalResults.sensitivity.length > 0) {
pdfHtml += `
Inflation Sensitivity on Nominal Goal Needed:
| Inflation Rate | Nominal Amount Needed ($) |
`;
lastGoalResults.sensitivity.forEach(scen => {
pdfHtml += `| ${scen.rate.toFixed(1)}% (${scen.name}) | $${scen.nominalGoal.toFixed(2)} |
`;
});
pdfHtml += `
`;
}
if (lastReachGoalResults) {
pdfHtml += `
Projection Towards Goal:
Total Projected Nominal Savings: $${lastReachGoalResults.totalProjectedNominal.toFixed(2)}
Result vs. Nominal Goal ($${lastReachGoalResults.inputs.nominalTarget.toFixed(2)}):
${lastReachGoalResults.shortfallSurplus >= 0 ? 'SURPLUS of $' + lastReachGoalResults.shortfallSurplus.toFixed(2) : 'SHORTFALL of $' + Math.abs(lastReachGoalResults.shortfallSurplus).toFixed(2)}
`;
}
pdfHtml += `
`;
}
pdfHtml += `
`; // Close iist-pdf-output
if(pdfOutputEl) pdfOutputEl.innerHTML = pdfHtml;
const opt = {
margin: [0.5, 0.5, 0.5, 0.5],
filename: `Inflation_Impact_Savings_Report_${new Date().toISOString().slice(0,10)}.pdf`,
image: { type: 'jpeg', quality: 0.98 },
html2canvas: { scale: 2, useCORS: true, logging: false },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
};
if (typeof html2pdf !== 'undefined' && pdfOutputEl) {
html2pdf().from(pdfOutputEl).set(opt).save()
.catch(err => { console.error("PDF generation failed:", err); alert("PDF generation error."); });
} else {
alert("PDF library not loaded or output element not found.");
}
});
}
// --- Tab Navigation ---
window.iist_openTab = function(evt, tabName) {
tabs.forEach(tab => { if(tab) tab.style.display = 'none'; });
tabLinks.forEach(link => { if(link) link.classList.remove('active'); });
const activeTabContent = toolContainer.querySelector('#' + tabName);
if(activeTabContent) activeTabContent.style.display = 'block';
else { console.error(`Tab content for ${tabName} not found.`); return; }
let clickedIndex = -1;
if (evt && evt.currentTarget) {
evt.currentTarget.classList.add('active');
clickedIndex = tabLinks.indexOf(evt.currentTarget);
} else {
clickedIndex = tabLinks.findIndex(link => {
if (!link) return false;
const onclickAttr = link.getAttribute('onclick');
return onclickAttr && onclickAttr.includes(`'${tabName}'`);
});
if (clickedIndex !== -1 && tabLinks[clickedIndex]) {
tabLinks[clickedIndex].classList.add('active');
} else {
if (tabLinks.length > 0 && tabLinks[0]) tabLinks[0].classList.add('active');
clickedIndex = 0;
}
}
currentTabIndex = (clickedIndex !== -1 && clickedIndex < tabs.length) ? clickedIndex : 0;
iist_updateNavButtons();
}
window.iist_navigateTab = function(direction) {
let newIndex = currentTabIndex;
if (direction === 'next' && currentTabIndex < tabs.length - 1) newIndex++;
else if (direction === 'prev' && currentTabIndex > 0) newIndex--;
if (tabLinks[newIndex] && newIndex !== currentTabIndex) tabLinks[newIndex].click();
}
function iist_updateNavButtons() {
if(prevButton) prevButton.disabled = currentTabIndex === 0;
if(nextButton) nextButton.disabled = currentTabIndex >= tabs.length - 1;
}
// --- LocalStorage for settings ---
function loadSettingsFromLocalStorage() {
try {
const storedYearsSavings = localStorage.getItem(`${toolId}_projYearsSavings`);
if (storedYearsSavings && projectionYearsSavingsEl) projectionYearsSavingsEl.value = storedYearsSavings;
const storedInflationSavings = localStorage.getItem(`${toolId}_inflationSavings`);
if (storedInflationSavings && inflationRateSavingsEl) inflationRateSavingsEl.value = storedInflationSavings;
const storedYearsGoal = localStorage.getItem(`${toolId}_projYearsGoal`);
if (storedYearsGoal && yearsUntilGoalEl) yearsUntilGoalEl.value = storedYearsGoal;
const storedInflationGoal = localStorage.getItem(`${toolId}_inflationGoal`);
if (storedInflationGoal && inflationRateGoalEl) inflationRateGoalEl.value = storedInflationGoal;
} catch(e) { console.warn("Could not load settings from local storage."); }
}
function saveSettingsToLocalStorage() {
try {
if(projectionYearsSavingsEl) localStorage.setItem(`${toolId}_projYearsSavings`, projectionYearsSavingsEl.value);
if(inflationRateSavingsEl) localStorage.setItem(`${toolId}_inflationSavings`, inflationRateSavingsEl.value);
if(yearsUntilGoalEl) localStorage.setItem(`${toolId}_projYearsGoal`, yearsUntilGoalEl.value);
if(inflationRateGoalEl) localStorage.setItem(`${toolId}_inflationGoal`, inflationRateGoalEl.value);
} catch (e) { console.warn("Could not save settings to local storage."); }
}
// Add event listeners to save settings on change
[projectionYearsSavingsEl, inflationRateSavingsEl, yearsUntilGoalEl, inflationRateGoalEl].forEach(el => {
if(el) el.addEventListener('change', saveSettingsToLocalStorage);
});
// --- Initialization ---
function initializeTool() {
loadSettingsFromLocalStorage(); // Load any saved settings
reachGoalSectionEl.style.display = 'none'; // Hide initially
iist_openTab(null, 'iist-tab1');
}
let iist_attempts = 0;
function checkLibrariesAndInit_iist() {
if (typeof html2pdf !== 'undefined') {
initializeTool();
} else if (iist_attempts < 20) {
iist_attempts++;
setTimeout(checkLibrariesAndInit_iist, 100);
} else {
alert("Error: PDF generation library (html2pdf.js) could not be loaded. PDF export will not work.");
initializeTool();
}
}
checkLibrariesAndInit_iist();
})();