add year 0 row and Nebenkosten mitfinanzieren toggle
This commit is contained in:
34
calc.js
34
calc.js
@@ -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;
|
||||||
|
|||||||
11
index.html
11
index.html
@@ -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">
|
||||||
|
|||||||
45
style.css
45
style.css
@@ -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
17
ui.js
@@ -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);
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user