PageSourceSearch

https://www.sloothy.ai/assets/calculators.js

js sloothy.ai collected 2026-10-05 14:58:39 UTC 12,984 bytes, 400 lines download raw bytes

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.