Electric vs. Gas Car Loan & Savings Calculator
Common Ownership Details
%
Electric Vehicle (EV) Details
Purchase & Loan (EV)
$
$
$
%
Running Costs & Resale (EV)
$
$
$
$
Gasoline Vehicle Details
Purchase & Loan (Gas Car)
$
$
%
Running Costs & Resale (Gas Car)
$
$
$
$
Savings Comparison Summary
Comparison over X years.
Error: Tool input fields are missing. Please check the HTML structure.
"; } return; } } const tabs = document.querySelectorAll('#evVsGasSavingsCalculator .evg-tab-content'); if (tabs.length > 0 && tabs[evg_currentTab] && tabs[evg_currentTab].style) { tabs[evg_currentTab].style.display = 'block'; } evg_updateNavButtons(); }); function evg_formatCurrency(value, addSymbol = true) { const num = Number(value); if (isNaN(num)) return addSymbol ? "$0.00" : "0.00"; const fixedNum = (Math.abs(num) < 0.0001) ? 0 : num; const formatted = fixedNum.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,'); return addSymbol ? `$${formatted}` : formatted; } // --- CORRECTED evg_validateInput FUNCTION --- function evg_validateInput(element, min, max, isInteger = false, allowZero = false, customMaxElementKey = null, customMaxVal = null) { const errorElementId = element.id + evg_el.errorSuffix; const errorElement = document.getElementById(errorElementId); if (!errorElement) { console.warn("Error element not found for " + element.id); return true; } errorElement.textContent = ''; const valStr = element.value; if (valStr.trim() === '') { errorElement.textContent = 'This field is required.'; return false; } const value = parseFloat(valStr); if (isNaN(value)) { errorElement.textContent = 'Please enter a valid number.'; return false; } let currentMax = max; let maxDisplaySource = ""; if (customMaxElementKey && evg_el[customMaxElementKey] && typeof evg_el[customMaxElementKey].value !== 'undefined') { let MVal = parseFloat(evg_el[customMaxElementKey].value); if (!isNaN(MVal)) { currentMax = MVal; } const labelElement = document.querySelector(`label[for='${evg_el[customMaxElementKey].id}']`); maxDisplaySource = labelElement ? labelElement.textContent.replace(':', '').split('\n')[0].trim() : customMaxElementKey; } else if (customMaxVal !== null && customMaxVal !== undefined && isFinite(customMaxVal)) { // **THIS IS THE CORRECTED CONDITION** currentMax = customMaxVal; maxDisplaySource = "Calculated Effective Price"; } if (value < min || value > currentMax) { let maxMsg; if (maxDisplaySource) { maxMsg = `${evg_formatCurrency(currentMax, false)} (max: ${maxDisplaySource})`; } else { maxMsg = `${evg_formatCurrency(currentMax, false)}`; } errorElement.textContent = `Value must be between ${evg_formatCurrency(min, false)} and ${maxMsg}.`; return false; } if (isInteger && !Number.isInteger(parseFloat(valStr))) { errorElement.textContent = 'Please enter a whole number (no decimals).'; return false; } // This check for allowZero might be redundant if min/max already cover it, // but retained for now if specific zero handling is needed. if (!allowZero && Math.abs(value) < 0.00001 && min !== 0) { // errorElement.textContent = `Value cannot be zero if minimum is not zero.`; // return false; } return true; } // --- END OF CORRECTED evg_validateInput FUNCTION --- function evg_validateTab1() { let isValid = true; isValid = evg_validateInput(evg_el.ownershipDuration, 1, 50, true, false) && isValid; isValid = evg_validateInput(evg_el.annualMilesDriven, 1, 100000, true, false) && isValid; isValid = evg_validateInput(evg_el.salesTaxRate, 0, 100, false, true) && isValid; const evPriceVal = parseFloat(evg_el.evPrice.value) || 0; const evCreditsVal = parseFloat(evg_el.evCredits.value) || 0; const evEffectivePrice = Math.max(0, evPriceVal - evCreditsVal); isValid = evg_validateInput(evg_el.evPrice, 0, 1000000, false, true) && isValid; isValid = evg_validateInput(evg_el.evCredits, 0, evPriceVal, false, true, 'evPrice') && isValid; isValid = evg_validateInput(evg_el.evDownPayment, 0, evEffectivePrice, false, true, null, evEffectivePrice) && isValid; isValid = evg_validateInput(evg_el.evLoanTerm, 1, 120, true, false) && isValid; isValid = evg_validateInput(evg_el.evLoanRate, 0, 100, false, true) && isValid; isValid = evg_validateInput(evg_el.evElectricityCost, 0.01, 5, false, false) && isValid; isValid = evg_validateInput(evg_el.evEfficiency, 0.1, 10, false, false) && isValid; isValid = evg_validateInput(evg_el.evMaintenance, 0, 10000, false, true) && isValid; isValid = evg_validateInput(evg_el.evInsurance, 0, 20000, false, true) && isValid; isValid = evg_validateInput(evg_el.evResaleValue, 0, evEffectivePrice, false, true, null, evEffectivePrice) && isValid; return isValid; } function evg_validateTab2() { let isValid = true; const gasPriceVal = parseFloat(evg_el.gasPrice.value) || 0; isValid = evg_validateInput(evg_el.gasPrice, 0, 1000000, false, true) && isValid; isValid = evg_validateInput(evg_el.gasDownPayment, 0, gasPriceVal, false, true, 'gasPrice') && isValid; isValid = evg_validateInput(evg_el.gasLoanTerm, 1, 120, true, false) && isValid; isValid = evg_validateInput(evg_el.gasLoanRate, 0, 100, false, true) && isValid; isValid = evg_validateInput(evg_el.gasFuelPrice, 0.01, 20, false, false) && isValid; isValid = evg_validateInput(evg_el.gasEfficiency, 1, 100, false, false) && isValid; isValid = evg_validateInput(evg_el.gasMaintenance, 0, 10000, false, true) && isValid; isValid = evg_validateInput(evg_el.gasInsurance, 0, 20000, false, true) && isValid; isValid = evg_validateInput(evg_el.gasResaleValue, 0, gasPriceVal, false, true, 'gasPrice') && isValid; return isValid; } function evg_openTab(evt, tabId) { const tabContents = document.querySelectorAll('#evVsGasSavingsCalculator .evg-tab-content'); tabContents.forEach(tc => { if(tc && tc.style) tc.style.display = 'none'; }); const tabButtons = document.querySelectorAll('#evVsGasSavingsCalculator .evg-tab-button'); tabButtons.forEach(tb => { if(tb) tb.classList.remove('active'); }); const targetTab = document.getElementById(tabId); if (targetTab && targetTab.style) targetTab.style.display = 'block'; if (evt && evt.currentTarget) { evt.currentTarget.classList.add('active'); evg_currentTab = Array.from(tabButtons).indexOf(evt.currentTarget); } else { if (tabButtons[evg_currentTab]) tabButtons[evg_currentTab].classList.add('active'); } evg_updateNavButtons(); } function evg_navigateTab(direction) { const tabs = document.querySelectorAll('#evVsGasSavingsCalculator .evg-tab-button'); let newTabIndex = evg_currentTab + direction; if (direction === 1) { if (evg_currentTab === 0) { if (!evg_validateTab1()) { alert("Please correct errors on Step 1."); return; } } else if (evg_currentTab === 1) { if (!evg_validateTab1()) { alert("Please correct errors on Step 1."); if(tabs[0]) tabs[0].click(); return; } if (!evg_validateTab2()) { alert("Please correct errors on Step 2."); return; } evg_calculateAndDisplayResults(); if (evg_el.resultsTabButton) evg_el.resultsTabButton.disabled = false; } } if (newTabIndex >= 0 && newTabIndex < tabs.length) { if(tabs[newTabIndex]) tabs[newTabIndex].click(); } } function evg_updateNavButtons() { if (!evg_el.prevBtn || !evg_el.nextBtn) return; const numTabs = document.querySelectorAll('#evVsGasSavingsCalculator .evg-tab-button').length; evg_el.prevBtn.disabled = (evg_currentTab === 0); evg_el.nextBtn.disabled = (evg_currentTab === numTabs - 1); if (evg_currentTab === 1) evg_el.nextBtn.textContent = 'Calculate & Compare'; else evg_el.nextBtn.textContent = 'Next'; } function evg_calculateMonthlyLoanPayment(principal, annualRate, termMonths) { if (principal <= 0) return 0; if (termMonths <=0) return principal; // Or NaN to indicate error more clearly const monthlyRate = (annualRate / 100) / 12; if (monthlyRate === 0) return principal / termMonths; // Avoid division by zero in formula for 0% rate return principal * (monthlyRate * Math.pow(1 + monthlyRate, termMonths)) / (Math.pow(1 + monthlyRate, termMonths) - 1); } function evg_calculateLoanCostsOverPeriod(principal, annualRate, loanTermMonths, ownershipMonths) { const monthlyPayment = evg_calculateMonthlyLoanPayment(principal, annualRate, loanTermMonths); if (isNaN(monthlyPayment) || principal <=0) { // if principal is 0, no loan costs return { totalPayments: 0, totalInterest: 0, monthlyPayment: 0 }; } const actualLoanTermMonths = Math.max(1, loanTermMonths); // Ensure loan term is at least 1 for calc const monthsToPay = Math.min(ownershipMonths, actualLoanTermMonths); let totalInterestPaid = 0; let balance = principal; const monthlyRate = (annualRate / 100) / 12; for (let i = 0; i < monthsToPay; i++) { let interestThisMonth = 0; if (monthlyRate > 0) { interestThisMonth = balance * monthlyRate; } let principalThisMonth = monthlyPayment - interestThisMonth; if (balance - principalThisMonth < 0.01 && i === monthsToPay -1 && i === actualLoanTermMonths -1) { // last payment of the loan principalThisMonth = balance; // Recalculate interest for this last payment based on exact principal if (monthlyRate > 0) interestThisMonth = (monthlyPayment - principalThisMonth); else interestThisMonth = 0; } totalInterestPaid += interestThisMonth; balance -= principalThisMonth; if (balance <= 0.01 && i < actualLoanTermMonths -1) { // Paid off early within ownership period (e.g. due to rounding) // totalInterestPaid should reflect actual interest up to payoff totalInterestPaid = (monthlyPayment * (i + 1)) - principal; break; } } const totalPaymentsMade = monthlyPayment * monthsToPay; totalInterestPaid = Math.max(0, totalInterestPaid); // Ensure it's not negative return { totalPayments: totalPaymentsMade, totalInterest: totalInterestPaid, monthlyPayment: monthlyPayment }; } function evg_calculateAndDisplayResults() { const ownershipYears = parseFloat(evg_el.ownershipDuration.value); const ownershipMonths = ownershipYears * 12; const annualMiles = parseFloat(evg_el.annualMilesDriven.value); const salesTaxRate = parseFloat(evg_el.salesTaxRate.value) / 100; // EV Calculations const evPrice = parseFloat(evg_el.evPrice.value); const evCredits = parseFloat(evg_el.evCredits.value); const evEffectivePrice = evPrice - evCredits; const evDownPayment = parseFloat(evg_el.evDownPayment.value); const evLoanTerm = parseInt(evg_el.evLoanTerm.value); const evLoanRate = parseFloat(evg_el.evLoanRate.value); const evSalesTax = evEffectivePrice * salesTaxRate; const evNetUpfront = evDownPayment + evSalesTax; const evLoanPrincipal = Math.max(0, evEffectivePrice - evDownPayment); const evLoanCosts = evg_calculateLoanCostsOverPeriod(evLoanPrincipal, evLoanRate, evLoanTerm, ownershipMonths); const evElectricityCostPerKWh = parseFloat(evg_el.evElectricityCost.value); const evEfficiency = parseFloat(evg_el.evEfficiency.value); const evAnnualElectricityCost = evEfficiency > 0 ? (annualMiles / evEfficiency) * evElectricityCostPerKWh : 0; const evTotalElectricityCost = evAnnualElectricityCost * ownershipYears; const evTotalMaintenance = parseFloat(evg_el.evMaintenance.value) * ownershipYears; const evTotalInsurance = parseFloat(evg_el.evInsurance.value) * ownershipYears; const evResaleValue = parseFloat(evg_el.evResaleValue.value); const evTotalCost = evNetUpfront + evLoanCosts.totalPayments + evTotalElectricityCost + evTotalMaintenance + evTotalInsurance - evResaleValue; // Gas Car Calculations const gasPrice = parseFloat(evg_el.gasPrice.value); const gasDownPayment = parseFloat(evg_el.gasDownPayment.value); const gasLoanTerm = parseInt(evg_el.gasLoanTerm.value); const gasLoanRate = parseFloat(evg_el.gasLoanRate.value); const gasSalesTax = gasPrice * salesTaxRate; const gasNetUpfront = gasDownPayment + gasSalesTax; const gasLoanPrincipal = Math.max(0, gasPrice - gasDownPayment); const gasLoanCosts = evg_calculateLoanCostsOverPeriod(gasLoanPrincipal, gasLoanRate, gasLoanTerm, ownershipMonths); const gasFuelPricePerGallon = parseFloat(evg_el.gasFuelPrice.value); const gasEfficiencyMPG = parseFloat(evg_el.gasEfficiency.value); const gasAnnualFuelCost = gasEfficiencyMPG > 0 ? (annualMiles / gasEfficiencyMPG) * gasFuelPricePerGallon : 0; const gasTotalFuelCost = gasAnnualFuelCost * ownershipYears; const gasTotalMaintenance = parseFloat(evg_el.gasMaintenance.value) * ownershipYears; const gasTotalInsurance = parseFloat(evg_el.gasInsurance.value) * ownershipYears; const gasResaleValue = parseFloat(evg_el.gasResaleValue.value); const gasTotalCost = gasNetUpfront + gasLoanCosts.totalPayments + gasTotalFuelCost + gasTotalMaintenance + gasTotalInsurance - gasResaleValue; if(evg_el.comparisonPeriodText) evg_el.comparisonPeriodText.textContent = `Comparison over ${ownershipYears} years (${ownershipMonths} months).`; const resultsHTML = `| Metric | Electric Car (EV) | Gasoline Car |
|---|---|---|
| Effective Purchase Price (after credits) | ${evg_formatCurrency(evEffectivePrice)} | ${evg_formatCurrency(gasPrice)} |
| Net Upfront Cost (Down Payment + Sales Tax) | ${evg_formatCurrency(evNetUpfront)} | ${evg_formatCurrency(gasNetUpfront)} |
| Loan Amount | ${evg_formatCurrency(evLoanPrincipal)} | ${evg_formatCurrency(gasLoanPrincipal)} |
| Monthly Loan Payment | ${evg_formatCurrency(evLoanCosts.monthlyPayment)} | ${evg_formatCurrency(gasLoanCosts.monthlyPayment)} |
| Total Loan Payments (over ownership) | ${evg_formatCurrency(evLoanCosts.totalPayments)} | ${evg_formatCurrency(gasLoanCosts.totalPayments)} |
| Total Interest Paid (over ownership) | ${evg_formatCurrency(evLoanCosts.totalInterest)} | ${evg_formatCurrency(gasLoanCosts.totalInterest)} |
| Annual Energy/Fuel Cost | ${evg_formatCurrency(evAnnualElectricityCost)} | ${evg_formatCurrency(gasAnnualFuelCost)} |
| Total Energy/Fuel Cost (over ownership) | ${evg_formatCurrency(evTotalElectricityCost)} | ${evg_formatCurrency(gasTotalFuelCost)} |
| Total Maintenance Cost (over ownership) | ${evg_formatCurrency(evTotalMaintenance)} | ${evg_formatCurrency(gasTotalMaintenance)} |
| Total Insurance Cost (over ownership) | ${evg_formatCurrency(evTotalInsurance)} | ${evg_formatCurrency(gasTotalInsurance)} |
| (-) Estimated Resale Value | -${evg_formatCurrency(evResaleValue)} | -${evg_formatCurrency(gasResaleValue)} |
| Net Total Cost of Ownership | ${evg_formatCurrency(evTotalCost)} | ${evg_formatCurrency(gasTotalCost)} |
| Average Monthly Cost (TCO / months) | ${evg_formatCurrency(evTotalCost / ownershipMonths)} | ${evg_formatCurrency(gasTotalCost / ownershipMonths)} |
