1/* Sloothy-Rechner (Vanilla-JS, exakt nach Lovable-Logik). 2 Selbst-erkennend: bindet nur, was auf der Seite vorhanden ist. 3 Aufgerufen aus app.js via window.SLOOTHY_initCalculators(nf). */ 4(() => { 5 var eur0 = new Intl.NumberFormat("de-DE", { maximumFractionDigits: 0 }); 6 function eur(n) { 7 return eur0.format(Math.round(n)) + " â¬"; 8 } 9 function num(n) { 10 return eur0.format(Math.round(n)); 11 } 12 function comma(n) { 13 return String(n).replace(".", ","); 14 } 15 16 function fillRange(el, pct, color, track) { 17 el.style.background = 18 "linear-gradient(90deg," + 19 (color || "var(--green)") + 20 " " + 21 pct + 22 "%," + 23 (track || "rgba(255,255,255,.16)") + 24 " " + 25 pct + 26 "%)"; 27 } 28 29 /* ---------- 1. Ersparnisrechner (Verkäuferanteil 3,57 %) ---------- */ 30 function bindErsparnis(root) { 31 var range = root.querySelector("[data-erspar-range]"); 32 if (!range) return; 33 if (root.dataset.cbound) return; 34 root.dataset.cbound = "1"; 35 var pOut = root.querySelector("[data-erspar-price]"); 36 var sOut = root.querySelector("[data-erspar-save]"); 37 function u() { 38 var v = +range.value; 39 if (pOut) pOut.textContent = eur(v); 40 if (sOut) sOut.textContent = eur(v * 0.0357); 41 fillRange(range, ((v - range.min) / (range.max - range.min)) * 100); 42 } 43 range.addEventListener("input", u); 44 u(); 45 } 46 47 /* ---------- 2. Provisionsrechner (7,14 % gesamt / 3,57 % je Partei) ---------- */ 48 function bindProvision(root) { 49 var range = root.querySelector("[data-prov-range]"); 50 if (!range) return; 51 if (root.dataset.cbound) return; 52 root.dataset.cbound = "1"; 53 var price = root.querySelector("[data-prov-price]"); 54 var gesamt = root.querySelector("[data-prov-gesamt]"); 55 var verk = root.querySelector("[data-prov-verk]"); 56 var kauf = root.querySelector("[data-prov-kauf]"); 57 function u() { 58 var v = +range.value, 59 g = v * 0.0714, 60 a = v * 0.0357; 61 if (price) price.textContent = eur(v); 62 if (gesamt) gesamt.textContent = eur(g); 63 if (verk) verk.textContent = eur(a); 64 if (kauf) kauf.textContent = eur(a); 65 fillRange(range, ((v - range.min) / (range.max - range.min)) * 100); 66 } 67 range.addEventListener("input", u); 68 u(); 69 } 70 71 /* ---------- 3. Zins-/Kaufpreis-Rechner (Annuität) ---------- */ 72 function kaufpreisFor(rate, ek, tilgungPct, zinsPct) { 73 var sum = zinsPct + tilgungPct; 74 var darlehen = sum > 0 ? (rate * 1200) / sum : 0; 75 var r = zinsPct / 100 / 12; 76 var Lr = darlehen * r; 77 var months = 78 tilgungPct > 0 && rate > Lr && r > 0 79 ? Math.log(rate / (rate - Lr)) / Math.log(1 + r) 80 : 0; 81 return { 82 kaufpreis: darlehen + ek, 83 darlehen: darlehen, 84 laufzeit: months / 12, 85 }; 86 } 87 function bindZinsen(root) { 88 var zins = root.querySelector("[data-z-zins]"); 89 if (!zins) return; 90 if (root.dataset.cbound) return; 91 root.dataset.cbound = "1"; 92 var rateIn = root.querySelector("[data-z-rate]"); 93 var ekIn = root.querySelector("[data-z-ek]"); 94 var tilPills = Array.prototype.slice.call( 95 root.querySelectorAll("[data-tilgung]"), 96 ); 97 var out = { 98 kp: root.querySelector("[data-z-kaufpreis]"), 99 dar: root.querySelector("[data-z-darlehen]"), 100 ek: root.querySelector("[data-z-ekout]"), 101 lauf: root.querySelector("[data-z-laufzeit]"), 102 sub: root.querySelector("[data-z-sub]"), 103 sens: root.querySelector("[data-z-sens]"), 104 fazit: root.querySelector("[data-z-fazit]"), 105 zval: root.querySelector("[data-z-zinsval]"), 106 }; 107 var state = { rate: 2000, ek: 100000, tilgung: 2, zins: 4 }; 108 function clampNum(v, min, max) { 109 v = parseInt(String(v).replace(/[^\d]/g, ""), 10); 110 if (isNaN(v)) v = min; 111 return Math.min(max, Math.max(min, v)); 112 } 113 114 function render() { 115 var res = kaufpreisFor(state.rate, state.ek, state.tilgung, state.zins); 116 if (out.kp) out.kp.textContent = eur(res.kaufpreis); 117 if (out.dar) out.dar.textContent = eur(res.darlehen); 118 if (out.ek) out.ek.textContent = eur(state.ek); 119 if (out.lauf) 120 out.lauf.textContent = 121 res.laufzeit > 0 ? res.laufzeit.toFixed(0) + " Jahre" : "â"; 122 if (out.sub) 123 out.sub.textContent = 124 "bei " + 125 eur(state.rate) + 126 "/Monat, " + 127 comma(state.tilgung) + 128 " % Tilgung und " + 129 comma(state.zins) + 130 " % Zins"; 131 if (out.zval) out.zval.textContent = comma(state.zins) + " %"; 132 fillRange(zins, ((state.zins - 0.5) / 7.5) * 100); 133 134 // Sensitivität: Balken über {2,3,4,5,6} % Zins 135 if (out.sens) { 136 var pts = [2, 3, 4, 5, 6].map((z) => ({ 137 z: z, 138 kp: kaufpreisFor(state.rate, state.ek, state.tilgung, z).kaufpreis, 139 })); 140 var maxKp = Math.max.apply( 141 null, 142 pts.map((p) => p.kp), 143 ); 144 out.sens.innerHTML = pts 145 .map((p) => { 146 var cur = Math.abs(p.z - state.zins) < 0.5; 147 return ( 148 '<div class="sens-row"><span class="sz">' + 149 comma(p.z) + 150 " %</span>" + 151 '<span class="sbar"><i style="width:' +
152 ((p.kp / maxKp) * 100).toFixed(1) + 153 "%;background:" + 154 (cur ? "var(--green)" : "var(--green-200)") + 155 '"></i></span>' + 156 '<span class="sv">' + 157 eur(p.kp) + 158 "</span></div>" 159 ); 160 }) 161 .join(""); 162 } 163 // Fazit-Band 164 if (out.fazit) { 165 var zs = comma(state.zins.toFixed(1)), 166 t; 167 if (state.zins <= 2.5) 168 t = 169 "Bei nur " + 170 zs + 171 " % Zins ist die Finanzierungskraft hoch â Käufer können mehr bieten. Ein guter Moment zum Verkauf."; 172 else if (state.zins <= 4.5) 173 t = 174 "Bei " + 175 zs + 176 " % Zins bewegt sich der Markt im mittleren Bereich. Ein realistischer Angebotspreis ist jetzt entscheidend."; 177 else 178 t = 179 "Bei " + 180 zs + 181 " % Zins sinkt die Finanzierungskraft der Käufer spürbar. Eine kluge Preisstrategie holt trotzdem das Maximum heraus."; 182 out.fazit.textContent = t; 183 } 184 } 185 if (rateIn) { 186 rateIn.value = num(state.rate); 187 rateIn.addEventListener("input", () => { 188 state.rate = clampNum(rateIn.value, 300, 10000); 189 render(); 190 }); 191 rateIn.addEventListener("blur", () => { 192 rateIn.value = num(state.rate); 193 }); 194 } 195 if (ekIn) { 196 ekIn.value = num(state.ek); 197 ekIn.addEventListener("input", () => { 198 state.ek = clampNum(ekIn.value, 0, 2000000); 199 render(); 200 }); 201 ekIn.addEventListener("blur", () => { 202 ekIn.value = num(state.ek); 203 }); 204 } 205 tilPills.forEach((p) => { 206 p.addEventListener("click", () => { 207 state.tilgung = +p.getAttribute("data-tilgung"); 208 tilPills.forEach((x) => { 209 x.classList.toggle("active", x === p); 210 }); 211 render(); 212 }); 213 }); 214 zins.addEventListener("input", () => { 215 state.zins = +zins.value; 216 render(); 217 }); 218 render(); 219 } 220 221 /* ---------- 4. Hero âMein Objekt"-Karte (Angebotspreis â Ersparnis) ---------- */ 222 function bindObjektKarte(root) { 223 var range = root.querySelector("[data-ok-range]"); 224 if (!range) return; 225 if (root.dataset.cbound) return; 226 root.dataset.cbound = "1"; 227 var priceIn = root.querySelector("[data-ok-price]"); 228 var markt = root.querySelector("[data-ok-markt]"); 229 var save = root.querySelector("[data-ok-save]"); 230 var MIN = +range.min, 231 MAX = +range.max; 232 function update(v, fromInput) { 233 v = Math.min(MAX, Math.max(MIN, Math.round(v / 5000) * 5000)); 234 range.value = v; 235 if (!fromInput && priceIn) priceIn.value = num(v); 236 if (priceIn) priceIn.style.width = priceIn.value.length + 0.5 + "ch"; 237 if (markt) { 238 var lowT = Math.round((v * 0.958) / 5000) * 5, 239 highT = Math.round((v * 1.035) / 5000) * 5; 240 markt.textContent = lowT + "â" + highT + " Tâ¬"; 241 } 242 if (save) save.textContent = eur(v * 0.0357); 243 fillRange( 244 range, 245 ((v - MIN) / (MAX - MIN)) * 100, 246 "var(--green)", 247 "#e4dfd5", 248 ); 249 } 250 range.addEventListener("input", () => { 251 update(+range.value, false); 252 }); 253 if (priceIn) { 254 priceIn.addEventListener("input", () => { 255 var n = parseInt(priceIn.value.replace(/[^\d]/g, ""), 10); 256 if (!isNaN(n)) update(n, true); 257 }); 258 priceIn.addEventListener("blur", () => { 259 priceIn.value = num(+range.value); 260 }); 261 priceIn.addEventListener("focus", () => { 262 priceIn.select(); 263 }); 264 } 265 update(+range.value, false); 266 } 267 268 /* ---------- 5. Notarkosten-Rechner (GNotKG) ---------- 269 1,0-Gebuehr nach Gebuehrentabelle B (Anlage 2 GNotKG), aufgebaut aus den 270 Stufen des § 34 Abs. 2 GNotKG. Angesetzte Saetze: Beurkundung 2,0 271 (KV 21100, mind. 120 â¬), Vollzug 0,5 (KV 22110), Betreuung 0,5 (KV 22200), 272 Eigentumsumschreibung 1,0 (KV 14110), Loeschungserklaerung 0,5 273 (KV 21201 Nr. 4, mind. 30 â¬), Loeschung Abt. III 0,5 (KV 14140). 274 Umsatzsteuer 19 % nur auf die Notargebuehren (KV 32014). 275 Die Logik ist identisch zu schaetzeNotarkosten() in 276 src/app/(marketing)/_render/notarkosten-page.ts. */ 277 function gebuehrTabelleB(wert) { 278 if (wert <= 0) return 0; 279 if (wert <= 500) return 15; 280 var stufen = [ 281 [2000, 500, 4], 282 [10000, 1000, 6], 283 [25000, 3000, 8], 284 [50000, 5000, 10], 285 [200000, 15000, 27], 286 [500000, 30000, 50], 287 [5000000, 50000, 80], 288 ]; 289 var g = 15, 290 unten = 500; 291 for (var i = 0; i < stufen.length; i++) { 292 if (wert <= unten) break; 293 g += 294 Math.ceil((Math.min(wert, stufen[i][0]) - unten) / stufen[i][1]) * 295 stufen[i][2]; 296 unten = stufen[i][0]; 297 } 298 return g; 299 } 300 function notarkosten(kaufpreis, mitLoeschung) { 301 var b = gebuehrTabelleB(kaufpreis); 302 var beurkundung = Math.max(2 * b, 120); 303 var vollzug = 0.5 * b, 304 betreuung = 0.5 * b; 305 var netto = beurkundung + vollzug + betreuung; 306 var ust = netto * 0.19; 307 var grundbuch = 1 * b; 308 var loeschNotar = mitLoeschung ? Math.max(0.5 * b, 30) : 0; 309 var loeschUst = loeschNotar * 0.19; 310 var loeschGb = mitLoeschung ? 0.5 * b : 0; 311 var kaeufer = netto + ust + grundbuch; 312 var verkaeufer = loeschNotar + loeschUst + loeschGb; 313 return { 314 basis: b, 315 beurkundung: beurkundung, 316 neben: vollzug + betreuung, 317 ust: ust, 318 grundbuch: grundbuch, 319 loeschNotar: loeschNotar + loeschUst, 320 loeschGb: loeschGb, 321 kaeufer: kaeufer, 322 verkaeufer: verkaeufer, 323 gesamt: kaeufer + verkaeufer, 324 prozent: kaufpreis > 0 ? ((kaeufer + verkaeufer) / kaufpreis) * 100 : 0, 325 }; 326 } 327 function bindNotar(root) { 328 var range = root.querySelector("[data-nk-range]"); 329 if (!range) return; 330 if (root.dataset.cbound) return; 331 root.dataset.cbound = "1"; 332 var priceIn = root.querySelector("[data-nk-price]"); 333 var loesch = root.querySelector("[data-nk-loeschung]"); 334 var out = { 335 total: root.querySelector("[data-nk-total]"), 336 pct: root.querySelector("[data-nk-pct]"), 337 kaeufer: root.querySelector("[data-nk-kaeufer]"), 338 verkaeufer: root.querySelector("[data-nk-verkaeufer]"), 339 beurkundung: root.querySelector("[data-nk-beurkundung]"), 340 neben: root.querySelector("[data-nk-neben]"), 341 grundbuch: root.querySelector("[data-nk-grundbuch]"), 342 ust: root.querySelector("[data-nk-ust]"), 343 loeschNotar: root.querySelector("[data-nk-loeschnotar]"), 344 loeschGb: root.querySelector("[data-nk-loeschgb]"), 345 }; 346 var MIN = +range.min, 347 MAX = +range.max; 348 function render(v, fromInput) { 349 v = Math.min(MAX, Math.max(MIN, Math.round(v / 5000) * 5000)); 350 range.value = v; 351 if (!fromInput && priceIn) priceIn.value = num(v); 352 var r = notarkosten(v, !!(loesch && loesch.checked)); 353 if (out.total) out.total.textContent = eur(r.gesamt); 354 if (out.pct) out.pct.textContent = comma(r.prozent.toFixed(2)) + " %"; 355 if (out.kaeufer) out.kaeufer.textContent = eur(r.kaeufer); 356 if (out.verkaeufer) out.verkaeufer.textContent = eur(r.verkaeufer); 357 if (out.beurkundung) out.beurkundung.textContent = eur(r.beurkundung); 358 if (out.neben) out.neben.textContent = eur(r.neben); 359 if (out.grundbuch) out.grundbuch.textContent = eur(r.grundbuch); 360 if (out.ust) out.ust.textContent = eur(r.ust); 361 if (out.loeschNotar) out.loeschNotar.textContent = eur(r.loeschNotar); 362 if (out.loeschGb) out.loeschGb.textContent = eur(r.loeschGb); 363 fillRange(range, ((v - MIN) / (MAX - MIN)) * 100); 364 } 365 range.addEventListener("input", () => { 366 render(+range.value, false); 367 }); 368 if (priceIn) { 369 priceIn.addEventListener("input", () => { 370 var n = parseInt(priceIn.value.replace(/[^\d]/g, ""), 10); 371 if (!isNaN(n)) render(n, true); 372 }); 373 priceIn.addEventListener("blur", () => { 374 priceIn.value = num(+range.value); 375 }); 376 priceIn.addEventListener("focus", () => { 377 priceIn.select(); 378 }); 379 } 380 if (loesch) 381 loesch.addEventListener("change", () => { 382 render(+range.value, false); 383 }); 384 render(+range.value, false); 385 } 386 387 window.SLOOTHY_initCalculators = () => { 388 document.querySelectorAll("[data-erspar]").forEach(bindErsparnis); 389 document.querySelectorAll("[data-prov]").forEach(bindProvision); 390 document.querySelectorAll("[data-zinsen]").forEach(bindZinsen); 391 document.querySelectorAll("[data-objekt]").forEach(bindObjektKarte); 392 document.querySelectorAll("[data-notar]").forEach(bindNotar); 393 }; 394 // Falls app.js schon geladen hat: 395 if (document.readyState !== "loading") { 396 try { 397 window.SLOOTHY_initCalculators(); 398 } catch (e) {} 399 } 400})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.