add year 0 row and Nebenkosten mitfinanzieren toggle

This commit is contained in:
Johannes
2026-03-20 02:33:17 +01:00
parent 9d735fb10f
commit 183e69f0b7
4 changed files with 106 additions and 11 deletions

34
calc.js
View File

@@ -13,7 +13,16 @@ function run_simulation(p) {
const months = p.years * 12; const months = p.years * 12;
// --- mortgage setup --- // --- mortgage setup ---
const loan = p.home_price * (1 - p.down_pct / 100); const down_payment = p.home_price * (p.down_pct / 100);
const closing_costs = p.home_price * (p.closing_buy_pct / 100);
// if financed: closing costs rolled into loan, buyer only needs down payment upfront
const loan = p.finance_nebenkosten
? p.home_price * (1 - p.down_pct / 100) + closing_costs
: p.home_price * (1 - p.down_pct / 100);
const upfront_buy = p.finance_nebenkosten
? down_payment
: down_payment + closing_costs;
const r_m = p.interest_rate / 100 / 12; const r_m = p.interest_rate / 100 / 12;
const n = p.loan_years * 12; const n = p.loan_years * 12;
let monthly_payment; let monthly_payment;
@@ -23,9 +32,6 @@ function run_simulation(p) {
monthly_payment = loan * (r_m * Math.pow(1 + r_m, n)) / (Math.pow(1 + r_m, n) - 1); monthly_payment = loan * (r_m * Math.pow(1 + r_m, n)) / (Math.pow(1 + r_m, n) - 1);
} }
// upfront costs for buyer
const down_payment = p.home_price * (p.down_pct / 100);
const upfront_buy = down_payment + p.home_price * (p.closing_buy_pct / 100);
const start_capital = p.start_capital || 0; const start_capital = p.start_capital || 0;
// --- state --- // --- state ---
@@ -53,6 +59,26 @@ function run_simulation(p) {
let breakeven_month = null; let breakeven_month = null;
// --- year 0 snapshot (day of purchase) ---
const selling_costs_0 = home_value * (p.closing_sell_pct / 100);
labels.push('Jahr 0');
buyer_nw_arr.push(Math.round(home_value - selling_costs_0 - balance + buyer_portfolio));
renter_nw_arr.push(Math.round(start_capital));
cum_buy_arr.push(Math.round(upfront_buy));
cum_rent_arr.push(0);
detail_arr.push({
house_value: Math.round(home_value),
mortgage_balance: Math.round(balance),
buyer_portfolio: Math.round(buyer_portfolio),
yr_interest: 0,
yr_principal: 0,
yr_upkeep: 0,
yr_rent: 0,
yr_renters_ins: 0,
kaufnebenkosten: Math.round(closing_costs),
finance_nebenkosten: p.finance_nebenkosten,
});
const invest_r_m = p.invest_return / 100 / 12; const invest_r_m = p.invest_return / 100 / 12;
const app_r_m = p.appreciation / 100 / 12; const app_r_m = p.appreciation / 100 / 12;
const rent_inc_m = p.rent_increase / 100 / 12; const rent_inc_m = p.rent_increase / 100 / 12;

View File

@@ -120,6 +120,17 @@
</div> </div>
</div> </div>
<div class="input-group toggle-group">
<label class="toggle-label" for="finance_nebenkosten">
<span>Nebenkosten mitfinanzieren</span>
<span class="hint">in Hypothek einrechnen</span>
</label>
<label class="toggle-switch">
<input type="checkbox" id="finance_nebenkosten" />
<span class="toggle-track"><span class="toggle-thumb"></span></span>
</label>
</div>
<div class="input-group"> <div class="input-group">
<label for="closing_sell_pct">Verkaufsnebenkosten <span class="hint">(Makler)</span></label> <label for="closing_sell_pct">Verkaufsnebenkosten <span class="hint">(Makler)</span></label>
<div class="slider-row"> <div class="slider-row">

View File

@@ -201,6 +201,51 @@ label {
.unit.prefix { color: var(--text-muted); } .unit.prefix { color: var(--text-muted); }
/* ===== Toggle switch ===== */
.toggle-group {
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.toggle-label {
display: flex;
flex-direction: column;
gap: 0.1rem;
color: var(--text-muted);
font-size: 0.82rem;
font-weight: 500;
cursor: pointer;
}
.toggle-switch { cursor: pointer; flex-shrink: 0; }
.toggle-switch input { display: none; }
.toggle-track {
display: block;
width: 40px;
height: 22px;
background: rgba(255,255,255,0.1);
border-radius: 99px;
position: relative;
transition: background 0.2s;
}
.toggle-thumb {
position: absolute;
top: 3px; left: 3px;
width: 16px; height: 16px;
background: var(--text-muted);
border-radius: 50%;
transition: transform 0.2s, background 0.2s;
}
.toggle-switch input:checked + .toggle-track { background: var(--buy-muted); }
.toggle-switch input:checked + .toggle-track .toggle-thumb {
transform: translateX(18px);
background: var(--buy);
}
/* ===== Range slider styling ===== */ /* ===== Range slider styling ===== */
input[type="range"] { input[type="range"] {
-webkit-appearance: none; -webkit-appearance: none;

17
ui.js
View File

@@ -25,6 +25,7 @@ const get_params = () => ({
rent_increase: +document.getElementById('rent_increase').value, rent_increase: +document.getElementById('rent_increase').value,
renters_ins: +document.getElementById('renters_ins').value, renters_ins: +document.getElementById('renters_ins').value,
start_capital: +document.getElementById('start_capital').value, start_capital: +document.getElementById('start_capital').value,
finance_nebenkosten: document.getElementById('finance_nebenkosten').checked,
}); });
// ===== Charts ===== // ===== Charts =====
@@ -206,12 +207,22 @@ const build_table = (result) => {
const td_buy_cost = document.createElement('td'); const td_buy_cost = document.createElement('td');
td_buy_cost.textContent = fmt_eur(result.cum_buy_arr[i]); td_buy_cost.textContent = fmt_eur(result.cum_buy_arr[i]);
td_buy_cost.classList.add('has-tip'); td_buy_cost.classList.add('has-tip');
td_buy_cost.addEventListener('mouseenter', (e) => show_tooltip(e, [ td_buy_cost.addEventListener('mouseenter', (e) => {
const rows = i === 0
? [
`<span style="color:#8b949e;font-size:0.72rem;text-transform:uppercase;letter-spacing:.05em">Kauftag</span>`,
tip_row('Eigenkapital', result.cum_buy_arr[0] - (d.finance_nebenkosten ? 0 : d.kaufnebenkosten), '#f59e0b'),
tip_row('Kaufnebenkosten', d.kaufnebenkosten, d.finance_nebenkosten ? '#8b949e' : '#f59e0b'),
d.finance_nebenkosten ? `<span style="color:#6e7681;font-size:0.75rem">↳ mitfinanziert</span>` : null,
].filter(Boolean)
: [
`<span style="color:#8b949e;font-size:0.72rem;text-transform:uppercase;letter-spacing:.05em">dieses Jahr</span>`, `<span style="color:#8b949e;font-size:0.72rem;text-transform:uppercase;letter-spacing:.05em">dieses Jahr</span>`,
tip_row('Zinsen', d.yr_interest, '#f59e0b'), tip_row('Zinsen', d.yr_interest, '#f59e0b'),
tip_row('Tilgung', d.yr_principal, '#f59e0b'), tip_row('Tilgung', d.yr_principal, '#f59e0b'),
tip_row('Nebenkosten', d.yr_upkeep, '#f59e0b'), tip_row('Nebenkosten', d.yr_upkeep, '#f59e0b'),
].join('<br>'))); ];
show_tooltip(e, rows.join('<br>'));
});
const td_rent_cost = document.createElement('td'); const td_rent_cost = document.createElement('td');
td_rent_cost.textContent = fmt_eur(result.cum_rent_arr[i]); td_rent_cost.textContent = fmt_eur(result.cum_rent_arr[i]);
@@ -304,6 +315,8 @@ const SLIDER_IDS = [
SLIDER_IDS.forEach(wire_slider); SLIDER_IDS.forEach(wire_slider);
// wire plain number inputs (no slider) // wire plain number inputs (no slider)
document.getElementById('finance_nebenkosten').addEventListener('change', recalc);
['home_price', 'insurance', 'monthly_rent', 'renters_ins', 'start_capital'].forEach((id) => { ['home_price', 'insurance', 'monthly_rent', 'renters_ins', 'start_capital'].forEach((id) => {
document.getElementById(id).addEventListener('input', recalc); document.getElementById(id).addEventListener('input', recalc);
}); });