<body>
<div class="calculator-card">
<!-- Banner Image -->
<img src="Banner - White.jpg" alt="Agent Banner" class="banner-img" onerror="this.style.display='none'" style="display: none;">
<div class="card-content-wrapper">
<header>
<h1>Real Estate Investment Analyzer</h1>
<p style="color:var(--text-sub); margin-top:0.5rem">Ontario, Canada</p>
</header>
<div class="layout-grid">
<!-- 1. Mortgage Section -->
<div class="section-box">
<div class="section-header">
<h2>1. Mortgage & Investment</h2>
</div>
<div class="section-content">
<!-- Row 1: Price & Down Payment % -->
<div class="mortgage-grid-inner">
<div class="input-group">
<label>Property Price</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<!-- Changed to type="text" for formatting -->
<input type="text" inputmode="decimal" id="price" value="800,000" style="padding-left: 1.8rem;">
</div>
</div>
<div class="input-group">
<label>Down Payment</label>
<div class="input-wrapper">
<input type="number" id="down-payment" value="20">
<span class="suffix">%</span>
</div>
</div>
</div>
<!-- Row 2: Calculated Down Payment $ & Loan Amount (New) -->
<div class="mortgage-grid-inner">
<div class="input-group">
<label>Down Payment Amount</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" id="down-payment-amt" readonly="" style="padding-left: 1.8rem;">
</div>
</div>
<div class="input-group">
<label>Loan Amount</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" id="loan-amount" readonly="" style="padding-left: 1.8rem;">
</div>
</div>
</div>
<!-- Row 3: Closing Costs & Appreciation -->
<div class="mortgage-grid-inner">
<div class="input-group">
<label>Closing Costs</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="closing-costs" value="15,000" style="padding-left: 1.8rem;">
</div>
</div>
<div class="input-group">
<label>Annual Appreciation</label>
<div class="input-wrapper">
<input type="number" id="appreciation" value="3.0" step="0.1">
<span class="suffix">%</span>
</div>
</div>
</div>
<!-- Row 4: Rate & Amortization -->
<div class="mortgage-grid-inner">
<div class="input-group">
<label>Interest Rate</label>
<div class="input-wrapper">
<input type="number" id="rate" value="5.0" step="0.01">
<span class="suffix">%</span>
</div>
</div>
<div class="input-group">
<label>Amortization</label>
<select id="amortization">
<option value="25" selected="">25 Years</option>
<option value="30">30 Years</option>
</select>
</div>
</div>
<div class="result-highlight">
<label>Est. Monthly Payment</label>
<span id="monthly-mortgage-display" class="value-display">$3,722.27</span>
</div>
</div>
</div>
<!-- 2. Rental Income Section -->
<div class="section-box">
<div class="section-header">
<h2>2. Rental Income</h2>
</div>
<div class="section-content">
<div class="input-group">
<label>Unit 1 Rent</label>
<div class="input-wrapper">
<span style="position:absolute; left:1rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="rent-unit-1" value="2,500" style="padding-left: 2rem;">
</div>
</div>
<div class="input-group">
<label>Unit 2 Rent</label>
<div class="input-wrapper">
<span style="position:absolute; left:1rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="rent-unit-2" value="1,800" style="padding-left: 2rem;">
</div>
</div>
<div class="result-highlight" style="background-color: #ecfdf5; border-color: #d1fae5;">
<label style="color: #047857;">Total Monthly Income</label>
<span id="total-rental-income" class="value-display" style="color: #047857;">$4,300.00</span>
</div>
</div>
</div>
<!-- 3. Operating Expenses Section -->
<div class="section-box">
<div class="section-header">
<h2>3. Monthly Expenses</h2>
</div>
<div class="section-content">
<div class="expenses-grid">
<div class="input-group">
<label>Tax</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="exp-tax" value="450" style="padding-left: 1.5rem;">
</div>
</div>
<div class="input-group">
<label>Utilities</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="exp-util" value="300" style="padding-left: 1.5rem;">
</div>
</div>
<div class="input-group">
<label>Insurance</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="exp-ins" value="120" style="padding-left: 1.5rem;">
</div>
</div>
<div class="input-group">
<label>Maint.</label>
<div class="input-wrapper">
<span style="position:absolute; left:0.8rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="exp-maint" value="200" style="padding-left: 1.5rem;">
</div>
</div>
</div>
<div class="input-group">
<label>Property Management</label>
<div class="input-wrapper">
<span style="position:absolute; left:1rem; color:var(--text-sub);">$</span>
<input type="text" inputmode="decimal" id="exp-mgmt" value="0" style="padding-left: 2rem;">
</div>
</div>
<div class="result-highlight" style="background-color: #fff1f2; border-color: #ffe4e6;">
<label style="color: #be123c;">Total Monthly Expenses</label>
<span id="total-expenses" class="value-display" style="color: #be123c;">$1,070.00</span>
</div>
</div>
</div>
<!-- 4. Analysis Section -->
<div class="section-box">
<div class="section-header">
<h2>4. Analysis</h2>
</div>
<div class="section-content">
<div class="analysis-grid">
<div class="analysis-card dark">
<h3 id="cash-flow-result" style="color: rgb(248, 113, 113);">-$492.27</h3>
<p>Monthly Cash Flow</p>
<span class="sub-text">(Net Income)</span>
</div>
<div class="analysis-card blue">
<h3 id="coc-result">-3.4%</h3>
<p>Cash on Cash</p>
<span class="sub-text">(Annual Cash Flow / Invested)</span>
</div>
<div class="analysis-card white">
<h3 id="total-return-result" style="color: var(--primary);">$615.60</h3>
<p>Monthly Total Return</p>
<span class="sub-text">(Cash Flow + Principal)</span>
</div>
<div class="analysis-card accent">
<h3 id="roi-result">89.4%</h3>
<p>5-Year Total ROI</p>
<span class="sub-text">(Total Gain / Invested)</span>
</div>
</div>
</div>
</div>
</div> <!-- End Grid -->
<!-- Table Section -->
<div class="table-container">
<h4 style="margin-top:0; margin-bottom:1rem; color:var(--primary);">5-Year Projection</h4>
<table>
<thead>
<tr>
<th>Year</th>
<th>Annual Interest</th>
<th>Total Principal Paid</th>
<th>Balance Remaining</th>
</tr>
</thead>
<tbody id="projection-body"><tr>
<td style="font-weight:600">Year 1</td>
<td style="color:#ef4444">$31,372.82</td>
<td style="color:var(--primary); font-weight:500">$13,294.44</td>
<td style="color:var(--text-sub)">$626,705.56</td>
</tr><tr>
<td style="font-weight:600">Year 2</td>
<td style="color:#ef4444">$30,699.79</td>
<td style="color:var(--primary); font-weight:500">$27,261.91</td>
<td style="color:var(--text-sub)">$612,738.09</td>
</tr><tr>
<td style="font-weight:600">Year 3</td>
<td style="color:#ef4444">$29,992.69</td>
<td style="color:var(--primary); font-weight:500">$41,936.49</td>
<td style="color:var(--text-sub)">$598,063.51</td>
</tr><tr>
<td style="font-weight:600">Year 4</td>
<td style="color:#ef4444">$29,249.79</td>
<td style="color:var(--primary); font-weight:500">$57,353.97</td>
<td style="color:var(--text-sub)">$582,646.03</td>
</tr><tr>
<td style="font-weight:600">Year 5</td>
<td style="color:#ef4444">$28,469.28</td>
<td style="color:var(--primary); font-weight:500">$73,551.95</td>
<td style="color:var(--text-sub)">$566,448.05</td>
</tr></tbody>
</table>
</div>
<p class="note-canada">Mortgage calculated using semi-annual compounding (Canadian standard).</p>
</div>
</div>
<script>
// --- DOM Elements ---
const inputs = {
// Currency Fields (Managed as Text for Formatting)
price: document.getElementById('price'),
closingCosts: document.getElementById('closing-costs'),
rent1: document.getElementById('rent-unit-1'),
rent2: document.getElementById('rent-unit-2'),
tax: document.getElementById('exp-tax'),
util: document.getElementById('exp-util'),
ins: document.getElementById('exp-ins'),
maint: document.getElementById('exp-maint'),
mgmt: document.getElementById('exp-mgmt'),
// Percentage/Number Fields
downPayment: document.getElementById('down-payment'),
appreciation: document.getElementById('appreciation'),
rate: document.getElementById('rate'),
amortization: document.getElementById('amortization'),
};
const outputs = {
// Readonly Inputs for Mortgage Section
downPaymentAmt: document.getElementById('down-payment-amt'),
loanAmount: document.getElementById('loan-amount'),
monthlyMortgage: document.getElementById('monthly-mortgage-display'),
totalRent: document.getElementById('total-rental-income'),
totalExp: document.getElementById('total-expenses'),
cashFlow: document.getElementById('cash-flow-result'),
totalReturn: document.getElementById('total-return-result'),
coc: document.getElementById('coc-result'),
roi: document.getElementById('roi-result'),
tableBody: document.getElementById('projection-body')
};
// --- Formatting Helpers ---
function parseCurrency(valueStr) {
if (!valueStr) return 0;
// Remove commas and any non-numeric, non-dot characters
const cleanStr = valueStr.replace(/,/g, '');
return parseFloat(cleanStr) || 0;
}
function formatCurrencyDisplay(num) {
if (isNaN(num)) return '';
// Format with commas, no $ sign (handled by CSS/HTML labels)
return num.toLocaleString('en-US', {
minimumFractionDigits: 0,
maximumFractionDigits: 2
});
}
// --- Input Event Handling for Formatting ---
// List of keys in 'inputs' that are currency fields requiring formatting
const currencyKeys = ['price', 'closingCosts', 'rent1', 'rent2', 'tax', 'util', 'ins', 'maint', 'mgmt'];
currencyKeys.forEach(key => {
const el = inputs[key];
// On Focus: Remove commas to allow editing
el.addEventListener('focus', () => {
const val = parseCurrency(el.value);
el.value = val === 0 ? '' : val;
});
// On Blur: Add commas back
el.addEventListener('blur', () => {
const val = parseCurrency(el.value);
el.value = formatCurrencyDisplay(val);
});
});
// --- URL State Management ---
function loadStateFromURL() {
try {
const params = new URLSearchParams(window.location.search);
let hasChanges = false;
Object.keys(inputs).forEach(key => {
const val = params.get(key);
if (val !== null) {
// For currency fields, we need to ensure we format the loaded value
if (currencyKeys.includes(key)) {
const numVal = parseFloat(val);
inputs[key].value = formatCurrencyDisplay(numVal);
} else {
inputs[key].value = val;
}
hasChanges = true;
}
});
return hasChanges;
} catch (e) {
console.warn('Could not load state from URL:', e);
return false;
}
}
function updateURL() {
try {
const params = new URLSearchParams();
Object.keys(inputs).forEach(key => {
// Store raw numbers in URL for currency fields
if (currencyKeys.includes(key)) {
params.set(key, parseCurrency(inputs[key].value));
} else {
params.set(key, inputs[key].value);
}
});
const newUrl = window.location.pathname + '?' + params.toString();
window.history.replaceState(null, '', newUrl);
} catch (e) {
console.warn('URL state update failed (sandbox environment):', e);
}
}
// Share function removed per user request
// --- Formatter for Outputs ---
const money = new Intl.NumberFormat('en-CA', {
style: 'currency',
currency: 'CAD',
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const percent = new Intl.NumberFormat('en-CA', {
style: 'percent',
minimumFractionDigits: 1,
maximumFractionDigits: 1
});
// --- Main Logic ---
function calculateAll() {
updateURL();
// 1. Parse Inputs (using helper for currency fields)
const price = parseCurrency(inputs.price.value);
const dpPercent = parseFloat(inputs.downPayment.value) || 0;
const annualRatePercent = parseFloat(inputs.rate.value) || 0;
const years = parseInt(inputs.amortization.value);
const closingCosts = parseCurrency(inputs.closingCosts.value);
const appreciationRate = parseFloat(inputs.appreciation.value) || 0;
const downPaymentAmount = price * (dpPercent / 100);
const principal = price - downPaymentAmount;
const totalInitialInvestment = downPaymentAmount + closingCosts;
// Update Readonly Fields for Down Payment Amount and Loan Amount
outputs.downPaymentAmt.value = formatCurrencyDisplay(downPaymentAmount);
outputs.loanAmount.value = formatCurrencyDisplay(principal);
// Effective Monthly Rate from Semi-Annual
let monthlyRate = 0;
if (annualRatePercent > 0) {
const semiAnnualRate = (annualRatePercent / 100) / 2;
monthlyRate = Math.pow(1 + semiAnnualRate, 1 / 6) - 1;
}
const totalPayments = years * 12;
let monthlyPayment = 0;
if (monthlyRate === 0) {
monthlyPayment = principal / totalPayments;
} else {
monthlyPayment = principal * (
(monthlyRate * Math.pow(1 + monthlyRate, totalPayments)) /
(Math.pow(1 + monthlyRate, totalPayments) - 1)
);
}
outputs.monthlyMortgage.textContent = isFinite(monthlyPayment) ? money.format(monthlyPayment) : "$0.00";
// 2. Rental Income
const rent1 = parseCurrency(inputs.rent1.value);
const rent2 = parseCurrency(inputs.rent2.value);
const totalRent = rent1 + rent2;
outputs.totalRent.textContent = money.format(totalRent);
// 3. Operating Expenses
const tax = parseCurrency(inputs.tax.value);
const util = parseCurrency(inputs.util.value);
const ins = parseCurrency(inputs.ins.value);
const maint = parseCurrency(inputs.maint.value);
const mgmt = parseCurrency(inputs.mgmt.value);
const totalExp = tax + util + ins + maint + mgmt;
outputs.totalExp.textContent = money.format(totalExp);
// 4. Cash Flow
const netMonthlyCashFlow = totalRent - totalExp - monthlyPayment;
outputs.cashFlow.textContent = money.format(netMonthlyCashFlow);
if (netMonthlyCashFlow >= 0) {
outputs.cashFlow.style.color = '#4ade80';
} else {
outputs.cashFlow.style.color = '#f87171';
}
// 5. Cash on Cash Return
const annualCashFlow = netMonthlyCashFlow * 12;
let cocReturn = 0;
if (totalInitialInvestment > 0) {
cocReturn = annualCashFlow / totalInitialInvestment;
}
outputs.coc.textContent = percent.format(cocReturn);
// 6. Total Monthly Return
let firstYearPrincipal = 0;
let tempBalance = principal;
for (let m = 1; m <= 12; m++) {
const interestPart = tempBalance * monthlyRate;
const principalPart = monthlyPayment - interestPart;
firstYearPrincipal += principalPart;
tempBalance -= principalPart;
}
const avgMonthlyPrincipal = firstYearPrincipal / 12;
const totalReturn = netMonthlyCashFlow + avgMonthlyPrincipal;
outputs.totalReturn.textContent = money.format(totalReturn);
outputs.totalReturn.style.color = totalReturn >= 0 ? 'var(--primary)' : '#dc2626';
// 7. 5-Year ROI
let balanceLoop = principal;
let totalCashFlow5Years = netMonthlyCashFlow * 60;
for (let m = 1; m <= 60; m++) {
const interestP = balanceLoop * monthlyRate;
const principalP = monthlyPayment - interestP;
balanceLoop -= principalP;
if (balanceLoop < 0) balanceLoop = 0;
}
const futureValue = price * Math.pow(1 + appreciationRate/100, 5);
const equityAtExit = futureValue - balanceLoop;
const totalMoneyReturned = equityAtExit + totalCashFlow5Years;
const totalProfit = totalMoneyReturned - totalInitialInvestment;
let roi = 0;
if (totalInitialInvestment > 0) {
roi = totalProfit / totalInitialInvestment;
}
outputs.roi.textContent = percent.format(roi);
// 8. Table
generateTable(principal, monthlyRate, monthlyPayment);
}
function generateTable(startBalance, monthlyRate, monthlyPayment) {
outputs.tableBody.innerHTML = '';
let balance = startBalance;
let cumulativePrincipal = 0;
let yearlyInterest = 0;
for (let month = 1; month <= 60; month++) {
const interestPayment = balance * monthlyRate;
const principalPayment = monthlyPayment - interestPayment;
cumulativePrincipal += principalPayment;
yearlyInterest += interestPayment;
balance -= principalPayment;
if (balance < 0) balance = 0;
if (month % 12 === 0) {
const currentYear = month / 12;
const row = document.createElement('tr');
row.innerHTML = `
<td style="font-weight:600">Year ${currentYear}</td>
<td style="color:#ef4444">${money.format(yearlyInterest)}</td>
<td style="color:var(--primary); font-weight:500">${money.format(cumulativePrincipal)}</td>
<td style="color:var(--text-sub)">${money.format(balance)}</td>
`;
outputs.tableBody.appendChild(row);
yearlyInterest = 0;
}
}
}
// --- Event Listeners ---
Object.values(inputs).forEach(el => {
el.addEventListener('input', calculateAll);
});
// Initial Run
loadStateFromURL();
calculateAll();
</script>
</body>