/* eslint-disable */
// @ts-nocheck
// AUTO-GENERATED from kotsch.tech quick.blade.php — do not edit by hand.
// Regenerate via: node scripts/import-kotsch-quick.mjs
export function runKotschQuickCalc(slug: string, values: Record<string, string>): string {
  const value = (id: string) => (values[id] ?? "").toString().trim();
  const n = (id: string) => Number(String(value(id)).replace(",", ".")) || 0;
const euro = new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' });
    const num = new Intl.NumberFormat('de-DE', { maximumFractionDigits: 2 });
    const clamp = (val, min, max) => Math.min(max, Math.max(min, Number(val) || 0));
    const escapeHtml = text => String(text).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
    const rows = items => items.map(([label, val]) => `<div class="metric"><span>${label}</span><strong>${val}</strong></div>`).join('');
    const duration = seconds => {
        if (!Number.isFinite(seconds) || seconds < 0) return '-';
        if (seconds < 60) return `${num.format(seconds)} s`;
        if (seconds < 3600) return `${num.format(seconds / 60)} min`;
        if (seconds < 86400) return `${num.format(seconds / 3600)} h`;
        return `${num.format(seconds / 86400)} Tage`;
    };
    const parseNumbers = text => {
        // Split on the unambiguous separators first (whitespace, semicolon, newline).
        // If that yields several tokens, any comma inside a token is a German decimal mark
        // (so "12,5 13,1" -> [12.5, 13.1]). If it stays a single token, a lone comma is a
        // decimal mark ("12,5" -> [12.5]) but two or more commas mean a list ("12,15,11" ->
        // [12, 15, 11]).
        const tokens = String(text).trim().split(/[\s;]+/).filter(Boolean);
        let parts;
        if (tokens.length > 1) {
            parts = tokens;
        } else if (!tokens.length) {
            parts = [];
        } else {
            parts = (tokens[0].split(',').length > 2) ? tokens[0].split(',') : [tokens[0]];
        }
        return parts.map(t => Number(t.replace(',', '.'))).filter(v => Number.isFinite(v));
    };
    const daysBetween = (a, b) => Math.round((b - a) / 86400000);
    const gcd = (a, b) => b ? gcd(b, a % b) : Math.abs(a);
    const hexToRgb = hex => {
        const clean = String(hex).replace('#', '');
        return [0, 2, 4].map(i => parseInt(clean.slice(i, i + 2), 16) / 255);
    };
    const luminance = hex => {
        const [r, g, b] = hexToRgb(hex).map(v => v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4));
        return 0.2126 * r + 0.7152 * g + 0.0722 * b;
    };
    const simpleMarkdown = md => escapeHtml(md)
        .replace(/^### (.*)$/gm, '<h3>$1</h3>')
        .replace(/^## (.*)$/gm, '<h2>$1</h2>')
        .replace(/^# (.*)$/gm, '<h1>$1</h1>')
        .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
        .replace(/\*(.*?)\*/g, '<em>$1</em>')
        .replace(/\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g, '<a href="$2">$1</a>')
        .replace(/\n/g, '<br>');

    function calcIban(raw) {
        const iban = raw.toUpperCase().replace(/\s+/g, '');
        if (!/^[A-Z]{2}\d{2}[A-Z0-9]+$/.test(iban)) return { ok:false, msg:'Format ungültig' };
        const rearranged = iban.slice(4) + iban.slice(0, 4);
        let expanded = '';
        for (const ch of rearranged) expanded += /[A-Z]/.test(ch) ? String(ch.charCodeAt(0) - 55) : ch;
        let mod = 0;
        for (const digit of expanded) mod = (mod * 10 + Number(digit)) % 97;
        return { ok: mod === 1, msg: mod === 1 ? 'IBAN ist formal gültig' : 'Prüfziffer passt nicht', country: iban.slice(0,2), check: iban.slice(2,4), length: iban.length };
    }

    
function calculate() {
        switch (slug) {
            case 'stromkosten-rechner': {
                const kwh = n('watt') / 1000 * n('hours') * n('days');
                const dayKwh = n('watt') / 1000 * n('hours');
                return rows([
                    ['Verbrauch', `${num.format(kwh)} kWh`],
                    ['Kosten', euro.format(kwh * n('price'))],
                    ['Kosten pro Tag', euro.format(dayKwh * n('price'))],
                    ['Verbrauch pro Tag', `${num.format(dayKwh)} kWh`],
                    ['Kosten pro Woche', euro.format(dayKwh * 7 * n('price'))],
                    ['Kosten pro Monat 30 Tage', euro.format(dayKwh * 30 * n('price'))],
                    ['Kosten pro Jahr', euro.format(dayKwh * 365 * n('price'))],
                    ['Verbrauch pro Jahr', `${num.format(dayKwh * 365)} kWh`],
                    ['CO2 grob/Jahr', `${num.format(dayKwh * 365 * 0.38)} kg`],
                    ['Leistung in kW', `${num.format(n('watt') / 1000)} kW`],
                    ['Kosten pro Stunde', euro.format(n('watt') / 1000 * n('price'))],
                ]);
            }
            case 'heizkosten-rechner': {
                const kwh = n('area') * n('usage');
                const total = kwh * n('price') + n('base');
                return rows([
                    ['Jahresverbrauch', `${num.format(kwh)} kWh`],
                    ['Energiekosten', euro.format(kwh * n('price'))],
                    ['Gesamt pro Jahr', euro.format(total)],
                    ['Gesamt pro Monat', euro.format(total / 12)],
                    ['Kosten pro m2/Jahr', euro.format(total / Math.max(1, n('area')))],
                    ['Kosten pro m2/Monat', euro.format(total / Math.max(1, n('area')) / 12)],
                    ['Grundpreis-Anteil', `${num.format(total ? n('base') / total * 100 : 0)} %`],
                    ['Arbeitspreis-Anteil', `${num.format(total ? (kwh * n('price')) / total * 100 : 0)} %`],
                    ['Verbrauch pro m2', `${num.format(n('usage'))} kWh`],
                    ['CO2 grob/Jahr', `${num.format(kwh * 0.2)} kg`],
                    ['Effizienz grob', n('usage') < 80 ? 'gut' : n('usage') < 140 ? 'mittel' : 'hoch'],
                ]);
            }
            case 'leasing-rechner': {
                const total = n('rate') * n('months') + n('down');
                const effective = total / Math.max(1, n('months'));
                const factor = n('list') ? (n('rate') / n('list') * 100) : 0;
                return rows([
                    ['Leasingfaktor', `${num.format(factor)} %`],
                    ['Gesamtkosten', euro.format(total)],
                    ['Effektive Monatskosten', euro.format(effective)],
                    ['Raten gesamt', euro.format(n('rate') * n('months'))],
                    ['Anzahlung', euro.format(n('down'))],
                    ['Kostenanteil am Listenpreis', `${num.format(n('list') ? total / n('list') * 100 : 0)} %`],
                    ['Durchschnitt pro Jahr', euro.format(total / Math.max(1, n('months')) * 12)],
                    ['Rate pro 10.000 Euro Listenpreis', euro.format(n('list') ? n('rate') / n('list') * 10000 : 0)],
                    ['Bewertung Faktor', factor < 0.7 ? 'sehr gut' : factor < 1 ? 'gut' : 'hoch'],
                    ['Monate', n('months')],
                    ['Listenpreis', euro.format(n('list'))],
                ]);
            }
            case 'skonto-rechner': {
                const saved = n('amount') * n('discount') / 100;
                return rows([
                    ['Skonto', euro.format(saved)],
                    ['Zahlbetrag', euro.format(n('amount') - saved)],
                    ['Ersparnis', `${num.format(n('discount'))} %`],
                    ['Originalbetrag', euro.format(n('amount'))],
                    ['Nettoquote nach Skonto', `${num.format(100 - n('discount'))} %`],
                    ['Skonto bei 1%', euro.format(n('amount') * 0.01)],
                    ['Skonto bei 2%', euro.format(n('amount') * 0.02)],
                    ['Skonto bei 3%', euro.format(n('amount') * 0.03)],
                    ['Zahlbetrag bei 2%', euro.format(n('amount') * 0.98)],
                    ['Zahlbetrag bei 3%', euro.format(n('amount') * 0.97)],
                    ['Rundung', euro.format(Math.round((n('amount') - saved) * 100) / 100)],
                ]);
            }
            case 'inflations-rechner': {
                const future = n('amount') * Math.pow(1 + n('rate') / 100, n('years'));
                const factor = Math.pow(1 + n('rate') / 100, n('years'));
                return rows([
                    ['Zukuenftiger Preis', euro.format(future)],
                    ['Mehrkosten', euro.format(future - n('amount'))],
                    ['Heutige Kaufkraft dann', euro.format(n('amount') / factor)],
                    ['Inflationsfaktor', num.format(factor)],
                    ['Gesamtsteigerung', `${num.format((factor - 1) * 100)} %`],
                    ['Preis nach 1 Jahr', euro.format(n('amount') * Math.pow(1 + n('rate') / 100, 1))],
                    ['Preis nach 5 Jahren', euro.format(n('amount') * Math.pow(1 + n('rate') / 100, 5))],
                    ['Preis nach 10 Jahren', euro.format(n('amount') * Math.pow(1 + n('rate') / 100, 10))],
                    ['Kaufkraftverlust', euro.format(n('amount') - n('amount') / factor)],
                    ['Rate', `${num.format(n('rate'))} % p.a.`],
                    ['Zeitraum', `${num.format(n('years'))} Jahre`],
                ]);
            }
            case 'sparziel-rechner': {
                const remaining = Math.max(0, n('target') - n('current'));
                const months = n('monthly') > 0 ? Math.ceil(remaining / n('monthly')) : 0;
                return rows([
                    ['Noch nötig', euro.format(remaining)],
                    ['Dauer', months ? `${months} Monate` : 'Keine Sparrate'],
                    ['Ziel erreicht nach', months ? `${Math.floor(months / 12)} Jahren und ${months % 12} Monaten` : '-'],
                    ['Bereits gespart', euro.format(n('current'))],
                    ['Zielbetrag', euro.format(n('target'))],
                    ['Fortschritt', `${num.format(n('target') ? n('current') / n('target') * 100 : 0)} %`],
                    ['Sparrate pro Jahr', euro.format(n('monthly') * 12)],
                    ['Nach 6 Monaten', euro.format(n('current') + n('monthly') * 6)],
                    ['Nach 12 Monaten', euro.format(n('current') + n('monthly') * 12)],
                    ['Nötige Rate für 12 Monate', euro.format(remaining / 12)],
                    ['Nötige Rate für 24 Monate', euro.format(remaining / 24)],
                ]);
            }
            case 'stundenlohn-rechner': {
                const monthlyHours = n('hours') * 4.348;
                const hourly = n('salary') / Math.max(1, monthlyHours);
                return rows([
                    ['Monatsstunden', `${num.format(monthlyHours)} h`],
                    ['Stundenlohn', euro.format(hourly)],
                    ['Jahresgehalt', euro.format(n('salary') * 12)],
                    ['Wochenlohn rechnerisch', euro.format(n('salary') * 12 / 52)],
                    ['Tageslohn bei 5 Tagen', euro.format(n('salary') * 12 / 260)],
                    ['Minutenlohn', euro.format(hourly / 60)],
                    ['Halbtagslohn 4h', euro.format(hourly * 4)],
                    ['Tag 8h', euro.format(hourly * 8)],
                    ['Überstunden 10h', euro.format(hourly * 10)],
                    ['Monatsstunden gerundet', Math.round(monthlyHours)],
                    ['Jahresstunden', `${num.format(monthlyHours * 12)} h`],
                ]);
            }
            case 'meeting-kosten-rechner': {
                const hours = n('minutes') / 60;
                const total = n('people') * hours * n('rate');
                return rows([
                    ['Meetingkosten', euro.format(total)],
                    ['Personenstunden', `${num.format(n('people') * hours)} h`],
                    ['Kosten pro Minute', euro.format(n('people') * n('rate') / 60)],
                    ['Kosten pro Person', euro.format(hours * n('rate'))],
                    ['Dauer in Stunden', `${num.format(hours)} h`],
                    ['Kosten bei 30 min', euro.format(n('people') * 0.5 * n('rate'))],
                    ['Kosten bei 60 min', euro.format(n('people') * 1 * n('rate'))],
                    ['Kosten bei 90 min', euro.format(n('people') * 1.5 * n('rate'))],
                    ['Jahreskosten woechentlich', euro.format(total * 52)],
                    ['Jahreskosten monatlich', euro.format(total * 12)],
                    ['Einschätzung', total < 100 ? 'klein' : total < 500 ? 'mittel' : 'teuer'],
                ]);
            }
            case 'notendurchschnitt-rechner': {
                const grades = parseNumbers(value('grades'));
                const avg = grades.reduce((a,b) => a + b, 0) / Math.max(1, grades.length);
                const min = Math.min(...grades);
                const max = Math.max(...grades);
                const sorted = [...grades].sort((a,b) => a-b);
                const median = sorted.length ? sorted[Math.floor(sorted.length / 2)] : 0;
                return rows([
                    ['Anzahl Noten', grades.length],
                    ['Durchschnitt', num.format(avg)],
                    ['Gerundet', num.format(Math.round(avg * 10) / 10)],
                    ['Beste Note', grades.length ? num.format(min) : '-'],
                    ['Schlechteste Note', grades.length ? num.format(max) : '-'],
                    ['Median', grades.length ? num.format(median) : '-'],
                    ['Summe', num.format(grades.reduce((a,b) => a+b, 0))],
                    ['Noten kleiner/gleich 2', grades.filter(g => g <= 2).length],
                    ['Noten größer 4', grades.filter(g => g > 4).length],
                    ['Nötige 1,0 für neuen Schnitt', num.format((avg * grades.length + 1) / (grades.length + 1))],
                    ['Nötige 2,0 für neuen Schnitt', num.format((avg * grades.length + 2) / (grades.length + 1))],
                ]);
            }
            case 'iban-pruefer': {
                const check = calcIban(value('iban'));
                const iban = value('iban').toUpperCase().replace(/\s+/g, '');
                return rows([
                    ['Status', check.ok ? 'Gültig' : 'Nicht gültig'],
                    ['Hinweis', check.msg],
                    ['Land', check.country ?? '-'],
                    ['Prüfziffer', check.check ?? '-'],
                    ['Länge', check.length ?? '-'],
                    ['Ohne Leerzeichen', `<span class="mono">${escapeHtml(iban)}</span>`],
                    ['Gruppiert', `<span class="mono">${escapeHtml(iban.replace(/(.{4})/g, '$1 ').trim())}</span>`],
                    ['Beginnt mit DE', iban.startsWith('DE') ? 'ja' : 'nein'],
                    ['Enthaelt nur erlaubte Zeichen', /^[A-Z0-9]+$/.test(iban) ? 'ja' : 'nein'],
                    ['BBAN Teil', `<span class="mono">${escapeHtml(iban.slice(4) || '-')}</span>`],
                    ['Datenschutz', 'Prüfung lokal im Browser'],
                ]);
            }
            case 'utm-generator': {
                const url = new URL(value('url') || 'https://example.com');
                [['utm_source','source'], ['utm_medium','medium'], ['utm_campaign','campaign']].forEach(([key, id]) => { if (value(id)) url.searchParams.set(key, value(id)); });
                return rows([
                    ['Tracking URL', `<span class="mono">${escapeHtml(url.toString())}</span>`],
                    ['Domain', escapeHtml(url.hostname)],
                    ['Pfad', escapeHtml(url.pathname)],
                    ['utm_source', escapeHtml(value('source') || '-')],
                    ['utm_medium', escapeHtml(value('medium') || '-')],
                    ['utm_campaign', escapeHtml(value('campaign') || '-')],
                    ['Parameter gesamt', Array.from(url.searchParams.keys()).length],
                    ['URL Länge', url.toString().length],
                    ['Encoded Campaign', escapeHtml(encodeURIComponent(value('campaign')))],
                    ['Hat HTTPS', url.protocol === 'https:' ? 'ja' : 'nein'],
                    ['Kurz-Hinweis', url.toString().length > 180 ? 'sehr lang' : 'ok'],
                ]) + `<div class="result mono">${escapeHtml(url.toString())}</div>`;
            }
            case 'meta-title-checker': {
                const title = value('title');
                const status = title.length < 35 ? 'Eher kurz' : title.length > 60 ? 'Eher lang' : 'Guter Bereich';
                const words = title.trim().split(/\s+/).filter(Boolean).length;
                return rows([
                    ['Zeichen', title.length],
                    ['Einschätzung', status],
                    ['Woerter', words],
                    ['Pixel grob', `${num.format(title.length * 8.5)} px`],
                    ['Rest bis 60 Zeichen', 60 - title.length],
                    ['Mobile Vorschau', escapeHtml(title.slice(0, 45))],
                    ['Desktop Vorschau', escapeHtml(title.slice(0, 60))],
                    ['Hat Marke/Trenner', /[-|]/.test(title) ? 'ja' : 'nein'],
                    ['Hat Zahl', /\d/.test(title) ? 'ja' : 'nein'],
                    ['Erstes Wort', escapeHtml(title.trim().split(/\s+/)[0] || '-')],
                    ['Empfehlung', title.length >= 35 && title.length <= 60 ? 'passt' : 'anpassen'],
                ]) + `<div class="snippet"><div class="snippet-title">${escapeHtml(title)}</div><div class="snippet-url">https://kotsch.tech/</div><div class="snippet-desc">Beispiel einer Google-Snippet-Vorschau mit deinem Title.</div></div>`;
            }
            case 'meta-description-checker': {
                const desc = value('description');
                const status = desc.length < 90 ? 'Eher kurz' : desc.length > 160 ? 'Eher lang' : 'Guter Bereich';
                const words = desc.trim().split(/\s+/).filter(Boolean).length;
                return rows([
                    ['Zeichen', desc.length],
                    ['Einschätzung', status],
                    ['Woerter', words],
                    ['Rest bis 160 Zeichen', 160 - desc.length],
                    ['Mobile Vorschau 120', escapeHtml(desc.slice(0, 120))],
                    ['Desktop Vorschau 155', escapeHtml(desc.slice(0, 155))],
                    ['Hat Call-to-Action', /(jetzt|kostenlos|testen|berechnen|lernen|starten)/i.test(desc) ? 'ja' : 'nein'],
                    ['Hat Zahl', /\d/.test(desc) ? 'ja' : 'nein'],
                    ['Durchschnitt Wortlänge', num.format(desc.replace(/\s+/g, '').length / Math.max(1, words))],
                    ['Satzanzahl', desc.split(/[.!?]+/).filter(s => s.trim()).length],
                    ['Empfehlung', desc.length >= 90 && desc.length <= 160 ? 'passt' : 'anpassen'],
                ]) + `<div class="snippet"><div class="snippet-title">Beispiel SEO Titel</div><div class="snippet-url">https://kotsch.tech/</div><div class="snippet-desc">${escapeHtml(desc)}</div></div>`;
            }
            case 'open-graph-preview': {
                const image = value('image');
                return `<div class="og-card"><div class="og-image">${image ? `<img src="${escapeHtml(image)}" alt="" style="width:100%;height:100%;object-fit:cover">` : 'OG PREVIEW'}</div><div class="og-body"><div class="og-url">${escapeHtml(value('url'))}</div><div class="og-title">${escapeHtml(value('title'))}</div><div class="snippet-desc">${escapeHtml(value('description'))}</div></div></div>`;
            }
            case 'robots-txt-generator': {
                const site = (value('site') || 'https://example.com').replace(/\/$/, '');
                const dis = value('disallow').split(/\n+/).map(s => s.trim()).filter(Boolean).map(p => `Disallow: ${p}`).join('\n');
                const txt = `User-agent: *\nAllow: /\n${dis}\n\nSitemap: ${site}/sitemap.xml`;
                return `<div class="result copy-box">${escapeHtml(txt)}</div>`;
            }
            case 'lesbarkeitsrechner': {
                const text = value('text');
                const words = text.match(/[A-Za-zÄÖÜäöüß0-9]+/g) ?? [];
                const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
                const avg = words.length / Math.max(1, sentences.length);
                const score = avg <= 12 ? 'leicht' : avg <= 20 ? 'mittel' : 'schwer';
                const chars = text.length;
                const longWords = words.filter(w => w.length >= 12).length;
                return rows([
                    ['Woerter', words.length],
                    ['Saetze', sentences.length],
                    ['Durchschnittliche Satzlänge', `${num.format(avg)} Woerter`],
                    ['Einschätzung', score],
                    ['Zeichen', chars],
                    ['Zeichen ohne Leerzeichen', text.replace(/\s+/g, '').length],
                    ['Lange Woerter', longWords],
                    ['Lange-Woerter-Anteil', `${num.format(words.length ? longWords / words.length * 100 : 0)} %`],
                    ['Lesezeit grob', `${num.format(words.length / 220)} min`],
                    ['Sprechzeit grob', `${num.format(words.length / 140)} min`],
                    ['Empfehlung', avg > 20 ? 'kuerzere Saetze prüfen' : 'gut lesbar'],
                ]);
            }
            case 'zeichenlimit-pruefer': {
                const limits = { x: 280, instagram: 2200, linkedin: 3000, title: 60, description: 160 };
                const limit = limits[value('platform')] ?? 280;
                const length = value('text').length;
                const text = value('text');
                const words = text.trim().split(/\s+/).filter(Boolean).length;
                return rows([
                    ['Zeichen', `${length} / ${limit}`],
                    ['Rest', limit - length],
                    ['Status', length <= limit ? 'Passt' : 'Zu lang'],
                    ['Woerter', words],
                    ['Auslastung', `${num.format(limit ? length / limit * 100 : 0)} %`],
                    ['Über Limit', Math.max(0, length - limit)],
                    ['Kurzvorschau', escapeHtml(text.slice(0, 80))],
                    ['Hashtags', (text.match(/#[\wäöüß]+/gi) ?? []).length],
                    ['Mentions', (text.match(/@[\w.]+/g) ?? []).length],
                    ['Links', (text.match(/https?:\/\/\S+/g) ?? []).length],
                    ['Empfehlung', length <= limit ? 'veroeffentlichbar' : 'kuerzen'],
                ]);
            }
            case 'kosten-pro-person': {
                const total = n('total') * (1 + n('tip') / 100);
                const tip = n('total') * n('tip') / 100;
                return rows([
                    ['Gesamt inkl. Trinkgeld', euro.format(total)],
                    ['Pro Person', euro.format(total / Math.max(1, n('people')))],
                    ['Trinkgeld', euro.format(tip)],
                    ['Originalbetrag', euro.format(n('total'))],
                    ['Personen', n('people')],
                    ['Trinkgeld pro Person', euro.format(tip / Math.max(1, n('people')))],
                    ['Rechnung pro Person ohne Trinkgeld', euro.format(n('total') / Math.max(1, n('people')))],
                    ['Gesamt bei 5% Trinkgeld', euro.format(n('total') * 1.05)],
                    ['Gesamt bei 10% Trinkgeld', euro.format(n('total') * 1.10)],
                    ['Gesamt bei 15% Trinkgeld', euro.format(n('total') * 1.15)],
                    ['Aufgerundet pro Person', euro.format(Math.ceil(total / Math.max(1, n('people'))))],
                ]);
            }
            case 'reisekosten-rechner': {
                const liters = n('km') / 100 * n('consumption');
                const total = liters * n('fuel') + n('extra');
                return rows([
                    ['Benötigter Kraftstoff', `${num.format(liters)} l`],
                    ['Gesamtkosten', euro.format(total)],
                    ['Pro Person', euro.format(total / Math.max(1, n('people')))],
                    ['Spritkosten', euro.format(liters * n('fuel'))],
                    ['Zusatzkosten', euro.format(n('extra'))],
                    ['Kosten pro km', euro.format(total / Math.max(1, n('km')))],
                    ['Kosten pro 100 km', euro.format(total / Math.max(1, n('km')) * 100)],
                    ['Strecke hin/zurueck', `${num.format(n('km') * 2)} km`],
                    ['Kosten hin/zurueck', euro.format(total * 2)],
                    ['Liter pro Person', `${num.format(liters / Math.max(1, n('people')))} l`],
                    ['CO2 grob', `${num.format(liters * 2.31)} kg`],
                ]);
            }
            case 'packliste-generator': {
                const base = ['Ausweis/Reisepass', 'Geld/Karten', 'Handy', 'Ladegerät', 'Hygieneartikel', 'Unterwaesche', 'Socken'];
                const clothing = [`${Math.max(1, n('days'))} Oberteile`, `${Math.ceil(Math.max(1, n('days')) / 2)} Hosen/Roecke`, 'Schuhe'];
                const type = value('type');
                const extraByType = {
                    urlaub: ['Sonnencreme', 'Badekleidung', 'Reiseapotheke'],
                    business: ['Laptop', 'Notizbuch', 'Business-Outfit'],
                    camping: ['Zelt', 'Schlafsack', 'Taschenlampe'],
                    weekend: ['Kleiner Rucksack', 'Wechselkleidung']
                };
                const extras = value('extras').split(/\n+/).map(s => s.trim()).filter(Boolean);
                const list = [...base, ...clothing, ...(extraByType[type] || []), ...extras];
                return `<div class="result copy-box">${escapeHtml(list.map(item => `- ${item}`).join('\n'))}</div>`;
            }
            case 'kreditrechner': {
                const monthlyRate = n('rate') / 100 / 12;
                const months = Math.max(1, n('months'));
                const payment = monthlyRate ? n('amount') * monthlyRate / (1 - Math.pow(1 + monthlyRate, -months)) : n('amount') / months;
                const total = payment * months;
                return rows([
                    ['Monatsrate', euro.format(payment)],
                    ['Gesamtbetrag', euro.format(total)],
                    ['Zinskosten', euro.format(total - n('amount'))],
                    ['Kreditsumme', euro.format(n('amount'))],
                    ['Laufzeit Jahre', num.format(months / 12)],
                    ['Jahresrate', euro.format(payment * 12)],
                    ['Zinsanteil gesamt', `${num.format(total ? (total - n('amount')) / total * 100 : 0)} %`],
                    ['Rate bei 36 Monaten', euro.format(monthlyRate ? n('amount') * monthlyRate / (1 - Math.pow(1 + monthlyRate, -36)) : n('amount') / 36)],
                    ['Rate bei 60 Monaten', euro.format(monthlyRate ? n('amount') * monthlyRate / (1 - Math.pow(1 + monthlyRate, -60)) : n('amount') / 60)],
                    ['Rate bei 84 Monaten', euro.format(monthlyRate ? n('amount') * monthlyRate / (1 - Math.pow(1 + monthlyRate, -84)) : n('amount') / 84)],
                    ['Monatszins', `${num.format(monthlyRate * 100)} %`],
                ]);
            }
            case 'tilgungsrechner': {
                const annualRate = n('amount') * (n('interest') + n('repayment')) / 100;
                const interest = n('amount') * n('interest') / 100;
                const principal = annualRate - interest;
                return rows([
                    ['Jahresrate', euro.format(annualRate)],
                    ['Monatsrate', euro.format(annualRate / 12)],
                    ['Zinsanteil im ersten Jahr', euro.format(interest)],
                    ['Tilgungsanteil im ersten Jahr', euro.format(principal)],
                    ['Restschuld nach Jahr 1', euro.format(n('amount') - principal)],
                    ['Sollzins', `${num.format(n('interest'))} %`],
                    ['Tilgung', `${num.format(n('repayment'))} %`],
                    ['Annuitaet', `${num.format(n('interest') + n('repayment'))} %`],
                    ['Zins pro Monat anfangs', euro.format(interest / 12)],
                    ['Tilgung pro Monat anfangs', euro.format(principal / 12)],
                    ['Grobe Volltilgung Jahre', principal > 0 ? num.format(n('amount') / principal) : '-'],
                ]);
            }
            case 'rendite-rechner': {
                const profit = n('sell') - n('buy') - n('costs');
                const netSell = n('sell') - n('costs');
                return rows([
                    ['Gewinn', euro.format(profit)],
                    ['Rendite', `${num.format(n('buy') ? profit / n('buy') * 100 : 0)} %`],
                    ['Rueckfluss nach Kosten', euro.format(netSell)],
                    ['Einsatz', euro.format(n('buy'))],
                    ['Verkauf', euro.format(n('sell'))],
                    ['Kosten', euro.format(n('costs'))],
                    ['Kostenquote', `${num.format(n('sell') ? n('costs') / n('sell') * 100 : 0)} %`],
                    ['Bruttogewinn vor Kosten', euro.format(n('sell') - n('buy'))],
                    ['Bruttorendite vor Kosten', `${num.format(n('buy') ? (n('sell') - n('buy')) / n('buy') * 100 : 0)} %`],
                    ['Verdopplungsfaktor', num.format(n('buy') ? netSell / n('buy') : 0)],
                    ['Status', profit >= 0 ? 'Gewinn' : 'Verlust'],
                ]);
            }
            case 'marge-rechner': {
                const profit = n('price') - n('cost');
                const margin = n('price') ? profit / n('price') * 100 : 0;
                return rows([
                    ['Gewinn pro Stück', euro.format(profit)],
                    ['Marge (vom Verkaufspreis)', `${num.format(margin)} %`],
                    ['Aufschlag (auf Einkauf)', `${num.format(n('cost') ? profit / n('cost') * 100 : 0)} %`],
                    ['Verkaufspreis', euro.format(n('price'))],
                    ['Einkaufspreis', euro.format(n('cost'))],
                    ['Faktor (Preis/Kosten)', `${num.format(n('cost') ? n('price') / n('cost') : 0)} x`],
                    ['Gewinn bei 100 Stück', euro.format(profit * 100)],
                    ['Nötiger Preis für 50% Marge', euro.format(n('cost') * 2)],
                    ['Einordnung', margin >= 50 ? 'sehr hoch' : margin >= 30 ? 'gut' : margin >= 10 ? 'okay' : margin >= 0 ? 'niedrig' : 'Verlust'],
                ]);
            }
            case 'break-even-rechner': {
                const contribution = n('price') - n('variable');
                const units = contribution > 0 ? Math.ceil(n('fixed') / contribution) : 0;
                return rows([
                    ['Break-even-Menge', units ? `${num.format(units)} Einheiten` : 'Nicht erreichbar'],
                    ['Deckungsbeitrag pro Einheit', euro.format(contribution)],
                    ['Deckungsbeitragsquote', `${num.format(n('price') ? contribution / n('price') * 100 : 0)} %`],
                    ['Umsatz am Break-even', units ? euro.format(units * n('price')) : '-'],
                    ['Fixkosten', euro.format(n('fixed'))],
                    ['Variable Kosten/Einheit', euro.format(n('variable'))],
                    ['Break-even pro Monat', units ? `${num.format(units / 12)} Einheiten` : '-'],
                    ['Gewinn bei doppelter Menge', units ? euro.format(contribution * units * 2 - n('fixed')) : '-'],
                    ['Sicherheit', contribution > 0 ? 'erreichbar' : 'Preis unter variablen Kosten'],
                ]);
            }
            case 'freelancer-stundensatz-rechner': {
                const needed = n('target') + n('costs');
                const hours = n('days') * n('hours');
                const rate = needed / Math.max(1, hours);
                return rows([
                    ['Nötiger Stundensatz', euro.format(rate)],
                    ['Nötiger Tagessatz', euro.format(needed / Math.max(1, n('days')))],
                    ['Monatsumsatz nötig', euro.format(needed)],
                    ['Abrechenbare Stunden/Monat', `${num.format(hours)} h`],
                    ['Jahresumsatz nötig', euro.format(needed * 12)],
                    ['Stundensatz +20% Puffer', euro.format(rate * 1.2)],
                    ['Gewinnziel', euro.format(n('target'))],
                    ['Kosten', euro.format(n('costs'))],
                    ['Kostenanteil pro Stunde', euro.format(n('costs') / Math.max(1, hours))],
                ]);
            }
            case 'urlaubsanspruch-rechner': {
                const days = n('fullDays') / 5 * n('workDays') * Math.min(12, Math.max(0, n('months'))) / 12;
                const full = n('fullDays') / 5 * n('workDays');
                return rows([
                    ['Urlaubsanspruch (anteilig)', `${num.format(days)} Tage`],
                    ['Aufgerundet', `${Math.ceil(days)} Tage`],
                    ['Voller Jahresanspruch', `${num.format(full)} Tage`],
                    ['Pro Monat', `${num.format(full / 12)} Tage`],
                    ['Arbeitstage pro Woche', num.format(n('workDays'))],
                    ['Beschaeftigte Monate', num.format(Math.min(12, n('months')))],
                    ['Gesetzl. Minimum (24 bei 6-Tage)', `${num.format(24 / 6 * n('workDays'))} Tage`],
                    ['Resturlaub bei halbem Jahr', `${num.format(full / 2)} Tage`],
                ]);
            }
            case 'arbeitstage-rechner': {
                const start = new Date(value('start'));
                const end = new Date(value('end'));
                if (isNaN(start.getTime()) || isNaN(end.getTime())) return rows([['Fehler', 'Bitte gültige Daten waehlen']]);
                let count = 0;
                for (let d = new Date(start); d <= end; d.setDate(d.getDate() + 1)) {
                    const day = d.getDay();
                    if (day !== 0 && day !== 6) count++;
                }
                const cal = Math.max(0, daysBetween(start, end) + 1);
                return rows([
                    ['Arbeitstage (ohne Wochenende)', `${num.format(count)} Tage`],
                    ['Kalendertage', `${num.format(cal)} Tage`],
                    ['Wochenenden abgezogen', `${num.format(cal - count)} Tage`],
                    ['Arbeitswochen ca.', num.format(count / 5)],
                    ['Arbeitsstunden (8h)', `${num.format(count * 8)} h`],
                    ['Start', start.toLocaleDateString('de-DE')],
                    ['Ende', end.toLocaleDateString('de-DE')],
                    ['Anteil Arbeitstage', `${num.format(cal ? count / cal * 100 : 0)} %`],
                ]);
            }
            case 'datumsrechner': {
                const start = new Date(value('date'));
                if (isNaN(start.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const next = new Date(start);
                next.setDate(next.getDate() + Math.round(n('days')));
                const names = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
                return rows([
                    ['Ergebnisdatum', next.toLocaleDateString('de-DE')],
                    ['Wochentag', names[next.getDay()]],
                    ['Startdatum', start.toLocaleDateString('de-DE')],
                    ['Differenz', `${num.format(n('days'))} Tage`],
                    ['Differenz in Wochen', `${num.format(n('days') / 7)} Wochen`],
                    ['Differenz in Monaten', `${num.format(n('days') / 30.44)} Monate`],
                    ['Ergebnis ist Wochenende', (next.getDay() === 0 || next.getDay() === 6) ? 'ja' : 'nein'],
                ]);
            }
            case 'altersrechner': {
                const birth = new Date(value('birth'));
                if (isNaN(birth.getTime())) return rows([['Fehler', 'Bitte ein gültiges Geburtsdatum waehlen']]);
                const now = new Date();
                let years = now.getFullYear() - birth.getFullYear();
                const birthdayThisYear = new Date(now.getFullYear(), birth.getMonth(), birth.getDate());
                if (now < birthdayThisYear) years--;
                const nextBirthday = now <= birthdayThisYear ? birthdayThisYear : new Date(now.getFullYear() + 1, birth.getMonth(), birth.getDate());
                const days = Math.max(0, daysBetween(birth, now));
                return rows([
                    ['Alter', `${num.format(years)} Jahre`],
                    ['Nächster Geburtstag in', `${num.format(Math.max(0, daysBetween(now, nextBirthday)))} Tagen`],
                    ['Tage seit Geburt', `${num.format(days)} Tage`],
                    ['Wochen seit Geburt', `${num.format(days / 7)} Wochen`],
                    ['Monate seit Geburt', `${num.format(days / 30.44)} Monate`],
                    ['Stunden gelebt', `${num.format(days * 24)} h`],
                    ['Nächstes rundes Alter', `${num.format((Math.floor(years / 10) + 1) * 10)} Jahre`],
                    ['Geburtswochentag', ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'][birth.getDay()]],
                ]);
            }
            case 'bildgroesse-rechner': {
                const pixels = n('width') * n('height');
                const mb = pixels * n('bytes') / 1024 / 1024;
                const w = Math.max(1, Math.round(n('width')));
                const h = Math.max(1, Math.round(n('height')));
                const divisor = gcd(w, h);
                return rows([
                    ['Pixel gesamt', num.format(pixels)],
                    ['Megapixel', `${num.format(pixels / 1000000)} MP`],
                    ['Unkomprimiert ca.', `${num.format(mb)} MB`],
                    ['Komprimiert (JPEG ~10:1)', `${num.format(mb / 10)} MB`],
                    ['Seitenverhältnis', `${w / divisor}:${h / divisor}`],
                    ['Auflösung', `${num.format(w)} x ${num.format(h)} px`],
                    ['Bytes unkomprimiert', `${num.format(pixels * n('bytes'))} B`],
                    ['Speicher für 100 Bilder', `${num.format(mb * 100)} MB`],
                ]);
            }
            case 'aspect-ratio-rechner': {
                const w = Math.max(1, Math.round(n('width')));
                const h = Math.max(1, Math.round(n('height')));
                const divisor = gcd(w, h);
                const newHeight = n('newWidth') * h / w;
                return rows([
                    ['Seitenverhältnis', `${w / divisor}:${h / divisor}`],
                    ['Dezimal', num.format(w / h)],
                    ['Neue Höhe bei neuer Breite', `${num.format(newHeight)} px`],
                    ['Neue Breite (umgekehrt)', `${num.format(n('newWidth') * w / h)} px`],
                    ['Orientierung', w > h ? 'Querformat' : w < h ? 'Hochformat' : 'Quadrat'],
                    ['Gekuerzt um Faktor', num.format(divisor)],
                    ['Naehe zu 16:9', Math.abs(w / h - 16 / 9) < 0.05 ? 'sehr nah' : '-'],
                    ['Originalgröße', `${num.format(w)} x ${num.format(h)} px`],
                ]);
            }
            case 'dpi-rechner': {
                const dpi = Math.max(1, n('dpi'));
                const widthCm = n('width') / dpi * 2.54;
                const heightCm = n('height') / dpi * 2.54;
                return rows([
                    ['Druckgröße', `${num.format(widthCm)} x ${num.format(heightCm)} cm`],
                    ['Druckbreite', `${num.format(widthCm)} cm`],
                    ['Druckhöhe', `${num.format(heightCm)} cm`],
                    ['Druckbreite in Zoll', `${num.format(n('width') / dpi)} in`],
                    ['DPI', num.format(dpi)],
                    ['Empfohlen für Fotodruck (300 DPI)', `${num.format(n('width') / 300 * 2.54)} cm breit`],
                    ['Max. Druck bei 150 DPI', `${num.format(n('width') / 150 * 2.54)} cm breit`],
                    ['Megapixel', `${num.format(n('width') * n('height') / 1000000)} MP`],
                ]);
            }
            case 'json-validator': {
                try {
                    const parsed = JSON.parse(value('json'));
                    const formatted = JSON.stringify(parsed, null, 2);
                    const minified = JSON.stringify(parsed);
                    let keys = 0, maxDepth = 0;
                    const walk = (o, d) => { maxDepth = Math.max(maxDepth, d); if (o && typeof o === 'object') for (const k of Object.keys(o)) { if (!Array.isArray(o)) keys++; walk(o[k], d + 1); } };
                    walk(parsed, 0);
                    return rows([
                        ['Status', 'Gültiges JSON'],
                        ['Typ', Array.isArray(parsed) ? `Array (${parsed.length})` : typeof parsed],
                        ['Schlüssel gesamt', num.format(keys)],
                        ['Max. Verschachtelung', num.format(maxDepth)],
                        ['Zeichen formatiert', num.format(formatted.length)],
                        ['Zeichen minifiziert', num.format(minified.length)],
                        ['Ersparnis minifiziert', `${num.format(formatted.length ? (1 - minified.length / formatted.length) * 100 : 0)} %`],
                    ]) + `<div class="result copy-box">${escapeHtml(formatted)}</div>`;
                } catch (error) {
                    return rows([['Status', 'Ungültiges JSON'], ['Fehler', escapeHtml(error.message)]]);
                }
            }
            case 'base64-encoder-decoder': {
                try {
                    const input = value('text');
                    const normalized = input.replace(/\s+/g, '').replace(/-/g, '+').replace(/_/g, '/');
                    const padded = normalized + '='.repeat((4 - normalized.length % 4) % 4);
                    const output = value('mode') === 'encode' ? btoa(unescape(encodeURIComponent(input))) : decodeURIComponent(escape(atob(padded)));
                    return `<div class="result copy-box">${escapeHtml(output)}</div>`;
                } catch (error) {
                    return rows([['Fehler', 'Eingabe konnte nicht sicher umgewandelt werden'], ['Details', escapeHtml(error.message)]]);
                }
            }
            case 'url-encoder-decoder': {
                try {
                    const output = value('mode') === 'encode' ? encodeURIComponent(value('text')) : decodeURIComponent(value('text'));
                    return `<div class="result copy-box">${escapeHtml(output)}</div>`;
                } catch (error) {
                    return rows([['Fehler', 'Eingabe konnte nicht umgewandelt werden']]);
                }
            }
            case 'markdown-html-konverter': {
                const html = simpleMarkdown(value('markdown'));
                return `<div class="result copy-box">${escapeHtml(html)}</div><div class="snippet">${html}</div>`;
            }
            case 'regex-match-tester': {
                try {
                    if (!value('pattern')) return rows([['Hinweis', 'Bitte ein Regex-Pattern eingeben']]);
                    const regex = new RegExp(value('pattern'), 'g');
                    const all = Array.from(value('text').matchAll(regex));
                    const matches = all.map(m => m[0]);
                    const unique = [...new Set(matches)];
                    const groups = all.length ? (all[0].length - 1) : 0;
                    return rows([
                        ['Treffer gesamt', num.format(matches.length)],
                        ['Eindeutige Treffer', num.format(unique.length)],
                        ['Erfassungsgruppen', num.format(groups)],
                        ['Erster Treffer', matches[0] ? escapeHtml(matches[0]) : '-'],
                        ['Pattern', `<span class="mono">${escapeHtml(value('pattern'))}</span>`],
                        ['Textlänge', num.format(value('text').length)],
                    ]) + `<div class="result copy-box">${escapeHtml(matches.join('\n') || 'Keine Treffer')}</div>`;
                } catch (error) {
                    return rows([['Fehler', escapeHtml(error.message)]]);
                }
            }
            case 'farbkontrast-checker': {
                if (!/^#?[0-9a-fA-F]{6}$/.test(value('fg').trim()) || !/^#?[0-9a-fA-F]{6}$/.test(value('bg').trim())) return rows([['Fehler', 'Bitte zwei gültige Hex-Farben eingeben (z. B. #1d4ed8)']]);
                const l1 = luminance(value('fg'));
                const l2 = luminance(value('bg'));
                const ratio = (Math.max(l1, l2) + 0.05) / (Math.min(l1, l2) + 0.05);
                const preview = `<div class="result" style="background:${escapeHtml(value('bg'))};color:${escapeHtml(value('fg'))};font-weight:900">Beispieltext mit diesem Farbkontrast</div>`;
                return rows([
                    ['Kontrastverhältnis', `${num.format(ratio)}:1`],
                    ['WCAG AA normaler Text (4.5:1)', ratio >= 4.5 ? 'Bestanden' : 'Zu niedrig'],
                    ['WCAG AA grosser Text (3:1)', ratio >= 3 ? 'Bestanden' : 'Zu niedrig'],
                    ['WCAG AAA normaler Text (7:1)', ratio >= 7 ? 'Bestanden' : 'Zu niedrig'],
                    ['WCAG AAA grosser Text (4.5:1)', ratio >= 4.5 ? 'Bestanden' : 'Zu niedrig'],
                    ['UI-Komponenten (3:1)', ratio >= 3 ? 'Bestanden' : 'Zu niedrig'],
                    ['Bewertung', ratio >= 7 ? 'exzellent' : ratio >= 4.5 ? 'gut' : ratio >= 3 ? 'grenzwertig' : 'ungenuegend'],
                ]) + preview;
            }
            case 'css-clamp-generator': {
                const vpRange = n('maxViewport') - n('minViewport');
                const slope = vpRange ? (n('maxSize') - n('minSize')) / vpRange * 100 : 0;
                const intercept = n('minSize') - slope * n('minViewport') / 100;
                const css = `font-size: clamp(${n('minSize')}px, ${num.format(intercept)}px + ${num.format(slope)}vw, ${n('maxSize')}px);`;
                return rows([
                    ['CSS', `<span class="mono">${escapeHtml(css)}</span>`],
                    ['Min. Größe', `${num.format(n('minSize'))} px`],
                    ['Max. Größe', `${num.format(n('maxSize'))} px`],
                    ['Steigung', `${num.format(slope)} vw`],
                    ['Wachst zwischen', `${num.format(n('minViewport'))} - ${num.format(n('maxViewport'))} px Viewport`],
                    ['Größe bei 768px', `${num.format(clamp(intercept + slope * 768 / 100, n('minSize'), n('maxSize')))} px`],
                    ['Größe bei 1440px', `${num.format(clamp(intercept + slope * 1440 / 100, n('minSize'), n('maxSize')))} px`],
                ]) + `<div class="result copy-box">${escapeHtml(css)}</div>`;
            }
            case 'schwangerschaftsrechner': {
                const start = new Date(value('lastPeriod'));
                if (Number.isNaN(start.getTime())) return rows([['Fehler', 'Bitte gültiges Datum eingeben'], ['Hinweis', 'Keine medizinische Beratung']]);
                const cycle = clamp(n('cycle'), 20, 45);
                const due = new Date(start);
                due.setDate(due.getDate() + 280 + (cycle - 28));
                const weeks = Math.max(0, Math.floor(daysBetween(start, new Date()) / 7));
                const days = Math.max(0, daysBetween(start, new Date()) % 7);
                return rows([['Voraussichtlicher Geburtstermin', due.toLocaleDateString('de-DE')], ['Aktuelle SSW grob', `${weeks}+${days}`], ['Zyklus genutzt', `${cycle} Tage`], ['Tage bis Termin', `${num.format(Math.max(0, daysBetween(new Date(), due)))} Tage`], ['Hinweis', 'Nur Orientierung, keine medizinische Beratung oder Diagnose']]);
            }
            case 'promillerechner': {
                const factor = value('gender') === 'w' ? 0.6 : 0.7;
                const weight = clamp(n('weight'), 30, 250);
                const alcohol = clamp(n('alcohol'), 0, 500);
                const hours = clamp(n('hours'), 0, 48);
                const peak = alcohol / (weight * factor);
                const current = Math.max(0, peak - hours * 0.15);
                const soberIn = current / 0.15;
                return rows([
                    ['Promille aktuell (grob)', `${num.format(current)} Promille`],
                    ['Spitzenwert ohne Abbau', `${num.format(peak)} Promille`],
                    ['Nuechtern in ca.', `${num.format(soberIn)} h`],
                    ['Abbau pro Stunde', '~0,15 Promille'],
                    ['Bereits abgebaut', `${num.format(Math.min(peak, hours * 0.15))} Promille`],
                    ['Genutztes Gewicht', `${num.format(weight)} kg`],
                    ['Getrunkener Alkohol', `${num.format(alcohol)} g`],
                    ['Einordnung', current >= 1.1 ? 'absolut fahruntuechtig' : current >= 0.5 ? 'über 0,5-Grenze' : current > 0 ? 'Restalkohol' : 'nuechtern'],
                    ['Hinweis', 'Nie für Fahrtauglichkeit, Recht oder Medizin nutzen'],
                ]);
            }
            case 'makro-rechner': {
                const proteinCal = n('protein') * 4;
                const fatCal = n('fat') * 9;
                const carbCal = Math.max(0, n('calories') - proteinCal - fatCal);
                const cal = Math.max(1, n('calories'));
                return rows([
                    ['Protein', `${num.format(n('protein'))} g (${num.format(proteinCal)} kcal)`],
                    ['Fett', `${num.format(n('fat'))} g (${num.format(fatCal)} kcal)`],
                    ['Kohlenhydrate', `${num.format(carbCal / 4)} g (${num.format(carbCal)} kcal)`],
                    ['Protein-Anteil', `${num.format(proteinCal / cal * 100)} %`],
                    ['Fett-Anteil', `${num.format(fatCal / cal * 100)} %`],
                    ['Kohlenhydrat-Anteil', `${num.format(carbCal / cal * 100)} %`],
                    ['Kalorien gesamt', `${num.format(n('calories'))} kcal`],
                    ['Verteilung', `${num.format(proteinCal / cal * 100)}/${num.format(fatCal / cal * 100)}/${num.format(carbCal / cal * 100)} (P/F/K)`],
                ]);
            }
            case 'pace-rechner': {
                const totalSeconds = n('hours') * 3600 + n('minutes') * 60 + n('seconds');
                const paceSeconds = totalSeconds / Math.max(0.01, n('km'));
                const paceMin = Math.floor(paceSeconds / 60);
                const paceSec = Math.round(paceSeconds % 60).toString().padStart(2, '0');
                const speed = totalSeconds ? n('km') / (totalSeconds / 3600) : 0;
                const mmss = s => `${Math.floor(s / 60)}:${String(Math.round(s % 60)).padStart(2, '0')}`;
                return rows([
                    ['Pace', `${paceMin}:${paceSec} min/km`],
                    ['Geschwindigkeit', `${num.format(speed)} km/h`],
                    ['Gesamtzeit', `${Math.floor(totalSeconds / 3600)}:${Math.floor(totalSeconds % 3600 / 60).toString().padStart(2, '0')}:${Math.round(totalSeconds % 60).toString().padStart(2, '0')}`],
                    ['Pace pro Meile', `${mmss(paceSeconds * 1.60934)} min/mi`],
                    ['Hochrechnung 5 km', mmss(paceSeconds * 5)],
                    ['Hochrechnung 10 km', mmss(paceSeconds * 10)],
                    ['Hochrechnung Halbmarathon', mmss(paceSeconds * 21.0975)],
                    ['Hochrechnung Marathon', mmss(paceSeconds * 42.195)],
                    ['Strecke', `${num.format(n('km'))} km`],
                ]);
            }
            case 'kalenderwochen-rechner': {
                const date = new Date(`${value('date')}T12:00:00`);
                if (Number.isNaN(date.getTime())) return rows([['Fehler', 'Bitte gültiges Datum eingeben']]);
                const thursday = new Date(date);
                const day = thursday.getDay() || 7;
                thursday.setDate(thursday.getDate() + 4 - day);
                const isoYear = thursday.getFullYear();
                const jan4 = new Date(isoYear, 0, 4, 12);
                const jan4Day = jan4.getDay() || 7;
                const week1Thursday = new Date(jan4);
                week1Thursday.setDate(jan4.getDate() + 4 - jan4Day);
                const week = 1 + Math.round((thursday - week1Thursday) / 604800000);
                const dayOfYear = Math.floor((date - new Date(date.getFullYear(), 0, 0)) / 86400000);
                const names = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
                return rows([
                    ['Kalenderwoche', `KW ${num.format(week)}`],
                    ['ISO-Jahr', num.format(isoYear)],
                    ['Wochentag', names[date.getDay()]],
                    ['Tag im Jahr', num.format(dayOfYear)],
                    ['Quartal', `Q${Math.floor(date.getMonth() / 3) + 1}`],
                    ['Datum', date.toLocaleDateString('de-DE')],
                    ['Verbleibende Wochen im Jahr', num.format(Math.max(0, 52 - week))],
                    ['Norm', 'ISO 8601, Woche beginnt Montag'],
                ]);
            }
            case 'countdown-rechner': {
                const target = new Date(value('target'));
                if (isNaN(target.getTime())) return rows([['Fehler', 'Bitte ein gültiges Zieldatum waehlen']]);
                const diff = Math.max(0, target - new Date());
                const days = Math.floor(diff / 86400000);
                return rows([
                    ['Verbleibend', `${num.format(days)} Tage`],
                    ['Tage + Stunden', `${num.format(days)} T ${Math.floor(diff % 86400000 / 3600000)} h`],
                    ['Stunden gesamt', num.format(Math.floor(diff / 3600000))],
                    ['Minuten gesamt', num.format(Math.floor(diff / 60000))],
                    ['Sekunden gesamt', num.format(Math.floor(diff / 1000))],
                    ['Wochen', num.format(days / 7)],
                    ['Monate ca.', num.format(days / 30.44)],
                    ['Zieldatum', target.toLocaleDateString('de-DE')],
                    ['Status', diff === 0 ? 'erreicht oder vergangen' : 'laeuft'],
                ]);
            }
            case 'haushaltsbudget-rechner': {
                const spent = n('fixed') + n('variable');
                const rest = n('income') - spent;
                const inc = Math.max(1, n('income'));
                return rows([
                    ['Restbudget', euro.format(rest)],
                    ['Ausgaben gesamt', euro.format(spent)],
                    ['Sparquote', `${num.format(rest / inc * 100)} %`],
                    ['Fixkostenquote', `${num.format(n('fixed') / inc * 100)} %`],
                    ['Variable Quote', `${num.format(n('variable') / inc * 100)} %`],
                    ['Restbudget pro Tag', euro.format(rest / 30)],
                    ['Restbudget pro Jahr', euro.format(rest * 12)],
                    ['Empfehlung 50/30/20 spart', euro.format(n('income') * 0.2)],
                    ['Status', rest > 0 ? 'positiv' : rest < 0 ? 'Defizit' : 'ausgeglichen'],
                ]);
            }
            case 'nebenkosten-rechner': {
                const yearly = n('monthly') * 12;
                const area = Math.max(1, n('area'));
                return rows([
                    ['Jahreskosten', euro.format(yearly)],
                    ['Nebenkosten pro Monat', euro.format(n('monthly'))],
                    ['Pro m2 und Monat', euro.format(n('monthly') / area)],
                    ['Pro m2 und Jahr', euro.format(yearly / area)],
                    ['Wohnfläche', `${num.format(n('area'))} m2`],
                    ['Vergleich Schnitt (2,50/m2)', euro.format(area * 2.5)],
                    ['Abweichung vom Schnitt', `${num.format(area ? (n('monthly') / area / 2.5 - 1) * 100 : 0)} %`],
                    ['Einordnung', n('monthly') / area <= 2 ? 'guenstig' : n('monthly') / area <= 3 ? 'normal' : 'hoch'],
                ]);
            }
            case 'mietrendite-rechner': {
                const invest = n('price') + n('buyCosts');
                const annualRent = n('rent') * 12;
                const annualNet = annualRent - n('costs');
                return rows([
                    ['Bruttomietrendite', `${num.format(n('price') ? annualRent / n('price') * 100 : 0)} %`],
                    ['Nettomietrendite', `${num.format(invest ? annualNet / invest * 100 : 0)} %`],
                    ['Investition inkl. Nebenkosten', euro.format(invest)],
                    ['Kaufnebenkostenquote', `${num.format(n('price') ? n('buyCosts') / n('price') * 100 : 0)} %`],
                    ['Monatsmiete', euro.format(n('rent'))],
                    ['Jahreskaltmiete', euro.format(annualRent)],
                    ['Nicht umlagefaehige Kosten/Monat', euro.format(n('costs') / 12)],
                    ['Jahresnetto', euro.format(annualNet)],
                    ['Netto pro Monat', euro.format(annualNet / 12)],
                    ['Preis-Miete-Faktor', num.format(annualRent ? invest / annualRent : 0)],
                    ['Amortisation grob', annualNet > 0 ? `${num.format(invest / annualNet)} Jahre` : 'Nicht positiv'],
                ]);
            }
            case 'csv-tabellen-generator': {
                const delimiter = value('delimiter') || ',';
                const table = value('csv').split(/\n+/).filter(Boolean).map(line => line.split(delimiter));
                const html = `<table style="width:100%;border-collapse:collapse">${table.map((row, index) => `<tr>${row.map(cell => `<${index === 0 ? 'th' : 'td'} style="border:1px solid #e2e8f0;padding:8px;text-align:left">${escapeHtml(cell.trim())}</${index === 0 ? 'th' : 'td'}>`).join('')}</tr>`).join('')}</table>`;
                const cells = table.reduce((s, r) => s + r.length, 0);
                return rows([
                    ['Zeilen', num.format(table.length)],
                    ['Datenzeilen', num.format(Math.max(0, table.length - 1))],
                    ['Spalten', num.format(table[0]?.length ?? 0)],
                    ['Zellen gesamt', num.format(cells)],
                    ['Trennzeichen', delimiter === '\t' ? 'Tab' : delimiter],
                ]) + `<div class="result">${html}</div>`;
            }
            case 'sql-formatter': {
                const formatted = value('sql')
                    .replace(/\s+/g, ' ')
                    .replace(/\b(SELECT|FROM|WHERE|AND|OR|ORDER BY|GROUP BY|LIMIT|INNER JOIN|LEFT JOIN|RIGHT JOIN|JOIN|VALUES|SET)\b/gi, '\n$1')
                    .trim();
                return `<div class="result copy-box">${escapeHtml(formatted)}</div>`;
            }
            case 'html-minifier': {
                const input = value('html');
                const output = input.replace(/<!--[\s\S]*?-->/g, '').replace(/>\s+</g, '><').replace(/\s{2,}/g, ' ').trim();
                const saved = input.length - output.length;
                return rows([
                    ['Vorher', `${num.format(input.length)} Zeichen`],
                    ['Nachher', `${num.format(output.length)} Zeichen`],
                    ['Eingespart', `${num.format(saved)} Zeichen`],
                    ['Ersparnis', `${num.format(input.length ? saved / input.length * 100 : 0)} %`],
                    ['Zeilen vorher', num.format(input.split('\n').length)],
                    ['Größe ca.', `${num.format(output.length / 1024)} KB`],
                ]) + `<div class="result copy-box">${escapeHtml(output)}</div>`;
            }
            case 'css-minifier': {
                const input = value('css');
                const output = input.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\s+/g, ' ').replace(/\s*([{}:;,])\s*/g, '$1').replace(/;}/g, '}').trim();
                const saved = input.length - output.length;
                return rows([
                    ['Vorher', `${num.format(input.length)} Zeichen`],
                    ['Nachher', `${num.format(output.length)} Zeichen`],
                    ['Eingespart', `${num.format(saved)} Zeichen`],
                    ['Ersparnis', `${num.format(input.length ? saved / input.length * 100 : 0)} %`],
                    ['Regeln ca.', num.format((output.match(/}/g) || []).length)],
                    ['Größe ca.', `${num.format(output.length / 1024)} KB`],
                ]) + `<div class="result copy-box">${escapeHtml(output)}</div>`;
            }
            case 'javascript-minifier': {
                const input = value('js');
                const output = input.replace(/\/\*[\s\S]*?\*\//g, '').replace(/\/\/.*$/gm, '').replace(/\s+/g, ' ').replace(/\s*([{}();,+\-*/=<>])\s*/g, '$1').trim();
                const saved = input.length - output.length;
                return rows([
                    ['Vorher', `${num.format(input.length)} Zeichen`],
                    ['Nachher', `${num.format(output.length)} Zeichen`],
                    ['Eingespart', `${num.format(saved)} Zeichen`],
                    ['Ersparnis', `${num.format(input.length ? saved / input.length * 100 : 0)} %`],
                    ['Zeilen vorher', num.format(input.split('\n').length)],
                    ['Größe ca.', `${num.format(output.length / 1024)} KB`],
                ]) + `<div class="result copy-box">${escapeHtml(output)}</div>`;
            }
            case 'lorem-ipsum-generator-pro': {
                const source = ['Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 'Praesent vitae sapien non arcu facilisis luctus.', 'Integer posuere erat a ante venenatis dapibus.', 'Donec ullamcorper nulla non metus auctor fringilla.', 'Curabitur blandit tempus porttitor.'];
                const paragraphs = Array.from({ length: Math.max(1, Math.min(20, Math.round(n('paragraphs')))) }, (_, p) => {
                    return Array.from({ length: Math.max(1, Math.min(12, Math.round(n('sentences')))) }, (_, s) => source[(p + s) % source.length]).join(' ');
                });
                return `<div class="result copy-box">${escapeHtml(paragraphs.join('\n\n'))}</div>`;
            }
            case 'emoji-entferner': {
                const input = value('text');
                const emojis = (input.match(/[\p{Extended_Pictographic}]/gu) || []).length;
                const output = input.replace(/[\p{Extended_Pictographic}\uFE0F]/gu, '').replace(/\s{2,}/g, ' ').trim();
                return rows([
                    ['Vorher', `${num.format(input.length)} Zeichen`],
                    ['Nachher', `${num.format(output.length)} Zeichen`],
                    ['Emojis entfernt', num.format(emojis)],
                    ['Zeichen entfernt', num.format(input.length - output.length)],
                    ['Woerter', num.format(output.split(/\s+/).filter(Boolean).length)],
                ]) + `<div class="result copy-box">${escapeHtml(output)}</div>`;
            }
            case 'hashtag-generator': {
                const tags = value('keywords').split(/[,\n]+/).map(s => s.trim().toLowerCase().replace(/[^a-z0-9äöüß]+/gi, '')).filter(Boolean).map(s => '#' + s);
                const unique = [...new Set(tags)];
                return rows([
                    ['Hashtags', num.format(unique.length)],
                    ['Doppelte entfernt', num.format(tags.length - unique.length)],
                    ['Zeichen gesamt', num.format(unique.join(' ').length)],
                    ['Instagram (max 30)', unique.length <= 30 ? 'okay' : 'zu viele'],
                    ['Twitter/X (1-2 empfohlen)', unique.length <= 2 ? 'okay' : 'evtl. zu viele'],
                ]) + `<div class="result copy-box">${escapeHtml(unique.join(' '))}</div>`;
            }
            case 'youtube-titel-checker': {
                const title = value('title');
                const words = title.trim().split(/\s+/).filter(Boolean).length;
                const status = title.length <= 60 ? 'Sehr gut für Vorschau' : title.length <= 80 ? 'Ok, aber lang' : 'Zu lang';
                return rows([
                    ['Zeichen', num.format(title.length)],
                    ['Woerter', num.format(words)],
                    ['Einschätzung', status],
                    ['Sichtbar in Suche (~60)', title.length <= 60 ? 'vollstaendig' : 'wird abgeschnitten'],
                    ['Mobil sichtbar (~40)', title.length <= 40 ? 'vollstaendig' : 'gekuerzt'],
                    ['Zahlen enthalten', /\d/.test(title) ? 'ja (gut für CTR)' : 'nein'],
                    ['Rest bis 60 Zeichen', num.format(Math.max(0, 60 - title.length))],
                ]);
            }
            case 'domain-ideen-generator': {
                const keyword = value('keyword').toLowerCase().replace(/[^a-z0-9]/g, '') || 'projekt';
                const prefixes = value('style') === 'tech' ? ['get', 'try', 'cloud', 'stack'] : value('style') === 'short' ? ['go', 'up', 'io', 'my'] : ['hello', 'smart', 'bright', 'next'];
                const suffixes = value('style') === 'tech' ? ['hub', 'lab', 'base', 'kit'] : value('style') === 'short' ? ['ly', 'io', 'app', 'x'] : ['studio', 'pilot', 'works', 'flow'];
                const ideas = [...prefixes.map(p => `${p}${keyword}.de`), ...suffixes.map(s => `${keyword}${s}.de`)];
                return `<div class="result copy-box">${escapeHtml(ideas.join('\n'))}</div>`;
            }
            case 'wlan-passwort-generator': {
                const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#%+-_';
                const length = Math.max(12, Math.min(80, Math.round(n('length'))));
                const bytes = new Uint32Array(length);
                crypto.getRandomValues(bytes);
                const password = Array.from(bytes, b => chars[b % chars.length]).join('');
                const entropy = length * Math.log2(chars.length);
                return rows([
                    ['Länge', num.format(length)],
                    ['Zeichenraum', `${chars.length} Zeichen`],
                    ['Entropie', `${num.format(entropy)} Bit`],
                    ['Stärke', entropy >= 128 ? 'sehr stark' : entropy >= 80 ? 'stark' : 'okay'],
                    ['Ohne Verwechslungszeichen', 'ja (kein 0/O/1/l)'],
                    ['Hinweis', 'Lokal im Browser erzeugt, bei jeder Eingabe neu'],
                ]) + `<div class="result copy-box">${escapeHtml(password)}</div>`;
            }
            case 'eier-kochzeit-rechner': {
                const base = { weich: 4.5, wachsweich: 6.5, hart: 9.5 }[value('style')] ?? 6.5;
                const sizeAdd = { s: -0.5, m: 0, l: 0.8 }[value('size')] ?? 0;
                const fridgeAdd = value('fridge') === 'fridge' ? 0.8 : 0;
                const minutes = base + sizeAdd + fridgeAdd;
                return rows([
                    ['Kochzeit grob', `${num.format(minutes)} Minuten`],
                    ['Sekunden', `${num.format(minutes * 60)} s`],
                    ['Gargrad', value('style')],
                    ['Größen-Zuschlag', `${num.format(sizeAdd)} min`],
                    ['Kühlschrank-Zuschlag', `${num.format(fridgeAdd)} min`],
                    ['Sehr weich Richtwert', `${num.format(4 + sizeAdd + fridgeAdd)} min`],
                    ['Wachsweich Richtwert', `${num.format(6.5 + sizeAdd + fridgeAdd)} min`],
                    ['Hart Richtwert', `${num.format(9.5 + sizeAdd + fridgeAdd)} min`],
                    ['Timer-Vorschlag', `${Math.floor(minutes)}:${Math.round((minutes % 1) * 60).toString().padStart(2, '0')} min`],
                    ['Abschrecken', value('style') === 'hart' ? 'Optional' : 'Empfohlen'],
                    ['Hinweis', 'Ab sprudelnd kochendem Wasser gerechnet'],
                ]);
            }
            case 'kaffee-wasser-rechner': {
                const coffee = n('water') / Math.max(1, n('ratio'));
                return rows([
                    ['Kaffee', `${num.format(coffee)} g`],
                    ['Wasser', `${num.format(n('water'))} ml`],
                    ['Ratio', `1:${num.format(n('ratio'))}`],
                    ['Tassen ca. 125 ml', num.format(n('water') / 125)],
                    ['Becher ca. 250 ml', num.format(n('water') / 250)],
                    ['Kaffee pro 100 ml', `${num.format(coffee / Math.max(1, n('water')) * 100)} g`],
                    ['Milde Ratio 1:18', `${num.format(n('water') / 18)} g Kaffee`],
                    ['Standard Ratio 1:16', `${num.format(n('water') / 16)} g Kaffee`],
                    ['Starke Ratio 1:14', `${num.format(n('water') / 14)} g Kaffee`],
                    ['Bloom Wasser 2x Kaffee', `${num.format(coffee * 2)} ml`],
                    ['Restwasser nach Bloom', `${num.format(Math.max(0, n('water') - coffee * 2))} ml`],
                ]);
            }
            case 'pizzateig-rechner': {
                const total = n('balls') * n('weight');
                const flour = total / (1 + n('hydration') / 100 + 0.025 + 0.005);
                const water = flour * n('hydration') / 100;
                const salt = flour * 0.025;
                const yeast = flour * 0.005;
                return rows([
                    ['Teig gesamt', `${num.format(total)} g`],
                    ['Mehl', `${num.format(flour)} g`],
                    ['Wasser', `${num.format(water)} g`],
                    ['Salz', `${num.format(salt)} g`],
                    ['Hefe grob', `${num.format(yeast)} g`],
                    ['Hydration', `${num.format(n('hydration'))} %`],
                    ['Teigballen', `${num.format(n('balls'))} Stück`],
                    ['Gewicht pro Ballen', `${num.format(n('weight'))} g`],
                    ['Mehl pro Pizza', `${num.format(flour / Math.max(1, n('balls')))} g`],
                    ['Wasser pro Pizza', `${num.format(water / Math.max(1, n('balls')))} g`],
                    ['Salz pro Pizza', `${num.format(salt / Math.max(1, n('balls')))} g`],
                ]);
            }
            case 'backform-umrechner': {
                const factor = Math.pow(n('newDiameter') / Math.max(1, n('oldDiameter')), 2);
                const oldArea = Math.PI * Math.pow(n('oldDiameter') / 2, 2);
                const newArea = Math.PI * Math.pow(n('newDiameter') / 2, 2);
                return rows([
                    ['Umrechnungsfaktor', `${num.format(factor)} x`],
                    ['Alte Fläche', `${num.format(oldArea)} cm2`],
                    ['Neue Fläche', `${num.format(newArea)} cm2`],
                    ['100 g werden', `${num.format(100 * factor)} g`],
                    ['250 g werden', `${num.format(250 * factor)} g`],
                    ['3 Eier werden', `${num.format(3 * factor)} Eier`],
                    ['Backzeit-Hinweis', factor > 1 ? 'evtl. etwas länger' : 'evtl. etwas kuerzer'],
                    ['Alte / neue Form', `${num.format(n('oldDiameter'))} cm / ${num.format(n('newDiameter'))} cm`],
                ]);
            }
            case 'grillfleisch-rechner': {
                const grams = { leicht: 220, normal: 300, gross: 400 }[value('hunger')] ?? 300;
                const meatPeople = Math.max(0, n('people') - n('veggie'));
                const meatKg = meatPeople * grams / 1000;
                return rows([
                    ['Fleisch gesamt', `${num.format(meatKg)} kg`],
                    ['Fleisch pro Esser', `${num.format(grams)} g`],
                    ['Vegetarische Menge', `${num.format(n('veggie') * 250)} g`],
                    ['Beilagen grob', `${num.format(n('people') * 250 / 1000)} kg`],
                    ['Brot/Brötchen', `${num.format(Math.ceil(n('people') * 1.5))} Stück`],
                    ['Salate', `${num.format(n('people') * 150 / 1000)} kg`],
                    ['Getränke', `${num.format(n('people') * 1.5)} Liter`],
                    ['Grillkohle grob', `${num.format(Math.ceil(n('people') / 4))} kg`],
                ]);
            }
            case 'einkaufsliste-generator': {
                const meals = value('meals').split(/\n+/).map(s => s.trim()).filter(Boolean);
                const extras = value('extras').split(/\n+/).map(s => s.trim()).filter(Boolean);
                const basics = ['Salz/Pfeffer prüfen', 'Oel/Butter prüfen'];
                return `<div class="result copy-box">${escapeHtml([...meals.map(m => `- Zutaten für ${m}`), ...extras.map(e => `- ${e}`), ...basics.map(b => `- ${b}`)].join('\n'))}</div>`;
            }
            case 'verbrauch-pro-100km-rechner': {
                const consumption = n('liters') / Math.max(1, n('km')) * 100;
                return rows([
                    ['Verbrauch', `${num.format(consumption)} l/100 km`],
                    ['Strecke', `${num.format(n('km'))} km`],
                    ['Tankmenge', `${num.format(n('liters'))} l`],
                    ['Reichweite je Liter', `${num.format(consumption ? 100 / consumption : 0)} km`],
                    ['Kosten/100km (1,75 EUR/l)', euro.format(consumption * 1.75)],
                    ['Kosten für Strecke', euro.format(n('liters') * 1.75)],
                    ['Hochrechnung 15.000 km/Jahr', `${num.format(consumption * 150)} l`],
                    ['CO2 grob (2,37 kg/l)', `${num.format(n('liters') * 2.37)} kg`],
                ]);
            }
            case 'hundejahre-rechner': {
                const age = Math.max(0, n('age'));
                const multiplier = { small: 4.5, medium: 5.5, large: 6.5 }[value('size')] ?? 5.5;
                const human = age <= 1 ? 15 : age <= 2 ? 24 : 24 + (age - 2) * multiplier;
                return rows([
                    ['Menschenjahre (grob)', `${num.format(human)} Jahre`],
                    ['Hundealter', `${num.format(age)} Jahre`],
                    ['Größe', value('size')],
                    ['Lebensphase', age < 1 ? 'Welpe' : age < 7 ? 'erwachsen' : age < 10 ? 'reif' : 'Senior'],
                    ['In Menschenjahren pro Hundejahr', `~${num.format(multiplier)}`],
                    ['Nächstes Jahr entspricht', `${num.format(age >= 2 ? human + multiplier : 24)} Menschenjahren`],
                    ['Hinweis', 'Grobe Faustregel, rassenabhaengig'],
                ]);
            }
            case 'katzenjahre-rechner': {
                const age = Math.max(0, n('age'));
                const human = age <= 1 ? 15 : age <= 2 ? 24 : 24 + (age - 2) * 4;
                return rows([
                    ['Menschenjahre (grob)', `${num.format(human)} Jahre`],
                    ['Katzenalter', `${num.format(age)} Jahre`],
                    ['Lebensphase', age < 1 ? 'Kitten' : age < 7 ? 'erwachsen' : age < 11 ? 'reif' : 'Senior'],
                    ['Pro Katzenjahr (ab 2)', '~4 Menschenjahre'],
                    ['Nächstes Jahr entspricht', `${num.format(age >= 2 ? human + 4 : 24)} Menschenjahren`],
                    ['Hinweis', 'Grobe Faustregel'],
                ]);
            }
            case 'blutdruck-einordnung': {
                const sys = clamp(n('sys'), 60, 260);
                const dia = clamp(n('dia'), 30, 160);
                const status = sys >= 180 || dia >= 120 ? 'kritisch hoch' : sys >= 160 || dia >= 100 ? 'stark erhoeht' : sys >= 140 || dia >= 90 ? 'erhoeht' : sys < 100 || dia < 60 ? 'niedrig' : 'normal bis hochnormal';
                const warning = sys >= 180 || dia >= 120 ? 'Sehr hohe Werte: medizinisch zeitnah/sofort abklaeren, bei Beschwerden Notruf' : 'Keine Diagnose, bei Beschwerden medizinisch klaeren';
                return rows([
                    ['Einordnung', status],
                    ['Systolisch', `${num.format(sys)} mmHg`],
                    ['Diastolisch', `${num.format(dia)} mmHg`],
                    ['Pulsdruck', `${num.format(sys - dia)} mmHg`],
                    ['Mittlerer Druck (MAP)', `${num.format(dia + (sys - dia) / 3)} mmHg`],
                    ['Optimal', 'unter 120/80'],
                    ['Hypertonie ab', '140/90'],
                    ['Hinweis', warning],
                ]);
            }
            case 'schuhgroessen-umrechner': {
                const eu = n('eu');
                const cm = (eu / 1.5) * 2 / 3 + 1.5;
                return rows([
                    ['EU', num.format(eu)],
                    ['UK (grob)', num.format(eu - 33)],
                    ['US Herren (grob)', num.format(eu - 32)],
                    ['US Damen (grob)', num.format(eu - 30.5)],
                    ['Fusslänge ca.', `${num.format((eu - 2) / 1.5)} cm`],
                    ['JP/CN (cm, grob)', num.format((eu - 2) / 1.5)],
                    ['Hinweis', 'Marken weichen ab, immer anprobieren'],
                ]);
            }
            case 'ringgroessen-umrechner': {
                const circumference = n('diameter') * Math.PI;
                return rows([
                    ['Innenumfang', `${num.format(circumference)} mm`],
                    ['Innendurchmesser', `${num.format(n('diameter'))} mm`],
                    ['EU/DE Ringgröße (grob)', num.format(Math.round(circumference))],
                    ['US (grob)', num.format((circumference - 36.5) / 2.55)],
                    ['UK (grob)', String.fromCharCode(65 + Math.max(0, Math.round((circumference - 37.8) / 1.25)))],
                    ['Hinweis', 'Abends messen, Finger sind dann größer'],
                ]);
            }
            case 'pixel-rem-rechner': {
                const root = Math.max(1, n('root'));
                const rem = n('px') / root;
                return rows([
                    ['rem', `${num.format(rem)} rem`],
                    ['px', `${num.format(n('px'))} px`],
                    ['Root-Größe', `${num.format(root)} px`],
                    ['em (bei gleicher Basis)', `${num.format(rem)} em`],
                    ['Prozent', `${num.format(rem * 100)} %`],
                    ['pt (ca.)', `${num.format(n('px') * 0.75)} pt`],
                    ['0,5 rem entspricht', `${num.format(root * 0.5)} px`],
                ]);
            }
            case 'meta-robots-generator': {
                const tag = `<meta name="robots" content="${value('indexing')}, ${value('following')}">`;
                return `<div class="result copy-box">${escapeHtml(tag)}</div>`;
            }
            case 'htaccess-redirect-generator': {
                const from = value('from').startsWith('/') ? value('from') : '/' + value('from');
                const rule = `Redirect 301 ${from} ${value('to')}`;
                return `<div class="result copy-box">${escapeHtml(rule)}</div>`;
            }
            case 'cron-generator': {
                const presets = {
                    hourly: '0 * * * *',
                    daily: '0 3 * * *',
                    weekly: '0 3 * * 1',
                    monthly: '0 3 1 * *'
                };
                const cron = presets[value('preset')] ?? presets.daily;
                const human = { hourly: 'jede Stunde zur Minute 0', daily: 'taeglich um 03:00 Uhr', weekly: 'montags um 03:00 Uhr', monthly: 'am 1. des Monats um 03:00 Uhr' }[value('preset')] ?? '';
                const perYear = { hourly: 8760, daily: 365, weekly: 52, monthly: 12 }[value('preset')] ?? 0;
                return rows([
                    ['Cron-Ausdruck', `<span class="mono">${cron}</span>`],
                    ['Bedeutung', human],
                    ['Intervall', value('preset')],
                    ['Ausfuehrungen pro Jahr', num.format(perYear)],
                    ['Felder', 'Minute Stunde Tag Monat Wochentag'],
                ]) + `<div class="result copy-box">${escapeHtml(cron)}</div>`;
            }
            case 'sitemap-url-liste-generator': {
                const urls = value('urls').split(/\n+/).map(s => s.trim()).filter(Boolean);
                const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n${urls.map(url => `  <url><loc>${escapeHtml(url)}</loc></url>`).join('\n')}\n</urlset>`;
                const valid = urls.filter(u => /^https?:\/\//i.test(u)).length;
                return rows([
                    ['URLs gesamt', num.format(urls.length)],
                    ['Mit http(s)', num.format(valid)],
                    ['Ohne Protokoll', num.format(urls.length - valid)],
                    ['Eindeutige URLs', num.format(new Set(urls).size)],
                    ['Größe XML', `${num.format(xml.length / 1024)} KB`],
                    ['Sitemap-Limit', urls.length <= 50000 ? 'okay (max 50.000)' : 'aufteilen nötig'],
                ]) + `<div class="result copy-box">${escapeHtml(xml)}</div>`;
            }
            case 'http-status-code-spicker': {
                const info = {
                    200: 'OK: Anfrage erfolgreich.',
                    301: 'Moved Permanently: dauerhaft weitergeleitet.',
                    302: 'Found: temporaer weitergeleitet.',
                    400: 'Bad Request: Anfrage fehlerhaft.',
                    401: 'Unauthorized: Anmeldung nötig.',
                    403: 'Forbidden: Zugriff verboten.',
                    404: 'Not Found: Ressource nicht gefunden.',
                    500: 'Internal Server Error: Serverfehler.',
                    503: 'Service Unavailable: Dienst nicht verfuegbar.'
                };
                const code = Math.round(n('code'));
                const klass = code >= 500 ? '5xx Serverfehler' : code >= 400 ? '4xx Clientfehler' : code >= 300 ? '3xx Weiterleitung' : code >= 200 ? '2xx Erfolg' : code >= 100 ? '1xx Information' : 'unbekannt';
                return rows([
                    ['Code', num.format(code)],
                    ['Bedeutung', info[code] ?? 'Unbekannter oder seltener Statuscode.'],
                    ['Kategorie', klass],
                    ['Erfolgreich', code >= 200 && code < 300 ? 'ja' : 'nein'],
                    ['Fehler', code >= 400 ? 'ja' : 'nein'],
                    ['Cachebar (Standard)', [200, 203, 300, 301, 410].includes(code) ? 'oft ja' : 'meist nein'],
                ]);
            }
            case 'dns-record-spicker': {
                const info = {
                    A: 'IPv4-Adresse für eine Domain.',
                    AAAA: 'IPv6-Adresse für eine Domain.',
                    CNAME: 'Alias auf einen anderen Hostnamen.',
                    MX: 'Mailserver für E-Mail-Empfang.',
                    TXT: 'Textdaten, oft für SPF, DKIM, Verifikation.'
                };
                const ttl = { A: '300-3600 s', AAAA: '300-3600 s', CNAME: '3600 s', MX: '3600 s', TXT: '3600 s' };
                const example = { A: '93.184.216.34', AAAA: '2606:2800:220:1:248:1893:25c8:1946', CNAME: 'ziel.example.com.', MX: '10 mail.example.com.', TXT: 'v=spf1 include:_spf.example.com ~all' };
                const rec = value('record');
                return rows([
                    ['Record-Typ', rec],
                    ['Bedeutung', info[rec] ?? '-'],
                    ['Beispielwert', escapeHtml(example[rec] ?? '-')],
                    ['Typische TTL', ttl[rec] ?? '-'],
                    ['Mehrere möglich', rec === 'MX' || rec === 'TXT' || rec === 'A' ? 'ja' : 'nein'],
                    ['Hinweis', rec === 'CNAME' ? 'CNAME nicht auf Root-Domain mischen' : 'Aenderungen brauchen TTL-Zeit'],
                ]);
            }
            case 'preis-pro-quadratmeter-rechner': {
                const sqm = n('price') / Math.max(1, n('area'));
                return rows([
                    ['Preis pro m2', euro.format(sqm)],
                    ['Preis', euro.format(n('price'))],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Preis pro 10 m2', euro.format(sqm * 10)],
                    ['Preis pro 100 m2', euro.format(sqm * 100)],
                    ['Fläche pro 100.000 Euro', `${num.format(100000 / Math.max(1, sqm))} m2`],
                    ['Preis bei 50 m2', euro.format(sqm * 50)],
                    ['Preis bei 75 m2', euro.format(sqm * 75)],
                    ['Preis bei 100 m2', euro.format(sqm * 100)],
                    ['Preis bei 120 m2', euro.format(sqm * 120)],
                    ['Vergleichswert', sqm < 2500 ? 'niedrig' : sqm < 5000 ? 'mittel' : 'hoch'],
                ]);
            }
            case 'paypal-gebuehren-rechner': {
                const fee = n('amount') * n('percent') / 100 + n('fixed');
                const net = n('amount') - fee;
                const needed = (n('amount') + n('fixed')) / Math.max(0.01, 1 - n('percent') / 100);
                return rows([
                    ['Gebühr', euro.format(fee)],
                    ['Nettoauszahlung', euro.format(net)],
                    ['Brutto für Zielbetrag', euro.format(needed)],
                    ['Prozentanteil', euro.format(n('amount') * n('percent') / 100)],
                    ['Fixanteil', euro.format(n('fixed'))],
                    ['Effektive Gebühr', `${num.format(n('amount') ? fee / n('amount') * 100 : 0)} %`],
                    ['Nettoquote', `${num.format(n('amount') ? net / n('amount') * 100 : 0)} %`],
                    ['Gebühr bei 10 Zahlungen', euro.format(fee * 10)],
                    ['Gebühr bei 100 Zahlungen', euro.format(fee * 100)],
                    ['Monatsvolumen 100 Zahlungen', euro.format(n('amount') * 100)],
                    ['Netto bei 100 Zahlungen', euro.format(net * 100)],
                ]);
            }
            case 'ebay-gewinn-rechner': {
                const fee = n('sale') * n('fee') / 100;
                const profit = n('sale') - n('cost') - fee - n('shipping');
                return rows([
                    ['Gebühren', euro.format(fee)],
                    ['Gewinn', euro.format(profit)],
                    ['Marge', `${num.format(n('sale') ? profit / n('sale') * 100 : 0)} %`],
                    ['Auszahlung nach Gebühr', euro.format(n('sale') - fee)],
                    ['Kosten gesamt', euro.format(n('cost') + fee + n('shipping'))],
                    ['ROI auf Einkauf', `${num.format(n('cost') ? profit / n('cost') * 100 : 0)} %`],
                    ['Break-even Verkaufspreis grob', euro.format((n('cost') + n('shipping')) / Math.max(0.01, 1 - n('fee') / 100))],
                    ['Gewinn bei 10 Verkäufen', euro.format(profit * 10)],
                    ['Gebühr bei 10 Verkäufen', euro.format(fee * 10)],
                    ['Versandanteil', `${num.format(n('sale') ? n('shipping') / n('sale') * 100 : 0)} %`],
                    ['Einschätzung', profit > 0 ? 'profitabel' : 'nicht profitabel'],
                ]);
            }
            case 'kreditkarten-zinsen-rechner': {
                const monthly = n('apr') / 100 / 12;
                const interest = n('balance') * (Math.pow(1 + monthly, n('months')) - 1);
                return rows([
                    ['Zinskosten grob', euro.format(interest)],
                    ['Saldo plus Zinsen', euro.format(n('balance') + interest)],
                    ['Monatszins', `${num.format(monthly * 100)} %`],
                    ['Jahreszins', `${num.format(n('apr'))} %`],
                    ['Kosten pro Monat grob', euro.format(interest / Math.max(1, n('months')))],
                    ['Kosten nach 1 Monat', euro.format(n('balance') * monthly)],
                    ['Kosten nach 3 Monaten', euro.format(n('balance') * (Math.pow(1 + monthly, 3) - 1))],
                    ['Kosten nach 6 Monaten', euro.format(n('balance') * (Math.pow(1 + monthly, 6) - 1))],
                    ['Kosten nach 12 Monaten', euro.format(n('balance') * (Math.pow(1 + monthly, 12) - 1))],
                    ['Zinsanteil am Saldo', `${num.format(n('balance') ? interest / n('balance') * 100 : 0)} %`],
                    ['Hinweis', 'Ohne Tilgung modelliert'],
                ]);
            }
            case 'amortisationsrechner': {
                const months = n('saving') > 0 ? Math.ceil(n('investment') / n('saving')) : 0;
                return rows([
                    ['Amortisationsdauer', months ? `${months} Monate` : 'Keine Ersparnis'],
                    ['In Jahren', months ? num.format(months / 12) : '-'],
                    ['Investition', euro.format(n('investment'))],
                    ['Monatliche Ersparnis', euro.format(n('saving'))],
                    ['Jaehrliche Ersparnis', euro.format(n('saving') * 12)],
                    ['Ersparnis nach 1 Jahr', euro.format(n('saving') * 12)],
                    ['Ersparnis nach 3 Jahren', euro.format(n('saving') * 36)],
                    ['Ersparnis nach 5 Jahren', euro.format(n('saving') * 60)],
                    ['Überschuss nach 5 Jahren', euro.format(n('saving') * 60 - n('investment'))],
                    ['ROI nach 5 Jahren', `${num.format(n('investment') ? (n('saving') * 60 - n('investment')) / n('investment') * 100 : 0)} %`],
                    ['Status', months && months <= 60 ? 'unter 5 Jahren' : 'langfristig'],
                ]);
            }
            case 'reifendruck-umrechner': {
                const bar = n('bar');
                return rows([
                    ['bar', num.format(bar)],
                    ['psi', num.format(bar * 14.5038)],
                    ['kPa', num.format(bar * 100)],
                    ['MPa', num.format(bar * 0.1)],
                    ['atm', num.format(bar * 0.986923)],
                    ['kg/cm2', num.format(bar * 1.01972)],
                    ['bar + 0,2', num.format(bar + 0.2)],
                    ['bar - 0,2', num.format(Math.max(0, bar - 0.2))],
                    ['psi gerundet', Math.round(bar * 14.5038)],
                    ['kPa gerundet', Math.round(bar * 100)],
                    ['Hinweis', 'Herstellerangabe am Fahrzeug beachten'],
                ]);
            }
            case 'drehmoment-umrechner': {
                const nm = n('nm');
                return rows([
                    ['Nm', num.format(nm)],
                    ['ft-lb', num.format(nm * 0.737562)],
                    ['in-lb', num.format(nm * 8.85075)],
                    ['kgf-m', num.format(nm * 0.101972)],
                    ['kgf-cm', num.format(nm * 10.1972)],
                    ['N-cm', num.format(nm * 100)],
                    ['Nm + 10%', num.format(nm * 1.1)],
                    ['Nm - 10%', num.format(nm * 0.9)],
                    ['ft-lb gerundet', Math.round(nm * 0.737562)],
                    ['in-lb gerundet', Math.round(nm * 8.85075)],
                    ['Hinweis', 'Drehmomentspezifikation prüfen'],
                ]);
            }
            case 'geschwindigkeit-umrechner': {
                const kmh = n('kmh');
                return rows([
                    ['km/h', num.format(kmh)],
                    ['mph', num.format(kmh * 0.621371)],
                    ['m/s', num.format(kmh / 3.6)],
                    ['Knoten', num.format(kmh * 0.539957)],
                    ['ft/s', num.format(kmh * 0.911344)],
                    ['min pro km', kmh > 0 ? `${num.format(60 / kmh)} min/km` : '-'],
                    ['Zeit für 1 km', kmh > 0 ? `${num.format(3600 / kmh)} s` : '-'],
                    ['Zeit für 10 km', kmh > 0 ? `${num.format(10 / kmh * 60)} min` : '-'],
                    ['Strecke in 1 h', `${num.format(kmh)} km`],
                    ['Strecke in 30 min', `${num.format(kmh / 2)} km`],
                    ['Strecke in 10 min', `${num.format(kmh / 6)} km`],
                ]);
            }
            case 'windchill-rechner': {
                const t = n('temp');
                const v = Math.max(4.8, n('wind'));
                const wc = 13.12 + 0.6215 * t - 11.37 * Math.pow(v, 0.16) + 0.3965 * t * Math.pow(v, 0.16);
                return rows([
                    ['Windchill', `${num.format(wc)} °C`],
                    ['Temperatur', `${num.format(t)} °C`],
                    ['Wind', `${num.format(n('wind'))} km/h`],
                    ['Wind in m/s', `${num.format(n('wind') / 3.6)} m/s`],
                    ['Abkühlung', `${num.format(t - wc)} °C`],
                    ['Windchill Fahrenheit', `${num.format(wc * 9 / 5 + 32)} °F`],
                    ['Temperatur Fahrenheit', `${num.format(t * 9 / 5 + 32)} °F`],
                    ['Risiko grob', wc < -15 ? 'hoch' : wc < 0 ? 'kalt' : 'gering'],
                    ['Wind-Kategorie', n('wind') < 20 ? 'maessig' : n('wind') < 50 ? 'windig' : 'stark'],
                    ['Formel aktiv ab', 'ca. 4,8 km/h Wind'],
                    ['Hinweis', 'Schätzung für kühle Bedingungen'],
                ]);
            }
            case 'taupunkt-rechner': {
                const a = 17.62, b = 243.12;
                const gamma = Math.log(Math.max(1, n('humidity')) / 100) + (a * n('temp')) / (b + n('temp'));
                const dew = (b * gamma) / (a - gamma);
                const spread = n('temp') - dew;
                return rows([
                    ['Taupunkt', `${num.format(dew)} °C`],
                    ['Temperatur', `${num.format(n('temp'))} °C`],
                    ['Luftfeuchtigkeit', `${num.format(n('humidity'))} %`],
                    ['Taupunkt Fahrenheit', `${num.format(dew * 9 / 5 + 32)} °F`],
                    ['Abstand Temperatur/Taupunkt', `${num.format(spread)} °C`],
                    ['Kondensationsnaehe', spread < 2 ? 'sehr nah' : spread < 6 ? 'nah' : 'entfernt'],
                    ['Luftgefuehl', dew >= 20 ? 'schwuel' : dew >= 13 ? 'angenehm/feucht' : 'trocken'],
                    ['Relative Feuchte gerundet', `${Math.round(n('humidity'))} %`],
                    ['Schimmelrisiko grob', n('humidity') >= 65 ? 'erhoeht' : 'normal'],
                    ['Lueftungs-Hinweis', dew > 16 ? 'Feuchte beachten' : 'unauffaellig'],
                    ['Formel', 'Magnus-Naeherung'],
                ]);
            }
            case 'hitzeindex-rechner': {
                const c = n('temp');
                const rh = n('humidity');
                const f = c * 9 / 5 + 32;
                const hiF = -42.379 + 2.04901523 * f + 10.14333127 * rh - 0.22475541 * f * rh - 0.00683783 * f * f - 0.05481717 * rh * rh + 0.00122874 * f * f * rh + 0.00085282 * f * rh * rh - 0.00000199 * f * f * rh * rh;
                const hiC = (hiF - 32) * 5 / 9;
                return rows([
                    ['Hitzeindex', `${num.format(hiC)} °C`],
                    ['Temperatur', `${num.format(c)} °C`],
                    ['Luftfeuchtigkeit', `${num.format(rh)} %`],
                    ['Hitzeindex Fahrenheit', `${num.format(hiF)} °F`],
                    ['Gefuehlter Aufschlag', `${num.format(hiC - c)} °C`],
                    ['Risikostufe grob', hiC >= 41 ? 'hoch' : hiC >= 32 ? 'Vorsicht' : 'moderat'],
                    ['Temperatur Fahrenheit', `${num.format(f)} °F`],
                    ['Feuchteklasse', rh >= 70 ? 'sehr feucht' : rh >= 50 ? 'feucht' : 'normal'],
                    ['Trinken-Hinweis', hiC >= 32 ? 'viel trinken' : 'normal'],
                    ['Schatten-Hinweis', hiC >= 32 ? 'Schatten/Pausen sinnvoll' : 'unauffaellig'],
                    ['Formel', 'NOAA Heat Index'],
                ]);
            }
            case 'kleidergroessen-umrechner': {
                const eu = n('eu');
                const intl = eu <= 34 ? 'XS' : eu <= 38 ? 'S' : eu <= 42 ? 'M' : eu <= 46 ? 'L' : eu <= 50 ? 'XL' : 'XXL+';
                return rows([
                    ['EU', num.format(eu)],
                    ['International grob', intl],
                    ['US Damen grob', num.format(eu - 30)],
                    ['US Herren grob', num.format(eu - 10)],
                    ['UK Damen grob', num.format(eu - 32)],
                    ['UK Herren grob', num.format(eu - 34)],
                    ['Italien grob', num.format(eu + 4)],
                    ['Frankreich grob', num.format(eu + 2)],
                    ['Japan grob', num.format(eu - 2)],
                    ['Nächst kleiner', num.format(eu - 2)],
                    ['Nächst größer', num.format(eu + 2)],
                ]);
            }
            case 'bh-groessen-rechner': {
                const band = Math.round(n('under') / 5) * 5;
                const diff = n('bust') - band;
                const cups = ['AA', 'A', 'B', 'C', 'D', 'E', 'F', 'G'];
                const cup = cups[Math.max(0, Math.min(cups.length - 1, Math.round((diff - 10) / 2)))] ?? 'B';
                return rows([
                    ['BH-Größe grob', `${band}${cup}`],
                    ['Unterbrust gerundet', `${band} cm`],
                    ['Differenz', `${num.format(diff)} cm`],
                    ['Cup grob', cup],
                    ['Band kleiner', `${band - 5}${cup}`],
                    ['Band größer', `${band + 5}${cup}`],
                    ['Differenzklasse', diff < 12 ? 'klein' : diff < 18 ? 'mittel' : 'gross'],
                    ['UK Band grob', num.format(Math.round(band / 2.54))],
                    ['US Band grob', num.format(Math.round(band / 2.54))],
                    ['Unterbrust in inch', num.format(n('under') / 2.54)],
                    ['Brustumfang in inch', num.format(n('bust') / 2.54)],
                ]);
            }
            case 'farbpalette-generator': {
                const base = value('color');
                if (!/^#?[0-9a-fA-F]{6}$/.test(base.trim())) return rows([['Fehler', 'Bitte eine gültige Hex-Farbe eingeben (z. B. #2563eb)']]);
                const rgb = hexToRgb(base).map(v => Math.round(v * 255));
                const colors = [base, `rgb(${rgb.map(v => Math.max(0, v - 45)).join(', ')})`, `rgb(${rgb.map(v => Math.min(255, v + 45)).join(', ')})`, `rgb(${rgb[2]}, ${rgb[0]}, ${rgb[1]})`, `rgb(${255 - rgb[0]}, ${255 - rgb[1]}, ${255 - rgb[2]})`];
                return `<div class="list">${colors.map(c => `<span class="pill"><span class="swatch" style="background:${escapeHtml(c)}"></span><span class="mono">${escapeHtml(c)}</span></span>`).join('')}</div>`;
            }
            case 'hsl-farb-generator': {
                const h = ((n('h') % 360) + 360) % 360;
                const s = Math.max(0, Math.min(100, n('s')));
                const l = Math.max(0, Math.min(100, n('l')));
                const css = `hsl(${num.format(h)} ${num.format(s)}% ${num.format(l)}%)`;
                const c = (1 - Math.abs(2 * l / 100 - 1)) * (s / 100);
                const x = c * (1 - Math.abs((h / 60) % 2 - 1));
                const m = l / 100 - c / 2;
                const seg = [[c, x, 0], [x, c, 0], [0, c, x], [0, x, c], [x, 0, c], [c, 0, x]][Math.floor(h / 60) % 6];
                const rgb = seg.map(v => Math.round((v + m) * 255));
                const hex = '#' + rgb.map(v => v.toString(16).padStart(2, '0')).join('');
                return rows([
                    ['CSS HSL', `<span class="mono">${css}</span>`],
                    ['Hex', hex],
                    ['RGB', `rgb(${rgb.join(', ')})`],
                    ['Hue', num.format(h)],
                    ['Saettigung', `${num.format(s)} %`],
                    ['Helligkeit', `${num.format(l)} %`],
                    ['Komplementaerfarbe', `hsl(${num.format((h + 180) % 360)} ${num.format(s)}% ${num.format(l)}%)`],
                ]) + `<div class="result" style="background:${css};min-height:70px"></div>`;
            }
            case 'email-betreff-checker': {
                const subject = value('subject');
                const words = subject.trim().split(/\s+/).filter(Boolean).length;
                const status = subject.length <= 50 ? 'Gut für viele Inboxen' : subject.length <= 70 ? 'Ok, aber eher lang' : 'Sehr lang';
                const hasNumber = /\d/.test(subject);
                const hasQuestion = subject.includes('?');
                const hasBang = subject.includes('!');
                return rows([
                    ['Zeichen', subject.length],
                    ['Woerter', words],
                    ['Einschätzung', status],
                    ['Mobile Vorschau 35 Zeichen', escapeHtml(subject.slice(0, 35))],
                    ['Desktop Vorschau 60 Zeichen', escapeHtml(subject.slice(0, 60))],
                    ['Hat Zahl', hasNumber ? 'ja' : 'nein'],
                    ['Hat Frage', hasQuestion ? 'ja' : 'nein'],
                    ['Hat Ausrufezeichen', hasBang ? 'ja' : 'nein'],
                    ['Durchschnittliche Wortlänge', num.format(subject.replace(/\s+/g, '').length / Math.max(1, words))],
                    ['Empfehlung', subject.length <= 50 && words <= 9 ? 'knackig' : 'kuerzen prüfen'],
                    ['Spam-Risiko grob', subject.toUpperCase() === subject && subject.length > 8 ? 'erhoeht' : 'normal'],
                ]);
            }
            case 'keyword-dichte-rechner': {
                const text = value('text').toLowerCase();
                const keyword = value('keyword').toLowerCase().trim();
                const words = text.match(/[a-zA-Z0-9äöüÄÖÜß]+/g) ?? [];
                const count = keyword ? (text.match(new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) ?? []).length : 0;
                const density = words.length ? count / words.length * 100 : 0;
                const sentences = text.split(/[.!?]+/).filter(s => s.trim()).length;
                return rows([
                    ['Woerter', words.length],
                    ['Keyword Treffer', count],
                    ['Keyword Dichte', `${num.format(density)} %`],
                    ['Zeichen', text.length],
                    ['Saetze', sentences],
                    ['Woerter pro Satz', num.format(words.length / Math.max(1, sentences))],
                    ['Keyword im ersten Satz', keyword && text.split(/[.!?]+/)[0]?.includes(keyword) ? 'ja' : 'nein'],
                    ['Keyword im Titel-Stil', keyword ? keyword.replace(/\b\w/g, c => c.toUpperCase()) : '-'],
                    ['Dichte-Einschätzung', density < 0.5 ? 'niedrig' : density <= 3 ? 'normal' : 'hoch'],
                    ['Empfohlene Treffer bei 1%', num.format(words.length * 0.01)],
                    ['Empfohlene Treffer bei 2%', num.format(words.length * 0.02)],
                ]);
            }
            case 'hreflang-generator': {
                const tags = value('pairs').split(/\n+/).map(line => line.trim()).filter(Boolean).map(line => {
                    const [lang, url] = line.split('=').map(s => s.trim());
                    return `<link rel="alternate" hreflang="${lang}" href="${url}">`;
                });
                return `<div class="result copy-box">${escapeHtml(tags.join('\n'))}</div>`;
            }
            case 'canonical-tag-generator': {
                const tag = `<link rel="canonical" href="${value('url')}">`;
                return `<div class="result copy-box">${escapeHtml(tag)}</div>`;
            }
            case 'faq-schema-generator': {
                const questions = value('faq').split(/\n+/).map(line => line.trim()).filter(Boolean).map(line => {
                    const parts = line.split('?');
                    return { q: (parts[0] || '').trim() + '?', a: parts.slice(1).join('?').trim() || 'Antwort ergaenzen.' };
                });
                const at = String.fromCharCode(64);
                const schema = { [at + 'context']: 'https://schema.org', [at + 'type']: 'FAQPage', mainEntity: questions.map(item => ({ [at + 'type']: 'Question', name: item.q, acceptedAnswer: { [at + 'type']: 'Answer', text: item.a } })) };
                const json = JSON.stringify(schema, null, 2);
                const withAnswers = questions.filter(q => q.a !== 'Antwort ergaenzen.').length;
                return rows([
                    ['Fragen', num.format(questions.length)],
                    ['Mit Antwort', num.format(withAnswers)],
                    ['Ohne Antwort', num.format(questions.length - withAnswers)],
                    ['JSON-LD Zeichen', num.format(json.length)],
                    ['Einbau', 'als <script type="application/ld+json">'],
                    ['Rich-Result faehig', questions.length >= 2 ? 'ja' : 'min. 2 Fragen empfohlen'],
                ]) + `<div class="result copy-box">${escapeHtml(json)}</div>`;
            }
            case 'co2-fussabdruck-rechner': {
                const carKm = clamp(n('carKm'), 0, 20000);
                const kwh = clamp(n('kwh'), 0, 10000);
                const flightHours = clamp(n('flightHours'), 0, 1000);
                const car = carKm * 0.17 * 12;
                const power = kwh * 0.38 * 12;
                const flight = flightHours * 90;
                const total = car + power + flight;
                return rows([
                    ['Auto pro Jahr', `${num.format(car)} kg CO2`],
                    ['Strom pro Jahr', `${num.format(power)} kg CO2`],
                    ['Flug pro Jahr grob', `${num.format(flight)} kg CO2`],
                    ['Gesamt pro Jahr', `${num.format(total)} kg CO2`],
                    ['In Tonnen', `${num.format(total / 1000)} t`],
                    ['Pro Monat', `${num.format(total / 12)} kg CO2`],
                    ['Baeume grob zur Bindung/Jahr', Math.ceil(total / 22)],
                    ['Auto-Faktor', '0,17 kg CO2/km'],
                    ['Strom-Faktor', '0,38 kg CO2/kWh'],
                    ['Kategorie', total < 500 ? 'niedrig' : total < 2000 ? 'mittel' : 'hoch'],
                    ['Hinweis', 'Naeherungswerte'],
                    ['Datenschutz', 'lokal im Browser'],
                ]);
            }
            case 'solar-ertrag-rechner': {
                const yearly = n('kwp') * n('yield');
                const own = yearly * n('self') / 100;
                const saving = own * n('price');
                const payback = saving > 0 ? n('cost') / saving : 0;
                return rows([
                    ['Jahresertrag', `${num.format(yearly)} kWh`],
                    ['Eigenverbrauch', `${num.format(own)} kWh`],
                    ['Einspeisung grob', `${num.format(yearly - own)} kWh`],
                    ['Ersparnis/Jahr', euro.format(saving)],
                    ['Ersparnis/Monat', euro.format(saving / 12)],
                    ['Amortisation grob', payback ? `${num.format(payback)} Jahre` : '-'],
                    ['Ertrag in 20 Jahren', `${num.format(yearly * 20)} kWh`],
                    ['Ersparnis 20 Jahre', euro.format(saving * 20)],
                    ['CO2-Vermeidung/Jahr grob', `${num.format(yearly * 0.38)} kg`],
                    ['Kosten pro kWp', euro.format(n('cost') / Math.max(1, n('kwp')))],
                    ['Bewertung', payback && payback < 12 ? 'attraktiv' : 'prüfen'],
                ]);
            }
            case 'backup-speicher-rechner': {
                const grown = n('gb') * Math.pow(1 + n('growth') / 100, n('months'));
                const total = grown * n('copies') * n('months');
                return rows([
                    ['Daten nach Zeitraum', `${num.format(grown)} GB`],
                    ['Backup-Speicher grob', `${num.format(total)} GB`],
                    ['In TB', `${num.format(total / 1024)} TB`],
                    ['Startdaten', `${num.format(n('gb'))} GB`],
                    ['Wachstum absolut', `${num.format(grown - n('gb'))} GB`],
                    ['Vollbackups gesamt', n('copies') * n('months')],
                    ['Mit 20% Reserve', `${num.format(total * 1.2 / 1024)} TB`],
                    ['3-2-1 Kopien grob', `${num.format(total * 3 / 1024)} TB`],
                    ['Monatliches Wachstum', `${num.format(n('growth'))} %`],
                    ['Retention', `${num.format(n('months'))} Monate`],
                    ['Hinweis', 'Inkrementelle Backups koennen deutlich kleiner sein'],
                ]);
            }
            case 'daten-transferzeit-rechner': {
                const bits = n('size') * 1024 * 8 * (1 + n('overhead') / 100);
                const seconds = bits / Math.max(0.001, n('mbit'));
                return rows([
                    ['Dauer', duration(seconds)],
                    ['Sekunden', num.format(seconds)],
                    ['Minuten', num.format(seconds / 60)],
                    ['Stunden', num.format(seconds / 3600)],
                    ['Effektive Größe', `${num.format(n('size') * (1 + n('overhead') / 100))} GB`],
                    ['Bandbreite', `${num.format(n('mbit'))} Mbit/s`],
                    ['MB/s grob', num.format(n('mbit') / 8)],
                    ['Dauer bei halber Leitung', duration(seconds * 2)],
                    ['Dauer bei doppelter Leitung', duration(seconds / 2)],
                    ['Overhead', `${num.format(n('overhead'))} %`],
                    ['Hinweis', 'Netzwerk-Schwankungen nicht enthalten'],
                ]);
            }
            case 'api-rate-limit-rechner': {
                const used = n('users') * n('calls');
                const limit = n('limit');
                return rows([
                    ['RPS Limit', num.format(limit / 60)],
                    ['Requests/Stunde', num.format(limit * 60)],
                    ['Requests/Tag', num.format(limit * 1440)],
                    ['Aktueller Bedarf/Minute', num.format(used)],
                    ['Auslastung', `${num.format(limit ? used / limit * 100 : 0)} %`],
                    ['Reserve/Minute', num.format(limit - used)],
                    ['Max Nutzer bei Calls', num.format(n('calls') ? limit / n('calls') : 0)],
                    ['Calls pro Nutzer bei Limit', num.format(n('users') ? limit / n('users') : 0)],
                    ['Status', used <= limit ? 'innerhalb Limit' : 'Limit überschritten'],
                    ['Empfohlenes Limit +30%', num.format(used * 1.3)],
                    ['Burst 10 Sekunden', num.format(limit / 6)],
                ]);
            }
            case 'uptime-rechner': {
                const down = Math.max(0, 100 - n('sla')) / 100;
                return rows([
                    ['Downtime pro Tag', duration(86400 * down)],
                    ['Downtime pro Woche', duration(604800 * down)],
                    ['Downtime pro Monat', duration(2592000 * down)],
                    ['Downtime pro Jahr', duration(31536000 * down)],
                    ['Uptime', `${num.format(n('sla'))} %`],
                    ['Downtime Prozent', `${num.format(down * 100)} %`],
                    ['99% pro Jahr', duration(31536000 * 0.01)],
                    ['99.9% pro Jahr', duration(31536000 * 0.001)],
                    ['99.99% pro Jahr', duration(31536000 * 0.0001)],
                    ['99.999% pro Jahr', duration(31536000 * 0.00001)],
                    ['SLA-Klasse', n('sla') >= 99.99 ? 'hoch' : n('sla') >= 99.9 ? 'solide' : 'basic'],
                ]);
            }
            case 'server-kosten-rechner': {
                const monthly = n('base') + n('traffic') * n('trafficprice') + n('backup');
                return rows([
                    ['Monatlich', euro.format(monthly)],
                    ['Jaehrlich', euro.format(monthly * 12)],
                    ['Server-Grundpreis', euro.format(n('base'))],
                    ['Traffic-Kosten', euro.format(n('traffic') * n('trafficprice'))],
                    ['Backup/Lizenzen', euro.format(n('backup'))],
                    ['3 Jahre', euro.format(monthly * 36)],
                    ['5 Jahre', euro.format(monthly * 60)],
                    ['Pro Tag', euro.format(monthly / 30)],
                    ['Pro TB effektiv', euro.format(monthly / Math.max(1, n('traffic')))],
                    ['Reserve +20%', euro.format(monthly * 1.2)],
                    ['Kategorie', monthly < 25 ? 'klein' : monthly < 100 ? 'mittel' : 'gross'],
                ]);
            }
            case 'docker-image-size-rechner': {
                const totalMb = n('size') * n('nodes') * n('deploys');
                const pullSeconds = n('size') * 8 / Math.max(0.001, n('mbit'));
                return rows([
                    ['Traffic pro Tag', `${num.format(totalMb / 1024)} GB`],
                    ['Traffic pro Monat', `${num.format(totalMb * 30 / 1024)} GB`],
                    ['Pull-Zeit pro Node', duration(pullSeconds)],
                    ['Image-Größe', `${num.format(n('size'))} MB`],
                    ['Nodes', n('nodes')],
                    ['Deploys/Tag', n('deploys')],
                    ['Registry-Speicher 10 Tags', `${num.format(n('size') * 10 / 1024)} GB`],
                    ['Traffic bei 2x Größe', `${num.format(totalMb * 2 / 1024)} GB/Tag`],
                    ['Traffic bei halbiert', `${num.format(totalMb / 2 / 1024)} GB/Tag`],
                    ['Pull parallel alle Nodes', `${num.format(n('size') * n('nodes') / 1024)} GB`],
                    ['Tipp', n('size') > 1000 ? 'Image slimming lohnt sich' : 'ok'],
                ]);
            }
            case 'cron-intervall-rechner': {
                const runsDay = 1440 / Math.max(1, n('minutes'));
                const busy = runsDay * n('duration');
                return rows([
                    ['Runs pro Tag', num.format(runsDay)],
                    ['Runs pro Woche', num.format(runsDay * 7)],
                    ['Runs pro Monat', num.format(runsDay * 30)],
                    ['Runs pro Jahr', num.format(runsDay * 365)],
                    ['Laufzeit pro Tag', duration(busy)],
                    ['Auslastung pro Tag', `${num.format(busy / 86400 * 100)} %`],
                    ['Nächste 24h Jobs', Math.floor(runsDay)],
                    ['Intervall', `${num.format(n('minutes'))} min`],
                    ['Cron grob', `*/${Math.max(1, Math.round(n('minutes')))} * * * *`],
                    ['Parallel-Risiko', n('duration') > n('minutes') * 60 ? 'hoch' : 'niedrig'],
                    ['Jobdauer', duration(n('duration'))],
                ]);
            }
            case 'regex-escape-tool': {
                const raw = value('text');
                const escaped = raw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                return rows([
                    ['Original Zeichen', raw.length],
                    ['Escaped Zeichen', escaped.length],
                    ['Mehrzeichen', escaped.length - raw.length],
                    ['Regex Literal JS', `<span class="mono">/${escapeHtml(escaped)}/</span>`],
                    ['Enthaelt Sonderzeichen', escaped.length > raw.length ? 'ja' : 'nein'],
                    ['Case-insensitive', `<span class="mono">/${escapeHtml(escaped)}/i</span>`],
                    ['Global', `<span class="mono">/${escapeHtml(escaped)}/g</span>`],
                    ['Multiline', `<span class="mono">/${escapeHtml(escaped)}/gm</span>`],
                    ['Datenschutz', 'lokal im Browser'],
                    ['Escaped Text', `<span class="mono">${escapeHtml(escaped)}</span>`],
                    ['Hinweis', 'Für JavaScript RegExp'],
                ]);
            }
            case 'html-minifier-light': {
                const raw = value('html');
                const mini = raw.replace(/<!--[\s\S]*?-->/g, '').replace(/>\s+</g, '><').replace(/\s{2,}/g, ' ').trim();
                const saved = raw.length ? (1 - mini.length / raw.length) * 100 : 0;
                return rows([
                    ['Original Zeichen', raw.length],
                    ['Minified Zeichen', mini.length],
                    ['Gespart', `${num.format(saved)} %`],
                    ['Bytes gespart', Math.max(0, raw.length - mini.length)],
                    ['Zeilen vorher', raw.split('\n').length],
                    ['Zeilen nachher', mini.split('\n').length],
                    ['Enthaelt Kommentare', raw.includes('<!--') ? 'ja' : 'nein'],
                    ['Status', mini.length <= raw.length ? 'komprimiert' : 'unveraendert'],
                    ['Hinweis', 'Light-Minifier für einfache Snippets'],
                    ['Ausgabe', `<span class="mono">${escapeHtml(mini)}</span>`],
                    ['Datenschutz', 'lokal im Browser'],
                ]);
            }
            case 'fluid-spacing-generator': {
                const slope = (n('max') - n('min')) / Math.max(1, n('maxvw') - n('minvw')) * 100;
                const intercept = n('min') - slope * n('minvw') / 100;
                const css = `clamp(${n('min')}px, ${num.format(intercept)}px + ${num.format(slope)}vw, ${n('max')}px)`;
                return rows([
                    ['CSS', `<span class="mono">${escapeHtml(css)}</span>`],
                    ['Steigung', `${num.format(slope)}vw`],
                    ['Basis', `${num.format(intercept)}px`],
                    ['Min Wert', `${num.format(n('min'))}px`],
                    ['Max Wert', `${num.format(n('max'))}px`],
                    ['Viewport min/max', `${num.format(n('minvw'))} / ${num.format(n('maxvw'))} px`],
                    ['Wert bei 768px', `${num.format(intercept + slope * 7.68)}px`],
                    ['Wert bei 1024px', `${num.format(intercept + slope * 10.24)}px`],
                    ['Wert bei 1440px', `${num.format(Math.min(n('max'), Math.max(n('min'), intercept + slope * 14.4)))}px`],
                    ['CSS Property Beispiel', `<span class="mono">font-size: ${escapeHtml(css)};</span>`],
                    ['Datenschutz', 'lokal im Browser'],
                ]);
            }
            case 'viewport-rechner': {
                return rows([
                    ['vw', `${num.format(n('px') / Math.max(1, n('width')) * 100)}vw`],
                    ['vh', `${num.format(n('px') / Math.max(1, n('height')) * 100)}vh`],
                    ['vmin', `${num.format(n('px') / Math.max(1, Math.min(n('width'), n('height'))) * 100)}vmin`],
                    ['vmax', `${num.format(n('px') / Math.max(1, Math.max(n('width'), n('height'))) * 100)}vmax`],
                    ['Pixel', `${num.format(n('px'))}px`],
                    ['Viewport', `${num.format(n('width'))} x ${num.format(n('height'))}`],
                    ['1vw in px', `${num.format(n('width') / 100)}px`],
                    ['1vh in px', `${num.format(n('height') / 100)}px`],
                    ['50vw in px', `${num.format(n('width') / 2)}px`],
                    ['100vh in px', `${num.format(n('height'))}px`],
                    ['CSS Beispiel', `<span class="mono">width: ${num.format(n('px') / Math.max(1, n('width')) * 100)}vw;</span>`],
                ]);
            }
            case 'aspect-ratio-crop-rechner': {
                const target = n('rw') / Math.max(1, n('rh'));
                const current = n('w') / Math.max(1, n('h'));
                let cropW = n('w'), cropH = n('h');
                if (target > 0) { if (current > target) cropW = n('h') * target; else cropH = n('w') / target; }
                return rows([
                    ['Crop Breite', `${num.format(cropW)} px`],
                    ['Crop Höhe', `${num.format(cropH)} px`],
                    ['Beschnitt Breite', `${num.format(n('w') - cropW)} px`],
                    ['Beschnitt Höhe', `${num.format(n('h') - cropH)} px`],
                    ['Ziel Ratio', `${n('rw')}:${n('rh')}`],
                    ['Original Ratio', num.format(current)],
                    ['Ziel Ratio Dezimal', num.format(target)],
                    ['Beschnitt Richtung', current > target ? 'links/rechts' : 'oben/unten'],
                    ['Behaltene Fläche', `${num.format((cropW * cropH) / Math.max(1, n('w') * n('h')) * 100)} %`],
                    ['Zentrum X/Y', `${num.format(n('w') / 2)} / ${num.format(n('h') / 2)}`],
                    ['CSS object-fit', 'cover'],
                ]);
            }
            case 'dpi-pixel-rechner': {
                const inchW = n('pxw') / Math.max(1, n('dpi'));
                const inchH = n('pxh') / Math.max(1, n('dpi'));
                return rows([
                    ['Breite cm', `${num.format(inchW * 2.54)} cm`],
                    ['Höhe cm', `${num.format(inchH * 2.54)} cm`],
                    ['Breite inch', `${num.format(inchW)} in`],
                    ['Höhe inch', `${num.format(inchH)} in`],
                    ['Megapixel', `${num.format(n('pxw') * n('pxh') / 1000000)} MP`],
                    ['Pixel gesamt', num.format(n('pxw') * n('pxh'))],
                    ['A4 bei 300dpi?', n('pxw') >= 2480 && n('pxh') >= 3508 ? 'ja grob' : 'eher nein'],
                    ['DPI bei 10 cm Breite', num.format(n('pxw') / (10 / 2.54))],
                    ['DPI bei 20 cm Breite', num.format(n('pxw') / (20 / 2.54))],
                    ['Seitenverhältnis', num.format(n('pxw') / Math.max(1, n('pxh')))],
                    ['Hinweis', 'Druckqualitaet Motiv/Material beachten'],
                ]);
            }
            case 'roas-rechner': {
                const roas = n('revenue') / Math.max(0.01, n('adcost'));
                const gross = n('revenue') * n('margin') / 100;
                const profit = gross - n('adcost');
                return rows([
                    ['ROAS', `${num.format(roas)}x`],
                    ['ROAS Prozent', `${num.format(roas * 100)} %`],
                    ['Bruttogewinn', euro.format(gross)],
                    ['Gewinn nach Ads', euro.format(profit)],
                    ['Break-even ROAS', `${num.format(100 / Math.max(1, n('margin')))}x`],
                    ['Kosten-Umsatz-Relation', `${num.format(n('adcost') / Math.max(1, n('revenue')) * 100)} %`],
                    ['Marge', `${num.format(n('margin'))} %`],
                    ['Umsatz pro Euro Adspend', euro.format(roas)],
                    ['Max Adspend Break-even', euro.format(gross)],
                    ['Reserve bis Break-even', euro.format(gross - n('adcost'))],
                    ['Status', profit >= 0 ? 'profitabel' : 'unter Break-even'],
                ]);
            }
            case 'newsletter-roi-rechner': {
                const opens = n('subs') * n('open') / 100;
                const clicks = opens * n('ctr') / 100;
                const sales = clicks * n('conv') / 100;
                const revenue = sales * n('aov');
                return rows([
                    ['Opens', num.format(opens)],
                    ['Klicks', num.format(clicks)],
                    ['Bestellungen', num.format(sales)],
                    ['Umsatz', euro.format(revenue)],
                    ['Umsatz pro Empfaenger', euro.format(revenue / Math.max(1, n('subs')))],
                    ['Open Rate', `${num.format(n('open'))} %`],
                    ['Click Rate auf Opens', `${num.format(n('ctr'))} %`],
                    ['Conversion auf Klicks', `${num.format(n('conv'))} %`],
                    ['Warenkorbwert', euro.format(n('aov'))],
                    ['Umsatz bei +10% Liste', euro.format(revenue * 1.1)],
                    ['Umsatz bei doppelter CTR', euro.format(revenue * 2)],
                ]);
            }
            case 'deckungsbeitrag-rechner': {
                const contribution = n('price') - n('variable');
                const units = contribution > 0 ? Math.ceil(n('fixed') / contribution) : 0;
                return rows([
                    ['Deckungsbeitrag/Stück', euro.format(contribution)],
                    ['Break-even Menge', units || 'nicht erreichbar'],
                    ['Break-even Umsatz', units ? euro.format(units * n('price')) : '-'],
                    ['Fixkosten', euro.format(n('fixed'))],
                    ['Variable Kosten/Stück', euro.format(n('variable'))],
                    ['Deckungsbeitrag Prozent', `${num.format(n('price') ? contribution / n('price') * 100 : 0)} %`],
                    ['Gewinn bei 100 Stück', euro.format(contribution * 100 - n('fixed'))],
                    ['Gewinn bei 500 Stück', euro.format(contribution * 500 - n('fixed'))],
                    ['Einheiten für 1.000 Euro Gewinn', contribution > 0 ? Math.ceil((n('fixed') + 1000) / contribution) : '-'],
                    ['Status', contribution > 0 ? 'rechenbar' : 'Preis zu niedrig'],
                    ['Preis +10%', euro.format(n('price') * 1.1)],
                ]);
            }
            case 'freelancer-tagessatz-rechner': {
                const billable = n('days') * n('util') / 100;
                const rate = (n('target') + n('costs')) / Math.max(1, billable);
                return rows([
                    ['Nötiger Tagessatz', euro.format(rate)],
                    ['Nötiger Stundensatz 8h', euro.format(rate / 8)],
                    ['Abrechenbare Tage', num.format(billable)],
                    ['Nicht abrechenbare Tage', num.format(n('days') - billable)],
                    ['Jahresbedarf', euro.format(n('target') + n('costs'))],
                    ['Kostenanteil pro Tag', euro.format(n('costs') / Math.max(1, billable))],
                    ['Monatsziel', euro.format(n('target') / 12)],
                    ['Monatskosten', euro.format(n('costs') / 12)],
                    ['Tagessatz +20% Puffer', euro.format(rate * 1.2)],
                    ['Auslastung', `${num.format(n('util'))} %`],
                    ['Status', rate < 500 ? 'niedrig/mittel' : rate < 900 ? 'solide' : 'premium'],
                ]);
            }
            case 'ab-test-rechner': {
                const rateA = n('convA') / Math.max(1, n('visA'));
                const rateB = n('convB') / Math.max(1, n('visB'));
                const lift = rateA ? (rateB / rateA - 1) * 100 : 0;
                return rows([
                    ['Conversion Rate A', `${num.format(rateA * 100)} %`],
                    ['Conversion Rate B', `${num.format(rateB * 100)} %`],
                    ['Lift B gegen A', `${num.format(lift)} %`],
                    ['Gewinner', rateB > rateA ? 'Variante B' : rateA > rateB ? 'Variante A' : 'gleichauf'],
                    ['Conversions Differenz', num.format(n('convB') - n('convA'))],
                    ['Besucher gesamt', num.format(n('visA') + n('visB'))],
                    ['Conversions gesamt', num.format(n('convA') + n('convB'))],
                    ['Gesamt-CR', `${num.format((n('convA') + n('convB')) / Math.max(1, n('visA') + n('visB')) * 100)} %`],
                    ['Mehr Conversions bei 10k Visits', num.format((rateB - rateA) * 10000)],
                    ['Hinweis', 'vereinfachter Vergleich ohne Signifikanztest'],
                    ['Status', Math.abs(lift) > 10 ? 'deutlicher Unterschied' : 'kleiner Unterschied'],
                ]);
            }
            case 'text-tonalitaet-checker': {
                const text = value('text');
                const words = text.match(/[a-zA-Z0-9äöüÄÖÜß]+/g) ?? [];
                const sentences = text.split(/[.!?]+/).filter(s => s.trim()).length;
                const du = (text.match(/\b(du|dir|dich|dein|deine)\b/gi) ?? []).length;
                const sie = (text.match(/\b(Sie|Ihnen|Ihr|Ihre)\b/g) ?? []).length;
                const bangs = (text.match(/!/g) ?? []).length;
                const questions = (text.match(/\?/g) ?? []).length;
                return rows([
                    ['Woerter', words.length],
                    ['Zeichen', text.length],
                    ['Saetze', sentences],
                    ['Woerter pro Satz', num.format(words.length / Math.max(1, sentences))],
                    ['Ausrufezeichen', bangs],
                    ['Fragen', questions],
                    ['Du-Ansprachen', du],
                    ['Sie-Ansprachen', sie],
                    ['Ton grob', du > sie ? 'direkt/locker' : sie > du ? 'formell' : 'neutral'],
                    ['Energie', bangs > 2 ? 'hoch' : bangs > 0 ? 'mittel' : 'ruhig'],
                    ['Lesbarkeit grob', words.length / Math.max(1, sentences) <= 14 ? 'leicht' : 'dichter Text'],
                ]);
            }
            case 'zinseszins-rechner': {
                const comp = Math.max(1, n('comp'));
                const periods = n('years') * comp;
                const factor = Math.pow(1 + n('rate') / 100 / comp, periods);
                const end = n('capital') * factor;
                const interest = end - n('capital');
                const doubling = n('rate') > 0 ? 72 / n('rate') : 0;
                return rows([
                    ['Endkapital', euro.format(end)],
                    ['Zinsertrag gesamt', euro.format(interest)],
                    ['Startkapital', euro.format(n('capital'))],
                    ['Wachstumsfaktor', `${num.format(factor)} x`],
                    ['Ertrag in Prozent', `${num.format(n('capital') ? interest / n('capital') * 100 : 0)} %`],
                    ['Endkapital nach 5 Jahren', euro.format(n('capital') * Math.pow(1 + n('rate') / 100 / comp, 5 * comp))],
                    ['Durchschnitt pro Jahr', euro.format(interest / Math.max(1, n('years')))],
                    ['Verdopplungszeit (Faustregel 72)', doubling ? `${num.format(doubling)} Jahre` : '-'],
                    ['Effektivzins pro Jahr', `${num.format((Math.pow(1 + n('rate') / 100 / comp, comp) - 1) * 100)} %`],
                ]);
            }
            case 'sparplan-rechner': {
                const r = n('rate') / 100 / 12;
                const months = n('years') * 12;
                const fvStart = n('start') * Math.pow(1 + r, months);
                const fvRates = r === 0 ? n('monthly') * months : n('monthly') * (Math.pow(1 + r, months) - 1) / r;
                const total = fvStart + fvRates;
                const deposits = n('start') + n('monthly') * months;
                const interest = total - deposits;
                return rows([
                    ['Endwert', euro.format(total)],
                    ['Eingezahlt gesamt', euro.format(deposits)],
                    ['Zinsertrag', euro.format(interest)],
                    ['davon aus Sparraten', euro.format(fvRates)],
                    ['davon aus Startkapital', euro.format(fvStart)],
                    ['Anzahl Raten', num.format(months)],
                    ['Ertrag in Prozent', `${num.format(deposits ? interest / deposits * 100 : 0)} %`],
                    ['Durchschnitt pro Jahr', euro.format(interest / Math.max(1, n('years')))],
                    ['Endwert pro 100 Euro Rate', euro.format(n('monthly') ? total / n('monthly') * 100 : 0)],
                ]);
            }
            case 'abschreibung-rechner': {
                const base = Math.max(0, n('cost') - n('residual'));
                const annual = base / Math.max(1, n('years'));
                const rate = n('cost') ? annual / n('cost') * 100 : 0;
                const halfYears = Math.floor(n('years') / 2);
                return rows([
                    ['Jaehrliche AfA', euro.format(annual)],
                    ['Monatliche AfA', euro.format(annual / 12)],
                    ['AfA-Satz pro Jahr', `${num.format(rate)} %`],
                    ['Abschreibungsbasis', euro.format(base)],
                    ['Buchwert nach 1 Jahr', euro.format(n('cost') - annual)],
                    [`Buchwert nach ${halfYears} Jahren`, euro.format(Math.max(n('residual'), n('cost') - annual * halfYears))],
                    ['Gesamte AfA', euro.format(base)],
                    ['Restwert', euro.format(n('residual'))],
                    ['Taegliche AfA', euro.format(annual / 365)],
                ]);
            }
            case 'abgeltungssteuer-rechner': {
                const taxable = Math.max(0, n('gain') - n('allowance'));
                const tax = taxable * 0.25;
                const soli = tax * 0.055;
                const church = tax * (n('church') / 100);
                const totalTax = tax + soli + church;
                const net = n('gain') - totalTax;
                return rows([
                    ['Steuer gesamt', euro.format(totalTax)],
                    ['Nettoertrag', euro.format(net)],
                    ['Steuerpflichtig', euro.format(taxable)],
                    ['Abgeltungssteuer 25%', euro.format(tax)],
                    ['Solidaritaetszuschlag', euro.format(soli)],
                    ['Kirchensteuer', euro.format(church)],
                    ['Freibetrag genutzt', euro.format(Math.min(n('gain'), n('allowance')))],
                    ['Effektive Steuerlast', `${num.format(n('gain') ? totalTax / n('gain') * 100 : 0)} %`],
                    ['Hinweis', 'Schätzung, ohne Guenstigerprüfung'],
                ]);
            }
            case 'effektivzins-rechner': {
                const periods = Math.max(1, n('periods'));
                const eff = (Math.pow(1 + n('nominal') / 100 / periods, periods) - 1) * 100;
                return rows([
                    ['Effektiver Jahreszins', `${num.format(eff)} %`],
                    ['Nominalzins', `${num.format(n('nominal'))} %`],
                    ['Differenz', `${num.format(eff - n('nominal'))} %-Punkte`],
                    ['Zins pro Periode', `${num.format(n('nominal') / periods)} %`],
                    ['Zinsperioden pro Jahr', num.format(periods)],
                    ['Faktor pro Jahr', `${num.format(Math.pow(1 + n('nominal') / 100 / periods, periods))} x`],
                    ['Beispiel 1000 Euro Ertrag', euro.format(1000 * eff / 100)],
                ]);
            }
            case 'dispozinsen-rechner': {
                const interest = n('amount') * n('rate') / 100 * n('days') / 365;
                const perDay = n('amount') * n('rate') / 100 / 365;
                return rows([
                    ['Zinskosten gesamt', euro.format(interest)],
                    ['Kosten pro Tag', euro.format(perDay)],
                    ['Kosten pro Monat (30 Tage)', euro.format(perDay * 30)],
                    ['Hochrechnung pro Jahr', euro.format(n('amount') * n('rate') / 100)],
                    ['Überziehungsbetrag', euro.format(n('amount'))],
                    ['Tage überzogen', num.format(n('days'))],
                    ['Kosten je 100 Euro', euro.format(100 * n('rate') / 100 * n('days') / 365)],
                    ['Endbelastung', euro.format(n('amount') + interest)],
                ]);
            }
            case 'weihnachtsgeld-rechner': {
                const full = n('salary') * n('percent') / 100;
                const months = clamp(n('months'), 0, 12);
                const pro = full * months / 12;
                return rows([
                    ['Weihnachtsgeld anteilig', euro.format(pro)],
                    ['Weihnachtsgeld voll', euro.format(full)],
                    ['Differenz zu voll', euro.format(full - pro)],
                    ['Beschaeftigte Monate', num.format(months)],
                    ['Anteil pro Monat', euro.format(full / 12)],
                    ['Prozent vom Monatsgehalt', `${num.format(n('percent'))} %`],
                    ['Prozent vom Jahresgehalt', `${num.format(n('salary') ? pro / (n('salary') * 12) * 100 : 0)} %`],
                ]);
            }
            case 'minijob-rechner': {
                const earn = n('wage') * n('hours');
                const maxHours = n('wage') ? n('limit') / n('wage') : 0;
                const over = earn > n('limit');
                return rows([
                    ['Monatsverdienst', euro.format(earn)],
                    ['Verdienstgrenze', euro.format(n('limit'))],
                    ['Verbleibend bis Grenze', euro.format(Math.max(0, n('limit') - earn))],
                    ['Maximale Stunden pro Monat', `${num.format(maxHours)} h`],
                    ['Status', over ? 'Grenze überschritten' : 'innerhalb der Grenze'],
                    ['Jahresverdienst', euro.format(earn * 12)],
                    ['Jahresgrenze', euro.format(n('limit') * 12)],
                    ['Stundenlohn', euro.format(n('wage'))],
                    ['Stunden pro Woche (Schnitt)', `${num.format(n('hours') / 4.33)} h`],
                ]);
            }
            case 'provisions-rechner': {
                const commission = n('revenue') * n('rate') / 100;
                const total = commission + n('base');
                return rows([
                    ['Provision', euro.format(commission)],
                    ['Fixum', euro.format(n('base'))],
                    ['Gesamtverdienst', euro.format(total)],
                    ['Provisionssatz', `${num.format(n('rate'))} %`],
                    ['Provision je 1000 Euro Umsatz', euro.format(n('revenue') ? commission / n('revenue') * 1000 : 10 * n('rate'))],
                    ['Umsatz', euro.format(n('revenue'))],
                    ['Hochrechnung pro Jahr', euro.format(total * 12)],
                ]);
            }
            case 'tagesgeld-rechner': {
                const gross = n('capital') * n('rate') / 100 * n('days') / 365;
                const taxAmt = gross * n('tax') / 100;
                const net = gross - taxAmt;
                return rows([
                    ['Zinsertrag brutto', euro.format(gross)],
                    ['Steuer', euro.format(taxAmt)],
                    ['Zinsertrag netto', euro.format(net)],
                    ['Endkapital netto', euro.format(n('capital') + net)],
                    ['Ertrag pro Tag (brutto)', euro.format(n('capital') * n('rate') / 100 / 365)],
                    ['Ertrag pro Monat (brutto)', euro.format(n('capital') * n('rate') / 100 / 12)],
                    ['Hochrechnung 1 Jahr (brutto)', euro.format(n('capital') * n('rate') / 100)],
                    ['Nettorendite', `${num.format(n('capital') ? net / n('capital') / Math.max(1, n('days')) * 365 * 100 : 0)} %`],
                ]);
            }
            case 'anleihe-rendite-rechner': {
                const couponEuro = n('nominal') * n('coupon') / 100;
                const buyPrice = n('nominal') * n('price') / 100;
                const currentYield = buyPrice ? couponEuro / buyPrice * 100 : 0;
                const gainPerYear = (n('nominal') - buyPrice) / Math.max(1, n('years'));
                const simpleYtm = buyPrice ? (couponEuro + gainPerYear) / buyPrice * 100 : 0;
                return rows([
                    ['Laufende Rendite', `${num.format(currentYield)} %`],
                    ['Einfache Endrendite', `${num.format(simpleYtm)} %`],
                    ['Kupon pro Jahr', euro.format(couponEuro)],
                    ['Kaufpreis', euro.format(buyPrice)],
                    ['Rueckzahlung', euro.format(n('nominal'))],
                    ['Kursgewinn gesamt', euro.format(n('nominal') - buyPrice)],
                    ['Kursgewinn pro Jahr', euro.format(gainPerYear)],
                    ['Kupon gesamt über Laufzeit', euro.format(couponEuro * n('years'))],
                    ['Gesamtrueckfluss', euro.format(couponEuro * n('years') + n('nominal'))],
                ]);
            }
            case 'mengenrabatt-rechner': {
                const gross = n('qty') * n('price');
                const disc = gross * n('discount') / 100;
                const net = gross - disc;
                return rows([
                    ['Gesamtpreis nach Rabatt', euro.format(net)],
                    ['Ersparnis', euro.format(disc)],
                    ['Gesamtpreis ohne Rabatt', euro.format(gross)],
                    ['Stückpreis nach Rabatt', euro.format(n('qty') ? net / n('qty') : 0)],
                    ['Stückpreis ohne Rabatt', euro.format(n('price'))],
                    ['Rabatt pro Stück', euro.format(n('qty') ? disc / n('qty') : 0)],
                    ['Menge', num.format(n('qty'))],
                    ['Rabattsatz', `${num.format(n('discount'))} %`],
                ]);
            }
            case 'fremdwaehrungsgebuehr-rechner': {
                const eurBase = n('rate') ? n('amount') / n('rate') : 0;
                const feeAmt = eurBase * n('fee') / 100;
                const total = eurBase + feeAmt;
                return rows([
                    ['Endbetrag in Euro', euro.format(total)],
                    ['Umrechnung ohne Gebühr', euro.format(eurBase)],
                    ['Auslandsgebühr', euro.format(feeAmt)],
                    ['Betrag Fremdwährung', num.format(n('amount'))],
                    ['Kurs (FW pro Euro)', num.format(n('rate'))],
                    ['Gebührensatz', `${num.format(n('fee'))} %`],
                    ['Effektiver Kurs inkl. Gebühr', num.format(total ? n('amount') / total : 0)],
                    ['Aufschlag gesamt', `${num.format(eurBase ? feeAmt / eurBase * 100 : 0)} %`],
                ]);
            }
            case 'eigenkapitalquote-rechner': {
                const total = n('equity') + n('debt');
                const ratio = total ? n('equity') / total * 100 : 0;
                return rows([
                    ['Eigenkapitalquote', `${num.format(ratio)} %`],
                    ['Fremdkapitalquote', `${num.format(total ? n('debt') / total * 100 : 0)} %`],
                    ['Bilanzsumme', euro.format(total)],
                    ['Eigenkapital', euro.format(n('equity'))],
                    ['Fremdkapital', euro.format(n('debt'))],
                    ['Verschuldungsgrad', `${num.format(n('equity') ? n('debt') / n('equity') * 100 : 0)} %`],
                    ['Einordnung', ratio >= 30 ? 'solide' : ratio >= 15 ? 'mittel' : 'niedrig'],
                ]);
            }
            case 'cashflow-rechner': {
                const cf = n('income') - n('expenses');
                const rate = n('income') ? cf / n('income') * 100 : 0;
                return rows([
                    ['Monats-Cashflow', euro.format(cf)],
                    ['Jahres-Cashflow', euro.format(cf * 12)],
                    ['Sparquote', `${num.format(rate)} %`],
                    ['Einnahmen pro Monat', euro.format(n('income'))],
                    ['Ausgaben pro Monat', euro.format(n('expenses'))],
                    ['Überschuss pro Tag', euro.format(cf / 30)],
                    ['Ausgabenquote', `${num.format(n('income') ? n('expenses') / n('income') * 100 : 0)} %`],
                    ['Status', cf > 0 ? 'positiv' : cf < 0 ? 'negativ' : 'ausgeglichen'],
                ]);
            }
            case 'roi-rechner': {
                const roi = n('cost') ? n('gain') / n('cost') * 100 : 0;
                return rows([
                    ['ROI', `${num.format(roi)} %`],
                    ['Gewinn', euro.format(n('gain'))],
                    ['Investition', euro.format(n('cost'))],
                    ['Gesamtwert', euro.format(n('cost') + n('gain'))],
                    ['Gewinn je 100 Euro Einsatz', euro.format(n('cost') ? n('gain') / n('cost') * 100 : 0)],
                    ['Faktor', `${num.format(n('cost') ? (n('cost') + n('gain')) / n('cost') : 0)} x`],
                    ['Einordnung', roi >= 100 ? 'sehr hoch' : roi >= 20 ? 'gut' : roi >= 0 ? 'positiv' : 'Verlust'],
                ]);
            }
            case 'wasserkosten-rechner': {
                const water = n('usage') * n('price');
                const sewer = n('usage') * n('waste');
                const total = water + sewer + n('base');
                return rows([
                    ['Jahreskosten', euro.format(total)],
                    ['Kosten pro Monat', euro.format(total / 12)],
                    ['Frischwasser', euro.format(water)],
                    ['Abwasser', euro.format(sewer)],
                    ['Grundgebühr', euro.format(n('base'))],
                    ['Kosten pro m3 gesamt', euro.format(n('usage') ? total / n('usage') : 0)],
                    ['Kosten pro Tag', euro.format(total / 365)],
                    ['Verbrauch pro Tag', `${num.format(n('usage') * 1000 / 365)} Liter`],
                ]);
            }
            case 'standby-kosten-rechner': {
                const kwhYear = n('watt') / 1000 * 24 * 365 * n('devices');
                const cost = kwhYear * n('price');
                return rows([
                    ['Kosten pro Jahr', euro.format(cost)],
                    ['Kosten pro Monat', euro.format(cost / 12)],
                    ['kWh pro Jahr', `${num.format(kwhYear)} kWh`],
                    ['Kosten pro Gerät/Jahr', euro.format(n('devices') ? cost / n('devices') : 0)],
                    ['Standby-Leistung gesamt', `${num.format(n('watt') * n('devices'))} W`],
                    ['Kosten pro Tag', euro.format(cost / 365)],
                    ['CO2 pro Jahr (0,38 kg/kWh)', `${num.format(kwhYear * 0.38)} kg`],
                ]);
            }
            case 'led-sparrechner': {
                const oldKwh = n('oldW') / 1000 * n('hours') * 365 * n('count');
                const ledKwh = n('ledW') / 1000 * n('hours') * 365 * n('count');
                const saveKwh = oldKwh - ledKwh;
                const save = saveKwh * n('price');
                return rows([
                    ['Ersparnis pro Jahr', euro.format(save)],
                    ['Eingesparte kWh pro Jahr', `${num.format(saveKwh)} kWh`],
                    ['Ersparnis pro Monat', euro.format(save / 12)],
                    ['Alte Kosten pro Jahr', euro.format(oldKwh * n('price'))],
                    ['LED Kosten pro Jahr', euro.format(ledKwh * n('price'))],
                    ['Ersparnis in Prozent', `${num.format(oldKwh ? saveKwh / oldKwh * 100 : 0)} %`],
                    ['Ersparnis pro Lampe/Jahr', euro.format(n('count') ? save / n('count') : 0)],
                    ['Ersparnis über 10 Jahre', euro.format(save * 10)],
                ]);
            }
            case 'akku-ladekosten-rechner': {
                const perCharge = n('capacity') * (1 + n('loss') / 100) * n('price');
                const month = perCharge * n('charges');
                return rows([
                    ['Kosten pro Ladung', euro.format(perCharge)],
                    ['Kosten pro Monat', euro.format(month)],
                    ['Kosten pro Jahr', euro.format(month * 12)],
                    ['Energie pro Ladung', `${num.format(n('capacity') * (1 + n('loss') / 100))} kWh`],
                    ['Nutzkapazitaet', `${num.format(n('capacity'))} kWh`],
                    ['Verlust pro Ladung', euro.format(n('capacity') * n('loss') / 100 * n('price'))],
                    ['Ladungen pro Jahr', num.format(n('charges') * 12)],
                    ['Kosten je kWh nutzbar', euro.format(n('price') * (1 + n('loss') / 100))],
                ]);
            }
            case 'regenwasser-rechner': {
                const liters = n('area') * n('rain') * n('eff') / 100;
                const m3 = liters / 1000;
                return rows([
                    ['Regenwasser pro Jahr', `${num.format(liters)} Liter`],
                    ['Regenwasser pro Jahr', `${num.format(m3)} m3`],
                    ['Pro Monat', `${num.format(liters / 12)} Liter`],
                    ['Pro Tag', `${num.format(liters / 365)} Liter`],
                    ['Ersparnis bei 4 Euro/m3', euro.format(m3 * 4)],
                    ['Fuellungen 200L Tonne', num.format(liters / 200)],
                    ['Dachfläche', `${num.format(n('area'))} m2`],
                    ['Effizienz', `${num.format(n('eff'))} %`],
                ]);
            }
            case 'heizoel-rechner': {
                const cost = n('liters') * n('price');
                const kwh = n('liters') * 10;
                return rows([
                    ['Gesamtkosten', euro.format(cost)],
                    ['Energieinhalt', `${num.format(kwh)} kWh`],
                    ['Preis pro kWh', euro.format(kwh ? cost / kwh : 0)],
                    ['Kosten pro Monat (Schnitt)', euro.format(cost / 12)],
                    ['Kosten pro 100 Liter', euro.format(100 * n('price'))],
                    ['Menge', `${num.format(n('liters'))} Liter`],
                    ['CO2 (2,66 kg/Liter)', `${num.format(n('liters') * 2.66)} kg`],
                ]);
            }
            case 'pellets-heizkosten-rechner': {
                const cost = n('kg') / 1000 * n('price');
                const kwh = n('kg') * 4.8;
                return rows([
                    ['Gesamtkosten', euro.format(cost)],
                    ['Energieinhalt', `${num.format(kwh)} kWh`],
                    ['Preis pro kWh', euro.format(kwh ? cost / kwh : 0)],
                    ['Kosten pro Monat (Schnitt)', euro.format(cost / 12)],
                    ['Preis pro kg', euro.format(n('price') / 1000)],
                    ['Menge', `${num.format(n('kg'))} kg`],
                    ['Menge in Tonnen', `${num.format(n('kg') / 1000)} t`],
                ]);
            }
            case 'waschmaschine-kosten-rechner': {
                const perLoad = n('power') * n('priceKwh') + n('water') / 1000 * n('priceWater');
                const week = perLoad * n('loads');
                const year = week * 52;
                return rows([
                    ['Kosten pro Waschgang', euro.format(perLoad)],
                    ['Kosten pro Woche', euro.format(week)],
                    ['Kosten pro Monat', euro.format(year / 12)],
                    ['Kosten pro Jahr', euro.format(year)],
                    ['Stromanteil pro Waschgang', euro.format(n('power') * n('priceKwh'))],
                    ['Wasseranteil pro Waschgang', euro.format(n('water') / 1000 * n('priceWater'))],
                    ['Waschgaenge pro Jahr', num.format(n('loads') * 52)],
                    ['Wasser pro Jahr', `${num.format(n('water') * n('loads') * 52 / 1000)} m3`],
                ]);
            }
            case 'poolheizung-rechner': {
                const energy = n('volume') * n('tempRise') * 1.163;
                const electrical = energy / Math.max(0.1, n('cop'));
                const cost = electrical * n('price');
                return rows([
                    ['Stromkosten Aufheizen', euro.format(cost)],
                    ['Thermische Energie', `${num.format(energy)} kWh`],
                    ['Stromverbrauch', `${num.format(electrical)} kWh`],
                    ['Temperaturerhoehung', `${num.format(n('tempRise'))} Grad C`],
                    ['Wassermenge', `${num.format(n('volume'))} m3 / ${num.format(n('volume') * 1000)} Liter`],
                    ['Kosten pro Grad', euro.format(n('tempRise') ? cost / n('tempRise') : 0)],
                    ['Ohne Wärmepumpe (COP 1)', euro.format(energy * n('price'))],
                    ['Effizienz / COP', num.format(n('cop'))],
                ]);
            }
            case 'luftentfeuchter-kosten-rechner': {
                const kwhMonth = n('watt') / 1000 * n('hours') * n('days');
                const cost = kwhMonth * n('price');
                return rows([
                    ['Kosten pro Monat', euro.format(cost)],
                    ['Kosten pro Jahr', euro.format(cost * 12)],
                    ['kWh pro Monat', `${num.format(kwhMonth)} kWh`],
                    ['Kosten pro Tag', euro.format(n('watt') / 1000 * n('hours') * n('price'))],
                    ['Kosten pro Stunde', euro.format(n('watt') / 1000 * n('price'))],
                    ['Saison 4 Monate', euro.format(cost * 4)],
                    ['Leistung', `${num.format(n('watt'))} W`],
                    ['Laufzeit pro Monat', `${num.format(n('hours') * n('days'))} h`],
                ]);
            }
            case 'trinkmenge-rechner': {
                const base = n('weight') * 35;
                const sportMl = n('sport') / 30 * 500;
                const total = (base + sportMl) * (1 + n('heat') / 100);
                return rows([
                    ['Empfohlene Trinkmenge', `${num.format(total / 1000)} Liter`],
                    ['In Milliliter', `${num.format(total)} ml`],
                    ['Glaeser a 250 ml', num.format(total / 250)],
                    ['Grundbedarf', `${num.format(base)} ml`],
                    ['Zuschlag Sport', `${num.format(sportMl)} ml`],
                    ['Zuschlag Hitze', `${num.format(total - base - sportMl)} ml`],
                    ['Pro Wachstunde (16h)', `${num.format(total / 16)} ml`],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'grundumsatz-rechner': {
                const sex = value('sex');
                const bmr = 10 * n('weight') + 6.25 * n('height') - 5 * n('age') + (sex === 'w' ? -161 : 5);
                const total = bmr * Math.max(1, n('pal'));
                return rows([
                    ['Grundumsatz (BMR)', `${num.format(bmr)} kcal`],
                    ['Gesamtumsatz', `${num.format(total)} kcal`],
                    ['Zum Abnehmen (-500)', `${num.format(total - 500)} kcal`],
                    ['Zum Zunehmen (+500)', `${num.format(total + 500)} kcal`],
                    ['Grundumsatz pro Stunde', `${num.format(bmr / 24)} kcal`],
                    ['Grundumsatz in kJ', `${num.format(bmr * 4.184)} kJ`],
                    ['Aktivitaetsfaktor', num.format(n('pal'))],
                    ['Geschlecht', sex === 'w' ? 'weiblich' : 'maennlich'],
                ]);
            }
            case 'idealgewicht-rechner': {
                const sex = value('sex');
                const broca = (n('height') - 100) * (sex === 'w' ? 0.85 : 0.9);
                const normal = n('height') - 100;
                const m = n('height') / 100;
                return rows([
                    ['Idealgewicht (Broca)', `${num.format(broca)} kg`],
                    ['Normalgewicht (Broca)', `${num.format(normal)} kg`],
                    ['BMI-Normbereich von', `${num.format(18.5 * m * m)} kg`],
                    ['BMI-Normbereich bis', `${num.format(25 * m * m)} kg`],
                    ['BMI 22 (Mitte)', `${num.format(22 * m * m)} kg`],
                    ['Körpergröße', `${num.format(n('height'))} cm`],
                    ['Geschlecht', sex === 'w' ? 'weiblich' : 'maennlich'],
                ]);
            }
            case 'one-rep-max-rechner': {
                const epley = n('weight') * (1 + n('reps') / 30);
                const brzycki = n('reps') < 37 ? n('weight') * 36 / (37 - n('reps')) : 0;
                const avg = brzycki ? (epley + brzycki) / 2 : epley;
                return rows([
                    ['1RM (Epley)', `${num.format(epley)} kg`],
                    ['1RM (Brzycki)', brzycki ? `${num.format(brzycki)} kg` : 'zu viele Reps'],
                    ['Durchschnitt', `${num.format(avg)} kg`],
                    ['95% (1-2 Reps)', `${num.format(avg * 0.95)} kg`],
                    ['90% (3-4 Reps)', `${num.format(avg * 0.9)} kg`],
                    ['80% (8 Reps)', `${num.format(avg * 0.8)} kg`],
                    ['70% (12 Reps)', `${num.format(avg * 0.7)} kg`],
                    ['60% (Aufwärmen)', `${num.format(avg * 0.6)} kg`],
                ]);
            }
            case 'trainingspuls-rechner': {
                const maxHR = 220 - n('age');
                const reserve = maxHR - n('resting');
                const lowHR = n('resting') + reserve * n('low') / 100;
                const highHR = n('resting') + reserve * n('high') / 100;
                return rows([
                    ['Trainingszone', `${num.format(lowHR)} - ${num.format(highHR)} bpm`],
                    ['Maximalpuls', `${num.format(maxHR)} bpm`],
                    ['Herzfrequenzreserve', `${num.format(reserve)} bpm`],
                    ['Fettverbrennung 60-70%', `${num.format(n('resting') + reserve * 0.6)} - ${num.format(n('resting') + reserve * 0.7)} bpm`],
                    ['Ausdauer 70-80%', `${num.format(n('resting') + reserve * 0.7)} - ${num.format(n('resting') + reserve * 0.8)} bpm`],
                    ['Intensiv 80-90%', `${num.format(n('resting') + reserve * 0.8)} - ${num.format(n('resting') + reserve * 0.9)} bpm`],
                    ['Untere Grenze', `${num.format(lowHR)} bpm`],
                    ['Obere Grenze', `${num.format(highHR)} bpm`],
                ]);
            }
            case 'waist-height-ratio-rechner': {
                const ratio = n('height') ? n('waist') / n('height') : 0;
                const cat = ratio < 0.4 ? 'sehr schlank' : ratio < 0.5 ? 'gesund' : ratio < 0.6 ? 'erhoeht' : 'hoch';
                return rows([
                    ['Waist-Height-Ratio', num.format(ratio)],
                    ['Einordnung', cat],
                    ['Taillenumfang', `${num.format(n('waist'))} cm`],
                    ['Körpergröße', `${num.format(n('height'))} cm`],
                    ['Grenzwert 0,5 entspricht', `${num.format(n('height') * 0.5)} cm Taille`],
                    ['Differenz zu 0,5', `${num.format((0.5 - ratio) * n('height'))} cm`],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'proteinbedarf-rechner': {
                const total = n('weight') * n('goal');
                return rows([
                    ['Proteinbedarf pro Tag', `${num.format(total)} g`],
                    ['Pro Mahlzeit (4)', `${num.format(total / 4)} g`],
                    ['Energie aus Protein', `${num.format(total * 4)} kcal`],
                    ['Minimum (0,8 g/kg)', `${num.format(n('weight') * 0.8)} g`],
                    ['Sportler (1,6 g/kg)', `${num.format(n('weight') * 1.6)} g`],
                    ['Muskelaufbau (2,0 g/kg)', `${num.format(n('weight') * 2)} g`],
                    ['entspricht Eiern (6g/Stk)', num.format(total / 6)],
                    ['Ziel', `${num.format(n('goal'))} g/kg`],
                ]);
            }
            case 'kalorienverbrauch-sport-rechner': {
                const perMin = n('met') * 3.5 * n('weight') / 200;
                const total = perMin * n('minutes');
                return rows([
                    ['Kalorienverbrauch', `${num.format(total)} kcal`],
                    ['Pro Minute', `${num.format(perMin)} kcal`],
                    ['Pro Stunde', `${num.format(perMin * 60)} kcal`],
                    ['In Kilojoule', `${num.format(total * 4.184)} kJ`],
                    ['Fettaequivalent (9 kcal/g)', `${num.format(total / 9)} g`],
                    ['MET-Wert', num.format(n('met'))],
                    ['Dauer', `${num.format(n('minutes'))} min`],
                    ['entspricht Schritten grob', num.format(total / 0.04)],
                ]);
            }
            case 'blutzucker-umrechner': {
                const unit = value('unit');
                const mgdl = unit === 'mmol' ? n('value') * 18.0182 : n('value');
                const mmol = unit === 'mmol' ? n('value') : n('value') / 18.0182;
                const cat = mgdl < 70 ? 'niedrig' : mgdl < 100 ? 'normal (nuechtern)' : mgdl < 126 ? 'erhoeht' : 'hoch';
                return rows([
                    ['mg/dL', num.format(mgdl)],
                    ['mmol/L', num.format(mmol)],
                    ['Einordnung nuechtern', cat],
                    ['Eingabe', `${num.format(n('value'))} ${unit === 'mmol' ? 'mmol/L' : 'mg/dL'}`],
                    ['Umrechnungsfaktor', '18,0182'],
                    ['Normal nuechtern', '70 - 99 mg/dL'],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'cholesterin-umrechner': {
                const unit = value('unit');
                const mgdl = unit === 'mmol' ? n('value') * 38.67 : n('value');
                const mmol = unit === 'mmol' ? n('value') : n('value') / 38.67;
                const cat = mgdl < 200 ? 'wuenschenswert' : mgdl < 240 ? 'grenzwertig' : 'hoch';
                return rows([
                    ['mg/dL', num.format(mgdl)],
                    ['mmol/L', num.format(mmol)],
                    ['Einordnung Gesamtcholesterin', cat],
                    ['Eingabe', `${num.format(n('value'))} ${unit === 'mmol' ? 'mmol/L' : 'mg/dL'}`],
                    ['Umrechnungsfaktor', '38,67'],
                    ['Wuenschenswert', 'unter 200 mg/dL'],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'schritte-distanz-rechner': {
                const stride = n('height') * 0.414;
                const meters = n('steps') * stride / 100;
                const km = meters / 1000;
                const kcal = n('steps') * 0.04;
                return rows([
                    ['Distanz', `${num.format(km)} km`],
                    ['In Meter', `${num.format(meters)} m`],
                    ['Schrittlänge', `${num.format(stride)} cm`],
                    ['Kalorien (grob)', `${num.format(kcal)} kcal`],
                    ['Gehzeit bei 5 km/h', duration(km / 5 * 3600)],
                    ['Schritte', num.format(n('steps'))],
                    ['Bis 10.000 Schritte', num.format(Math.max(0, 10000 - n('steps')))],
                    ['10.000 Schritte sind', `${num.format(10000 * stride / 100000)} km`],
                ]);
            }
            case 'schwimm-pace-rechner': {
                const totalSec = n('minutes') * 60 + n('seconds');
                const pace100 = n('distance') ? totalSec / (n('distance') / 100) : 0;
                const speed = totalSec ? n('distance') / totalSec : 0;
                const mmss = s => `${Math.floor(s / 60)}:${String(Math.round(s % 60)).padStart(2, '0')}`;
                return rows([
                    ['Pace pro 100 m', `${mmss(pace100)} min`],
                    ['Geschwindigkeit', `${num.format(speed)} m/s`],
                    ['Geschwindigkeit', `${num.format(speed * 3.6)} km/h`],
                    ['Pace pro 50 m', `${mmss(pace100 / 2)} min`],
                    ['Gesamtzeit', `${mmss(totalSec)} min`],
                    ['Strecke', `${num.format(n('distance'))} m`],
                    ['Bahnen a 25 m', num.format(n('distance') / 25)],
                    ['Bahnen a 50 m', num.format(n('distance') / 50)],
                ]);
            }
            case 'reis-wasser-rechner': {
                const sorte = value('sorte');
                const data = { weiss: [1.5, '12-15 min'], basmati: [1.4, '10-12 min'], vollkorn: [2.0, '35-45 min'], parboiled: [1.6, '12-15 min'] }[sorte] || [1.5, '12-15 min'];
                const water = n('rice') * data[0];
                return rows([
                    ['Wassermenge', `${num.format(water)} ml`],
                    ['Reismenge', `${num.format(n('rice'))} g`],
                    ['Verhältnis Reis:Wasser', `1 : ${num.format(data[0])}`],
                    ['Kochzeit', data[1]],
                    ['Portionen (60 g roh)', num.format(n('rice') / 60)],
                    ['Gekocht ca.', `${num.format(n('rice') * 3)} g`],
                    ['Wasser pro Portion', `${num.format(n('rice') / 60 ? water / (n('rice') / 60) : 0)} ml`],
                    ['Sorte', sorte],
                ]);
            }
            case 'pasta-portionen-rechner': {
                const role = value('role');
                const perPerson = { haupt: 125, beilage: 60, kinder: 70 }[role] || 125;
                const total = perPerson * n('persons');
                const waterL = total / 100;
                const salt = waterL * 10;
                return rows([
                    ['Nudeln gesamt', `${num.format(total)} g`],
                    ['Pro Person', `${num.format(perPerson)} g`],
                    ['Wasser', `${num.format(waterL)} Liter`],
                    ['Salz', `${num.format(salt)} g`],
                    ['Personen', num.format(n('persons'))],
                    ['Topf mindestens', `${num.format(Math.max(3, waterL * 1.3))} Liter`],
                    ['Nudeln in kg', `${num.format(total / 1000)} kg`],
                    ['Faustregel', '100 g Nudeln je 1 L Wasser + 10 g Salz'],
                ]);
            }
            case 'ofen-umluft-rechner': {
                const umluft = n('temp') - 20;
                const gas = Math.max(1, Math.min(10, Math.round((n('temp') - 110) / 14)));
                return rows([
                    ['Umluft', `${num.format(umluft)} Grad C`],
                    ['Ober-/Unterhitze', `${num.format(n('temp'))} Grad C`],
                    ['Heissluft', `${num.format(umluft)} Grad C`],
                    ['Gasstufe (ca.)', `Stufe ${num.format(gas)}`],
                    ['Differenz', '-20 Grad C bei Umluft'],
                    ['Tipp', 'Umluft heizt schneller und trockener'],
                ]);
            }
            case 'hefe-umrechner': {
                const from = value('from');
                const dry = from === 'frisch' ? n('amount') / 3 : n('amount');
                const fresh = from === 'frisch' ? n('amount') : n('amount') * 3;
                const flour = fresh / 42 * 500;
                return rows([
                    ['Frische Hefe', `${num.format(fresh)} g`],
                    ['Trockenhefe', `${num.format(dry)} g`],
                    ['Wuerfel frische Hefe (42 g)', num.format(fresh / 42)],
                    ['Paeckchen Trockenhefe (7 g)', num.format(dry / 7)],
                    ['Passend für Mehl', `${num.format(flour)} g`],
                    ['Verhältnis', '1 g Trockenhefe = 3 g frische Hefe'],
                    ['Eingabe', `${num.format(n('amount'))} g ${from === 'frisch' ? 'frisch' : 'trocken'}`],
                ]);
            }
            case 'cocktail-mengen-rechner': {
                const drinks = n('guests') * n('hours') * n('drinksPerHour');
                const spirit = drinks * 0.05;
                const filler = drinks * 0.15;
                const ice = drinks * 0.2;
                return rows([
                    ['Drinks gesamt', num.format(drinks)],
                    ['Spirituosen', `${num.format(spirit)} Liter`],
                    ['Flaschen a 0,7 L', num.format(spirit / 0.7)],
                    ['Filler / Saft', `${num.format(filler)} Liter`],
                    ['Eis', `${num.format(ice)} kg`],
                    ['Glaeser einplanen', num.format(Math.ceil(n('guests') * 1.5))],
                    ['Drinks pro Stunde', num.format(n('guests') * n('drinksPerHour'))],
                    ['Drinks pro Gast', num.format(n('hours') * n('drinksPerHour'))],
                ]);
            }
            case 'salz-kochwasser-rechner': {
                const salt = n('water') * n('gramsPerLiter');
                return rows([
                    ['Salzmenge', `${num.format(salt)} g`],
                    ['Teeloeffel (ca. 6 g)', num.format(salt / 6)],
                    ['Essloeffel (ca. 18 g)', num.format(salt / 18)],
                    ['Salz pro Liter', `${num.format(n('gramsPerLiter'))} g`],
                    ['Wassermenge', `${num.format(n('water'))} Liter`],
                    ['Empfehlung Pasta', `${num.format(n('water') * 10)} g (10 g/L)`],
                    ['Salzgehalt', `${num.format(n('water') ? salt / (n('water') * 1000) * 100 : 0)} %`],
                ]);
            }
            case 'zucker-honig-umrechner': {
                const from = value('from');
                const honey = from === 'zucker' ? n('amount') * 0.8 : n('amount');
                const sugar = from === 'zucker' ? n('amount') : n('amount') * 1.25;
                const lessLiquid = honey * 0.2;
                return rows([
                    ['Zucker', `${num.format(sugar)} g`],
                    ['Honig', `${num.format(honey)} g`],
                    ['Fluessigkeit reduzieren um', `${num.format(lessLiquid)} ml`],
                    ['Backtemperatur', 'ca. 10 Grad C niedriger'],
                    ['Verhältnis', '100 g Zucker = ca. 80 g Honig'],
                    ['Eingabe', `${num.format(n('amount'))} g ${from === 'zucker' ? 'Zucker' : 'Honig'}`],
                    ['Hinweis', 'Honig suesst stärker und braeunt schneller'],
                ]);
            }
            case 'butter-oel-umrechner': {
                const from = value('from');
                const oil = from === 'butter' ? n('amount') * 0.8 : n('amount');
                const butter = from === 'butter' ? n('amount') : n('amount') * 1.25;
                return rows([
                    ['Butter', `${num.format(butter)} g`],
                    ['Oel', `${num.format(oil)} g`],
                    ['Oel in Milliliter', `${num.format(oil / 0.92)} ml`],
                    ['Verhältnis', '100 g Butter = ca. 80 g Oel'],
                    ['Eingabe', `${num.format(n('amount'))} g ${from === 'butter' ? 'Butter' : 'Oel'}`],
                    ['Tipp', 'Bei Oel ggf. etwas Fluessigkeit reduzieren'],
                ]);
            }
            case 'eiswuerfel-party-rechner': {
                const ice = n('guests') * (0.3 + n('hours') * 0.1);
                const cubes = ice * 50;
                return rows([
                    ['Eismenge gesamt', `${num.format(ice)} kg`],
                    ['Beutel a 2 kg', num.format(Math.ceil(ice / 2))],
                    ['Eiswuerfel (ca. 50/kg)', num.format(Math.round(cubes))],
                    ['Pro Gast', `${num.format(n('guests') ? ice / n('guests') : 0)} kg`],
                    ['Gaeste', num.format(n('guests'))],
                    ['Dauer', `${num.format(n('hours'))} h`],
                    ['Tipp', 'Bei Hitze 50% mehr einplanen'],
                ]);
            }
            case 'teig-hydration-rechner': {
                const hydration = n('flour') ? n('water') / n('flour') * 100 : 0;
                const saltPct = n('flour') ? n('salt') / n('flour') * 100 : 0;
                const dough = n('flour') + n('water') + n('salt');
                const cat = hydration < 60 ? 'fester Teig' : hydration <= 70 ? 'mittel' : hydration <= 80 ? 'weich' : 'sehr weich';
                return rows([
                    ['Hydration', `${num.format(hydration)} %`],
                    ['Salzanteil', `${num.format(saltPct)} %`],
                    ['Teiggewicht', `${num.format(dough)} g`],
                    ['Einordnung', cat],
                    ['Wasser für 65% Hydration', `${num.format(n('flour') * 0.65)} g`],
                    ['Wasser für 70% Hydration', `${num.format(n('flour') * 0.7)} g`],
                    ['Empfohlenes Salz (2%)', `${num.format(n('flour') * 0.02)} g`],
                    ['Mehl', `${num.format(n('flour'))} g`],
                ]);
            }
            case 'fahrtkosten-rechner': {
                const liters = n('distance') * n('consumption') / 100;
                const cost = liters * n('price');
                const persons = Math.max(1, n('persons'));
                return rows([
                    ['Spritkosten einfach', euro.format(cost)],
                    ['Hin und zurueck', euro.format(cost * 2)],
                    ['Kosten pro Person', euro.format(cost / persons)],
                    ['Verbrauchte Liter', `${num.format(liters)} L`],
                    ['Kosten pro km', euro.format(n('distance') ? cost / n('distance') : 0)],
                    ['Pendeln pro Monat (20 Tage)', euro.format(cost * 2 * 20)],
                    ['Pro Person hin und zurueck', euro.format(cost * 2 / persons)],
                    ['Strecke', `${num.format(n('distance'))} km`],
                ]);
            }
            case 'e-auto-kosten-rechner': {
                const per100 = n('consumption') * n('price');
                const cost = n('distance') / 100 * per100;
                const benziner = n('distance') / 100 * 7 * 1.75;
                return rows([
                    ['Kosten für Strecke', euro.format(cost)],
                    ['Kosten pro 100 km', euro.format(per100)],
                    ['Kosten pro km', euro.format(per100 / 100)],
                    ['kWh für Strecke', `${num.format(n('distance') / 100 * n('consumption'))} kWh`],
                    ['Kosten pro Jahr (15.000 km)', euro.format(150 * per100)],
                    ['Benziner-Vergleich (7L)', euro.format(benziner)],
                    ['Ersparnis ggu. Benziner', euro.format(benziner - cost)],
                    ['Strecke', `${num.format(n('distance'))} km`],
                ]);
            }
            case 'ladezeit-eauto-rechner': {
                const netKwh = n('battery') * Math.max(0, n('to') - n('from')) / 100;
                const grossKwh = netKwh * (1 + n('loss') / 100);
                const hours = n('power') ? grossKwh / n('power') : 0;
                return rows([
                    ['Ladezeit', duration(hours * 3600)],
                    ['Ladezeit in Stunden', `${num.format(hours)} h`],
                    ['Geladene Energie (netto)', `${num.format(netKwh)} kWh`],
                    ['Aus dem Netz (brutto)', `${num.format(grossKwh)} kWh`],
                    ['Ladehub', `${num.format(Math.max(0, n('to') - n('from')))} %`],
                    ['Reichweite ca. (5 km/kWh)', `${num.format(netKwh * 5)} km`],
                    ['Ladeverlust', `${num.format(grossKwh - netKwh)} kWh`],
                    ['Ladeleistung', `${num.format(n('power'))} kW`],
                ]);
            }
            case 'bremsweg-rechner': {
                const brake = Math.pow(n('speed') / 10, 2);
                const emergency = brake / 2;
                const reaction = n('speed') / 10 * 3;
                return rows([
                    ['Bremsweg (normal)', `${num.format(brake)} m`],
                    ['Gefahrenbremsung', `${num.format(emergency)} m`],
                    ['Reaktionsweg (1 s)', `${num.format(reaction)} m`],
                    ['Anhalteweg (normal)', `${num.format(reaction + brake)} m`],
                    ['Anhalteweg bei Gefahr', `${num.format(reaction + emergency)} m`],
                    ['Geschwindigkeit', `${num.format(n('speed'))} km/h`],
                    ['Formel Bremsweg', '(km/h : 10)^2'],
                ]);
            }
            case 'anhalteweg-rechner': {
                const reactionDist = n('speed') / 3.6 * n('reaction');
                const brake = Math.pow(n('speed') / 10, 2);
                const stopping = reactionDist + brake;
                return rows([
                    ['Anhalteweg', `${num.format(stopping)} m`],
                    ['Reaktionsweg', `${num.format(reactionDist)} m`],
                    ['Bremsweg', `${num.format(brake)} m`],
                    ['Anhalteweg bei Gefahr', `${num.format(reactionDist + brake / 2)} m`],
                    ['Geschwindigkeit in m/s', `${num.format(n('speed') / 3.6)} m/s`],
                    ['Reaktionszeit', `${num.format(n('reaction'))} s`],
                    ['Geschwindigkeit', `${num.format(n('speed'))} km/h`],
                ]);
            }
            case 'ps-kw-rechner': {
                const unit = value('unit');
                const ps = unit === 'kw' ? n('value') / 0.73549875 : n('value');
                const kw = unit === 'kw' ? n('value') : n('value') * 0.73549875;
                return rows([
                    ['Leistung in PS', `${num.format(ps)} PS`],
                    ['Leistung in kW', `${num.format(kw)} kW`],
                    ['Eingabe', `${num.format(n('value'))} ${unit === 'kw' ? 'kW' : 'PS'}`],
                    ['1 PS', '0,7355 kW'],
                    ['1 kW', '1,3596 PS'],
                    ['Leistung in W', `${num.format(kw * 1000)} W`],
                ]);
            }
            case 'tankreichweite-rechner': {
                const range = n('consumption') ? n('tank') / n('consumption') * 100 : 0;
                const usable = n('consumption') ? Math.max(0, n('tank') - n('reserve')) / n('consumption') * 100 : 0;
                return rows([
                    ['Reichweite (voll)', `${num.format(range)} km`],
                    ['Reichweite bis Reserve', `${num.format(usable)} km`],
                    ['Reserve-Reichweite', `${num.format(range - usable)} km`],
                    ['Tankinhalt', `${num.format(n('tank'))} L`],
                    ['Verbrauch', `${num.format(n('consumption'))} L/100km`],
                    ['Reichweite pro Liter', `${num.format(n('consumption') ? 100 / n('consumption') : 0)} km`],
                    ['Reserve', `${num.format(n('reserve'))} L`],
                ]);
            }
            case 'reifenumfang-rechner': {
                const sidewall = n('width') * n('ratio') / 100;
                const diameter = n('inch') * 25.4 + 2 * sidewall;
                const circumference = diameter * Math.PI;
                const rolling = circumference * 0.97;
                return rows([
                    ['Durchmesser', `${num.format(diameter)} mm`],
                    ['Umfang', `${num.format(circumference)} mm`],
                    ['Umfang', `${num.format(circumference / 1000)} m`],
                    ['Abrollumfang (ca.)', `${num.format(rolling)} mm`],
                    ['Umdrehungen pro km', num.format(rolling ? 1000000 / rolling : 0)],
                    ['Flankenhöhe', `${num.format(sidewall)} mm`],
                    ['Reifengröße', `${num.format(n('width'))}/${num.format(n('ratio'))} R${num.format(n('inch'))}`],
                ]);
            }
            case 'subnetz-rechner': {
                const parts = value('ip').split('.').map(Number);
                if (parts.length !== 4 || parts.some(p => !Number.isInteger(p) || p < 0 || p > 255)) {
                    return rows([['Fehler', 'Bitte eine gültige IPv4-Adresse eingeben']]);
                }
                const cidr = clamp(Math.round(n('cidr')), 0, 32);
                const ipNum = parts.reduce((a, p) => (a * 256 + p), 0);
                const maskNum = cidr === 0 ? 0 : (0xFFFFFFFF << (32 - cidr)) >>> 0;
                const network = (ipNum & maskNum) >>> 0;
                const broadcast = (network | (~maskNum >>> 0)) >>> 0;
                const toOct = num => [num >>> 24 & 255, num >>> 16 & 255, num >>> 8 & 255, num & 255].join('.');
                const totalAddr = Math.pow(2, 32 - cidr);
                const usable = cidr >= 31 ? (cidr === 32 ? 1 : 2) : totalAddr - 2;
                return rows([
                    ['Netzadresse', toOct(network)],
                    ['Broadcast', toOct(broadcast)],
                    ['Subnetzmaske', toOct(maskNum)],
                    ['Erste Host-Adresse', cidr >= 31 ? toOct(network) : toOct((network + 1) >>> 0)],
                    ['Letzte Host-Adresse', cidr >= 31 ? toOct(broadcast) : toOct((broadcast - 1) >>> 0)],
                    ['Nutzbare Hosts', num.format(usable)],
                    ['Adressen gesamt', num.format(totalAddr)],
                    ['Wildcard-Maske', toOct(~maskNum >>> 0)],
                    ['Notation', `${toOct(network)}/${cidr}`],
                ]);
            }
            case 'basic-auth-generator': {
                const creds = `${value('user')}:${value('pass')}`;
                let b64 = '';
                try { b64 = btoa(unescape(encodeURIComponent(creds))); } catch (e) { b64 = ''; }
                return rows([
                    ['Authorization Header', `Basic ${b64}`],
                    ['Base64', b64],
                    ['Klartext', escapeHtml(creds)],
                    ['Benutzername', escapeHtml(value('user'))],
                    ['curl-Beispiel', escapeHtml(`curl -H "Authorization: Basic ${b64}" URL`)],
                    ['Hinweis', 'Base64 ist keine Verschlüsselung, nur HTTPS nutzen'],
                ]);
            }
            case 'nanoid-generator': {
                const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
                const len = clamp(Math.round(n('length')), 1, 64);
                const count = clamp(Math.round(n('count')), 1, 20);
                const gen = () => Array.from({ length: len }, () => alphabet[Math.floor(Math.random() * alphabet.length)]).join('');
                const ids = Array.from({ length: count }, (_, i) => [`ID ${i + 1}`, gen()]);
                return rows([
                    ...ids,
                    ['Länge', num.format(len)],
                    ['Alphabet-Größe', '64 Zeichen'],
                    ['Bei jeder Eingabe neu', 'ja'],
                ]);
            }
            case 'passwort-entropie-rechner': {
                const pool = { l26: 26, d36: 36, u62: 62, s94: 94 }[value('pool')] || 62;
                const len = Math.max(0, Math.round(n('length')));
                const bits = len * Math.log2(pool);
                const log10years = bits * Math.log10(2) - 10 - Math.log10(3.15e7);
                const strength = bits < 28 ? 'sehr schwach' : bits < 36 ? 'schwach' : bits < 60 ? 'mittel' : bits < 128 ? 'stark' : 'sehr stark';
                const crack = log10years < -2 ? 'Sekundenbruchteile' : log10years < 0 ? 'Sekunden bis Minuten' : log10years < 1 ? 'Stunden bis Tage' : `ca. 10^${Math.round(log10years)} Jahre`;
                return rows([
                    ['Entropie', `${num.format(bits)} Bit`],
                    ['Passwortstärke', strength],
                    ['Zeichenraum', `${pool} Zeichen`],
                    ['Länge', num.format(len)],
                    ['Kombinationen', `ca. 10^${num.format(len * Math.log10(pool))}`],
                    ['Crack-Zeit (10 Mrd/s)', crack],
                    ['Empfehlung', bits < 60 ? 'länger oder mehr Zeichenarten' : 'gut'],
                ]);
            }
            case 'color-shades-generator': {
                const parseHex = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const toHex = arr => '#' + arr.map(v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0')).join('');
                const base = parseHex(value('hex'));
                const steps = clamp(Math.round(n('steps')), 2, 6);
                const out = [['Basis', toHex(base)]];
                for (let i = 1; i <= steps; i++) {
                    const t = i / (steps + 1);
                    out.push([`Heller ${i}`, toHex(base.map(v => v + (255 - v) * t))]);
                }
                for (let i = 1; i <= steps; i++) {
                    const t = i / (steps + 1);
                    out.push([`Dunkler ${i}`, toHex(base.map(v => v * (1 - t)))]);
                }
                return rows(out);
            }
            case 'box-shadow-generator': {
                const css = `box-shadow: ${n('x')}px ${n('y')}px ${n('blur')}px ${n('spread')}px rgba(0,0,0,${clamp(n('opacity') / 100, 0, 1)});`;
                return rows([
                    ['CSS', escapeHtml(css)],
                    ['X-Versatz', `${num.format(n('x'))} px`],
                    ['Y-Versatz', `${num.format(n('y'))} px`],
                    ['Weichzeichnen', `${num.format(n('blur'))} px`],
                    ['Spread', `${num.format(n('spread'))} px`],
                    ['Deckkraft', `${num.format(n('opacity'))} %`],
                    ['rgba', `rgba(0,0,0,${num.format(clamp(n('opacity') / 100, 0, 1))})`],
                ]);
            }
            case 'border-radius-generator': {
                const css = `border-radius: ${n('tl')}px ${n('tr')}px ${n('br')}px ${n('bl')}px;`;
                const allSame = n('tl') === n('tr') && n('tr') === n('br') && n('br') === n('bl');
                return rows([
                    ['CSS', escapeHtml(css)],
                    ['Kurzform', allSame ? escapeHtml(`border-radius: ${n('tl')}px;`) : 'nicht möglich (Ecken unterschiedlich)'],
                    ['Oben links', `${num.format(n('tl'))} px`],
                    ['Oben rechts', `${num.format(n('tr'))} px`],
                    ['Unten rechts', `${num.format(n('br'))} px`],
                    ['Unten links', `${num.format(n('bl'))} px`],
                ]);
            }
            case 'text-shadow-generator': {
                const css = `text-shadow: ${n('x')}px ${n('y')}px ${n('blur')}px rgba(0,0,0,${clamp(n('opacity') / 100, 0, 1)});`;
                return rows([
                    ['CSS', escapeHtml(css)],
                    ['X-Versatz', `${num.format(n('x'))} px`],
                    ['Y-Versatz', `${num.format(n('y'))} px`],
                    ['Weichzeichnen', `${num.format(n('blur'))} px`],
                    ['Deckkraft', `${num.format(n('opacity'))} %`],
                ]);
            }
            case 'modular-scale-rechner': {
                const ratio = n('ratio') > 0 ? n('ratio') : 1.25;
                const steps = clamp(Math.round(n('steps')), 1, 8);
                const out = [['Verhältnis', num.format(ratio)], ['Basis (Stufe 0)', `${num.format(n('base'))} px`]];
                for (let i = 1; i <= steps; i++) out.push([`Stufe +${i}`, `${num.format(n('base') * Math.pow(ratio, i))} px`]);
                out.push([`Stufe -1`, `${num.format(n('base') / ratio)} px`]);
                out.push([`Stufe -2`, `${num.format(n('base') / Math.pow(ratio, 2))} px`]);
                return rows(out);
            }
            case 'golden-ratio-rechner': {
                const phi = 1.6180339887;
                if (value('mode') === 'extend') {
                    return rows([
                        ['Wert', `${num.format(n('value'))} px`],
                        ['Nächst größer (x Phi)', `${num.format(n('value') * phi)} px`],
                        ['Nächst kleiner (/ Phi)', `${num.format(n('value') / phi)} px`],
                        ['Zwei Schritte größer', `${num.format(n('value') * phi * phi)} px`],
                        ['Zwei Schritte kleiner', `${num.format(n('value') / phi / phi)} px`],
                        ['Phi', num.format(phi)],
                    ]);
                }
                const larger = n('value') / phi;
                const smaller = n('value') - larger;
                return rows([
                    ['Größerer Teil', `${num.format(larger)} px`],
                    ['Kleinerer Teil', `${num.format(smaller)} px`],
                    ['Gesamt', `${num.format(n('value'))} px`],
                    ['Verhältnis', `${num.format(smaller ? larger / smaller : 0)} (Ziel 1,618)`],
                    ['Aufteilung', `${num.format(n('value') ? larger / n('value') * 100 : 0)} % zu ${num.format(n('value') ? smaller / n('value') * 100 : 0)} %`],
                    ['Phi', num.format(phi)],
                ]);
            }
            case 'line-height-rechner': {
                const lh = n('fontSize') * n('ratio');
                return rows([
                    ['Zeilenhöhe', `${num.format(lh)} px`],
                    ['Faktor (unitless)', num.format(n('ratio'))],
                    ['In em', `${num.format(n('ratio'))} em`],
                    ['Zeilenabstand', `${num.format(lh - n('fontSize'))} px`],
                    ['CSS', escapeHtml(`line-height: ${num.format(n('ratio'))};`)],
                    ['Schriftgröße', `${num.format(n('fontSize'))} px`],
                    ['Empfehlung Fliesstext', '1,4 bis 1,6'],
                ]);
            }
            case 'json-zu-csv-konverter': {
                let data;
                try { data = JSON.parse(value('json')); } catch (e) { return rows([['Fehler', 'Ungültiges JSON']]); }
                if (!Array.isArray(data)) return rows([['Fehler', 'JSON muss ein Array von Objekten sein']]);
                if (!data.length) return rows([['Hinweis', 'Array ist leer']]);
                const keys = [...new Set(data.flatMap(o => (o && typeof o === 'object') ? Object.keys(o) : []))];
                const esc = v => { const s = v === undefined || v === null ? '' : String(v); return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; };
                const lines = [keys.join(','), ...data.map(o => keys.map(k => esc(o ? o[k] : '')).join(','))];
                const csv = lines.join('\n');
                return rows([
                    ['CSV', escapeHtml(csv).replace(/\n/g, '<br>')],
                    ['Zeilen (mit Kopf)', num.format(lines.length)],
                    ['Datensaetze', num.format(data.length)],
                    ['Spalten', num.format(keys.length)],
                    ['Spaltennamen', escapeHtml(keys.join(', '))],
                ]);
            }
            case 'color-mix-rechner': {
                const parseHex = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const toHex = arr => '#' + arr.map(v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0')).join('');
                const c1 = parseHex(value('color1'));
                const c2 = parseHex(value('color2'));
                const t = clamp(n('ratio') / 100, 0, 1);
                const mixed = c1.map((v, i) => v * (1 - t) + c2[i] * t);
                const hex = toHex(mixed);
                return rows([
                    ['Gemischte Farbe (Hex)', hex],
                    ['RGB', `rgb(${mixed.map(v => Math.round(v)).join(', ')})`],
                    ['Farbe 1', toHex(c1)],
                    ['Farbe 2', toHex(c2)],
                    ['Anteil Farbe 2', `${num.format(n('ratio'))} %`],
                    ['Anteil Farbe 1', `${num.format(100 - n('ratio'))} %`],
                ]);
            }
            case 'opacity-hex-rechner': {
                const op = clamp(n('opacity'), 0, 100);
                const alpha = Math.round(op / 100 * 255);
                const hex = alpha.toString(16).padStart(2, '0');
                return rows([
                    ['Hex-Alpha (2-stellig)', hex.toUpperCase()],
                    ['Beispiel 8-stellig', `#000000${hex.toUpperCase()}`],
                    ['Alpha dezimal', num.format(alpha)],
                    ['RGBA-Alpha', num.format(op / 100)],
                    ['Deckkraft', `${num.format(op)} %`],
                    ['CSS', escapeHtml(`opacity: ${num.format(op / 100)};`)],
                ]);
            }
            case 'ctr-rechner': {
                const ctr = n('impressions') ? n('clicks') / n('impressions') * 100 : 0;
                return rows([
                    ['CTR', `${num.format(ctr)} %`],
                    ['Klicks', num.format(n('clicks'))],
                    ['Impressionen', num.format(n('impressions'))],
                    ['Klicks je 1000 Impr.', num.format(n('impressions') ? n('clicks') / n('impressions') * 1000 : 0)],
                    ['Impressionen je Klick', num.format(n('clicks') ? n('impressions') / n('clicks') : 0)],
                    ['Klicks für 5% CTR', num.format(n('impressions') * 0.05)],
                    ['Einordnung', ctr >= 5 ? 'sehr gut' : ctr >= 2 ? 'gut' : ctr >= 1 ? 'okay' : 'niedrig'],
                ]);
            }
            case 'cpc-rechner': {
                const cpc = n('clicks') ? n('spend') / n('clicks') : 0;
                return rows([
                    ['Kosten pro Klick', euro.format(cpc)],
                    ['Klicks', num.format(n('clicks'))],
                    ['Werbekosten', euro.format(n('spend'))],
                    ['Klicks je 100 Euro', num.format(cpc ? 100 / cpc : 0)],
                    ['Kosten für 1000 Klicks', euro.format(cpc * 1000)],
                    ['Kosten pro 10 Klicks', euro.format(cpc * 10)],
                ]);
            }
            case 'cpm-rechner': {
                const cpm = n('impressions') ? n('spend') / n('impressions') * 1000 : 0;
                return rows([
                    ['CPM / TKP', euro.format(cpm)],
                    ['Kosten', euro.format(n('spend'))],
                    ['Impressionen', num.format(n('impressions'))],
                    ['Kosten je Impression', euro.format(n('impressions') ? n('spend') / n('impressions') : 0)],
                    ['Impressionen je 100 Euro', num.format(cpm ? 100 / cpm * 1000 : 0)],
                    ['Reichweite für 1000 Euro', num.format(cpm ? 1000 / cpm * 1000 : 0)],
                ]);
            }
            case 'conversion-rate-rechner': {
                const cr = n('visitors') ? n('conversions') / n('visitors') * 100 : 0;
                return rows([
                    ['Conversion Rate', `${num.format(cr)} %`],
                    ['Conversions', num.format(n('conversions'))],
                    ['Besucher', num.format(n('visitors'))],
                    ['Besucher je Conversion', num.format(n('conversions') ? n('visitors') / n('conversions') : 0)],
                    ['Conversions bei 10.000 Besuchern', num.format(cr * 100)],
                    ['Einordnung', cr >= 5 ? 'sehr gut' : cr >= 2 ? 'gut' : cr >= 1 ? 'okay' : 'niedrig'],
                ]);
            }
            case 'cac-rechner': {
                const total = n('marketing') + n('sales');
                const cac = n('customers') ? total / n('customers') : 0;
                return rows([
                    ['CAC pro Neukunde', euro.format(cac)],
                    ['Gesamtkosten', euro.format(total)],
                    ['Neukunden', num.format(n('customers'))],
                    ['Marketingkosten', euro.format(n('marketing'))],
                    ['Vertriebskosten', euro.format(n('sales'))],
                    ['Neukunden je 1000 Euro', num.format(cac ? 1000 / cac : 0)],
                ]);
            }
            case 'ltv-rechner': {
                const revenue = n('avgOrder') * n('frequency') * n('years');
                const ltv = revenue * n('margin') / 100;
                return rows([
                    ['LTV (Deckungsbeitrag)', euro.format(ltv)],
                    ['Umsatz gesamt', euro.format(revenue)],
                    ['Umsatz pro Jahr', euro.format(n('avgOrder') * n('frequency'))],
                    ['Deckungsbeitrag pro Jahr', euro.format(n('avgOrder') * n('frequency') * n('margin') / 100)],
                    ['Kaeufe gesamt', num.format(n('frequency') * n('years'))],
                    ['Max. sinnvoller CAC (1/3)', euro.format(ltv / 3)],
                    ['Marge', `${num.format(n('margin'))} %`],
                ]);
            }
            case 'engagement-rate-rechner': {
                const er = n('followers') ? n('interactions') / n('followers') * 100 : 0;
                return rows([
                    ['Engagement Rate', `${num.format(er)} %`],
                    ['Interaktionen', num.format(n('interactions'))],
                    ['Follower', num.format(n('followers'))],
                    ['Interaktionen je 1000 Follower', num.format(n('followers') ? n('interactions') / n('followers') * 1000 : 0)],
                    ['Einordnung', er >= 6 ? 'sehr hoch' : er >= 3 ? 'gut' : er >= 1 ? 'durchschnittlich' : 'niedrig'],
                    ['Ziel 3% braucht Interaktionen', num.format(n('followers') * 0.03)],
                ]);
            }
            case 'ppc-budget-rechner': {
                const clicks = n('cpc') ? n('budget') / n('cpc') : 0;
                const conv = clicks * n('cr') / 100;
                const cpa = conv ? n('budget') / conv : 0;
                return rows([
                    ['Klicks pro Tag', num.format(clicks)],
                    ['Conversions pro Tag', num.format(conv)],
                    ['Kosten pro Conversion (CPA)', euro.format(cpa)],
                    ['Klicks pro Monat', num.format(clicks * 30)],
                    ['Conversions pro Monat', num.format(conv * 30)],
                    ['Budget pro Monat', euro.format(n('budget') * 30)],
                    ['CPC', euro.format(n('cpc'))],
                ]);
            }
            case 'lesezeit-rechner': {
                const wpm = n('wpm') > 0 ? n('wpm') : 200;
                const minutes = n('words') / wpm;
                const fmt = m => `${Math.floor(m)}:${String(Math.round((m % 1) * 60)).padStart(2, '0')} min`;
                return rows([
                    ['Lesezeit', fmt(minutes)],
                    ['Lesezeit in Minuten', num.format(minutes)],
                    ['Sprechzeit (130 WpM)', fmt(n('words') / 130)],
                    ['Schnell-Leser (300 WpM)', fmt(n('words') / 300)],
                    ['Wortanzahl', num.format(n('words'))],
                    ['Woerter pro Minute', num.format(wpm)],
                    ['Zeichen ca. (5,5/Wort)', num.format(n('words') * 5.5)],
                ]);
            }
            case 'follower-wachstum-rechner': {
                const future = n('current') * Math.pow(1 + n('rate') / 100, n('months'));
                const growth = future - n('current');
                const doubling = n('rate') > 0 ? Math.log(2) / Math.log(1 + n('rate') / 100) : 0;
                return rows([
                    [`Follower nach ${num.format(n('months'))} Monaten`, num.format(future)],
                    ['Zuwachs gesamt', num.format(growth)],
                    ['Zuwachs in Prozent', `${num.format(n('current') ? growth / n('current') * 100 : 0)} %`],
                    ['Durchschnitt pro Monat', num.format(growth / Math.max(1, n('months')))],
                    ['Zuwachs erster Monat', num.format(n('current') * n('rate') / 100)],
                    ['Verdopplungszeit', doubling ? `${num.format(doubling)} Monate` : '-'],
                    ['Wachstum pro Monat', `${num.format(n('rate'))} %`],
                ]);
            }
            case 'quadratische-gleichung-rechner': {
                const a = n('a'), b = n('b'), c = n('c');
                if (a === 0) {
                    if (b === 0) return rows([['Hinweis', c === 0 ? 'unendlich viele Lösungen' : 'keine Lösung']]);
                    return rows([
                        ['Gleichungstyp', 'linear (a = 0)'],
                        ['Lösung x', num.format(-c / b)],
                        ['b', num.format(b)],
                        ['c', num.format(c)],
                    ]);
                }
                const disc = b * b - 4 * a * c;
                const vertexX = -b / (2 * a);
                const vertexY = a * vertexX * vertexX + b * vertexX + c;
                if (disc < 0) {
                    const re = -b / (2 * a);
                    const im = Math.sqrt(-disc) / (2 * a);
                    return rows([
                        ['Diskriminante', num.format(disc)],
                        ['Lösungen', 'keine reelle Lösung'],
                        ['x1 (komplex)', `${num.format(re)} + ${num.format(Math.abs(im))}i`],
                        ['x2 (komplex)', `${num.format(re)} - ${num.format(Math.abs(im))}i`],
                        ['Scheitelpunkt', `(${num.format(vertexX)} | ${num.format(vertexY)})`],
                    ]);
                }
                const sq = Math.sqrt(disc);
                const x1 = (-b + sq) / (2 * a);
                const x2 = (-b - sq) / (2 * a);
                return rows([
                    ['x1', num.format(x1)],
                    ['x2', num.format(x2)],
                    ['Diskriminante', num.format(disc)],
                    ['Anzahl Lösungen', disc === 0 ? '1 (doppelt)' : '2'],
                    ['Scheitelpunkt', `(${num.format(vertexX)} | ${num.format(vertexY)})`],
                    ['Summe der Lösungen', num.format(x1 + x2)],
                    ['Produkt der Lösungen', num.format(x1 * x2)],
                ]);
            }
            case 'dreieck-rechner': {
                const a = n('a'), b = n('b');
                const c = Math.sqrt(a * a + b * b);
                const area = a * b / 2;
                const angleA = Math.atan2(a, b) * 180 / Math.PI;
                return rows([
                    ['Hypotenuse c', num.format(c)],
                    ['Fläche', num.format(area)],
                    ['Umfang', num.format(a + b + c)],
                    ['Winkel bei a', `${num.format(angleA)} Grad`],
                    ['Winkel bei b', `${num.format(90 - angleA)} Grad`],
                    ['Rechter Winkel', '90 Grad'],
                    ['Höhe auf c', num.format(c ? a * b / c : 0)],
                    ['Kathete a', num.format(a)],
                    ['Kathete b', num.format(b)],
                ]);
            }
            case 'kreis-rechner': {
                const r = n('radius');
                return rows([
                    ['Fläche', num.format(Math.PI * r * r)],
                    ['Umfang', num.format(2 * Math.PI * r)],
                    ['Durchmesser', num.format(2 * r)],
                    ['Radius', num.format(r)],
                    ['Fläche (r2 x Pi)', `${num.format(r * r)} x Pi`],
                    ['Viertelkreis-Fläche', num.format(Math.PI * r * r / 4)],
                    ['Bogen pro Grad', num.format(2 * Math.PI * r / 360)],
                ]);
            }
            case 'zylinder-volumen-rechner': {
                const r = n('radius'), h = n('height');
                const vol = Math.PI * r * r * h;
                const mantel = 2 * Math.PI * r * h;
                const surface = 2 * Math.PI * r * (r + h);
                return rows([
                    ['Volumen', `${num.format(vol)} cm3`],
                    ['Volumen in Liter', `${num.format(vol / 1000)} L`],
                    ['Mantelfläche', `${num.format(mantel)} cm2`],
                    ['Oberfläche', `${num.format(surface)} cm2`],
                    ['Grundfläche', `${num.format(Math.PI * r * r)} cm2`],
                    ['Radius', `${num.format(r)} cm`],
                    ['Höhe', `${num.format(h)} cm`],
                ]);
            }
            case 'kugel-volumen-rechner': {
                const r = n('radius');
                const vol = 4 / 3 * Math.PI * r * r * r;
                return rows([
                    ['Volumen', `${num.format(vol)} cm3`],
                    ['Volumen in Liter', `${num.format(vol / 1000)} L`],
                    ['Oberfläche', `${num.format(4 * Math.PI * r * r)} cm2`],
                    ['Durchmesser', `${num.format(2 * r)} cm`],
                    ['Umfang (Grosskreis)', `${num.format(2 * Math.PI * r)} cm`],
                    ['Radius', `${num.format(r)} cm`],
                ]);
            }
            case 'ggt-kgv-rechner': {
                const a = Math.abs(Math.round(n('a')));
                const b = Math.abs(Math.round(n('b')));
                const g = gcd(a, b);
                const l = g ? a / g * b : 0;
                return rows([
                    ['GGT (groesster Teiler)', num.format(g)],
                    ['KGV (kleinstes Vielfaches)', num.format(l)],
                    ['Zahl a', num.format(a)],
                    ['Zahl b', num.format(b)],
                    ['a / GGT', num.format(g ? a / g : 0)],
                    ['b / GGT', num.format(g ? b / g : 0)],
                    ['teilerfremd', g === 1 ? 'ja' : 'nein'],
                ]);
            }
            case 'fakultaet-rechner': {
                const nn = clamp(Math.round(n('num')), 0, 170);
                let f = 1;
                for (let i = 2; i <= nn; i++) f *= i;
                let log10 = 0;
                for (let i = 2; i <= nn; i++) log10 += Math.log10(i);
                return rows([
                    [`${nn}!`, f >= 1e15 ? f.toExponential(6) : num.format(f)],
                    ['Anzahl Stellen', num.format(Math.floor(log10) + 1)],
                    [`(${nn}-1)!`, nn > 0 ? (f / nn >= 1e15 ? (f / nn).toExponential(6) : num.format(f / nn)) : '1'],
                    ['Zahl n', num.format(nn)],
                    ['Naeherung (x10^?)', `ca. 10^${num.format(log10)}`],
                    ['Hinweis', nn >= 170 ? 'ab 171! zu gross für genaue Anzeige' : 'exakt'],
                ]);
            }
            case 'kombinatorik-rechner': {
                const nn = Math.max(0, Math.round(n('num')));
                const k = clamp(Math.round(n('k')), 0, nn);
                let nPr = 1;
                for (let i = 0; i < k; i++) nPr *= (nn - i);
                let kFac = 1;
                for (let i = 2; i <= k; i++) kFac *= i;
                const nCr = kFac ? nPr / kFac : 0;
                return rows([
                    ['Kombinationen nCr', nCr >= 1e15 ? nCr.toExponential(6) : num.format(nCr)],
                    ['Variationen nPr', nPr >= 1e15 ? nPr.toExponential(6) : num.format(nPr)],
                    ['k! (Permutationen)', num.format(kFac)],
                    ['n', num.format(nn)],
                    ['k', num.format(k)],
                    ['Wahrscheinlichkeit 1 Treffer', nCr ? `1 zu ${num.format(nCr)}` : '-'],
                    ['Erklaerung', 'nCr ohne, nPr mit Reihenfolge'],
                ]);
            }
            case 'ohmsches-gesetz-rechner': {
                const u = n('voltage'), r = n('resistance');
                const i = r ? u / r : 0;
                const p = u * i;
                return rows([
                    ['Strom I', `${num.format(i)} A`],
                    ['Strom I', `${num.format(i * 1000)} mA`],
                    ['Leistung P', `${num.format(p)} W`],
                    ['Spannung U', `${num.format(u)} V`],
                    ['Widerstand R', `${num.format(r)} Ohm`],
                    ['Energie pro Stunde', `${num.format(p)} Wh`],
                    ['Formel', 'I = U / R, P = U x I'],
                ]);
            }
            case 'kinetische-energie-rechner': {
                const v = n('speed') / 3.6;
                const e = 0.5 * n('mass') * v * v;
                return rows([
                    ['Kinetische Energie', `${num.format(e)} J`],
                    ['In Kilojoule', `${num.format(e / 1000)} kJ`],
                    ['Geschwindigkeit', `${num.format(v)} m/s`],
                    ['Masse', `${num.format(n('mass'))} kg`],
                    ['Bei doppeltem Tempo', `${num.format(e * 4)} J`],
                    ['Vergleich kcal', `${num.format(e / 4184)} kcal`],
                    ['Fallhöhe-Aequivalent', `${num.format(n('mass') ? e / (n('mass') * 9.81) : 0)} m`],
                ]);
            }
            case 'winkel-umrechner': {
                const unit = value('unit');
                let deg;
                if (unit === 'rad') deg = n('value') * 180 / Math.PI;
                else if (unit === 'gon') deg = n('value') * 0.9;
                else deg = n('value');
                const rad = deg * Math.PI / 180;
                return rows([
                    ['Grad', num.format(deg)],
                    ['Radiant', num.format(rad)],
                    ['Gon', num.format(deg / 0.9)],
                    ['sin', num.format(Math.sin(rad))],
                    ['cos', num.format(Math.cos(rad))],
                    ['tan', Math.abs(Math.cos(rad)) < 1e-12 ? 'undefiniert' : num.format(Math.tan(rad))],
                    ['Vielfaches von Pi', `${num.format(rad / Math.PI)} Pi`],
                ]);
            }
            case 'dichte-rechner': {
                const density = n('volume') ? n('mass') / n('volume') : 0;
                return rows([
                    ['Dichte', `${num.format(density)} g/cm3`],
                    ['Dichte', `${num.format(density * 1000)} kg/m3`],
                    ['Masse', `${num.format(n('mass'))} g`],
                    ['Volumen', `${num.format(n('volume'))} cm3`],
                    ['Schwimmt in Wasser', density < 1 ? 'ja (Dichte < 1)' : 'nein'],
                    ['Vergleich Wasser', `${num.format(density)} x Wasser`],
                    ['Masse pro Liter', `${num.format(density * 1000)} g`],
                ]);
            }
            case 'abo-kosten-rechner': {
                const totalMonthly = n('monthly') * n('count');
                const yearly = totalMonthly * 12;
                const perUse = n('uses') ? totalMonthly / n('uses') : 0;
                return rows([
                    ['Kosten pro Monat', euro.format(totalMonthly)],
                    ['Kosten pro Jahr', euro.format(yearly)],
                    ['Kosten pro 5 Jahre', euro.format(yearly * 5)],
                    ['Kosten pro Tag', euro.format(yearly / 365)],
                    ['Kosten pro Nutzung', euro.format(perUse)],
                    ['Anzahl Abos', num.format(n('count'))],
                    ['Nutzungen pro Jahr', num.format(n('uses') * 12)],
                    ['Pro Abo und Jahr', euro.format(n('monthly') * 12)],
                ]);
            }
            case 'schaltjahr-checker': {
                const y = Math.round(n('year'));
                const leap = (y % 4 === 0 && y % 100 !== 0) || y % 400 === 0;
                let next = y + 1;
                while (!((next % 4 === 0 && next % 100 !== 0) || next % 400 === 0)) next++;
                let prev = y - 1;
                while (!((prev % 4 === 0 && prev % 100 !== 0) || prev % 400 === 0)) prev--;
                return rows([
                    ['Schaltjahr', leap ? 'Ja' : 'Nein'],
                    ['Jahr', num.format(y)],
                    ['Tage im Jahr', leap ? '366' : '365'],
                    ['Tage im Februar', leap ? '29' : '28'],
                    ['Nächstes Schaltjahr', num.format(next)],
                    ['Letztes Schaltjahr', num.format(prev)],
                    ['Wochen im Jahr', leap ? '52 + 2 Tage' : '52 + 1 Tag'],
                ]);
            }
            case 'wochentag-rechner': {
                const d = new Date(value('date') + 'T00:00:00');
                if (isNaN(d.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const names = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
                const dayOfYear = Math.floor((d - new Date(d.getFullYear(), 0, 0)) / 86400000);
                const t = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate()));
                const wd = (t.getUTCDay() + 6) % 7;
                t.setUTCDate(t.getUTCDate() - wd + 3);
                const firstThu = new Date(Date.UTC(t.getUTCFullYear(), 0, 4));
                const fwd = (firstThu.getUTCDay() + 6) % 7;
                firstThu.setUTCDate(firstThu.getUTCDate() - fwd + 3);
                const week = 1 + Math.round((t - firstThu) / (7 * 86400000));
                const today = new Date(); today.setHours(0, 0, 0, 0);
                const diff = daysBetween(today, d);
                return rows([
                    ['Wochentag', names[d.getDay()]],
                    ['Datum', d.toLocaleDateString('de-DE')],
                    ['Tag im Jahr', num.format(dayOfYear)],
                    ['Kalenderwoche (ISO)', `KW ${num.format(week)}`],
                    ['Tage ab heute', diff === 0 ? 'heute' : (diff > 0 ? `in ${num.format(diff)} Tagen` : `vor ${num.format(-diff)} Tagen`)],
                    ['Wochenende', (d.getDay() === 0 || d.getDay() === 6) ? 'ja' : 'nein'],
                    ['Quartal', `Q${Math.floor(d.getMonth() / 3) + 1}`],
                ]);
            }
            case 'zinstage-rechner': {
                const d1 = new Date(value('from') + 'T00:00:00');
                const d2 = new Date(value('to') + 'T00:00:00');
                if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return rows([['Fehler', 'Bitte gültige Daten waehlen']]);
                const actual = daysBetween(d1, d2);
                let da = Math.min(d1.getDate(), 30);
                let db = d2.getDate();
                if (da === 30 && db === 31) db = 30;
                const days360 = (d2.getFullYear() - d1.getFullYear()) * 360 + (d2.getMonth() - d1.getMonth()) * 30 + (db - da);
                return rows([
                    ['Tatsaechliche Tage', num.format(actual)],
                    ['Zinstage (30/360)', num.format(days360)],
                    ['Monate (ca.)', num.format(actual / 30.4375)],
                    ['Jahre (ca.)', num.format(actual / 365)],
                    ['Wochen', num.format(actual / 7)],
                    ['Startdatum', d1.toLocaleDateString('de-DE')],
                    ['Enddatum', d2.toLocaleDateString('de-DE')],
                    ['Jahresbruchteil (act/365)', num.format(actual / 365)],
                ]);
            }
            case 'wechselgeld-rechner': {
                let cents = Math.round((n('paid') - n('price')) * 100);
                if (cents < 0) return rows([['Hinweis', 'Gegebener Betrag ist zu niedrig'], ['Fehlbetrag', euro.format(-cents / 100)]]);
                const total = cents;
                const denoms = [[5000, '50 Euro'], [2000, '20 Euro'], [1000, '10 Euro'], [500, '5 Euro'], [200, '2 Euro'], [100, '1 Euro'], [50, '50 Cent'], [20, '20 Cent'], [10, '10 Cent'], [5, '5 Cent'], [2, '2 Cent'], [1, '1 Cent']];
                const out = [['Rueckgeld', euro.format(total / 100)]];
                let pieces = 0;
                for (const [val, label] of denoms) {
                    const count = Math.floor(cents / val);
                    if (count > 0) { out.push([`${count} x ${label}`, euro.format(count * val / 100)]); cents -= count * val; pieces += count; }
                }
                out.push(['Anzahl Scheine/Muenzen', num.format(pieces)]);
                return rows(out);
            }
            case 'benzin-vs-bahn-rechner': {
                const carCost = n('distance') * n('consumption') / 100 * n('fuelPrice');
                const train = n('ticket');
                const cheaper = carCost < train ? 'Auto' : carCost > train ? 'Bahn' : 'gleich teuer';
                return rows([
                    ['Auto einfach', euro.format(carCost)],
                    ['Bahn einfach', euro.format(train)],
                    ['Differenz einfach', euro.format(Math.abs(carCost - train))],
                    ['Guenstiger', cheaper],
                    ['Auto hin und zurueck', euro.format(carCost * 2)],
                    ['Bahn hin und zurueck', euro.format(train * 2)],
                    ['Auto pro Monat (20 Tage)', euro.format(carCost * 2 * 20)],
                    ['Bahn pro Monat (20 Tage)', euro.format(train * 2 * 20)],
                ]);
            }
            case 'zeitersparnis-rechner': {
                const perWeek = n('minutes') * n('days');
                const perYear = perWeek * 52;
                return rows([
                    ['Pro Tag', `${num.format(n('minutes'))} min`],
                    ['Pro Woche', `${num.format(perWeek)} min (${num.format(perWeek / 60)} h)`],
                    ['Pro Monat', `${num.format(perWeek * 4.33 / 60)} h`],
                    ['Pro Jahr', `${num.format(perYear / 60)} h`],
                    ['Pro Jahr in Arbeitstagen (8h)', num.format(perYear / 60 / 8)],
                    ['Pro Jahr in Tagen (24h)', num.format(perYear / 60 / 24)],
                    ['über 5 Jahre', `${num.format(perYear * 5 / 60)} h`],
                ]);
            }
            case 'gehaltserhoehung-rechner': {
                const increase = n('salary') * n('percent') / 100;
                const neu = n('salary') + increase;
                return rows([
                    ['Neues Gehalt pro Monat', euro.format(neu)],
                    ['Erhoehung pro Monat', euro.format(increase)],
                    ['Erhoehung pro Jahr', euro.format(increase * 12)],
                    ['Neues Jahresgehalt', euro.format(neu * 12)],
                    ['Altes Jahresgehalt', euro.format(n('salary') * 12)],
                    ['Erhoehung pro Stunde (173h)', euro.format(increase / 173.2)],
                    ['Real nach 3% Inflation', euro.format(neu / 1.03)],
                    ['Erhoehung', `${num.format(n('percent'))} %`],
                ]);
            }
            case 'einfache-zinsen-rechner': {
                const interest = n('capital') * n('rate') / 100 * n('years');
                return rows([
                    ['Zinsen gesamt', euro.format(interest)],
                    ['Endkapital', euro.format(n('capital') + interest)],
                    ['Zinsen pro Jahr', euro.format(n('capital') * n('rate') / 100)],
                    ['Zinsen pro Monat', euro.format(n('capital') * n('rate') / 100 / 12)],
                    ['Kapital', euro.format(n('capital'))],
                    ['Ertrag in Prozent', `${num.format(n('rate') * n('years'))} %`],
                    ['Unterschied zu Zinseszins', euro.format(n('capital') * (Math.pow(1 + n('rate') / 100, n('years')) - 1) - interest)],
                ]);
            }
            case 'annuitaeten-rechner': {
                const r = n('rate') / 100 / 12;
                const months = n('years') * 12;
                const rate = r === 0 ? n('amount') / Math.max(1, months) : n('amount') * r / (1 - Math.pow(1 + r, -months));
                const total = rate * months;
                return rows([
                    ['Monatsrate', euro.format(rate)],
                    ['Gesamtkosten', euro.format(total)],
                    ['Zinskosten gesamt', euro.format(total - n('amount'))],
                    ['Darlehensbetrag', euro.format(n('amount'))],
                    ['Laufzeit', `${num.format(months)} Monate`],
                    ['Erste Zinszahlung', euro.format(n('amount') * r)],
                    ['Erste Tilgung', euro.format(rate - n('amount') * r)],
                    ['Rate pro Jahr', euro.format(rate * 12)],
                    ['Zinsanteil gesamt', `${num.format(total ? (total - n('amount')) / total * 100 : 0)} %`],
                ]);
            }
            case 'restschuld-rechner': {
                const r = n('rate') / 100 / 12;
                const m = Math.round(n('years') * 12);
                let bal = n('amount');
                for (let i = 0; i < m; i++) { bal = bal + bal * r - n('monthly'); if (bal < 0) bal = 0; }
                const paid = n('monthly') * m;
                return rows([
                    ['Restschuld', euro.format(bal)],
                    ['Bereits gezahlt', euro.format(paid)],
                    ['Bereits getilgt', euro.format(Math.max(0, n('amount') - bal))],
                    ['Gezahlte Zinsen', euro.format(Math.max(0, paid - (n('amount') - bal)))],
                    ['Darlehen', euro.format(n('amount'))],
                    ['Monatsrate', euro.format(n('monthly'))],
                    ['Vergangene Monate', num.format(m)],
                    ['Getilgt in Prozent', `${num.format(n('amount') ? (n('amount') - bal) / n('amount') * 100 : 0)} %`],
                ]);
            }
            case 'sondertilgung-rechner': {
                const saved = n('extra') * n('rate') / 100 * n('years');
                return rows([
                    ['Zinsersparnis (grob)', euro.format(saved)],
                    ['Sondertilgung', euro.format(n('extra'))],
                    ['Neue Restschuld', euro.format(Math.max(0, n('balance') - n('extra')))],
                    ['Effektiver Nutzen', euro.format(n('extra') + saved)],
                    ['Rendite der Tilgung', `${num.format(n('rate'))} % p.a.`],
                    ['Ersparnis pro Jahr', euro.format(n('extra') * n('rate') / 100)],
                    ['Restlaufzeit', `${num.format(n('years'))} Jahre`],
                    ['Hinweis', 'Naeherung ohne exakten Tilgungsplan'],
                ]);
            }
            case 'dividendenrendite-rechner': {
                const yield_ = n('price') ? n('dividend') / n('price') * 100 : 0;
                return rows([
                    ['Dividendenrendite', `${num.format(yield_)} %`],
                    ['Dividende je Aktie', euro.format(n('dividend'))],
                    ['Aktienkurs', euro.format(n('price'))],
                    ['Dividende gesamt', euro.format(n('dividend') * n('shares'))],
                    ['Depotwert', euro.format(n('price') * n('shares'))],
                    ['Aktien', num.format(n('shares'))],
                    ['Einordnung', yield_ >= 5 ? 'hoch' : yield_ >= 3 ? 'solide' : yield_ >= 1 ? 'niedrig' : 'sehr niedrig'],
                    ['Dividende pro Monat', euro.format(n('dividend') * n('shares') / 12)],
                ]);
            }
            case 'aktien-anzahl-rechner': {
                const usable = Math.max(0, n('budget') - n('fee'));
                const shares = n('price') ? Math.floor(usable / n('price')) : 0;
                const spent = shares * n('price') + n('fee');
                return rows([
                    ['Kaufbare Aktien', num.format(shares)],
                    ['Kosten gesamt', euro.format(spent)],
                    ['Davon Aktien', euro.format(shares * n('price'))],
                    ['Gebühr', euro.format(n('fee'))],
                    ['Restbetrag', euro.format(n('budget') - spent)],
                    ['Budget', euro.format(n('budget'))],
                    ['Gebührenquote', `${num.format(spent ? n('fee') / spent * 100 : 0)} %`],
                ]);
            }
            case 'trade-gebuehren-rechner': {
                const pct = n('value') * n('feePct') / 100;
                const fee = Math.max(pct, n('minFee'));
                return rows([
                    ['Ordergebühr', euro.format(fee)],
                    ['Prozentuale Gebühr', euro.format(pct)],
                    ['Mindestgebühr greift', pct < n('minFee') ? 'ja' : 'nein'],
                    ['Gebühr in Prozent', `${num.format(n('value') ? fee / n('value') * 100 : 0)} %`],
                    ['Ordervolumen', euro.format(n('value'))],
                    ['Hin- und Rueckkauf', euro.format(fee * 2)],
                    ['Break-even-Bewegung nötig', `${num.format(n('value') ? fee * 2 / n('value') * 100 : 0)} %`],
                ]);
            }
            case 'kursgewinn-rechner': {
                const buyTotal = n('buy') * n('qty') + n('fee');
                const sellTotal = n('sell') * n('qty') - n('fee');
                const profit = sellTotal - buyTotal;
                return rows([
                    ['Gewinn / Verlust', euro.format(profit)],
                    ['Rendite', `${num.format(buyTotal ? profit / buyTotal * 100 : 0)} %`],
                    ['Kaufwert inkl. Gebühr', euro.format(buyTotal)],
                    ['Verkaufswert abzgl. Gebühr', euro.format(sellTotal)],
                    ['Kursdifferenz je Aktie', euro.format(n('sell') - n('buy'))],
                    ['Gebühren gesamt', euro.format(n('fee') * 2)],
                    ['Stückzahl', num.format(n('qty'))],
                    ['Status', profit > 0 ? 'Gewinn' : profit < 0 ? 'Verlust' : 'neutral'],
                ]);
            }
            case 'cost-average-rechner': {
                const totalQty = n('qty1') + n('qty2');
                const totalCost = n('qty1') * n('price1') + n('qty2') * n('price2');
                const avg = totalQty ? totalCost / totalQty : 0;
                return rows([
                    ['Durchschnittskurs', euro.format(avg)],
                    ['Gesamtstückzahl', num.format(totalQty)],
                    ['Investiert gesamt', euro.format(totalCost)],
                    ['Kauf 1', `${num.format(n('qty1'))} zu ${euro.format(n('price1'))}`],
                    ['Kauf 2', `${num.format(n('qty2'))} zu ${euro.format(n('price2'))}`],
                    ['Break-even-Kurs', euro.format(avg)],
                    ['Gegenüber Kauf 1', `${num.format(n('price1') ? (avg / n('price1') - 1) * 100 : 0)} %`],
                ]);
            }
            case 'kreditkarten-tilgung-rechner': {
                const r = n('apr') / 100 / 12;
                let months = 0, paid = 0, bal = n('balance');
                if (n('payment') > bal * r) {
                    while (bal > 0 && months < 1200) { bal = bal + bal * r - n('payment'); months++; paid += n('payment'); }
                    paid = Math.min(paid, paid - Math.max(0, -bal));
                }
                const reachable = n('payment') > n('balance') * r;
                return rows([
                    ['Tilgungsdauer', reachable ? `${num.format(months)} Monate` : 'Rate zu niedrig'],
                    ['In Jahren', reachable ? `${num.format(months / 12)} Jahre` : '-'],
                    ['Gezahlt gesamt', reachable ? euro.format(n('balance') + (paid - n('balance'))) : '-'],
                    ['Zinskosten gesamt', reachable ? euro.format(Math.max(0, paid - n('balance'))) : '-'],
                    ['Saldo', euro.format(n('balance'))],
                    ['Monatsrate', euro.format(n('payment'))],
                    ['Erste Monatszinsen', euro.format(n('balance') * r)],
                    ['Mindestrate nötig', euro.format(n('balance') * r)],
                ]);
            }
            case 'kapitalentnahme-rechner': {
                const yearly = n('capital') * n('rate') / 100;
                return rows([
                    ['Entnahme pro Jahr', euro.format(yearly)],
                    ['Entnahme pro Monat', euro.format(yearly / 12)],
                    ['Entnahme pro Tag', euro.format(yearly / 365)],
                    ['Vermoegen', euro.format(n('capital'))],
                    ['Entnahmequote', `${num.format(n('rate'))} %`],
                    ['Reicht ohne Rendite', `${num.format(n('rate') ? 100 / n('rate') : 0)} Jahre`],
                    ['Bei 2% Realrendite ewig ab', euro.format(n('capital') * 0.02)],
                ]);
            }
            case 'fire-rechner': {
                const target = n('rate') ? n('monthly') * 12 / (n('rate') / 100) : 0;
                return rows([
                    ['Zielvermoegen (FIRE)', euro.format(target)],
                    ['Jahresausgaben', euro.format(n('monthly') * 12)],
                    ['Monatsausgaben', euro.format(n('monthly'))],
                    ['Entnahmequote', `${num.format(n('rate'))} %`],
                    ['Lean-FIRE (75%)', euro.format(target * 0.75)],
                    ['Fat-FIRE (150%)', euro.format(target * 1.5)],
                    ['Faktor der Jahresausgaben', `${num.format(n('rate') ? 100 / n('rate') : 0)} x`],
                    ['Nötig pro Monat sparen (20 J, 5%)', euro.format(target * (0.05 / 12) / (Math.pow(1 + 0.05 / 12, 240) - 1))],
                ]);
            }
            case 'rentenluecke-rechner': {
                const gap = Math.max(0, n('desired') - n('pension'));
                return rows([
                    ['Rentenluecke pro Monat', euro.format(gap)],
                    ['Luecke pro Jahr', euro.format(gap * 12)],
                    ['Luecke im Ruhestand gesamt', euro.format(gap * 12 * n('years'))],
                    ['Wunscheinkommen', euro.format(n('desired'))],
                    ['Erwartete Rente', euro.format(n('pension'))],
                    ['Deckungsgrad der Rente', `${num.format(n('desired') ? n('pension') / n('desired') * 100 : 0)} %`],
                    ['Nötiges Kapital (4%-Regel)', euro.format(gap * 12 / 0.04)],
                    ['Ruhestandsdauer', `${num.format(n('years'))} Jahre`],
                ]);
            }
            case 'goldwert-rechner': {
                const fine = n('weight') * n('purity') / 1000;
                const valuePerGram = n('price') * n('purity') / 1000;
                return rows([
                    ['Materialwert', euro.format(fine * n('price'))],
                    ['Feingoldanteil', `${num.format(fine)} g`],
                    ['Wert je Gramm Legierung', euro.format(valuePerGram)],
                    ['Gewicht', `${num.format(n('weight'))} g`],
                    ['Feingehalt', `${num.format(n('purity'))} / 1000 (${num.format(n('purity') / 1000 * 24)} Karat)`],
                    ['Wert je Unze (31,1 g)', euro.format(31.1035 * n('price') * n('purity') / 1000)],
                    ['Ankauf grob (-15%)', euro.format(fine * n('price') * 0.85)],
                ]);
            }
            case 'silberwert-rechner': {
                const fine = n('weight') * n('purity') / 1000;
                return rows([
                    ['Materialwert', euro.format(fine * n('price'))],
                    ['Feinsilberanteil', `${num.format(fine)} g`],
                    ['Wert je Gramm Legierung', euro.format(n('price') * n('purity') / 1000)],
                    ['Gewicht', `${num.format(n('weight'))} g`],
                    ['Feingehalt', `${num.format(n('purity'))} / 1000`],
                    ['Wert je Unze (31,1 g)', euro.format(31.1035 * n('price') * n('purity') / 1000)],
                    ['Ankauf grob (-20%)', euro.format(fine * n('price') * 0.8)],
                ]);
            }
            case 'bitcoin-satoshi-rechner': {
                const unit = value('unit');
                let btc;
                if (unit === 'sats') btc = n('amount') / 1e8;
                else if (unit === 'eur') btc = n('price') ? n('amount') / n('price') : 0;
                else btc = n('amount');
                return rows([
                    ['Bitcoin', `${num.format(btc)} BTC`],
                    ['Satoshi', num.format(Math.round(btc * 1e8))],
                    ['Wert in Euro', euro.format(btc * n('price'))],
                    ['Eingabe', `${num.format(n('amount'))} ${unit}`],
                    ['Kurs', euro.format(n('price'))],
                    ['1 Euro entspricht', `${num.format(n('price') ? 1e8 / n('price') : 0)} Satoshi`],
                    ['mBTC', `${num.format(btc * 1000)} mBTC`],
                ]);
            }
            case 'krypto-gewinn-rechner': {
                const buyVal = n('buy') * n('amount');
                const sellVal = n('sell') * n('amount');
                const fees = (buyVal + sellVal) * n('fee') / 100;
                const profit = sellVal - buyVal - fees;
                return rows([
                    ['Gewinn / Verlust', euro.format(profit)],
                    ['Rendite', `${num.format(buyVal ? profit / buyVal * 100 : 0)} %`],
                    ['Kaufwert', euro.format(buyVal)],
                    ['Verkaufswert', euro.format(sellVal)],
                    ['Gebühren gesamt', euro.format(fees)],
                    ['Menge', num.format(n('amount'))],
                    ['Kursveraenderung', `${num.format(n('buy') ? (n('sell') / n('buy') - 1) * 100 : 0)} %`],
                    ['Status', profit > 0 ? 'Gewinn' : profit < 0 ? 'Verlust' : 'neutral'],
                ]);
            }
            case 'ratenzahlung-rechner': {
                const financed = n('price') - n('down');
                const total = n('down') + n('monthly') * n('months');
                const surcharge = total - n('price');
                return rows([
                    ['Gesamtkosten', euro.format(total)],
                    ['Aufschlag ggue. Barpreis', euro.format(surcharge)],
                    ['Aufschlag in Prozent', `${num.format(n('price') ? surcharge / n('price') * 100 : 0)} %`],
                    ['Finanzierter Betrag', euro.format(financed)],
                    ['Anzahlung', euro.format(n('down'))],
                    ['Monatsrate', euro.format(n('monthly'))],
                    ['Laufzeit', `${num.format(n('months'))} Monate`],
                    ['Effektiv-Aufschlag p.a. (grob)', `${num.format(financed && n('months') ? surcharge / financed / (n('months') / 12) * 100 : 0)} %`],
                ]);
            }
            case 'leasing-vs-kauf-rechner': {
                const leaseTotal = n('leaseDown') + n('leaseMonthly') * n('months');
                const buyTotal = n('buyPrice') - n('resale');
                const diff = leaseTotal - buyTotal;
                return rows([
                    ['Leasing-Gesamtkosten', euro.format(leaseTotal)],
                    ['Kauf-Nettokosten', euro.format(buyTotal)],
                    ['Differenz', euro.format(Math.abs(diff))],
                    ['Guenstiger', diff < 0 ? 'Leasing' : diff > 0 ? 'Kauf' : 'gleich'],
                    ['Leasing pro Monat', euro.format(leaseTotal / Math.max(1, n('months')))],
                    ['Kauf pro Monat', euro.format(buyTotal / Math.max(1, n('months')))],
                    ['Wertverlust beim Kauf', euro.format(n('buyPrice') - n('resale'))],
                    ['Restwertquote', `${num.format(n('buyPrice') ? n('resale') / n('buyPrice') * 100 : 0)} %`],
                ]);
            }
            case 'waehrungsaufschlag-rechner': {
                const markup = n('offered') ? (n('official') / n('offered') - 1) * 100 : 0;
                const fairFx = n('amount') * n('official');
                const realFx = n('amount') * n('offered');
                return rows([
                    ['Versteckter Aufschlag', `${num.format(markup)} %`],
                    ['Fairer Gegenwert', `${num.format(fairFx)} FW`],
                    ['Tatsaechlicher Gegenwert', `${num.format(realFx)} FW`],
                    ['Verlust durch Aufschlag', `${num.format(fairFx - realFx)} FW`],
                    ['Offizieller Kurs', num.format(n('official'))],
                    ['Angebotener Kurs', num.format(n('offered'))],
                    ['Betrag', euro.format(n('amount'))],
                ]);
            }
            case 'trinkgeld-aufteilen-rechner': {
                const tip = n('bill') * n('tipPct') / 100;
                const total = n('bill') + tip;
                const people = Math.max(1, n('people'));
                return rows([
                    ['Pro Person', euro.format(total / people)],
                    ['Gesamt inkl. Trinkgeld', euro.format(total)],
                    ['Trinkgeld gesamt', euro.format(tip)],
                    ['Trinkgeld pro Person', euro.format(tip / people)],
                    ['Rechnung pro Person', euro.format(n('bill') / people)],
                    ['Rechnungsbetrag', euro.format(n('bill'))],
                    ['Personen', num.format(people)],
                    ['Aufgerundet pro Person', euro.format(Math.ceil(total / people))],
                ]);
            }
            case 'jahresgehalt-rechner': {
                const yearly = n('monthly') * n('salaries');
                const hoursMonth = n('hours') * 4.33;
                return rows([
                    ['Jahresgehalt brutto', euro.format(yearly)],
                    ['Monatsgehalt', euro.format(n('monthly'))],
                    ['Gehaelter pro Jahr', num.format(n('salaries'))],
                    ['Durchschnitt pro Monat', euro.format(yearly / 12)],
                    ['Stundenlohn (rechnerisch)', euro.format(hoursMonth ? n('monthly') / hoursMonth : 0)],
                    ['Pro Woche', euro.format(yearly / 52)],
                    ['Pro Tag (Arbeitstage)', euro.format(yearly / 220)],
                    ['Sonderzahlungen', euro.format(n('monthly') * Math.max(0, n('salaries') - 12))],
                ]);
            }
            case 'wertverlust-auto-rechner': {
                const value_ = n('price') * Math.pow(1 - n('lossPct') / 100, n('years'));
                const loss = n('price') - value_;
                return rows([
                    ['Restwert nach Jahren', euro.format(value_)],
                    ['Wertverlust gesamt', euro.format(loss)],
                    ['Wertverlust in Prozent', `${num.format(n('price') ? loss / n('price') * 100 : 0)} %`],
                    ['Verlust pro Jahr (Schnitt)', euro.format(loss / Math.max(1, n('years')))],
                    ['Verlust pro Monat', euro.format(loss / Math.max(1, n('years') * 12))],
                    ['Restwert nach 1 Jahr', euro.format(n('price') * (1 - n('lossPct') / 100))],
                    ['Kaufpreis', euro.format(n('price'))],
                    ['Restwertquote', `${num.format(n('price') ? value_ / n('price') * 100 : 0)} %`],
                ]);
            }
            case 'kfz-steuer-schaetzer': {
                const diesel = value('fuel') === 'diesel';
                const hubraumTax = Math.ceil(n('ccm') / 100) * (diesel ? 9.5 : 2);
                const freeCo2 = 95;
                const co2Tax = Math.max(0, n('co2') - freeCo2) * 2;
                const total = hubraumTax + co2Tax;
                return rows([
                    ['KFZ-Steuer pro Jahr (ca.)', euro.format(total)],
                    ['Hubraum-Anteil', euro.format(hubraumTax)],
                    ['CO2-Anteil', euro.format(co2Tax)],
                    ['Pro Monat', euro.format(total / 12)],
                    ['Kraftstoff', diesel ? 'Diesel' : 'Benzin'],
                    ['Hubraum', `${num.format(n('ccm'))} ccm`],
                    ['CO2 über Freigrenze', `${num.format(Math.max(0, n('co2') - freeCo2))} g/km`],
                    ['Hinweis', 'Grobe Schätzung, Erstzulassung beachten'],
                ]);
            }
            case 'koerperwasser-rechner': {
                const sex = value('sex');
                const tbw = sex === 'w'
                    ? -2.097 + 0.1069 * n('height') + 0.2466 * n('weight')
                    : 2.447 - 0.09516 * n('age') + 0.1074 * n('height') + 0.3362 * n('weight');
                return rows([
                    ['Gesamtkörperwasser', `${num.format(tbw)} Liter`],
                    ['Anteil am Gewicht', `${num.format(n('weight') ? tbw / n('weight') * 100 : 0)} %`],
                    ['Gewicht', `${num.format(n('weight'))} kg`],
                    ['Richtwert maennlich', '~60 %'],
                    ['Richtwert weiblich', '~50-55 %'],
                    ['In Glaesern (250 ml)', num.format(tbw * 1000 / 250)],
                    ['Hinweis', 'Schätzung nach Watson-Formel'],
                ]);
            }
            case 'taille-hueft-ratio-rechner': {
                const whr = n('hip') ? n('waist') / n('hip') : 0;
                const limit = value('sex') === 'w' ? 0.85 : 1.0;
                return rows([
                    ['Taille-Huefte-Ratio', num.format(whr)],
                    ['Grenzwert', num.format(limit)],
                    ['Einordnung', whr <= limit ? 'unauffaellig' : 'erhoeht'],
                    ['Taille', `${num.format(n('waist'))} cm`],
                    ['Huefte', `${num.format(n('hip'))} cm`],
                    ['Fettverteilung', whr >= (value('sex') === 'w' ? 0.85 : 0.9) ? 'eher Apfeltyp' : 'eher Birnentyp'],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'wunschgewicht-zeit-rechner': {
                const toLose = n('current') - n('target');
                const weeks = n('perWeek') ? Math.abs(toLose) / n('perWeek') : 0;
                return rows([
                    ['Zu veraendern', `${num.format(Math.abs(toLose))} kg`],
                    ['Dauer', `${num.format(weeks)} Wochen`],
                    ['Dauer in Monaten', `${num.format(weeks / 4.345)} Monate`],
                    ['Richtung', toLose > 0 ? 'abnehmen' : toLose < 0 ? 'zunehmen' : 'Ziel erreicht'],
                    ['Pro Woche', `${num.format(n('perWeek'))} kg`],
                    ['Nötiges Tagesdefizit', `${num.format(n('perWeek') * 7700 / 7)} kcal`],
                    ['Ziel erreicht ca.', weeks ? `in ${num.format(weeks * 7)} Tagen` : 'jetzt'],
                ]);
            }
            case 'kaloriendefizit-rechner': {
                const dailyDeficit = n('perWeek') * 7700 / 7;
                const target = n('tdee') - dailyDeficit;
                return rows([
                    ['Taegliches Defizit', `${num.format(dailyDeficit)} kcal`],
                    ['Ziel-Kalorienzufuhr', `${num.format(target)} kcal`],
                    ['Gesamtumsatz', `${num.format(n('tdee'))} kcal`],
                    ['Woechentliches Defizit', `${num.format(dailyDeficit * 7)} kcal`],
                    ['Abnahme pro Woche', `${num.format(n('perWeek'))} kg`],
                    ['Abnahme pro Monat', `${num.format(n('perWeek') * 4.345)} kg`],
                    ['Defizit-Anteil', `${num.format(n('tdee') ? dailyDeficit / n('tdee') * 100 : 0)} %`],
                    ['Hinweis', target < 1200 ? 'sehr niedrig, vorsichtig' : 'plausibel'],
                ]);
            }
            case 'koerpergroesse-kind-rechner': {
                const base = (n('mother') + n('father')) / 2;
                const predicted = value('sex') === 'm' ? base + 6.5 : base - 6.5;
                return rows([
                    ['Voraussichtliche Größe', `${num.format(predicted)} cm`],
                    ['Spanne', `${num.format(predicted - 8.5)} - ${num.format(predicted + 8.5)} cm`],
                    ['Mittelelterngröße', `${num.format(base)} cm`],
                    ['Größe Mutter', `${num.format(n('mother'))} cm`],
                    ['Größe Vater', `${num.format(n('father'))} cm`],
                    ['Geschlecht', value('sex') === 'm' ? 'Junge' : 'Maedchen'],
                    ['Hinweis', 'Grobe Schätzung, viele Faktoren spielen mit'],
                ]);
            }
            case 'bauchumfang-risiko-rechner': {
                const w = n('waist');
                const f = value('sex') === 'w';
                const status = w >= (f ? 88 : 102) ? 'stark erhoeht' : w >= (f ? 80 : 94) ? 'erhoeht' : 'normal';
                return rows([
                    ['Bauchumfang', `${num.format(w)} cm`],
                    ['Einordnung', status],
                    ['Erhoeht ab', `${f ? 80 : 94} cm`],
                    ['Stark erhoeht ab', `${f ? 88 : 102} cm`],
                    ['Geschlecht', f ? 'weiblich' : 'maennlich'],
                    ['Bis zur nächsten Grenze', `${num.format(Math.max(0, (w < (f ? 80 : 94) ? (f ? 80 : 94) : (f ? 88 : 102)) - w))} cm`],
                    ['Hinweis', 'Richtwert, kein medizinischer Rat'],
                ]);
            }
            case 'zigaretten-kosten-rechner': {
                const perDayCost = n('perDay') / Math.max(1, n('perPack')) * n('pricePack');
                return rows([
                    ['Kosten pro Tag', euro.format(perDayCost)],
                    ['Kosten pro Monat', euro.format(perDayCost * 30.44)],
                    ['Kosten pro Jahr', euro.format(perDayCost * 365)],
                    ['Kosten in 10 Jahren', euro.format(perDayCost * 365 * 10)],
                    ['Zigaretten pro Jahr', num.format(n('perDay') * 365)],
                    ['Packungen pro Monat', num.format(n('perDay') * 30.44 / Math.max(1, n('perPack')))],
                    ['Ersparnis pro Jahr beim Aufhoeren', euro.format(perDayCost * 365)],
                    ['Als Anlage (5%, 10 J.)', euro.format(perDayCost * 365 * ((Math.pow(1.05, 10) - 1) / 0.05))],
                ]);
            }
            case 'alkohol-kalorien-rechner': {
                const alcoholGrams = n('volume') * n('abv') / 100 * 0.789;
                const kcal = alcoholGrams * 7;
                return rows([
                    ['Kalorien (Alkohol)', `${num.format(kcal)} kcal`],
                    ['Reiner Alkohol', `${num.format(alcoholGrams)} g`],
                    ['Menge', `${num.format(n('volume'))} ml`],
                    ['Alkoholgehalt', `${num.format(n('abv'))} %`],
                    ['Standardglaeser (10 g)', num.format(alcoholGrams / 10)],
                    ['Joggen zum Abbau (~10 kcal/min)', `${num.format(kcal / 10)} min`],
                    ['Hinweis', 'Ohne Zucker/Restextrakt, nur Alkoholkalorien'],
                ]);
            }
            case 'koffein-rechner': {
                const half = 5;
                const remaining = n('mg') * Math.pow(0.5, n('hours') / half);
                return rows([
                    ['Restkoffein', `${num.format(remaining)} mg`],
                    ['Abgebaut', `${num.format(n('mg') - remaining)} mg`],
                    ['Ausgangsmenge', `${num.format(n('mg'))} mg`],
                    ['Stunden', num.format(n('hours'))],
                    ['Halbwertszeit', '~5 Stunden'],
                    ['Nach 10 Stunden', `${num.format(n('mg') * Math.pow(0.5, 10 / half))} mg`],
                    ['Tageshoechstmenge (400 mg)', `${num.format(400 - n('mg'))} mg uebrig`],
                    ['Entspricht Tassen Kaffee (~80 mg)', num.format(n('mg') / 80)],
                ]);
            }
            case 'kalorien-abtrainieren-rechner': {
                const perMin = n('met') * 3.5 * n('weight') / 200;
                const minutes = perMin ? n('kcal') / perMin : 0;
                return rows([
                    ['Nötige Dauer', `${num.format(minutes)} min`],
                    ['In Stunden', `${num.format(minutes / 60)} h`],
                    ['Verbrauch pro Minute', `${num.format(perMin)} kcal`],
                    ['Verbrauch pro Stunde', `${num.format(perMin * 60)} kcal`],
                    ['Zu verbrennen', `${num.format(n('kcal'))} kcal`],
                    ['MET-Wert', num.format(n('met'))],
                    ['Strecke beim Laufen ca.', `${num.format(n('weight') ? n('kcal') / n('weight') : 0)} km`],
                ]);
            }
            case 'maximalpuls-rechner': {
                const age = n('age');
                const fox = 220 - age;
                const tanaka = 208 - 0.7 * age;
                const gellish = 207 - 0.7 * age;
                return rows([
                    ['Maximalpuls (220 - Alter)', `${num.format(fox)} bpm`],
                    ['Tanaka-Formel', `${num.format(tanaka)} bpm`],
                    ['Gellish-Formel', `${num.format(gellish)} bpm`],
                    ['Fettverbrennung (60-70%)', `${num.format(fox * 0.6)} - ${num.format(fox * 0.7)} bpm`],
                    ['Ausdauer (70-80%)', `${num.format(fox * 0.7)} - ${num.format(fox * 0.8)} bpm`],
                    ['Intensiv (80-90%)', `${num.format(fox * 0.8)} - ${num.format(fox * 0.9)} bpm`],
                    ['Alter', num.format(age)],
                ]);
            }
            case 'vo2max-schaetzer': {
                const vo2 = (n('distance') - 504.9) / 44.73;
                const cat = vo2 >= 52 ? 'sehr gut' : vo2 >= 42 ? 'gut' : vo2 >= 35 ? 'durchschnittlich' : 'unter Durchschnitt';
                return rows([
                    ['VO2max (geschätzt)', `${num.format(vo2)} ml/kg/min`],
                    ['Einordnung', cat],
                    ['Cooper-Strecke', `${num.format(n('distance'))} m`],
                    ['Durchschnittstempo', `${num.format(n('distance') / 1000 / 0.2)} km/h`],
                    ['Pace', n('distance') > 0 ? `${Math.floor(720 / (n('distance') / 1000) / 60)}:${String(Math.round(720 / (n('distance') / 1000) % 60)).padStart(2, '0')} min/km` : '-'],
                    ['Alter', num.format(n('age'))],
                    ['Hinweis', 'Schätzung nach Cooper-12-Minuten-Test'],
                ]);
            }
            case 'kalorien-pro-mahlzeit-rechner': {
                const meals = Math.max(1, Math.round(n('meals')));
                const per = n('daily') / meals;
                return rows([
                    ['Kalorien pro Mahlzeit', `${num.format(per)} kcal`],
                    ['Tageskalorien', `${num.format(n('daily'))} kcal`],
                    ['Mahlzeiten', num.format(meals)],
                    ['Fruehstück (25%)', `${num.format(n('daily') * 0.25)} kcal`],
                    ['Mittagessen (35%)', `${num.format(n('daily') * 0.35)} kcal`],
                    ['Abendessen (30%)', `${num.format(n('daily') * 0.3)} kcal`],
                    ['Snacks (10%)', `${num.format(n('daily') * 0.1)} kcal`],
                ]);
            }
            case 'wasserverlust-sport-rechner': {
                const loss = n('sweatRate') * (n('minutes') / 60);
                return rows([
                    ['Fluessigkeitsverlust', `${num.format(loss)} Liter`],
                    ['In Milliliter', `${num.format(loss * 1000)} ml`],
                    ['Trinkempfehlung (150%)', `${num.format(loss * 1.5)} Liter`],
                    ['Pro 15 min nachtrinken', `${num.format(loss / (n('minutes') / 15 || 1))} Liter`],
                    ['Dauer', `${num.format(n('minutes'))} min`],
                    ['Schweissrate', `${num.format(n('sweatRate'))} L/h`],
                    ['Gewichtsverlust ca.', `${num.format(loss)} kg`],
                ]);
            }
            case 'ballaststoff-rechner': {
                const fiber = n('kcal') / 1000 * 14;
                return rows([
                    ['Empfohlene Ballaststoffe', `${num.format(fiber)} g`],
                    ['Mindestempfehlung (DGE)', '30 g'],
                    ['Kalorienzufuhr', `${num.format(n('kcal'))} kcal`],
                    ['Pro Mahlzeit (3)', `${num.format(fiber / 3)} g`],
                    ['Entspricht Vollkornbrot', `${num.format(fiber / 7 * 100)} g`],
                    ['Entspricht Aepfeln (~2,5 g)', num.format(fiber / 2.5)],
                    ['Hinweis', 'Faustregel 14 g je 1000 kcal'],
                ]);
            }
            case 'zuckerlimit-rechner': {
                const g10 = n('kcal') * 0.1 / 4;
                const g5 = n('kcal') * 0.05 / 4;
                return rows([
                    ['Obergrenze (10% WHO)', `${num.format(g10)} g`],
                    ['Empfehlung (5% WHO)', `${num.format(g5)} g`],
                    ['In Wuerfelzucker (3 g)', `${num.format(g10 / 3)} Stück`],
                    ['Tageskalorien', `${num.format(n('kcal'))} kcal`],
                    ['Kalorien aus Zucker (10%)', `${num.format(n('kcal') * 0.1)} kcal`],
                    ['Eine Cola (0,33 l) hat', '~35 g'],
                    ['Hinweis', 'Bezieht sich auf freien Zucker'],
                ]);
            }
            case 'koerperfettanteil-ziel-rechner': {
                const fatMass = n('weight') * n('current') / 100;
                const leanMass = n('weight') - fatMass;
                const targetWeight = (1 - n('target') / 100) ? leanMass / (1 - n('target') / 100) : 0;
                return rows([
                    ['Zielgewicht', `${num.format(targetWeight)} kg`],
                    ['Fettmasse aktuell', `${num.format(fatMass)} kg`],
                    ['Magermasse', `${num.format(leanMass)} kg`],
                    ['Ziel-Fettmasse', `${num.format(targetWeight * n('target') / 100)} kg`],
                    ['Zu verlieren', `${num.format(n('weight') - targetWeight)} kg Fett`],
                    ['Aktueller KFA', `${num.format(n('current'))} %`],
                    ['Ziel-KFA', `${num.format(n('target'))} %`],
                ]);
            }
            case 'trainingsvolumen-rechner': {
                const vol = n('sets') * n('reps') * n('weight');
                return rows([
                    ['Trainingsvolumen', `${num.format(vol)} kg`],
                    ['Volumen in Tonnen', `${num.format(vol / 1000)} t`],
                    ['Wiederholungen gesamt', num.format(n('sets') * n('reps'))],
                    ['Saetze', num.format(n('sets'))],
                    ['Wiederholungen je Satz', num.format(n('reps'))],
                    ['Gewicht', `${num.format(n('weight'))} kg`],
                    ['Volumen pro Satz', `${num.format(n('reps') * n('weight'))} kg`],
                    ['Woche bei 3 Einheiten', `${num.format(vol * 3 / 1000)} t`],
                ]);
            }
            case 'lauf-kalorien-rechner': {
                const kcal = n('distance') * n('weight') * 1.036;
                return rows([
                    ['Kalorienverbrauch', `${num.format(kcal)} kcal`],
                    ['Pro km', `${num.format(n('weight') * 1.036)} kcal`],
                    ['Strecke', `${num.format(n('distance'))} km`],
                    ['Gewicht', `${num.format(n('weight'))} kg`],
                    ['Fettaequivalent', `${num.format(kcal / 9)} g`],
                    ['Entspricht (Schokoriegel ~230 kcal)', num.format(kcal / 230)],
                    ['Bei 3x pro Woche', `${num.format(kcal * 3)} kcal/Woche`],
                ]);
            }
            case 'rad-kalorien-rechner': {
                const met = n('speed') < 16 ? 4 : n('speed') < 19 ? 6 : n('speed') < 22 ? 8 : n('speed') < 26 ? 10 : 12;
                const kcal = met * 3.5 * n('weight') / 200 * n('minutes');
                return rows([
                    ['Kalorienverbrauch', `${num.format(kcal)} kcal`],
                    ['MET-Wert (Tempo)', num.format(met)],
                    ['Pro Stunde', `${num.format(kcal / Math.max(1, n('minutes')) * 60)} kcal`],
                    ['Strecke', `${num.format(n('speed') * n('minutes') / 60)} km`],
                    ['Tempo', `${num.format(n('speed'))} km/h`],
                    ['Dauer', `${num.format(n('minutes'))} min`],
                    ['Fettaequivalent', `${num.format(kcal / 9)} g`],
                ]);
            }
            case 'herzschlag-leben-rechner': {
                const perDay = n('bpm') * 60 * 24;
                return rows([
                    ['Schlaege pro Tag', num.format(perDay)],
                    ['Schlaege pro Jahr', num.format(perDay * 365)],
                    ['Schlaege im Leben bisher', num.format(perDay * 365 * n('age'))],
                    ['Schlaege pro Stunde', num.format(n('bpm') * 60)],
                    ['Schlaege pro Minute', num.format(n('bpm'))],
                    ['Hochrechnung 80 Jahre', num.format(perDay * 365 * 80)],
                    ['Alter', `${num.format(n('age'))} Jahre`],
                ]);
            }
            case 'atemzuege-rechner': {
                const perDay = n('rate') * 60 * 24;
                return rows([
                    ['Atemzuege pro Tag', num.format(perDay)],
                    ['Atemzuege pro Jahr', num.format(perDay * 365)],
                    ['Atemzuege im Leben bisher', num.format(perDay * 365 * n('age'))],
                    ['Atemzuege pro Stunde', num.format(n('rate') * 60)],
                    ['Luftmenge pro Tag (0,5 l)', `${num.format(perDay * 0.5)} Liter`],
                    ['Hochrechnung 80 Jahre', num.format(perDay * 365 * 80)],
                    ['Atemfrequenz', `${num.format(n('rate'))} /min`],
                ]);
            }
            case 'fastenfenster-rechner': {
                const start = ((Math.round(n('start')) % 24) + 24) % 24;
                const eat = clamp(n('eatHours'), 0, 24);
                const end = (start + eat) % 24;
                const fast = 24 - eat;
                const fmt = h => `${String(Math.floor(h) % 24).padStart(2, '0')}:00`;
                return rows([
                    ['Essfenster', `${fmt(start)} - ${fmt(end)} Uhr`],
                    ['Fastenfenster', `${num.format(fast)} Stunden`],
                    ['Essfenster-Länge', `${num.format(eat)} Stunden`],
                    ['Schema', `${num.format(fast)}:${num.format(eat)}`],
                    ['Letzte Mahlzeit bis', `${fmt(end)} Uhr`],
                    ['Nächste Mahlzeit ab', `${fmt(start)} Uhr`],
                    ['Hinweis', 'Beliebt: 16:8 oder 14:10'],
                ]);
            }
            case 'naehrwert-portion-rechner': {
                const f = n('grams') / 100;
                return rows([
                    ['Kalorien je Portion', `${num.format(n('kcal100') * f)} kcal`],
                    ['Protein je Portion', `${num.format(n('protein100') * f)} g`],
                    ['Kohlenhydrate je Portion', `${num.format(n('carbs100') * f)} g`],
                    ['Fett je Portion', `${num.format(n('fat100') * f)} g`],
                    ['Portionsgröße', `${num.format(n('grams'))} g`],
                    ['Faktor', `${num.format(f)} x`],
                    ['Kalorien je 100 g', `${num.format(n('kcal100'))} kcal`],
                ]);
            }
            case 'koerperoberflaeche-rechner': {
                const bsa = Math.sqrt(n('weight') * n('height') / 3600);
                return rows([
                    ['Körperoberfläche (BSA)', `${num.format(bsa)} m2`],
                    ['Gewicht', `${num.format(n('weight'))} kg`],
                    ['Größe', `${num.format(n('height'))} cm`],
                    ['Richtwert Erwachsener', '1,6 - 1,9 m2'],
                    ['Formel', 'Mosteller: sqrt(kg x cm / 3600)'],
                    ['Vergleich Durchschnitt (1,73)', `${num.format(bsa / 1.73 * 100)} %`],
                    ['Hinweis', 'Wird u.a. für Dosierungen genutzt'],
                ]);
            }
            case 'rgb-cmyk-konverter': {
                const ph = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const [r, g, b] = ph(value('hex'));
                const k = 1 - Math.max(r, g, b) / 255;
                const c = k < 1 ? (1 - r / 255 - k) / (1 - k) : 0;
                const m = k < 1 ? (1 - g / 255 - k) / (1 - k) : 0;
                const y = k < 1 ? (1 - b / 255 - k) / (1 - k) : 0;
                return rows([
                    ['CMYK', `${num.format(c * 100)}% ${num.format(m * 100)}% ${num.format(y * 100)}% ${num.format(k * 100)}%`],
                    ['Cyan', `${num.format(c * 100)} %`],
                    ['Magenta', `${num.format(m * 100)} %`],
                    ['Yellow', `${num.format(y * 100)} %`],
                    ['Key (Schwarz)', `${num.format(k * 100)} %`],
                    ['RGB', `rgb(${r}, ${g}, ${b})`],
                    ['Hinweis', 'CMYK je nach Druckprofil unterschiedlich'],
                ]);
            }
            case 'css-spezifitaet-rechner': {
                const sel = value('selector');
                const ids = (sel.match(/#[\w-]+/g) || []).length;
                const classes = (sel.match(/\.[\w-]+|\[[^\]]+\]|:[a-z-]+(?!:)/gi) || []).length;
                const elements = (sel.match(/(^|[\s>+~])[a-z][\w-]*/gi) || []).length + (sel.match(/::[a-z-]+/gi) || []).length;
                return rows([
                    ['Spezifitaet', `${ids}-${classes}-${elements}`],
                    ['Gesamtwert', num.format(ids * 100 + classes * 10 + elements)],
                    ['IDs', num.format(ids)],
                    ['Klassen / Attribute / Pseudoklassen', num.format(classes)],
                    ['Elemente / Pseudoelemente', num.format(elements)],
                    ['Selektor', `<span class="mono">${escapeHtml(sel)}</span>`],
                    ['Inline-Style schlaegt alles', '1-0-0-0'],
                ]);
            }
            case 'bitrate-dateigroesse-rechner': {
                const mb = n('bitrate') * n('minutes') * 60 / 8 / 1024;
                return rows([
                    ['Dateigröße', `${num.format(mb)} MB`],
                    ['In Gigabyte', `${num.format(mb / 1024)} GB`],
                    ['Pro Minute', `${num.format(mb / Math.max(1, n('minutes')))} MB`],
                    ['Bitrate', `${num.format(n('bitrate'))} kbit/s`],
                    ['Dauer', `${num.format(n('minutes'))} min`],
                    ['Pro Stunde', `${num.format(mb / Math.max(1, n('minutes')) * 60)} MB`],
                    ['Datenmenge', `${num.format(mb * 1024 * 1024)} Byte`],
                ]);
            }
            case 'hash-laenge-spicker': {
                const d = { md5: [128, 32], sha1: [160, 40], sha256: [256, 64], sha512: [512, 128] }[value('algo')] || [256, 64];
                return rows([
                    ['Algorithmus', value('algo').toUpperCase()],
                    ['Ausgabe in Bit', num.format(d[0])],
                    ['Hex-Zeichen', num.format(d[1])],
                    ['Bytes', num.format(d[0] / 8)],
                    ['Base64-Zeichen ca.', num.format(Math.ceil(d[0] / 8 / 3) * 4)],
                    ['Kollisionssicher', value('algo') === 'md5' || value('algo') === 'sha1' ? 'nein (veraltet)' : 'ja'],
                    ['Einsatz', value('algo') === 'md5' ? 'nur Prüfsummen' : 'Signaturen, Passwoerter'],
                ]);
            }
            case 'mime-type-spicker': {
                const map = { json: 'application/json', html: 'text/html', css: 'text/css', js: 'text/javascript', png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', svg: 'image/svg+xml', webp: 'image/webp', pdf: 'application/pdf', zip: 'application/zip', mp4: 'video/mp4', mp3: 'audio/mpeg', csv: 'text/csv', xml: 'application/xml', txt: 'text/plain', woff2: 'font/woff2', ico: 'image/x-icon', webm: 'video/webm' };
                const ext = value('ext').toLowerCase().replace(/^\./, '');
                return rows([
                    ['Dateiendung', `.${escapeHtml(ext)}`],
                    ['MIME-Type', escapeHtml(map[ext] || 'application/octet-stream')],
                    ['Bekannt', map[ext] ? 'ja' : 'nein (Fallback)'],
                    ['Kategorie', (map[ext] || '').split('/')[0] || 'unbekannt'],
                    ['Content-Type Header', escapeHtml(`Content-Type: ${map[ext] || 'application/octet-stream'}`)],
                ]);
            }
            case 'css-einheiten-umrechner': {
                const base = Math.max(1, n('base'));
                const px = value('unit') === 'px' ? n('value') : n('value') * base;
                return rows([
                    ['px', `${num.format(px)} px`],
                    ['em', `${num.format(px / base)} em`],
                    ['rem', `${num.format(px / base)} rem`],
                    ['Prozent (von Basis)', `${num.format(px / base * 100)} %`],
                    ['pt', `${num.format(px * 0.75)} pt`],
                    ['Basisgröße', `${num.format(base)} px`],
                    ['Eingabe', `${num.format(n('value'))} ${value('unit')}`],
                ]);
            }
            case 'tailwind-spacing-rechner': {
                const scale = n('px') / 4;
                const nearest = Math.round(scale * 2) / 2;
                return rows([
                    ['Tailwind-Klasse', `p-${num.format(nearest)} / m-${num.format(nearest)}`],
                    ['Scale-Wert', num.format(scale)],
                    ['Nächster Scale-Wert', num.format(nearest)],
                    ['Entspricht px', `${num.format(nearest * 4)} px`],
                    ['Entspricht rem', `${num.format(nearest * 0.25)} rem`],
                    ['Eingabe', `${num.format(n('px'))} px`],
                    ['Exakt', scale === nearest ? 'ja' : 'gerundet'],
                ]);
            }
            case 'zeichen-pro-zeile-rechner': {
                const charWidth = n('fontSize') * 0.5;
                const cpl = charWidth ? n('width') / charWidth : 0;
                const idealWidth = 66 * charWidth;
                return rows([
                    ['Zeichen pro Zeile', num.format(cpl)],
                    ['Ideal (45-75)', cpl >= 45 && cpl <= 75 ? 'ja' : cpl < 45 ? 'zu schmal' : 'zu breit'],
                    ['Ideale Breite (66 Zeichen)', `${num.format(idealWidth)} px`],
                    ['Schriftgröße', `${num.format(n('fontSize'))} px`],
                    ['Spaltenbreite', `${num.format(n('width'))} px`],
                    ['Ideale Breite in em', `${num.format(33)} em`],
                    ['Mittlere Zeichenbreite', `${num.format(charWidth)} px`],
                ]);
            }
            case 'ladezeit-budget-rechner': {
                const seconds = n('speed') ? n('size') * 8 / 1024 / n('speed') : 0;
                return rows([
                    ['Ladezeit (reine Übertragung)', `${num.format(seconds)} s`],
                    ['Mit Latenz (+0,3 s)', `${num.format(seconds + 0.3)} s`],
                    ['Seitengröße', `${num.format(n('size'))} KB`],
                    ['In Megabyte', `${num.format(n('size') / 1024)} MB`],
                    ['Geschwindigkeit', `${num.format(n('speed'))} Mbit/s`],
                    ['Budget für 3 s', `${num.format(n('speed') * 3 * 1024 / 8)} KB`],
                    ['Bewertung', seconds <= 2 ? 'gut' : seconds <= 4 ? 'okay' : 'zu langsam'],
                ]);
            }
            case 'json-groesse-rechner': {
                const raw = value('json');
                let min = raw;
                try { min = JSON.stringify(JSON.parse(raw)); } catch (e) {}
                const bytes = new TextEncoder().encode(raw).length;
                return rows([
                    ['Zeichen', num.format(raw.length)],
                    ['Bytes (UTF-8)', num.format(bytes)],
                    ['Minifiziert', `${num.format(min.length)} Zeichen`],
                    ['Einsparung', `${num.format(raw.length ? (1 - min.length / raw.length) * 100 : 0)} %`],
                    ['Größe', `${num.format(bytes / 1024)} KB`],
                    ['Gzip-Schätzung (~30%)', `${num.format(bytes * 0.3 / 1024)} KB`],
                    ['Gültiges JSON', (() => { try { JSON.parse(raw); return 'ja'; } catch (e) { return 'nein'; } })()],
                ]);
            }
            case 'zeichen-bytes-rechner': {
                const text = value('text');
                const bytes = new TextEncoder().encode(text).length;
                return rows([
                    ['Zeichen', num.format([...text].length)],
                    ['Bytes (UTF-8)', num.format(bytes)],
                    ['Bytes pro Zeichen (Schnitt)', num.format(text.length ? bytes / [...text].length : 0)],
                    ['Mehrbyte-Zeichen', num.format(bytes - text.length)],
                    ['In Kilobyte', `${num.format(bytes / 1024)} KB`],
                    ['Bits', num.format(bytes * 8)],
                    ['Nur ASCII', bytes === [...text].length ? 'ja' : 'nein'],
                ]);
            }
            case 'farbe-invertieren': {
                const ph = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const th = a => '#' + a.map(v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0')).join('');
                const rgb = ph(value('hex'));
                const inv = rgb.map(v => 255 - v);
                return rows([
                    ['Invertierte Farbe', th(inv)],
                    ['Invertiert RGB', `rgb(${inv.join(', ')})`],
                    ['Original', th(rgb)],
                    ['Original RGB', `rgb(${rgb.join(', ')})`],
                    ['Summe je Kanal', '255'],
                    ['Hinweis', 'Einfache RGB-Invertierung (keine HSL-Drehung)'],
                ]);
            }
            case 'farbe-graustufe': {
                const ph = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const th = a => '#' + a.map(v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0')).join('');
                const [r, g, b] = ph(value('hex'));
                const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
                return rows([
                    ['Graustufe', th([gray, gray, gray])],
                    ['Grauwert', num.format(gray)],
                    ['Helligkeit', `${num.format(gray / 255 * 100)} %`],
                    ['Original', th([r, g, b])],
                    ['Eher hell oder dunkel', gray >= 128 ? 'hell' : 'dunkel'],
                    ['Empfohlene Textfarbe', gray >= 140 ? 'schwarz' : 'weiss'],
                ]);
            }
            case 'svg-viewbox-rechner': {
                const g = gcd(Math.round(n('w')), Math.round(n('h'))) || 1;
                return rows([
                    ['ViewBox', `0 0 ${num.format(n('w'))} ${num.format(n('h'))}`],
                    ['Seitenverhältnis', `${num.format(n('w') / g)}:${num.format(n('h') / g)}`],
                    ['Ratio dezimal', num.format(n('h') ? n('w') / n('h') : 0)],
                    ['Höhe bei 100% Breite', `${num.format(n('w') ? n('h') / n('w') * 100 : 0)} %`],
                    ['Breite bei Höhe 400', `${num.format(n('h') ? n('w') / n('h') * 400 : 0)} px`],
                    ['preserveAspectRatio', 'xMidYMid meet'],
                    ['Größe', `${num.format(n('w'))} x ${num.format(n('h'))}`],
                ]);
            }
            case 'ip-klasse-rechner': {
                const parts = value('ip').split('.').map(Number);
                if (parts.length !== 4 || parts.some(p => !Number.isInteger(p) || p < 0 || p > 255)) return rows([['Fehler', 'Bitte eine gültige IPv4-Adresse eingeben']]);
                const o = parts[0];
                const cls = o < 128 ? 'A' : o < 192 ? 'B' : o < 224 ? 'C' : o < 240 ? 'D (Multicast)' : 'E (reserviert)';
                const mask = o < 128 ? '255.0.0.0 (/8)' : o < 192 ? '255.255.0.0 (/16)' : o < 224 ? '255.255.255.0 (/24)' : '-';
                const isPrivate = o === 10 || (o === 172 && parts[1] >= 16 && parts[1] <= 31) || (o === 192 && parts[1] === 168);
                return rows([
                    ['IP-Adresse', escapeHtml(value('ip'))],
                    ['Klasse', cls],
                    ['Standardmaske', mask],
                    ['Typ', isPrivate ? 'privat' : o === 127 ? 'Loopback' : 'oeffentlich'],
                    ['Erstes Oktett', num.format(o)],
                    ['Loopback', o === 127 ? 'ja' : 'nein'],
                    ['Hinweis', 'Klassen heute durch CIDR abgeloest'],
                ]);
            }
            case 'port-spicker': {
                const map = { 20: 'FTP-Daten', 21: 'FTP', 22: 'SSH', 23: 'Telnet', 25: 'SMTP', 53: 'DNS', 67: 'DHCP', 80: 'HTTP', 110: 'POP3', 143: 'IMAP', 161: 'SNMP', 443: 'HTTPS', 465: 'SMTPS', 587: 'SMTP (Submission)', 993: 'IMAPS', 995: 'POP3S', 3306: 'MySQL', 5432: 'PostgreSQL', 6379: 'Redis', 8080: 'HTTP alternativ', 27017: 'MongoDB', 3389: 'RDP' };
                const port = Math.round(n('port'));
                const range = port < 1024 ? 'Well-known (System)' : port < 49152 ? 'Registriert' : 'Dynamisch/Privat';
                return rows([
                    ['Port', num.format(port)],
                    ['Dienst', map[port] || 'kein Standarddienst'],
                    ['Bereich', range],
                    ['Privilegiert (<1024)', port < 1024 ? 'ja' : 'nein'],
                    ['Gültig', port >= 0 && port <= 65535 ? 'ja' : 'nein (0-65535)'],
                    ['Protokoll (typisch)', map[port] ? 'TCP' : '-'],
                ]);
            }
            case 'user-agent-parser': {
                const ua = value('ua');
                const browser = /Edg\//.test(ua) ? 'Edge' : /OPR\/|Opera/.test(ua) ? 'Opera' : /Chrome\//.test(ua) ? 'Chrome' : /Firefox\//.test(ua) ? 'Firefox' : /Safari\//.test(ua) ? 'Safari' : 'unbekannt';
                const os = /Windows NT 10/.test(ua) ? 'Windows 10/11' : /Windows/.test(ua) ? 'Windows' : /Mac OS X|Macintosh/.test(ua) ? 'macOS' : /Android/.test(ua) ? 'Android' : /iPhone|iPad|iOS/.test(ua) ? 'iOS' : /Linux/.test(ua) ? 'Linux' : 'unbekannt';
                const mobile = /Mobile|Android|iPhone/.test(ua);
                const ver = (ua.match(/(?:Chrome|Firefox|Version|Edg)\/(\d+)/) || [])[1] || '-';
                return rows([
                    ['Browser', browser],
                    ['Version', ver],
                    ['Betriebssystem', os],
                    ['Gerätetyp', mobile ? 'Mobil' : 'Desktop'],
                    ['Engine', /Gecko\/|Firefox/.test(ua) && !/like Gecko/.test(ua) ? 'Gecko' : /WebKit/.test(ua) ? 'WebKit/Blink' : 'unbekannt'],
                    ['Länge', `${num.format(ua.length)} Zeichen`],
                ]);
            }
            case 'slug-zu-titel': {
                const title = value('slug').replace(/[-_]+/g, ' ').replace(/\.[a-z0-9]+$/i, '').trim().replace(/\b\w/g, c => c.toUpperCase());
                return rows([
                    ['Titel', escapeHtml(title)],
                    ['Woerter', num.format(title.split(/\s+/).filter(Boolean).length)],
                    ['Zeichen', num.format(title.length)],
                    ['Original-Slug', escapeHtml(value('slug'))],
                    ['Kleinschreibung', escapeHtml(title.toLowerCase())],
                    ['Grossschreibung', escapeHtml(title.toUpperCase())],
                ]);
            }
            case 'case-konverter': {
                const wordsArr = value('text').toLowerCase().match(/[a-z0-9äöüß]+/gi) || [];
                const camel = wordsArr.map((w, i) => i === 0 ? w.toLowerCase() : w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
                const pascal = wordsArr.map(w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join('');
                return rows([
                    ['camelCase', escapeHtml(camel)],
                    ['PascalCase', escapeHtml(pascal)],
                    ['snake_case', escapeHtml(wordsArr.join('_').toLowerCase())],
                    ['kebab-case', escapeHtml(wordsArr.join('-').toLowerCase())],
                    ['CONSTANT_CASE', escapeHtml(wordsArr.join('_').toUpperCase())],
                    ['Title Case', escapeHtml(wordsArr.map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '))],
                    ['Woerter', num.format(wordsArr.length)],
                ]);
            }
            case 'tippgeschwindigkeit-rechner': {
                const minutes = n('seconds') / 60;
                const wpm = minutes ? n('words') / minutes : 0;
                return rows([
                    ['Woerter pro Minute (WPM)', num.format(wpm)],
                    ['Anschlaege pro Minute (ca.)', num.format(wpm * 5)],
                    ['Getippte Woerter', num.format(n('words'))],
                    ['Zeit', `${num.format(n('seconds'))} s`],
                    ['Einordnung', wpm >= 70 ? 'sehr schnell' : wpm >= 40 ? 'gut' : wpm >= 25 ? 'durchschnittlich' : 'Anfaenger'],
                    ['Hochrechnung 1 Stunde', `${num.format(wpm * 60)} Woerter`],
                    ['Profi-Niveau ab', '65 WPM'],
                ]);
            }
            case 'text-entropie-rechner': {
                const text = value('text');
                const freq = {};
                for (const ch of text) freq[ch] = (freq[ch] || 0) + 1;
                let entropy = 0;
                const len = text.length || 1;
                for (const k in freq) { const p = freq[k] / len; entropy -= p * Math.log2(p); }
                return rows([
                    ['Entropie', `${num.format(entropy)} Bit/Zeichen`],
                    ['Gesamt-Information', `${num.format(entropy * text.length)} Bit`],
                    ['Zeichen', num.format(text.length)],
                    ['Eindeutige Zeichen', num.format(Object.keys(freq).length)],
                    ['Maximale Entropie', `${num.format(Math.log2(Object.keys(freq).length || 1))} Bit`],
                    ['Redundanz', `${num.format(Object.keys(freq).length > 1 ? (1 - entropy / Math.log2(Object.keys(freq).length)) * 100 : 0)} %`],
                ]);
            }
            case 'fortschritt-rechner': {
                const pct = n('total') ? n('done') / n('total') * 100 : 0;
                const bars = Math.round(clamp(pct, 0, 100) / 10);
                return rows([
                    ['Fortschritt', `${num.format(pct)} %`],
                    ['Balken', '█'.repeat(bars) + '░'.repeat(10 - bars)],
                    ['Erledigt', num.format(n('done'))],
                    ['Gesamt', num.format(n('total'))],
                    ['Verbleibend', num.format(Math.max(0, n('total') - n('done')))],
                    ['Verbleibend in Prozent', `${num.format(100 - pct)} %`],
                    ['Status', pct >= 100 ? 'fertig' : pct >= 50 ? 'über die Haelfte' : 'in Arbeit'],
                ]);
            }
            case 'timestamp-differenz-rechner': {
                const diff = Math.abs(n('ts2') - n('ts1'));
                return rows([
                    ['Differenz', duration(diff)],
                    ['Sekunden', num.format(diff)],
                    ['Minuten', num.format(diff / 60)],
                    ['Stunden', num.format(diff / 3600)],
                    ['Tage', num.format(diff / 86400)],
                    ['Wochen', num.format(diff / 604800)],
                    ['Datum 1', new Date(n('ts1') * 1000).toLocaleString('de-DE')],
                    ['Datum 2', new Date(n('ts2') * 1000).toLocaleString('de-DE')],
                ]);
            }
            case 'gradient-stops-rechner': {
                const count = clamp(Math.round(n('count')), 2, 12);
                const stops = Array.from({ length: count }, (_, i) => `${num.format(i / (count - 1) * 100)}%`);
                return rows([
                    ['Stops', stops.join(', ')],
                    ['Anzahl', num.format(count)],
                    ['Abstand', `${num.format(100 / (count - 1))} %`],
                    ['Erster Stop', '0%'],
                    ['Letzter Stop', '100%'],
                    ['Beispiel', escapeHtml(`linear-gradient(90deg, ${stops.map(s => `#000 ${s}`).join(', ')})`)],
                ]);
            }
            case 'farbe-aufhellen-abdunkeln-rechner': {
                const ph = h => { const c = String(h).replace('#', '').trim(); const v = c.length === 3 ? c.split('').map(x => x + x).join('') : (c + '000000').slice(0, 6); return [0, 2, 4].map(i => parseInt(v.slice(i, i + 2), 16) || 0); };
                const th = a => '#' + a.map(v => clamp(Math.round(v), 0, 255).toString(16).padStart(2, '0')).join('');
                const rgb = ph(value('hex'));
                const p = clamp(n('percent'), 0, 100) / 100;
                const lighter = rgb.map(v => v + (255 - v) * p);
                const darker = rgb.map(v => v * (1 - p));
                return rows([
                    ['Heller', th(lighter)],
                    ['Dunkler', th(darker)],
                    ['Original', th(rgb)],
                    ['Heller RGB', `rgb(${lighter.map(v => Math.round(v)).join(', ')})`],
                    ['Dunkler RGB', `rgb(${darker.map(v => Math.round(v)).join(', ')})`],
                    ['Veraenderung', `${num.format(n('percent'))} %`],
                ]);
            }
            case 'trockner-kosten-rechner': {
                const perLoad = n('kwh') * n('price');
                const year = perLoad * n('loads') * 52;
                return rows([
                    ['Kosten pro Ladung', euro.format(perLoad)],
                    ['Kosten pro Woche', euro.format(perLoad * n('loads'))],
                    ['Kosten pro Monat', euro.format(year / 12)],
                    ['Kosten pro Jahr', euro.format(year)],
                    ['Verbrauch pro Jahr', `${num.format(n('kwh') * n('loads') * 52)} kWh`],
                    ['Ladungen pro Jahr', num.format(n('loads') * 52)],
                    ['Verbrauch pro Ladung', `${num.format(n('kwh'))} kWh`],
                ]);
            }
            case 'geschirrspueler-kosten-rechner': {
                const perLoad = n('kwh') * n('priceKwh') + n('water') / 1000 * n('priceWater');
                const year = perLoad * n('loads') * 52;
                return rows([
                    ['Kosten pro Spuelgang', euro.format(perLoad)],
                    ['Kosten pro Woche', euro.format(perLoad * n('loads'))],
                    ['Kosten pro Jahr', euro.format(year)],
                    ['Stromanteil', euro.format(n('kwh') * n('priceKwh'))],
                    ['Wasseranteil', euro.format(n('water') / 1000 * n('priceWater'))],
                    ['Spuelgaenge pro Jahr', num.format(n('loads') * 52)],
                    ['Wasser pro Jahr', `${num.format(n('water') * n('loads') * 52 / 1000)} m3`],
                ]);
            }
            case 'kuehlschrank-kosten-rechner': {
                const cost = n('kwhYear') * n('price');
                return rows([
                    ['Kosten pro Jahr', euro.format(cost)],
                    ['Kosten pro Monat', euro.format(cost / 12)],
                    ['Kosten pro Tag', euro.format(cost / 365)],
                    ['Jahresverbrauch', `${num.format(n('kwhYear'))} kWh`],
                    ['Verbrauch pro Tag', `${num.format(n('kwhYear') / 365)} kWh`],
                    ['Kosten über 10 Jahre', euro.format(cost * 10)],
                    ['Einordnung', n('kwhYear') < 120 ? 'sehr sparsam' : n('kwhYear') < 200 ? 'normal' : 'hoch'],
                ]);
            }
            case 'backofen-kosten-rechner': {
                const perUse = n('kw') * (n('minutes') / 60) * n('price');
                const year = perUse * n('uses') * 52;
                return rows([
                    ['Kosten pro Nutzung', euro.format(perUse)],
                    ['Kosten pro Woche', euro.format(perUse * n('uses'))],
                    ['Kosten pro Jahr', euro.format(year)],
                    ['Verbrauch pro Nutzung', `${num.format(n('kw') * n('minutes') / 60)} kWh`],
                    ['Nutzungen pro Jahr', num.format(n('uses') * 52)],
                    ['Kosten pro Stunde', euro.format(n('kw') * n('price'))],
                    ['Leistung', `${num.format(n('kw'))} kW`],
                ]);
            }
            case 'klimaanlage-kosten-rechner': {
                const perDay = n('watt') / 1000 * n('hours') * n('price');
                const total = perDay * n('days');
                return rows([
                    ['Kosten Saison', euro.format(total)],
                    ['Kosten pro Tag', euro.format(perDay)],
                    ['Kosten pro Stunde', euro.format(n('watt') / 1000 * n('price'))],
                    ['Kosten pro Monat', euro.format(perDay * 30)],
                    ['Verbrauch Saison', `${num.format(n('watt') / 1000 * n('hours') * n('days'))} kWh`],
                    ['Leistung', `${num.format(n('watt'))} W`],
                    ['Laufzeit Saison', `${num.format(n('hours') * n('days'))} h`],
                ]);
            }
            case 'gasverbrauch-rechner': {
                const kwh = n('m3') * n('brennwert');
                const total = kwh * n('price') + n('base');
                return rows([
                    ['Jahreskosten', euro.format(total)],
                    ['Energie', `${num.format(kwh)} kWh`],
                    ['Arbeitspreis', euro.format(kwh * n('price'))],
                    ['Grundpreis', euro.format(n('base'))],
                    ['Kosten pro Monat', euro.format(total / 12)],
                    ['Kosten pro m3', euro.format(n('m3') ? total / n('m3') : 0)],
                    ['Gasverbrauch', `${num.format(n('m3'))} m3`],
                ]);
            }
            case 'warmwasser-kosten-rechner': {
                const litersYear = n('persons') * n('liters') * 365;
                const kwh = litersYear * 1.163 * n('tempRise') / 1000;
                const cost = kwh * n('price');
                return rows([
                    ['Kosten pro Jahr', euro.format(cost)],
                    ['Kosten pro Monat', euro.format(cost / 12)],
                    ['Energie pro Jahr', `${num.format(kwh)} kWh`],
                    ['Warmwasser pro Jahr', `${num.format(litersYear / 1000)} m3`],
                    ['Kosten pro Person', euro.format(n('persons') ? cost / n('persons') : 0)],
                    ['Pro Tag', euro.format(cost / 365)],
                    ['Erwaermung', `${num.format(n('tempRise'))} Grad C`],
                ]);
            }
            case 'stromverbrauch-haushalt-rechner': {
                const base = value('type') === 'haus' ? [2300, 3000, 3500, 4000, 4500] : [1300, 2000, 2500, 3000, 3500];
                const idx = clamp(Math.round(n('persons')) - 1, 0, 4);
                const kwh = base[idx];
                const cost = kwh * n('price');
                return rows([
                    ['Typischer Jahresverbrauch', `${num.format(kwh)} kWh`],
                    ['Jahreskosten', euro.format(cost)],
                    ['Kosten pro Monat', euro.format(cost / 12)],
                    ['Verbrauch pro Person', `${num.format(n('persons') ? kwh / n('persons') : 0)} kWh`],
                    ['Wohnform', value('type') === 'haus' ? 'Haus' : 'Wohnung'],
                    ['Personen', num.format(n('persons'))],
                    ['Pro Tag', `${num.format(kwh / 365)} kWh`],
                ]);
            }
            case 'photovoltaik-eigenverbrauch-rechner': {
                const self = n('kwh') * n('selfPct') / 100;
                const feed = n('kwh') - self;
                const saving = self * n('price');
                const feedIncome = feed * n('feed');
                return rows([
                    ['Nutzen pro Jahr', euro.format(saving + feedIncome)],
                    ['Ersparnis Eigenverbrauch', euro.format(saving)],
                    ['Einspeisevergütung', euro.format(feedIncome)],
                    ['Eigenverbrauch', `${num.format(self)} kWh`],
                    ['Einspeisung', `${num.format(feed)} kWh`],
                    ['Jahresertrag', `${num.format(n('kwh'))} kWh`],
                    ['Nutzen über 20 Jahre', euro.format((saving + feedIncome) * 20)],
                ]);
            }
            case 'stromzaehler-ablesen-rechner': {
                const used = Math.max(0, n('end') - n('start'));
                const cost = used * n('price');
                const perDay = n('days') ? used / n('days') : 0;
                return rows([
                    ['Verbrauch', `${num.format(used)} kWh`],
                    ['Kosten', euro.format(cost)],
                    ['Verbrauch pro Tag', `${num.format(perDay)} kWh`],
                    ['Kosten pro Tag', euro.format(perDay * n('price'))],
                    ['Hochrechnung pro Jahr', `${num.format(perDay * 365)} kWh`],
                    ['Jahreskosten (Hochrechnung)', euro.format(perDay * 365 * n('price'))],
                    ['Zeitraum', `${num.format(n('days'))} Tage`],
                ]);
            }
            case 'heizkoerper-leistung-rechner': {
                const factor = { gut: 30, mittel: 60, alt: 100 }[value('standard')] || 60;
                const volume = n('area') * n('height');
                const power = volume * factor;
                return rows([
                    ['Heizleistung', `${num.format(power)} W`],
                    ['Heizleistung', `${num.format(power / 1000)} kW`],
                    ['Raumvolumen', `${num.format(volume)} m3`],
                    ['Leistung pro m3', `${num.format(factor)} W`],
                    ['Raumfläche', `${num.format(n('area'))} m2`],
                    ['Daemmstandard', value('standard')],
                    ['Reserve +20%', `${num.format(power * 1.2)} W`],
                ]);
            }
            case 'wandfarbe-rechner': {
                const liters = n('coverage') ? n('area') * n('coats') / n('coverage') : 0;
                return rows([
                    ['Benötigte Farbe', `${num.format(liters)} Liter`],
                    ['Mit 10% Reserve', `${num.format(liters * 1.1)} Liter`],
                    ['Wandfläche', `${num.format(n('area'))} m2`],
                    ['Gestrichene Fläche', `${num.format(n('area') * n('coats'))} m2`],
                    ['Anstriche', num.format(n('coats'))],
                    ['Ergiebigkeit', `${num.format(n('coverage'))} m2/Liter`],
                    ['Eimer a 10 Liter', num.format(Math.ceil(liters / 10))],
                ]);
            }
            case 'tapeten-rechner': {
                const stripsPerRoll = Math.floor(n('rollLength') / (n('height') + 0.1));
                const stripsNeeded = n('rollWidth') > 0 ? Math.ceil(n('perimeter') / n('rollWidth')) : 0;
                const rolls = stripsPerRoll ? Math.ceil(stripsNeeded / stripsPerRoll) : 0;
                return rows([
                    ['Benötigte Rollen', num.format(rolls)],
                    ['Bahnen gesamt', num.format(stripsNeeded)],
                    ['Bahnen pro Rolle', num.format(stripsPerRoll)],
                    ['Mit Reserve', num.format(rolls + 1)],
                    ['Raumumfang', `${num.format(n('perimeter'))} m`],
                    ['Raumhöhe', `${num.format(n('height'))} m`],
                    ['Fläche ca.', `${num.format(n('perimeter') * n('height'))} m2`],
                ]);
            }
            case 'fliesen-rechner': {
                const tileM2 = n('tileW') / 100 * n('tileH') / 100;
                const base = tileM2 ? n('area') / tileM2 : 0;
                const total = Math.ceil(base * (1 + n('waste') / 100));
                return rows([
                    ['Benötigte Fliesen', num.format(total)],
                    ['Ohne Verschnitt', num.format(Math.ceil(base))],
                    ['Verschnitt-Stück', num.format(total - Math.ceil(base))],
                    ['Fläche je Fliese', `${num.format(tileM2)} m2`],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Fliesengröße', `${num.format(n('tileW'))} x ${num.format(n('tileH'))} cm`],
                    ['Verschnitt', `${num.format(n('waste'))} %`],
                ]);
            }
            case 'laminat-rechner': {
                const need = n('area') * (1 + n('waste') / 100);
                const packs = n('packArea') ? Math.ceil(need / n('packArea')) : 0;
                return rows([
                    ['Benötigte Pakete', num.format(packs)],
                    ['Fläche inkl. Verschnitt', `${num.format(need)} m2`],
                    ['Abgedeckte Fläche', `${num.format(packs * n('packArea'))} m2`],
                    ['Raumfläche', `${num.format(n('area'))} m2`],
                    ['Inhalt pro Paket', `${num.format(n('packArea'))} m2`],
                    ['Verschnitt', `${num.format(n('waste'))} %`],
                    ['Rest', `${num.format(packs * n('packArea') - n('area'))} m2`],
                ]);
            }
            case 'beton-rechner': {
                const vol = n('length') * n('width') * n('depth');
                const bags = Math.ceil(vol / 0.0125);
                return rows([
                    ['Betonvolumen', `${num.format(vol)} m3`],
                    ['In Liter', `${num.format(vol * 1000)} Liter`],
                    ['Gewicht (~2400 kg/m3)', `${num.format(vol * 2400)} kg`],
                    ['Saecke a 40 kg (ca.)', num.format(bags)],
                    ['Mit 10% Reserve', `${num.format(vol * 1.1)} m3`],
                    ['Masse', `${num.format(n('length'))} x ${num.format(n('width'))} x ${num.format(n('depth'))} m`],
                    ['Hinweis', '1 Sack 40 kg ergibt ca. 12,5 Liter'],
                ]);
            }
            case 'estrich-rechner': {
                const vol = n('area') * n('thickness') / 100;
                return rows([
                    ['Estrichvolumen', `${num.format(vol)} m3`],
                    ['In Liter', `${num.format(vol * 1000)} Liter`],
                    ['Gewicht (~2000 kg/m3)', `${num.format(vol * 2000)} kg`],
                    ['Saecke a 40 kg (ca.)', num.format(Math.ceil(vol * 2000 / 40))],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Schichtdicke', `${num.format(n('thickness'))} cm`],
                    ['Mit 5% Reserve', `${num.format(vol * 1.05)} m3`],
                ]);
            }
            case 'rasen-saatgut-rechner': {
                const grams = n('area') * n('rate');
                return rows([
                    ['Benötigtes Saatgut', `${num.format(grams / 1000)} kg`],
                    ['In Gramm', `${num.format(grams)} g`],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Saatdichte', `${num.format(n('rate'))} g/m2`],
                    ['Nachsaat (halbe Menge)', `${num.format(grams / 2000)} kg`],
                    ['Mit 10% Reserve', `${num.format(grams * 1.1 / 1000)} kg`],
                    ['Packungen a 1 kg', num.format(Math.ceil(grams / 1000))],
                ]);
            }
            case 'duenger-rechner': {
                const grams = n('area') * n('rate');
                return rows([
                    ['Benötigter Duenger', `${num.format(grams / 1000)} kg`],
                    ['In Gramm', `${num.format(grams)} g`],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Dosierung', `${num.format(n('rate'))} g/m2`],
                    ['Pro 100 m2', `${num.format(n('rate') * 100 / 1000)} kg`],
                    ['Packungen a 5 kg', num.format(Math.ceil(grams / 5000))],
                    ['Reicht (10-kg-Sack) für', `${num.format(n('rate') ? 10000 / n('rate') : 0)} m2`],
                ]);
            }
            case 'mulch-rechner': {
                const vol = n('area') * n('depth') / 100;
                return rows([
                    ['Mulch-Volumen', `${num.format(vol)} m3`],
                    ['In Liter', `${num.format(vol * 1000)} Liter`],
                    ['Saecke a 50 Liter', num.format(Math.ceil(vol * 1000 / 50))],
                    ['Saecke a 70 Liter', num.format(Math.ceil(vol * 1000 / 70))],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Schichthöhe', `${num.format(n('depth'))} cm`],
                    ['Gewicht (~250 kg/m3)', `${num.format(vol * 250)} kg`],
                ]);
            }
            case 'blumenerde-rechner': {
                const liters = n('length') / 100 * n('width') / 100 * n('depth') / 100 * 1000;
                return rows([
                    ['Benötigte Erde', `${num.format(liters)} Liter`],
                    ['In m3', `${num.format(liters / 1000)} m3`],
                    ['Saecke a 40 Liter', num.format(Math.ceil(liters / 40))],
                    ['Saecke a 70 Liter', num.format(Math.ceil(liters / 70))],
                    ['Masse', `${num.format(n('length'))} x ${num.format(n('width'))} x ${num.format(n('depth'))} cm`],
                    ['Gewicht (~0,4 kg/L)', `${num.format(liters * 0.4)} kg`],
                    ['Mit 10% Reserve', `${num.format(liters * 1.1)} Liter`],
                ]);
            }
            case 'zaun-pfosten-rechner': {
                const sections = Math.ceil(n('length') / Math.max(0.1, n('spacing')));
                const posts = sections + 1;
                return rows([
                    ['Pfosten', num.format(posts)],
                    ['Zaunelemente', num.format(sections)],
                    ['Zaunlänge', `${num.format(n('length'))} m`],
                    ['Pfostenabstand', `${num.format(n('spacing'))} m`],
                    ['Tatsaechlicher Abstand', `${num.format(sections ? n('length') / sections : 0)} m`],
                    ['Beton-Saecke (1/Pfosten)', num.format(posts)],
                    ['Hinweis', 'Tore/Ecken extra einplanen'],
                ]);
            }
            case 'streusalz-rechner': {
                const grams = n('area') * n('rate');
                return rows([
                    ['Benötigtes Streugut', `${num.format(grams / 1000)} kg`],
                    ['In Gramm', `${num.format(grams)} g`],
                    ['Fläche', `${num.format(n('area'))} m2`],
                    ['Dosierung', `${num.format(n('rate'))} g/m2`],
                    ['Pro Streuung', `${num.format(grams / 1000)} kg`],
                    ['Reicht (25-kg-Sack) für', `${num.format(n('rate') ? 25000 / n('rate') : 0)} m2`],
                    ['Saecke a 25 kg', num.format(Math.ceil(grams / 25000))],
                ]);
            }
            case 'brennholz-bedarf-rechner': {
                const kwh = n('area') * n('demand');
                const rm = n('kwhPerRm') ? kwh / n('kwhPerRm') : 0;
                return rows([
                    ['Brennholzbedarf', `${num.format(rm)} Raummeter`],
                    ['Energiebedarf', `${num.format(kwh)} kWh`],
                    ['Schuettraummeter (x1,5)', `${num.format(rm * 1.5)} SRM`],
                    ['Festmeter (x0,7)', `${num.format(rm * 0.7)} FM`],
                    ['Beheizte Fläche', `${num.format(n('area'))} m2`],
                    ['Energie pro Raummeter', `${num.format(n('kwhPerRm'))} kWh`],
                    ['Gewicht ca. (~400 kg/RM)', `${num.format(rm * 400)} kg`],
                ]);
            }
            case 'raumvolumen-rechner': {
                const vol = n('length') * n('width') * n('height');
                return rows([
                    ['Raumvolumen', `${num.format(vol)} m3`],
                    ['Grundfläche', `${num.format(n('length') * n('width'))} m2`],
                    ['Luft pro Stosslueftung', `${num.format(vol)} m3`],
                    ['Bei 0,5-fachem Luftwechsel/h', `${num.format(vol * 0.5)} m3/h`],
                    ['Heizleistung grob (60 W/m3)', `${num.format(vol * 60)} W`],
                    ['Klima-Empfehlung (~100 W/m2)', `${num.format(n('length') * n('width') * 100)} W`],
                    ['Masse', `${num.format(n('length'))} x ${num.format(n('width'))} x ${num.format(n('height'))} m`],
                ]);
            }
            case 'weltzeit-rechner': {
                const src = n('hour') + n('minute') / 60;
                let t = ((src - n('from') + n('to')) % 24 + 24) % 24;
                const h = Math.floor(t);
                const m = Math.round((t - h) * 60);
                const dayShift = src - n('from') + n('to');
                const fmt2 = x => String(x).padStart(2, '0');
                return rows([
                    ['Zielzeit', `${fmt2(h)}:${fmt2(m % 60)} Uhr`],
                    ['Ausgangszeit', `${fmt2(Math.floor(src))}:${fmt2(n('minute'))} Uhr`],
                    ['Zeitverschiebung', `${num.format(n('to') - n('from'))} Stunden`],
                    ['Tagesgrenze', dayShift >= 24 ? 'nächster Tag' : dayShift < 0 ? 'Vortag' : 'gleicher Tag'],
                    ['Von UTC', `${n('from') >= 0 ? '+' : ''}${num.format(n('from'))}`],
                    ['Nach UTC', `${n('to') >= 0 ? '+' : ''}${num.format(n('to'))}`],
                ]);
            }
            case 'zeit-einheiten-rechner': {
                const factor = { s: 1, min: 60, h: 3600, d: 86400 }[value('unit')] || 1;
                const sec = n('value') * factor;
                return rows([
                    ['Sekunden', num.format(sec)],
                    ['Minuten', num.format(sec / 60)],
                    ['Stunden', num.format(sec / 3600)],
                    ['Tage', num.format(sec / 86400)],
                    ['Wochen', num.format(sec / 604800)],
                    ['Lesbar', duration(sec)],
                    ['Eingabe', `${num.format(n('value'))} ${value('unit')}`],
                ]);
            }
            case 'dezimalzeit-rechner': {
                const dec = n('hours') + n('minutes') / 60;
                return rows([
                    ['Dezimalstunden', num.format(dec)],
                    ['Industrieminuten', num.format(dec * 100)],
                    ['Eingabe', `${num.format(n('hours'))}:${String(Math.round(n('minutes'))).padStart(2, '0')} h`],
                    ['Minuten gesamt', num.format(n('hours') * 60 + n('minutes'))],
                    ['Beispiel Lohn (15 EUR/h)', euro.format(dec * 15)],
                    ['Anteil eines Tages', `${num.format(dec / 24 * 100)} %`],
                ]);
            }
            case 'arbeitstage-jahr-rechner': {
                const y = Math.round(n('year'));
                let weekdays = 0;
                for (let m = 0; m < 12; m++) {
                    const days = new Date(y, m + 1, 0).getDate();
                    for (let d = 1; d <= days; d++) { const wd = new Date(y, m, d).getDay(); if (wd !== 0 && wd !== 6) weekdays++; }
                }
                const net = weekdays - n('holidays') - n('vacation');
                return rows([
                    ['Arbeitstage netto', `${num.format(net)} Tage`],
                    ['Werktage im Jahr', `${num.format(weekdays)} Tage`],
                    ['Feiertage abgezogen', num.format(n('holidays'))],
                    ['Urlaubstage abgezogen', num.format(n('vacation'))],
                    ['Arbeitsstunden (8h)', `${num.format(net * 8)} h`],
                    ['Wochenendtage', num.format(365 + (new Date(y, 1, 29).getMonth() === 1 ? 1 : 0) - weekdays)],
                    ['Jahr', num.format(y)],
                ]);
            }
            case 'stunden-addieren-rechner': {
                const total = n('h1') * 60 + n('m1') + n('h2') * 60 + n('m2');
                const h = Math.floor(total / 60);
                const m = Math.round(total % 60);
                return rows([
                    ['Summe', `${num.format(h)}:${String(m).padStart(2, '0')} h`],
                    ['Gesamtminuten', num.format(total)],
                    ['Dezimalstunden', num.format(total / 60)],
                    ['Zeit 1', `${num.format(n('h1'))}:${String(Math.round(n('m1'))).padStart(2, '0')}`],
                    ['Zeit 2', `${num.format(n('h2'))}:${String(Math.round(n('m2'))).padStart(2, '0')}`],
                    ['In Sekunden', num.format(total * 60)],
                ]);
            }
            case 'quartal-rechner': {
                const d = new Date(value('date') + 'T00:00:00');
                if (isNaN(d.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const q = Math.floor(d.getMonth() / 3) + 1;
                const qStart = new Date(d.getFullYear(), (q - 1) * 3, 1);
                const qEnd = new Date(d.getFullYear(), q * 3, 0);
                return rows([
                    ['Quartal', `Q${q} ${d.getFullYear()}`],
                    ['Quartalsbeginn', qStart.toLocaleDateString('de-DE')],
                    ['Quartalsende', qEnd.toLocaleDateString('de-DE')],
                    ['Tag im Quartal', num.format(daysBetween(qStart, d) + 1)],
                    ['Tage im Quartal', num.format(daysBetween(qStart, qEnd) + 1)],
                    ['Verbleibend im Quartal', num.format(daysBetween(d, qEnd))],
                    ['Halbjahr', d.getMonth() < 6 ? 'H1' : 'H2'],
                ]);
            }
            case 'jahr-fortschritt-rechner': {
                const d = new Date(value('date') + 'T12:00:00');
                if (isNaN(d.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const y = d.getFullYear();
                const start = new Date(y, 0, 1);
                const end = new Date(y + 1, 0, 1);
                const totalDays = daysBetween(start, end);
                const passed = daysBetween(start, d);
                const pct = totalDays ? passed / totalDays * 100 : 0;
                return rows([
                    ['Jahr-Fortschritt', `${num.format(pct)} %`],
                    ['Tag im Jahr', `${num.format(passed + 1)} von ${num.format(totalDays)}`],
                    ['Verbleibende Tage', num.format(totalDays - passed)],
                    ['Verbleibende Wochen', num.format((totalDays - passed) / 7)],
                    ['Vergangene Wochen', num.format(passed / 7)],
                    ['Schaltjahr', totalDays === 366 ? 'ja' : 'nein'],
                    ['Datum', d.toLocaleDateString('de-DE')],
                ]);
            }
            case 'rentenbeginn-rechner': {
                const b = new Date(value('birth') + 'T00:00:00');
                if (isNaN(b.getTime())) return rows([['Fehler', 'Bitte ein gültiges Geburtsdatum waehlen']]);
                const retire = new Date(b.getFullYear() + Math.round(n('age')), b.getMonth(), b.getDate());
                const now = new Date(); now.setHours(0, 0, 0, 0);
                const days = daysBetween(now, retire);
                return rows([
                    ['Rentenbeginn', retire.toLocaleDateString('de-DE')],
                    ['Renteneintrittsalter', `${num.format(n('age'))} Jahre`],
                    ['Verbleibend', days > 0 ? `${num.format(days / 365)} Jahre` : 'bereits erreicht'],
                    ['Verbleibende Tage', num.format(Math.max(0, days))],
                    ['Verbleibende Monate', num.format(Math.max(0, days / 30.44))],
                    ['Wochenende', [0, 6].includes(retire.getDay()) ? 'ja' : 'nein'],
                    ['Jahr', num.format(retire.getFullYear())],
                ]);
            }
            case 'mondphase-rechner': {
                const d = new Date(value('date') + 'T12:00:00');
                if (isNaN(d.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const ref = new Date(Date.UTC(2000, 0, 6, 18, 14));
                const synodic = 29.530588853;
                const ageDays = ((d - ref) / 86400000 % synodic + synodic) % synodic;
                const illum = (1 - Math.cos(2 * Math.PI * ageDays / synodic)) / 2 * 100;
                const phase = ageDays < 1.8 ? 'Neumond' : ageDays < 5.5 ? 'zunehmende Sichel' : ageDays < 9.2 ? 'erstes Viertel' : ageDays < 12.9 ? 'zunehmender Mond' : ageDays < 16.6 ? 'Vollmond' : ageDays < 20.3 ? 'abnehmender Mond' : ageDays < 24 ? 'letztes Viertel' : 'abnehmende Sichel';
                return rows([
                    ['Mondphase', phase],
                    ['Beleuchtung', `${num.format(illum)} %`],
                    ['Mondalter', `${num.format(ageDays)} Tage`],
                    ['Tendenz', ageDays < synodic / 2 ? 'zunehmend' : 'abnehmend'],
                    ['Nächster Neumond in', `${num.format(synodic - ageDays)} Tagen`],
                    ['Nächster Vollmond in', `${num.format(((synodic / 2 - ageDays) % synodic + synodic) % synodic)} Tagen`],
                    ['Datum', d.toLocaleDateString('de-DE')],
                ]);
            }
            case 'pendelzeit-jahr-rechner': {
                const perDay = n('minutes') * 2;
                const perWeek = perDay * n('days');
                const perYear = perWeek * 46;
                return rows([
                    ['Pendelzeit pro Tag', `${num.format(perDay)} min`],
                    ['Pro Woche', `${num.format(perWeek / 60)} h`],
                    ['Pro Monat', `${num.format(perYear / 12 / 60)} h`],
                    ['Pro Jahr', `${num.format(perYear / 60)} h`],
                    ['Pro Jahr in Tagen (24h)', num.format(perYear / 60 / 24)],
                    ['Arbeitstage-Aequivalent (8h)', num.format(perYear / 60 / 8)],
                    ['Über 10 Jahre', `${num.format(perYear * 10 / 60)} h`],
                ]);
            }
            case 'datum-plus-werktage-rechner': {
                const d = new Date(value('date') + 'T00:00:00');
                if (isNaN(d.getTime())) return rows([['Fehler', 'Bitte ein gültiges Datum waehlen']]);
                const result = new Date(d);
                let added = 0;
                const target = Math.round(n('days'));
                while (added < target) { result.setDate(result.getDate() + 1); const wd = result.getDay(); if (wd !== 0 && wd !== 6) added++; }
                const names = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'];
                return rows([
                    ['Zieldatum', result.toLocaleDateString('de-DE')],
                    ['Wochentag', names[result.getDay()]],
                    ['Startdatum', d.toLocaleDateString('de-DE')],
                    ['Werktage', num.format(target)],
                    ['Kalendertage gesamt', num.format(daysBetween(d, result))],
                    ['Enthaltene Wochenenden', num.format(daysBetween(d, result) - target)],
                ]);
            }
            case 'uhrzeit-differenz-rechner': {
                let diff = (n('h2') * 60 + n('m2')) - (n('h1') * 60 + n('m1'));
                if (diff < 0) diff += 1440;
                const h = Math.floor(diff / 60);
                const m = Math.round(diff % 60);
                return rows([
                    ['Differenz', `${num.format(h)}:${String(m).padStart(2, '0')} h`],
                    ['In Minuten', num.format(diff)],
                    ['Dezimalstunden', num.format(diff / 60)],
                    ['Start', `${num.format(n('h1'))}:${String(Math.round(n('m1'))).padStart(2, '0')}`],
                    ['Ende', `${num.format(n('h2'))}:${String(Math.round(n('m2'))).padStart(2, '0')}`],
                    ['Über Mitternacht', (n('h2') * 60 + n('m2')) < (n('h1') * 60 + n('m1')) ? 'ja' : 'nein'],
                ]);
            }
            case 'reisezeit-rechner': {
                const driveH = n('speed') ? n('distance') / n('speed') : 0;
                const totalMin = driveH * 60 + n('breaks');
                return rows([
                    ['Reisezeit gesamt', duration(totalMin * 60)],
                    ['Reine Fahrtzeit', duration(driveH * 3600)],
                    ['Pausen', `${num.format(n('breaks'))} min`],
                    ['Strecke', `${num.format(n('distance'))} km`],
                    ['Durchschnitt', `${num.format(n('speed'))} km/h`],
                    ['Effektives Tempo', `${num.format(totalMin ? n('distance') / (totalMin / 60) : 0)} km/h`],
                    ['Ankunft nach', `${num.format(totalMin / 60)} Stunden`],
                ]);
            }
            case 'ankunftszeit-rechner': {
                const total = n('startH') * 60 + n('startM') + n('durH') * 60 + n('durM');
                const h = Math.floor(total / 60) % 24;
                const m = Math.round(total % 60);
                const days = Math.floor(total / 1440);
                return rows([
                    ['Ankunftszeit', `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')} Uhr`],
                    ['Tagesversatz', days > 0 ? `+${days} Tag(e)` : 'gleicher Tag'],
                    ['Startzeit', `${String(Math.round(n('startH'))).padStart(2, '0')}:${String(Math.round(n('startM'))).padStart(2, '0')}`],
                    ['Dauer', `${num.format(n('durH'))} h ${num.format(n('durM'))} min`],
                    ['Dauer in Minuten', num.format(n('durH') * 60 + n('durM'))],
                    ['Dauer dezimal', `${num.format(n('durH') + n('durM') / 60)} h`],
                ]);
            }
            case 'jetlag-rechner': {
                const factor = value('direction') === 'ost' ? 1 : 0.7;
                const days = n('zones') * factor;
                return rows([
                    ['Erholungszeit (ca.)', `${num.format(days)} Tage`],
                    ['Zeitzonen', num.format(n('zones'))],
                    ['Richtung', value('direction') === 'ost' ? 'Osten (schwieriger)' : 'Westen (leichter)'],
                    ['Faustregel', value('direction') === 'ost' ? '~1 Tag pro Zone' : '~0,7 Tage pro Zone'],
                    ['Tipp', 'Vor Ort sofort an Ortszeit anpassen'],
                    ['Spuerbar ab', n('zones') >= 3 ? 'ja' : 'meist gering'],
                ]);
            }
            case 'koffer-gewicht-rechner': {
                const diff = n('limit') - n('weight');
                return rows([
                    ['Status', diff >= 0 ? 'innerhalb des Limits' : 'Übergewicht'],
                    ['Spielraum / Übergewicht', `${num.format(Math.abs(diff))} kg`],
                    ['Koffergewicht', `${num.format(n('weight'))} kg`],
                    ['Limit', `${num.format(n('limit'))} kg`],
                    ['Auslastung', `${num.format(n('limit') ? n('weight') / n('limit') * 100 : 0)} %`],
                    ['Übergepaeck-Gebühr grob', diff < 0 ? euro.format(Math.abs(diff) * 15) : euro.format(0)],
                    ['Hinweis', 'Gebühren je Airline unterschiedlich'],
                ]);
            }
            case 'handgepaeck-volumen-rechner': {
                const volL = n('l') * n('w') * n('h') / 1000;
                const sum = n('l') + n('w') + n('h');
                const ok = n('l') <= 55 && n('w') <= 40 && n('h') <= 23;
                return rows([
                    ['Volumen', `${num.format(volL)} Liter`],
                    ['Masse', `${num.format(n('l'))} x ${num.format(n('w'))} x ${num.format(n('h'))} cm`],
                    ['Summe der Kanten', `${num.format(sum)} cm`],
                    ['Standard 55x40x23', ok ? 'passt' : 'zu gross'],
                    ['Volumen in m3', `${num.format(volL / 1000)} m3`],
                    ['Groesster Wert', `${num.format(Math.max(n('l'), n('w'), n('h')))} cm`],
                    ['Hinweis', 'Limits je Airline unterschiedlich'],
                ]);
            }
            case 'trinkgeld-international-rechner': {
                const pct = { de: 10, us: 18, jp: 0, fr: 5, uk: 12 }[value('country')] || 10;
                const tip = n('bill') * pct / 100;
                return rows([
                    ['Empfohlenes Trinkgeld', euro.format(tip)],
                    ['Trinkgeld-Satz', `${num.format(pct)} %`],
                    ['Gesamt inkl. Trinkgeld', euro.format(n('bill') + tip)],
                    ['Rechnungsbetrag', euro.format(n('bill'))],
                    ['Land', value('country').toUpperCase()],
                    ['Untere Empfehlung', euro.format(n('bill') * Math.max(0, pct - 3) / 100)],
                    ['Hinweis', value('country') === 'jp' ? 'In Japan ist Trinkgeld unueblich' : 'Richtwert für Restaurants'],
                ]);
            }
            case 'fotospeicher-rechner': {
                const count = n('photo') ? n('storage') * 1024 / n('photo') : 0;
                return rows([
                    ['Anzahl Fotos', num.format(Math.floor(count))],
                    ['Speicher', `${num.format(n('storage'))} GB`],
                    ['Fotogröße', `${num.format(n('photo'))} MB`],
                    ['Nutzbar (90%)', num.format(Math.floor(count * 0.9))],
                    ['Speicher in MB', num.format(n('storage') * 1024)],
                    ['Für 5000 Fotos nötig', `${num.format(5000 * n('photo') / 1024)} GB`],
                ]);
            }
            case 'musikspeicher-rechner': {
                const count = n('song') ? n('storage') * 1024 / n('song') : 0;
                return rows([
                    ['Anzahl Songs', num.format(Math.floor(count))],
                    ['Spielzeit (4 min/Song)', duration(count * 4 * 60)],
                    ['Speicher', `${num.format(n('storage'))} GB`],
                    ['Songgröße', `${num.format(n('song'))} MB`],
                    ['Nutzbar (95%)', num.format(Math.floor(count * 0.95))],
                    ['Alben (12 Songs)', num.format(Math.floor(count / 12))],
                ]);
            }
            case 'videospeicher-rechner': {
                const gbPerHour = n('bitrate') * 3600 / 8 / 1024;
                const hours = gbPerHour ? n('storage') / gbPerHour : 0;
                return rows([
                    ['Aufnahmedauer', duration(hours * 3600)],
                    ['Stunden', num.format(hours)],
                    ['Pro Stunde', `${num.format(gbPerHour)} GB`],
                    ['Speicher', `${num.format(n('storage'))} GB`],
                    ['Bitrate', `${num.format(n('bitrate'))} Mbit/s`],
                    ['Pro Minute', `${num.format(gbPerHour / 60 * 1024)} MB`],
                ]);
            }
            case 'akkulaufzeit-rechner': {
                const hours = n('draw') ? n('capacity') / n('draw') : 0;
                return rows([
                    ['Laufzeit', duration(hours * 3600)],
                    ['Stunden', num.format(hours)],
                    ['Mit 80% Effizienz', duration(hours * 0.8 * 3600)],
                    ['Kapazitaet', `${num.format(n('capacity'))} mAh`],
                    ['Stromaufnahme', `${num.format(n('draw'))} mA`],
                    ['Laufzeit in Tagen', num.format(hours / 24)],
                ]);
            }
            case 'powerbank-rechner': {
                const charges = n('phone') ? n('powerbank') * (n('eff') / 100) / n('phone') : 0;
                return rows([
                    ['Mögliche Ladungen', num.format(charges)],
                    ['Volle Ladungen', num.format(Math.floor(charges))],
                    ['Powerbank', `${num.format(n('powerbank'))} mAh`],
                    ['Geräteakku', `${num.format(n('phone'))} mAh`],
                    ['Wirkungsgrad', `${num.format(n('eff'))} %`],
                    ['Nutzbare Kapazitaet', `${num.format(n('powerbank') * n('eff') / 100)} mAh`],
                    ['Verlust', `${num.format(n('powerbank') * (1 - n('eff') / 100))} mAh`],
                ]);
            }
            case 'lebenserwartung-rest-rechner': {
                const years = Math.max(0, n('expectancy') - n('age'));
                const days = years * 365.25;
                return rows([
                    ['Verbleibende Jahre', `${num.format(years)} Jahre`],
                    ['Verbleibende Tage', num.format(days)],
                    ['Verbleibende Wochen', num.format(days / 7)],
                    ['Verbleibende Monate', num.format(years * 12)],
                    ['Verbleibende Sommer', num.format(Math.floor(years))],
                    ['Gelebt in Prozent', `${num.format(n('expectancy') ? n('age') / n('expectancy') * 100 : 0)} %`],
                    ['Hinweis', 'Rein statistischer Denkanstoss'],
                ]);
            }
            case 'schrittziel-zeit-rechner': {
                const remaining = Math.max(0, n('goal') - n('current'));
                const minutes = n('cadence') ? remaining / n('cadence') : 0;
                return rows([
                    ['Nötige Gehzeit', `${num.format(minutes)} min`],
                    ['Verbleibende Schritte', num.format(remaining)],
                    ['Strecke ca. (0,75 m)', `${num.format(remaining * 0.75 / 1000)} km`],
                    ['Bereits gegangen', num.format(n('current'))],
                    ['Ziel', num.format(n('goal'))],
                    ['Fortschritt', `${num.format(n('goal') ? n('current') / n('goal') * 100 : 0)} %`],
                    ['Schrittfrequenz', `${num.format(n('cadence'))} /min`],
                ]);
            }
            case 'prozent-bruch-dezimal-rechner': {
                const dec = value('mode') === 'prozent' ? n('value') / 100 : n('value');
                const den = 10000;
                const g = gcd(Math.round(dec * den), den) || 1;
                return rows([
                    ['Prozent', `${num.format(dec * 100)} %`],
                    ['Dezimalzahl', num.format(dec)],
                    ['Bruch (gekuerzt)', `${num.format(Math.round(dec * den) / g)}/${num.format(den / g)}`],
                    ['Promille', `${num.format(dec * 1000)} ‰`],
                    ['Als x von 100', num.format(dec * 100)],
                    ['Eingabe', `${num.format(n('value'))} (${value('mode')})`],
                ]);
            }
            case 'bruch-dezimal-rechner': {
                const dec = n('den') ? n('num') / n('den') : 0;
                const g = gcd(Math.round(n('num')), Math.round(n('den'))) || 1;
                return rows([
                    ['Dezimalzahl', num.format(dec)],
                    ['Prozent', `${num.format(dec * 100)} %`],
                    ['Gekuerzter Bruch', `${num.format(Math.round(n('num') / g))}/${num.format(Math.round(n('den') / g))}`],
                    ['Bruch', `${num.format(n('num'))}/${num.format(n('den'))}`],
                    ['Kehrwert', num.format(n('num') ? n('den') / n('num') : 0)],
                    ['Ganzzahlanteil', num.format(Math.floor(dec))],
                ]);
            }
            case 'verhaeltnis-rechner': {
                const x = n('a') ? n('b') * n('c') / n('a') : 0;
                return rows([
                    ['x', num.format(x)],
                    ['Proportion', `${num.format(n('a'))} : ${num.format(n('b'))} = ${num.format(n('c'))} : ${num.format(x)}`],
                    ['Verhältnis a:b', num.format(n('b') ? n('a') / n('b') : 0)],
                    ['Faktor c/a', num.format(n('a') ? n('c') / n('a') : 0)],
                    ['Summe c + x', num.format(n('c') + x)],
                    ['Gekuerzt a:b', `${num.format(n('a') / (gcd(Math.round(n('a')), Math.round(n('b'))) || 1))} : ${num.format(n('b') / (gcd(Math.round(n('a')), Math.round(n('b'))) || 1))}`],
                ]);
            }
            case 'prozentpunkte-rechner': {
                const pp = n('new') - n('old');
                const rel = n('old') ? pp / n('old') * 100 : 0;
                return rows([
                    ['Prozentpunkte', `${num.format(pp)} pp`],
                    ['Relative Aenderung', `${num.format(rel)} %`],
                    ['Alter Wert', `${num.format(n('old'))} %`],
                    ['Neuer Wert', `${num.format(n('new'))} %`],
                    ['Richtung', pp > 0 ? 'Anstieg' : pp < 0 ? 'Rueckgang' : 'unveraendert'],
                    ['Merksatz', 'Punkte = absolute, Prozent = relative Aenderung'],
                ]);
            }
            case 'wurzel-rechner': {
                const sqrt = n('number') >= 0 ? Math.sqrt(n('number')) : NaN;
                const nth = n('n') ? (n('number') >= 0 || Math.round(n('n')) % 2 === 1 ? Math.sign(n('number')) * Math.pow(Math.abs(n('number')), 1 / n('n')) : NaN) : 0;
                return rows([
                    ['Quadratwurzel', n('number') >= 0 ? num.format(sqrt) : 'nicht reell'],
                    [`${num.format(n('n'))}-te Wurzel`, Number.isFinite(nth) ? num.format(nth) : 'nicht reell'],
                    ['Kubikwurzel', num.format(Math.cbrt(n('number')))],
                    ['Zahl', num.format(n('number'))],
                    ['Probe (Wurzel^2)', num.format(sqrt * sqrt)],
                    ['4. Wurzel', n('number') >= 0 ? num.format(Math.pow(n('number'), 0.25)) : 'nicht reell'],
                ]);
            }
            case 'logarithmus-rechner': {
                const valid = n('number') > 0 && n('base') > 0 && n('base') !== 1;
                const log = valid ? Math.log(n('number')) / Math.log(n('base')) : NaN;
                return rows([
                    [`log Basis ${num.format(n('base'))}`, valid ? num.format(log) : 'undefiniert'],
                    ['Natuerlicher Log (ln)', n('number') > 0 ? num.format(Math.log(n('number'))) : 'undefiniert'],
                    ['Log Basis 10', n('number') > 0 ? num.format(Math.log10(n('number'))) : 'undefiniert'],
                    ['Log Basis 2', n('number') > 0 ? num.format(Math.log2(n('number'))) : 'undefiniert'],
                    ['Zahl', num.format(n('number'))],
                    ['Probe (Basis^Ergebnis)', valid ? num.format(Math.pow(n('base'), log)) : '-'],
                ]);
            }
            case 'potenz-rechner': {
                const r = Math.pow(n('base'), n('exp'));
                return rows([
                    ['Ergebnis', Number.isFinite(r) ? (Math.abs(r) >= 1e15 ? r.toExponential(6) : num.format(r)) : 'nicht definiert'],
                    ['Basis', num.format(n('base'))],
                    ['Exponent', num.format(n('exp'))],
                    ['Kehrwert (Basis^-Exp)', Number.isFinite(Math.pow(n('base'), -n('exp'))) ? num.format(Math.pow(n('base'), -n('exp'))) : '-'],
                    ['Quadrat der Basis', num.format(n('base') * n('base'))],
                    ['Stellen ca.', Number.isFinite(r) && r > 0 ? num.format(Math.floor(Math.log10(r)) + 1) : '-'],
                ]);
            }
            case 'exponentielles-wachstum-rechner': {
                const factor = Math.pow(1 + n('rate') / 100, n('periods'));
                const end = n('start') * factor;
                const doubling = n('rate') > 0 ? Math.log(2) / Math.log(1 + n('rate') / 100) : 0;
                return rows([
                    ['Endwert', num.format(end)],
                    ['Startwert', num.format(n('start'))],
                    ['Zuwachs', num.format(end - n('start'))],
                    ['Wachstumsfaktor', `${num.format(factor)} x`],
                    ['Verdopplungszeit', doubling ? `${num.format(doubling)} Perioden` : '-'],
                    ['Rate pro Periode', `${num.format(n('rate'))} %`],
                    ['Perioden', num.format(n('periods'))],
                ]);
            }
            case 'halbwertszeit-rechner': {
                const halfLives = n('half') ? n('time') / n('half') : 0;
                const remaining = n('start') * Math.pow(0.5, halfLives);
                return rows([
                    ['Verbleibende Menge', num.format(remaining)],
                    ['Zerfallen', num.format(n('start') - remaining)],
                    ['Anteil verbleibend', `${num.format(Math.pow(0.5, halfLives) * 100)} %`],
                    ['Anzahl Halbwertszeiten', num.format(halfLives)],
                    ['Startmenge', num.format(n('start'))],
                    ['Halbwertszeit', num.format(n('half'))],
                    ['Nach 1 weiterer HWZ', num.format(remaining / 2)],
                ]);
            }
            case 'trigonometrie-rechner': {
                const rad = n('angle') * Math.PI / 180;
                const cos = Math.cos(rad);
                return rows([
                    ['sin', num.format(Math.sin(rad))],
                    ['cos', num.format(cos)],
                    ['tan', Math.abs(cos) < 1e-12 ? 'undefiniert' : num.format(Math.tan(rad))],
                    ['Winkel in Radiant', num.format(rad)],
                    ['cot', Math.abs(Math.sin(rad)) < 1e-12 ? 'undefiniert' : num.format(cos / Math.sin(rad))],
                    ['Winkel', `${num.format(n('angle'))} Grad`],
                    ['Quadrant', `${Math.floor(((n('angle') % 360) + 360) % 360 / 90) + 1}`],
                ]);
            }
            case 'vektor-betrag-rechner': {
                const mag = Math.sqrt(n('x') * n('x') + n('y') * n('y') + n('z') * n('z'));
                return rows([
                    ['Betrag |v|', num.format(mag)],
                    ['Vektor', `(${num.format(n('x'))}, ${num.format(n('y'))}, ${num.format(n('z'))})`],
                    ['Betrag in 2D (x,y)', num.format(Math.sqrt(n('x') * n('x') + n('y') * n('y')))],
                    ['Einheitsvektor x', num.format(mag ? n('x') / mag : 0)],
                    ['Einheitsvektor y', num.format(mag ? n('y') / mag : 0)],
                    ['Einheitsvektor z', num.format(mag ? n('z') / mag : 0)],
                    ['Quadrat der Länge', num.format(mag * mag)],
                ]);
            }
            case 'abstand-punkte-rechner': {
                const dx = n('x2') - n('x1'), dy = n('y2') - n('y1');
                const dist = Math.sqrt(dx * dx + dy * dy);
                return rows([
                    ['Abstand', num.format(dist)],
                    ['Delta x', num.format(dx)],
                    ['Delta y', num.format(dy)],
                    ['Punkt 1', `(${num.format(n('x1'))}, ${num.format(n('y1'))})`],
                    ['Punkt 2', `(${num.format(n('x2'))}, ${num.format(n('y2'))})`],
                    ['Manhattan-Distanz', num.format(Math.abs(dx) + Math.abs(dy))],
                    ['Winkel zur x-Achse', `${num.format(Math.atan2(dy, dx) * 180 / Math.PI)} Grad`],
                ]);
            }
            case 'steigung-rechner': {
                const dx = n('x2') - n('x1'), dy = n('y2') - n('y1');
                const slope = dx ? dy / dx : Infinity;
                return rows([
                    ['Steigung m', Number.isFinite(slope) ? num.format(slope) : 'senkrecht'],
                    ['Steigung in Prozent', Number.isFinite(slope) ? `${num.format(slope * 100)} %` : '-'],
                    ['Steigungswinkel', Number.isFinite(slope) ? `${num.format(Math.atan(slope) * 180 / Math.PI)} Grad` : '90 Grad'],
                    ['Delta y', num.format(dy)],
                    ['Delta x', num.format(dx)],
                    ['y-Achsenabschnitt', Number.isFinite(slope) ? num.format(n('y1') - slope * n('x1')) : '-'],
                    ['Geradengleichung', Number.isFinite(slope) ? `y = ${num.format(slope)}x + ${num.format(n('y1') - slope * n('x1'))}` : 'x = const'],
                ]);
            }
            case 'mittelpunkt-rechner': {
                const mx = (n('x1') + n('x2')) / 2, my = (n('y1') + n('y2')) / 2;
                const dist = Math.sqrt(Math.pow(n('x2') - n('x1'), 2) + Math.pow(n('y2') - n('y1'), 2));
                return rows([
                    ['Mittelpunkt', `(${num.format(mx)}, ${num.format(my)})`],
                    ['Mittelpunkt x', num.format(mx)],
                    ['Mittelpunkt y', num.format(my)],
                    ['Punkt 1', `(${num.format(n('x1'))}, ${num.format(n('y1'))})`],
                    ['Punkt 2', `(${num.format(n('x2'))}, ${num.format(n('y2'))})`],
                    ['Streckenlänge', num.format(dist)],
                    ['Halbe Strecke', num.format(dist / 2)],
                ]);
            }
            case 'modulo-rechner': {
                const b = n('b');
                const mod = b ? ((n('a') % b) + b) % b : 0;
                const quot = b ? Math.floor(n('a') / b) : 0;
                return rows([
                    ['Rest (a mod b)', num.format(b ? n('a') % b : 0)],
                    ['Positiver Rest', num.format(mod)],
                    ['Ganzzahliger Quotient', num.format(quot)],
                    ['Dividend', num.format(n('a'))],
                    ['Divisor', num.format(n('b'))],
                    ['Teilbar', b && n('a') % b === 0 ? 'ja' : 'nein'],
                    ['Probe', `${num.format(quot)} x ${num.format(b)} + ${num.format(b ? n('a') % b : 0)} = ${num.format(quot * b + (b ? n('a') % b : 0))}`],
                ]);
            }
            case 'primfaktor-rechner': {
                let nn = Math.abs(Math.round(n('number')));
                const orig = nn;
                const factors = [];
                for (let d = 2; d * d <= nn; d++) { while (nn % d === 0) { factors.push(d); nn /= d; } }
                if (nn > 1) factors.push(nn);
                const counts = {};
                factors.forEach(f => counts[f] = (counts[f] || 0) + 1);
                const pretty = Object.keys(counts).map(k => counts[k] > 1 ? `${k}^${counts[k]}` : k).join(' x ') || '-';
                return rows([
                    ['Primfaktoren', escapeHtml(pretty)],
                    ['Ausgeschrieben', factors.join(' x ') || '-'],
                    ['Anzahl Faktoren', num.format(factors.length)],
                    ['Verschiedene Primfaktoren', num.format(Object.keys(counts).length)],
                    ['Zahl', num.format(orig)],
                    ['Primzahl', factors.length === 1 ? 'ja' : 'nein'],
                ]);
            }
            case 'teiler-rechner': {
                const nn = Math.abs(Math.round(n('number')));
                const divs = [];
                for (let d = 1; d * d <= nn; d++) { if (nn % d === 0) { divs.push(d); if (d !== nn / d) divs.push(nn / d); } }
                divs.sort((a, b) => a - b);
                const sum = divs.reduce((s, d) => s + d, 0);
                return rows([
                    ['Teiler', escapeHtml(divs.join(', ') || '-')],
                    ['Anzahl Teiler', num.format(divs.length)],
                    ['Teilersumme', num.format(sum)],
                    ['Echte Teiler-Summe', num.format(sum - nn)],
                    ['Vollkommene Zahl', sum - nn === nn ? 'ja' : 'nein'],
                    ['Primzahl', divs.length === 2 ? 'ja' : 'nein'],
                    ['Zahl', num.format(nn)],
                ]);
            }
            case 'quersumme-rechner': {
                const digits = Math.abs(Math.round(n('number'))).toString();
                const sum = digits.split('').reduce((s, d) => s + Number(d), 0);
                let iter = sum;
                while (iter >= 10) iter = iter.toString().split('').reduce((s, d) => s + Number(d), 0);
                return rows([
                    ['Quersumme', num.format(sum)],
                    ['Digitale Wurzel', num.format(iter)],
                    ['Stellen', num.format(digits.length)],
                    ['Zahl', escapeHtml(digits)],
                    ['Teilbar durch 3', sum % 3 === 0 ? 'ja' : 'nein'],
                    ['Teilbar durch 9', sum % 9 === 0 ? 'ja' : 'nein'],
                    ['Alternierende Quersumme', num.format(digits.split('').reduce((s, d, i) => s + (i % 2 ? -1 : 1) * Number(d), 0))],
                ]);
            }
            case 'zahl-runden-rechner': {
                const dec = Math.max(0, Math.round(n('decimals')));
                const f = Math.pow(10, dec);
                return rows([
                    ['Kaufmaennisch gerundet', num.format(Math.round(n('value') * f) / f)],
                    ['Abgerundet', num.format(Math.floor(n('value') * f) / f)],
                    ['Aufgerundet', num.format(Math.ceil(n('value') * f) / f)],
                    ['Auf Ganze', num.format(Math.round(n('value')))],
                    ['Auf 0,5', num.format(Math.round(n('value') * 2) / 2)],
                    ['Original', num.format(n('value'))],
                    ['Dezimalstellen', num.format(dec)],
                ]);
            }
            case 'gewichteter-durchschnitt-rechner': {
                const sumW = n('w1') + n('w2') + n('w3');
                const avg = sumW ? (n('v1') * n('w1') + n('v2') * n('w2') + n('v3') * n('w3')) / sumW : 0;
                const simple = (n('v1') + n('v2') + n('v3')) / 3;
                return rows([
                    ['Gewichteter Durchschnitt', num.format(avg)],
                    ['Einfacher Durchschnitt', num.format(simple)],
                    ['Summe der Gewichte', num.format(sumW)],
                    ['Beitrag Wert 1', num.format(sumW ? n('v1') * n('w1') / sumW : 0)],
                    ['Beitrag Wert 2', num.format(sumW ? n('v2') * n('w2') / sumW : 0)],
                    ['Beitrag Wert 3', num.format(sumW ? n('v3') * n('w3') / sumW : 0)],
                    ['Differenz zu einfach', num.format(avg - simple)],
                ]);
            }
            case 'trapez-flaeche-rechner': {
                const area = (n('a') + n('c')) / 2 * n('h');
                return rows([
                    ['Fläche', num.format(area)],
                    ['Mittellinie', num.format((n('a') + n('c')) / 2)],
                    ['Seite a', num.format(n('a'))],
                    ['Seite c', num.format(n('c'))],
                    ['Höhe', num.format(n('h'))],
                    ['Formel', '(a + c) / 2 x h'],
                ]);
            }
            case 'pyramide-volumen-rechner': {
                const base = n('side') * n('side');
                const vol = base * n('height') / 3;
                return rows([
                    ['Volumen', num.format(vol)],
                    ['Grundfläche', num.format(base)],
                    ['Grundseite', num.format(n('side'))],
                    ['Höhe', num.format(n('height'))],
                    ['Seitenkante', num.format(Math.sqrt(Math.pow(n('side') / 2, 2) * 2 + n('height') * n('height')))],
                    ['Formel', 'Grundfläche x Höhe / 3'],
                ]);
            }
            case 'kegel-volumen-rechner': {
                const r = n('radius'), h = n('height');
                const slant = Math.sqrt(r * r + h * h);
                return rows([
                    ['Volumen', num.format(Math.PI * r * r * h / 3)],
                    ['Volumen in Liter (cm)', num.format(Math.PI * r * r * h / 3 / 1000)],
                    ['Mantelfläche', num.format(Math.PI * r * slant)],
                    ['Oberfläche', num.format(Math.PI * r * (r + slant))],
                    ['Mantellinie', num.format(slant)],
                    ['Grundfläche', num.format(Math.PI * r * r)],
                    ['Radius / Höhe', `${num.format(r)} / ${num.format(h)}`],
                ]);
            }
            case 'ellipse-flaeche-rechner': {
                const a = n('a'), b = n('b');
                const peri = Math.PI * (3 * (a + b) - Math.sqrt((3 * a + b) * (a + 3 * b)));
                return rows([
                    ['Fläche', num.format(Math.PI * a * b)],
                    ['Umfang (Naeherung)', num.format(peri)],
                    ['Grosse Halbachse a', num.format(a)],
                    ['Kleine Halbachse b', num.format(b)],
                    ['Exzentritaet', num.format(a === 0 || b === 0 ? 0 : (a >= b ? Math.sqrt(1 - b * b / (a * a)) : Math.sqrt(1 - a * a / (b * b))))],
                    ['Brennweite', num.format(Math.sqrt(Math.abs(a * a - b * b)))],
                ]);
            }
            case 'rechteck-diagonale-rechner': {
                const d = Math.sqrt(n('w') * n('w') + n('h') * n('h'));
                return rows([
                    ['Diagonale', num.format(d)],
                    ['Fläche', num.format(n('w') * n('h'))],
                    ['Umfang', num.format(2 * (n('w') + n('h')))],
                    ['Breite', num.format(n('w'))],
                    ['Höhe', num.format(n('h'))],
                    ['Seitenverhältnis', num.format(n('h') ? n('w') / n('h') : 0)],
                    ['Winkel der Diagonale', `${num.format(Math.atan2(n('h'), n('w')) * 180 / Math.PI)} Grad`],
                ]);
            }
            case 'produktivitaet-rechner': {
                const perHour = n('hours') ? n('output') / n('hours') : 0;
                return rows([
                    ['Output pro Stunde', num.format(perHour)],
                    ['Output pro Mitarbeiter', num.format(n('people') ? n('output') / n('people') : 0)],
                    ['Output pro Mitarbeiterstunde', num.format(n('hours') * n('people') ? n('output') / (n('hours') * n('people')) : 0)],
                    ['Output gesamt', num.format(n('output'))],
                    ['Stunden gesamt', num.format(n('hours'))],
                    ['Zeit je Einheit', `${num.format(n('output') ? n('hours') * 60 / n('output') : 0)} min`],
                    ['Mitarbeiter', num.format(n('people'))],
                ]);
            }
            case 'arbeitskosten-projekt-rechner': {
                const total = n('hours') * n('rate') * n('people');
                return rows([
                    ['Projektkosten gesamt', euro.format(total)],
                    ['Kosten pro Person', euro.format(n('hours') * n('rate'))],
                    ['Stunden gesamt', num.format(n('hours') * n('people'))],
                    ['Stundensatz', euro.format(n('rate'))],
                    ['Personen', num.format(n('people'))],
                    ['Mit 15% Puffer', euro.format(total * 1.15)],
                    ['Kosten pro Tag (8h)', euro.format(n('rate') * 8 * n('people'))],
                ]);
            }
            case 'personalkosten-rechner': {
                const monthly = n('gross') * (1 + n('overhead') / 100);
                return rows([
                    ['Arbeitgeberkosten pro Monat', euro.format(monthly)],
                    ['Pro Jahr', euro.format(monthly * 12)],
                    ['Lohnnebenkosten pro Monat', euro.format(n('gross') * n('overhead') / 100)],
                    ['Bruttogehalt', euro.format(n('gross'))],
                    ['Aufschlag', `${num.format(n('overhead'))} %`],
                    ['Kosten pro Stunde (160h)', euro.format(monthly / 160)],
                    ['Kosten pro Arbeitstag (220/J.)', euro.format(monthly * 12 / 220)],
                ]);
            }
            case 'fluktuation-rechner': {
                const rate = n('headcount') ? n('leavers') / n('headcount') * 100 : 0;
                return rows([
                    ['Fluktuationsrate', `${num.format(rate)} %`],
                    ['Abgaenge', num.format(n('leavers'))],
                    ['Mitarbeiter', num.format(n('headcount'))],
                    ['Bindungsrate', `${num.format(100 - rate)} %`],
                    ['Durchschn. Verweildauer ca.', `${num.format(rate ? 100 / rate : 0)} Jahre`],
                    ['Einordnung', rate < 5 ? 'niedrig' : rate < 15 ? 'normal' : 'hoch'],
                ]);
            }
            case 'krankenstand-rechner': {
                const soll = n('workDays') * n('employees');
                const rate = soll ? n('sickDays') / soll * 100 : 0;
                return rows([
                    ['Krankenquote', `${num.format(rate)} %`],
                    ['Krankheitstage gesamt', num.format(n('sickDays'))],
                    ['Krankheitstage je Mitarbeiter', num.format(n('employees') ? n('sickDays') / n('employees') : 0)],
                    ['Sollarbeitstage gesamt', num.format(soll)],
                    ['Mitarbeiter', num.format(n('employees'))],
                    ['Einordnung', rate < 3 ? 'niedrig' : rate < 6 ? 'durchschnittlich' : 'hoch'],
                ]);
            }
            case 'ueberstunden-rechner': {
                const overWeek = Math.max(0, n('actual') - n('contract'));
                const payPerHour = n('hourly') * (1 + n('surcharge') / 100);
                return rows([
                    ['Überstunden pro Woche', `${num.format(overWeek)} h`],
                    ['Überstunden pro Monat', `${num.format(overWeek * 4.33)} h`],
                    ['Vergütung je Überstunde', euro.format(payPerHour)],
                    ['Vergütung pro Woche', euro.format(overWeek * payPerHour)],
                    ['Vergütung pro Monat', euro.format(overWeek * 4.33 * payPerHour)],
                    ['Zuschlag', `${num.format(n('surcharge'))} %`],
                    ['Überstunden pro Jahr', `${num.format(overWeek * 46)} h`],
                ]);
            }
            case 'nettoumsatz-rechner': {
                const afterReturns = n('gross') * (1 - n('returns') / 100);
                const net = afterReturns * (1 - n('discount') / 100);
                return rows([
                    ['Nettoumsatz', euro.format(net)],
                    ['Bruttoumsatz', euro.format(n('gross'))],
                    ['Retouren', euro.format(n('gross') * n('returns') / 100)],
                    ['Rabatte', euro.format(afterReturns * n('discount') / 100)],
                    ['Abzüge gesamt', euro.format(n('gross') - net)],
                    ['Nettoquote', `${num.format(n('gross') ? net / n('gross') * 100 : 0)} %`],
                ]);
            }
            case 'warenkorbwert-rechner': {
                const aov = n('orders') ? n('revenue') / n('orders') : 0;
                return rows([
                    ['Durchschnittlicher Bestellwert', euro.format(aov)],
                    ['Umsatz', euro.format(n('revenue'))],
                    ['Bestellungen', num.format(n('orders'))],
                    ['Umsatz je 100 Bestellungen', euro.format(aov * 100)],
                    ['Bestellungen für 10.000 Euro', num.format(aov ? 10000 / aov : 0)],
                    ['+10% AOV bringt', euro.format(n('revenue') * 0.1)],
                ]);
            }
            case 'lagerumschlag-rechner': {
                const turn = n('inventory') ? n('cogs') / n('inventory') : 0;
                return rows([
                    ['Umschlagshaeufigkeit', `${num.format(turn)} x pro Jahr`],
                    ['Durchschnittliche Lagerdauer', `${num.format(turn ? 365 / turn : 0)} Tage`],
                    ['Wareneinsatz', euro.format(n('cogs'))],
                    ['Durchschn. Lagerbestand', euro.format(n('inventory'))],
                    ['Kapitalbindung', euro.format(n('inventory'))],
                    ['Einordnung', turn >= 8 ? 'hoch' : turn >= 4 ? 'mittel' : 'niedrig'],
                ]);
            }
            case 'preiskalkulation-rechner': {
                const net = n('cost') * (1 + n('markup') / 100);
                const gross = net * (1 + n('vat') / 100);
                return rows([
                    ['Verkaufspreis brutto', euro.format(gross)],
                    ['Verkaufspreis netto', euro.format(net)],
                    ['Selbstkosten', euro.format(n('cost'))],
                    ['Aufschlag (Gewinn)', euro.format(net - n('cost'))],
                    ['Mehrwertsteuer', euro.format(gross - net)],
                    ['Marge vom Nettopreis', `${num.format(net ? (net - n('cost')) / net * 100 : 0)} %`],
                    ['Aufschlag', `${num.format(n('markup'))} %`],
                ]);
            }
            case 'break-even-monate-rechner': {
                const months = n('monthly') > 0 ? n('invest') / n('monthly') : 0;
                return rows([
                    ['Amortisation nach', months ? `${num.format(months)} Monaten` : 'nicht erreichbar'],
                    ['In Jahren', months ? `${num.format(months / 12)} Jahre` : '-'],
                    ['Investition', euro.format(n('invest'))],
                    ['Monatlicher Gewinn', euro.format(n('monthly'))],
                    ['Gewinn nach Amortisation/Jahr', euro.format(n('monthly') * 12)],
                    ['Gewinn nach 5 Jahren', euro.format(n('monthly') * 60 - n('invest'))],
                ]);
            }
            case 'kapazitaet-auslastung-rechner': {
                const util = n('available') ? n('used') / n('available') * 100 : 0;
                return rows([
                    ['Auslastung', `${num.format(util)} %`],
                    ['Genutzte Stunden', num.format(n('used'))],
                    ['Verfuegbare Stunden', num.format(n('available'))],
                    ['Freie Kapazitaet', `${num.format(Math.max(0, n('available') - n('used')))} h`],
                    ['Überlast', n('used') > n('available') ? `${num.format(n('used') - n('available'))} h` : 'keine'],
                    ['Einordnung', util > 100 ? 'überlastet' : util >= 85 ? 'hoch' : util >= 60 ? 'gut' : 'niedrig'],
                ]);
            }
            case 'zielumsatz-rechner': {
                const units = n('price') ? n('goal') / n('price') : 0;
                return rows([
                    ['Benötigte Stückzahl', num.format(Math.ceil(units))],
                    ['Pro Monat', num.format(Math.ceil(units / 12))],
                    ['Pro Woche', num.format(Math.ceil(units / 52))],
                    ['Pro Arbeitstag (220)', num.format(Math.ceil(units / 220))],
                    ['Umsatzziel', euro.format(n('goal'))],
                    ['Stückpreis', euro.format(n('price'))],
                    ['Umsatz je 100 Stück', euro.format(n('price') * 100)],
                ]);
            }
            case 'deckungsgrad-rechner': {
                const grad = n('fixed') ? n('revenue') / n('fixed') * 100 : 0;
                return rows([
                    ['Deckungsgrad', `${num.format(grad)} %`],
                    ['Deckungsbeitrag', euro.format(n('revenue'))],
                    ['Fixkosten', euro.format(n('fixed'))],
                    ['Überdeckung / Unterdeckung', euro.format(n('revenue') - n('fixed'))],
                    ['Status', grad >= 100 ? 'gedeckt' : 'nicht gedeckt'],
                    ['Noch nötig', n('revenue') < n('fixed') ? euro.format(n('fixed') - n('revenue')) : euro.format(0)],
                ]);
            }
            case 'stundenverrechnungssatz-rechner': {
                const m = 1 - n('margin') / 100;
                const rate = m > 0 ? n('cost') / m : 0;
                return rows([
                    ['Verrechnungssatz', euro.format(rate)],
                    ['Selbstkosten pro Stunde', euro.format(n('cost'))],
                    ['Gewinn pro Stunde', euro.format(rate - n('cost'))],
                    ['Zielmarge', `${num.format(n('margin'))} %`],
                    ['Tagessatz (8h)', euro.format(rate * 8)],
                    ['Bei 1400 Std/Jahr', euro.format(rate * 1400)],
                    ['Aufschlag auf Kosten', `${num.format(n('cost') ? (rate - n('cost')) / n('cost') * 100 : 0)} %`],
                ]);
            }
            case 'nps-rechner': {
                const total = n('promoters') + n('passives') + n('detractors');
                const nps = total ? (n('promoters') - n('detractors')) / total * 100 : 0;
                return rows([
                    ['NPS', num.format(nps)],
                    ['Promotoren-Anteil', `${num.format(total ? n('promoters') / total * 100 : 0)} %`],
                    ['Detraktoren-Anteil', `${num.format(total ? n('detractors') / total * 100 : 0)} %`],
                    ['Antworten gesamt', num.format(total)],
                    ['Einordnung', nps >= 50 ? 'exzellent' : nps >= 0 ? 'gut' : 'verbesserungswuerdig'],
                    ['Skala', '-100 bis +100'],
                ]);
            }
            case 'churn-rate-rechner': {
                const churn = n('start') ? n('lost') / n('start') * 100 : 0;
                return rows([
                    ['Churn-Rate', `${num.format(churn)} %`],
                    ['Bindungsrate', `${num.format(100 - churn)} %`],
                    ['Verlorene Kunden', num.format(n('lost'))],
                    ['Kunden zu Beginn', num.format(n('start'))],
                    ['Verbliebene Kunden', num.format(n('start') - n('lost'))],
                    ['Durchschn. Kundenlebensdauer', `${num.format(churn ? 100 / churn : 0)} Perioden`],
                    ['Einordnung', churn < 5 ? 'niedrig' : churn < 10 ? 'okay' : 'hoch'],
                ]);
            }
            case 'mrr-arr-rechner': {
                const mrr = n('customers') * n('arpu');
                return rows([
                    ['MRR (monatlich)', euro.format(mrr)],
                    ['ARR (jaehrlich)', euro.format(mrr * 12)],
                    ['Kunden', num.format(n('customers'))],
                    ['ARPU', euro.format(n('arpu'))],
                    ['Bei +10% Kunden', euro.format(mrr * 1.1)],
                    ['Umsatz pro Quartal', euro.format(mrr * 3)],
                    ['+50 Kunden bringen', euro.format(50 * n('arpu'))],
                ]);
            }
            case 'burn-rate-runway-rechner': {
                const months = n('burn') > 0 ? n('cash') / n('burn') : 0;
                return rows([
                    ['Runway', months ? `${num.format(months)} Monate` : 'unbegrenzt'],
                    ['In Jahren', months ? `${num.format(months / 12)} Jahre` : '-'],
                    ['Kapital', euro.format(n('cash'))],
                    ['Monatlicher Burn', euro.format(n('burn'))],
                    ['Geld leer ca.', months ? `in ${num.format(Math.floor(months))} Monaten` : '-'],
                    ['Burn pro Jahr', euro.format(n('burn') * 12)],
                    ['Warnung ab', '6 Monaten Runway neu finanzieren'],
                ]);
            }
            case 'break-even-preis-rechner': {
                const perUnit = n('units') ? n('fixed') / n('units') + n('variable') : 0;
                return rows([
                    ['Mindestpreis je Einheit', euro.format(perUnit)],
                    ['Fixkostenanteil je Einheit', euro.format(n('units') ? n('fixed') / n('units') : 0)],
                    ['Variable Kosten je Einheit', euro.format(n('variable'))],
                    ['Gesamtkosten', euro.format(n('fixed') + n('variable') * n('units'))],
                    ['Stückzahl', num.format(n('units'))],
                    ['Preis +20% Marge', euro.format(perUnit * 1.2)],
                    ['Preis +50% Marge', euro.format(perUnit * 1.5)],
                ]);
            }
            case 'skonto-jahreszins-rechner': {
                const days = n('netDays') - n('skontoDays');
                const eff = days > 0 ? n('discount') / (100 - n('discount')) * 360 / days * 100 : 0;
                return rows([
                    ['Effektiver Jahreszins', `${num.format(eff)} %`],
                    ['Skonto', `${num.format(n('discount'))} %`],
                    ['Zinswirksame Tage', num.format(days)],
                    ['Lohnt sich ab Kreditzins', `${num.format(eff)} %`],
                    ['Skontofrist', `${num.format(n('skontoDays'))} Tage`],
                    ['Zahlungsziel', `${num.format(n('netDays'))} Tage`],
                    ['Empfehlung', eff > 8 ? 'Skonto nutzen' : 'prüfen'],
                ]);
            }
            case 'mahngebuehr-rechner': {
                const interest = n('amount') * n('rate') / 100 * n('days') / 365;
                const total = n('amount') + interest + n('fee');
                return rows([
                    ['Gesamtforderung', euro.format(total)],
                    ['Verzugszinsen', euro.format(interest)],
                    ['Mahngebühr', euro.format(n('fee'))],
                    ['Offener Betrag', euro.format(n('amount'))],
                    ['Verzugstage', num.format(n('days'))],
                    ['Zinsen pro Tag', euro.format(n('amount') * n('rate') / 100 / 365)],
                    ['Zinssatz', `${num.format(n('rate'))} % p.a.`],
                ]);
            }
            case 'rechnung-splitten-rechner': {
                const a = n('total') * clamp(n('share'), 0, 100) / 100;
                return rows([
                    ['Anteil A', euro.format(a)],
                    ['Anteil B', euro.format(n('total') - a)],
                    ['Gesamtbetrag', euro.format(n('total'))],
                    ['Anteil A', `${num.format(n('share'))} %`],
                    ['Anteil B', `${num.format(100 - n('share'))} %`],
                    ['Differenz', euro.format(Math.abs(a - (n('total') - a)))],
                ]);
            }
            case 'wiederkaufrate-rechner': {
                const rate = n('total') ? n('repeat') / n('total') * 100 : 0;
                return rows([
                    ['Wiederkaufrate', `${num.format(rate)} %`],
                    ['Wiederkaufende Kunden', num.format(n('repeat'))],
                    ['Kunden gesamt', num.format(n('total'))],
                    ['Einmalkunden', num.format(n('total') - n('repeat'))],
                    ['Einordnung', rate >= 30 ? 'stark' : rate >= 15 ? 'okay' : 'niedrig'],
                    ['Einmalkundenanteil', `${num.format(100 - rate)} %`],
                ]);
            }
            case 'kosten-pro-lead-rechner': {
                const cpl = n('leads') ? n('spend') / n('leads') : 0;
                return rows([
                    ['Kosten pro Lead', euro.format(cpl)],
                    ['Werbebudget', euro.format(n('spend'))],
                    ['Leads', num.format(n('leads'))],
                    ['Leads je 1000 Euro', num.format(cpl ? 1000 / cpl : 0)],
                    ['Bei 5% Abschluss CPA', euro.format(cpl / 0.05)],
                    ['Budget für 100 Leads', euro.format(cpl * 100)],
                ]);
            }
            case 'gross-klein-konverter': {
                const t = value('text');
                const title = t.toLowerCase().replace(/\b\w/g, c => c.toUpperCase());
                const sentence = t.toLowerCase().replace(/(^\s*\w|[.!?]\s*\w)/g, c => c.toUpperCase());
                return rows([
                    ['GROSSBUCHSTABEN', escapeHtml(t.toUpperCase())],
                    ['kleinbuchstaben', escapeHtml(t.toLowerCase())],
                    ['Title Case', escapeHtml(title)],
                    ['Satzanfang gross', escapeHtml(sentence)],
                    ['wECHSEL', escapeHtml([...t].map((c, i) => i % 2 ? c.toUpperCase() : c.toLowerCase()).join(''))],
                    ['Zeichen', num.format(t.length)],
                ]);
            }
            case 'text-umkehren': {
                const t = value('text');
                const rev = [...t].reverse().join('');
                return rows([
                    ['Umgekehrt', escapeHtml(rev)],
                    ['Original', escapeHtml(t)],
                    ['Zeichen', num.format([...t].length)],
                    ['Palindrom', t.replace(/\s/g, '').toLowerCase() === rev.replace(/\s/g, '').toLowerCase() ? 'ja' : 'nein'],
                    ['Woerter', num.format(t.split(/\s+/).filter(Boolean).length)],
                ]);
            }
            case 'woerter-umkehren': {
                const words = value('text').split(/\s+/).filter(Boolean);
                return rows([
                    ['Umgekehrt', escapeHtml(words.slice().reverse().join(' '))],
                    ['Original', escapeHtml(value('text'))],
                    ['Woerter', num.format(words.length)],
                    ['Erstes Wort', escapeHtml(words[0] || '-')],
                    ['Letztes Wort', escapeHtml(words[words.length - 1] || '-')],
                ]);
            }
            case 'text-wiederholen': {
                const times = clamp(Math.round(n('times')), 0, 1000);
                const out = value('text').repeat(times);
                return rows([
                    ['Ergebnis', escapeHtml(out.slice(0, 500)) + (out.length > 500 ? ' ...' : '')],
                    ['Wiederholungen', num.format(times)],
                    ['Gesamtlänge', `${num.format(out.length)} Zeichen`],
                    ['Länge je Wiederholung', `${num.format(value('text').length)} Zeichen`],
                ]);
            }
            case 'vokale-konsonanten-rechner': {
                const t = value('text');
                const vowels = (t.match(/[aeiouäöü]/gi) || []).length;
                const letters = (t.match(/[a-zäöüß]/gi) || []).length;
                const cons = letters - vowels;
                return rows([
                    ['Vokale', num.format(vowels)],
                    ['Konsonanten', num.format(cons)],
                    ['Buchstaben gesamt', num.format(letters)],
                    ['Vokal-Anteil', `${num.format(letters ? vowels / letters * 100 : 0)} %`],
                    ['Verhältnis V:K', `${num.format(cons ? vowels / cons : 0)}`],
                    ['Zeichen gesamt', num.format(t.length)],
                ]);
            }
            case 'zeichenarten-zaehler': {
                const t = value('text');
                const upper = (t.match(/[A-ZÄÖÜ]/g) || []).length;
                const lower = (t.match(/[a-zäöüß]/g) || []).length;
                const digits = (t.match(/\d/g) || []).length;
                const spaces = (t.match(/\s/g) || []).length;
                const special = t.length - upper - lower - digits - spaces;
                return rows([
                    ['Grossbuchstaben', num.format(upper)],
                    ['Kleinbuchstaben', num.format(lower)],
                    ['Ziffern', num.format(digits)],
                    ['Leerzeichen', num.format(spaces)],
                    ['Sonderzeichen', num.format(special)],
                    ['Zeichen gesamt', num.format(t.length)],
                    ['Ohne Leerzeichen', num.format(t.length - spaces)],
                ]);
            }
            case 'zeilen-nummerieren': {
                const lines = value('text').split('\n');
                const width = String(lines.length).length;
                const out = lines.map((l, i) => `${String(i + 1).padStart(width, '0')}. ${l}`).join('\n');
                return rows([
                    ['Nummeriert', escapeHtml(out).replace(/\n/g, '<br>')],
                    ['Zeilen', num.format(lines.length)],
                    ['Nicht leere Zeilen', num.format(lines.filter(l => l.trim()).length)],
                    ['Laengste Zeile', `${num.format(Math.max(...lines.map(l => l.length), 0))} Zeichen`],
                ]);
            }
            case 'akronym-generator': {
                const words = value('text').split(/\s+/).filter(Boolean);
                const acr = words.map(w => w.charAt(0).toUpperCase()).join('');
                return rows([
                    ['Akronym', escapeHtml(acr)],
                    ['Kleingeschrieben', escapeHtml(acr.toLowerCase())],
                    ['Mit Punkten', escapeHtml(acr.split('').join('.') + (acr ? '.' : ''))],
                    ['Woerter', num.format(words.length)],
                    ['Länge', `${num.format(acr.length)} Buchstaben`],
                ]);
            }
            case 'buchstaben-haeufigkeit': {
                const t = value('text').toLowerCase();
                const freq = {};
                for (const ch of t.match(/[a-zäöüß]/g) || []) freq[ch] = (freq[ch] || 0) + 1;
                const sorted = Object.entries(freq).sort((a, b) => b[1] - a[1]);
                const total = sorted.reduce((s, e) => s + e[1], 0);
                const out = sorted.slice(0, 6).map(([k, v]) => [`Buchstabe "${k}"`, `${num.format(v)} (${num.format(total ? v / total * 100 : 0)} %)`]);
                return rows([
                    ['Haeufigster Buchstabe', sorted[0] ? `"${sorted[0][0]}" (${num.format(sorted[0][1])}x)` : '-'],
                    ['Verschiedene Buchstaben', num.format(sorted.length)],
                    ['Buchstaben gesamt', num.format(total)],
                    ...out,
                ]);
            }
            case 'palindrom-checker': {
                const clean = value('text').toLowerCase().replace(/[^a-z0-9äöüß]/g, '');
                const rev = [...clean].reverse().join('');
                const isPal = clean.length > 0 && clean === rev;
                return rows([
                    ['Palindrom', isPal ? 'Ja' : 'Nein'],
                    ['Bereinigt', escapeHtml(clean)],
                    ['Rueckwaerts', escapeHtml(rev)],
                    ['Original', escapeHtml(value('text'))],
                    ['Relevante Zeichen', num.format(clean.length)],
                ]);
            }
            case 'anagramm-checker': {
                const norm = s => (s.toLowerCase().match(/[a-z0-9äöüß]/g) || []).sort().join('');
                const na = norm(value('a')), nb = norm(value('b'));
                const isAna = na.length > 0 && na === nb;
                return rows([
                    ['Anagramm', isAna ? 'Ja' : 'Nein'],
                    ['Begriff 1', escapeHtml(value('a'))],
                    ['Begriff 2', escapeHtml(value('b'))],
                    ['Buchstaben 1', num.format(na.length)],
                    ['Buchstaben 2', num.format(nb.length)],
                    ['Sortiert gleich', na === nb ? 'ja' : 'nein'],
                ]);
            }
            case 'text-zu-binaer': {
                const bin = [...value('text')].map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' ');
                return rows([
                    ['Binaercode', escapeHtml(bin)],
                    ['Zeichen', num.format([...value('text')].length)],
                    ['Bits', num.format([...value('text')].length * 8)],
                    ['Bytes', num.format([...value('text')].length)],
                    ['Original', escapeHtml(value('text'))],
                ]);
            }
            case 'binaer-zu-text': {
                const groups = value('binary').trim().split(/\s+/).filter(Boolean);
                let text = '';
                try { text = groups.map(g => String.fromCharCode(parseInt(g, 2))).join(''); } catch (e) {}
                const valid = groups.every(g => /^[01]+$/.test(g));
                return rows([
                    ['Text', valid ? escapeHtml(text) : 'ungültiger Binaercode'],
                    ['Bloecke', num.format(groups.length)],
                    ['Gültig', valid ? 'ja' : 'nein'],
                    ['Bits gesamt', num.format(groups.join('').length)],
                ]);
            }
            case 'text-zu-hex': {
                const hex = [...value('text')].map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(' ');
                return rows([
                    ['Hexadezimal', escapeHtml(hex)],
                    ['Ohne Leerzeichen', escapeHtml(hex.replace(/ /g, ''))],
                    ['Zeichen', num.format([...value('text')].length)],
                    ['Hex-Zeichen', num.format(hex.replace(/ /g, '').length)],
                    ['Original', escapeHtml(value('text'))],
                ]);
            }
            case 'rot13-konverter': {
                const out = value('text').replace(/[a-z]/gi, c => String.fromCharCode((c.charCodeAt(0) - (c <= 'Z' ? 65 : 97) + 13) % 26 + (c <= 'Z' ? 65 : 97)));
                return rows([
                    ['ROT13', escapeHtml(out)],
                    ['Original', escapeHtml(value('text'))],
                    ['Hinweis', 'ROT13 ist selbstinvers: nochmal anwenden = Original'],
                    ['Zeichen', num.format(value('text').length)],
                ]);
            }
            case 'caesar-verschluesselung': {
                const s = ((Math.round(n('shift')) % 26) + 26) % 26;
                const enc = value('text').replace(/[a-z]/gi, c => { const base = c <= 'Z' ? 65 : 97; return String.fromCharCode((c.charCodeAt(0) - base + s) % 26 + base); });
                const dec = value('text').replace(/[a-z]/gi, c => { const base = c <= 'Z' ? 65 : 97; return String.fromCharCode((c.charCodeAt(0) - base + 26 - s) % 26 + base); });
                return rows([
                    ['Verschlüsselt', escapeHtml(enc)],
                    ['Entschlüsselt', escapeHtml(dec)],
                    ['Verschiebung', num.format(s)],
                    ['Original', escapeHtml(value('text'))],
                    ['Hinweis', s === 13 ? 'entspricht ROT13' : 'klassische Caesar-Chiffre'],
                ]);
            }
            case 'leetspeak-konverter': {
                const map = { a: '4', e: '3', i: '1', o: '0', t: '7', s: '5', b: '8', g: '9', l: '1' };
                const out = [...value('text').toLowerCase()].map(c => map[c] || c).join('');
                return rows([
                    ['Leetspeak', escapeHtml(out)],
                    ['Original', escapeHtml(value('text'))],
                    ['Ersetzte Zeichen', num.format([...value('text').toLowerCase()].filter(c => map[c]).length)],
                    ['Zeichen', num.format(value('text').length)],
                ]);
            }
            case 'nato-alphabet-konverter': {
                const nato = { a: 'Alpha', b: 'Bravo', c: 'Charlie', d: 'Delta', e: 'Echo', f: 'Foxtrot', g: 'Golf', h: 'Hotel', i: 'India', j: 'Juliett', k: 'Kilo', l: 'Lima', m: 'Mike', n: 'November', o: 'Oscar', p: 'Papa', q: 'Quebec', r: 'Romeo', s: 'Sierra', t: 'Tango', u: 'Uniform', v: 'Victor', w: 'Whiskey', x: 'X-Ray', y: 'Yankee', z: 'Zulu', '0': 'Zero', '1': 'One', '2': 'Two', '3': 'Three', '4': 'Four', '5': 'Five', '6': 'Six', '7': 'Seven', '8': 'Eight', '9': 'Nine' };
                const out = [...value('text').toLowerCase()].map(c => nato[c] || (c === ' ' ? '/' : '')).filter(Boolean).join(' ');
                return rows([
                    ['NATO-Alphabet', escapeHtml(out)],
                    ['Original', escapeHtml(value('text'))],
                    ['Zeichen umgewandelt', num.format([...value('text').toLowerCase()].filter(c => nato[c]).length)],
                    ['Hinweis', 'Internationales Funkalphabet'],
                ]);
            }
            case 'zahl-in-worten': {
                const ones = ['null', 'eins', 'zwei', 'drei', 'vier', 'fuenf', 'sechs', 'sieben', 'acht', 'neun', 'zehn', 'elf', 'zwoelf', 'dreizehn', 'vierzehn', 'fuenfzehn', 'sechzehn', 'siebzehn', 'achtzehn', 'neunzehn'];
                const tens = ['', '', 'zwanzig', 'dreissig', 'vierzig', 'fuenfzig', 'sechzig', 'siebzig', 'achtzig', 'neunzig'];
                function below1000(x) {
                    let s = '';
                    if (x >= 100) { s += (Math.floor(x / 100) === 1 ? 'ein' : ones[Math.floor(x / 100)]) + 'hundert'; x %= 100; }
                    if (x >= 20) { const u = x % 10; s += (u ? (u === 1 ? 'ein' : ones[u]) + 'und' : '') + tens[Math.floor(x / 10)]; }
                    else if (x > 0) s += ones[x];
                    return s;
                }
                let nn = Math.abs(Math.round(n('number')));
                let words;
                if (nn === 0) words = 'null';
                else {
                    const parts = [];
                    const scales = [[1e9, 'milliarde', 'milliarden'], [1e6, 'million', 'millionen'], [1000, 'tausend', 'tausend']];
                    for (const [val, sing, plur] of scales) {
                        if (nn >= val) { const c = Math.floor(nn / val); const cw = (val === 1000 && c === 1) ? 'ein' : (c === 1 && val >= 1e6 ? 'eine ' : below1000(c)); parts.push(cw + (val === 1000 ? 'tausend' : ' ' + (c === 1 ? sing : plur))); nn %= val; }
                    }
                    if (nn > 0) parts.push(below1000(nn));
                    words = parts.join(' ').replace(/\s+/g, ' ').trim();
                }
                if (n('number') < 0) words = 'minus ' + words;
                return rows([
                    ['In Worten', escapeHtml(words)],
                    ['Zahl', num.format(Math.round(n('number')))],
                    ['Stellen', num.format(Math.abs(Math.round(n('number'))).toString().length)],
                    ['Grossgeschrieben', escapeHtml(words.charAt(0).toUpperCase() + words.slice(1))],
                    ['Hinweis', 'Ganze Zahlen bis Milliarden'],
                ]);
            }
            case 'telefon-buchstaben-rechner': {
                const map = { a: 2, b: 2, c: 2, d: 3, e: 3, f: 3, g: 4, h: 4, i: 4, j: 5, k: 5, l: 5, m: 6, n: 6, o: 6, p: 7, q: 7, r: 7, s: 7, t: 8, u: 8, v: 8, w: 9, x: 9, y: 9, z: 9 };
                const out = [...value('text').toLowerCase()].map(c => map[c] ?? (/\d/.test(c) ? c : c === ' ' ? ' ' : '')).join('');
                return rows([
                    ['Telefonnummer', escapeHtml(out)],
                    ['Original', escapeHtml(value('text'))],
                    ['Ziffern', num.format(out.replace(/\D/g, '').length)],
                    ['Hinweis', 'Klassische Handytastatur (2=ABC ...)'],
                ]);
            }
            case 'silben-zaehler': {
                const words = value('text').toLowerCase().match(/[a-zäöüß]+/g) || [];
                let syl = 0;
                words.forEach(w => { const m = w.match(/[aeiouäöüy]+/g); syl += Math.max(1, m ? m.length : 0); });
                return rows([
                    ['Silben (geschätzt)', num.format(syl)],
                    ['Woerter', num.format(words.length)],
                    ['Silben pro Wort', num.format(words.length ? syl / words.length : 0)],
                    ['Laengstes Wort', escapeHtml(words.reduce((a, b) => b.length > a.length ? b : a, ''))],
                    ['Hinweis', 'Naeherung über Vokalgruppen'],
                    ['Haiku-Check (5-7-5)', syl === 17 ? 'möglich' : `${num.format(syl)} Silben`],
                ]);
            }
            case 'schreibzeit-rechner': {
                const minutes = n('wpm') ? n('words') / n('wpm') : 0;
                return rows([
                    ['Schreibzeit', duration(minutes * 60)],
                    ['In Minuten', num.format(minutes)],
                    ['Wortanzahl', num.format(n('words'))],
                    ['Schreibtempo', `${num.format(n('wpm'))} WpM`],
                    ['Zeichen ca. (5,5/Wort)', num.format(n('words') * 5.5)],
                    ['Bei 50 WpM', duration(n('words') / 50 * 60)],
                    ['Seiten (250 Woerter)', num.format(n('words') / 250)],
                ]);
            }
            case 'tabs-zu-spaces-konverter': {
                const sp = ' '.repeat(clamp(Math.round(n('spaces')), 0, 16));
                const out = value('text').replace(/\t/g, sp);
                return rows([
                    ['Ergebnis', escapeHtml(out).replace(/\n/g, '<br>')],
                    ['Tabs ersetzt', num.format((value('text').match(/\t/g) || []).length)],
                    ['Leerzeichen pro Tab', num.format(n('spaces'))],
                    ['Länge vorher', `${num.format(value('text').length)} Zeichen`],
                    ['Länge nachher', `${num.format(out.length)} Zeichen`],
                ]);
            }
            case 'csv-spalten-zaehler': {
                const delim = value('delimiter') || ',';
                const lines = value('csv').split(/\n/).filter(l => l.trim());
                const cols = lines.map(l => l.split(delim).length);
                const maxCols = Math.max(...cols, 0);
                const consistent = cols.every(c => c === cols[0]);
                return rows([
                    ['Zeilen', num.format(lines.length)],
                    ['Datenzeilen (ohne Kopf)', num.format(Math.max(0, lines.length - 1))],
                    ['Spalten', num.format(cols[0] || 0)],
                    ['Maximale Spalten', num.format(maxCols)],
                    ['Konsistent', consistent ? 'ja' : 'nein (unterschiedliche Spaltenzahl)'],
                    ['Zellen gesamt', num.format(cols.reduce((s, c) => s + c, 0))],
                    ['Trennzeichen', delim === '\t' ? 'Tab' : delim],
                ]);
            }
            case 'url-parameter-parser': {
                const url = value('url').trim();
                const qIndex = url.indexOf('?');
                const query = qIndex >= 0 ? url.slice(qIndex + 1).split('#')[0] : '';
                const params = query ? query.split('&').filter(Boolean) : [];
                const out = params.slice(0, 8).map(p => { const [k, v] = p.split('='); return [escapeHtml(decodeURIComponent(k || '')), escapeHtml(decodeURIComponent((v || '').replace(/\+/g, ' ')))]; });
                return rows([
                    ['Parameter', num.format(params.length)],
                    ['Basis-URL', escapeHtml(qIndex >= 0 ? url.slice(0, qIndex) : url)],
                    ['Anker', url.includes('#') ? escapeHtml(url.slice(url.indexOf('#'))) : 'keiner'],
                    ...out,
                ]);
            }
            case 'satzzeichen-zaehler': {
                const t = value('text');
                return rows([
                    ['Punkte', num.format((t.match(/\./g) || []).length)],
                    ['Kommas', num.format((t.match(/,/g) || []).length)],
                    ['Fragezeichen', num.format((t.match(/\?/g) || []).length)],
                    ['Ausrufezeichen', num.format((t.match(/!/g) || []).length)],
                    ['Doppelpunkte', num.format((t.match(/:/g) || []).length)],
                    ['Strichpunkte', num.format((t.match(/;/g) || []).length)],
                    ['Satzzeichen gesamt', num.format((t.match(/[.,!?;:]/g) || []).length)],
                    ['Saetze ca.', num.format((t.match(/[.!?]+/g) || []).length)],
                ]);
            }
            case 'mehrwertsteuer-rechner': {
                const rate = clamp(n('rate'), 0, 100);
                const amount = n('amount');
                const isNet = n('mode') >= 1;
                const net = isNet ? amount : amount / (1 + rate / 100);
                const tax = net * rate / 100;
                const gross = net + tax;
                return rows([
                  ['Eingegeben als', isNet ? 'Nettobetrag' : 'Bruttobetrag'],
                  ['Steuersatz', `${num.format(rate)} %`],
                  ['Nettobetrag', euro.format(net)],
                  ['Mehrwertsteuer', euro.format(tax)],
                  ['Bruttobetrag', euro.format(gross)],
                  ['MwSt-Anteil am Brutto', `${num.format(gross ? tax / gross * 100 : 0)} %`],
                  ['Netto bei 7 %', euro.format(isNet ? net : amount / 1.07)],
                  ['Brutto bei 7 %', euro.format(net * 1.07)],
                  ['Brutto bei 19 %', euro.format(net * 1.19)]
                ]);
            }
            case 'eigenkapitalrendite-rechner': {
                const profit = n('profit');
                const equity = Math.max(1, n('equity'));
                const debt = n('debt');
                const total = equity + debt;
                const roe = profit / equity * 100;
                const roa = profit / Math.max(1, total) * 100;
                const leverage = total / equity;
                return rows([
                  ['Eigenkapitalrendite (ROE)', `${num.format(roe)} %`],
                  ['Gesamtkapitalrendite (ROA)', `${num.format(roa)} %`],
                  ['Gesamtkapital', euro.format(total)],
                  ['Eigenkapital', euro.format(equity)],
                  ['Fremdkapital', euro.format(debt)],
                  ['Eigenkapitalquote', `${num.format(equity / Math.max(1, total) * 100)} %`],
                  ['Kapital-Hebel', `${num.format(leverage)} x`],
                  ['Gewinn pro 1.000 € EK', euro.format(roe * 10)],
                  ['Jahresgewinn', euro.format(profit)]
                ]);
            }
            case 'tagesgeld-vergleich-rechner': {
                const amount = n('amount');
                const years = Math.max(0, n('months')) / 12;
                const rA = clamp(n('rateA'), 0, 100);
                const rB = clamp(n('rateB'), 0, 100);
                const interestA = amount * rA / 100 * years;
                const interestB = amount * rB / 100 * years;
                const diff = interestB - interestA;
                const better = diff > 0 ? 'Angebot B' : diff < 0 ? 'Angebot A' : 'beide gleich';
                return rows([
                  ['Besseres Angebot', better],
                  ['Zinsertrag A', euro.format(interestA)],
                  ['Zinsertrag B', euro.format(interestB)],
                  ['Zinsvorteil', euro.format(Math.abs(diff))],
                  ['Endkapital A', euro.format(amount + interestA)],
                  ['Endkapital B', euro.format(amount + interestB)],
                  ['Vorteil pro Monat', euro.format(Math.abs(diff) / Math.max(1, n('months')))],
                  ['Zinsdifferenz', `${num.format(Math.abs(rB - rA))} %-Punkte`],
                  ['Anlagebetrag', euro.format(amount)]
                ]);
            }
            case 'sparquote-rechner': {
                const income = Math.max(1, n('income'));
                const saving = n('saving');
                const rate = saving / income * 100;
                const spent = income - saving;
                return rows([
                  ['Sparquote', `${num.format(rate)} %`],
                  ['Monatlicher Sparbetrag', euro.format(saving)],
                  ['Konsum pro Monat', euro.format(spent)],
                  ['Ersparnis pro Jahr', euro.format(saving * 12)],
                  ['Ersparnis in 5 Jahren', euro.format(saving * 60)],
                  ['Ersparnis in 10 Jahren', euro.format(saving * 120)],
                  ['Konsumquote', `${num.format(spent / income * 100)} %`],
                  ['Einordnung', rate < 10 ? 'niedrig' : rate < 20 ? 'solide' : 'sehr gut'],
                  ['Nettoeinkommen', euro.format(income)]
                ]);
            }
            case 'kreditleistbarkeit-rechner': {
                const free = Math.max(0, n('income') - n('expenses'));
                const buffer = clamp(n('buffer'), 0, 90);
                const affordable = free * (1 - buffer / 100);
                const months = Math.max(1, Math.round(n('years') * 12));
                const i = clamp(n('rate'), 0, 100) / 100 / 12;
                const loan = i > 0 ? affordable * (1 - Math.pow(1 + i, -months)) / i : affordable * months;
                const totalPaid = affordable * months;
                return rows([
                  ['Freie Mittel pro Monat', euro.format(free)],
                  ['Tragbare Monatsrate', euro.format(affordable)],
                  ['Sicherheitspuffer', euro.format(free - affordable)],
                  ['Mögliche Kreditsumme', euro.format(loan)],
                  ['Laufzeit', `${num.format(months)} Monate`],
                  ['Summe aller Raten', euro.format(totalPaid)],
                  ['Geschätzte Zinskosten', euro.format(Math.max(0, totalPaid - loan))],
                  ['Rate je 10.000 € Kredit', euro.format(loan > 0 ? affordable / loan * 10000 : 0)],
                  ['Anteil Rate am Einkommen', `${num.format(n('income') ? affordable / n('income') * 100 : 0)} %`]
                ]);
            }
            case 'watt-pro-kg-rechner': {
                const kg = Math.max(1, n('kg'));
                const wkg = n('watt') / kg;
                let stufe;
                if (wkg < 2) stufe = 'Einsteiger';
                else if (wkg < 3) stufe = 'Freizeitsportler';
                else if (wkg < 4) stufe = 'Ambitioniert';
                else if (wkg < 5) stufe = 'Sehr gut';
                else stufe = 'Elite';
                return rows([
                  ['Leistungsgewicht', num.format(wkg) + ' W/kg'],
                  ['Einordnung', stufe],
                  ['Leistung gesamt', num.format(n('watt')) + ' W']
                ]);
            }
            case 'ftp-zonen-rechner': {
                const ftp = Math.max(1, n('ftp'));
                const z = (lo, hi) => num.format(Math.round(ftp * lo)) + (hi ? ' - ' + num.format(Math.round(ftp * hi)) + ' W' : '+ W');
                return rows([
                  ['Z1 Aktive Erholung', z(0, 0.55)],
                  ['Z2 Grundlage', z(0.56, 0.75)],
                  ['Z3 Tempo', z(0.76, 0.90)],
                  ['Z4 Schwelle', z(0.91, 1.05)],
                  ['Z5 VO2max', z(1.06, 1.20)],
                  ['Z6 Anaerob', z(1.21, 1.50)],
                  ['Z7 Sprint', z(1.50, 0)]
                ]);
            }
            case 'schrittlaenge-rechner': {
                const schritte = Math.max(1, n('schritte'));
                const gemessen = (n('strecke') / schritte) * 100;
                const geschätzt = n('cm') * 0.415;
                const proKm = Math.round(100000 / Math.max(1, gemessen));
                return rows([
                  ['Schrittlänge gemessen', num.format(gemessen) + ' cm'],
                  ['Schrittlänge geschätzt', num.format(geschätzt) + ' cm (aus Größe)'],
                  ['Schritte pro km', num.format(proKm)]
                ]);
            }
            case 'karvonen-puls-rechner': {
                const hfmax = 220 - n('alter');
                const ruhe = n('ruhe');
                const reserve = Math.max(0, hfmax - ruhe);
                const i = clamp(n('intensitaet'), 0, 100) / 100;
                const puls = Math.round(ruhe + reserve * i);
                return rows([
                  ['Trainingspuls', num.format(puls) + ' Schlaege/Min'],
                  ['Maximalpuls (geschätzt)', num.format(hfmax) + ' Schlaege/Min'],
                  ['Herzfrequenzreserve', num.format(reserve) + ' Schlaege/Min'],
                  ['Empfohlener Bereich', num.format(Math.round(ruhe + reserve * 0.6)) + ' - ' + num.format(Math.round(ruhe + reserve * 0.8)) + ' Schlaege/Min']
                ]);
            }
            case 'cooper-test-rechner': {
                const m = Math.max(1, n('meter'));
                const vo2 = (m - 504.9) / 44.73;
                const tempo = 12 / (m / 1000);
                let stufe;
                if (m < 1600) stufe = 'Schwach';
                else if (m < 2200) stufe = 'Maessig';
                else if (m < 2800) stufe = 'Gut';
                else if (m < 3200) stufe = 'Sehr gut';
                else stufe = 'Hervorragend';
                return rows([
                  ['VO2max (geschätzt)', num.format(vo2) + ' ml/kg/min'],
                  ['Durchschnittstempo', num.format(tempo) + ' min/km'],
                  ['Geschwindigkeit', num.format(m / 200) + ' km/h'],
                  ['Einordnung', stufe]
                ]);
            }
            case 'umzugskartons-schaetzer': {
                const fläche = Math.max(1, n('flaeche'));
                const personen = Math.max(1, n('personen'));
                const buecher = clamp(n('buecher'), 0, 5);
                const basis = fläche * 0.7;
                const proPerson = personen * 6;
                const extraBuecher = buecher * 4;
                const kartons = Math.ceil(basis + proPerson + extraBuecher);
                const tage = Math.max(1, Math.ceil(kartons / 25));
                return rows([
                  ['Benötigte Kartons', num.format(kartons)],
                  ['Davon Bücherkartons', num.format(Math.ceil(extraBuecher + fläche * 0.1))],
                  ['Geschätzte Packtage', num.format(tage)],
                  ['Wohnfläche', num.format(fläche) + ' m²']
                ]);
            }
            case 'waesche-trockenzeit-rechner': {
                const dicke = clamp(n('basis'), 1, 5);
                const feuchte = clamp(n('feuchte'), 10, 100);
                const drinnen = n('drinnen') >= 1 ? 1.3 : 1.0;
                const wind = clamp(n('wind'), 0, 5);
                let stunden = dicke * 1.4 * drinnen;
                stunden *= (0.6 + feuchte / 100);
                stunden *= (1 - wind * 0.08);
                stunden = Math.max(0.5, stunden);
                const minuten = Math.round(stunden * 60);
                return rows([
                  ['Geschätzte Trockenzeit', num.format(Math.round(stunden * 10) / 10) + ' Std'],
                  ['In Minuten', num.format(minuten) + ' min'],
                  ['Ort', drinnen > 1 ? 'Drinnen' : 'Draußen'],
                  ['Luftfeuchtigkeit', num.format(feuchte) + ' %']
                ]);
            }
            case 'gefrierschrank-vorrat-rechner': {
                const liter = Math.max(1, n('liter'));
                const personen = Math.max(1, n('personen'));
                const proWoche = Math.max(1, n('proWoche'));
                const nutzbar = liter * 0.75;
                const portionen = Math.floor(nutzbar / 1.2);
                const proWocheGesamt = personen * proWoche;
                const wochen = portionen / Math.max(1, proWocheGesamt);
                return rows([
                  ['Nutzbares Volumen', num.format(Math.round(nutzbar)) + ' L'],
                  ['Passende Portionen', num.format(portionen)],
                  ['Verbrauch pro Woche', num.format(proWocheGesamt) + ' Portionen'],
                  ['Reicht für', num.format(Math.round(wochen * 10) / 10) + ' Wochen']
                ]);
            }
            case 'regentonnen-volumen-rechner': {
                const dach = Math.max(0, n('dachflaeche'));
                const regen = Math.max(0, n('niederschlag'));
                const tonne = Math.max(1, n('tonne'));
                const ertragsfaktor = 0.8;
                const liter = dach * regen * ertragsfaktor;
                const gefuellt = Math.min(100, liter / tonne * 100);
                const überlauf = Math.max(0, liter - tonne);
                return rows([
                  ['Sammelbares Wasser', num.format(Math.round(liter)) + ' L'],
                  ['Füllung der Tonne', num.format(Math.round(gefuellt)) + ' %'],
                  ['Überlauf', num.format(Math.round(überlauf)) + ' L'],
                  ['Gießkannen à 10 L', num.format(Math.floor(Math.min(liter, tonne) / 10))]
                ]);
            }
            case 'buffet-portionen-rechner': {
                const gaeste = Math.max(1, n('gaeste'));
                const stunden = Math.max(1, n('stunden'));
                const appetit = clamp(n('appetit'), 1, 3);
                const faktor = (0.7 + appetit * 0.15) * (1 + (stunden - 3) * 0.05);
                const haupt = gaeste * 200 * faktor;
                const beilage = gaeste * 150 * faktor;
                const salat = gaeste * 100 * faktor;
                const getränke = gaeste * stunden * 0.4;
                return rows([
                  ['Hauptgang (Fleisch/Fisch)', num.format(Math.round(haupt / 100) / 10) + ' kg'],
                  ['Beilagen', num.format(Math.round(beilage / 100) / 10) + ' kg'],
                  ['Salat/Gemüse', num.format(Math.round(salat / 100) / 10) + ' kg'],
                  ['Getränke', num.format(Math.round(getränke * 10) / 10) + ' L'],
                  ['Gäste', num.format(gaeste)]
                ]);
            }
            case 'cron-ausdruck-erklaerer': {
                const raw = value('cron').trim();
                const parts = raw.split(/\s+/);
                if (parts.length !== 5) {
                  return rows([['Hinweis', 'Bitte genau 5 Felder eingeben: Minute Stunde Tag Monat Wochentag.'], ['Eingabe', '<span class="mono">' + escapeHtml(raw || '-') + '</span>']]);
                }
                const days = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag','Sonntag'];
                const months = ['','Januar','Februar','Maerz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
                const explainField = (f, unit, map) => {
                  if (f === '*') return 'jede(n) ' + unit;
                  if (f.startsWith('*/')) return 'alle ' + escapeHtml(f.slice(2)) + ' ' + unit;
                  if (f.includes('-')) { const [a,b] = f.split('-'); return unit + ' ' + (map ? escapeHtml(map(a)) : escapeHtml(a)) + ' bis ' + (map ? escapeHtml(map(b)) : escapeHtml(b)); }
                  if (f.includes(',')) return unit + ' ' + escapeHtml(f.split(',').map(x => map ? map(x) : x).join(', '));
                  return unit + ' ' + (map ? escapeHtml(map(f)) : escapeHtml(f));
                };
                const [min, hr, dom, mon, dow] = parts;
                return rows([
                  ['Ausdruck', '<span class="mono">' + escapeHtml(raw) + '</span>'],
                  ['Minute', explainField(min, 'Minute', null)],
                  ['Stunde', explainField(hr, 'Stunde', null)],
                  ['Tag im Monat', explainField(dom, 'Tag', null)],
                  ['Monat', explainField(mon, 'Monat', x => months[clamp(parseInt(x,10),1,12)] || x)],
                  ['Wochentag', explainField(dow, 'Wochentag', x => days[clamp(parseInt(x,10),0,7)] || x)]
                ]);
            }
            case 'bytes-umrechner': {
                const b = Math.max(0, n('bytes'));
                const dec = u => num.format(b / Math.pow(1000, u));
                const bin = u => num.format(b / Math.pow(1024, u));
                return rows([
                  ['Bytes', num.format(b) + ' B'],
                  ['Dezimal KB', dec(1) + ' KB'],
                  ['Dezimal MB', dec(2) + ' MB'],
                  ['Dezimal GB', dec(3) + ' GB'],
                  ['Dezimal TB', dec(4) + ' TB'],
                  ['Binaer KiB', bin(1) + ' KiB'],
                  ['Binaer MiB', bin(2) + ' MiB'],
                  ['Binaer GiB', bin(3) + ' GiB'],
                  ['Binaer TiB', bin(4) + ' TiB']
                ]);
            }
            case 'http-status-code-erklaerer': {
                const code = clamp(Math.round(n('code')), 100, 599);
                const classes = {
                  1: ['1xx Informationell', 'Die Anfrage wurde empfangen, der Vorgang laeuft weiter.'],
                  2: ['2xx Erfolg', 'Die Anfrage wurde erfolgreich empfangen, verstanden und akzeptiert.'],
                  3: ['3xx Umleitung', 'Weitere Schritte sind nötig, um die Anfrage abzuschliessen.'],
                  4: ['4xx Client-Fehler', 'Die Anfrage enthaelt einen Fehler oder kann nicht erfuellt werden.'],
                  5: ['5xx Server-Fehler', 'Der Server konnte eine gültige Anfrage nicht bearbeiten.']
                };
                const names = {
                  100: 'Continue', 101: 'Switching Protocols',
                  200: 'OK', 201: 'Created', 202: 'Accepted', 204: 'No Content', 206: 'Partial Content',
                  301: 'Moved Permanently', 302: 'Found', 303: 'See Other', 304: 'Not Modified', 307: 'Temporary Redirect', 308: 'Permanent Redirect',
                  400: 'Bad Request', 401: 'Unauthorized', 403: 'Forbidden', 404: 'Not Found', 405: 'Method Not Allowed', 408: 'Request Timeout', 409: 'Conflict', 410: 'Gone', 418: 'I am a teapot', 422: 'Unprocessable Entity', 429: 'Too Many Requests',
                  500: 'Internal Server Error', 501: 'Not Implemented', 502: 'Bad Gateway', 503: 'Service Unavailable', 504: 'Gateway Timeout'
                };
                const usecases = {
                  200: 'Standardantwort für erfolgreiche GET- und POST-Anfragen.',
                  201: 'Eine neue Ressource wurde angelegt, etwa nach einem POST.',
                  204: 'Erfolg, aber kein Inhalt zurueck, oft bei DELETE.',
                  301: 'Seite dauerhaft umgezogen, Suchmaschinen sollen die neue URL übernehmen.',
                  302: 'Vorübergehende Umleitung, die alte URL bleibt gültig.',
                  304: 'Inhalt unveraendert, der Browser nutzt seinen Cache.',
                  400: 'Ungültige Anfrage, etwa fehlerhafte Parameter.',
                  401: 'Authentifizierung fehlt oder ist ungültig.',
                  403: 'Zugriff verweigert trotz gültiger Anmeldung.',
                  404: 'Die angeforderte Ressource existiert nicht.',
                  429: 'Zu viele Anfragen in kurzer Zeit, Rate-Limit erreicht.',
                  500: 'Allgemeiner Serverfehler ohne genauere Angabe.',
                  502: 'Ein vorgeschalteter Server lieferte eine ungültige Antwort.',
                  503: 'Server vorübergehend nicht verfuegbar, etwa bei Wartung.',
                  504: 'Ein vorgeschalteter Server antwortete nicht rechtzeitig.'
                };
                const cls = classes[Math.floor(code / 100)] || ['Unbekannt', 'Keine Standardklasse für diesen Code.'];
                const name = names[code] || 'Kein offizieller Standardname';
                const usecase = usecases[code] || 'Kein gesonderter Hinweis für diesen Code hinterlegt.';
                return rows([
                  ['Statuscode', num.format(code)],
                  ['Klasse', escapeHtml(cls[0])],
                  ['Klassen-Bedeutung', escapeHtml(cls[1])],
                  ['Name', escapeHtml(name)],
                  ['Typischer Fall', escapeHtml(usecase)]
                ]);
            }
            case 'passwort-staerke-score': {
                const pw = value('pw');
                const len = pw.length;
                const hasLower = /[a-z]/.test(pw);
                const hasUpper = /[A-Z]/.test(pw);
                const hasDigit = /[0-9]/.test(pw);
                const hasSpecial = /[^A-Za-z0-9]/.test(pw);
                let pool = 0;
                if (hasLower) pool += 26;
                if (hasUpper) pool += 26;
                if (hasDigit) pool += 10;
                if (hasSpecial) pool += 33;
                const entropy = len > 0 && pool > 0 ? len * (Math.log(pool) / Math.log(2)) : 0;
                let score = 0;
                score += clamp(len * 4, 0, 40);
                score += (hasLower ? 1 : 0) * 10 + (hasUpper ? 1 : 0) * 12 + (hasDigit ? 1 : 0) * 12 + (hasSpecial ? 1 : 0) * 16;
                if (/^[a-z]+$/i.test(pw) || /^[0-9]+$/.test(pw)) score -= 15;
                score = clamp(Math.round(score), 0, 100);
                let level = 'Sehr schwach';
                if (score >= 80) level = 'Sehr stark';
                else if (score >= 60) level = 'Stark';
                else if (score >= 40) level = 'Mittel';
                else if (score >= 20) level = 'Schwach';
                return rows([
                  ['Länge', num.format(len) + ' Zeichen'],
                  ['Kleinbuchstaben', hasLower ? 'Ja' : 'Nein'],
                  ['Grossbuchstaben', hasUpper ? 'Ja' : 'Nein'],
                  ['Zahlen', hasDigit ? 'Ja' : 'Nein'],
                  ['Sonderzeichen', hasSpecial ? 'Ja' : 'Nein'],
                  ['Geschätzte Entropie', num.format(entropy) + ' Bit'],
                  ['Score', num.format(score) + ' / 100'],
                  ['Einordnung', escapeHtml(level)]
                ]);
            }
            case 'keyword-dichte-light': {
                const text = value('text');
                const kw = value('kw').trim();
                const totalWords = (text.match(/\S+/g) || []).length;
                let count = 0;
                if (kw) {
                  const esc = kw.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                  const re = new RegExp(esc, 'gi');
                  count = (text.match(re) || []).length;
                }
                const kwWords = kw ? (kw.match(/\S+/g) || []).length : 1;
                const density = totalWords > 0 ? (count * kwWords / totalWords) * 100 : 0;
                let hint = 'Keine Bewertung';
                if (kw && totalWords > 0) {
                  if (density === 0) hint = 'Keyword kommt nicht vor';
                  else if (density < 1) hint = 'Eher niedrig';
                  else if (density <= 3) hint = 'Guter Bereich';
                  else hint = 'Möglicherweise zu hoch';
                }
                return rows([
                  ['Keyword', '<span class="mono">' + escapeHtml(kw || '-') + '</span>'],
                  ['Treffer', num.format(count)],
                  ['Woerter gesamt', num.format(totalWords)],
                  ['Keyword-Dichte', num.format(density) + ' %'],
                  ['Einordnung', escapeHtml(hint)]
                ]);
            }
            case 'reifenwechsel-termin-rechner': {
                const proKm = n('abrieb') / 10000;
                const restMm = Math.max(0, n('profil') - n('min'));
                const restKm = proKm > 0 ? restMm / proKm : 0;
                const restMonate = n('kmJahr') > 0 ? restKm / n('kmJahr') * 12 : 0;
                const genutzt = Math.max(0.1, n('neu') - n('min'));
                const prozent = clamp(restMm / genutzt * 100, 0, 100);
                return rows([
                  ['Nutzbares Restprofil', num.format(restMm) + ' mm'],
                  ['Restlaufleistung', num.format(restKm) + ' km'],
                  ['Restdauer', num.format(restMonate) + ' Monate'],
                  ['Profil verbleibend', num.format(prozent) + ' %']
                ]);
            }
            case 'maut-schaetzer-rechner': {
                const faktor = n('hinRueck') >= 1 ? 2 : 1;
                const strecke = n('km') * faktor;
                const mautStrecke = strecke * n('satz');
                const gesamt = mautStrecke + n('vignette');
                return rows([
                  ['Gefahrene Mautstrecke', num.format(strecke) + ' km'],
                  ['Streckenmaut', euro.format(mautStrecke)],
                  ['Vignette', euro.format(n('vignette'))],
                  ['Gesamtkosten', euro.format(gesamt)],
                  ['Kosten pro km', euro.format(strecke ? gesamt / strecke : 0)]
                ]);
            }
            case 'gepaeck-gewicht-splitter': {
                const anzahl = Math.max(1, Math.round(n('koffer')));
                const proKoffer = n('gesamt') / anzahl;
                const freiGesamt = anzahl * n('limit');
                const über = Math.max(0, n('gesamt') - freiGesamt);
                const gebühr = über * n('uebergewichtKg');
                const status = über > 0 ? 'Übergepäck!' : 'Im Limit';
                return rows([
                  ['Gewicht pro Koffer', num.format(proKoffer) + ' kg'],
                  ['Freigepäck gesamt', num.format(freiGesamt) + ' kg'],
                  ['Übergewicht', num.format(über) + ' kg'],
                  ['Status', status],
                  ['Geschätzte Gebühr', euro.format(gebühr)]
                ]);
            }
            case 'spritkosten-vergleich-rechner': {
                const kostenA = n('km') / 100 * n('verbA') * n('preisA');
                const kostenB = n('km') / 100 * n('verbB') * n('preisB');
                const diff = Math.abs(kostenA - kostenB);
                const guenstiger = kostenA <= kostenB ? 'Auto A' : 'Auto B';
                return rows([
                  ['Spritkosten Auto A / Jahr', euro.format(kostenA)],
                  ['Spritkosten Auto B / Jahr', euro.format(kostenB)],
                  ['Günstiger', guenstiger],
                  ['Ersparnis pro Jahr', euro.format(diff)],
                  ['Ersparnis pro Monat', euro.format(diff / 12)]
                ]);
            }
            case 'gefuehlte-temperatur-rechner': {
                const t = n('temp');
                const v = Math.max(0, n('wind'));
                const rh = clamp(n('feuchte'), 0, 100);
                let gefuehlt = t;
                let modus = 'Lufttemperatur';
                if (t <= 10 && v > 4.8) {
                  gefuehlt = 13.12 + 0.6215 * t - 11.37 * Math.pow(v, 0.16) + 0.3965 * t * Math.pow(v, 0.16);
                  modus = 'Windchill';
                } else if (t >= 27) {
                  gefuehlt = t + 0.05 * (rh - 40);
                  modus = 'Hitzeindex';
                }
                const diff = gefuehlt - t;
                return rows([
                  ['Gefühlte Temperatur', num.format(gefuehlt) + ' °C'],
                  ['Berechnungsmodus', modus],
                  ['Abweichung zur Luft', num.format(diff) + ' °C']
                ]);
            }
            case 'prozentpunkt-differenz-rechner': {
                const alt = n('alt');
                const neu = n('neu');
                const diffPp = neu - alt;
                const relativ = alt !== 0 ? (neu - alt) / Math.abs(alt) * 100 : 0;
                const richtung = diffPp > 0 ? 'Anstieg' : (diffPp < 0 ? 'Rueckgang' : 'unveraendert');
                return rows([
                  ['Alter Wert', num.format(alt) + ' %'],
                  ['Neuer Wert', num.format(neu) + ' %'],
                  ['Differenz in Prozentpunkten', num.format(diffPp) + ' Prozentpunkte'],
                  ['Relative Veraenderung', alt !== 0 ? num.format(relativ) + ' %' : 'nicht definiert (alter Wert 0)'],
                  ['Richtung', richtung]
                ]);
            }
            case 'mittelwert-median-rechner': {
                const list = parseNumbers(value('werte')).sort(function(x, y){ return x - y; });
                const count = list.length;
                const sum = list.reduce(function(s, v){ return s + v; }, 0);
                const mean = count ? sum / count : 0;
                let median = 0;
                if (count) {
                  const mid = Math.floor(count / 2);
                  median = count % 2 ? list[mid] : (list[mid - 1] + list[mid]) / 2;
                }
                return rows([
                  ['Anzahl Werte', num.format(count)],
                  ['Summe', num.format(sum)],
                  ['Mittelwert (Durchschnitt)', num.format(mean)],
                  ['Median', num.format(median)],
                  ['Minimum', count ? num.format(list[0]) : '-'],
                  ['Maximum', count ? num.format(list[count - 1]) : '-'],
                  ['Spannweite', count ? num.format(list[count - 1] - list[0]) : '-']
                ]);
            }
            case '72er-regel-rechner': {
                const zins = Math.max(n('zins'), 0.01);
                const faust = 72 / zins;
                const exakt = Math.log(2) / Math.log(1 + zins / 100);
                const kapital = n('kapital');
                return rows([
                  ['Verdopplung (72er-Regel)', num.format(faust) + ' Jahre'],
                  ['Verdopplung (exakt)', num.format(exakt) + ' Jahre'],
                  ['Aus Kapital wird', kapital ? euro.format(kapital * 2) : '-'],
                  ['Vervierfachung (ca.)', num.format(faust * 2) + ' Jahre']
                ]);
            }
            case 'email-oeffnungsrate-ziel-rechner': {
                const empf = Math.max(n('empfaenger'), 0);
                const geoeffnet = n('geoeffnet');
                const ziel = n('ziel');
                const aktuell = empf ? geoeffnet / empf * 100 : 0;
                const nötig = Math.round(empf * ziel / 100);
                const luecke = Math.max(nötig - geoeffnet, 0);
                return rows([
                  ['Aktuelle Oeffnungsrate', num.format(aktuell) + ' %'],
                  ['Oeffnungen für Ziel', num.format(nötig)],
                  ['Noch benötigte Oeffnungen', num.format(luecke)],
                  ['Ziel erreicht?', aktuell >= ziel ? 'Ja, Ziel erreicht' : 'Noch nicht']
                ]);
            }
            case 'umsatzrentabilitaet-rechner': {
                const umsatz = n('umsatz');
                const gewinn = n('gewinn');
                const rentabilitaet = umsatz ? gewinn / umsatz * 100 : 0;
                const kosten = umsatz - gewinn;
                const kostenquote = umsatz ? kosten / umsatz * 100 : 0;
                return rows([
                  ['Umsatzrentabilitaet', num.format(rentabilitaet) + ' %'],
                  ['Gewinn', euro.format(gewinn)],
                  ['Kosten (Umsatz - Gewinn)', euro.format(kosten)],
                  ['Kostenquote', num.format(kostenquote) + ' %']
                ]);
            }
            case 'rendite-nach-steuern-rechner': {
                const invest = Math.max(0, n('invest')); const grossGain = invest * n('rate') / 100; const freibetrag = Math.max(0, n('freibetrag')); const steuerpflichtig = Math.max(0, grossGain - freibetrag); const kest = steuerpflichtig * 0.25; const soliBetrag = value('soli') === 'ja' ? kest * 0.055 : 0; const steuer = kest + soliBetrag; const netGain = grossGain - steuer; const effSteuer = grossGain > 0 ? steuer / grossGain * 100 : 0; const netRate = invest > 0 ? netGain / invest * 100 : 0; return rows([['Brutto-Gewinn', euro.format(grossGain)],['Steuerfreier Anteil (Pauschbetrag)', euro.format(Math.min(grossGain, freibetrag))],['Steuerpflichtiger Gewinn', euro.format(steuerpflichtig)],['Abgeltungssteuer (25 %)', euro.format(kest)],['Solidaritaetszuschlag', euro.format(soliBetrag)],['Steuerlast gesamt', euro.format(steuer)],['Effektiver Steuersatz', num.format(effSteuer) + ' %'],['Netto-Gewinn nach Steuern', euro.format(netGain)],['Netto-Rendite', num.format(netRate) + ' %'],['Endkapital nach Steuern', euro.format(invest + netGain)]]);
            }
            case 'notgroschen-rechner': {
                const ausgaben = Math.max(0, n('ausgaben')); const monate = Math.max(0, n('monate')); const ziel = ausgaben * monate; const vorhanden = Math.max(0, n('vorhanden')); const fehlt = Math.max(0, ziel - vorhanden); const sparrate = n('sparrate'); const monateBisZiel = sparrate > 0 ? Math.ceil(fehlt / sparrate) : 0; const fortschritt = ziel > 0 ? Math.min(100, vorhanden / ziel * 100) : 100; return rows([['Empfohlener Notgroschen', euro.format(ziel)],['Bereits gespart', euro.format(vorhanden)],['Noch noetig', euro.format(fehlt)],['Fortschritt', num.format(fortschritt) + ' %'],['Aktuell abgedeckte Monate', num.format(ausgaben > 0 ? vorhanden / ausgaben : 0)],['Dauer bis Ziel', fehlt === 0 ? 'Ziel erreicht' : (sparrate > 0 ? num.format(monateBisZiel) + ' Monate' : 'Sparrate eingeben')],['Voraussichtlich fertig in', fehlt === 0 ? 'Erreicht' : (sparrate > 0 ? num.format(monateBisZiel / 12) + ' Jahren' : '-')],['Ziel bei 3 Monaten', euro.format(ausgaben * 3)],['Ziel bei 6 Monaten', euro.format(ausgaben * 6)],['Ziel bei 12 Monaten', euro.format(ausgaben * 12)]]);
            }
            case 'kreditangebot-vergleich-rechner': {
                const betrag = Math.max(0, n('betrag')); function rate(zins, monate){ const m = Math.max(1, monate); const i = zins / 100 / 12; if (i === 0) return betrag / m; return betrag * i / (1 - Math.pow(1 + i, -m)); } const mA = Math.max(1, n('laufzeitA')); const mB = Math.max(1, n('laufzeitB')); const rateA = rate(n('zinsA'), mA); const rateB = rate(n('zinsB'), mB); const totalA = rateA * mA; const totalB = rateB * mB; const zinsenA = totalA - betrag; const zinsenB = totalB - betrag; const guenstiger = totalA <= totalB ? 'Angebot A' : 'Angebot B'; const ersparnis = Math.abs(totalA - totalB); return rows([['Guenstigeres Angebot (Gesamtkosten)', guenstiger],['Ersparnis gesamt', euro.format(ersparnis)],['Monatsrate A', euro.format(rateA)],['Monatsrate B', euro.format(rateB)],['Gesamtkosten A', euro.format(totalA)],['Gesamtkosten B', euro.format(totalB)],['Zinskosten A', euro.format(zinsenA)],['Zinskosten B', euro.format(zinsenB)],['Laufzeit A', num.format(mA) + ' Monate'],['Laufzeit B', num.format(mB) + ' Monate'],['Niedrigere Monatsrate', rateA <= rateB ? 'Angebot A' : 'Angebot B']]);
            }
            case 'gehalt-inflationsausgleich-rechner': {
                const gehalt = Math.max(0, n('gehalt')); const inflation = n('inflation'); const jahre = Math.max(0, n('jahre')); const faktor = Math.pow(1 + inflation / 100, jahre); const kaufkraft = faktor > 0 ? gehalt / faktor : gehalt; const verlust = gehalt - kaufkraft; const noetigesGehalt = gehalt * faktor; const noetigeErhoehung = (faktor - 1) * 100; const neuesGehalt = gehalt * (1 + n('erhoehung') / 100); const realerGewinn = faktor > 0 ? neuesGehalt / faktor : neuesGehalt; const realDiff = realerGewinn - gehalt; return rows([['Kaufkraft heute', euro.format(gehalt)],['Kaufkraft in ' + num.format(jahre) + ' Jahren', euro.format(kaufkraft)],['Kaufkraftverlust', euro.format(verlust)],['Verlust in Prozent', num.format(gehalt > 0 ? verlust / gehalt * 100 : 0) + ' %'],['Noetiges Gehalt fuer Ausgleich', euro.format(noetigesGehalt)],['Noetige Erhoehung gesamt', num.format(noetigeErhoehung) + ' %'],['Noetige Erhoehung pro Jahr ca.', num.format(inflation) + ' %'],['Dein Gehalt nach Erhoehung', euro.format(neuesGehalt)],['Reale Kaufkraft nach Erhoehung', euro.format(realerGewinn)],['Realer Zugewinn/Verlust', euro.format(realDiff)]]);
            }
            case 'dynamische-sparrate-rechner': {
                let kapital = Math.max(0, n('start')); const jahre = clamp(Math.round(n('jahre')), 0, 100); const zinsM = n('zins') / 100 / 12; let rate = Math.max(0, n('rate')); const dyn = 1 + n('dynamik') / 100; let eingezahlt = Math.max(0, n('start')); for (let j = 0; j < jahre; j++){ for (let m = 0; m < 12; m++){ kapital = kapital * (1 + zinsM) + rate; eingezahlt += rate; } rate = rate * dyn; } const gewinn = kapital - eingezahlt; const letzteRate = Math.max(0, n('rate')) * Math.pow(dyn, Math.max(0, jahre - 1)); return rows([['Endkapital', euro.format(kapital)],['Eingezahlt gesamt', euro.format(eingezahlt)],['Zinsgewinn', euro.format(gewinn)],['Gewinn-Anteil am Endkapital', num.format(kapital > 0 ? gewinn / kapital * 100 : 0) + ' %'],['Startkapital', euro.format(Math.max(0, n('start')))],['Sparrate im 1. Jahr', euro.format(Math.max(0, n('rate')))],['Sparrate im letzten Jahr', euro.format(letzteRate)],['Laufzeit', num.format(jahre) + ' Jahre'],['Faktor (Endkapital / Eingezahlt)', num.format(eingezahlt > 0 ? kapital / eingezahlt : 0) + ' x'],['Vergleich ohne Zinsen', euro.format(eingezahlt)]]);
            }
            case 'ueberholweg-rechner': {
                const vown = Math.max(1, n('vown')); const vfront = Math.max(0, Math.min(vown - 0.1, n('vfront'))); const lfront = Math.max(0, n('lfront')); const lown = Math.max(0, n('lown')); const vDiff = vown - vfront; const vDiffMs = vDiff / 3.6; const vownMs = vown / 3.6; const vfrontMs = vfront / 3.6; const gapVor = vfrontMs * 1.8; const gapNach = vownMs * 1.8; const distLuecke = gapVor + lfront + lown + gapNach; const zeit = vDiffMs > 0 ? distLuecke / vDiffMs : 0; const ueberholweg = vownMs * zeit; const gegenWeg = vownMs * zeit; const gesamtSicht = ueberholweg + gegenWeg; return rows([['Geschwindigkeitsdifferenz', num.format(vDiff) + ' km/h'],['Dauer des Überholvorgangs', num.format(zeit) + ' s'],['Dein zurückgelegter Überholweg', num.format(ueberholweg) + ' m'],['Strecke des Vordermanns währenddessen', num.format(vfrontMs * zeit) + ' m'],['Abstand davor (1,8 s Vordermann)', num.format(gapVor) + ' m'],['Abstand danach (1,8 s eigen)', num.format(gapNach) + ' m'],['Zu überbrückende Lücke gesamt', num.format(distLuecke) + ' m'],['Weg des Gegenverkehrs währenddessen', num.format(gegenWeg) + ' m'],['Nötige freie Sicht nach vorn', num.format(gesamtSicht) + ' m'],['Empfehlung', vDiff < 20 ? 'Differenz klein - Überholen dauert lange, lieber abwarten' : 'Überholen vertretbar, wenn freie Sicht ausreicht']]);
            }
            case 'frostschutz-mischung-rechner': {
                const volumen = Math.max(0.1, n('volumen')); const frost = -Math.abs(n('frost')); const target = Math.min(0, frost); let anteil; if (target >= 0) anteil = 0; else if (target >= -37) anteil = clamp(0.20 + (Math.abs(target) - 13) / 24 * 0.30, 0, 0.6); else anteil = 0.6; anteil = clamp(anteil, 0, 0.6); const konz = volumen * anteil; const wasser = volumen - konz; const erreichbar = anteil >= 0.6 ? -37 : Math.round(-(13 + (anteil - 0.20) / 0.30 * 24)); const kosten = konz * Math.max(0, n('preis')); return rows([['Empfohlenes Mischverhältnis', num.format(anteil * 100) + ' % Konzentrat'],['Frostschutz-Konzentrat', num.format(konz) + ' l'],['Wasser (möglichst destilliert)', num.format(wasser) + ' l'],['Verhältnis Konzentrat zu Wasser', '1 : ' + num.format(konz > 0 ? wasser / konz : 0)],['Erreichter Frostschutz ca.', num.format(erreichbar) + ' Grad C'],['Kosten Konzentrat', euro.format(kosten)],['Kühlervolumen gesamt', num.format(volumen) + ' l'],['Hinweis', Math.abs(target) > 37 ? 'Unter -37 Grad bringt mehr Konzentrat keinen Vorteil' : 'Maximal etwa 60 % Konzentrat einsetzen']]);
            }
            case 'auto-abstand-tempo-rechner': {
                const tempo = Math.max(0, n('tempo')); const abstand = Math.max(0, n('abstand')); const halberTacho = tempo / 2; const reaktionsweg = tempo / 3.6 * 1; const verhaeltnis = halberTacho > 0 ? abstand / halberTacho : 1; const zeitluecke = tempo > 0 ? abstand / (tempo / 3.6) : 0; let bewertung; let bussgeld; if (verhaeltnis >= 1) { bewertung = 'ausreichend'; bussgeld = 0; } else if (verhaeltnis >= 0.5) { bewertung = 'zu gering'; bussgeld = tempo > 80 ? 75 : 25; } else if (verhaeltnis >= 0.333) { bewertung = 'deutlich zu gering'; bussgeld = tempo > 80 ? 100 : 35; } else { bewertung = 'gefährlich gering'; bussgeld = tempo > 80 ? 160 : 35; } const fehlend = Math.max(0, halberTacho - abstand); return rows([['Empfohlener Mindestabstand (halber Tacho)', num.format(halberTacho) + ' m'],['Dein Abstand', num.format(abstand) + ' m'],['Abstand als Zeitlücke', num.format(zeitluecke) + ' s'],['Fehlende Meter', num.format(fehlend) + ' m'],['Anteil des Soll-Abstands', num.format(verhaeltnis * 100) + ' %'],['Reaktionsweg bei 1 s', num.format(reaktionsweg) + ' m'],['Bewertung', escapeHtml(bewertung)],['Mögliches Bußgeld (Richtwert)', bussgeld > 0 ? euro.format(bussgeld) : 'kein Bußgeld'],['Punkte in Flensburg (Richtwert)', tempo > 80 && verhaeltnis < 0.333 ? '1 Punkt möglich' : 'keine'],['Faustregel', '2-Sekunden-Abstand entspricht ca. ' + num.format(tempo / 3.6 * 2) + ' m']]);
            }
            case 'motoroel-wechsel-rechner': {
                const fuellmenge = Math.max(0, n('fuellmenge')); const preis = Math.max(0, n('preis')); const filter = Math.max(0, n('filter')); const intervall = Math.max(1, n('intervall')); const kmstand = Math.max(0, n('kmstand')); const oelkosten = fuellmenge * preis; const gesamt = oelkosten + filter; const proKm = gesamt / intervall; const naechster = Math.ceil((kmstand + 1) / intervall) * intervall; const bisNaechster = naechster - kmstand; const proJahr15tkm = gesamt * (15000 / intervall); return rows([['Benötigte Ölmenge', num.format(fuellmenge) + ' l'],['Reine Ölkosten', euro.format(oelkosten)],['Ölfilter', euro.format(filter)],['Materialkosten je Wechsel', euro.format(gesamt)],['Kosten pro 1.000 km', euro.format(proKm * 1000)],['Kosten pro km', euro.format(proKm)],['Nächster Ölwechsel bei', num.format(naechster) + ' km'],['Noch bis zum Wechsel', num.format(bisNaechster) + ' km'],['Geschätzte Kosten bei 15.000 km/Jahr', euro.format(proJahr15tkm)],['Tipp', fuellmenge < 3 ? 'Kleiner Motor - prüfe die genaue Füllmenge im Handbuch' : 'Altöl gehört kostenlos zur Sammelstelle oder zum Verkäufer zurück']]);
            }
            case 'dachlast-zuladung-rechner': {
                const leer = Math.max(0, n('leergewicht')); const zul = Math.max(0, n('zulgesamt')); const dachMax = Math.max(0, n('dachlastmax')); const personen = Math.max(0, Math.round(n('personen'))); const gpp = Math.max(0, n('gewichtproperson')); const gepaeck = Math.max(0, n('gepaeck')); const dach = Math.max(0, n('dachtraeger')); const zuladungMax = Math.max(0, zul - leer); const personenGewicht = personen * gpp; const beladung = personenGewicht + gepaeck + dach; const aktuell = leer + beladung; const restZuladung = zuladungMax - beladung; const dachOk = dach <= dachMax; const gesamtOk = aktuell <= zul; const restPersonen = gpp > 0 ? Math.floor(Math.max(0, restZuladung) / gpp) : 0; return rows([['Maximale Zuladung', num.format(zuladungMax) + ' kg'],['Personen gesamt', num.format(personenGewicht) + ' kg'],['Beladung gesamt', num.format(beladung) + ' kg'],['Aktuelles Gewicht', num.format(aktuell) + ' kg'],['Verbleibende Zuladung', num.format(restZuladung) + ' kg'],['Gesamtgewicht', gesamtOk ? 'OK - unter dem Limit' : 'ÜBERLADEN um ' + num.format(aktuell - zul) + ' kg'],['Dachlast genutzt', num.format(dach) + ' von ' + num.format(dachMax) + ' kg'],['Dachlast', dachOk ? 'OK' : 'ÜBERSCHRITTEN um ' + num.format(dach - dachMax) + ' kg'],['Auslastung Zuladung', num.format(zuladungMax > 0 ? beladung / zuladungMax * 100 : 0) + ' %'],['Noch Platz für Personen', restZuladung > 0 ? num.format(restPersonen) + ' Person(en)' : 'keine'],['Gesamtbewertung', gesamtOk && dachOk ? 'Beladung zulässig' : 'Beladung nicht zulässig - umverteilen']]);
            }
            case 'reisebudget-tagessatz-rechner': {
                const budget = n('budget'); const fix = Math.min(n('fix'), budget); const tage = Math.max(1, Math.round(n('tage'))); const personen = Math.max(1, Math.round(n('personen'))); const rest = Math.max(0, budget - fix); const proTag = rest / tage; const proTagPerson = proTag / personen; const proPerson = budget / personen; return rows([['Verfügbar nach Fixkosten', euro.format(rest)], ['Budget pro Tag (gesamt)', euro.format(proTag)], ['Budget pro Tag & Person', euro.format(proTagPerson)], ['Gesamtbudget pro Person', euro.format(proPerson)], ['Fixkostenanteil', num.format(budget > 0 ? fix / budget * 100 : 0) + ' %']]);
            }
            case 'auslandskrankenversicherung-rechner': {
                const praemie = Math.max(0, n('praemie')); const tage = Math.max(1, Math.round(n('tage'))); const personen = Math.max(1, Math.round(n('personen'))); const proPerson = praemie * tage; const gesamt = proPerson * personen; const jahr = Math.max(0, n('jahrestarif')); const jahrGesamt = jahr * personen; const out = [['Kosten pro Person', euro.format(proPerson)], ['Gesamtkosten Einzelreise', euro.format(gesamt)], ['Kosten pro Reisetag (gesamt)', euro.format(gesamt / tage)]]; if (jahr > 0) { out.push(['Jahrestarif gesamt', euro.format(jahrGesamt)]); out.push(['Günstiger ist', jahrGesamt < gesamt ? 'Jahrestarif' : 'Einzelreise']); out.push(['Ersparnis', euro.format(Math.abs(gesamt - jahrGesamt))]); } return rows(out);
            }
            case 'flugzeit-zeitverschiebung-rechner': {
                const abMin = clamp(Math.round(n('abflug_std')), 0, 23) * 60 + clamp(Math.round(n('abflug_min')), 0, 59); const dauerMin = Math.max(0, Math.round(n('dauer_std')) * 60 + Math.round(n('dauer_min'))); const shift = Math.round(n('verschiebung') * 60); const ankunftHeimat = abMin + dauerMin; const ankunftLokalRaw = ankunftHeimat + shift; const tage = Math.floor(ankunftLokalRaw / 1440); const lokal = ((ankunftLokalRaw % 1440) + 1440) % 1440; const fmt = m => String(Math.floor(m / 60)).padStart(2, '0') + ':' + String(m % 60).padStart(2, '0'); const tagText = tage > 0 ? '+' + tage + ' Tag' + (tage > 1 ? 'e' : '') : (tage < 0 ? tage + ' Tag' : 'gleicher Tag'); return rows([['Flugdauer', num.format(dauerMin / 60) + ' h'], ['Lokale Ankunftszeit', escapeHtml(fmt(lokal)) + ' Uhr'], ['Tageswechsel', escapeHtml(tagText)], ['Ankunft in Heimatzeit', escapeHtml(fmt(((ankunftHeimat % 1440) + 1440) % 1440)) + ' Uhr'], ['Zeitverschiebung', (shift >= 0 ? '+' : '') + num.format(shift / 60) + ' h']]);
            }
            case 'roaming-datenvolumen-rechner': {
                const tage = Math.max(1, Math.round(n('tage'))); const surfenMB = n('surfen') * 1.5; const mapsMB = n('maps') * 5; const videoMB = n('video') * 25; const tagMB = surfenMB + mapsMB + videoMB; const gesamtMB = tagMB * tage; const gesamtGB = gesamtMB / 1024; const preisGb = Math.max(0, n('preis_gb')); return rows([['Verbrauch pro Tag', num.format(tagMB) + ' MB'], ['Gesamt für ' + tage + ' Tage', num.format(gesamtGB) + ' GB'], ['Empfohlenes Paket (+20 %)', num.format(Math.ceil(gesamtGB * 1.2)) + ' GB'], ['Geschätzte Kosten', euro.format(Math.ceil(gesamtGB * 1.2) * preisGb)], ['Davon Streaming-Anteil', num.format(tagMB > 0 ? videoMB / tagMB * 100 : 0) + ' %']]);
            }
            case 'kilometergeld-reise-rechner': {
                const strecke = Math.max(0, n('strecke')); const satz = Math.max(0, n('satz')); const fahrten = Math.max(1, Math.round(n('fahrten'))); const mitfahrer = Math.max(0, Math.round(n('mitfahrer'))); const zuschlag = Math.max(0, n('zuschlag')); const gesamtKm = strecke * fahrten; const grund = gesamtKm * satz; const extra = gesamtKm * mitfahrer * zuschlag; const summe = grund + extra; return rows([['Gesamtkilometer', num.format(gesamtKm) + ' km'], ['Grundpauschale', euro.format(grund)], ['Mitfahrer-Zuschlag', euro.format(extra)], ['Gesamtes Kilometergeld', euro.format(summe)], ['Pro Fahrt', euro.format(summe / fahrten)]]);
            }
            case 'trinkgeld-tronc-rechner': {
                const pot = Math.max(0, n('pot')); const people = Math.max(1, Math.round(n('people'))); const hours = Math.max(0.1, n('hours')); const housePct = clamp(n('house'), 0, 100); const houseCut = pot * housePct / 100; const teamPot = pot - houseCut; const totalHours = people * hours; const perHour = teamPot / Math.max(0.1, totalHours); const perPerson = teamPot / people; return rows([['Topf gesamt', euro.format(pot)],['Hausanteil ('+num.format(housePct)+' %)', euro.format(houseCut)],['Verteilbar an Team', euro.format(teamPot)],['Pro Person (gleiche Stunden)', euro.format(perPerson)],['Pro Stunde', euro.format(perHour)],['Geleistete Stunden gesamt', num.format(totalHours)+' h'],['Beispiel: 6 h Schicht', euro.format(perHour * 6)],['Beispiel: 10 h Schicht', euro.format(perHour * 10)],['Anteil je Person in Prozent', num.format(100 / people)+' %']]);
            }
            case 'rezept-portionen-skalierer': {
                const from = Math.max(0.1, n('from')); const to = Math.max(0, n('to')); const factor = to / from; const lines = String(value('list')).split('\n').map(l => l.trim()).filter(Boolean); const out = lines.map(line => { const m = line.match(/^(\d+(?:[.,]\d+)?)(.*)$/); if (!m) { return [escapeHtml(line), '&mdash;']; } const qty = parseFloat(m[1].replace(',', '.')); const rest = m[2].trim(); const scaled = qty * factor; const shown = Math.round(scaled * 100) / 100; return [escapeHtml(rest || 'Zutat'), num.format(shown)]; }); if (!out.length) { out.push(['Keine Zutaten erkannt', 'Bitte Liste eingeben']); } return rows([['Skalierungsfaktor', num.format(Math.round(factor * 1000) / 1000) + ' x'],['Von Portionen', num.format(from)],['Auf Portionen', num.format(to)], ...out]);
            }
            case 'countdown-tage-rechner': {
                const raw = value('target'); const today = new Date(); today.setHours(0,0,0,0); let target = new Date(raw); if (isNaN(target.getTime())) { target = today; } target.setHours(0,0,0,0); const msDay = 86400000; const diffDays = Math.round((target - today) / msDay); const abs = Math.abs(diffDays); const future = diffDays >= 0; const weeks = abs / 7; const sleepH = clamp(n('perday'), 0, 24); const sleepHours = abs * sleepH; let weekends = 0; for (let i = 1; i <= abs; i++) { const d = new Date(today.getTime() + (future ? 1 : -1) * i * msDay); const wd = d.getDay(); if (wd === 0 || wd === 6) weekends++; } const label = future ? 'verbleiben bis' : 'vergangen seit'; const status = future ? (diffDays === 0 ? 'Heute ist der Tag!' : 'Noch ' + num.format(abs) + ' Tage') : 'Liegt ' + num.format(abs) + ' Tage zurück'; return rows([['Status', status],['Zieldatum', escapeHtml(target.toLocaleDateString('de-DE', {weekday:'long', year:'numeric', month:'long', day:'numeric'}))],['Tage ' + label, num.format(abs)],['Wochen', num.format(Math.round(weeks * 10) / 10)],['Monate (ca.)', num.format(Math.round(abs / 30.44 * 10) / 10)],['Wochenend-Tage', num.format(weekends)],['Nächte mit Schlaf', num.format(abs)],['Schlaf gesamt', num.format(Math.round(sleepHours)) + ' h'],['Stunden gesamt', num.format(abs * 24)]]);
            }
            case 'pizza-flaeche-vergleich': {
                const area = d => Math.PI * Math.pow(Math.max(0, d) / 2, 2); const a1 = area(n('d1')) * Math.max(0, n('c1')); const a2 = area(n('d2')) * Math.max(0, n('c2')); const p1 = Math.max(0, n('p1')); const p2 = Math.max(0, n('p2')); const ppc1 = a1 > 0 ? p1 / a1 : 0; const ppc2 = a2 > 0 ? p2 / a2 : 0; let verdict = 'Gleichstand'; if (ppc1 > 0 && ppc2 > 0) { verdict = ppc1 < ppc2 ? 'Option A ist günstiger pro Fläche' : (ppc2 < ppc1 ? 'Option B ist günstiger pro Fläche' : 'Beide gleich teuer pro Fläche'); } const moreArea = a1 >= a2 ? 'Option A' : 'Option B'; const ratio = Math.min(a1, a2) > 0 ? Math.max(a1, a2) / Math.min(a1, a2) : 0; return rows([['Empfehlung', verdict],['Mehr Pizza-Fläche', moreArea],['Fläche Option A', num.format(Math.round(a1)) + ' cm2'],['Fläche Option B', num.format(Math.round(a2)) + ' cm2'],['Preis pro 100 cm2 A', euro.format(ppc1 * 100)],['Preis pro 100 cm2 B', euro.format(ppc2 * 100)],['Fläche pro Euro A', num.format(p1 > 0 ? Math.round(a1 / p1) : 0) + ' cm2'],['Fläche pro Euro B', num.format(p2 > 0 ? Math.round(a2 / p2) : 0) + ' cm2'],['Flächen-Verhältnis', num.format(Math.round(ratio * 100) / 100) + ' : 1']]);
            }
            case 'waeschekorb-beladung-rechner': {
                const drum = Math.max(0.5, n('drum')); const shirts = Math.max(0, Math.round(n('shirts'))); const pants = Math.max(0, Math.round(n('pants'))); const towels = Math.max(0, Math.round(n('towels'))); const bed = Math.max(0, Math.round(n('bed'))); const totalG = shirts * 200 + pants * 600 + towels * 400 + bed * 800; const totalKg = totalG / 1000; const fill = clamp(n('fill'), 30, 100); const usable = drum * fill / 100; const loads = usable > 0 ? Math.ceil(totalKg / usable) : 0; const perLoad = loads > 0 ? totalKg / loads : 0; const fullPct = drum > 0 ? totalKg / drum * 100 : 0; const items = shirts + pants + towels + bed; let hint = 'Passt in eine Ladung'; if (loads > 1) hint = num.format(loads) + ' Ladungen nötig'; else if (fullPct < 50) hint = 'Trommel halb leer, ggf. Sparprogramm'; return rows([['Empfehlung', hint],['Geschätztes Gesamtgewicht', num.format(Math.round(totalKg * 10) / 10) + ' kg'],['Anzahl Wäschestücke', num.format(items)],['Nutzbare Beladung (' + num.format(fill) + ' %)', num.format(Math.round(usable * 10) / 10) + ' kg'],['Benötigte Waschladungen', num.format(loads)],['Gewicht pro Ladung', num.format(Math.round(perLoad * 10) / 10) + ' kg'],['Füllgrad bei einer Ladung', num.format(Math.round(fullPct)) + ' %'],['Schwerstes: Bettwäsche', num.format(Math.round(bed * 0.8 * 10) / 10) + ' kg'],['Leichtestes: Oberteile', num.format(Math.round(shirts * 0.2 * 10) / 10) + ' kg']]);
            }
            case 'geraete-stromkosten-vergleich-rechner': {
                const wattA = Math.max(0, n('wattA')); const wattB = Math.max(0, n('wattB')); const stunden = Math.max(0, n('stunden')); const preis = Math.max(0, n('preis')); const kwhA = wattA / 1000 * stunden * 365; const kwhB = wattB / 1000 * stunden * 365; const kostenA = kwhA * preis; const kostenB = kwhB * preis; const diff = kostenA - kostenB; const guenstiger = diff > 0 ? 'Gerät B' : (diff < 0 ? 'Gerät A' : 'gleich teuer'); const out = [['Jahreskosten Gerät A', euro.format(kostenA)], ['Jahreskosten Gerät B', euro.format(kostenB)], ['Verbrauch A / B pro Jahr', num.format(kwhA) + ' / ' + num.format(kwhB) + ' kWh'], ['Günstiger', escapeHtml(guenstiger)], ['Ersparnis pro Jahr', euro.format(Math.abs(diff))]]; const neu = Math.max(0, n('neupreis')); if (neu > 0 && diff > 0) { out.push(['Amortisation Neukauf', num.format(neu / Math.max(0.01, diff)) + ' Jahre']); } return rows(out);
            }
            case 'dusche-vs-vollbad-rechner': {
                const minuten = Math.max(0, n('minuten')); const durchfluss = Math.max(0, n('durchfluss')); const wanne = Math.max(0, n('wanne')); const wPreis = Math.max(0, n('wasserpreis')); const ePreis = Math.max(0, n('energiepreis')); const literDusche = minuten * durchfluss; const literBad = wanne; const kwhProLiter = 0.035; const kostenDusche = literDusche / 1000 * wPreis + literDusche * kwhProLiter * ePreis; const kostenBad = literBad / 1000 * wPreis + literBad * kwhProLiter * ePreis; const diff = kostenBad - kostenDusche; const jahr = diff * 365; return rows([['Wasser Dusche', num.format(literDusche) + ' L'], ['Wasser Vollbad', num.format(literBad) + ' L'], ['Kosten pro Dusche', euro.format(kostenDusche)], ['Kosten pro Vollbad', euro.format(kostenBad)], ['Ersparnis je Vorgang', euro.format(Math.abs(diff))], ['Günstiger', escapeHtml(diff > 0 ? 'Dusche' : (diff < 0 ? 'Vollbad' : 'gleich teuer'))], ['Ersparnis bei 1x täglich/Jahr', euro.format(Math.abs(jahr))]]);
            }
            case 'lueftungs-empfehlung-rechner': {
                const flaeche = Math.max(1, n('flaeche')); const hoehe = Math.max(2, n('hoehe')); const personen = Math.max(1, Math.round(n('personen'))); const jz = value('jahreszeit'); const feucht = value('feucht') === 'ja'; const volumen = flaeche * hoehe; let dauer = jz === 'winter' ? 5 : (jz === 'sommer' ? 20 : 10); let mal = 3 + (personen - 1); if (feucht) mal += 2; mal = clamp(mal, 3, 8); dauer = clamp(Math.round(dauer + (personen - 1) * 1.5), 4, 30); const minutenTag = mal * dauer; return rows([['Raumvolumen', num.format(volumen) + ' m³'], ['Stoßlüften pro Tag', num.format(mal) + ' x'], ['Dauer je Lüftung', num.format(dauer) + ' Minuten'], ['Lüftungszeit gesamt/Tag', num.format(minutenTag) + ' Minuten'], ['Empfehlung', escapeHtml('Quer- statt Kipplüften für ' + dauer + ' Min, ' + mal + 'x täglich')]]);
            }
            case 'vorrats-reichweite-rechner': {
                const vorrat = Math.max(0, n('vorrat')); const personen = Math.max(1, Math.round(n('personen'))); const verbrauch = Math.max(0.0001, n('verbrauch')); const reserve = Math.max(0, Math.round(n('reserve'))); const proTag = verbrauch * personen; const tage = vorrat / proTag; const tageGanz = Math.floor(tage); const nachkaufTag = Math.max(0, tageGanz - reserve); const d = new Date(); d.setDate(d.getDate() + tageGanz); const leerDatum = d.toLocaleDateString('de-DE'); const n2 = new Date(); n2.setDate(n2.getDate() + nachkaufTag); const nachDatum = n2.toLocaleDateString('de-DE'); return rows([['Verbrauch pro Tag', num.format(proTag) + ' Einheiten'], ['Reichweite', num.format(Math.round(tage * 10) / 10) + ' Tage'], ['Das entspricht', num.format(Math.round(tage / 7 * 10) / 10) + ' Wochen'], ['Vorrat leer am', escapeHtml(leerDatum)], ['Nachkaufen ab', escapeHtml(nachDatum) + ' (' + reserve + ' Tage Puffer)']]);
            }
            case 'luftbefeuchter-wasserbedarf-rechner': {
                const leistung = Math.max(0, n('leistung')); const stunden = clamp(n('stunden'), 0, 24); const tank = Math.max(0.1, n('tank')); const stufe = value('stufe'); const faktor = stufe === 'niedrig' ? 0.6 : (stufe === 'hoch' ? 1.3 : 1); const mlProStunde = leistung * faktor; const proTagMl = mlProStunde * stunden; const proTagL = proTagMl / 1000; const tankMl = tank * 1000; const reichweiteStd = mlProStunde > 0 ? tankMl / mlProStunde : 0; const fuellungenTag = proTagL / tank; const proWoche = proTagL * 7; return rows([['Verbrauch pro Stunde', num.format(mlProStunde) + ' ml'], ['Verbrauch pro Tag', num.format(Math.round(proTagL * 100) / 100) + ' L'], ['Reichweite einer Tankfüllung', num.format(Math.round(reichweiteStd * 10) / 10) + ' Stunden'], ['Nachfüllen', num.format(Math.round(fuellungenTag * 100) / 100) + ' x pro Tag'], ['Verbrauch pro Woche', num.format(Math.round(proWoche * 10) / 10) + ' L']]);
            }
            case 'fleisch-auftauzeit-rechner': {
                const weight = Math.max(50, n('weight')); const kg = weight / 1000; const method = value('method') || 'fridge'; const ratesPerKg = { fridge: 12, water: 1.5, room: 3 }; const labels = { fridge: 'Kühlschrank', water: 'Kaltes Wasserbad', room: 'Raumtemperatur' }; const hours = kg * (ratesPerKg[method] || 12); const totalMin = Math.round(hours * 60); const h = Math.floor(totalMin / 60); const m = totalMin % 60; const readyRaw = value('ready') || '18:00'; const parts = readyRaw.split(':'); const rh = clamp(parseInt(parts[0], 10) || 0, 0, 23); const rm = clamp(parseInt(parts[1], 10) || 0, 0, 59); const readyMin = rh * 60 + rm; let startMin = ((readyMin - totalMin) % 1440 + 1440) % 1440; const sh = Math.floor(startMin / 60); const sm = startMin % 60; const pad = x => String(x).padStart(2, '0'); const dayShift = readyMin - totalMin < 0 ? ' (am Vortag)' : ''; return rows([['Auftaumethode', escapeHtml(labels[method] || 'Kühlschrank')],['Gewicht', num.format(kg) + ' kg'],['Geschätzte Auftauzeit', (h > 0 ? h + ' h ' : '') + m + ' min'],['Spätester Start', pad(sh) + ':' + pad(sm) + ' Uhr' + dayShift],['Fertig auftauen bis', pad(rh) + ':' + pad(rm) + ' Uhr'],['Kühlschrank-Vergleich', num.format(Math.round(kg * 12 * 10) / 10) + ' h'],['Wasserbad-Vergleich', num.format(Math.round(kg * 1.5 * 10) / 10) + ' h'],['Tipp Wasser wechseln', 'alle 30 min']]);
            }
            case 'sous-vide-rechner': {
                const type = value('type') || 'beef'; const done = value('done') || 'medium'; const mm = clamp(n('thickness'), 5, 100); const temps = { beef: { rare: 52, medium: 56, well: 62 }, pork: { rare: 56, medium: 60, well: 68 }, chicken: { rare: 62, medium: 65, well: 72 }, fish: { rare: 44, medium: 50, well: 55 } }; const typeLabel = { beef: 'Rind / Steak', pork: 'Schwein', chicken: 'Geflügel', fish: 'Fisch' }; const doneLabel = { rare: 'Rare', medium: 'Medium', well: 'Durch' }; const baseTemp = (temps[type] || temps.beef)[done] || 56; const cm = mm / 10; const minHours = Math.max(0.5, 0.5 * cm * cm); const minMin = Math.round(minHours * 60); const h = Math.floor(minMin / 60); const m = minMin % 60; const maxHours = type === 'chicken' || type === 'fish' ? minHours + 2 : minHours + 3; return rows([['Fleischart', escapeHtml(typeLabel[type])],['Garstufe', escapeHtml(doneLabel[done])],['Gartemperatur', num.format(baseTemp) + ' Grad'],['Dicke', num.format(mm) + ' mm'],['Mindest-Garzeit', (h > 0 ? h + ' h ' : '') + (m > 0 ? m + ' min' : (h > 0 ? '' : '0 min'))],['Maximale Garzeit (Richtwert)', num.format(Math.round(maxHours * 10) / 10) + ' h'],['Faustregel Kerntemperatur', '= Wasserbad-Temperatur'],['Danach scharf anbraten', '30 bis 60 Sekunden je Seite']]);
            }
            case 'zuckersirup-rechner': {
                const volume = Math.max(10, n('volume')); const ratio = Math.max(0.1, n('ratio')); const sugarFinal = Math.round(volume * (ratio / (ratio + 1)) * 1.2); const waterMl = Math.round(volume * (1 / (ratio + 1))); const ratioLabel = ratio === 0.5 ? '1:2 (leicht)' : (ratio === 2 ? '2:1 (Rich)' : '1:1 (Standard)'); const brix = Math.round(ratio / (ratio + 1) * 100); const perDrink = Math.round(volume / 15); return rows([['Verhältnis', ratioLabel],['Zielmenge Sirup', num.format(volume) + ' ml'],['Zucker', num.format(sugarFinal) + ' g'],['Wasser', num.format(waterMl) + ' ml'],['Ungefährer Zuckergehalt', num.format(brix) + ' %'],['Reicht für (15 ml/Drink)', num.format(perDrink) + ' Drinks'],['Haltbarkeit gekühlt', ratio >= 2 ? 'ca. 4 Wochen' : 'ca. 2 bis 3 Wochen'],['Tipp', 'Zucker in heißem Wasser klar auflösen']]);
            }
            case 'schlagsahne-rechner': {
                const portions = Math.max(1, Math.round(n('portions'))); const perPortion = Math.max(10, n('perPortion')); const sugarPer200 = Math.max(0, n('sugar')); const whippedTotal = portions * perPortion; const liquid = whippedTotal / 2; const liquidRounded = Math.round(liquid / 10) * 10; const sugarTotal = Math.round(liquid / 200 * sugarPer200); const stabilizer = Math.ceil(liquid / 200); const becher = Math.round(liquid / 200 * 10) / 10; return rows([['Portionen', num.format(portions)],['Geschlagene Sahne gesamt', num.format(whippedTotal) + ' ml'],['Benötigte flüssige Sahne', num.format(liquidRounded) + ' ml'],['Sahnebecher (200 ml)', num.format(becher)],['Zucker gesamt', num.format(sugarTotal) + ' g'],['Päckchen Sahnesteif', num.format(stabilizer)],['Pro Portion (geschlagen)', num.format(perPortion) + ' ml'],['Tipp Volumen', 'Sahne verdoppelt sich beim Schlagen'],['Tipp Kälte', 'Sahne und Schüssel gut gekühlt aufschlagen']]);
            }
            case 'kraeuter-frisch-getrocknet-rechner': {
                const amount = Math.max(0, n('amount')); const unit = value('unit') || 'tl'; const dir = value('direction') || 'fresh2dry'; const ratio = 3; const result = dir === 'fresh2dry' ? amount / ratio : amount * ratio; const round = x => Math.round(x * 100) / 100; const unitLabel = { tl: 'TL', el: 'EL', g: 'g' }[unit] || ''; const fromLabel = dir === 'fresh2dry' ? 'frisch' : 'getrocknet'; const toLabel = dir === 'fresh2dry' ? 'getrocknet' : 'frisch'; const tlInEl = unit === 'tl' ? result / 3 : (unit === 'el' ? result : 0); const out = [['Eingabe', num.format(amount) + ' ' + unitLabel + ' ' + fromLabel],['Faustregel', '1 Teil getrocknet = 3 Teile frisch'],['Ergebnis', num.format(round(result)) + ' ' + unitLabel + ' ' + toLabel]]; if (unit === 'tl') { out.push(['Umgerechnet in EL', num.format(round(result / 3)) + ' EL']); } if (unit === 'el') { out.push(['Umgerechnet in TL', num.format(round(result * 3)) + ' TL']); } out.push(['Beispiel Petersilie', dir === 'fresh2dry' ? '3 TL frisch = 1 TL getrocknet' : '1 TL getrocknet = 3 TL frisch']); out.push(['Hinweis', 'Aroma variiert je nach Kraut und Frische']); return rows(out);
            }
            case 'arbeitstag-kosten-rechner': {
                const brutto = Math.max(0, n('jahresbrutto')); const nebenkosten = brutto * Math.max(0, n('nebenkosten')) / 100; const sach = Math.max(0, n('sachkosten')); const vollkosten = brutto + nebenkosten + sach; const tage = Math.max(1, n('arbeitstage')); const std = Math.max(0.5, n('stunden')); const tageskosten = vollkosten / tage; const stundenkosten = tageskosten / std; const wochenkosten = tageskosten * 5; const monatskosten = vollkosten / 12; return rows([['Vollkosten pro Jahr', euro.format(vollkosten)],['Davon Lohnnebenkosten', euro.format(nebenkosten)],['Davon Sachkosten', euro.format(sach)],['Kosten pro Arbeitstag', euro.format(tageskosten)],['Kosten pro Stunde', euro.format(stundenkosten)],['Kosten pro Arbeitswoche (5 Tage)', euro.format(wochenkosten)],['Kosten pro Monat', euro.format(monatskosten)],['Aufschlag auf das Bruttogehalt', num.format(brutto > 0 ? (vollkosten - brutto) / brutto * 100 : 0) + ' %'],['Produktive Tage pro Jahr', num.format(tage)],['Kosten eines Krankheitstags', euro.format(tageskosten)]]);
            }
            case 'wiederbeschaffungskosten-mitarbeiter-rechner': {
                const brutto = Math.max(0, n('jahresbrutto')); const tagessatz = brutto / 220; const recruiting = Math.max(0, n('recruiting')); const vakanz = Math.max(0, n('vakanztage')); const vakanzkosten = tagessatz * vakanz; const einMonate = Math.max(0, n('einarbeitung')); const prod = clamp(n('produktivitaet'), 0, 100); const monatsbrutto = brutto / 12; const einarbeitungskosten = monatsbrutto * einMonate * (1 - prod / 100); const gesamt = recruiting + vakanzkosten + einarbeitungskosten; const anteilGehalt = brutto > 0 ? gesamt / brutto * 100 : 0; return rows([['Wiederbeschaffungskosten gesamt', euro.format(gesamt)],['Recruiting-Kosten', euro.format(recruiting)],['Kosten der Vakanz', euro.format(vakanzkosten)],['Einarbeitungs-Produktivitaetsverlust', euro.format(einarbeitungskosten)],['Anteil am Jahresgehalt', num.format(anteilGehalt) + ' %'],['Kosten pro Vakanztag', euro.format(tagessatz)],['Verlorene Produktivitaet (Monate)', num.format(einMonate * (1 - prod / 100))],['Jahresbrutto der Stelle', euro.format(brutto)],['Monatsbrutto', euro.format(monatsbrutto)],['Faustregel-Vergleich (50 % Gehalt)', euro.format(brutto * 0.5)]]);
            }
            case 'angebot-nachlass-marge-rechner': {
                const preis = Math.max(0, n('preis')); const ek = Math.max(0, n('einkauf')); const rabatt = clamp(n('rabatt'), 0, 100); const menge = Math.max(0, n('menge')); const neuerPreis = preis * (1 - rabatt / 100); const margeVor = preis - ek; const margeNach = neuerPreis - ek; const margeProzVor = preis > 0 ? margeVor / preis * 100 : 0; const margeProzNach = neuerPreis > 0 ? margeNach / neuerPreis * 100 : 0; const gewinnVor = margeVor * menge; const gewinnNach = margeNach * menge; const gewinnVerlust = gewinnVor - gewinnNach; const mehrmenge = margeNach > 0 ? gewinnVor / margeNach : 0; const mehrumsatzProz = margeNach > 0 && menge > 0 ? (mehrmenge / menge - 1) * 100 : 0; return rows([['Listenpreis', euro.format(preis)],['Preis nach Rabatt', euro.format(neuerPreis)],['Marge vorher', euro.format(margeVor) + ' (' + num.format(margeProzVor) + ' %)'],['Marge nach Rabatt', euro.format(margeNach) + ' (' + num.format(margeProzNach) + ' %)'],['Gewinn vorher (gesamt)', euro.format(gewinnVor)],['Gewinn nach Rabatt (gesamt)', euro.format(gewinnNach)],['Entgangener Gewinn', euro.format(gewinnVerlust)],['Noetige Menge zum Ausgleich', margeNach > 0 ? num.format(Math.ceil(mehrmenge)) + ' Stueck' : 'Rabatt zu hoch'],['Noetiger Mehrumsatz', margeNach > 0 ? '+' + num.format(mehrumsatzProz) + ' %' : '-'],['Marge-Verlust in Prozentpunkten', num.format(margeProzVor - margeProzNach) + ' pp']]);
            }
            case 'zahlungsziel-liquiditaet-rechner': {
                const umsatz = Math.max(0, n('jahresumsatz')); const ziel = Math.max(0, n('zahlungsziel')); const neu = Math.max(0, n('neuesziel')); const zins = Math.max(0, n('zins')); const tagesumsatz = umsatz / 365; const gebunden = tagesumsatz * ziel; const gebundenNeu = tagesumsatz * neu; const freigesetzt = gebunden - gebundenNeu; const zinskostenAlt = gebunden * zins / 100; const zinskostenNeu = gebundenNeu * zins / 100; const ersparnis = zinskostenAlt - zinskostenNeu; const umschlag = ziel > 0 ? 365 / ziel : 0; return rows([['Durchschnittlicher Tagesumsatz', euro.format(tagesumsatz)],['Gebundenes Kapital (aktuell)', euro.format(gebunden)],['Gebundenes Kapital (neues Ziel)', euro.format(gebundenNeu)],['Freigesetzte Liquiditaet', euro.format(freigesetzt)],['Kapitalkosten aktuell pro Jahr', euro.format(zinskostenAlt)],['Kapitalkosten neues Ziel pro Jahr', euro.format(zinskostenNeu)],['Zins-Ersparnis pro Jahr', euro.format(ersparnis)],['Forderungsumschlag pro Jahr', num.format(umschlag) + ' x'],['Tage weniger Zahlungsziel', num.format(Math.max(0, ziel - neu)) + ' Tage'],['Kapitalbindung pro Tag Ziel', euro.format(tagesumsatz)]]);
            }
            case 'mitarbeiter-vollkosten-rechner': {
                const monatsbrutto = Math.max(0, n('monatsbrutto')); const monate = Math.max(0, n('monate')); const jahresbrutto = monatsbrutto * monate; const nebenkosten = jahresbrutto * Math.max(0, n('nebenkosten')) / 100; const gemein = Math.max(0, n('gemeinkosten')); const vollkosten = jahresbrutto + nebenkosten + gemein; const std = Math.max(1, n('stunden')); const stundensatz = vollkosten / std; const vollkostenMonat = vollkosten / 12; const faktor = jahresbrutto > 0 ? vollkosten / jahresbrutto : 0; const aufschlag = jahresbrutto > 0 ? (vollkosten - jahresbrutto) / jahresbrutto * 100 : 0; return rows([['Jahresbruttogehalt', euro.format(jahresbrutto)],['Arbeitgeber-Nebenkosten', euro.format(nebenkosten)],['Gemeinkosten', euro.format(gemein)],['Vollkosten pro Jahr', euro.format(vollkosten)],['Vollkosten pro Monat', euro.format(vollkostenMonat)],['Vollkosten-Stundensatz', euro.format(stundensatz)],['Vollkosten-Faktor (x Brutto)', num.format(faktor) + ' x'],['Aufschlag auf das Brutto', num.format(aufschlag) + ' %'],['Produktive Stunden pro Jahr', num.format(std)],['Anteil Nebenkosten an Vollkosten', num.format(vollkosten > 0 ? nebenkosten / vollkosten * 100 : 0) + ' %']]);
            }
            case 'ltv-cac-ratio-rechner': {
                const ltv = n('ltv'); const cac = Math.max(0.01, n('cac')); const ratio = ltv / cac; const profit = ltv - n('cac'); const monat = Math.max(0.01, n('monatswert')); const payback = n('cac') / monat; const dbLtv = ltv * n('margin') / 100; const dbRatio = dbLtv / cac; let status = 'gesund'; if (ratio < 1) status = 'Verlust pro Kunde'; else if (ratio < 3) status = 'zu niedrig, optimieren'; else if (ratio > 5) status = 'evtl. Wachstum verschenkt'; return rows([['LTV:CAC-Ratio', num.format(ratio) + ' : 1'], ['Gewinn pro Kunde', euro.format(profit)], ['CAC-Payback', num.format(payback) + ' Monate'], ['LTV (nur Deckungsbeitrag)', euro.format(dbLtv)], ['Ratio auf Deckungsbeitrag', num.format(dbRatio) + ' : 1'], ['Max. tragbarer CAC (Ziel 3:1)', euro.format(ltv / 3)], ['Bewertung', escapeHtml(status)]]);
            }
            case 'break-even-roas-rechner': {
                const margin = clamp(n('margin'), 0.01, 100); const beRoas = 100 / margin; const beAcos = margin; const ziel = clamp(n('margin') - n('zielgewinn'), 0.01, 100); const zielRoas = 100 / ziel; const preis = n('preis'); const maxCpa = preis * margin / 100; const zielCpa = preis * ziel / 100; const db = preis * margin / 100; return rows([['Break-even ROAS', num.format(beRoas) + 'x'], ['Break-even ROAS in Prozent', num.format(beRoas * 100) + ' %'], ['Max. Werbekostenanteil (ACoS)', num.format(beAcos) + ' %'], ['Ziel-ROAS (mit Gewinn)', num.format(zielRoas) + 'x'], ['Max. Kosten pro Bestellung (Break-even)', euro.format(maxCpa)], ['Ziel-Kosten pro Bestellung', euro.format(zielCpa)], ['Deckungsbeitrag pro Bestellung', euro.format(db)]]);
            }
            case 'werbebudget-rechner': {
                const aov = Math.max(0.01, n('aov')); const orders = n('umsatzziel') / aov; const cr = clamp(n('cr'), 0.01, 100); const clicks = orders / (cr / 100); const budget = clicks * n('cpc'); const cpa = orders > 0 ? budget / orders : 0; const roas = budget > 0 ? n('umsatzziel') / budget : 0; return rows([['Benötigte Bestellungen', num.format(orders)], ['Benötigte Klicks', num.format(clicks)], ['Werbebudget gesamt', euro.format(budget)], ['Kosten pro Bestellung (CPA)', euro.format(cpa)], ['Erwarteter ROAS', num.format(roas) + 'x'], ['Budget pro Bestellung', euro.format(n('cpc') / (cr / 100))], ['Umsatzziel', euro.format(n('umsatzziel'))]]);
            }
            case 'email-conversion-ziel-rechner': {
                const open = clamp(n('open'), 0.01, 100) / 100; const ctr = clamp(n('ctr'), 0.01, 100) / 100; const conv = clamp(n('conv'), 0.01, 100) / 100; const funnel = Math.max(1e-9, open * ctr * conv); const empf = n('ziel') / funnel; const opens = empf * open; const clicks = opens * ctr; const sales = n('ziel'); const umsatz = sales * n('aov'); const gesamtConv = funnel * 100; return rows([['Benötigte Empfänger', num.format(empf)], ['Erwartete Öffnungen', num.format(opens)], ['Erwartete Klicks', num.format(clicks)], ['Erwartete Verkäufe', num.format(sales)], ['Gesamt-Conversion (Empfänger zu Kauf)', num.format(gesamtConv) + ' %'], ['Erwarteter Umsatz', euro.format(umsatz)], ['Umsatz pro Empfänger', euro.format(empf > 0 ? umsatz / empf : 0)]]);
            }
            case 'share-of-voice-rechner': {
                const markt = Math.max(0.01, n('markt')); const eigen = clamp(n('eigen'), 0, markt); const sov = eigen / markt * 100; const wettbewerb = markt - eigen; const som = n('marktanteil'); const gap = sov - som; const zielEigen = som / 100 * markt; const diffBudget = zielEigen - eigen; let signal = 'ausgewogen'; if (gap > 2) signal = 'Offensiv: SOV über Marktanteil'; else if (gap < -2) signal = 'Risiko: SOV unter Marktanteil'; return rows([['Share of Voice (SOV)', num.format(sov) + ' %'], ['Anteil Wettbewerber', num.format(100 - sov) + ' %'], ['Wert der Wettbewerber', num.format(wettbewerb)], ['Share of Market (SOM)', num.format(som) + ' %'], ['SOV minus SOM (Lücke)', num.format(gap) + ' Prozentpunkte'], ['Wert für SOV = Marktanteil', num.format(zielEigen)], ['Anpassung nötig', euro.format(diffBudget).replace('€', '') + 'Einheiten'], ['Signal', escapeHtml(signal)]]);
            }
            case 'skalarprodukt-rechner': {
                const ax = n('ax'), ay = n('ay'), bx = n('bx'), by = n('by'); const dot = ax * bx + ay * by; const betragA = Math.sqrt(ax * ax + ay * ay); const betragB = Math.sqrt(bx * bx + by * by); const nenner = betragA * betragB; const cosPhi = nenner > 0 ? clamp(dot / nenner, -1, 1) : 0; const winkel = Math.acos(cosPhi) * 180 / Math.PI; let lage = 'schräg'; if (nenner > 0) { if (Math.abs(dot) < 1e-9) lage = 'senkrecht (90 Grad)'; else if (Math.abs(cosPhi - 1) < 1e-9) lage = 'parallel (gleiche Richtung)'; else if (Math.abs(cosPhi + 1) < 1e-9) lage = 'antiparallel (180 Grad)'; } else { lage = 'Nullvektor enthalten'; } const proj = betragB > 0 ? dot / betragB : 0; return rows([['Skalarprodukt a . b', num.format(dot)],['Betrag von a', num.format(betragA)],['Betrag von b', num.format(betragB)],['Winkel zwischen a und b', num.format(winkel) + ' Grad'],['cos(Winkel)', num.format(cosPhi)],['Lage der Vektoren', escapeHtml(lage)],['Vektor a', '(' + num.format(ax) + ', ' + num.format(ay) + ')'],['Vektor b', '(' + num.format(bx) + ', ' + num.format(by) + ')'],['Projektion von a auf b', num.format(proj)],['Produkt der Beträge', num.format(nenner)]]);
            }
            case 'parabel-scheitelpunkt-rechner': {
                let a = n('a'); const b = n('b'); const c = n('c'); if (a === 0) { return rows([['Hinweis', 'a darf nicht 0 sein - sonst ist es keine Parabel'],['Funktion', escapeHtml(num.format(b) + 'x + ' + num.format(c))],['Koeffizient a', num.format(a)],['Koeffizient b', num.format(b)],['Koeffizient c', num.format(c)]]); } const xs = -b / (2 * a); const ys = a * xs * xs + b * xs + c; const disk = b * b - 4 * a * c; let nullstellen; if (disk > 0) { const w = Math.sqrt(disk); nullstellen = 'x1 = ' + num.format((-b - w) / (2 * a)) + ' , x2 = ' + num.format((-b + w) / (2 * a)); } else if (Math.abs(disk) < 1e-9) { nullstellen = 'x = ' + num.format(xs) + ' (doppelt)'; } else { nullstellen = 'keine reellen Nullstellen'; } const oeffnung = a > 0 ? 'nach oben offen (Minimum)' : 'nach unten offen (Maximum)'; const scheitelform = num.format(a) + ' (x - (' + num.format(xs) + '))^2 + ' + num.format(ys); return rows([['Scheitelpunkt S', '(' + num.format(xs) + ' | ' + num.format(ys) + ')'],['Scheitel x', num.format(xs)],['Scheitel y', num.format(ys)],['Scheitelform', escapeHtml(scheitelform)],['Diskriminante', num.format(disk)],['Nullstellen', escapeHtml(nullstellen)],['Öffnung', escapeHtml(oeffnung)],['Schnittpunkt mit y-Achse', '(0 | ' + num.format(c) + ')'],['Streckfaktor a', num.format(a)]]);
            }
            case 'arithmetische-folge-rechner': {
                const a1 = n('a1'); const d = n('d'); const nGlieder = Math.max(1, Math.round(n('n'))); const an = a1 + (nGlieder - 1) * d; const summe = nGlieder / 2 * (a1 + an); const mittel = summe / nGlieder; const erste = []; const grenze = Math.min(nGlieder, 8); for (let i = 0; i < grenze; i++) { erste.push(num.format(a1 + i * d)); } let folgeText = erste.join(', '); if (nGlieder > 8) folgeText += ', ...'; return rows([['n-tes Glied a' + num.format(nGlieder), num.format(an)],['Summe der ersten ' + num.format(nGlieder) + ' Glieder', num.format(summe)],['Anfangsglied a1', num.format(a1)],['Differenz d', num.format(d)],['Anzahl Glieder n', num.format(nGlieder)],['Mittelwert der Glieder', num.format(mittel)],['2. Glied a2', num.format(a1 + d)],['Letztes Glied', num.format(an)],['Erste Glieder', escapeHtml(folgeText)]]);
            }
            case 'geometrische-folge-rechner': {
                const a1 = n('a1'); const q = n('q'); const nGlieder = clamp(Math.round(n('n')), 1, 1000); const an = a1 * Math.pow(q, nGlieder - 1); let summe; if (Math.abs(q - 1) < 1e-9) { summe = a1 * nGlieder; } else { summe = a1 * (Math.pow(q, nGlieder) - 1) / (q - 1); } const grenzwert = Math.abs(q) < 1 ? a1 / (1 - q) : null; const erste = []; const grenze = Math.min(nGlieder, 8); for (let i = 0; i < grenze; i++) { erste.push(num.format(a1 * Math.pow(q, i))); } let folgeText = erste.join(', '); if (nGlieder > 8) folgeText += ', ...'; return rows([['n-tes Glied a' + num.format(nGlieder), num.format(an)],['Summe der ersten ' + num.format(nGlieder) + ' Glieder', num.format(summe)],['Anfangsglied a1', num.format(a1)],['Faktor q', num.format(q)],['Anzahl Glieder n', num.format(nGlieder)],['2. Glied a2', num.format(a1 * q)],['Letztes Glied', num.format(an)],['Grenzwert unendliche Reihe', grenzwert === null ? 'divergiert (|q| >= 1)' : num.format(grenzwert)],['Erste Glieder', escapeHtml(folgeText)]]);
            }
            case 'polarkoordinaten-rechner': {
                const x = n('x'); const y = n('y'); const r = Math.sqrt(x * x + y * y); let winkelRad = Math.atan2(y, x); let winkelGrad = winkelRad * 180 / Math.PI; let winkel360 = winkelGrad < 0 ? winkelGrad + 360 : winkelGrad; let quadrant; if (Math.abs(x) < 1e-9 && Math.abs(y) < 1e-9) quadrant = 'Ursprung'; else if (Math.abs(x) < 1e-9) quadrant = (y > 0 ? 'positive' : 'negative') + ' y-Achse'; else if (Math.abs(y) < 1e-9) quadrant = (x > 0 ? 'positive' : 'negative') + ' x-Achse'; else if (x > 0 && y > 0) quadrant = 'I. Quadrant'; else if (x < 0 && y > 0) quadrant = 'II. Quadrant'; else if (x < 0 && y < 0) quadrant = 'III. Quadrant'; else quadrant = 'IV. Quadrant'; const rueckX = r * Math.cos(winkelRad); const rueckY = r * Math.sin(winkelRad); return rows([['Radius r', num.format(r)],['Winkel in Grad (0 bis 360)', num.format(winkel360) + ' Grad'],['Winkel in Grad (-180 bis 180)', num.format(winkelGrad) + ' Grad'],['Winkel in Radiant', num.format(winkelRad)],['Quadrant', escapeHtml(quadrant)],['Punkt kartesisch', '(' + num.format(x) + ' | ' + num.format(y) + ')'],['Polarform', 'r = ' + num.format(r) + ' , phi = ' + num.format(winkel360) + ' Grad'],['Probe x = r*cos(phi)', num.format(rueckX)],['Probe y = r*sin(phi)', num.format(rueckY)]]);
            }
            case 'standardabweichung-rechner': {
                const xs = parseNumbers(value('werte')); if (xs.length < 1) return rows([['Hinweis', 'Bitte mindestens eine Zahl eingeben']]); const nn = xs.length; const summe = xs.reduce((a,b)=>a+b,0); const mittel = summe / nn; const quadSumme = xs.reduce((a,b)=>a+Math.pow(b-mittel,2),0); const varP = quadSumme / nn; const varS = nn > 1 ? quadSumme / (nn-1) : 0; const sortiert = xs.slice().sort((a,b)=>a-b); const spannweite = sortiert[nn-1] - sortiert[0]; return rows([['Anzahl Werte', num.format(nn)],['Mittelwert', num.format(mittel)],['Standardabweichung Stichprobe (n-1)', num.format(Math.sqrt(varS))],['Standardabweichung Grundgesamtheit (n)', num.format(Math.sqrt(varP))],['Varianz Stichprobe (n-1)', num.format(varS)],['Varianz Grundgesamtheit (n)', num.format(varP)],['Minimum', num.format(sortiert[0])],['Maximum', num.format(sortiert[nn-1])],['Spannweite', num.format(spannweite)],['Summe', num.format(summe)],['Variationskoeffizient', mittel !== 0 ? num.format(Math.sqrt(varS)/Math.abs(mittel)*100)+' %' : '-']]);
            }
            case 'binomialverteilung-rechner': {
                const nn = clamp(Math.round(n('anzahl')), 0, 1000); const k = clamp(Math.round(n('k')), 0, nn); const p = clamp(n('p'), 0, 100) / 100; const logFac = m => { let s = 0; for (let i = 2; i <= m; i++) s += Math.log(i); return s; }; const binom = x => { if (x < 0 || x > nn) return 0; const logC = logFac(nn) - logFac(x) - logFac(nn - x); const logP = logC + (p > 0 ? x*Math.log(p) : (x === 0 ? 0 : -Infinity)) + ((1-p) > 0 ? (nn-x)*Math.log(1-p) : (nn-x === 0 ? 0 : -Infinity)); return Math.exp(logP); }; const exakt = binom(k); let kumUnten = 0; for (let i = 0; i <= k; i++) kumUnten += binom(i); let kumOben = 0; for (let i = k; i <= nn; i++) kumOben += binom(i); const ew = nn * p; const sigma = Math.sqrt(nn * p * (1-p)); const pct = v => num.format(clamp(v,0,1)*100) + ' %'; return rows([['P(X = '+k+') genau', pct(exakt)],['P(X kleiner gleich '+k+') hoechstens', pct(kumUnten)],['P(X groesser gleich '+k+') mindestens', pct(kumOben)],['P(X kleiner '+k+')', pct(kumUnten - exakt)],['P(X groesser '+k+')', pct(kumOben - exakt)],['Erwartungswert n x p', num.format(ew)],['Standardabweichung', num.format(sigma)],['Versuche n', num.format(nn)],['Treffer k', num.format(k)],['p', num.format(p*100)+' %']]);
            }
            case 'harmonisches-mittel-rechner': {
                const alle = parseNumbers(value('werte')); const xs = alle.filter(v => v > 0); if (xs.length < 1) return rows([['Hinweis', 'Bitte mindestens eine positive Zahl eingeben']]); const nn = xs.length; const summe = xs.reduce((a,b)=>a+b,0); const arith = summe / nn; const kehrSumme = xs.reduce((a,b)=>a + 1/b, 0); const harm = kehrSumme > 0 ? nn / kehrSumme : 0; const logProd = xs.reduce((a,b)=>a + Math.log(b), 0); const geo = Math.exp(logProd / nn); return rows([['Arithmetisches Mittel', num.format(arith)],['Geometrisches Mittel', num.format(geo)],['Harmonisches Mittel', num.format(harm)],['Anzahl Werte', num.format(nn)],['Summe', num.format(summe)],['Kleinster Wert', num.format(Math.min(...xs))],['Groesster Wert', num.format(Math.max(...xs))],['Ausgeschlossene (nicht positiv)', num.format(alle.length - nn)],['Regel', 'harmonisch kleiner gleich geometrisch kleiner gleich arithmetisch']]);
            }
            case 'summen-reihen-rechner': {
                const a1 = n('start'); const d = n('diff'); const q = n('faktor'); const nn = clamp(Math.round(n('glieder')), 1, 100000); const anLetzt = a1 + (nn - 1) * d; const arithSumme = nn / 2 * (a1 + anLetzt); const arithMittel = arithSumme / nn; let geoSumme, geoLetzt; if (q === 1) { geoSumme = a1 * nn; geoLetzt = a1; } else { geoLetzt = a1 * Math.pow(q, nn - 1); geoSumme = a1 * (Math.pow(q, nn) - 1) / (q - 1); } const grenzwert = (Math.abs(q) < 1) ? a1 / (1 - q) : null; const fmt = v => Math.abs(v) >= 1e15 ? v.toExponential(6) : num.format(v); return rows([['Arithmetische Summe', fmt(arithSumme)],['Letztes Glied arithmetisch a_n', fmt(anLetzt)],['Mittelwert arithmetisch', fmt(arithMittel)],['Geometrische Summe', fmt(geoSumme)],['Letztes Glied geometrisch a_n', fmt(geoLetzt)],['Grenzwert unendliche geom. Reihe', grenzwert !== null ? fmt(grenzwert) : 'divergiert (|q| >= 1)'],['Erstes Glied a1', num.format(a1)],['Schrittweite d', num.format(d)],['Faktor q', num.format(q)],['Anzahl Glieder n', num.format(nn)]]);
            }
            case 'kreissegment-flaeche-rechner': {
                const r = Math.max(0, n('radius')); const grad = clamp(n('winkel'), 0, 360); const rad = grad * Math.PI / 180; const sektor = 0.5 * r * r * rad; const dreieck = 0.5 * r * r * Math.sin(rad); const segment = sektor - dreieck; const bogen = r * rad; const sehne = 2 * r * Math.sin(rad / 2); const hoehe = r * (1 - Math.cos(rad / 2)); const vollFlaeche = Math.PI * r * r; const anteil = vollFlaeche > 0 ? segment / vollFlaeche * 100 : 0; return rows([['Segmentflaeche', num.format(segment)],['Sektorflaeche (Tortenstueck)', num.format(sektor)],['Bogenlaenge', num.format(bogen)],['Sehne', num.format(sehne)],['Segmenthoehe', num.format(hoehe)],['Dreiecksflaeche', num.format(dreieck)],['Radius r', num.format(r)],['Winkel', num.format(grad)+' Grad'],['Winkel in Bogenmass', num.format(rad)+' rad'],['Gesamte Kreisflaeche', num.format(vollFlaeche)],['Anteil am Kreis', num.format(anteil)+' %']]);
            }
            case 'dachrinnen-ablauf-rechner': {
                const grund = Math.max(0, n('laenge')) * Math.max(0, n('breite'));
                const rad = clamp(n('neigung'), 0, 89) * Math.PI / 180;
                const dachflaeche = grund / Math.max(0.05, Math.cos(rad));
                const wirksam = grund; // Grundfläche zählt hydraulisch (Projektion)
                const literProSek = wirksam / 10000 * Math.max(0, n('regen'));
                const literProMin = literProSek * 60;
                const rohre = Math.max(1, Math.round(n('rohre')));
                const proRohr = literProSek / rohre;
                let dn = 'DN 70';
                if (proRohr > 0.9) dn = 'DN 80';
                if (proRohr > 1.5) dn = 'DN 100';
                if (proRohr > 4.0) dn = 'DN 125';
                if (proRohr > 7.0) dn = 'DN 150';
                return rows([
                  ['Dachfläche (geneigt)', num.format(dachflaeche) + ' m²'],
                  ['Wirksame Grundfläche', num.format(wirksam) + ' m²'],
                  ['Wasseranfall', num.format(literProSek) + ' l/s'],
                  ['Wasseranfall pro Minute', num.format(literProMin) + ' l/min'],
                  ['Wasseranfall pro Stunde', num.format(literProMin * 60) + ' l/h'],
                  ['Fallrohre', num.format(rohre)],
                  ['Anfall je Fallrohr', num.format(proRohr) + ' l/s'],
                  ['Empfohlene Fallrohr-Nennweite', escapeHtml(dn)],
                  ['Faustregel Rinne', proRohr > 1.5 ? 'halbrund 150 mm' : 'halbrund 125 mm']
                ]);
            }
            case 'gartenteich-volumen-rechner': {
                const l = Math.max(0, n('laenge'));
                const b = Math.max(0, n('breite'));
                const t = Math.max(0, n('tiefe'));
                const formfaktor = 0.75; // unregelmäßige Teichform statt Quader
                const m3 = l * b * t * formfaktor;
                const liter = m3 * 1000;
                const folieL = l + 2 * t + 0.5;
                const folieB = b + 2 * t + 0.5;
                const folieFlaeche = folieL * folieB;
                const umwaelzung = liter / 2; // Inhalt pro 2 Stunden umwälzen
                const art = value('fischart');
                const literProFisch = art === 'koi' ? 1000 : art === 'gold' ? 50 : 200;
                const fische = Math.floor(liter / Math.max(1, literProFisch));
                return rows([
                  ['Volumen', num.format(m3) + ' m³'],
                  ['Volumen in Litern', num.format(liter) + ' l'],
                  ['Folienlänge', num.format(folieL) + ' m'],
                  ['Folienbreite', num.format(folieB) + ' m'],
                  ['Teichfolie gesamt', num.format(folieFlaeche) + ' m²'],
                  ['Pumpe Mindestleistung', num.format(umwaelzung) + ' l/h'],
                  ['Empfohlener Fischbesatz', num.format(fische) + ' Fische'],
                  ['Wasser pro Fisch', num.format(literProFisch) + ' l'],
                  ['Verdunstung Sommer grob', num.format(l * b * 1000 * 0.005) + ' l/Tag']
                ]);
            }
            case 'hochbeet-fuellung-rechner': {
                const l = Math.max(0, n('laenge')) / 100;
                const b = Math.max(0, n('breite')) / 100;
                const h = Math.max(0, n('hoehe')) / 100;
                const m3 = l * b * h;
                const liter = m3 * 1000;
                const grobholz = liter * 0.30;
                const gruenschnitt = liter * 0.20;
                const laub = liter * 0.15;
                const kompost = liter * 0.20;
                const erde = liter * 0.15;
                const sack = Math.max(1, n('saumel'));
                const saecke = Math.ceil(erde / sack);
                return rows([
                  ['Gesamtvolumen', num.format(m3) + ' m³'],
                  ['Gesamtvolumen in Litern', num.format(liter) + ' l'],
                  ['1. Schicht Grobholz/Äste (30%)', num.format(grobholz) + ' l'],
                  ['2. Schicht Grünschnitt (20%)', num.format(gruenschnitt) + ' l'],
                  ['3. Schicht Laub/Stroh (15%)', num.format(laub) + ' l'],
                  ['4. Schicht Kompost (20%)', num.format(kompost) + ' l'],
                  ['5. Schicht Pflanzerde (15%)', num.format(erde) + ' l'],
                  ['Säcke Pflanzerde', num.format(saecke) + ' Stück'],
                  ['Setzungsreserve nachfüllen', num.format(liter * 0.15) + ' l']
                ]);
            }
            case 'heizlast-rechner': {
                const flaeche = Math.max(0, n('flaeche'));
                const hoehe = Math.max(1, n('hoehe'));
                const volumen = flaeche * hoehe;
                const dt = Math.max(1, n('tinnen') - n('taussen'));
                const d = value('daemmung');
                const wPerM2 = d === 'neubau' ? 40 : d === 'saniert' ? 60 : d === 'mittel' ? 90 : 130;
                const dtFaktor = dt / 33; // Referenz: 21 innen, -12 außen
                const watt = flaeche * wPerM2 * dtFaktor;
                const kw = watt / 1000;
                const jahresStunden = 1800; // grobe Volllaststunden
                return rows([
                  ['Beheizte Fläche', num.format(flaeche) + ' m²'],
                  ['Beheiztes Volumen', num.format(volumen) + ' m³'],
                  ['Temperaturdifferenz', num.format(dt) + ' K'],
                  ['Spezifische Heizlast', num.format(wPerM2 * dtFaktor) + ' W/m²'],
                  ['Geschätzte Heizlast', num.format(watt) + ' W'],
                  ['Geschätzte Heizlast', num.format(kw) + ' kW'],
                  ['Empfohlene Geräteleistung +15%', num.format(kw * 1.15) + ' kW'],
                  ['Grober Jahreswärmebedarf', num.format(kw * jahresStunden) + ' kWh'],
                  ['Wärmebedarf je m²/Jahr', num.format(flaeche ? kw * jahresStunden / flaeche : 0) + ' kWh']
                ]);
            }
            case 'rollrasen-rechner': {
                const flaeche = Math.max(0, n('laenge')) * Math.max(0, n('breite'));
                const zuschlag = 1 + clamp(n('verschnitt'), 0, 100) / 100;
                const bedarf = flaeche * zuschlag;
                const proRolle = Math.max(0.1, n('rolle'));
                const rollen = Math.ceil(bedarf / proRolle);
                const gewicht = bedarf * 15; // ca. 15 kg pro m² frisch
                const preis = Math.max(0, n('preis'));
                const materialkosten = bedarf * preis;
                return rows([
                  ['Rasenfläche', num.format(flaeche) + ' m²'],
                  ['Mit Verschnitt', num.format(bedarf) + ' m²'],
                  ['Fläche pro Rolle', num.format(proRolle) + ' m²'],
                  ['Benötigte Rollen', num.format(rollen) + ' Stück'],
                  ['Liefergewicht grob', num.format(gewicht) + ' kg'],
                  ['Liefergewicht in Tonnen', num.format(gewicht / 1000) + ' t'],
                  ['Materialkosten', euro.format(materialkosten)],
                  ['Kosten pro m²', euro.format(preis)],
                  ['Saatgut-Alternative grob (35 g/m²)', num.format(flaeche * 35 / 1000) + ' kg']
                ]);
            }
            case 'anleihe-kurs-rechner': {
                const nom = Math.max(0, n('nominal')); const c = n('kupon')/100*nom; const r = n('markt')/100; const t = Math.max(1, Math.round(n('jahre'))); let kurs; if (Math.abs(r) < 1e-9) { kurs = c*t + nom; } else { const disc = Math.pow(1+r, t); const barwertKupons = c * (1 - 1/disc) / r; const barwertNominal = nom / disc; kurs = barwertKupons + barwertNominal; } const kursProzent = nom ? kurs/nom*100 : 0; const agioDisagio = kurs - nom; const label = agioDisagio > 0.005 ? 'ueber pari (Agio)' : (agioDisagio < -0.005 ? 'unter pari (Disagio)' : 'pari'); return rows([['Fairer Kurs', euro.format(kurs)], ['Kurs in Prozent', num.format(kursProzent)+' %'], ['Bewertung', escapeHtml(label)], ['Auf- / Abschlag zum Nominal', euro.format(agioDisagio)], ['Kuponzahlung pro Jahr', euro.format(c)], ['Summe Kupons gesamt', euro.format(c*t)], ['Rueckzahlung am Ende', euro.format(nom)], ['Restlaufzeit', num.format(t)+' Jahre']]);
            }
            case 'realzins-rechner': {
                const i = n('nominal')/100; const inf = n('inflation')/100; const kapital = Math.max(0, n('kapital')); const realExakt = (1 + i) / (1 + inf) - 1; const realNaeherung = i - inf; const nominalEnd = kapital * (1 + i); const kaufkraftEnd = nominalEnd / (1 + inf); const realGewinn = kaufkraftEnd - kapital; const status = realExakt > 0.00005 ? 'positiv - Kaufkraft steigt' : (realExakt < -0.00005 ? 'negativ - Kaufkraft sinkt' : 'neutral'); return rows([['Realzins (exakt, Fisher)', num.format(realExakt*100)+' %'], ['Realzins (Naeherung)', num.format(realNaeherung*100)+' %'], ['Bewertung', escapeHtml(status)], ['Kapital heute', euro.format(kapital)], ['Nominalwert nach 1 Jahr', euro.format(nominalEnd)], ['Kaufkraft nach 1 Jahr', euro.format(kaufkraftEnd)], ['Realer Gewinn / Verlust', euro.format(realGewinn)], ['Nominalzins', num.format(n('nominal'))+' %'], ['Inflationsrate', num.format(n('inflation'))+' %']]);
            }
            case 'kgv-rechner': {
                const kurs = Math.max(0, n('kurs')); const eps = n('eps'); const wunsch = Math.max(0, n('wunschkgv')); const kgv = eps > 0 ? kurs / eps : 0; const gewinnrendite = kurs > 0 ? eps / kurs * 100 : 0; const fairerKurs = wunsch * eps; const amortisation = eps > 0 ? kurs / eps : 0; let einordnung; if (eps <= 0) { einordnung = 'kein Gewinn - KGV nicht aussagekraeftig'; } else if (kgv < 12) { einordnung = 'eher guenstig bewertet'; } else if (kgv <= 20) { einordnung = 'moderat bewertet'; } else { einordnung = 'eher teuer bewertet'; } return rows([['KGV', eps > 0 ? num.format(kgv) : '-'], ['Einordnung', escapeHtml(einordnung)], ['Gewinnrendite', num.format(gewinnrendite)+' %'], ['Fairer Kurs bei Wunsch-KGV', euro.format(fairerKurs)], ['Abweichung zum Wunschkurs', eps > 0 ? euro.format(kurs - fairerKurs) : '-'], ['Amortisation (Jahre)', eps > 0 ? num.format(amortisation)+' Jahre' : '-'], ['Aktienkurs', euro.format(kurs)], ['Gewinn je Aktie', euro.format(eps)]]);
            }
            case 'ter-kosten-rechner': {
                const kapital = Math.max(0, n('kapital')); const r = n('rendite')/100; const ter = Math.max(0, n('ter'))/100; const t = Math.max(1, Math.round(n('jahre'))); const ohne = kapital * Math.pow(1 + r, t); const netto = Math.max(0, r - ter); const mit = kapital * Math.pow(1 + netto, t); const kosten = ohne - mit; const kostenAnteil = ohne - kapital > 0 ? kosten / (ohne - kapital) * 100 : 0; const terErstesJahr = kapital * ter; return rows([['Endkapital ohne Kosten', euro.format(ohne)], ['Endkapital mit TER', euro.format(mit)], ['Verlust durch Gebuehren', euro.format(kosten)], ['Anteil am Bruttogewinn', num.format(kostenAnteil)+' %'], ['Effektive Rendite nach Kosten', num.format(netto*100)+' %'], ['TER im ersten Jahr', euro.format(terErstesJahr)], ['Eingesetztes Kapital', euro.format(kapital)], ['Anlagedauer', num.format(t)+' Jahre']]);
            }
            case 'barwert-rechner': {
                const fv = Math.max(0, n('zukunft')); const r = n('zins')/100; const t = Math.max(0, Math.round(n('jahre'))); const faktor = Math.pow(1 + r, t); const barwert = faktor !== 0 ? fv / faktor : fv; const diff = fv - barwert; const proJahr = t > 0 ? diff / t : 0; const wertverlustProzent = fv > 0 ? diff / fv * 100 : 0; return rows([['Barwert heute', euro.format(barwert)], ['Zukuenftiger Betrag', euro.format(fv)], ['Wertdifferenz (Abzinsung)', euro.format(diff)], ['Wertverlust gegenueber Zukunft', num.format(wertverlustProzent)+' %'], ['Durchschnitt pro Jahr', euro.format(proJahr)], ['Abzinsungsfaktor', num.format(faktor)], ['Diskontsatz', num.format(n('zins'))+' %'], ['Laufzeit', num.format(t)+' Jahre']]);
            }
            case 'co2-flug-rechner': {
                const km = clamp(n('km'), 0, 20000); const faktor = Math.max(0.5, n('klasse')); const strecken = Math.max(1, n('richtung')); const personen = Math.max(1, n('personen')); const distGesamt = km * strecken; const proPerson = distGesamt * 0.18 * faktor; const total = proPerson * personen; const baeume = total / 25; return rows([['Gesamtdistanz', num.format(distGesamt) + ' km'], ['CO2 pro Person', num.format(proPerson) + ' kg'], ['CO2 gesamt', num.format(total) + ' kg'], ['CO2 gesamt (Tonnen)', num.format(total / 1000) + ' t'], ['Baeume zum Ausgleich (1 Jahr grob)', num.format(baeume)]]);
            }
            case 'co2-auto-jahr-rechner': {
                const km = clamp(n('km'), 0, 200000); const verbrauch = clamp(n('verbrauch'), 0, 50); const faktor = Math.max(0.1, n('kraftstoff')); const liter = km / 100 * verbrauch; const co2 = liter * faktor; const proKm = km > 0 ? co2 / km * 1000 : 0; const baeume = co2 / 25; return rows([['Kraftstoff pro Jahr', num.format(liter) + ' l'], ['CO2 pro Jahr', num.format(co2) + ' kg'], ['CO2 pro Jahr (Tonnen)', num.format(co2 / 1000) + ' t'], ['CO2 pro km', num.format(proKm) + ' g/km'], ['CO2 pro Monat', num.format(co2 / 12) + ' kg'], ['Baeume zum Ausgleich (1 Jahr grob)', num.format(baeume)]]);
            }
            case 'wasser-fussabdruck-lebensmittel-rechner': {
                const proKg = Math.max(0, n('lebensmittel')); const menge = clamp(n('menge'), 0, 1000); const proWoche = clamp(n('haeufigkeit'), 0, 50); const proPortion = proKg * menge; const proJahr = proPortion * proWoche * 52; const badewannen = proJahr / 150; return rows([['Wasser pro Portion', num.format(proPortion) + ' l'], ['Wasser pro Woche', num.format(proPortion * proWoche) + ' l'], ['Wasser pro Jahr', num.format(proJahr) + ' l'], ['Wasser pro Jahr (m3)', num.format(proJahr / 1000) + ' m3'], ['Entspricht Badewannen/Jahr (150 l)', num.format(badewannen)]]);
            }
            case 'baum-co2-kompensation-rechner': {
                const co2 = clamp(n('co2'), 0, 1000000); const proBaum = Math.max(0.1, n('proBaum')); const jahre = clamp(n('jahre'), 1, 100); const baeumeJahr = co2 / proBaum; const co2Gesamt = co2 * jahre; const baeumeZeitraum = co2Gesamt / (proBaum * jahre); const flaeche = baeumeJahr * 25; return rows([['Baeume fuer 1 Jahr CO2', num.format(Math.ceil(baeumeJahr))], ['CO2 ueber Zeitraum', num.format(co2Gesamt) + ' kg'], ['Baeume fuer gesamten Zeitraum', num.format(Math.ceil(baeumeZeitraum))], ['CO2 pro Baum im Zeitraum', num.format(proBaum * jahre) + ' kg'], ['Ungefaehre Pflanzflaeche', num.format(flaeche) + ' m2']]);
            }
            case 'fleisch-co2-rechner': {
                const proKg = Math.max(0, n('sorte')); const gramm = clamp(n('menge'), 0, 100000); const personen = Math.max(1, n('personen')); const kgWoche = gramm / 1000; const co2Woche = kgWoche * proKg * personen; const co2Jahr = co2Woche * 52; const kmAuto = co2Jahr / 0.17; return rows([['Fleisch pro Woche (Haushalt)', num.format(kgWoche * personen) + ' kg'], ['CO2 pro Woche', num.format(co2Woche) + ' kg'], ['CO2 pro Jahr', num.format(co2Jahr) + ' kg'], ['CO2 pro Jahr (Tonnen)', num.format(co2Jahr / 1000) + ' t'], ['Entspricht Autokilometern/Jahr', num.format(kmAuto) + ' km'], ['CO2 pro Person/Jahr', num.format(co2Jahr / personen) + ' kg']]);
            }
            case 'schlafphasen-rechner': {
                const t = value('bedtime') || '23:00'; const parts = String(t).split(':'); const startMin = (Number(parts[0])||0)*60 + (Number(parts[1])||0) + n('fallasleep'); const cyc = Math.max(60, n('cyclemin')); const fmt = m => { m = ((Math.round(m) % 1440) + 1440) % 1440; const h = Math.floor(m/60); const mm = m % 60; return String(h).padStart(2,'0')+':'+String(mm).padStart(2,'0'); }; const r = []; r.push(['Eingeschlafen ca.', escapeHtml(fmt(startMin))+' Uhr']); for (let c = 6; c >= 4; c--) { const wake = startMin + c*cyc; r.push([c+' Zyklen ('+num.format(c*cyc/60)+' h Schlaf)', escapeHtml(fmt(wake))+' Uhr']); } r.push(['Empfohlen', '5 Zyklen = '+escapeHtml(fmt(startMin + 5*cyc))+' Uhr']); r.push(['Reine Schlafzeit bei 5 Zyklen', num.format(5*cyc/60)+' Stunden']); r.push(['Einschlafdauer eingerechnet', num.format(n('fallasleep'))+' Minuten']); return rows(r);
            }
            case 'kalorien-treppensteigen-rechner': {
                const steps = Math.max(0, n('steps')); const heightM = n('stepheight') / 100; const climbM = steps * heightM; const massKg = n('weight'); const eff = Math.max(5, n('efficiency')) / 100; const workJ = massKg * 9.81 * climbM; const kcalUp = (workJ / eff) / 4184; const kcalTotal = kcalUp * 1.33; const minutes = steps / 80; return rows([['Kalorienverbrauch gesamt', num.format(kcalTotal) + ' kcal'], ['Davon beim Hochsteigen', num.format(kcalUp) + ' kcal'], ['Ueberwundene Hoehenmeter', num.format(climbM) + ' m'], ['Anzahl Stufen', num.format(steps)], ['Geschaetzte Dauer', num.format(minutes) + ' Minuten'], ['Verbrauch pro 100 Stufen', num.format(steps ? kcalTotal / steps * 100 : 0) + ' kcal'], ['Etagen (ca. 15 Stufen)', num.format(steps / 15)], ['Hinweis', 'Schaetzwert, kein medizinischer Rat']]);
            }
            case 'wasser-pro-stunde-rechner': {
                const dailyMl = Math.max(0, n('daily')) * 1000; const hours = Math.max(1, n('wakehours')); const glass = Math.max(50, n('glass')); const perHour = dailyMl / hours; const glassesTotal = dailyMl / glass; const intervalMin = glassesTotal > 0 ? (hours * 60) / glassesTotal : 0; return rows([['Wasser pro Stunde', num.format(perHour) + ' ml'], ['Glaeser pro Tag', num.format(glassesTotal) + ' (' + num.format(glass) + ' ml)'], ['Trinkintervall', 'alle ' + num.format(intervalMin) + ' Minuten ein Glas'], ['Glaeser pro Stunde', num.format(perHour / glass)], ['Menge alle 2 Stunden', num.format(perHour * 2) + ' ml'], ['Tagesbedarf gesamt', num.format(dailyMl / 1000) + ' Liter'], ['Wachstunden', num.format(hours) + ' h'], ['Hinweis', 'Richtwert, kein medizinischer Rat']]);
            }
            case 'pulsregeneration-rechner': {
                const peak = n('peak'); const after = n('after1'); const rest = n('resting'); const hrr = peak - after; let bewertung; if (hrr >= 50) bewertung = 'ausgezeichnet'; else if (hrr >= 25) bewertung = 'gut'; else if (hrr >= 13) bewertung = 'durchschnittlich'; else bewertung = 'niedrig, ggf. aerztlich abklaeren'; const span = Math.max(1, peak - rest); const recoverPct = clamp(hrr / span * 100, 0, 100); return rows([['Herzfrequenz-Erholung (1 Min)', num.format(hrr) + ' bpm'], ['Einordnung', escapeHtml(bewertung)], ['Pulsabfall in Prozent', num.format(recoverPct) + ' %'], ['Belastungspuls', num.format(peak) + ' bpm'], ['Puls nach 1 Minute', num.format(after) + ' bpm'], ['Ruhepuls', num.format(rest) + ' bpm'], ['Abstand zum Ruhepuls', num.format(Math.max(0, after - rest)) + ' bpm'], ['Hinweis', 'Schaetzung, kein medizinischer Rat']]);
            }
            case 'schritte-kalorien-rechner': {
                const steps = Math.max(0, n('steps')); const strideM = n('stride') / 100; const distanceM = steps * strideM; const distanceKm = distanceM / 1000; const met = Math.max(1, n('pace')); const weight = n('weight'); const speedKmh = met < 4 ? 4 : (met < 6 ? 5 : 6); const hours = speedKmh > 0 ? distanceKm / speedKmh : 0; const kcal = met * weight * hours; return rows([['Kalorienverbrauch', num.format(kcal) + ' kcal'], ['Zurueckgelegte Strecke', num.format(distanceKm) + ' km'], ['Schritte', num.format(steps)], ['Geschaetzte Dauer', num.format(hours * 60) + ' Minuten'], ['Verbrauch pro 1000 Schritte', num.format(steps ? kcal / steps * 1000 : 0) + ' kcal'], ['Verbrauch pro km', num.format(distanceKm ? kcal / distanceKm : 0) + ' kcal'], ['Strecke pro Schritt', num.format(strideM) + ' m'], ['Hinweis', 'Schaetzwert, kein medizinischer Rat']]);
            }
            case 'eisenbedarf-rechner': {
                const g = value('geschlecht'); const alter = clamp(n('alter'), 0, 120); let basis; let label; if (g === 'mann') { basis = alter < 19 ? 12 : 10; label = 'Mann'; } else if (g === 'frau') { basis = alter < 19 ? 15 : 15; label = 'Frau (menstruierend)'; } else if (g === 'frau_nach') { basis = 10; label = 'Frau nach Menopause'; } else if (g === 'schwanger') { basis = 30; label = 'Schwangerschaft'; } else { basis = 20; label = 'Stillzeit'; } const sport = clamp(n('sport'), 0, 40); const sportPlus = sport >= 5 ? basis * 0.3 : (sport >= 2 ? basis * 0.15 : 0); const bedarf = basis + sportPlus; const zufuhr = clamp(n('zufuhr'), 0, 1000); const diff = zufuhr - bedarf; const deckung = bedarf > 0 ? zufuhr / bedarf * 100 : 0; const status = deckung >= 100 ? 'gedeckt' : (deckung >= 80 ? 'knapp' : 'unter Bedarf'); return rows([['Situation', escapeHtml(label)], ['Basisbedarf', num.format(basis) + ' mg/Tag'], ['Sport-Zuschlag', '+ ' + num.format(sportPlus) + ' mg/Tag'], ['Empfohlener Tagesbedarf', num.format(bedarf) + ' mg/Tag'], ['Deine Zufuhr', num.format(zufuhr) + ' mg/Tag'], ['Deckung', num.format(deckung) + ' %'], ['Bilanz', (diff >= 0 ? '+ ' : '- ') + num.format(Math.abs(diff)) + ' mg/Tag'], ['Status', status]]);
            }
            case 'bmr-katch-mcardle-rechner': {
                const gewicht = clamp(n('gewicht'), 20, 400); const kfa = clamp(n('kfa'), 1, 70); const ffm = gewicht * (1 - kfa / 100); const bmr = 370 + 21.6 * ffm; const faktor = n('aktivitaet') || 1.2; const gesamt = bmr * faktor; const fettmasse = gewicht - ffm; const defizit = gesamt - 500; const aufbau = gesamt + 300; return rows([['Fettfreie Masse', num.format(ffm) + ' kg'], ['Fettmasse', num.format(fettmasse) + ' kg'], ['Grundumsatz (BMR)', num.format(bmr) + ' kcal/Tag'], ['Aktivitätsfaktor', num.format(faktor)], ['Gesamtumsatz (TDEE)', num.format(gesamt) + ' kcal/Tag'], ['Abnehmen (-500 kcal)', num.format(defizit) + ' kcal/Tag'], ['Muskelaufbau (+300 kcal)', num.format(aufbau) + ' kcal/Tag'], ['BMR pro kg Gewicht', num.format(gewicht > 0 ? bmr / gewicht : 0) + ' kcal']]);
            }
            case 'glykaemische-last-rechner': {
                const gi = clamp(n('gi'), 0, 150); const kh100 = clamp(n('kh100'), 0, 100); const portion = clamp(n('portion'), 0, 5000); const khPortion = kh100 * portion / 100; const gl = gi * khPortion / 100; const gl100 = gi * kh100 / 100; let stufe; if (gl <= 10) stufe = 'niedrig'; else if (gl <= 19) stufe = 'mittel'; else stufe = 'hoch'; let giStufe; if (gi <= 55) giStufe = 'niedrig'; else if (gi <= 69) giStufe = 'mittel'; else giStufe = 'hoch'; return rows([['Kohlenhydrate je Portion', num.format(khPortion) + ' g'], ['Glykämischer Index', num.format(gi) + ' (' + giStufe + ')'], ['Glykämische Last (Portion)', num.format(gl)], ['Einordnung', stufe], ['GL pro 100 g', num.format(gl100)], ['Portionen für GL 20', num.format(gl > 0 ? 20 / gl : 0)]]);
            }
            case 'omega3-bedarf-rechner': {
                const ziel = value('ziel'); let bedarf; let label; if (ziel === 'herz') { bedarf = 1000; label = 'Herz-Kreislauf'; } else if (ziel === 'schwanger') { bedarf = 450; label = 'Schwangerschaft/Stillzeit'; } else if (ziel === 'sport') { bedarf = 2000; label = 'Leistungssport'; } else { bedarf = 250; label = 'Allgemeine Gesundheit'; } const fisch = clamp(n('fisch'), 0, 21); const epaPortion = clamp(n('epaProPortion'), 0, 20000); const ausFisch = fisch * epaPortion / 7; const luecke = Math.max(0, bedarf - ausFisch); const kapsel = Math.max(1, n('kapsel')); const kapseln = luecke / kapsel; const deckung = bedarf > 0 ? ausFisch / bedarf * 100 : 0; return rows([['Ziel', escapeHtml(label)], ['Empfohlener Tagesbedarf', num.format(bedarf) + ' mg EPA/DHA'], ['Zufuhr aus Fisch (pro Tag)', num.format(ausFisch) + ' mg'], ['Deckung durch Fisch', num.format(deckung) + ' %'], ['Verbleibende Lücke', num.format(luecke) + ' mg/Tag'], ['Kapseln/Dosen zum Ausgleich', num.format(kapseln) + ' pro Tag'], ['Bedarf pro Woche', num.format(bedarf * 7) + ' mg']]);
            }
            case 'vitamin-d-dosis-rechner': {
                const gewicht = clamp(n('gewicht'), 3, 300); const basis = clamp(n('basisIE'), 10, 100); const sonne = value('sonne'); let faktor; let sLabel; if (sonne === 'viel') { faktor = 0.4; sLabel = 'viel Sonne'; } else if (sonne === 'mittel') { faktor = 0.7; sLabel = 'mäßige Sonne'; } else if (sonne === 'wenig') { faktor = 1; sLabel = 'wenig Sonne'; } else { faktor = 1.2; sLabel = 'kaum Sonne'; } const taeglich = gewicht * basis * faktor; const woche = taeglich * 7; const kapsel = Math.max(100, n('kapselIE')); const einheitenProTag = taeglich / kapsel; return rows([['Sonnensituation', escapeHtml(sLabel)], ['Geschätzte Tagesdosis', num.format(taeglich) + ' I.E./Tag'], ['Pro Woche', num.format(woche) + ' I.E.'], ['Tropfen/Kapseln pro Tag', num.format(einheitenProTag)], ['Einmal-Wochendosis', num.format(woche) + ' I.E.'], ['Hinweis', taeglich > 4000 ? 'über üblichem Tageslimit – ärztlich klären' : 'im üblichen Bereich']]);
            }
            case 'wettkampf-zielzeit-rechner': {
                const d = Math.max(0.1, n('dist')); const t = n('std')*3600 + n('min')*60 + n('sek'); const tBase = Math.max(1, t); const k = clamp(n('faktor'), 1.0, 1.2); const pred = ziel => tBase * Math.pow(ziel / d, k); const mmss = s => { const h = Math.floor(s/3600); const m = Math.floor(s%3600/60); const sec = Math.round(s%60); return (h>0 ? h+':' : '') + String(m).padStart(h>0?2:1,'0') + ':' + String(sec).padStart(2,'0'); }; const paceOf = (s, km) => { const p = s / km; return Math.floor(p/60) + ':' + String(Math.round(p%60)).padStart(2,'0') + ' min/km'; }; const t5 = pred(5), t10 = pred(10), tHM = pred(21.0975), tM = pred(42.195); return rows([ ['Eingabe', num.format(d) + ' km in ' + mmss(tBase)], ['Aktuelle Pace', paceOf(tBase, d)], ['Prognose 5 km', mmss(t5) + ' (' + paceOf(t5, 5) + ')'], ['Prognose 10 km', mmss(t10) + ' (' + paceOf(t10, 10) + ')'], ['Prognose Halbmarathon', mmss(tHM) + ' (' + paceOf(tHM, 21.0975) + ')'], ['Prognose Marathon', mmss(tM) + ' (' + paceOf(tM, 42.195) + ')'], ['Verwendeter Faktor', num.format(k)] ]);
            }
            case 'laktatschwellen-pace-rechner': {
                const d = Math.max(0.1, n('dist')); const t = Math.max(1, n('min')*60 + n('sek')); const racePace = t / d; let adj; if (d <= 5) adj = 1.06; else if (d <= 10) adj = 1.03; else if (d <= 21.1) adj = 0.99; else adj = 0.97; const thr = racePace * adj; const fmt = s => Math.floor(s/60) + ':' + String(Math.round(s%60)).padStart(2,'0') + ' min/km'; const range = (lo, hi) => fmt(thr*lo) + ' - ' + fmt(thr*hi); return rows([ ['Wettkampf-Pace', fmt(racePace)], ['Geschaetzte Schwellen-Pace', fmt(thr)], ['Easy / Grundlage', range(1.20, 1.35)], ['Marathon-Tempo', range(1.06, 1.10)], ['Tempolauf (Schwelle)', range(0.99, 1.02)], ['Intervalle (VO2max)', range(0.90, 0.95)], ['Wiederholungen / schnell', range(0.84, 0.88)] ]);
            }
            case 'kalorienverbrauch-schwimmen-rechner': {
                const mets = { brustLangsam: 5.3, brustZuegig: 8.3, kraulMittel: 8.3, kraulSchnell: 10.0, ruecken: 7.0, schmetterling: 13.8, wassertreten: 5.5 }; const namen = { brustLangsam: 'Brust, locker', brustZuegig: 'Brust, zuegig', kraulMittel: 'Kraulen, mittel', kraulSchnell: 'Kraulen, schnell', ruecken: 'Rueckenschwimmen', schmetterling: 'Schmetterling', wassertreten: 'Wassertreten' }; const key = value('stil'); const met = mets[key] || 8.3; const kg = Math.max(1, n('gewicht')); const min = Math.max(0, n('dauer')); const std = min / 60; const kcal = met * kg * std; const proMin = min > 0 ? kcal / min : 0; const fettG = kcal * 0.5 / 9; return rows([ ['Schwimmstil', escapeHtml(namen[key] || 'Schwimmen')], ['MET-Wert', num.format(met)], ['Kalorienverbrauch', num.format(kcal) + ' kcal'], ['Verbrauch pro Minute', num.format(proMin) + ' kcal/min'], ['Verbrauch pro 100 m (ca.)', num.format(proMin * 2) + ' kcal'], ['Rechnerischer Fettanteil', num.format(fettG) + ' g'], ['Entspricht etwa', num.format(kcal / 250) + ' Riegel a 250 kcal'] ]);
            }
            case 'gehgeschwindigkeit-rechner': {
                const km = Math.max(0.01, n('strecke')); const tempo = Math.max(0.5, n('tempo')); const hm = Math.max(0, n('hoehe')); const schritt = Math.max(30, n('schrittlaenge')); const flachH = km / tempo; const stiegH = hm / 600; const gesamtH = flachH + stiegH; const fmt = h => { const totalMin = Math.round(h * 60); return Math.floor(totalMin / 60) + ' h ' + String(totalMin % 60).padStart(2,'0') + ' min'; }; const schritte = Math.round(km * 100000 / schritt); const kcal = km * 0.53 * 75; return rows([ ['Strecke', num.format(km) + ' km'], ['Gehzeit flach', fmt(flachH)], ['Zuschlag Hoehenmeter', fmt(stiegH)], ['Gesamte Gehzeit', fmt(gesamtH)], ['Durchschnittstempo gesamt', num.format(km / Math.max(0.01, gesamtH)) + ' km/h'], ['Schritte gesamt (ca.)', num.format(schritte)], ['Kalorien grob (75 kg)', num.format(kcal) + ' kcal'] ]);
            }
            case 'rudern-split-rechner': {
                const splitSek = Math.max(1, n('splitMin')*60 + n('splitSek')); const tProMeter = splitSek / 500; const watt = 2.80 / Math.pow(tProMeter, 3); const speed = (1 / tProMeter) * 3.6; const fmtTime = s => { const h = Math.floor(s/3600); const m = Math.floor(s%3600/60); const sec = Math.round(s%60); return (h>0 ? h+':' : '') + String(m).padStart(h>0?2:1,'0') + ':' + String(sec).padStart(2,'0'); }; const fmtSplit = s => Math.floor(s/60) + ':' + String(Math.round(s%60)).padStart(2,'0'); const t2000 = tProMeter * 2000; const t5000 = tProMeter * 5000; const kcalProH = (watt * 4 + 300) * 0.001 * 1; return rows([ ['500-m-Split', fmtSplit(splitSek) + ' min/500m'], ['Leistung', num.format(watt) + ' Watt'], ['Geschwindigkeit', num.format(speed) + ' km/h'], ['Hochrechnung 2000 m', fmtTime(t2000)], ['Hochrechnung 5000 m', fmtTime(t5000)], ['Distanz in 30 Min', num.format(30 * 60 / tProMeter / 1000) + ' km'], ['Kalorien pro Stunde (ca.)', num.format(watt * 4 * 0.8604 + 300) + ' kcal'] ]);
            }
            case 'hsv-rgb-konverter': {
                const h=clamp(n('h'),0,360);const s=clamp(n('s'),0,100)/100;const v=clamp(n('v'),0,100)/100;const c=v*s;const x=c*(1-Math.abs((h/60)%2-1));const m=v-c;let r1=0,g1=0,b1=0;if(h<60){r1=c;g1=x;}else if(h<120){r1=x;g1=c;}else if(h<180){g1=c;b1=x;}else if(h<240){g1=x;b1=c;}else if(h<300){r1=x;b1=c;}else{r1=c;b1=x;}const r=Math.round((r1+m)*255);const g=Math.round((g1+m)*255);const b=Math.round((b1+m)*255);const toHex=val=>clamp(val,0,255).toString(16).padStart(2,'0');const hex='#'+toHex(r)+toHex(g)+toHex(b);return rows([['Eingabe HSV', escapeHtml(num.format(h)+' / '+num.format(n('s'))+'% / '+num.format(n('v'))+'%')],['RGB', 'rgb('+r+', '+g+', '+b+')'],['Hex-Code', hex.toUpperCase()],['Rot R', String(r)],['Gruen G', String(g)],['Blau B', String(b)],['CSS hsl-Naehe', 'hsv('+num.format(h)+', '+num.format(n('s'))+'%, '+num.format(n('v'))+'%)']]);
            }
            case 'bitmasken-rechner': {
                const a=Math.trunc(clamp(n('a'),0,4294967295));const b=Math.trunc(clamp(n('b'),0,4294967295));const sh=Math.trunc(clamp(n('shift'),0,31));const bin=x=>(x>>>0).toString(2).padStart(8,'0');const hx=x=>'0x'+(x>>>0).toString(16).toUpperCase();const and=(a&b)>>>0;const or=(a|b)>>>0;const xor=(a^b)>>>0;const notA=(~a)>>>0;const left=(a<<sh)>>>0;const right=(a>>>sh)>>>0;return rows([['A binaer', bin(a)],['B binaer', bin(b)],['A AND B', num.format(and)+' = '+bin(and)+' = '+hx(and)],['A OR B', num.format(or)+' = '+bin(or)+' = '+hx(or)],['A XOR B', num.format(xor)+' = '+bin(xor)+' = '+hx(xor)],['NOT A (32 Bit)', num.format(notA)+' = '+hx(notA)],['A << '+sh, num.format(left)+' = '+bin(left)],['A >>> '+sh, num.format(right)+' = '+bin(right)],['Gesetzte Bits in A', String((a>>>0).toString(2).split('').filter(c=>c==='1').length)]]);
            }
            case 'datenrate-umrechner': {
                const wert=Math.max(0,n('wert'));const faktor={bit:1,kbit:1000,mbit:1000000,gbit:1000000000,byte:8,mbyte:8000000};const bitProSek=wert*(faktor[value('einheit')]||1);return rows([['Eingabe', escapeHtml(num.format(wert)+' '+value('einheit'))],['Bit/s', num.format(bitProSek)],['kbit/s', num.format(bitProSek/1000)],['Mbit/s', num.format(bitProSek/1000000)],['Gbit/s', num.format(bitProSek/1000000000)],['Byte/s', num.format(bitProSek/8)],['KB/s', num.format(bitProSek/8/1000)],['MB/s', num.format(bitProSek/8/1000000)],['GB/s', num.format(bitProSek/8/1000000000)],['1 GB Download dauert', duration(8000000000/Math.max(1,bitProSek))]]);
            }
            case 'epoch-konverter': {
                const ts=Math.trunc(Math.max(0,n('ts')));const offset=clamp(n('offset'),-12,14);const d=new Date((ts+offset*3600)*1000);const pad=x=>String(x).padStart(2,'0');const datum=pad(d.getUTCDate())+'.'+pad(d.getUTCMonth()+1)+'.'+d.getUTCFullYear();const zeit=pad(d.getUTCHours())+':'+pad(d.getUTCMinutes())+':'+pad(d.getUTCSeconds());const wochentage=['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'];return rows([['Timestamp', num.format(ts)+' s'],['Datum', datum],['Uhrzeit', zeit+(offset===0?' UTC':' (UTC'+(offset>0?'+':'')+offset+')')],['Wochentag', wochentage[d.getUTCDay()]],['In Millisekunden', num.format(ts*1000)],['Minuten seit 1970', num.format(Math.floor(ts/60))],['Stunden seit 1970', num.format(Math.floor(ts/3600))],['Tage seit 1970', num.format(Math.floor(ts/86400))],['Jahre (ca.)', num.format(ts/31557600)]]);
            }
            case 'quantisierungsstufen-rechner': {
                const bits=Math.trunc(clamp(n('bits'),1,53));const kanaele=Math.max(1,Math.trunc(n('kanaele')));const stufen=Math.pow(2,bits);const maxWert=stufen-1;const dynamik=bits*6.0206;const gesamtBits=bits*kanaele;const gesamtFarben=Math.pow(2,Math.min(53,gesamtBits));return rows([['Bit-Tiefe', num.format(bits)+' Bit'],['Stufen pro Kanal', num.format(stufen)],['Maximalwert (Ganzzahl)', num.format(maxWert)],['Dynamikbereich', num.format(dynamik)+' dB'],['Kanaele', num.format(kanaele)],['Bits gesamt', num.format(gesamtBits)+' Bit'],['Kombinationen gesamt', gesamtBits<=53?num.format(gesamtFarben):'sehr gross (2^'+gesamtBits+')'],['Schrittweite bei 0-1', (1/maxWert).toFixed(6)],['Beispiel Wert 50%', num.format(Math.round(maxWert*0.5))]]);
            }
            case 'chmod-rechner': {
                const raw = value('oktal').replace(/[^0-7]/g, '').slice(-3).padStart(3, '0'); const digits = raw.split('').map(Number); const names = ['Besitzer (u)', 'Gruppe (g)', 'Andere (o)']; const toRwx = d => (d & 4 ? 'r' : '-') + (d & 2 ? 'w' : '-') + (d & 1 ? 'x' : '-'); const rwx = digits.map(toRwx).join(''); const out = [['Oktal-Code', escapeHtml(raw)], ['rwx-Darstellung', escapeHtml(rwx)], ['Symbolisch', escapeHtml('-' + rwx)]]; digits.forEach((d, i) => { const perms = []; if (d & 4) perms.push('lesen'); if (d & 2) perms.push('schreiben'); if (d & 1) perms.push('ausfuehren'); out.push([names[i], escapeHtml((perms.length ? perms.join(', ') : 'keine Rechte') + ' (' + toRwx(d) + ')')]); }); out.push(['Befehl', escapeHtml('chmod ' + raw + ' datei')]); const offen = raw === '777'; out.push(['Hinweis', offen ? 'Achtung: 777 gibt jedem volle Rechte' : 'ueblich: 644 Dateien, 755 Ordner']); return rows(out);
            }
            case 'semver-bump-rechner': {
                const parts = value('version').replace(/^v/i, '').split('.').map(p => parseInt(p, 10)); const major = Number.isFinite(parts[0]) ? parts[0] : 0; const minor = Number.isFinite(parts[1]) ? parts[1] : 0; const patch = Number.isFinite(parts[2]) ? parts[2] : 0; const art = value('art') || 'patch'; let nm = major, nmi = minor, np = patch; if (art === 'major') { nm = major + 1; nmi = 0; np = 0; } else if (art === 'minor') { nmi = minor + 1; np = 0; } else { np = patch + 1; } const aktuell = major + '.' + minor + '.' + patch; const neu = nm + '.' + nmi + '.' + np; const erklaer = art === 'major' ? 'Inkompatible API-Aenderung: Minor und Patch werden auf 0 gesetzt.' : art === 'minor' ? 'Neues, abwaertskompatibles Feature: Patch wird auf 0 gesetzt.' : 'Abwaertskompatibler Bugfix: nur Patch steigt.'; return rows([['Aktuelle Version', escapeHtml(aktuell)], ['Naechste Version', escapeHtml(neu)], ['Mit v-Praefix', escapeHtml('v' + neu)], ['Major', num.format(nm)], ['Minor', num.format(nmi)], ['Patch', num.format(np)], ['Regel', escapeHtml(erklaer)]]);
            }
            case 'epoch-millis-konverter': {
                const raw = value('stamp').replace(/[^0-9]/g, ''); const num0 = raw === '' ? 0 : Number(raw); const einheit = value('einheit') || 's'; const ms = einheit === 'ms' ? num0 : num0 * 1000; const sek = ms / 1000; const d = new Date(ms); const valid = Number.isFinite(ms) && !isNaN(d.getTime()); const datum = valid ? d.toISOString().replace('T', ' ').slice(0, 19) + ' UTC' : 'ungueltig'; const tage = sek / 86400; const jahre = tage / 365.25; const empf = raw.length >= 13 ? 'Millisekunden (13+ Stellen)' : raw.length >= 10 ? 'Sekunden (10 Stellen)' : 'kurz / unklar'; return rows([['Eingelesen als', escapeHtml(einheit === 'ms' ? 'Millisekunden' : 'Sekunden')], ['Datum (UTC)', escapeHtml(datum)], ['Sekunden (Unix)', num.format(Math.round(sek))], ['Millisekunden', num.format(Math.round(ms))], ['Seit Epoch in Tagen', num.format(tage)], ['Seit Epoch in Jahren', num.format(jahre)], ['Vermutetes Format', escapeHtml(empf)]]);
            }
            case 'terminal-farbcode-rechner': {
                const i = clamp(Math.round(n('index')), 0, 255); const base16 = [[0,0,0],[205,0,0],[0,205,0],[205,205,0],[0,0,238],[205,0,205],[0,205,205],[229,229,229],[127,127,127],[255,0,0],[0,255,0],[255,255,0],[92,92,255],[255,0,255],[0,255,255],[255,255,255]]; let r, g, b, gruppe; if (i < 16) { [r, g, b] = base16[i]; gruppe = 'Standard / hell (0-15)'; } else if (i < 232) { const c = i - 16; const ri = Math.floor(c / 36), gi = Math.floor((c % 36) / 6), bi = c % 6; const conv = v => v === 0 ? 0 : 55 + v * 40; r = conv(ri); g = conv(gi); b = conv(bi); gruppe = '6x6x6 Farbwuerfel (16-231)'; } else { const v = 8 + (i - 232) * 10; r = g = b = v; gruppe = 'Graustufen (232-255)'; } const hx = x => x.toString(16).padStart(2, '0'); const hex = ('#' + hx(r) + hx(g) + hx(b)).toUpperCase(); return rows([['Farbindex', num.format(i)], ['Bereich', escapeHtml(gruppe)], ['RGB', escapeHtml(r + ', ' + g + ', ' + b)], ['Hex', escapeHtml(hex)], ['Vordergrund-Code', escapeHtml('\\e[38;5;' + i + 'm')], ['Hintergrund-Code', escapeHtml('\\e[48;5;' + i + 'm')], ['Reset', escapeHtml('\\e[0m')]]);
            }
            case 'mac-adresse-vendor-spicker': {
                const clean = value('mac').toUpperCase().replace(/[^0-9A-F]/g, ''); const valid = clean.length === 12; const pairs = []; for (let k = 0; k < clean.length && k < 12; k += 2) pairs.push(clean.slice(k, k + 2)); const colon = pairs.join(':'); const dash = pairs.join('-'); const cisco = (clean.match(/.{1,4}/g) || []).join('.').toLowerCase(); const oui = valid ? pairs.slice(0, 3).join(':') : '-'; let typ = '-', lokal = '-'; if (valid) { const firstByte = parseInt(pairs[0], 16); typ = (firstByte & 1) ? 'Multicast (Gruppe)' : 'Unicast (Einzelgeraet)'; lokal = (firstByte & 2) ? 'Lokal administriert' : 'Global / herstellerzugewiesen'; } const isBroadcast = clean === 'FFFFFFFFFFFF'; return rows([['Status', valid ? 'gueltige MAC-Adresse' : 'ungueltig (benoetigt 12 Hex-Zeichen)'], ['Mit Doppelpunkt', escapeHtml(valid ? colon : '-')], ['Mit Bindestrich', escapeHtml(valid ? dash : '-')], ['Cisco-Schreibweise', escapeHtml(valid ? cisco : '-')], ['OUI-Praefix (Hersteller)', escapeHtml(oui)], ['Adresstyp', escapeHtml(typ)], ['Vergabe', escapeHtml(lokal)], ['Besonderheit', isBroadcast ? 'Broadcast-Adresse' : (valid ? 'normal' : '-')]]);
            }
            case 'serp-pixelbreite-rechner': {
                const wide='mwMW@%'; const medium='abcdeghknopqsuvxyzABCDEFGHKNOPQRSTUVXYZ0123456789'; const narrow='fjrtIJ ()[]'; const thin='ilft.,:;\'!|'; const px=(s,sz)=>{ let w=0; for(const c of String(s)){ if(thin.includes(c)) w+=sz*0.30; else if(narrow.includes(c)) w+=sz*0.42; else if(wide.includes(c)) w+=sz*0.78; else if(medium.includes(c)) w+=sz*0.58; else w+=sz*0.55; } return w; }; const title=value('title'); const desc=value('desc'); const titlePx=Math.round(px(title,18)); const descPx=Math.round(px(desc,14)); const titleLimit=580; const descLimit=920; const titleOk=titlePx<=titleLimit; const descOk=descPx<=descLimit; const titleRest=titleLimit-titlePx; const descRest=descLimit-descPx; return rows([['Title-Zeichen', title.length],['Title-Pixel (ca.)', num.format(titlePx)+' px'],['Title-Limit Desktop', titleLimit+' px'],['Title-Status', titleOk?'passt ('+num.format(titleRest)+' px frei)':'zu lang ('+num.format(-titleRest)+' px drüber)'],['Description-Zeichen', desc.length],['Description-Pixel (ca.)', num.format(descPx)+' px'],['Description-Limit Desktop', descLimit+' px'],['Description-Status', descOk?'passt ('+num.format(descRest)+' px frei)':'zu lang ('+num.format(-descRest)+' px drüber)'],['Gesamt-Einschätzung', (titleOk&&descOk)?'beides im grünen Bereich':'mindestens ein Element wird gekürzt'],['Title-Vorschau', escapeHtml(title.slice(0,70))]]) + '<div class="snippet"><div class="snippet-title">'+escapeHtml(title)+'</div><div class="snippet-url">https://kotsch.tech/</div><div class="snippet-desc">'+escapeHtml(desc)+'</div></div>';
            }
            case 'ctr-nach-position-rechner': {
                const ctr=[0,28.5,15.7,11.0,8.0,7.2,5.1,4.0,3.2,2.8,2.5,2.0,1.7,1.5,1.3,1.2,1.1,1.0,0.9,0.8,0.7]; const ctrFor=p=>{ const i=Math.round(clamp(p,1,20)); return ctr[i]||0.5; }; const vol=Math.max(0, n('volumen')); const pos=clamp(n('position'),1,20); const ziel=clamp(n('ziel'),1,20); const ctrNow=ctrFor(pos); const ctrZiel=ctrFor(ziel); const klicksNow=Math.round(vol*ctrNow/100); const klicksZiel=Math.round(vol*ctrZiel/100); const diff=klicksZiel-klicksNow; const faktor=klicksNow>0?klicksZiel/klicksNow:0; return rows([['Suchvolumen', num.format(vol)+' /Monat'],['CTR auf Position '+num.format(Math.round(pos)), num.format(ctrNow)+' %'],['Klicks aktuell', num.format(klicksNow)+' /Monat'],['CTR auf Ziel-Position '+num.format(Math.round(ziel)), num.format(ctrZiel)+' %'],['Klicks auf Ziel-Position', num.format(klicksZiel)+' /Monat'],['Klick-Differenz', (diff>=0?'+':'')+num.format(diff)+' /Monat'],['Veränderung', faktor>0?num.format(faktor)+' x so viele Klicks':'-'],['Klicks pro Jahr (Ziel)', num.format(klicksZiel*12)],['Klicks Platz 1', num.format(Math.round(vol*ctr[1]/100))+' /Monat'],['Klicks Platz 10', num.format(Math.round(vol*ctr[10]/100))+' /Monat']]);
            }
            case 'crawl-budget-rechner': {
                const urls=Math.max(0, n('urls')); const rate=Math.max(0, n('rate')); const noindex=clamp(n('noindex'),0,100); const tageAll=rate>0?urls/rate:0; const verschwendet=urls*noindex/100; const sinnvoll=urls-verschwendet; const tageSinnvoll=rate>0?sinnvoll/rate:0; const ersparnis=tageAll-tageSinnvoll; const proWoche=rate*7; const proMonat=rate*30; return rows([['Indexierbare URLs', num.format(urls)],['Crawl-Rate', num.format(rate)+' Seiten/Tag'],['Voll-Crawl aller URLs', rate>0?num.format(tageAll)+' Tage':'Crawl-Rate eingeben'],['Davon unnötige URLs', num.format(Math.round(verschwendet))+' ('+num.format(noindex)+' %)'],['Sinnvoll zu crawlende URLs', num.format(Math.round(sinnvoll))],['Voll-Crawl nach Bereinigung', rate>0?num.format(tageSinnvoll)+' Tage':'-'],['Eingesparte Crawl-Zeit', rate>0?num.format(ersparnis)+' Tage':'-'],['Crawl-Kapazität pro Woche', num.format(proWoche)+' Seiten'],['Crawl-Kapazität pro Monat', num.format(proMonat)+' Seiten'],['Einschätzung', tageAll>30?'Crawl-Budget knapp - aufräumen lohnt sich':'Crawl-Budget unkritisch']]);
            }
            case 'anchor-text-verteilung-rechner': {
                const marke=value('marke').toLowerCase().trim(); const lines=value('anchors').split('\n').map(s=>s.trim()).filter(Boolean); const total=lines.length; const generic=['hier klicken','klicke hier','mehr erfahren','zur website','weiterlesen','mehr dazu','hier','klick hier','jetzt lesen','zum artikel','mehr','website']; let brand=0,gen=0,url=0,money=0; for(const a of lines){ const low=a.toLowerCase(); if(/^(https?:\/\/|www\.)/.test(low)||/\.(de|com|tech|org|net|io)\b/.test(low)) url++; else if(marke&&low.includes(marke)) brand++; else if(generic.includes(low)) gen++; else money++; } const pct=x=>total>0?Math.round(x/total*100):0; const moneyPct=pct(money); const warn=moneyPct>40?'WARNUNG: Money-Keyword-Anteil sehr hoch (Spam-Risiko)':moneyPct>25?'erhöht - etwas mehr Brand/generisch streuen':'natürlich verteilt'; return rows([['Anchor-Texte gesamt', num.format(total)],['Brand-Anker', num.format(brand)+' ('+pct(brand)+' %)'],['Money-Keyword-Anker', num.format(money)+' ('+moneyPct+' %)'],['Generische Anker', num.format(gen)+' ('+pct(gen)+' %)'],['URL/Domain-Anker', num.format(url)+' ('+pct(url)+' %)'],['Einschätzung Money-Keywords', warn],['Empfohlener Brand-Anteil', '40-60 %'],['Empfohlener Money-Anteil', 'unter 20-25 %'],['Häufigster Ankertyp', total===0?'-':[['Brand',brand],['Money',money],['Generisch',gen],['URL',url]].sort((a,b)=>b[1]-a[1])[0][0]]]);
            }
            case 'seo-sichtbarkeitsindex-rechner': {
                const ctr=[0,28.5,15.7,11.0,8.0,7.2,5.1,4.0,3.2,2.8,2.5,2.0,1.7,1.5,1.3,1.2,1.1,1.0,0.9,0.8,0.7]; const ctrFor=p=>{ const i=Math.round(clamp(p,1,20)); return ctr[i]||0.5; }; const lines=value('daten').split('\n').map(s=>s.trim()).filter(Boolean); let index=0, klicks=0, top3=0, top10=0, kw=0, volSum=0; for(const line of lines){ const nums=parseNumbers(line); if(nums.length<2) continue; const vol=Math.max(0,nums[0]); const pos=clamp(nums[1],1,20); kw++; volSum+=vol; const c=ctrFor(pos); klicks+=vol*c/100; index+=vol*c/100/100; if(pos<=3) top3++; if(pos<=10) top10++; } const schnittPos=kw>0?lines.map(l=>parseNumbers(l)).filter(a=>a.length>=2).reduce((s,a)=>s+clamp(a[1],1,20),0)/kw:0; return rows([['Erfasste Keywords', num.format(kw)],['Gesamtes Suchvolumen', num.format(volSum)+' /Monat'],['Sichtbarkeitsindex', num.format(index)],['Geschätzte Klicks gesamt', num.format(Math.round(klicks))+' /Monat'],['Keywords in Top 3', num.format(top3)+' ('+(kw>0?Math.round(top3/kw*100):0)+' %)'],['Keywords in Top 10', num.format(top10)+' ('+(kw>0?Math.round(top10/kw*100):0)+' %)'],['Durchschnittsposition', num.format(schnittPos)],['Potenzial bei allen auf Platz 1', num.format(Math.round(volSum*ctr[1]/100))+' Klicks/Monat'],['Ausschöpfung des Potenzials', volSum>0?num.format(klicks/(volSum*ctr[1]/100)*100)+' %':'-']]);
            }
            case 'passwort-cracking-zeit-rechner': {
                const pool = { l26: 26, d36: 36, u62: 62, s94: 94 }[value('pool')] || 62; const len = clamp(Math.round(n('length')), 1, 128); const rate = Math.max(1, n('rate')); const log10comb = len * Math.log10(pool); const log10secAvg = log10comb - Math.log10(2) - Math.log10(rate); const secAvg = Math.pow(10, Math.min(log10secAvg, 300)); const fmtDur = s => { if (s < 1) return 'unter 1 Sekunde'; if (s < 60) return num.format(s) + ' Sekunden'; if (s < 3600) return num.format(s / 60) + ' Minuten'; if (s < 86400) return num.format(s / 3600) + ' Stunden'; if (s < 31557600) return num.format(s / 86400) + ' Tage'; const y = s / 31557600; if (y < 1e6) return num.format(y) + ' Jahre'; return 'ca. 10^' + num.format(log10secAvg - Math.log10(31557600)) + ' Jahre'; }; const bits = len * Math.log2(pool); const verdict = log10secAvg < 0 ? 'sofort knackbar' : log10secAvg < 4 ? 'sehr unsicher' : log10secAvg < 8 ? 'schwach' : log10secAvg < 13 ? 'solide' : 'sehr sicher'; return rows([ ['Durchschnittliche Knack-Dauer', fmtDur(secAvg)], ['Maximale Knack-Dauer', fmtDur(secAvg * 2)], ['Mögliche Kombinationen', 'ca. 10^' + num.format(log10comb)], ['Entropie', num.format(bits) + ' Bit'], ['Zeichenraum', pool + ' Zeichen'], ['Angreifer-Tempo', num.format(rate) + ' Versuche/s'], ['Einschätzung', verdict] ]);
            }
            case 'totp-restzeit-rechner': {
                const step = clamp(Math.round(n('step')), 1, 300); let t; if (n('epoch') > 0) { t = Math.floor(n('epoch')); } else { t = clamp(Math.round(n('second')), 0, 59); } const into = ((t % step) + step) % step; const remaining = step - into; const counter = Math.floor(t / step); const pct = Math.round(into / step * 100); const bar = '█'.repeat(Math.round(remaining / step * 10)) + '░'.repeat(10 - Math.round(remaining / step * 10)); const status = remaining <= 5 ? 'läuft gleich ab – warte auf den nächsten Code' : remaining <= 10 ? 'bald neuer Code' : 'reichlich Zeit'; return rows([ ['Restzeit aktueller Code', num.format(remaining) + ' Sekunden'], ['Bereits verstrichen', num.format(into) + ' von ' + step + ' Sekunden'], ['Fortschritt im Fenster', pct + ' %'], ['Restzeit-Balken', bar], ['Aktueller Zeitschritt-Zähler', num.format(counter)], ['Nächstes Fenster beginnt in', num.format(remaining) + ' Sekunden'], ['Hinweis', status] ]);
            }
            case 'pin-sicherheit-rechner': {
                const digits = clamp(Math.round(n('digits')), 1, 12); const total = Math.pow(10, digits); const blocked = value('blocked') === 'ja' ? Math.min(25, total - 1) : 0; const effective = Math.max(1, total - blocked); const attempts = clamp(Math.round(n('attempts')), 1, total); const pGuess = attempts / effective; const pPct = pGuess * 100; const avgTries = effective / 2; const verdict = digits <= 3 ? 'sehr unsicher' : pPct > 5 ? 'unsicher bei wenigen Versuchen' : digits >= 6 ? 'gut' : 'okay mit Sperre'; const oneIn = Math.round(effective / Math.max(1, attempts)); return rows([ ['Mögliche Kombinationen', num.format(total)], ['Effektiv (ohne schwache)', num.format(effective)], ['Erlaubte Fehlversuche', num.format(attempts)], ['Knack-Chance pro Angriff', num.format(pPct) + ' %'], ['Das ist 1 zu', num.format(oneIn)], ['Durchschnittlich nötige Versuche', num.format(avgTries)], ['Einschätzung', verdict] ]);
            }
            case 'passwort-ablauf-rechner': {
                const validity = clamp(Math.round(n('validity')), 1, 3650); const elapsed = clamp(Math.round(n('elapsed')), 0, 100000); const warn = clamp(Math.round(n('warn')), 0, validity); const remaining = validity - elapsed; const pctUsed = clamp(Math.round(elapsed / validity * 100), 0, 100); const day = 86400000; const expiryDate = new Date(Date.now() + remaining * day); const dateStr = expiryDate.toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric' }); let status; if (remaining < 0) { status = 'bereits abgelaufen seit ' + num.format(-remaining) + ' Tagen'; } else if (remaining === 0) { status = 'läuft heute ab'; } else if (remaining <= warn) { status = 'läuft bald ab – jetzt wechseln'; } else { status = 'noch gültig'; } const bar = '█'.repeat(Math.round(pctUsed / 10)) + '░'.repeat(10 - Math.round(pctUsed / 10)); return rows([ ['Voraussichtliches Ablaufdatum', escapeHtml(dateStr)], ['Verbleibende Tage', num.format(Math.max(0, remaining)) + (remaining < 0 ? ' (überfällig)' : '')], ['Bereits genutzt', pctUsed + ' % der Gültigkeit'], ['Nutzung', bar], ['Gültigkeitsdauer', num.format(validity) + ' Tage'], ['Vorwarnung ab', num.format(warn) + ' Tagen Restzeit'], ['Status', status] ]);
            }
            case 'datenleck-passwort-risiko-rechner': {
                const accounts = clamp(Math.round(n('accounts')), 1, 100000); const reused = clamp(Math.round(n('reused')), 0, accounts); const twofa = clamp(Math.round(n('twofa')), 0, accounts); const breached = clamp(Math.round(n('breached')), 0, 1000); const reuseRate = reused / accounts; const twofaRate = twofa / accounts; const exposed = reused > 0 && breached > 0 ? reused : 0; let score = 100; score -= reuseRate * 55; score -= Math.min(20, breached * 6); score += twofaRate * 25; score = clamp(Math.round(score), 0, 100); const level = score >= 80 ? 'sehr gut' : score >= 60 ? 'solide' : score >= 40 ? 'verbesserungswürdig' : score >= 20 ? 'riskant' : 'kritisch'; const tip = reuseRate > 0.3 ? 'Wichtigster Schritt: einzigartige Passwörter pro Konto, am besten per Passwortmanager.' : twofaRate < 0.5 ? 'Aktiviere 2FA für deine wichtigsten Konten (E-Mail, Bank, Cloud).' : 'Gute Basis – behalte deine Passwort-Hygiene bei.'; const bar = '█'.repeat(Math.round(score / 10)) + '░'.repeat(10 - Math.round(score / 10)); return rows([ ['Sicherheits-Score', num.format(score) + ' / 100'], ['Risiko-Stufe', level], ['Score-Balken', bar], ['Wiederverwendungs-Quote', num.format(Math.round(reuseRate * 100)) + ' %'], ['2FA-Abdeckung', num.format(Math.round(twofaRate * 100)) + ' %'], ['Potenziell gefährdete Konten', num.format(exposed)], ['Wichtigster Tipp', escapeHtml(tip)] ]);
            }
            // NEW_CASES_MARKER
            case 'llm-api-kosten-rechner': {
    const reqProTag = n('reqProTag'); const inputTok = n('inputTok'); const outputTok = n('outputTok');
    const preisInput = n('preisInput'); const preisOutput = n('preisOutput'); const kurs = n('kurs');
    const inTokTag = reqProTag * inputTok;
    const outTokTag = reqProTag * outputTok;
    const inKostenUsd = (inTokTag / 1e6) * preisInput;
    const outKostenUsd = (outTokTag / 1e6) * preisOutput;
    const kostenTagUsd = inKostenUsd + outKostenUsd;
    const kostenTagEur = kostenTagUsd * kurs;
    const proAnfrage = reqProTag ? kostenTagEur / reqProTag : 0;
    const outAnteil = kostenTagUsd ? outKostenUsd / kostenTagUsd * 100 : 0;
    const tokenMonat = (inTokTag + outTokTag) * 30;
    return rows([
        ['Kosten pro Tag', euro.format(kostenTagEur)],
        ['Kosten pro Monat (x30)', euro.format(kostenTagEur * 30)],
        ['Kosten pro Jahr (x365)', euro.format(kostenTagEur * 365)],
        ['Kosten pro Anfrage', euro.format(proAnfrage)],
        ['Input-Token pro Tag', num.format(inTokTag)],
        ['Output-Token pro Tag', num.format(outTokTag)],
        ['Token gesamt pro Monat', num.format(tokenMonat)],
        ['Output-Anteil an den Kosten', num.format(outAnteil) + ' %'],
        ['Input-Kosten pro Tag', euro.format(inKostenUsd * kurs)],
        ['Output-Kosten pro Tag', euro.format(outKostenUsd * kurs)],
    ]);
}
            case 'token-schaetzer-text': {
    const zeichen = n('zeichen'); const sprache = value('sprache'); const preis1M = n('preis1M');
    const f = sprache === 'Englisch' ? 4.0 : sprache === 'Code' ? 3.6 : 3.3;
    const tokens = Math.round(zeichen / f);
    const tokenUnten = Math.round(zeichen / (f + 0.5));
    const tokenOben = Math.round(zeichen / Math.max(0.1, f - 0.6));
    const woerter = Math.round(zeichen / 6.1);
    const tokenProWort = woerter ? tokens / woerter : 0;
    const kostenUsd = (tokens / 1e6) * preis1M;
    const tokenPro1000 = zeichen ? tokens / zeichen * 1000 : 0;
    const anteil200k = tokens / 200000 * 100;
    const inBudget = tokens ? Math.round(1000000 / tokens) : 0;
    return rows([
        ['Geschätzte Token', num.format(tokens)],
        ['Token-Spanne', num.format(tokenUnten) + ' bis ' + num.format(tokenOben)],
        ['Geschätzte Wörter', num.format(woerter)],
        ['Token pro Wort', num.format(tokenProWort)],
        ['Token pro 1.000 Zeichen', num.format(tokenPro1000)],
        ['Kosten (USD)', num.format(kostenUsd) + ' USD'],
        ['Anteil an 200k-Kontext', num.format(anteil200k) + ' %'],
        ['Passt in 1M-Budget (mal)', num.format(inBudget)],
        ['Zeichen pro Token (Faktor)', num.format(f)],
    ]);
}
            case 'kontextfenster-auslastung-rechner': {
    const fenster = n('fenster'); const system = n('system'); const verlauf = n('verlauf');
    const dokumente = n('dokumente'); const antwort = n('antwort');
    const belegtEingabe = system + verlauf + dokumente;
    const belegtGesamt = belegtEingabe + antwort;
    const auslastung = fenster ? belegtGesamt / fenster * 100 : 0;
    const frei = fenster - belegtGesamt;
    const status = frei >= 0 ? 'passt' : 'Überlauf';
    const ueberlauf = Math.max(0, belegtGesamt - fenster);
    const anteilDok = fenster ? dokumente / fenster * 100 : 0;
    const anteilVerlauf = fenster ? verlauf / fenster * 100 : 0;
    const maxVerlauf = Math.max(0, fenster - belegtGesamt);
    const antwortReserve = fenster ? antwort / fenster * 100 : 0;
    return rows([
        ['Belegt (Eingabe)', num.format(belegtEingabe) + ' Token'],
        ['Belegt gesamt (inkl. Antwort)', num.format(belegtGesamt) + ' Token'],
        ['Auslastung', num.format(auslastung) + ' %'],
        ['Freie Token', num.format(frei) + ' Token'],
        ['Status', status],
        ['Überlauf-Token', num.format(ueberlauf) + ' Token'],
        ['Anteil Dokumente', num.format(anteilDok) + ' %'],
        ['Anteil Verlauf', num.format(anteilVerlauf) + ' %'],
        ['Max. zusätzlicher Verlauf', num.format(maxVerlauf) + ' Token'],
        ['Antwort-Reserve', num.format(antwortReserve) + ' %'],
    ]);
}
            case 'ki-bildgenerierung-kosten-rechner': {
    const bilderProMonat = n('bilderProMonat'); const preisProBild = n('preisProBild');
    const ausschuss = clamp(n('ausschuss'), 0, 100); const varianten = n('varianten'); const kurs = n('kurs');
    const trefferquote = 1 - ausschuss / 100;
    const versuche = 1 / Math.max(0.01, trefferquote);
    const genProBild = versuche * varianten;
    const genMonat = bilderProMonat * genProBild;
    const kostenMonatUsd = genMonat * preisProBild;
    const kostenMonatEur = kostenMonatUsd * kurs;
    const proBild = bilderProMonat ? kostenMonatEur / bilderProMonat : 0;
    const kostenJahr = kostenMonatEur * 12;
    const verschwendet = kostenMonatEur * (ausschuss / 100);
    const genJahr = genMonat * 12;
    return rows([
        ['Kosten pro Monat', euro.format(kostenMonatEur)],
        ['Kosten pro Jahr', euro.format(kostenJahr)],
        ['Kosten pro nutzbarem Bild', euro.format(proBild)],
        ['Trefferquote', num.format(trefferquote * 100) + ' %'],
        ['Versuche pro nutzbarem Bild', num.format(versuche)],
        ['Generierungen pro nutzbarem Bild', num.format(genProBild)],
        ['Generierungen pro Monat', num.format(genMonat)],
        ['Generierungen pro Jahr', num.format(genJahr)],
        ['Verschwendet durch Ausschuss/Monat', euro.format(verschwendet)],
    ]);
}
            case 'ki-video-generierung-kosten-rechner': {
    const clipsProMonat = n('clipsProMonat'); const sekProClip = n('sekProClip');
    const preisProSek = n('preisProSek'); const ausschuss = clamp(n('ausschuss'), 0, 100); const kurs = n('kurs');
    const trefferquote = 1 - ausschuss / 100;
    const versuche = 1 / Math.max(0.01, trefferquote);
    const genSekMonat = clipsProMonat * sekProClip * versuche;
    const kostenMonatUsd = genSekMonat * preisProSek;
    const kostenMonatEur = kostenMonatUsd * kurs;
    const proClip = clipsProMonat ? kostenMonatEur / clipsProMonat : 0;
    const proSekFertig = sekProClip ? proClip / sekProClip : 0;
    const verschwendet = kostenMonatEur * (ausschuss / 100);
    const kostenJahr = kostenMonatEur * 12;
    const genMinMonat = genSekMonat / 60;
    return rows([
        ['Kosten pro Monat', euro.format(kostenMonatEur)],
        ['Kosten pro Jahr', euro.format(kostenJahr)],
        ['Kosten pro nutzbarem Clip', euro.format(proClip)],
        ['Kosten pro Sekunde fertiges Video', euro.format(proSekFertig)],
        ['Trefferquote', num.format(trefferquote * 100) + ' %'],
        ['Versuche pro nutzbarem Clip', num.format(versuche)],
        ['Generierte Sekunden pro Monat', num.format(genSekMonat) + ' s'],
        ['Generierte Minuten pro Monat', num.format(genMinMonat) + ' min'],
        ['Verschwendet durch Ausschuss/Monat', euro.format(verschwendet)],
    ]);
}
            case 'gpu-vram-bedarf-rechner': {
    const params = n('params'); const quant = value('quant'); const kontext = n('kontext');
    const schichten = n('schichten'); const hidden = n('hidden'); const gpuVram = n('gpuVram');
    const b = quant === '8 bit' ? 8 : quant === '5 bit' ? 5 : quant === '4 bit' ? 4 : 16;
    const gewichte = params * 1e9 * b / 8 / 1e9;
    const kvCache = 2 * schichten * hidden * kontext * 2 / 1e9;
    const overhead = gewichte * 0.1;
    const bedarf = gewichte + kvCache + overhead;
    const frei = gpuVram - bedarf;
    const passt = frei >= 0 ? 'ja' : 'nein';
    const auslastung = gpuVram ? bedarf / gpuVram * 100 : 0;
    const anteilGewichte = bedarf ? gewichte / bedarf * 100 : 0;
    const anteilKv = bedarf ? kvCache / bedarf * 100 : 0;
    const kvNenner = 2 * schichten * hidden * 2;
    const maxKontext = kvNenner > 0 ? Math.max(0, Math.floor((gpuVram - gewichte - overhead) * 1e9 / kvNenner)) : 0;
    const klasse = bedarf <= 24 ? '24 GB' : bedarf <= 48 ? '48 GB' : bedarf <= 80 ? '80 GB' : 'Multi-GPU';
    return rows([
        ['VRAM-Bedarf gesamt', num.format(bedarf) + ' GB'],
        ['Gewichte', num.format(gewichte) + ' GB'],
        ['KV-Cache (Kontext)', num.format(kvCache) + ' GB'],
        ['Overhead', num.format(overhead) + ' GB'],
        ['Freier VRAM', num.format(frei) + ' GB'],
        ['Passt auf die GPU?', passt],
        ['Auslastung', num.format(auslastung) + ' %'],
        ['Anteil Gewichte', num.format(anteilGewichte) + ' %'],
        ['Anteil KV-Cache', num.format(anteilKv) + ' %'],
        ['Max. Kontext bei diesem VRAM', num.format(maxKontext) + ' Token'],
        ['Empfohlene GPU-Klasse', klasse],
    ]);
}
            case 'lokales-llm-tokens-pro-sekunde-rechner': {
    const params = n('params'); const quant = value('quant'); const bandbreite = n('bandbreite');
    const effizienz = clamp(n('effizienz'), 0, 100); const outputTok = n('outputTok');
    const b = quant === '8 bit' ? 8 : quant === '4 bit' ? 4 : 16;
    const modellGb = params * 1e9 * b / 8 / 1e9;
    const theoretisch = modellGb > 0 ? bandbreite / modellGb : 0;
    const praktisch = theoretisch * effizienz / 100;
    const sekPro100 = praktisch > 0 ? 100 / praktisch : 0;
    const antwortzeit = praktisch > 0 ? outputTok / praktisch : 0;
    const tokenProMin = praktisch * 60;
    const woerterProMin = tokenProMin / 1.4;
    const bewertung = praktisch >= 30 ? 'flüssig' : praktisch >= 10 ? 'brauchbar' : 'langsam';
    const tokenProStunde = praktisch * 3600;
    return rows([
        ['Praktische Geschwindigkeit', num.format(praktisch) + ' Token/s'],
        ['Theoretische Geschwindigkeit', num.format(theoretisch) + ' Token/s'],
        ['Modellgröße', num.format(modellGb) + ' GB'],
        ['Sekunden pro 100 Token', num.format(sekPro100) + ' s'],
        ['Antwortzeit (' + num.format(outputTok) + ' Token)', num.format(antwortzeit) + ' s'],
        ['Token pro Minute', num.format(tokenProMin)],
        ['Wörter pro Minute', num.format(woerterProMin)],
        ['Token pro Stunde', num.format(tokenProStunde)],
        ['Bewertung', bewertung],
    ]);
}
            case 'embedding-kosten-rechner': {
    const dokumente = n('dokumente'); const tokenProDok = n('tokenProDok'); const chunkGroesse = n('chunkGroesse');
    const preis1M = n('preis1M'); const neuindex = n('neuindexProJahr'); const kurs = n('kurs');
    const tokenGesamt = dokumente * tokenProDok;
    const chunkDiv = Math.max(1, chunkGroesse);
    const chunksGesamt = Math.ceil(tokenGesamt / chunkDiv);
    const chunksProDok = Math.ceil(tokenProDok / chunkDiv);
    const einmalUsd = (tokenGesamt / 1e6) * preis1M;
    const einmalEur = einmalUsd * kurs;
    const reindexEur = einmalEur * neuindex;
    const jahr1 = einmalEur + reindexEur;
    const proDok = dokumente ? einmalEur / dokumente : 0;
    const pro1000 = dokumente ? einmalEur / dokumente * 1000 : 0;
    return rows([
        ['Token gesamt', num.format(tokenGesamt)],
        ['Chunks gesamt (= Vektoren)', num.format(chunksGesamt)],
        ['Chunks pro Dokument', num.format(chunksProDok)],
        ['Einmalkosten Vektorisierung', euro.format(einmalEur)],
        ['Jährliche Reindex-Kosten', euro.format(reindexEur)],
        ['Gesamtkosten 1. Jahr', euro.format(jahr1)],
        ['Kosten pro Dokument', euro.format(proDok)],
        ['Kosten pro 1.000 Dokumente', euro.format(pro1000)],
        ['Einmalkosten (USD)', num.format(einmalUsd) + ' USD'],
    ]);
}
            case 'ki-zeitersparnis-roi-rechner': {
    const aufgabenProWoche = n('aufgabenProWoche'); const minutenOhne = n('minutenOhne'); const minutenMit = n('minutenMit');
    const nachbearbeitung = n('nachbearbeitung'); const stundenlohn = n('stundenlohn'); const aboMonat = n('aboMonat');
    const effektivMit = minutenMit * (1 + nachbearbeitung / 100);
    const gespartProAufgabe = minutenOhne - effektivMit;
    const gespartWoche = gespartProAufgabe * aufgabenProWoche;
    const gespartStdMonat = gespartWoche * 4.33 / 60;
    const gespartLohnMonat = gespartStdMonat * stundenlohn;
    const nettoMonat = gespartLohnMonat - aboMonat;
    const roi = aboMonat ? nettoMonat / aboMonat * 100 : 0;
    const proTag = gespartLohnMonat / 30;
    const amortisation = proTag > 0 ? aboMonat / proTag : 0;
    const gespartStdJahr = gespartStdMonat * 12;
    const nettoJahr = nettoMonat * 12;
    const effizienz = minutenOhne ? gespartProAufgabe / minutenOhne * 100 : 0;
    return rows([
        ['Netto-Nutzen pro Monat', euro.format(nettoMonat)],
        ['Netto-Nutzen pro Jahr', euro.format(nettoJahr)],
        ['ROI', num.format(roi) + ' %'],
        ['Amortisation', amortisation > 0 ? num.format(amortisation) + ' Tage' : 'keine Ersparnis'],
        ['Gesparte Lohnkosten pro Monat', euro.format(gespartLohnMonat)],
        ['Gesparte Stunden pro Monat', num.format(gespartStdMonat) + ' h'],
        ['Gesparte Stunden pro Jahr', num.format(gespartStdJahr) + ' h'],
        ['Effektive Minuten mit KI', num.format(effektivMit) + ' min'],
        ['Gesparte Minuten pro Aufgabe', num.format(gespartProAufgabe) + ' min'],
        ['Effizienzgewinn', num.format(effizienz) + ' %'],
    ]);
}
            case 'ki-vs-mensch-uebersetzung-rechner': {
    const woerter = n('woerter'); const preisHuman = n('preisHuman'); const kiTokenKosten = n('kiTokenKosten');
    const postEditing = n('postEditing'); const lektoratAnteil = clamp(n('lektoratAnteil'), 0, 100);
    const humanKosten = woerter * preisHuman;
    const kiGrund = woerter / 1000 * kiTokenKosten;
    const peKosten = woerter * (lektoratAnteil / 100) * postEditing;
    const kiGesamt = kiGrund + peKosten;
    const ersparnis = humanKosten - kiGesamt;
    const ersparnisProz = humanKosten ? ersparnis / humanKosten * 100 : 0;
    const kiProWort = woerter ? kiGesamt / woerter : 0;
    const ersparnisJahr = ersparnis * 12;
    const faktor = kiGesamt ? humanKosten / kiGesamt : 0;
    const peNenner = woerter * postEditing;
    const maxLektorat = peNenner > 0 ? (humanKosten - kiGrund) / peNenner * 100 : 0;
    return rows([
        ['Ersparnis pro Monat', euro.format(ersparnis)],
        ['Ersparnis pro Jahr', euro.format(ersparnisJahr)],
        ['Ersparnis in Prozent', num.format(ersparnisProz) + ' %'],
        ['Humanübersetzung gesamt', euro.format(humanKosten)],
        ['KI gesamt (inkl. Post-Editing)', euro.format(kiGesamt)],
        ['KI-Grundkosten', euro.format(kiGrund)],
        ['Post-Editing-Kosten', euro.format(peKosten)],
        ['KI-Kosten pro Wort', euro.format(kiProWort)],
        ['Faktor günstiger', num.format(faktor) + 'x'],
        ['Max. Lektoratsanteil für Break-even', num.format(clamp(maxLektorat, 0, 999)) + ' %'],
    ]);
}
            case 'chatbot-betriebskosten-rechner': {
    const nutzer = Math.max(0, n('nutzerProTag'));
    const nachr = Math.max(0, n('nachrichtenProSession'));
    const outTok = Math.max(0, n('tokenProNachricht'));
    const inTok = Math.max(0, n('kontextTokenSchnitt'));
    const pIn = n('preisInput'); const pOut = n('preisOutput'); const kurs = n('kurs');
    const antwortenTag = nutzer * nachr;
    const inputTokTag = antwortenTag * inTok;
    const outputTokTag = antwortenTag * outTok;
    const inKostTagUsd = inputTokTag / 1e6 * pIn;
    const outKostTagUsd = outputTokTag / 1e6 * pOut;
    const kostTagEur = (inKostTagUsd + outKostTagUsd) * kurs;
    const kostMonat = kostTagEur * 30;
    const proSession = nutzer ? kostTagEur / nutzer : 0;
    const proNachricht = antwortenTag ? kostTagEur / antwortenTag : 0;
    const kostJahr = kostTagEur * 365;
    const tokenMonat = (inputTokTag + outputTokTag) * 30;
    return rows([
        ['Antworten pro Tag', num.format(antwortenTag)],
        ['Kosten pro Tag', euro.format(kostTagEur)],
        ['Kosten pro Monat', euro.format(kostMonat)],
        ['Kosten pro Jahr', euro.format(kostJahr)],
        ['Kosten pro Session', euro.format(proSession)],
        ['Kosten pro Nachricht', euro.format(proNachricht)],
        ['Input-Token pro Tag', num.format(inputTokTag)],
        ['Output-Token pro Tag', num.format(outputTokTag)],
        ['Token gesamt pro Monat', num.format(tokenMonat)],
    ]);
}
            case 'ki-transkription-kosten-rechner': {
    const stunden = Math.max(0, n('stundenProMonat'));
    const preisMin = n('preisProMinute');
    const faktor = Math.max(0, n('manuellFaktor'));
    const lohn = n('manuellStundenlohn');
    const kurs = n('kurs');
    const audioMin = stunden * 60;
    const kiUsd = audioMin * preisMin;
    const kiEur = kiUsd * kurs;
    const kiJahr = kiEur * 12;
    const manuellStunden = stunden * faktor;
    const manuellEur = manuellStunden * lohn;
    const ersparnis = manuellEur - kiEur;
    const ersparnisProz = manuellEur ? ersparnis / manuellEur * 100 : 0;
    const guenstigerFaktor = kiEur ? manuellEur / kiEur : 0;
    const kiProStunde = stunden ? kiEur / stunden : 0;
    return rows([
        ['Audiominuten pro Monat', num.format(audioMin)],
        ['KI-Kosten pro Monat', euro.format(kiEur)],
        ['KI-Kosten pro Jahr', euro.format(kiJahr)],
        ['KI-Kosten pro Stunde Audio', euro.format(kiProStunde)],
        ['Manuelle Arbeitsstunden pro Monat', num.format(manuellStunden) + ' h'],
        ['Manuelle Kosten pro Monat', euro.format(manuellEur)],
        ['Ersparnis durch KI pro Monat', euro.format(ersparnis)],
        ['Ersparnis in Prozent', num.format(ersparnisProz) + ' %'],
        ['Faktor günstiger als manuell', num.format(guenstigerFaktor) + 'x'],
    ]);
}
            case 'fine-tuning-vs-prompting-rechner': {
    const fewShot = Math.max(0, n('fewShotToken'));
    const anfragen = Math.max(0, n('anfragenProMonat'));
    const pIn = n('preisInput');
    const training = n('trainingKosten');
    const aufpreis = n('aufpreisFt');
    const kurs = n('kurs');
    const promptErsparnisUsd = (fewShot / 1e6) * pIn;
    const mehrkostenUsd = (1000 / 1e6) * aufpreis;
    const nettoUsd = promptErsparnisUsd - mehrkostenUsd;
    const breakEvenAnfragen = nettoUsd > 0 ? training / nettoUsd : 0;
    const breakEvenMonate = (nettoUsd > 0 && anfragen) ? breakEvenAnfragen / anfragen : 0;
    const promptingMonat = (anfragen * promptErsparnisUsd) * kurs;
    const ftMonat = (anfragen * mehrkostenUsd) * kurs;
    const ersparnisMonat = (anfragen * nettoUsd) * kurs;
    const empfehlung = (nettoUsd > 0 && breakEvenMonate > 0 && breakEvenMonate <= 12) ? 'Fine-Tuning lohnt' : 'Prompting bleiben';
    return rows([
        ['Gesparte Token pro Anfrage', num.format(fewShot)],
        ['Prompt-Ersparnis pro Anfrage', euro.format(promptErsparnisUsd * kurs)],
        ['Modell-Aufpreis pro Anfrage', euro.format(mehrkostenUsd * kurs)],
        ['Netto-Ersparnis pro Anfrage', euro.format(nettoUsd * kurs)],
        ['Break-even nach Anfragen', nettoUsd > 0 ? num.format(Math.round(breakEvenAnfragen)) : 'nie'],
        ['Break-even in Monaten', (nettoUsd > 0 && anfragen) ? num.format(breakEvenMonate) : '-'],
        ['Prompting-Mehrkosten pro Monat', euro.format(promptingMonat)],
        ['Fine-Tuning-Mehrkosten pro Monat', euro.format(ftMonat)],
        ['Ersparnis pro Monat ab Break-even', euro.format(ersparnisMonat)],
        ['Empfehlung', empfehlung],
    ]);
}
            case 'prompt-caching-ersparnis-rechner': {
    const anfragen = Math.max(0, n('anfragenProTag'));
    const cachebar = Math.max(0, n('cachebareToken'));
    const uebrig = Math.max(0, n('uebrigeInput'));
    const treffer = clamp(n('trefferquote'), 0, 100);
    const pIn = n('preisInput');
    const rabatt = clamp(n('cacheRabatt'), 0, 100);
    const kurs = n('kurs');
    const inputTokTag = anfragen * (cachebar + uebrig);
    const ohneCacheUsd = inputTokTag / 1e6 * pIn;
    const treffTag = anfragen * treffer / 100;
    const gecachteTok = treffTag * cachebar;
    const kostGecachtUsd = gecachteTok / 1e6 * pIn * (1 - rabatt / 100);
    const kostNichtGecachtUsd = Math.max(0, inputTokTag - gecachteTok) / 1e6 * pIn;
    const mitCacheUsd = kostGecachtUsd + kostNichtGecachtUsd;
    const ersparnisTagEur = (ohneCacheUsd - mitCacheUsd) * kurs;
    const ersparnisMonat = ersparnisTagEur * 30;
    const ersparnisProz = ohneCacheUsd ? (ohneCacheUsd - mitCacheUsd) / ohneCacheUsd * 100 : 0;
    const mitCacheMonat = mitCacheUsd * 30 * kurs;
    const ohneCacheMonat = ohneCacheUsd * 30 * kurs;
    return rows([
        ['Input-Token pro Tag', num.format(inputTokTag)],
        ['Kosten ohne Cache pro Tag', euro.format(ohneCacheUsd * kurs)],
        ['Kosten mit Cache pro Tag', euro.format(mitCacheUsd * kurs)],
        ['Ersparnis pro Tag', euro.format(ersparnisTagEur)],
        ['Ersparnis pro Monat', euro.format(ersparnisMonat)],
        ['Ersparnis in Prozent', num.format(ersparnisProz) + ' %'],
        ['Kosten ohne Cache pro Monat', euro.format(ohneCacheMonat)],
        ['Kosten mit Cache pro Monat', euro.format(mitCacheMonat)],
        ['Cache-Treffer pro Tag', num.format(treffTag)],
    ]);
}
            case 'ki-energie-co2-rechner': {
    const anfragen = Math.max(0, n('anfragenProTag'));
    const wh = Math.max(0, n('whProAnfrage'));
    const co2Faktor = Math.max(0, n('co2Faktor'));
    const strompreis = n('strompreis');
    const anfragenMonat = anfragen * 30;
    const energieMonatWh = anfragenMonat * wh;
    const energieMonatKwh = energieMonatWh / 1000;
    const co2MonatG = energieMonatKwh * co2Faktor;
    const co2MonatKg = co2MonatG / 1000;
    const co2JahrKg = co2MonatKg * 12;
    const stromkostenMonat = energieMonatKwh * strompreis / 100;
    const autoKm = co2MonatG / 130;
    const ladungen = energieMonatWh / 12;
    const energieJahrKwh = energieMonatKwh * 12;
    const co2ProAnfrage = wh / 1000 * co2Faktor;
    return rows([
        ['Anfragen pro Monat', num.format(anfragenMonat)],
        ['Energie pro Monat', num.format(energieMonatKwh) + ' kWh'],
        ['Energie pro Jahr', num.format(energieJahrKwh) + ' kWh'],
        ['CO2 pro Monat', num.format(co2MonatKg) + ' kg'],
        ['CO2 pro Jahr', num.format(co2JahrKg) + ' kg'],
        ['CO2 pro Anfrage', num.format(co2ProAnfrage) + ' g'],
        ['Stromkosten pro Monat', euro.format(stromkostenMonat)],
        ['entspricht Auto-Kilometer pro Monat', num.format(autoKm) + ' km'],
        ['entspricht Smartphone-Ladungen pro Monat', num.format(ladungen)],
    ]);
}
            case 'rag-system-kosten-rechner': {
    const anfragen = Math.max(0, n('anfragenProTag'));
    const chunks = Math.max(0, n('chunksProAnfrage'));
    const tokChunk = Math.max(0, n('tokenProChunk'));
    const frageTok = Math.max(0, n('frageToken'));
    const antwortTok = Math.max(0, n('antwortToken'));
    const pIn = n('preisInput'); const pOut = n('preisOutput'); const kurs = n('kurs');
    const kontextTok = chunks * tokChunk;
    const inputTok = kontextTok + frageTok;
    const inKostUsd = inputTok / 1e6 * pIn;
    const outKostUsd = antwortTok / 1e6 * pOut;
    const kostAnfrageUsd = inKostUsd + outKostUsd;
    const kostAnfrageEur = kostAnfrageUsd * kurs;
    const kostTag = kostAnfrageEur * anfragen;
    const kostMonat = kostTag * 30;
    const anteilKontext = inputTok ? kontextTok / inputTok * 100 : 0;
    const kostJahr = kostTag * 365;
    const tokenMonat = (inputTok + antwortTok) * anfragen * 30;
    return rows([
        ['Kontext-Token pro Anfrage', num.format(kontextTok)],
        ['Input-Token pro Anfrage', num.format(inputTok)],
        ['Kosten pro Anfrage', euro.format(kostAnfrageEur)],
        ['Kosten pro Tag', euro.format(kostTag)],
        ['Kosten pro Monat', euro.format(kostMonat)],
        ['Kosten pro Jahr', euro.format(kostJahr)],
        ['Anteil Kontext an Input', num.format(anteilKontext) + ' %'],
        ['Token gesamt pro Monat', num.format(tokenMonat)],
        ['Output-Token pro Anfrage', num.format(antwortTok)],
    ]);
}
            case 'ki-agent-multi-step-kosten-rechner': {
    const aufgaben = Math.max(0, n('aufgabenProTag'));
    const schritte = Math.max(0, Math.round(n('schritte')));
    const basis = Math.max(0, n('basisInput'));
    const tokSchritt = Math.max(0, n('tokenProSchritt'));
    const outSchritt = Math.max(0, n('outputProSchritt'));
    const pIn = n('preisInput'); const pOut = n('preisOutput'); const kurs = n('kurs');
    let inputTokAufgabe = 0;
    for (let i = 1; i <= schritte; i++) {
        inputTokAufgabe += basis + (tokSchritt + outSchritt) * (i - 1);
    }
    inputTokAufgabe += tokSchritt * schritte;
    const outputTokAufgabe = outSchritt * schritte;
    const inKostUsd = inputTokAufgabe / 1e6 * pIn;
    const outKostUsd = outputTokAufgabe / 1e6 * pOut;
    const kostAufgabeEur = (inKostUsd + outKostUsd) * kurs;
    const kostTag = kostAufgabeEur * aufgaben;
    const kostMonat = kostTag * 30;
    const tokenGesamtAufgabe = inputTokAufgabe + outputTokAufgabe;
    const einSchritt = basis + tokSchritt + outSchritt;
    const mehrkostenFaktor = einSchritt ? tokenGesamtAufgabe / einSchritt : 0;
    const kostJahr = kostTag * 365;
    return rows([
        ['Schritte pro Aufgabe', num.format(schritte)],
        ['Input-Token pro Aufgabe', num.format(inputTokAufgabe)],
        ['Output-Token pro Aufgabe', num.format(outputTokAufgabe)],
        ['Token gesamt pro Aufgabe', num.format(tokenGesamtAufgabe)],
        ['Kosten pro Aufgabe', euro.format(kostAufgabeEur)],
        ['Kosten pro Tag', euro.format(kostTag)],
        ['Kosten pro Monat', euro.format(kostMonat)],
        ['Kosten pro Jahr', euro.format(kostJahr)],
        ['Mehrkosten vs. 1-Schritt-Prompt', num.format(mehrkostenFaktor) + 'x'],
    ]);
}
            case 'ki-abo-vs-api-rechner': {
    const nutzungenTag = Math.max(0, n('nutzungenProTag'));
    const inTok = Math.max(0, n('inputTok'));
    const outTok = Math.max(0, n('outputTok'));
    const pIn = n('preisInput'); const pOut = n('preisOutput');
    const abo = Math.max(0, n('aboMonat'));
    const kurs = n('kurs');
    const apiProNutzungUsd = inTok / 1e6 * pIn + outTok / 1e6 * pOut;
    const apiProNutzungEur = apiProNutzungUsd * kurs;
    const nutzungenMonat = nutzungenTag * 30;
    const apiMonat = apiProNutzungEur * nutzungenMonat;
    const guenstiger = apiMonat < abo ? 'API' : 'Abo';
    const ersparnis = Math.abs(apiMonat - abo);
    const breakEvenMonat = apiProNutzungEur ? abo / apiProNutzungEur : 0;
    const breakEvenTag = breakEvenMonat / 30;
    const aboJahr = abo * 12;
    const apiJahr = apiMonat * 12;
    const auslastung = breakEvenMonat ? nutzungenMonat / breakEvenMonat * 100 : 0;
    return rows([
        ['API-Kosten pro Nutzung', euro.format(apiProNutzungEur)],
        ['Nutzungen pro Monat', num.format(nutzungenMonat)],
        ['API-Kosten pro Monat', euro.format(apiMonat)],
        ['Günstigere Option', guenstiger],
        ['Monatliche Ersparnis', euro.format(ersparnis)],
        ['Break-even Nutzungen pro Monat', apiProNutzungEur ? num.format(Math.round(breakEvenMonat)) : 'unbegrenzt'],
        ['Break-even Nutzungen pro Tag', apiProNutzungEur ? num.format(breakEvenTag) : 'unbegrenzt'],
        ['Abo-Kosten pro Jahr', euro.format(aboJahr)],
        ['API-Kosten pro Jahr', euro.format(apiJahr)],
        ['Auslastung vs. Break-even', breakEvenMonat ? num.format(auslastung) + ' %' : '-'],
    ]);
}
            case 'prompt-token-budget-rechner': {
    const budget = Math.max(0, n('budget'));
    const system = Math.max(0, n('system'));
    const beispielTok = Math.max(0, n('beispielToken'));
    const anzahlBsp = Math.max(0, Math.round(n('anzahlBeispiele')));
    const kontext = Math.max(0, n('kontext'));
    const antwort = Math.max(0, n('antwort'));
    const beispieleGesamt = beispielTok * anzahlBsp;
    const belegt = system + beispieleGesamt + kontext + antwort;
    const rest = budget - belegt;
    const status = rest >= 0 ? 'passt' : 'überschritten';
    const ueberschreitung = Math.max(0, -rest);
    const auslastung = budget ? belegt / budget * 100 : 0;
    const maxWeitereBsp = beispielTok ? Math.floor(Math.max(0, rest) / beispielTok) : 0;
    const anteilKontext = budget ? kontext / budget * 100 : 0;
    const anteilBeispiele = budget ? beispieleGesamt / budget * 100 : 0;
    const anteilAntwort = budget ? antwort / budget * 100 : 0;
    const freierKontext0Bsp = budget - system - antwort - kontext;
    return rows([
        ['Beispiele gesamt', num.format(beispieleGesamt) + ' Token'],
        ['Belegt gesamt', num.format(belegt) + ' Token'],
        ['Restbudget', num.format(rest) + ' Token'],
        ['Status', status],
        ['Überschreitung', num.format(ueberschreitung) + ' Token'],
        ['Auslastung', num.format(auslastung) + ' %'],
        ['Max. weitere Beispiele', num.format(maxWeitereBsp)],
        ['Anteil Kontext', num.format(anteilKontext) + ' %'],
        ['Anteil Beispiele', num.format(anteilBeispiele) + ' %'],
        ['Anteil Antwort', num.format(anteilAntwort) + ' %'],
        ['Freier Platz bei 0 Beispielen', num.format(freierKontext0Bsp) + ' Token'],
    ]);
}
            case 'ki-content-skalierung-kosten-rechner': {
    const artikel = Math.max(0, n('artikelProMonat'));
    const woerter = Math.max(0, n('woerterProArtikel'));
    const promptTok = Math.max(0, n('promptToken'));
    const pIn = n('preisInput'); const pOut = n('preisOutput');
    const minLekt = Math.max(0, n('minutenLektorat'));
    const lohn = n('stundenlohn');
    const kurs = n('kurs');
    const outputTok = Math.round(woerter / 0.75);
    const kiUsd = promptTok / 1e6 * pIn + outputTok / 1e6 * pOut;
    const kiEur = kiUsd * kurs;
    const lektEur = minLekt / 60 * lohn;
    const gesamtArtikel = kiEur + lektEur;
    const kostMonat = gesamtArtikel * artikel;
    const lektStundenMonat = minLekt * artikel / 60;
    const anteilKi = gesamtArtikel ? kiEur / gesamtArtikel * 100 : 0;
    const kostJahr = kostMonat * 12;
    const reineKiMonat = kiEur * artikel;
    const woerterMonat = woerter * artikel;
    return rows([
        ['Output-Token pro Artikel', num.format(outputTok)],
        ['KI-Kosten pro Artikel', euro.format(kiEur)],
        ['Lektorat-Kosten pro Artikel', euro.format(lektEur)],
        ['Gesamtkosten pro Artikel', euro.format(gesamtArtikel)],
        ['Kosten pro Monat', euro.format(kostMonat)],
        ['Kosten pro Jahr', euro.format(kostJahr)],
        ['Reine KI-Kosten pro Monat', euro.format(reineKiMonat)],
        ['Lektorat-Stunden pro Monat', num.format(lektStundenMonat) + ' h'],
        ['Anteil KI an Stückkosten', num.format(anteilKi) + ' %'],
        ['Wörter gesamt pro Monat', num.format(woerterMonat)],
    ]);
}
            case 'lokale-ki-vs-cloud-amortisation-rechner': {
    const hw = Math.max(0, n('hardwareKosten'));
    const watt = Math.max(0, n('leistungWatt'));
    const stdTag = Math.max(0, n('stundenProTag'));
    const strom = Math.max(0, n('strompreis'));
    const cloud = Math.max(0, n('cloudKostenMonat'));
    const jahre = Math.max(0, n('nutzungsdauerJahre'));
    const kwhMonat = watt / 1000 * stdTag * 30;
    const stromMonat = kwhMonat * strom / 100;
    const lokalMonat = stromMonat;
    const ersparnisMonat = cloud - lokalMonat;
    const amortMonate = ersparnisMonat > 0 ? hw / ersparnisMonat : 0;
    const gesamtLokal = hw + lokalMonat * 12 * jahre;
    const gesamtCloud = cloud * 12 * jahre;
    const gesamtErsparnis = gesamtCloud - gesamtLokal;
    const guenstiger = gesamtLokal < gesamtCloud ? 'Lokal' : (gesamtLokal > gesamtCloud ? 'Cloud' : 'gleich');
    const effLokal = jahre > 0 ? hw / (jahre * 12) + lokalMonat : lokalMonat;
    return rows([
        ['Stromverbrauch pro Monat', num.format(kwhMonat) + ' kWh'],
        ['Stromkosten pro Monat', euro.format(stromMonat)],
        ['Lokale Kosten pro Monat', euro.format(lokalMonat)],
        ['Ersparnis vs. Cloud pro Monat', euro.format(ersparnisMonat)],
        ['Amortisation der Hardware', amortMonate > 0 ? num.format(amortMonate) + ' Monate' : 'rechnet sich nicht'],
        ['Gesamtkosten lokal (Zeitraum)', euro.format(gesamtLokal)],
        ['Gesamtkosten Cloud (Zeitraum)', euro.format(gesamtCloud)],
        ['Gesamtersparnis (Zeitraum)', euro.format(gesamtErsparnis)],
        ['Günstigere Option', guenstiger],
        ['Effektive lokale Kosten/Monat inkl. Abschreibung', euro.format(effLokal)],
    ]);
}
            case 'batch-verarbeitung-ersparnis-rechner': {
    const auftraege = Math.max(0, n('auftraegeProMonat'));
    const inTok = Math.max(0, n('inputTok'));
    const outTok = Math.max(0, n('outputTok'));
    const pIn = Math.max(0, n('preisInput'));
    const pOut = Math.max(0, n('preisOutput'));
    const rabatt = clamp(n('batchRabatt'), 0, 100);
    const kurs = Math.max(0, n('kurs'));
    const proAuftragUsd = inTok / 1e6 * pIn + outTok / 1e6 * pOut;
    const echtMonatUsd = proAuftragUsd * auftraege;
    const batchMonatUsd = echtMonatUsd * (1 - rabatt / 100);
    const echtEur = echtMonatUsd * kurs;
    const batchEur = batchMonatUsd * kurs;
    const ersparnisMonat = echtEur - batchEur;
    const proAuftragBatchEur = proAuftragUsd * (1 - rabatt / 100) * kurs;
    return rows([
        ['Kosten pro Auftrag (Echtzeit)', euro.format(proAuftragUsd * kurs)],
        ['Echtzeit-Kosten pro Monat', euro.format(echtEur)],
        ['Batch-Kosten pro Monat', euro.format(batchEur)],
        ['Ersparnis pro Monat', euro.format(ersparnisMonat)],
        ['Ersparnis pro Jahr', euro.format(ersparnisMonat * 12)],
        ['Kosten pro Auftrag (Batch)', euro.format(proAuftragBatchEur)],
        ['Token gesamt pro Monat', num.format((inTok + outTok) * auftraege)],
        ['Ersparnis in Prozent', num.format(rabatt) + ' %'],
        ['Aufträge pro Monat', num.format(auftraege)],
        ['Echtzeit-Kosten pro Jahr', euro.format(echtEur * 12)],
    ]);
}
            case 'ki-genauigkeit-kosten-tradeoff-rechner': {
    const anfragen = Math.max(0, n('anfragenProMonat'));
    const kostenA = Math.max(0, n('kostenKlein'));
    const fehlerA = clamp(n('fehlerKlein'), 0, 100);
    const kostenB = Math.max(0, n('kostenGross'));
    const fehlerB = clamp(n('fehlerGross'), 0, 100);
    const fehlerEur = Math.max(0, n('fehlerkostenEur'));
    const aDirekt = anfragen * kostenA / 100;
    const aFehler = anfragen * fehlerA / 100;
    const aFehlerKosten = aFehler * fehlerEur;
    const aGesamt = aDirekt + aFehlerKosten;
    const bDirekt = anfragen * kostenB / 100;
    const bFehler = anfragen * fehlerB / 100;
    const bFehlerKosten = bFehler * fehlerEur;
    const bGesamt = bDirekt + bFehlerKosten;
    const guenstiger = aGesamt < bGesamt ? 'Modell A' : (aGesamt > bGesamt ? 'Modell B' : 'gleichauf');
    const effA = anfragen > 0 ? aGesamt / anfragen : 0;
    const effB = anfragen > 0 ? bGesamt / anfragen : 0;
    return rows([
        ['Modell A: direkte Kosten/Monat', euro.format(aDirekt)],
        ['Modell A: Fehlerkosten/Monat', euro.format(aFehlerKosten)],
        ['Modell A: Gesamtkosten/Monat', euro.format(aGesamt)],
        ['Modell B: direkte Kosten/Monat', euro.format(bDirekt)],
        ['Modell B: Fehlerkosten/Monat', euro.format(bFehlerKosten)],
        ['Modell B: Gesamtkosten/Monat', euro.format(bGesamt)],
        ['Günstigeres Modell', guenstiger],
        ['Ersparnis pro Monat', euro.format(Math.abs(aGesamt - bGesamt))],
        ['Effektive Kosten/Anfrage Modell A', euro.format(effA)],
        ['Effektive Kosten/Anfrage Modell B', euro.format(effB)],
    ]);
}
            case 'ki-bild-aufloesung-speicher-rechner': {
    const breite = Math.max(0, n('breite'));
    const hoehe = Math.max(0, n('hoehe'));
    const fmt = value('format');
    const anzahl = Math.max(0, n('anzahl'));
    const cloudGb = Math.max(0, n('cloudPreisGb'));
    let bpp = 3.0;
    if (fmt.indexOf('JPEG') === 0 || fmt.indexOf('JPEG') > -1) bpp = 0.5;
    if (fmt.indexOf('WebP') > -1) bpp = 0.35;
    if (fmt.indexOf('PNG') > -1) bpp = 3.0;
    const mp = breite * hoehe / 1e6;
    const mbBild = breite * hoehe * bpp / 1e6;
    const mbMonat = mbBild * anzahl;
    const gbMonat = mbMonat / 1024;
    const gbJahr = gbMonat * 12;
    const cloudMonat = gbMonat * cloudGb;
    const g = gcd(Math.round(breite), Math.round(hoehe)) || 1;
    const ratio = (breite > 0 && hoehe > 0) ? (Math.round(breite) / g) + ':' + (Math.round(hoehe) / g) : '-';
    const klasse = mp < 2 ? 'SD' : (mp < 9 ? 'HD/2K' : (mp < 35 ? '4K' : '8K+'));
    return rows([
        ['Megapixel', num.format(mp) + ' MP'],
        ['Dateigröße pro Bild', num.format(mbBild) + ' MB'],
        ['Speicher pro Monat', num.format(mbMonat) + ' MB'],
        ['Speicher pro Monat', num.format(gbMonat) + ' GB'],
        ['Speicher pro Jahr', num.format(gbJahr) + ' GB'],
        ['Cloud-Kosten pro Monat', euro.format(cloudMonat)],
        ['Cloud-Kosten pro Jahr', euro.format(gbJahr * cloudGb)],
        ['Seitenverhältnis', ratio],
        ['Auflösungsklasse', klasse],
        ['Bilder gesamt pro Jahr', num.format(anzahl * 12)],
    ]);
}
            case 'cloud-egress-traffic-kosten-rechner': {
    const egress = Math.max(0, n('egress_gb'));
    const awsFree = value('aws_freebie');
    const hetznerInkl = Math.max(0, n('hetzner_inkl'));
    const pAws = 0.09, pGcp = 0.085, pAzure = 0.087, pHetzner = 0.001;
    const billableAws = awsFree === 'ja' ? Math.max(0, egress - 100) : egress;
    const kAws = billableAws * pAws;
    const kGcp = egress * pGcp;
    const kAzure = egress * pAzure;
    const kHetzner = Math.max(0, egress - hetznerInkl) * pHetzner;
    const liste = [['AWS', kAws], ['Google Cloud', kGcp], ['Azure', kAzure], ['Hetzner', kHetzner]];
    let min = liste[0], max = liste[0];
    liste.forEach(e => { if (e[1] < min[1]) min = e; if (e[1] > max[1]) max = e; });
    const ersparnisVsAws = kAws - min[1];
    const effAws = egress > 0 ? kAws / egress : 0;
    const anteil = kAws > 0 ? min[1] / kAws * 100 : 0;
    return rows([
        ['AWS-Kosten pro Monat', euro.format(kAws)],
        ['Google-Cloud-Kosten pro Monat', euro.format(kGcp)],
        ['Azure-Kosten pro Monat', euro.format(kAzure)],
        ['Hetzner-Kosten pro Monat', euro.format(kHetzner)],
        ['Günstigster Anbieter', min[0] + ' (' + euro.format(min[1]) + ')'],
        ['Teuerster Anbieter', max[0] + ' (' + euro.format(max[1]) + ')'],
        ['Ersparnis günstigster vs. AWS/Monat', euro.format(ersparnisVsAws)],
        ['Ersparnis pro Jahr', euro.format(ersparnisVsAws * 12)],
        ['AWS-Kosten pro Jahr', euro.format(kAws * 12)],
        ['Effektiver Preis je GB (AWS)', euro.format(effAws)],
        ['Anteil günstigster an AWS', num.format(anteil) + ' %'],
    ]);
}
            case 'datenuebertragungszeit-rechner': {
    const groesse = Math.max(0, n('groesse'));
    const einheit = value('einheit');
    const bandbreite = Math.max(0, n('bandbreite'));
    const overhead = clamp(n('overhead'), 0, 99);
    const faktor = einheit === 'GB' ? 1024 : (einheit === 'TB' ? 1048576 : 1);
    const groesseMb = groesse * faktor;
    const groesseMbit = groesseMb * 8;
    const effektivMbit = bandbreite * (1 - overhead / 100);
    const zeit = effektivMbit > 0 ? groesseMbit / effektivMbit : 0;
    const zeitTheorie = bandbreite > 0 ? groesseMbit / bandbreite : 0;
    const verlust = zeit - zeitTheorie;
    const zeitDoppelt = effektivMbit > 0 ? groesseMbit / (effektivMbit * 2) : 0;
    const durchsatzGbH = effektivMbit / 8 / 1024 * 3600;
    return rows([
        ['Datenmenge in Megabit', num.format(groesseMbit) + ' Mbit'],
        ['Effektive Bandbreite', num.format(effektivMbit) + ' Mbit/s'],
        ['Effektive Geschwindigkeit', num.format(effektivMbit / 8) + ' MB/s'],
        ['Übertragungszeit', duration(zeit)],
        ['Übertragungszeit in Sekunden', num.format(zeit) + ' s'],
        ['Theoretische Zeit ohne Overhead', duration(zeitTheorie)],
        ['Zeitverlust durch Overhead', duration(Math.max(0, verlust))],
        ['Datenmenge in GB', num.format(groesseMb / 1024) + ' GB'],
        ['Zeit bei doppelter Bandbreite', duration(zeitDoppelt)],
        ['Durchsatz pro Stunde', num.format(durchsatzGbH) + ' GB/h'],
    ]);
}
            case 'self-hosting-vs-saas-kosten-rechner': {
    const serverMonat = Math.max(0, n('server_monat'));
    const wartungH = Math.max(0, n('wartung_h'));
    const stundensatz = Math.max(0, n('stundensatz'));
    const saasMonat = Math.max(0, n('saas_monat'));
    const jahre = Math.max(0, n('jahre'));
    const arbeitMonat = wartungH * stundensatz;
    const selfMonat = serverMonat + arbeitMonat;
    const selfJahr = selfMonat * 12;
    const saasJahr = saasMonat * 12;
    const selfZeitraum = selfJahr * jahre;
    const saasZeitraum = saasJahr * jahre;
    const diff = saasZeitraum - selfZeitraum;
    const guenstiger = selfMonat < saasMonat ? 'Self-Hosting' : (selfMonat > saasMonat ? 'SaaS' : 'gleich');
    const ersparnisMonat = Math.abs(saasMonat - selfMonat);
    const arbeitAnteil = selfMonat > 0 ? arbeitMonat / selfMonat * 100 : 0;
    return rows([
        ['Self-Hosting Arbeitskosten/Monat', euro.format(arbeitMonat)],
        ['Self-Hosting gesamt/Monat', euro.format(selfMonat)],
        ['Self-Hosting gesamt/Jahr', euro.format(selfJahr)],
        ['SaaS gesamt/Jahr', euro.format(saasJahr)],
        ['Self-Hosting über Zeitraum', euro.format(selfZeitraum)],
        ['SaaS über Zeitraum', euro.format(saasZeitraum)],
        ['Differenz über Zeitraum', euro.format(diff)],
        ['Günstigere Option', guenstiger],
        ['Monatliche Ersparnis', euro.format(ersparnisMonat)],
        ['Arbeitskosten-Anteil an Self-Hosting', num.format(arbeitAnteil) + ' %'],
    ]);
}
            case 'container-ram-sizing-rechner': {
    const anzahl = Math.max(0, n('anzahl'));
    const ramContainer = Math.max(0, n('ram_container'));
    const osReserve = Math.max(0, n('os_reserve'));
    const puffer = clamp(n('puffer'), 0, 1000);
    const ramGesamt = anzahl * ramContainer;
    const zwischen = ramGesamt + osReserve;
    const pufferMb = zwischen * puffer / 100;
    const empfohlenMb = zwischen + pufferMb;
    const empfohlenGb = empfohlenMb / 1024;
    const stufen = [1, 2, 4, 8, 16, 32, 64, 128];
    let serverGb = stufen[stufen.length - 1];
    for (let i = 0; i < stufen.length; i++) { if (stufen[i] * 1024 >= empfohlenMb) { serverGb = stufen[i]; break; } }
    const serverMb = serverGb * 1024;
    const auslastung = serverMb > 0 ? empfohlenMb / serverMb * 100 : 0;
    const freierRam = serverMb - empfohlenMb;
    const maxContainer = ramContainer > 0 ? Math.floor((serverMb - osReserve) / ramContainer) : 0;
    const osAnteil = empfohlenMb > 0 ? osReserve / empfohlenMb * 100 : 0;
    return rows([
        ['RAM Container gesamt', num.format(ramGesamt) + ' MB'],
        ['Zwischensumme inkl. OS', num.format(zwischen) + ' MB'],
        ['Puffer-Aufschlag', num.format(pufferMb) + ' MB'],
        ['Empfohlener RAM', num.format(empfohlenMb) + ' MB'],
        ['Empfohlener RAM', num.format(empfohlenGb) + ' GB'],
        ['Nächste Servergröße', num.format(serverGb) + ' GB'],
        ['Auslastung bei dieser Größe', num.format(auslastung) + ' %'],
        ['Freier RAM bei dieser Größe', num.format(Math.max(0, freierRam)) + ' MB'],
        ['Maximale Container bei dieser Größe', num.format(Math.max(0, maxContainer))],
        ['RAM-Anteil OS', num.format(osAnteil) + ' %'],
    ]);
}
            case 'javascript-bundle-budget-rechner': {
    const bundle = Math.max(0, n('bundle_kb'));
    const gzip = clamp(n('gzip_quote'), 0, 99);
    const verb = value('verbindung');
    let speed = 1750;
    if (verb.indexOf('langsam') > -1) speed = 875;
    else if (verb.indexOf('3G') > -1) speed = 400;
    else speed = 1750;
    const budgetKb = 170;
    const komprimiert = bundle * (1 - gzip / 100);
    const download = speed > 0 ? komprimiert / speed : 0;
    const parse = bundle * 1 / 1000;
    const gesamt = download + parse;
    const status = komprimiert <= budgetKb ? 'im Budget' : 'über Budget';
    const ueber = Math.max(0, komprimiert - budgetKb);
    const download3g = komprimiert / 400;
    const maxFuer3s = speed * 3;
    const einsparung = bundle - komprimiert;
    const auslastung = budgetKb > 0 ? komprimiert / budgetKb * 100 : 0;
    return rows([
        ['Komprimierte Größe', num.format(komprimiert) + ' KB'],
        ['Download-Zeit', num.format(download) + ' s'],
        ['Parse-/Compile-Zeit', num.format(parse) + ' s'],
        ['Gesamt-Verarbeitungszeit', num.format(gesamt) + ' s'],
        ['Budget-Status', status],
        ['Überschreitung', num.format(ueber) + ' KB'],
        ['Komprimierte Größe', num.format(komprimiert / 1024) + ' MB'],
        ['Ladezeit bei 3G', num.format(download3g) + ' s'],
        ['Empf. max. Größe für 3 s Download', num.format(maxFuer3s) + ' KB'],
        ['Einsparung durch Kompression', num.format(einsparung) + ' KB'],
        ['Budget-Auslastung', num.format(auslastung) + ' %'],
    ]);
}
            case 'core-web-vitals-budget-rechner': {
  const lcp=n('lcp'), inp=n('inp'), cls=n('cls');
  const lcpStatus = lcp<=2.5 ? 'gut' : lcp>4.0 ? 'schlecht' : 'verbesserungswürdig';
  const inpStatus = inp<=200 ? 'gut' : inp>500 ? 'schlecht' : 'verbesserungswürdig';
  const clsStatus = cls<=0.1 ? 'gut' : cls>0.25 ? 'schlecht' : 'verbesserungswürdig';
  const scoreOf = (s)=> s==='gut'?100 : s==='schlecht'?0 : 50;
  const score = (scoreOf(lcpStatus)+scoreOf(inpStatus)+scoreOf(clsStatus))/3;
  const gruen = [lcpStatus,inpStatus,clsStatus].filter(s=>s==='gut').length;
  const anySchlecht = [lcpStatus,inpStatus,clsStatus].some(s=>s==='schlecht');
  const gesamt = gruen===3 ? 'Bestanden' : anySchlecht ? 'Nicht bestanden' : 'Grenzwertig';
  const relLcp = lcp/2.5, relInp = inp/200, relCls = cls/0.1;
  let schwaechste='LCP', maxRel=relLcp;
  if(relInp>maxRel){maxRel=relInp; schwaechste='INP';}
  if(relCls>maxRel){maxRel=relCls; schwaechste='CLS';}
  return rows([
    ['LCP-Status', num.format(lcp)+' s – '+lcpStatus],
    ['INP-Status', num.format(inp)+' ms – '+inpStatus],
    ['CLS-Status', num.format(cls)+' – '+clsStatus],
    ['LCP-Spielraum bis Grenze (2,5 s)', num.format(2.5-lcp)+' s'],
    ['INP-Spielraum bis Grenze (200 ms)', num.format(200-inp)+' ms'],
    ['CLS-Spielraum bis Grenze (0,1)', num.format(0.1-cls)],
    ['Metriken im grünen Bereich', gruen+' von 3'],
    ['Gesamt-Status', gesamt],
    ['Web-Vitals-Score', num.format(score)+' von 100'],
    ['Schwächste Metrik', schwaechste],
    ['Hinweis', 'Alle drei Metriken müssen mindestens „gut“ sein'],
  ]);
}
            case 'backup-321-speicher-rechner': {
  const daten=n('daten_gb'), versionen=n('versionen'), aenderung=n('aenderung'), wachstum=n('wachstum');
  const versSpeicher = daten + daten*aenderung/100*Math.max(0,versionen-1);
  const gesamtHeute = 3*versSpeicher;
  const datenJahr = daten*(1+wachstum/100);
  const gesamtJahr = 3*(datenJahr + datenJahr*aenderung/100*Math.max(0,versionen-1));
  const reserve = versSpeicher*1.3;
  const faktor = daten>0 ? gesamtHeute/daten : 0;
  return rows([
    ['Vollbackup (Rohdaten)', num.format(daten)+' GB'],
    ['Speicher je Kopie inkl. Versionen', num.format(versSpeicher)+' GB'],
    ['Lokale Kopie 1', num.format(versSpeicher)+' GB'],
    ['Lokale Kopie 2', num.format(versSpeicher)+' GB'],
    ['Offsite-Kopie', num.format(versSpeicher)+' GB'],
    ['Gesamtspeicher 3-2-1 heute', num.format(gesamtHeute)+' GB'],
    ['Gesamtspeicher in TB', num.format(gesamtHeute/1024)+' TB'],
    ['Datenvolumen in 1 Jahr', num.format(datenJahr)+' GB'],
    ['Gesamtspeicher 3-2-1 in 1 Jahr', num.format(gesamtJahr)+' GB'],
    ['Empfohlen je Standort (30 % Reserve)', num.format(reserve)+' GB'],
    ['Faktor gegenüber Rohdaten', num.format(faktor)+'x'],
  ]);
}
            case 'api-durchsatz-kapazitaet-rechner': {
  const zielRps=n('ziel_rps'), antwortzeit=n('antwortzeit'), worker=n('worker'), zielAusl=n('ziel_auslastung');
  const antwortS = antwortzeit/1000;
  const kapProWorker = antwortS>0 ? 1/antwortS : 0;
  const maxDurchsatz = worker*kapProWorker;
  const noetigeWorker = zielRps*antwortS;
  const noetigeWorkerAuf = Math.ceil(noetigeWorker);
  const auslFaktor = zielAusl>0 ? zielAusl/100 : 0;
  const workerInklAusl = auslFaktor>0 ? Math.ceil(noetigeWorker/auslFaktor) : 0;
  const aktAuslastung = maxDurchsatz>0 ? zielRps/maxDurchsatz*100 : 0;
  const status = (zielAusl>0 && aktAuslastung<=zielAusl) ? 'ausreichend' : 'überlastet';
  const reserve = maxDurchsatz-zielRps;
  const maxRpsAusl = maxDurchsatz*zielAusl/100;
  const reqProTag = zielRps*86400;
  const fehlendeWorker = Math.max(0, workerInklAusl-worker);
  return rows([
    ['Kapazität pro Worker', num.format(kapProWorker)+' RPS'],
    ['Maximaler Durchsatz aller Worker', num.format(maxDurchsatz)+' RPS'],
    ['Rechnerisch nötige Worker', num.format(noetigeWorker)],
    ['Nötige Worker (aufgerundet)', noetigeWorkerAuf+' Stück'],
    ['Worker inkl. Ziel-Auslastung', workerInklAusl+' Stück'],
    ['Aktuelle Auslastung', num.format(aktAuslastung)+' %'],
    ['Status', status],
    ['Reserve-Durchsatz', num.format(reserve)+' RPS'],
    ['Max. RPS bei Ziel-Auslastung', num.format(maxRpsAusl)+' RPS'],
    ['Requests pro Tag bei Ziel-RPS', num.format(reqProTag)+' Stück'],
    ['Fehlende Worker', fehlendeWorker+' Stück'],
  ]);
}
            case 'vps-preis-leistung-rechner': {
  const pa=n('preis_a'), ca=n('cpu_a'), ra=n('ram_a'), da=n('disk_a');
  const pb=n('preis_b'), cb=n('cpu_b'), rb=n('ram_b'), db=n('disk_b');
  const aCpu = ca>0 ? pa/ca : 0;
  const aRam = ra>0 ? pa/ra : 0;
  const aDisk = da>0 ? pa/da : 0;
  const bCpu = cb>0 ? pb/cb : 0;
  const bRam = rb>0 ? pb/rb : 0;
  const bDisk = db>0 ? pb/db : 0;
  const aPunkte = ca*2 + ra*1 + da*0.05;
  const bPunkte = cb*2 + rb*1 + db*0.05;
  const aProPunkt = aPunkte>0 ? pa/aPunkte : 0;
  const bProPunkt = bPunkte>0 ? pb/bPunkte : 0;
  let besser='unentschieden';
  if(aProPunkt>0 && bProPunkt>0) besser = aProPunkt<bProPunkt ? 'Server A' : aProPunkt>bProPunkt ? 'Server B' : 'gleichwertig';
  else if(aProPunkt>0) besser='Server A';
  else if(bProPunkt>0) besser='Server B';
  const maxPP = Math.max(aProPunkt,bProPunkt);
  const diffProz = maxPP>0 ? Math.abs(aProPunkt-bProPunkt)/maxPP*100 : 0;
  return rows([
    ['Server A: Preis pro vCPU', euro.format(aCpu)],
    ['Server A: Preis pro GB RAM', euro.format(aRam)],
    ['Server A: Preis pro GB Speicher', euro.format(aDisk)],
    ['Server B: Preis pro vCPU', euro.format(bCpu)],
    ['Server B: Preis pro GB RAM', euro.format(bRam)],
    ['Server B: Preis pro GB Speicher', euro.format(bDisk)],
    ['Server A: Preis pro Leistungspunkt', euro.format(aProPunkt)],
    ['Server B: Preis pro Leistungspunkt', euro.format(bProPunkt)],
    ['Leistungspunkte A / B', num.format(aPunkte)+' / '+num.format(bPunkte)],
    ['Besseres Preis-Leistungs-Angebot', besser],
    ['Preisvorteil', num.format(diffProz)+' %'],
  ]);
}
            case 'einkommensteuer-2026-rechner': {
    const zve = Math.max(0, n('zve'));
    const splitting = value('veranlagung').includes('Splitting');
    const ESt = x => {
        x = Math.max(0, Math.floor(x));
        let st;
        if (x <= 12348) st = 0;
        else if (x <= 17799) { const y = (x - 12348) / 10000; st = (932.3 * y + 1400) * y; }
        else if (x <= 69878) { const z = (x - 17799) / 10000; st = (176.64 * z + 2397) * z + 1015.13; }
        else if (x <= 277825) st = 0.42 * x - 10911.92;
        else st = 0.45 * x - 19246.67;
        return Math.floor(Math.max(0, st));
    };
    const base = splitting ? zve / 2 : zve;
    const steuer = splitting ? 2 * ESt(zve / 2) : ESt(zve);
    const grundfreibetrag = splitting ? 24696 : 12348;
    const durchschnitt = zve ? steuer / zve * 100 : 0;
    const grenz = (ESt(base + 1) - ESt(base)) * 100;
    const netto = zve - steuer;
    const freiAnteil = zve ? Math.min(100, grundfreibetrag / zve * 100) : 100;
    const steuerVorjahrGF = splitting ? 2 * ESt(12096) : ESt(12096);
    const entlastung = steuerVorjahrGF - steuer;
    const progressionsreserve = Math.max(0, 69878 - base);
    return rows([
        ['Tarifliche Einkommensteuer', euro.format(steuer)],
        ['Genutzter Grundfreibetrag', euro.format(grundfreibetrag)],
        ['Durchschnittssteuersatz', num.format(durchschnitt) + ' %'],
        ['Grenzsteuersatz', num.format(grenz) + ' %'],
        ['Netto-Jahreseinkommen nach ESt', euro.format(netto)],
        ['Steuerfreier Anteil', num.format(freiAnteil) + ' %'],
        ['Monatliche Steuerlast', euro.format(steuer / 12)],
        ['Steuer auf die letzten 1.000 €', euro.format(grenz / 100 * 1000)],
        ['Entlastung ggü. Vorjahres-Grundfreibetrag', euro.format(entlastung)],
        ['Reserve bis zum 42-%-Eckwert', euro.format(progressionsreserve)],
    ]);
}
            case 'brutto-netto-rechner-2026': {
    const brutto = Math.max(0, n('brutto'));
    const stkl = value('stkl');
    const kircheSel = value('kirche');
    const kinder = Math.max(0, n('kinder'));
    const kSatz = kircheSel.includes('8') ? 0.08 : (kircheSel.includes('9') ? 0.09 : 0);
    const ESt = x => {
        x = Math.max(0, Math.floor(x));
        let st;
        if (x <= 12348) st = 0;
        else if (x <= 17799) { const y = (x - 12348) / 10000; st = (932.3 * y + 1400) * y; }
        else if (x <= 69878) { const z = (x - 17799) / 10000; st = (176.64 * z + 2397) * z + 1015.13; }
        else if (x <= 277825) st = 0.42 * x - 10911.92;
        else st = 0.45 * x - 19246.67;
        return Math.floor(Math.max(0, st));
    };
    const JB = brutto * 12;
    const vorsorge = Math.min(0.12 * JB, 4000);
    const zvE = Math.max(0, JB - 1230 - vorsorge);
    let lstJahr;
    if (stkl === 'III') lstJahr = 2 * ESt(zvE / 2);
    else if (stkl === 'V' || stkl === 'VI') lstJahr = ESt(zvE) * 1.25;
    else lstJahr = ESt(zvE);
    const lst = lstJahr / 12;
    const soliFreigrenze = stkl === 'III' ? 39900 : 19950;
    const soli = lstJahr > soliFreigrenze ? lst * 0.055 : 0;
    const kist = lst * kSatz;
    const rv = Math.min(brutto, 8050) * 0.093;
    const alv = Math.min(brutto, 8050) * 0.013;
    const kv = Math.min(brutto, 5512.5) * 0.0865;
    const pvSatz = kinder === 0 ? 0.024 : 0.018;
    const pv = Math.min(brutto, 5512.5) * pvSatz;
    const abzuege = lst + soli + kist + rv + alv + kv + pv;
    const netto = brutto - abzuege;
    const nettoquote = brutto ? netto / brutto * 100 : 0;
    return rows([
        ['Lohnsteuer / Monat', euro.format(lst)],
        ['Solidaritätszuschlag / Monat', euro.format(soli)],
        ['Kirchensteuer / Monat', euro.format(kist)],
        ['Rentenversicherung (9,3 %)', euro.format(rv)],
        ['Arbeitslosenversicherung (1,3 %)', euro.format(alv)],
        ['Krankenversicherung (8,65 %)', euro.format(kv)],
        ['Pflegeversicherung (' + num.format(pvSatz * 100) + ' %)', euro.format(pv)],
        ['Summe Abzüge / Monat', euro.format(abzuege)],
        ['Nettogehalt / Monat', euro.format(netto)],
        ['Nettoquote', num.format(nettoquote) + ' %'],
        ['Nettogehalt / Jahr', euro.format(netto * 12)],
    ]);
}
            case 'soli-rechner-2026': {
    const est = Math.max(0, n('est'));
    const zusammen = value('veranlagung').includes('Zusammen');
    const FG = zusammen ? 39900 : 19950;
    const pflichtig = est > FG;
    const vollerSoli = est * 0.055;
    const milderung = 0.119 * (est - FG);
    const soli = est <= FG ? 0 : Math.min(milderung, vollerSoli);
    const effSatz = est ? soli / est * 100 : 0;
    let zone;
    if (est <= FG) zone = 'Soli-frei';
    else if (milderung < vollerSoli) zone = 'Milderungszone';
    else zone = 'Voller Satz';
    const abstand = est - FG;
    return rows([
        ['Maßgebliche Einkommensteuer', euro.format(est)],
        ['Anzuwendende Freigrenze', euro.format(FG)],
        ['Soli-pflichtig?', pflichtig ? 'ja' : 'nein'],
        ['Voller Soli (5,5 %)', euro.format(vollerSoli)],
        ['Milderungszonen-Soli (11,9 %)', euro.format(Math.max(0, milderung))],
        ['Tatsächlicher Solidaritätszuschlag', euro.format(soli)],
        ['Effektiver Soli-Satz', num.format(effSatz) + ' %'],
        ['Soli-Zone', zone],
        ['Abstand zur Freigrenze', euro.format(abstand)],
        ['Solidaritätszuschlag / Monat', euro.format(soli / 12)],
    ]);
}
            case 'kapitalertragsteuer-2026-rechner': {
    const ertrag = Math.max(0, n('ertrag'));
    const status = value('status');
    const kircheSel = value('kirche');
    const PB = status.includes('2.000') ? 2000 : 1000;
    const k = kircheSel.includes('8') ? 0.08 : (kircheSel.includes('9') ? 0.09 : 0);
    const stpfl = Math.max(0, ertrag - PB);
    const ohneKiSt = stpfl * 0.25;
    const mitKiSt = stpfl * 0.25 / (1 + 0.25 * k);
    const abgeltung = k === 0 ? ohneKiSt : mitKiSt;
    const soli = abgeltung * 0.055;
    const kist = abgeltung * k;
    const gesamt = abgeltung + soli + kist;
    const effSatz = ertrag ? gesamt / ertrag * 100 : 0;
    const netto = ertrag - gesamt;
    const nichtGenutzt = Math.max(0, PB - ertrag);
    return rows([
        ['Sparerpauschbetrag', euro.format(PB)],
        ['Steuerpflichtiger Ertrag', euro.format(stpfl)],
        ['Abgeltungsteuer (25 %)', euro.format(abgeltung)],
        ['Solidaritätszuschlag (5,5 %)', euro.format(soli)],
        ['Kirchensteuer', euro.format(kist)],
        ['Gesamte Steuerlast', euro.format(gesamt)],
        ['Effektiver Steuersatz', num.format(effSatz) + ' %'],
        ['Netto-Kapitalertrag', euro.format(netto)],
        ['Nicht genutzter Sparerpauschbetrag', euro.format(nichtGenutzt)],
    ]);
}
            case 'kirchensteuer-rechner-2026': {
    const steuer = Math.max(0, n('steuer'));
    const satzSel = value('satz');
    const kinderfb = Math.max(0, n('kinderfb'));
    const k = satzSel.includes('8') ? 0.08 : 0.09;
    const bemessung = Math.max(0, steuer - kinderfb * 1200);
    const kistJahr = bemessung * k;
    const kistMonat = kistJahr / 12;
    const anteil = steuer ? kistJahr / steuer * 100 : 0;
    const gesamt = steuer + kistJahr;
    const rueckfluss = kistJahr * 0.30;
    const effektiv = kistJahr - rueckfluss;
    return rows([
        ['Angewendeter Kirchensteuersatz', num.format(k * 100) + ' %'],
        ['Bemessungsgrundlage', euro.format(bemessung)],
        ['Kirchensteuer / Jahr', euro.format(kistJahr)],
        ['Kirchensteuer / Monat', euro.format(kistMonat)],
        ['Anteil an der Steuerlast', num.format(anteil) + ' %'],
        ['Steuer + Kirchensteuer gesamt', euro.format(gesamt)],
        ['Sonderausgaben-Rückfluss (30 %)', euro.format(rueckfluss)],
        ['Effektive Kirchensteuer nach Sonderausgaben', euro.format(effektiv)],
        ['Ersparnis bei Kirchenaustritt / Jahr', euro.format(kistJahr)],
        ['Ersparnis über 10 Jahre', euro.format(kistJahr * 10)],
    ]);
}
            case 'homeoffice-pauschale-rechner-2026': {
    const tage = Math.max(0, n('tage'));
    const grenz = Math.max(0, n('grenzsteuer'));
    const andereWk = Math.max(0, n('andere_wk'));
    const anrechenbar = Math.min(tage, 210);
    const pauschale = Math.min(anrechenbar * 6, 1260);
    const nichtAnrechenbar = Math.max(0, tage - 210);
    const wkGesamt = pauschale + andereWk;
    const ueberPauschbetrag = Math.max(0, wkGesamt - 1230);
    const ersparnis = ueberPauschbetrag * grenz / 100;
    const ersparnisSoli = ersparnis * 1.055;
    const nutzbarerAnteil = wkGesamt <= 1230 ? 0 : Math.min(pauschale, wkGesamt - 1230);
    const wertJeTag = ersparnis / Math.max(1, anrechenbar);
    return rows([
        ['Anrechenbare Homeoffice-Tage', num.format(anrechenbar)],
        ['Homeoffice-Pauschale', euro.format(pauschale)],
        ['Nicht anrechenbare Tage', num.format(nichtAnrechenbar)],
        ['Werbungskosten gesamt', euro.format(wkGesamt)],
        ['Über AN-Pauschbetrag (1.230 €) hinaus', euro.format(ueberPauschbetrag)],
        ['Steuerersparnis', euro.format(ersparnis)],
        ['Steuerersparnis inkl. Soli', euro.format(ersparnisSoli)],
        ['Tatsächlich nutzbarer Pauschale-Anteil', euro.format(nutzbarerAnteil)],
        ['Effektiver Wert je Homeoffice-Tag', euro.format(wertJeTag)],
    ]);
}
            case 'pendlerpauschale-rechner-2026': {
    const km = Math.max(0, n('km'));
    const tage = Math.max(0, n('tage'));
    const grenz = Math.max(0, n('grenzsteuer'));
    const km1 = Math.min(km, 20);
    const km2 = Math.max(0, km - 20);
    const proTag = km1 * 0.30 + km2 * 0.38;
    const jahr = proTag * tage;
    const anteil20 = km1 * 0.30 * tage;
    const anteilAb21 = km2 * 0.38 * tage;
    const ueber = Math.max(0, jahr - 1230);
    const ersparnis = ueber * grenz / 100;
    const ersparnisSoli = ersparnis * 1.055;
    const schnittJeKm = jahr / Math.max(1, km) / Math.max(1, tage);
    const ueberHoechst = Math.max(0, jahr - 4500);
    return rows([
        ['Kilometer in der 1. Stufe (bis 20)', num.format(km1)],
        ['Kilometer ab dem 21. km', num.format(km2)],
        ['Pauschale je Arbeitstag', euro.format(proTag)],
        ['Entfernungspauschale / Jahr', euro.format(jahr)],
        ['Anteil erste 20 km', euro.format(anteil20)],
        ['Anteil ab 21. km', euro.format(anteilAb21)],
        ['Über AN-Pauschbetrag (1.230 €) hinaus', euro.format(ueber)],
        ['Steuerersparnis', euro.format(ersparnis)],
        ['Steuerersparnis inkl. Soli', euro.format(ersparnisSoli)],
        ['Durchschnitt je km und Tag', euro.format(schnittJeKm)],
        ['Betrag über 4.500-€-Höchstbetrag', euro.format(ueberHoechst)],
    ]);
}
            case 'werbungskosten-rechner-2026': {
    const pendler = Math.max(0, n('pendler'));
    const homeoffice = Math.max(0, n('homeoffice'));
    const arbeitsmittel = Math.max(0, n('arbeitsmittel'));
    const fortbildung = Math.max(0, n('fortbildung'));
    const grenz = Math.max(0, n('grenzsteuer'));
    const summe = pendler + homeoffice + arbeitsmittel + fortbildung;
    const ueberschritten = summe > 1230;
    const mehrbetrag = Math.max(0, summe - 1230);
    const anzusetzen = Math.max(summe, 1230);
    const ersparnis = mehrbetrag * grenz / 100;
    const ersparnisSoli = ersparnis * 1.055;
    const anteilPendler = summe ? pendler / summe * 100 : 0;
    const fehlbetrag = Math.max(0, 1230 - summe);
    return rows([
        ['Summe Werbungskosten', euro.format(summe)],
        ['Arbeitnehmer-Pauschbetrag', euro.format(1230)],
        ['Pauschbetrag überschritten?', ueberschritten ? 'ja' : 'nein'],
        ['Steuerwirksamer Mehrbetrag', euro.format(mehrbetrag)],
        ['Anzusetzende Werbungskosten', euro.format(anzusetzen)],
        ['Steuerersparnis', euro.format(ersparnis)],
        ['Steuerersparnis inkl. Soli', euro.format(ersparnisSoli)],
        ['Anteil Pendlerpauschale', num.format(anteilPendler) + ' %'],
        ['Fehlbetrag bis zum Pauschbetrag', euro.format(fehlbetrag)],
        ['Nutzen je 100 € über dem Pauschbetrag', euro.format(grenz)],
    ]);
}
            case 'steuerklassen-wechsel-rechner-2026': {
    const bruttoA = Math.max(0, n('brutto_a'));
    const bruttoB = Math.max(0, n('brutto_b'));
    const ESt = x => {
        x = Math.max(0, Math.floor(x));
        let st;
        if (x <= 12348) st = 0;
        else if (x <= 17799) { const y = (x - 12348) / 10000; st = (932.3 * y + 1400) * y; }
        else if (x <= 69878) { const z = (x - 17799) / 10000; st = (176.64 * z + 2397) * z + 1015.13; }
        else if (x <= 277825) st = 0.42 * x - 10911.92;
        else st = 0.45 * x - 19246.67;
        return Math.floor(Math.max(0, st));
    };
    const zvE = b => Math.max(0, b - 1230 - Math.min(0.12 * b, 4000));
    const zveA = zvE(bruttoA);
    const zveB = zvE(bruttoB);
    const summeIVIV = ESt(zveA) + ESt(zveB);
    const hoch = Math.max(zveA, zveB);
    const niedrig = Math.min(zveA, zveB);
    const lstIII = 2 * ESt(hoch / 2);
    const lstV = Math.max(0, ESt(niedrig + 12348) - ESt(12348));
    const summeIIIV = lstIII + lstV;
    const guenstiger = Math.min(summeIVIV, summeIIIV);
    const liquiVorteil = Math.abs(summeIVIV - summeIIIV) / 12;
    const verhaeltnis = Math.max(bruttoA, bruttoB) / Math.max(1, Math.min(bruttoA, bruttoB));
    const empfehlung = verhaeltnis > 1.5 ? 'III/V' : 'IV/IV';
    return rows([
        ['zvE Partner A', euro.format(zveA)],
        ['zvE Partner B', euro.format(zveB)],
        ['Lohnsteuer IV/IV gesamt / Jahr', euro.format(summeIVIV)],
        ['Lohnsteuer III/V gesamt / Jahr', euro.format(summeIIIV)],
        ['Günstigere Jahres-Lohnsteuer', euro.format(guenstiger)],
        ['Monatliche Lohnsteuer IV/IV', euro.format(summeIVIV / 12)],
        ['Monatliche Lohnsteuer III/V', euro.format(summeIIIV / 12)],
        ['Monatlicher Liquiditätsvorteil', euro.format(liquiVorteil)],
        ['Empfehlung', empfehlung],
        ['Hinweis', 'Jahressteuer identisch, Differenz ist Liquiditätseffekt'],
    ]);
}
            case 'lohnsteuer-jahresausgleich-schaetzer-2026': {
    const brutto = Math.max(0, n('brutto'));
    const lstGezahlt = Math.max(0, n('lst_gezahlt'));
    const werbungskosten = Math.max(0, n('werbungskosten'));
    const sonderausgaben = Math.max(0, n('sonderausgaben'));
    const ESt = x => {
        x = Math.max(0, Math.floor(x));
        let st;
        if (x <= 12348) st = 0;
        else if (x <= 17799) { const y = (x - 12348) / 10000; st = (932.3 * y + 1400) * y; }
        else if (x <= 69878) { const z = (x - 17799) / 10000; st = (176.64 * z + 2397) * z + 1015.13; }
        else if (x <= 277825) st = 0.42 * x - 10911.92;
        else st = 0.45 * x - 19246.67;
        return Math.floor(Math.max(0, st));
    };
    const wkAnsatz = Math.max(werbungskosten, 1230);
    const zvE = Math.max(0, brutto - wkAnsatz - sonderausgaben);
    const jahressteuer = ESt(zvE);
    const erstattung = lstGezahlt - jahressteuer;
    const ergebnis = erstattung > 0 ? 'Erstattung' : 'Nachzahlung';
    const wkUeber = Math.max(0, werbungskosten - 1230);
    const wkErsparnis = Math.max(0, ESt(zvE + wkUeber) - jahressteuer);
    const durchschnitt = brutto ? jahressteuer / brutto * 100 : 0;
    const monatsAequiv = erstattung / (lstGezahlt / 12 + 1);
    const lohntErklaerung = Math.abs(erstattung) > 50 ? 'ja' : 'eher nein';
    return rows([
        ['Anzusetzende Werbungskosten', euro.format(wkAnsatz)],
        ['Zu versteuerndes Einkommen', euro.format(zvE)],
        ['Tarifliche Jahressteuer', euro.format(jahressteuer)],
        ['Bereits gezahlte Lohnsteuer', euro.format(lstGezahlt)],
        ['Erstattung / Nachzahlung', euro.format(erstattung)],
        ['Ergebnis', ergebnis],
        ['Wirkung der Werbungskosten über Pauschbetrag', euro.format(wkErsparnis)],
        ['Durchschnittssteuersatz', num.format(durchschnitt) + ' %'],
        ['Erstattung als Monatsnetto-Äquivalent', euro.format(monatsAequiv)],
        ['Lohnt sich die Erklärung?', lohntErklaerung],
    ]);
}
            case 'vl-arbeitnehmersparzulage-rechner-2026': {
  const vlMonat=n('vl_monat'), agZuschuss=n('ag_zuschuss'), zve=n('zve');
  const anlageart=value('anlageart');
  const verheiratet=value('verheiratet');
  const istBau = anlageart.indexOf('Bauspar')>-1 || anlageart.indexOf('Baudarlehen')>-1 || anlageart.indexOf('20')>-1;
  const zulagensatz = istBau ? 0.20 : 0.09;
  const hoechstbetrag = istBau ? 470 : 400;
  const verh = verheiratet==='ja';
  const grenze = istBau ? (verh?35800:17900) : (verh?80000:40000);
  const vlJahr = vlMonat*12;
  const eigenanteil = Math.max(0, (vlMonat-agZuschuss))*12;
  const agJahr = agZuschuss*12;
  const foerderfaehig = Math.min(vlJahr, hoechstbetrag);
  const anspruch = zve<=grenze;
  const sparzulage = anspruch ? foerderfaehig*zulagensatz : 0;
  const gesamtzufluss = vlJahr + sparzulage;
  const foerderquote = eigenanteil>0 ? (agJahr+sparzulage)/eigenanteil*100 : 0;
  const sparzulage6 = sparzulage*6;
  return rows([
    ['VL-Jahresbetrag', euro.format(vlJahr)],
    ['Eigenanteil pro Jahr', euro.format(eigenanteil)],
    ['Arbeitgeberzuschuss pro Jahr', euro.format(agJahr)],
    ['Anlageart', istBau?'Bausparen / Baudarlehen (20 %)':'Aktienfonds (9 %)'],
    ['Förderfähiger Betrag', euro.format(foerderfaehig)],
    ['Einkommensgrenze ('+(verh?'verheiratet':'ledig')+')', euro.format(grenze)],
    ['Anspruch auf Sparzulage', anspruch ? 'ja' : 'nein – Einkommen über Grenze'],
    ['Arbeitnehmersparzulage pro Jahr', euro.format(sparzulage)],
    ['Gesamtzufluss pro Jahr (VL + Zulage)', euro.format(gesamtzufluss)],
    ['Förderquote auf Eigenanteil', num.format(foerderquote)+' %'],
    ['Sparzulage über 6 Jahre', euro.format(sparzulage6)],
  ]);
}
            case 'umsatzsteuer-voranmeldung-rechner-2026': {
  const umsatz19=n('umsatz19'), umsatz7=n('umsatz7'), vorsteuer19=n('vorsteuer19'), vorsteuer7=n('vorsteuer7');
  const ust19 = umsatz19*0.19;
  const ust7 = umsatz7*0.07;
  const ustGesamt = ust19+ust7;
  const vst19 = vorsteuer19/1.19*0.19;
  const vst7 = vorsteuer7/1.07*0.07;
  const vstGesamt = vst19+vst7;
  const zahllast = ustGesamt-vstGesamt;
  const ergebnisart = zahllast>=0 ? 'Zahllast ans Finanzamt' : 'Erstattung vom Finanzamt';
  const bruttoEinnahmen = umsatz19*1.19+umsatz7*1.07;
  const vstQuote = ustGesamt>0 ? vstGesamt/ustGesamt*100 : 0;
  const nettoUmsatz = umsatz19+umsatz7;
  const margeBelastung = nettoUmsatz>0 ? zahllast/nettoUmsatz*100 : 0;
  return rows([
    ['Umsatzsteuer 19 %', euro.format(ust19)],
    ['Umsatzsteuer 7 %', euro.format(ust7)],
    ['Vereinnahmte USt gesamt', euro.format(ustGesamt)],
    ['Vorsteuer aus 19-%-Rechnungen', euro.format(vst19)],
    ['Vorsteuer aus 7-%-Rechnungen', euro.format(vst7)],
    ['Abziehbare Vorsteuer gesamt', euro.format(vstGesamt)],
    [zahllast>=0?'Zahllast ans Finanzamt':'Vorsteuerguthaben', euro.format(Math.abs(zahllast))],
    ['Ergebnisart', ergebnisart],
    ['Bruttoeinnahmen gesamt', euro.format(bruttoEinnahmen)],
    ['Vorsteuerquote', num.format(vstQuote)+' %'],
    ['USt-Belastung des Nettoumsatzes', num.format(margeBelastung)+' %'],
  ]);
}
            case 'kleinunternehmer-grenze-rechner-2026': {
    const grenzeVorjahr = 25000, grenzeJahr = 100000;
    const uv = n('umsatz_vorjahr'), uj = n('umsatz_jahr'), satz = n('ust_satz');
    const vorjahrOk = uv <= grenzeVorjahr;
    const jahrOk = uj <= grenzeJahr;
    const statusMoeglich = vorjahrOk && jahrOk;
    const pufferVorjahr = Math.max(0, grenzeVorjahr - uv);
    const pufferJahr = Math.max(0, grenzeJahr - uj);
    const keineUst = uj * satz / 100;
    const verloreneVorsteuer = uj * 0.08;
    const nettoVorteil = keineUst - verloreneVorsteuer;
    const empfehlung = (statusMoeglich && nettoVorteil > 0) ? 'Kleinunternehmerregelung nutzen' : 'Regelbesteuerung prüfen';
    return rows([
        ['Vorjahresgrenze 2025', euro.format(grenzeVorjahr)],
        ['Laufende Jahresgrenze 2026', euro.format(grenzeJahr)],
        ['Vorjahresgrenze eingehalten', vorjahrOk ? 'Ja' : 'Nein – überschritten'],
        ['Jahresgrenze eingehalten', jahrOk ? 'Ja' : 'Nein – überschritten'],
        ['Kleinunternehmer-Status möglich', statusMoeglich ? 'Ja' : 'Nein'],
        ['Puffer bis Vorjahresgrenze', euro.format(pufferVorjahr)],
        ['Puffer bis Jahresgrenze', euro.format(pufferJahr)],
        ['Nicht berechnete Umsatzsteuer/Jahr', euro.format(keineUst)],
        ['Nicht abziehbare Vorsteuer (ca. 8 %)', euro.format(verloreneVorsteuer)],
        ['Netto-Vorteil Kleinunternehmer', euro.format(nettoVorteil)],
        ['Empfehlung', empfehlung],
    ]);
}
            case 'gewerbesteuer-rechner-2026': {
    const ertrag = n('ertrag'), hebesatz = n('hebesatz'), rechtsform = value('rechtsform');
    const istKap = rechtsform.indexOf('Kapitalgesellschaft') !== -1;
    const freibetrag = istKap ? 0 : 24500;
    const ertragNachFb = Math.floor(Math.max(0, ertrag - freibetrag) / 100) * 100;
    const messbetrag = ertragNachFb * 0.035;
    const gewSt = messbetrag * hebesatz / 100;
    const effSatz = ertrag > 0 ? gewSt / ertrag * 100 : 0;
    const anrechenbar = istKap ? 0 : Math.min(messbetrag * 3.8, gewSt);
    const nettoGewSt = gewSt - anrechenbar;
    const proMonat = gewSt / 12;
    const breakEven = 380;
    const mehrbelastung = Math.max(0, hebesatz - breakEven) / 100 * messbetrag;
    const anteilNetto = ertrag > 0 ? nettoGewSt / ertrag * 100 : 0;
    return rows([
        ['Anzuwendender Freibetrag', euro.format(freibetrag)],
        ['Gewerbeertrag nach Freibetrag', euro.format(ertragNachFb)],
        ['Steuermessbetrag (3,5 %)', euro.format(messbetrag)],
        ['Gewerbesteuer', euro.format(gewSt)],
        ['Effektiver Gewerbesteuersatz', num.format(effSatz) + ' %'],
        ['Anrechenbar auf Einkommensteuer', euro.format(anrechenbar)],
        ['Verbleibende Netto-Gewerbesteuer', euro.format(nettoGewSt)],
        ['Gewerbesteuer pro Monat', euro.format(proMonat)],
        ['Break-even-Hebesatz volle Anrechnung', num.format(breakEven) + ' %'],
        ['Mehrbelastung ggü. 380-%-Gemeinde', euro.format(mehrbelastung)],
        ['Belastung am Gewinn nach Anrechnung', num.format(anteilNetto) + ' %'],
    ]);
}
            case 'dienstwagen-1-prozent-rechner-2026': {
    const listenpreis = n('listenpreis'), entfernung = n('entfernung'), grenzsteuer = n('grenzsteuer');
    const antrieb = value('antrieb');
    let f = 1;
    if (antrieb.indexOf('0,25') !== -1) f = 0.25;
    else if (antrieb.indexOf('0,5') !== -1) f = 0.5;
    const abgerundet = Math.floor(listenpreis / 100) * 100;
    const vorteilPrivat = abgerundet * f / 100;
    const arbeitsweg = abgerundet * 0.0003 * entfernung;
    const vorteilGesamtMonat = vorteilPrivat + arbeitsweg;
    const vorteilJahr = vorteilGesamtMonat * 12;
    const lastMonat = vorteilGesamtMonat * grenzsteuer / 100;
    const lastJahr = lastMonat * 12;
    const ersparnisEAuto = (abgerundet * 1 / 100 - abgerundet * f / 100) * 12;
    const anteilLp = abgerundet > 0 ? vorteilJahr / abgerundet * 100 : 0;
    const breakEvenKm = lastJahr / 0.30;
    return rows([
        ['Abgerundeter Listenpreis', euro.format(abgerundet)],
        ['Geldwerter Vorteil Privatnutzung/Monat', euro.format(vorteilPrivat)],
        ['Arbeitsweg-Zuschlag/Monat', euro.format(arbeitsweg)],
        ['Geldwerter Vorteil gesamt/Monat', euro.format(vorteilGesamtMonat)],
        ['Geldwerter Vorteil/Jahr', euro.format(vorteilJahr)],
        ['Zusätzliche Steuer-/SV-Last/Monat', euro.format(lastMonat)],
        ['Zusätzliche Last/Jahr', euro.format(lastJahr)],
        ['Effektive monatliche Netto-Kosten', euro.format(lastMonat)],
        ['Ersparnis E-Auto ggü. Verbrenner/Jahr', euro.format(ersparnisEAuto)],
        ['Vorteil als Anteil des Listenpreises p.a.', num.format(anteilLp) + ' %'],
        ['Break-even-Privatkilometer/Jahr', num.format(breakEvenKm) + ' km'],
    ]);
}
            case 'abfindung-fuenftelregelung-rechner-2026': {
    const abfindung = n('abfindung'), zveUebrig = n('zve_uebrig');
    const zusammen = value('veranlagung') === 'Zusammen';
    const tarif = function(x) {
        if (x <= 0) return 0;
        if (x <= 12348) return 0;
        if (x <= 17443) { const y = (x - 12348) / 10000; return (914.51 * y + 1400) * y; }
        if (x <= 68480) { const z = (x - 17443) / 10000; return (173.10 * z + 2397) * z + 1015.13; }
        if (x <= 277825) return 0.42 * x - 10911.92;
        return 0.45 * x - 19246.67;
    };
    const ESt = function(x) { return zusammen ? 2 * tarif(x / 2) : tarif(x); };
    const stUebrig = ESt(zveUebrig);
    const stMitFuenftel = ESt(zveUebrig + abfindung / 5);
    const stAbfFuenftel = 5 * (stMitFuenftel - stUebrig);
    const stAbfVoll = ESt(zveUebrig + abfindung) - stUebrig;
    const ersparnis = stAbfVoll - stAbfFuenftel;
    const soli = stAbfFuenftel > 19950 ? stAbfFuenftel * 0.055 : 0;
    const gesamtSteuer = stAbfFuenftel + soli;
    const nettoAbf = abfindung - gesamtSteuer;
    const effSatz = abfindung > 0 ? gesamtSteuer / abfindung * 100 : 0;
    const lohnt = ersparnis > 0 ? 'Ja' : 'Kein Vorteil';
    const nettoVorteil = Math.max(0, ersparnis) * 1.055;
    return rows([
        ['Steuer auf übriges Einkommen', euro.format(stUebrig)],
        ['Steuer mit 1/5 der Abfindung', euro.format(stMitFuenftel)],
        ['Steuer auf Abfindung (Fünftelregelung)', euro.format(stAbfFuenftel)],
        ['Steuer auf Abfindung (voll)', euro.format(stAbfVoll)],
        ['Steuerersparnis durch Fünftelregelung', euro.format(ersparnis)],
        ['Soli auf Abfindungssteuer', euro.format(soli)],
        ['Gesamtsteuer auf Abfindung inkl. Soli', euro.format(gesamtSteuer)],
        ['Netto-Abfindung', euro.format(nettoAbf)],
        ['Effektiver Steuersatz auf Abfindung', num.format(effSatz) + ' %'],
        ['Lohnt die Fünftelregelung?', lohnt],
        ['Netto-Vorteil ggü. voller Versteuerung', euro.format(nettoVorteil)],
    ]);
}
            case 'rentenbesteuerung-rechner-2026': {
    const jahresrente = n('jahresrente'), rentenbeginn = n('rentenbeginn'), weitereEink = n('weitere_eink');
    const zusammen = value('veranlagung') === 'Zusammen';
    const tarif = function(x) {
        if (x <= 0) return 0;
        if (x <= 12348) return 0;
        if (x <= 17443) { const y = (x - 12348) / 10000; return (914.51 * y + 1400) * y; }
        if (x <= 68480) { const z = (x - 17443) / 10000; return (173.10 * z + 2397) * z + 1015.13; }
        if (x <= 277825) return 0.42 * x - 10911.92;
        return 0.45 * x - 19246.67;
    };
    const ESt = function(x) { return zusammen ? 2 * tarif(x / 2) : tarif(x); };
    const anteil = clamp(82.5 + (rentenbeginn - 2023) * 0.5, 0, 100);
    const stpflRente = jahresrente * anteil / 100;
    const freibetrag = jahresrente - stpflRente;
    const wkPausch = 102;
    const stpflEink = Math.max(0, stpflRente - wkPausch);
    const zvE = stpflEink + weitereEink;
    const grundfreibetrag = zusammen ? 24696 : 12348;
    const est = ESt(zvE);
    const steuerFaellt = zvE > grundfreibetrag ? 'Ja' : 'Nein – unter Grundfreibetrag';
    const nettoRente = jahresrente - est;
    const effSatz = jahresrente > 0 ? est / jahresrente * 100 : 0;
    const monatlich = est / 12;
    return rows([
        ['Besteuerungsanteil', num.format(anteil) + ' %'],
        ['Steuerpflichtiger Rentenanteil', euro.format(stpflRente)],
        ['Steuerfreier Rentenfreibetrag', euro.format(freibetrag)],
        ['Werbungskosten-Pauschbetrag', euro.format(wkPausch)],
        ['Steuerpflichtige Renteneinkünfte', euro.format(stpflEink)],
        ['Zu versteuerndes Einkommen', euro.format(zvE)],
        ['Voraussichtliche Einkommensteuer', euro.format(est)],
        ['Steuer fällt an?', steuerFaellt],
        ['Netto-Rente nach Steuer', euro.format(nettoRente)],
        ['Effektiver Steuersatz auf die Rente', num.format(effSatz) + ' %'],
        ['Monatliche Steuerlast', euro.format(monatlich)],
    ]);
}
            case 'kurzfristige-beschaeftigung-rechner-2026': {
    const tage = n('tage'); const tageslohn = n('tageslohn'); const vst = value('versteuerung');
    const istIndividuell = vst.indexOf('individuell') !== -1;
    const eingehalten = tage <= 70;
    const verbleibend = Math.max(0, 70 - tage);
    const brutto = tage * tageslohn;
    const pauschalsteuer = istIndividuell ? 0 : brutto * 0.25;
    const lst = istIndividuell ? (brutto < 13578 ? 0 : brutto * 0.14) : 0;
    const netto = brutto - lst;
    const tagesnetto = tage > 0 ? netto / tage : 0;
    const hinweis = tage > 70 ? 'nicht mehr SV-frei – Midijob prüfen' : 'SV-frei eingehalten';
    return rows([
        ['Zeitgrenze (max. 70 Tage)', eingehalten ? 'eingehalten' : 'überschritten'],
        ['Verbleibende Tage', num.format(verbleibend) + ' Tage'],
        ['Gesamtverdienst (Brutto)', euro.format(brutto)],
        ['Sozialabgaben', euro.format(0) + ' (SV-frei)'],
        ['Versteuerung', istIndividuell ? 'individuell' : 'pauschal 25 %'],
        ['Lohnsteuer (Schätzung)', euro.format(lst)],
        ['Pauschalsteuer (AG trägt)', euro.format(pauschalsteuer)],
        ['Netto Arbeitnehmer', euro.format(netto)],
        ['Ø Netto pro Tag', euro.format(tagesnetto)],
        ['Hinweis', hinweis],
    ]);
}
            case 'spenden-steuerersparnis-rechner-2026': {
    const spende = n('spende'); const einkuenfte = n('einkuenfte'); const grenz = n('grenzsteuer');
    const kStr = value('kirche'); const k = kStr.indexOf('9') !== -1 ? 0.09 : (kStr.indexOf('8') !== -1 ? 0.08 : 0);
    const hoechst = einkuenfte * 0.20;
    const abziehbar = Math.min(spende, hoechst);
    const uebertrag = Math.max(0, spende - hoechst);
    const estErsparnis = abziehbar * grenz / 100;
    const soli = estErsparnis * 0.055;
    const kistErsparnis = estErsparnis * k;
    const gesamtErsparnis = estErsparnis + soli + kistErsparnis;
    const eigenbelastung = spende - gesamtErsparnis;
    const foerderquote = spende > 0 ? gesamtErsparnis / spende * 100 : 0;
    const kostenJe100 = spende > 0 ? eigenbelastung / spende * 100 : 0;
    return rows([
        ['Abziehbarer Höchstbetrag (20 %)', euro.format(hoechst)],
        ['Abziehbare Spende', euro.format(abziehbar)],
        ['Übertrag ins Folgejahr', euro.format(uebertrag)],
        ['Steuerersparnis Einkommensteuer', euro.format(estErsparnis)],
        ['Soli-Ersparnis', euro.format(soli)],
        ['Kirchensteuer-Ersparnis', euro.format(kistErsparnis)],
        ['Gesamte Steuerersparnis', euro.format(gesamtErsparnis)],
        ['Effektive Eigenbelastung', euro.format(eigenbelastung)],
        ['Förderquote', num.format(foerderquote) + ' %'],
        ['Echte Kosten je 100 € Spende', euro.format(kostenJe100)],
    ]);
}
            case 'elterngeld-rechner-2026': {
    const netto = n('netto');
    const gbStr = value('geschwisterbonus'); const hatGb = gbStr.indexOf('ja') !== -1;
    const mehrlinge = Math.max(0, n('mehrlinge'));
    let rate;
    if (netto < 1000) rate = Math.min(100, 67 + (1000 - netto) * 0.01);
    else if (netto <= 1200) rate = 67;
    else rate = Math.max(65, 67 - (netto - 1200) / 2 * 0.1);
    const vorDeckel = netto * rate / 100;
    const basis = clamp(vorDeckel, 300, 1800);
    const geschwisterbonus = hatGb ? Math.max(basis * 0.10, 75) : 0;
    const mehrlingszuschlag = mehrlinge * 300;
    const gesamt = basis + geschwisterbonus + mehrlingszuschlag;
    const basis12 = gesamt * 12;
    const plusMonat = gesamt / 2;
    const plus24 = plusMonat * 24;
    const ersatzquote = netto > 0 ? basis / netto * 100 : 0;
    const differenz = netto - basis;
    return rows([
        ['Maßgebliche Ersatzrate', num.format(rate) + ' %'],
        ['Elterngeld vor Deckelung', euro.format(vorDeckel)],
        ['Basiselterngeld pro Monat', euro.format(basis)],
        ['Geschwisterbonus', euro.format(geschwisterbonus)],
        ['Mehrlingszuschlag', euro.format(mehrlingszuschlag)],
        ['Gesamt-Basiselterngeld pro Monat', euro.format(gesamt)],
        ['Basiselterngeld 12 Monate', euro.format(basis12)],
        ['ElterngeldPlus pro Monat', euro.format(plusMonat)],
        ['ElterngeldPlus 24 Monate', euro.format(plus24)],
        ['Einkommens-Ersatzquote', num.format(ersatzquote) + ' %'],
        ['Differenz zum bisherigen Netto', euro.format(differenz)],
    ]);
}
            case 'riester-zulage-rechner-2026': {
    const brutto = n('brutto_vorjahr'); const kAb = Math.max(0, n('kinder_ab2008'));
    const kVor = Math.max(0, n('kinder_vor2008')); const eigen = n('eigenbeitrag');
    const grund = 175;
    const kinder = kAb * 300 + kVor * 185;
    const zulagen = grund + kinder;
    const gefordert = Math.min(brutto * 0.04, 2100);
    const mindest = Math.max(gefordert - zulagen, 60);
    const ausreichend = eigen >= mindest;
    const tatsZulage = ausreichend ? zulagen : (mindest > 0 ? zulagen * eigen / mindest : 0);
    const sparleistung = eigen + tatsZulage;
    const foerderquote = sparleistung > 0 ? tatsZulage / sparleistung * 100 : 0;
    const fehlbetrag = Math.max(0, mindest - eigen);
    const foerder30 = tatsZulage * 30;
    return rows([
        ['Grundzulage', euro.format(grund)],
        ['Kinderzulagen gesamt', euro.format(kinder)],
        ['Zulagen gesamt', euro.format(zulagen)],
        ['Geforderter Gesamtbeitrag (4 %)', euro.format(gefordert)],
        ['Mindesteigenbeitrag', euro.format(mindest)],
        ['Eigenbeitrag ausreichend?', ausreichend ? 'ja' : 'nein'],
        ['Tatsächliche Zulage', euro.format(tatsZulage)],
        ['Gesamtsparleistung pro Jahr', euro.format(sparleistung)],
        ['Staatliche Förderquote', num.format(foerderquote) + ' %'],
        ['Fehlbetrag bis Mindestbeitrag', euro.format(fehlbetrag)],
        ['Förderung über 30 Jahre', euro.format(foerder30)],
    ]);
}
            case 'mindestlohn-2026-rechner': {
    const stdWoche = n('stunden_woche'); const aktLohn = n('aktueller_stundenlohn');
    const wf = n('wochen_pro_monat') || 4.345;
    const ML26 = 13.90, ML27 = 14.60;
    const mlMonat = ML26 * stdWoche * wf;
    const mlJahr = mlMonat * 12;
    const deinMonat = aktLohn * stdWoche * wf;
    const diffStunde = ML26 - aktLohn;
    const status = aktLohn >= ML26 ? 'über Mindestlohn' : 'unter Mindestlohn';
    const unterzahlung = Math.max(0, ML26 - aktLohn) * stdWoche * wf;
    const ml27Monat = ML27 * stdWoche * wf;
    const erhoehungMonat = (ML27 - ML26) * stdWoche * wf;
    const erhoehungProz = (ML27 / ML26 - 1) * 100;
    return rows([
        ['Mindestlohn 2026 pro Stunde', euro.format(ML26)],
        ['Mindestlohn-Monatsbrutto 2026', euro.format(mlMonat)],
        ['Mindestlohn-Jahresbrutto 2026', euro.format(mlJahr)],
        ['Dein Monatsbrutto', euro.format(deinMonat)],
        ['Differenz pro Stunde', euro.format(diffStunde)],
        ['Status', status],
        ['Monatliche Unterzahlung', euro.format(unterzahlung)],
        ['Mindestlohn 2027 pro Stunde', euro.format(ML27)],
        ['Mindestlohn-Monatsbrutto 2027', euro.format(ml27Monat)],
        ['Erhöhung 2026→2027 monatlich', euro.format(erhoehungMonat)],
        ['Erhöhung 2026→2027', num.format(erhoehungProz) + ' %'],
    ]);
}
            case 'beitragsbemessungsgrenze-2026-rechner': {
    const brutto = n('brutto_monat');
    const BBG_RV = 8450, BBG_KV = 5812.50, VPG = 6450;
    const jahresbrutto = brutto * 12;
    const pflichtRV = Math.min(brutto, BBG_RV);
    const freiRV = Math.max(0, brutto - BBG_RV);
    const pflichtKV = Math.min(brutto, BBG_KV);
    const freiKV = Math.max(0, brutto - BBG_KV);
    const ueberVPG = brutto > VPG ? 'ja – PKV-Wechsel möglich' : 'nein';
    const ausRV = Math.min(100, brutto > 0 ? brutto / BBG_RV * 100 : 0);
    const ausKV = Math.min(100, brutto > 0 ? brutto / BBG_KV * 100 : 0);
    const anteilFrei = brutto > 0 ? ((freiRV + freiKV) / 2) / brutto * 100 : 0;
    return rows([
        ['Jahresbrutto', euro.format(jahresbrutto)],
        ['Beitragspflichtig RV/ALV (Monat)', euro.format(pflichtRV)],
        ['Beitragsfrei RV/ALV (Monat)', euro.format(freiRV)],
        ['Beitragspflichtig KV/PV (Monat)', euro.format(pflichtKV)],
        ['Beitragsfrei KV/PV (Monat)', euro.format(freiKV)],
        ['BBG RV/ALV jährlich', euro.format(101400)],
        ['BBG KV/PV jährlich', euro.format(69750)],
        ['Über Versicherungspflichtgrenze (6.450 €)?', ueberVPG],
        ['Ausschöpfung BBG RV', num.format(ausRV) + ' %'],
        ['Ausschöpfung BBG KV', num.format(ausKV) + ' %'],
        ['Anteil beitragsfreies Einkommen (Ø)', num.format(anteilFrei) + ' %'],
    ]);
}
            case 'sozialabgaben-2026-rechner': {
    const brutto = n('brutto_monat'); const zusatz = n('zusatzbeitrag');
    const plStr = value('kinderlos'); const kinderlos = plStr.indexOf('kinderlos') !== -1;
    const BBG_RV = 8450, BBG_KV = 5812.50;
    const bemRV = Math.min(brutto, BBG_RV);
    const bemKV = Math.min(brutto, BBG_KV);
    const kvSatz = 0.146 + zusatz / 100;
    const rvAN = bemRV * 0.186 / 2;
    const alvAN = bemRV * 0.026 / 2;
    const kvAN = bemKV * kvSatz / 2;
    const pvAN = bemKV * 0.036 / 2 + (kinderlos ? bemKV * 0.006 : 0);
    const summeAN = rvAN + alvAN + kvAN + pvAN;
    const quote = brutto > 0 ? summeAN / brutto * 100 : 0;
    const nettoVorSteuer = brutto - summeAN;
    const agAnteil = bemRV * 0.106 + bemKV * (kvSatz / 2 + 0.018);
    const gesamt = summeAN + agAnteil;
    const summeJahr = summeAN * 12;
    const pflegeMehr = kinderlos ? bemKV * 0.006 : 0;
    return rows([
        ['Rentenversicherung (AN)', euro.format(rvAN)],
        ['Arbeitslosenversicherung (AN)', euro.format(alvAN)],
        ['Krankenversicherung (AN)', euro.format(kvAN)],
        ['Pflegeversicherung (AN)', euro.format(pvAN)],
        ['Summe AN-Abgaben pro Monat', euro.format(summeAN)],
        ['Abgabenquote', num.format(quote) + ' %'],
        ['Netto vor Steuer', euro.format(nettoVorSteuer)],
        ['Arbeitgeberanteil (ca.)', euro.format(agAnteil)],
        ['Gesamtbelastung AN+AG pro Monat', euro.format(gesamt)],
        ['Summe AN-Abgaben pro Jahr', euro.format(summeJahr)],
        ['Pflege-Mehrbelastung (kinderlos)', euro.format(pflegeMehr)],
    ]);
}
            case 'rentenpunkte-wert-2026-rechner': {
    const bruttoJahr = n('brutto_jahr'); const jahre = n('jahre');
    const DURCH = 51944, RW = 42.52, BBG = 101400;
    const pflichtig = Math.min(bruttoJahr, BBG);
    const epProJahr = pflichtig / DURCH;
    const maxEp = BBG / DURCH;
    const monatsrente1J = epProJahr * RW;
    const punkteGesamt = epProJahr * jahre;
    const monatsrente = punkteGesamt * RW;
    const jahresrente = monatsrente * 12;
    const anteilDurch = bruttoJahr / DURCH * 100;
    const bisVoll = Math.max(0, 1 - epProJahr);
    const monatsbrutto = bruttoJahr / 12;
    const niveau = monatsbrutto > 0 ? monatsrente / monatsbrutto * 100 : 0;
    const verlustBBG = Math.max(0, bruttoJahr - BBG) / DURCH * RW;
    return rows([
        ['Beitragspflichtiges Jahresentgelt', euro.format(pflichtig)],
        ['Entgeltpunkte pro Jahr', num.format(epProJahr)],
        ['Max. mögliche Punkte pro Jahr', num.format(maxEp)],
        ['Monatsrente aus 1 Jahr', euro.format(monatsrente1J)],
        ['Gesammelte Punkte gesamt', num.format(punkteGesamt)],
        ['Monatliche Bruttorente', euro.format(monatsrente)],
        ['Jährliche Bruttorente', euro.format(jahresrente)],
        ['Anteil am Durchschnittsverdiener', num.format(anteilDurch) + ' %'],
        ['Punkte bis zum vollen Punkt/Jahr', num.format(bisVoll)],
        ['Rentenniveau-Faktor', num.format(niveau) + ' %'],
        ['Verlust durch BBG-Deckelung/Jahr', euro.format(verlustBBG)],
    ]);
}
            case 'rentenluecke-inflation-2026-rechner': {
    const wunsch = n('wunsch_einkommen'); const rente = n('erwartete_rente');
    const nJahre = n('jahre_bis_rente'); const i = n('inflation') / 100; const r = n('rendite') / 100;
    const infFaktor = Math.pow(1 + i, nJahre);
    const wunschZukunft = wunsch * infFaktor;
    const luckeHeute = Math.max(0, wunsch - rente);
    const realKaufkraft = infFaktor > 0 ? rente / infFaktor : rente;
    const realLucke = Math.max(0, wunsch - realKaufkraft);
    const nominalLucke = Math.max(0, wunschZukunft - rente);
    const kapital = nominalLucke * 12 * 25;
    const rm = r / 12;
    const monate = nJahre * 12;
    let sparrate;
    if (rm > 0 && monate > 0) {
        const faktor = Math.pow(1 + rm, monate) - 1;
        sparrate = faktor > 0 ? kapital * rm / faktor : (monate > 0 ? kapital / monate : 0);
    } else {
        sparrate = monate > 0 ? kapital / monate : 0;
    }
    const eingezahlt = sparrate * monate;
    const zinsertrag = Math.max(0, kapital - eingezahlt);
    const kaufkraftverlust = (1 - (infFaktor > 0 ? 1 / infFaktor : 1)) * 100;
    const luckenQuote = wunsch > 0 ? realLucke / wunsch * 100 : 0;
    return rows([
        ['Wunsch-Einkommen zukünftig (nominal)', euro.format(wunschZukunft)],
        ['Lücke heute (nominal)', euro.format(luckeHeute)],
        ['Reale Kaufkraft der Rente', euro.format(realKaufkraft)],
        ['Reale Rentenlücke (heutige Kaufkraft)', euro.format(realLucke)],
        ['Nominale Lücke bei Renteneintritt', euro.format(nominalLucke)],
        ['Benötigtes Kapital (25 J Bezug)', euro.format(kapital)],
        ['Monatliche Sparrate', euro.format(sparrate)],
        ['Eingezahltes Kapital', euro.format(eingezahlt)],
        ['Zinsertrag', euro.format(zinsertrag)],
        ['Kaufkraftverlust der Rente', num.format(kaufkraftverlust) + ' %'],
        ['Lücken-Quote', num.format(luckenQuote) + ' %'],
    ]);
}
            case 'buergergeld-2026-rechner': {
    const erwachsene = Math.max(0, n('erwachsene'));
    const k05 = Math.max(0, n('kinder_0_5')); const k613 = Math.max(0, n('kinder_6_13'));
    const k1417 = Math.max(0, n('kinder_14_17'));
    const miete = n('miete_warm'); const einkommen = n('netto_einkommen');
    const regelErw = erwachsene === 1 ? 563 : erwachsene * 506;
    const regelKinder = k05 * 357 + k613 * 390 + k1417 * 471;
    const regelGesamt = regelErw + regelKinder;
    const gesamtbedarf = regelGesamt + miete;
    const freibetrag = einkommen > 0
        ? (100 + Math.max(0, Math.min(einkommen, 520) - 100) * 0.20 + Math.max(0, Math.min(einkommen, 1000) - 520) * 0.30)
        : 0;
    const anrechenbar = Math.max(0, einkommen - freibetrag);
    const anspruch = Math.max(0, gesamtbedarf - anrechenbar);
    const ohneFrei = Math.max(0, gesamtbedarf - einkommen);
    const vorteil = anspruch - ohneFrei;
    const verfuegbar = anspruch + einkommen;
    return rows([
        ['Regelbedarf Erwachsene', euro.format(regelErw)],
        ['Regelbedarf Kinder', euro.format(regelKinder)],
        ['Gesamt-Regelbedarf', euro.format(regelGesamt)],
        ['Anerkannte Unterkunftskosten', euro.format(miete)],
        ['Gesamtbedarf', euro.format(gesamtbedarf)],
        ['Freibetrag Erwerbseinkommen', euro.format(freibetrag)],
        ['Anrechenbares Einkommen', euro.format(anrechenbar)],
        ['Bürgergeld-Anspruch pro Monat', euro.format(anspruch)],
        ['Vorteil durch Freibetrag', euro.format(vorteil)],
        ['Verfügbar gesamt (Anspruch + Einkommen)', euro.format(verfuegbar)],
    ]);
}
            case 'indexmiete-kappung-2026-rechner': {
    const alt = n('alte_miete'); const vb = n('vpi_basis'); const va = n('vpi_aktuell');
    const jahre = Math.max(0, n('jahre_seit_basis')); const ang = value('angespannt');
    const faktor = vb > 0 ? va / vb : 1;
    const steig = (faktor - 1) * 100;
    const vpiMiete = alt * faktor;
    const vpiErh = vpiMiete - alt;
    const deckelMax = ang === 'ja' ? alt * (1 + 0.035 * jahre) : vpiMiete;
    const zul = Math.min(vpiMiete, deckelMax);
    const zulErh = zul - alt;
    const zulPct = alt > 0 ? (zul / alt - 1) * 100 : 0;
    const greift = (ang === 'ja' && vpiMiete > deckelMax) ? 'ja' : 'nein';
    const ersp = Math.max(0, vpiMiete - zul);
    return rows([
        ['VPI-Steigerung', num.format(steig) + ' %'],
        ['VPI-basierte neue Miete', euro.format(vpiMiete)],
        ['VPI-basierte Erhöhung', euro.format(vpiErh)],
        ['Deckel greift (3,5 %/Jahr)?', greift],
        ['Zulässige neue Miete', euro.format(zul)],
        ['Zulässige Erhöhung', euro.format(zulErh)],
        ['Zulässige Erhöhung in %', num.format(zulPct) + ' %'],
        ['Ersparnis durch Deckel', euro.format(ersp)],
        ['Jahresmehrkosten', euro.format(zulErh * 12)],
    ]);
}
            case 'mieterhoehung-kappungsgrenze-rechner': {
    const m3 = n('miete_vor_3_jahren'); const akt = n('aktuelle_miete'); const vgl = n('vergleichsmiete');
    const markt = value('markt');
    const satz = markt === 'angespannt (15 %)' ? 0.15 : 0.20;
    const kappMax = m3 * (1 + satz);
    const zul = Math.min(vgl, kappMax);
    const maxErh = Math.max(0, zul - akt);
    const maxErhPct = akt > 0 ? (zul / akt - 1) * 100 : 0;
    const begrenzer = kappMax < vgl ? 'Kappungsgrenze' : 'Vergleichsmiete';
    const ausgeschoepft = m3 > 0 ? (akt / m3 - 1) * 100 : 0;
    const spielraum = Math.max(0, kappMax - akt);
    const unzulaessig = akt >= vgl ? 'ja' : 'nein';
    return rows([
        ['Kappungssatz', num.format(satz * 100) + ' %'],
        ['Kappungsgrenze-Maximalmiete', euro.format(kappMax)],
        ['Zulässige neue Miete', euro.format(zul)],
        ['Begrenzender Faktor', begrenzer],
        ['Maximal zulässige Erhöhung', euro.format(maxErh)],
        ['Maximal zulässige Erhöhung in %', num.format(maxErhPct) + ' %'],
        ['Bereits ausgeschöpft (3 Jahre)', num.format(ausgeschoepft) + ' %'],
        ['Verbleibender Spielraum', euro.format(spielraum)],
        ['Schon über Vergleichsmiete?', unzulaessig],
        ['Neue Jahresmiete', euro.format(zul * 12)],
        ['Mehrkosten pro Jahr', euro.format(maxErh * 12)],
    ]);
}
            case 'bauzins-annuitaet-2026-rechner': {
    const kp = n('kaufpreis'); const ek = n('eigenkapital'); const D = Math.max(0, kp - ek);
    const z = n('sollzins') / 100; const t = n('tilgung') / 100; const m = z / 12;
    const annu = D * (z + t); const rate = annu / 12;
    const zins1 = D * z; const tilg1 = D * t;
    const monate = Math.max(0, Math.round(n('zinsbindung') * 12));
    let rest = D;
    for (let i = 0; i < monate; i++) { rest = rest * (1 + m) - rate; }
    rest = Math.max(0, rest);
    const jahre = Math.max(0, Math.round(n('zinsbindung'))); const sond = Math.max(0, n('sondertilgung'));
    let restS = D;
    for (let y = 0; y < jahre; y++) { for (let mo = 0; mo < 12; mo++) { restS = restS * (1 + m) - rate; } restS = Math.max(0, restS - sond); }
    const getilgt = D - rest;
    const ltv = kp > 0 ? D / kp * 100 : 0;
    let voll = 0;
    if (annu > D * z && z > 0 && D > 0) { voll = Math.log(annu / (annu - D * z)) / Math.log(1 + z); }
    return rows([
        ['Darlehenssumme', euro.format(D)],
        ['Monatliche Rate', euro.format(rate)],
        ['Annuität pro Jahr', euro.format(annu)],
        ['Zins im 1. Jahr', euro.format(zins1)],
        ['Tilgung im 1. Jahr', euro.format(tilg1)],
        ['Restschuld nach Zinsbindung', euro.format(rest)],
        ['Restschuld mit Sondertilgung', euro.format(restS)],
        ['Getilgt in der Zinsbindung', euro.format(getilgt)],
        ['Beleihung (LTV)', num.format(ltv) + ' %'],
        ['Laufzeit bis Volltilgung', num.format(voll) + ' Jahre'],
    ]);
}
            case 'vorfaelligkeitsentschaedigung-rechner': {
    const rs = n('restschuld'); const sz = n('sollzins'); const wz = n('wiederanlagezins');
    const nj = Math.max(0, n('restlaufzeit')); const til = n('tilgung');
    const zs = sz / 100; const zw = wz / 100;
    const zinsdiff = Math.max(0, sz - wz);
    const durRest = Math.max(0, rs * (1 - (til / 100) * nj / 2));
    const jaehr = durRest * (zs - zw);
    const roh = jaehr * nj;
    let bwf = 1;
    if (zw > 0 && nj > 0) { bwf = (1 - Math.pow(1 + zw, -nj)) / zw / nj; }
    const abgez = roh * bwf;
    const pausch = Math.min(rs * 0.0025, 500);
    const vfeV = Math.max(0, abgez + pausch);
    const vfePct = rs > 0 ? vfeV / rs * 100 : 0;
    const hinweis = wz >= sz ? 'keine VFE' : 'VFE fällt an';
    const proJahr = nj > 0 ? vfeV / nj : 0;
    return rows([
        ['Zinsdifferenz p.a.', num.format(zinsdiff) + ' %'],
        ['Ø Restschuld im Zeitraum', euro.format(durRest)],
        ['Jährlicher Zinsschaden', euro.format(jaehr)],
        ['Roh-Zinsschaden gesamt', euro.format(roh)],
        ['Abgezinster Zinsschaden', euro.format(abgez)],
        ['Bearbeitungspauschale', euro.format(pausch)],
        ['Geschätzte Vorfälligkeitsentschädigung', euro.format(vfeV)],
        ['VFE in % der Restschuld', num.format(vfePct) + ' %'],
        ['Hinweis', hinweis],
        ['Effektive Mehrkosten pro Restjahr', euro.format(proJahr)],
    ]);
}
            case 'waermepumpe-wirtschaftlichkeit-rechner': {
    const bedarf = n('heizbedarf'); const jaz = n('jaz');
    const sp = n('strompreis'); const gp = n('gaspreis');
    const wg = n('gas_wirkungsgrad'); const mehr = n('mehrkosten');
    const co2g = n('co2_gas'); const co2s = n('co2_strom');
    const stromWP = jaz > 0 ? bedarf / jaz : 0;
    const kostenWP = stromWP * sp / 100;
    const gasVerbr = wg > 0 ? bedarf / (wg / 100) : 0;
    const kostenGas = gasVerbr * gp / 100;
    const ersp = kostenGas - kostenWP;
    const amort = ersp > 0 ? mehr / ersp : 0;
    const co2WP = stromWP * co2s / 1000;
    const co2Gas = gasVerbr * co2g / 1000;
    const co2Ersp = co2Gas - co2WP;
    const kostKwhWP = jaz > 0 ? sp / jaz : 0;
    const ersp20 = ersp * 20 - mehr;
    return rows([
        ['Stromverbrauch Wärmepumpe', num.format(stromWP) + ' kWh/Jahr'],
        ['Stromkosten Wärmepumpe', euro.format(kostenWP) + '/Jahr'],
        ['Gasverbrauch', num.format(gasVerbr) + ' kWh/Jahr'],
        ['Gaskosten', euro.format(kostenGas) + '/Jahr'],
        ['Jährliche Ersparnis', euro.format(ersp)],
        ['Amortisationsdauer', ersp > 0 ? num.format(amort) + ' Jahre' : 'keine Amortisation'],
        ['CO2 Wärmepumpe', num.format(co2WP) + ' kg/Jahr'],
        ['CO2 Gasheizung', num.format(co2Gas) + ' kg/Jahr'],
        ['CO2-Einsparung', num.format(co2Ersp) + ' kg/Jahr'],
        ['Kosten je kWh Wärme (WP)', num.format(kostKwhWP) + ' ct'],
        ['Ersparnis über 20 Jahre', euro.format(ersp20)],
    ]);
}
            case 'waermepumpe-mindest-jaz-rechner': {
    const sp = n('strompreis'); const gp = n('gaspreis');
    const wg = n('gas_wirkungsgrad'); const ziel = n('ziel_jaz'); const bedarf = n('heizbedarf');
    const gasWaerme = wg > 0 ? gp / (wg / 100) : 0;
    const beJaz = gasWaerme > 0 ? sp / gasWaerme : 0;
    const wpWaerme = ziel > 0 ? sp / ziel : 0;
    const reserve = ziel - beJaz;
    const bewertung = (beJaz > 0 && ziel >= beJaz) ? 'wirtschaftlich' : (beJaz > 0 ? 'unwirtschaftlich' : 'kein Gasvergleich');
    const vorteil = gasWaerme - wpWaerme;
    const erspJahr = vorteil * bedarf / 100;
    const reservePct = beJaz > 0 ? reserve / beJaz * 100 : 0;
    const maxStrom = gasWaerme * ziel;
    const stromVerbr = ziel > 0 ? bedarf / ziel : 0;
    return rows([
        ['Break-even-JAZ', num.format(beJaz)],
        ['Gas-Wärmekosten', num.format(gasWaerme) + ' ct/kWh'],
        ['WP-Wärmekosten bei Ziel-JAZ', num.format(wpWaerme) + ' ct/kWh'],
        ['JAZ-Reserve', num.format(reserve)],
        ['Bewertung', bewertung],
        ['Kostenvorteil je kWh', num.format(vorteil) + ' ct'],
        ['Jährliche Ersparnis', euro.format(erspJahr)],
        ['Prozentuale Reserve', num.format(reservePct) + ' %'],
        ['Maximal tragbarer Strompreis', num.format(maxStrom) + ' ct/kWh'],
        ['Stromverbrauch bei Ziel-JAZ', num.format(stromVerbr) + ' kWh/Jahr'],
    ]);
}
            case 'pv-eigenverbrauch-grenzpreis-rechner': {
    const ertrag = n('pv_ertrag'); const evAlt = n('ev_alt'); const evNeu = n('ev_neu');
    const sp = n('strompreis'); const eins = n('einspeise'); const inv = n('investition');
    const mehrwert = sp - eins;
    const evAltKwh = ertrag * evAlt / 100;
    const evNeuKwh = ertrag * evNeu / 100;
    const zusatz = evNeuKwh - evAltKwh;
    const wertSteig = zusatz * mehrwert / 100;
    const amort = wertSteig > 0 ? inv / wertSteig : 0;
    const rendite = inv > 0 ? wertSteig / inv * 100 : 0;
    const wertAlt = (evAltKwh * sp + (ertrag - evAltKwh) * eins) / 100;
    const wertNeu = (evNeuKwh * sp + (ertrag - evNeuKwh) * eins) / 100;
    return rows([
        ['Wert je eigenverbrauchter kWh', num.format(sp) + ' ct'],
        ['Wert je eingespeister kWh', num.format(eins) + ' ct'],
        ['Mehrwert Eigenverbrauch vs. Einspeisung', num.format(mehrwert) + ' ct/kWh'],
        ['Eigenverbrauch aktuell', num.format(evAltKwh) + ' kWh'],
        ['Eigenverbrauch angestrebt', num.format(evNeuKwh) + ' kWh'],
        ['Zusätzlicher Eigenverbrauch', num.format(zusatz) + ' kWh'],
        ['Wertsteigerung pro Jahr', euro.format(wertSteig)],
        ['Amortisation der Investition', wertSteig > 0 ? num.format(amort) + ' Jahre' : 'keine Amortisation'],
        ['Rendite pro Jahr', num.format(rendite) + ' %'],
        ['Ertragswert aktuell vs. angestrebt', euro.format(wertAlt) + ' → ' + euro.format(wertNeu)],
    ]);
}
            case 'pv-anlagengroesse-rechner': {
    const verbrauch = n('verbrauch'); const deckung = n('deckungsziel');
    const spez = n('spez_ertrag'); const modulW = n('modulleistung'); const modulFl = n('modulflaeche');
    const zielErtrag = verbrauch * deckung / 100;
    const leistung = spez > 0 ? zielErtrag / spez : 0;
    const anzahl = modulW > 0 ? Math.ceil(leistung * 1000 / modulW) : 0;
    const leistungReal = anzahl * modulW / 1000;
    const ertragReal = leistungReal * spez;
    const flaeche = anzahl * modulFl;
    const realDeckung = verbrauch > 0 ? ertragReal / verbrauch * 100 : 0;
    const flLeistung = flaeche > 0 ? leistungReal / flaeche : 0;
    const ertragQm = flaeche > 0 ? ertragReal / flaeche : 0;
    const ueberschuss = Math.max(0, ertragReal - verbrauch);
    return rows([
        ['Ziel-Jahresertrag', num.format(zielErtrag) + ' kWh'],
        ['Benötigte Leistung', num.format(leistung) + ' kWp'],
        ['Modulanzahl', num.format(anzahl)],
        ['Installierte Leistung real', num.format(leistungReal) + ' kWp'],
        ['Erwarteter Jahresertrag', num.format(ertragReal) + ' kWh'],
        ['Benötigte Dachfläche', num.format(flaeche) + ' m²'],
        ['Reale Deckung des Verbrauchs', num.format(realDeckung) + ' %'],
        ['Flächenleistung', num.format(flLeistung) + ' kWp/m²'],
        ['Ertrag pro m²', num.format(ertragQm) + ' kWh/m²/Jahr'],
        ['Überschuss über Verbrauch', num.format(ueberschuss) + ' kWh'],
    ]);
}
            case 'stromspeicher-dimensionierung-rechner': {
    const verbrauch = n('verbrauch'); const evOhne = n('ev_ohne'); const evMit = n('ev_mit');
    const ertrag = n('pv_ertrag'); const sp = n('strompreis'); const eins = n('einspeise'); const preisKwh = n('speicherpreis');
    const empfehlung = Math.round(verbrauch / 1000 * 2) / 2;
    const evOhneKwh = ertrag * evOhne / 100;
    const evMitKwh = ertrag * evMit / 100;
    const zusatz = Math.max(0, evMitKwh - evOhneKwh);
    const erspKwh = sp - eins;
    const ersp = zusatz * erspKwh / 100;
    const autarkie = verbrauch > 0 ? Math.min(100, evMitKwh / verbrauch * 100) : 0;
    const kosten = empfehlung * preisKwh;
    const amort = ersp > 0 ? kosten / ersp : 0;
    const zyklen = empfehlung > 0 ? zusatz / empfehlung : 0;
    return rows([
        ['Empfohlene Speichergröße', num.format(empfehlung) + ' kWh'],
        ['Eigenverbrauch ohne Speicher', num.format(evOhneKwh) + ' kWh'],
        ['Eigenverbrauch mit Speicher', num.format(evMitKwh) + ' kWh'],
        ['Zusätzlicher Eigenverbrauch', num.format(zusatz) + ' kWh'],
        ['Ersparnis durch mehr Eigenverbrauch', euro.format(ersp) + '/Jahr'],
        ['Autarkiegrad mit Speicher', num.format(autarkie) + ' %'],
        ['Ersparnis je gespeicherter kWh', num.format(erspKwh) + ' ct'],
        ['Speicherkosten', euro.format(kosten)],
        ['Amortisationsdauer', ersp > 0 ? num.format(amort) + ' Jahre' : 'keine Amortisation'],
        ['Ladezyklen pro Jahr (ca.)', num.format(zyklen)],
    ]);
}
            case 'balkonkraftwerk-amortisation-rechner': {
    const modulW = n('modulleistung'); const spez = n('spez_ertrag');
    const evQuote = n('ev_quote'); const sp = n('strompreis'); const anschaffung = n('anschaffung');
    const leistung = modulW / 1000;
    const ertrag = leistung * spez;
    const selbst = ertrag * evQuote / 100;
    const ersp = selbst * sp / 100;
    const amort = ersp > 0 ? anschaffung / ersp : 0;
    const ersp20 = ersp * 20 - anschaffung;
    const eingespeist = Math.max(0, ertrag - selbst);
    const rendite = anschaffung > 0 ? ersp / anschaffung * 100 : 0;
    const gestehung = ertrag > 0 ? anschaffung / (ertrag * 20) * 100 : 0;
    const co2 = ertrag * 0.38;
    return rows([
        ['Installierte Leistung', num.format(leistung) + ' kWp'],
        ['Jahresertrag', num.format(ertrag) + ' kWh'],
        ['Selbst genutzter Strom', num.format(selbst) + ' kWh'],
        ['Jährliche Ersparnis', euro.format(ersp)],
        ['Amortisationsdauer', ersp > 0 ? num.format(amort) + ' Jahre' : 'keine Amortisation'],
        ['Ersparnis über 20 Jahre', euro.format(ersp20)],
        ['Eingespeister Anteil', num.format(eingespeist) + ' kWh'],
        ['Rendite pro Jahr', num.format(rendite) + ' %'],
        ['Stromgestehungskosten (20 J.)', num.format(gestehung) + ' ct/kWh'],
        ['CO2-Einsparung', num.format(co2) + ' kg/Jahr'],
    ]);
}
            case 'e-auto-vs-verbrenner-kostenvergleich-rechner': {
    const km = n('fahrleistung');
    const evEnergie100 = n('ev_verbrauch') * n('strompreis') / 100;
    const verbEnergie100 = n('verb_verbrauch') * n('spritpreis');
    const evEnergieJahr = evEnergie100 * km / 100;
    const verbEnergieJahr = verbEnergie100 * km / 100;
    const evGesamt = evEnergieJahr + n('wartung_ev');
    const verbGesamt = verbEnergieJahr + n('wartung_verb');
    const ersparnis = verbGesamt - evGesamt;
    const amortJahre = ersparnis > 0 ? n('mehrpreis') / ersparnis : 0;
    const ersparnisProKm = km > 0 ? ersparnis / km : 0;
    const breakEvenKm = ersparnisProKm > 0 ? n('mehrpreis') / ersparnisProKm : 0;
    const ersparnis10 = ersparnis * 10 - n('mehrpreis');
    return rows([
        ['Energiekosten E-Auto je 100 km', euro.format(evEnergie100)],
        ['Energiekosten Verbrenner je 100 km', euro.format(verbEnergie100)],
        ['Energiekosten E-Auto / Jahr', euro.format(evEnergieJahr)],
        ['Energiekosten Verbrenner / Jahr', euro.format(verbEnergieJahr)],
        ['Gesamtkosten E-Auto / Jahr', euro.format(evGesamt)],
        ['Gesamtkosten Verbrenner / Jahr', euro.format(verbGesamt)],
        ['Jährliche Ersparnis E-Auto', euro.format(ersparnis)],
        ['Amortisation Mehrpreis', ersparnis > 0 ? num.format(amortJahre) + ' Jahre' : 'nie (kein Vorteil)'],
        ['Break-even-Laufleistung', breakEvenKm > 0 ? num.format(breakEvenKm) + ' km' : '–'],
        ['Ersparnis über 10 Jahre', euro.format(ersparnis10)],
    ]);
}
            case 'e-auto-ladekosten-zuhause-unterwegs-rechner': {
    const km = n('fahrleistung');
    const gesamtKwh = km / 100 * n('verbrauch');
    const aHeim = clamp(n('anteil_heim'), 0, 100);
    const aAc = clamp(n('anteil_ac'), 0, 100);
    const aHpc = Math.max(0, 100 - aHeim - aAc);
    const kwhHeim = gesamtKwh * aHeim / 100;
    const kwhAc = gesamtKwh * aAc / 100;
    const kwhHpc = gesamtKwh * aHpc / 100;
    const kostenHeim = kwhHeim * n('preis_heim') / 100;
    const kostenAc = kwhAc * n('preis_ac') / 100;
    const kostenHpc = kwhHpc * n('preis_hpc') / 100;
    const gesamt = kostenHeim + kostenAc + kostenHpc;
    const ctProKwh = gesamtKwh > 0 ? gesamt / gesamtKwh * 100 : 0;
    const eurPro100 = km > 0 ? gesamt / km * 100 : 0;
    const mehrHpc = gesamtKwh * n('preis_hpc') / 100 - gesamt;
    const ersparnisHeim = kwhHeim * (n('preis_hpc') - n('preis_heim')) / 100;
    return rows([
        ['Gesamtverbrauch / Jahr', num.format(gesamtKwh) + ' kWh'],
        ['Anteil HPC-Schnellladen', num.format(aHpc) + ' %'],
        ['Kosten Heimladen / Jahr', euro.format(kostenHeim)],
        ['Kosten AC öffentlich / Jahr', euro.format(kostenAc)],
        ['Kosten HPC-Schnellladen / Jahr', euro.format(kostenHpc)],
        ['Gesamte Ladekosten / Jahr', euro.format(gesamt)],
        ['Ø Kosten je kWh', num.format(ctProKwh) + ' ct'],
        ['Ø Kosten je 100 km', euro.format(eurPro100)],
        ['Mehrkosten bei reinem HPC-Laden', euro.format(mehrHpc)],
        ['Ersparnis durch Heimladen-Anteil', euro.format(ersparnisHeim)],
    ]);
}
            case 'wallbox-ladezeit-leistung-rechner': {
    const kap = n('kapazitaet');
    const hub = Math.max(0, n('soc_ziel') - n('soc_start'));
    const verlustF = clamp(n('ladeverlust'), 0, 95) / 100;
    const nutzF = 1 - verlustF;
    const energieAkku = kap * hub / 100;
    const energieNetz = nutzF > 0 ? energieAkku / nutzF : 0;
    const leistung = n('ladeleistung');
    const ladezeitH = leistung > 0 ? energieNetz / leistung : 0;
    const stunden = Math.floor(ladezeitH);
    const minuten = Math.round((ladezeitH - stunden) * 60);
    const verbrauch = n('verbrauch');
    const reichweite = verbrauch > 0 ? energieAkku / verbrauch * 100 : 0;
    const kostenLaden = energieNetz * n('strompreis') / 100;
    const kostenPro100 = nutzF > 0 ? verbrauch * n('strompreis') / 100 / nutzF : 0;
    const verlustEnergie = energieNetz - energieAkku;
    const effLeistung = leistung * nutzF;
    const vollH = (leistung > 0 && nutzF > 0) ? kap / nutzF / leistung : 0;
    return rows([
        ['Ladehub', num.format(hub) + ' %'],
        ['Energie in den Akku', num.format(energieAkku) + ' kWh'],
        ['Aus dem Netz gezogen', num.format(energieNetz) + ' kWh'],
        ['Ladezeit', stunden + ' h ' + minuten + ' min'],
        ['Ladezeit dezimal', num.format(ladezeitH) + ' h'],
        ['Nachgeladene Reichweite', num.format(reichweite) + ' km'],
        ['Kosten des Ladevorgangs', euro.format(kostenLaden)],
        ['Kosten je 100 km', euro.format(kostenPro100)],
        ['Verlustenergie (Ladeverlust)', num.format(verlustEnergie) + ' kWh'],
        ['Effektive Ladeleistung in Akku', num.format(effLeistung) + ' kW'],
        ['Vollladung 0–100 %', num.format(vollH) + ' h'],
    ]);
}
            case 'e-auto-reichweite-wetter-temperatur-rechner': {
    const kap = n('kapazitaet');
    const wltp = n('wltp_verbrauch');
    const tempF = 1 + Math.max(0, 15 - n('temperatur')) * 0.012;
    const tempoF = 1 + Math.max(0, n('tempo') - 100) * 0.006;
    const heizF = value('heizung') === 'ja' ? 1.10 : 1.0;
    const effVerbrauch = wltp * tempF * tempoF * heizF;
    const wltpReichweite = wltp > 0 ? kap / wltp * 100 : 0;
    const realReichweite = effVerbrauch > 0 ? kap / effVerbrauch * 100 : 0;
    const verlustKm = wltpReichweite - realReichweite;
    const verlustProz = wltpReichweite > 0 ? verlustKm / wltpReichweite * 100 : 0;
    const mehrverbrauch = wltp > 0 ? (effVerbrauch / wltp - 1) * 100 : 0;
    const ladestopps = realReichweite > 0 ? Math.max(0, Math.ceil(600 / realReichweite) - 1) : 0;
    return rows([
        ['Temperaturfaktor', '×' + num.format(tempF)],
        ['Tempofaktor', '×' + num.format(tempoF)],
        ['Heizungsfaktor', '×' + num.format(heizF)],
        ['Effektiver Verbrauch', num.format(effVerbrauch) + ' kWh/100km'],
        ['WLTP-Reichweite', num.format(wltpReichweite) + ' km'],
        ['Reale Reichweite', num.format(realReichweite) + ' km'],
        ['Reichweitenverlust', num.format(verlustKm) + ' km'],
        ['Reichweitenverlust', num.format(verlustProz) + ' %'],
        ['Mehrverbrauch ggü. WLTP', num.format(mehrverbrauch) + ' %'],
        ['Ladestopps auf 600 km', num.format(ladestopps)],
    ]);
}
            case 'heizlast-ueberschlag-rechner': {
    const std = value('standard');
    const spez = std.indexOf('100 W') >= 0 ? 100 : std.indexOf('75 W') >= 0 ? 75 : std.indexOf('50 W') >= 0 ? 50 : std.indexOf('40 W') >= 0 ? 40 : std.indexOf('25 W') >= 0 ? 25 : 50;
    const flaeche = n('wohnflaeche');
    const hoehe = n('raumhoehe');
    const heizlast = spez * flaeche / 1000;
    const heizlastHoehe = heizlast * (hoehe > 0 ? hoehe : 2.5) / 2.5;
    const wpLeistung = heizlastHoehe * 1.1;
    const jahresbedarf = heizlastHoehe * n('vollbenutzungsstunden');
    const spezBedarf = flaeche > 0 ? jahresbedarf / flaeche : 0;
    const volumen = flaeche * hoehe;
    const einordnung = spezBedarf <= 0 ? '–' : spezBedarf < 50 ? 'sehr gut' : spezBedarf <= 100 ? 'gut' : spezBedarf <= 160 ? 'mittel' : 'schlecht';
    const heizlastVol = volumen > 0 ? heizlastHoehe * 1000 / volumen : 0;
    const puffer = wpLeistung * 20;
    return rows([
        ['Spez. Heizlast (Standard)', num.format(spez) + ' W/m²'],
        ['Gebäudeheizlast (Fläche)', num.format(heizlast) + ' kW'],
        ['Heizlast höhenkorrigiert', num.format(heizlastHoehe) + ' kW'],
        ['Empfohlene WP-Leistung (+10 %)', num.format(wpLeistung) + ' kW'],
        ['Jahresheizwärmebedarf', num.format(jahresbedarf) + ' kWh'],
        ['Spez. Heizwärmebedarf', num.format(spezBedarf) + ' kWh/m²·a'],
        ['Beheiztes Volumen', num.format(volumen) + ' m³'],
        ['Energetische Einordnung', einordnung],
        ['Heizlast je m³', num.format(heizlastVol) + ' W/m³'],
        ['Empf. Pufferspeicher (ca.)', num.format(puffer) + ' l'],
    ]);
}
            case 'gasheizung-vs-pelletheizung-rechner': {
    const bedarf = n('heizbedarf');
    const HW_PELLET = 4800;
    const gasEta = n('gas_eta') > 0 ? n('gas_eta') / 100 : 0.9;
    const pelletEta = n('pellet_eta') > 0 ? n('pellet_eta') / 100 : 0.88;
    const jaz = n('jaz') > 0 ? n('jaz') : 3.5;
    const gasBrennstoff = bedarf / gasEta;
    const gasEnergie = gasBrennstoff * n('gaspreis') / 100;
    const gasVoll = gasEnergie + n('wartung_gas');
    const pelletTonnen = bedarf / pelletEta / HW_PELLET;
    const pelletEnergie = pelletTonnen * n('pelletpreis');
    const pelletVoll = pelletEnergie + n('wartung_pellet');
    const wpStrom = bedarf / jaz;
    const wpEnergie = wpStrom * n('strompreis') / 100;
    const wpVoll = wpEnergie + n('wartung_wp');
    const gasCtKwh = n('gaspreis') / gasEta;
    const wpCtKwh = n('strompreis') / jaz;
    const pelletCtKwh = (n('pelletpreis') / HW_PELLET) / pelletEta;
    const liste = [['Gas', gasVoll], ['Pellet', pelletVoll], ['Wärmepumpe', wpVoll]];
    const sortiert = liste.slice().sort((a, b) => a[1] - b[1]);
    const guenstig = sortiert[0];
    const teuer = sortiert[2];
    const sparpotenzial = teuer[1] - guenstig[1];
    return rows([
        ['Gas: Vollkosten / Jahr', euro.format(gasVoll)],
        ['Pellet: Vollkosten / Jahr', euro.format(pelletVoll)],
        ['Wärmepumpe: Vollkosten / Jahr', euro.format(wpVoll)],
        ['Gas je kWh Wärme', num.format(gasCtKwh) + ' ct'],
        ['Pellet je kWh Wärme', num.format(pelletCtKwh) + ' ct'],
        ['Wärmepumpe je kWh Wärme', num.format(wpCtKwh) + ' ct'],
        ['Pelletbedarf', num.format(pelletTonnen) + ' t/Jahr'],
        ['Günstigstes System', guenstig[0] + ' (' + euro.format(guenstig[1]) + ')'],
        ['Teuerstes System', teuer[0] + ' (' + euro.format(teuer[1]) + ')'],
        ['Sparpotenzial günstig vs. teuer', euro.format(sparpotenzial) + ' / Jahr'],
        ['Ranking', sortiert.map(s => s[0]).join(' < ')],
    ]);
}
            case 'co2-heizung-vergleich-rechner': {
    const bedarf = n('heizbedarf');
    const eta = n('eta_fossil') > 0 ? n('eta_fossil') / 100 : 0.9;
    const jaz = n('jaz') > 0 ? n('jaz') : 3.5;
    const co2Preis = n('co2_preis');
    const brennstoff = bedarf / eta;
    const co2Gas = brennstoff * 201 / 1000;
    const co2Oel = brennstoff * 266 / 1000;
    const co2Pellet = brennstoff * 36 / 1000;
    const co2Wp = (bedarf / jaz) * n('co2_strom') / 1000;
    const kostenGas = co2Gas / 1000 * co2Preis;
    const kostenOel = co2Oel / 1000 * co2Preis;
    const liste = [['Gas', co2Gas], ['Öl', co2Oel], ['Pellet', co2Pellet], ['Wärmepumpe', co2Wp]];
    const sieger = liste.slice().sort((a, b) => a[1] - b[1])[0];
    const einsparKg = co2Gas - co2Wp;
    const einsparProz = co2Gas > 0 ? einsparKg / co2Gas * 100 : 0;
    const einsparKosten = kostenGas - (co2Wp / 1000 * co2Preis);
    const co2WpKwh = bedarf > 0 ? co2Wp * 1000 / bedarf : 0;
    return rows([
        ['CO₂ Gas / Jahr', num.format(co2Gas) + ' kg'],
        ['CO₂ Öl / Jahr', num.format(co2Oel) + ' kg'],
        ['CO₂ Pellet / Jahr', num.format(co2Pellet) + ' kg'],
        ['CO₂ Wärmepumpe / Jahr', num.format(co2Wp) + ' kg'],
        ['CO₂-Kosten Gas', euro.format(kostenGas)],
        ['CO₂-Kosten Öl', euro.format(kostenOel)],
        ['Klimasieger', sieger[0] + ' (' + num.format(sieger[1]) + ' kg)'],
        ['CO₂-Einsparung WP vs. Gas', num.format(einsparKg) + ' kg'],
        ['CO₂-Einsparung WP vs. Gas', num.format(einsparProz) + ' %'],
        ['Eingesparte CO₂-Kosten WP vs. Gas', euro.format(einsparKosten) + ' / Jahr'],
        ['CO₂ je kWh Wärme (WP)', num.format(co2WpKwh) + ' g'],
    ]);
}
            case 'warmwasser-waermepumpe-vs-durchlauferhitzer-rechner': {
    const personen = n('personen');
    const literPP = n('liter_pro_person');
    const dT = n('temp_diff');
    const cop = n('cop_ww') > 0 ? n('cop_ww') : 3;
    const gasEta = n('gas_eta') > 0 ? n('gas_eta') / 100 : 0.85;
    const bedarfTag = personen * literPP;
    const energieJahr = bedarfTag * 365 * dT * 4.186 / 3600;
    const kostenDirekt = energieJahr * n('strompreis') / 100;
    const kostenWp = energieJahr / cop * n('strompreis') / 100;
    const kostenGas = energieJahr / gasEta * n('gaspreis') / 100;
    const liste = [['Direktstrom', kostenDirekt], ['Warmwasser-WP', kostenWp], ['Gas', kostenGas]];
    const guenstig = liste.slice().sort((a, b) => a[1] - b[1])[0];
    const sparWp = kostenDirekt - kostenWp;
    const kostenProPerson = personen > 0 ? guenstig[1] / personen : 0;
    const m3Jahr = bedarfTag * 365 / 1000;
    const kostenProM3Wp = m3Jahr > 0 ? kostenWp / m3Jahr : 0;
    const stromWp = energieJahr / cop;
    const co2EinsparKg = (energieJahr - stromWp) * 380 / 1000;
    return rows([
        ['Warmwasserbedarf', num.format(bedarfTag) + ' l/Tag'],
        ['Wärmeenergie / Jahr', num.format(energieJahr) + ' kWh'],
        ['Kosten Direktstrom / Jahr', euro.format(kostenDirekt)],
        ['Kosten Warmwasser-WP / Jahr', euro.format(kostenWp)],
        ['Kosten Gas / Jahr', euro.format(kostenGas)],
        ['Günstigste Variante', guenstig[0] + ' (' + euro.format(guenstig[1]) + ')'],
        ['Sparpotenzial WP vs. Direktstrom', euro.format(sparWp) + ' / Jahr'],
        ['Kosten pro Person (günstigste)', euro.format(kostenProPerson)],
        ['Kosten je m³ Warmwasser (WP)', euro.format(kostenProM3Wp)],
        ['Stromverbrauch WP / Jahr', num.format(stromWp) + ' kWh'],
        ['CO₂-Einsparung WP vs. Direktstrom', num.format(co2EinsparKg) + ' kg/Jahr'],
    ]);
}
            case 'netzbezug-vor-nach-pv-rechner': {
    const haushalt = n('haushalt');
    const pvErtrag = n('pv_ertrag');
    const evQuote = clamp(n('ev_quote'), 0, 100);
    const strompreis = n('strompreis');
    const evBedarf = n('fahrleistung') / 100 * n('ev_verbrauch');
    const rechnungVor = haushalt * strompreis / 100;
    const eigenverbrauch = pvErtrag * evQuote / 100;
    const ueberschuss = Math.max(0, pvErtrag - eigenverbrauch);
    const solarAutoWunsch = evBedarf * clamp(n('solaranteil_laden'), 0, 100) / 100;
    const solarAuto = Math.min(solarAutoWunsch, ueberschuss);
    const netzHaushalt = Math.max(0, haushalt - Math.max(0, eigenverbrauch - solarAuto));
    const netzAuto = Math.max(0, evBedarf - solarAuto);
    const einspeisung = Math.max(0, ueberschuss - solarAuto);
    const kostenNach = (netzHaushalt + netzAuto) * strompreis / 100;
    const einspeiseErloes = einspeisung * n('einspeise') / 100;
    const nettoNach = kostenNach - einspeiseErloes;
    const veraenderung = nettoNach - rechnungVor;
    return rows([
        ['E-Auto-Strombedarf / Jahr', num.format(evBedarf) + ' kWh'],
        ['Stromrechnung vor PV', euro.format(rechnungVor)],
        ['PV-Eigenverbrauch (Haushalt)', num.format(eigenverbrauch) + ' kWh'],
        ['Solarstrom fürs Auto', num.format(solarAuto) + ' kWh'],
        ['Netzbezug Haushalt nach PV', num.format(netzHaushalt) + ' kWh'],
        ['Netzbezug Auto', num.format(netzAuto) + ' kWh'],
        ['Einspeisung ins Netz', num.format(einspeisung) + ' kWh'],
        ['Stromkosten nach PV + Auto', euro.format(kostenNach)],
        ['Einspeiseerlös', euro.format(einspeiseErloes)],
        ['Netto-Stromrechnung nach PV + Auto', euro.format(nettoNach)],
        ['Veränderung ggü. vor PV', euro.format(veraenderung) + ' / Jahr'],
    ]);
}
            case 'wallbox-pv-ueberschussladen-rechner': {
    const verbrauch = n('verbrauch');
    const ladebedarf = n('fahrleistung') / 100 * verbrauch;
    const solarVerfuegbar = n('pv_ueberschuss') * clamp(n('nutzbarer_anteil'), 0, 100) / 100;
    const solarGeladen = Math.min(ladebedarf, solarVerfuegbar);
    const netzGeladen = Math.max(0, ladebedarf - solarGeladen);
    const deckung = ladebedarf > 0 ? solarGeladen / ladebedarf * 100 : 0;
    const ersparnis = solarGeladen * Math.max(0, n('strompreis') - n('einspeise')) / 100;
    const kostenRest = netzGeladen * n('strompreis') / 100;
    const solarKm = verbrauch > 0 ? solarGeladen / verbrauch * 100 : 0;
    const kostenGesamt = kostenRest;
    const proKm100 = n('fahrleistung') > 0 ? kostenGesamt / n('fahrleistung') * 100 : 0;
    const ungenutzt = Math.max(0, solarVerfuegbar - solarGeladen);
    return rows([
        ['Ladebedarf gesamt / Jahr', num.format(ladebedarf) + ' kWh'],
        ['Verfügbarer Solarstrom fürs Laden', num.format(solarVerfuegbar) + ' kWh'],
        ['Solar geladen', num.format(solarGeladen) + ' kWh'],
        ['Netz geladen', num.format(netzGeladen) + ' kWh'],
        ['Solar-Deckung des Ladebedarfs', num.format(deckung) + ' %'],
        ['Ersparnis ggü. Netzladen', euro.format(ersparnis) + ' / Jahr'],
        ['Kosten Restladung aus Netz', euro.format(kostenRest)],
        ['Reine Solar-Kilometer', num.format(solarKm) + ' km'],
        ['Ladekosten gesamt / Jahr', euro.format(kostenGesamt)],
        ['Ø Ladekosten je 100 km', euro.format(proKm100)],
        ['Ungenutzter Überschuss', num.format(ungenutzt) + ' kWh'],
    ]);
}
            case 'stromverbrauch-grossverbraucher-rechner': {
    const leistung = n('leistung'); const stundenTag = clamp(n('stunden_tag'), 0, 24); const tageJahr = clamp(n('tage_jahr'), 0, 366);
    const standbyWatt = n('standby_watt'); const strompreis = n('strompreis'); const haushalt = n('haushaltsrechnung');
    const betriebKwh = leistung * stundenTag * tageJahr / 1000;
    const standbyStd = Math.max(0, 24 - stundenTag) * tageJahr;
    const standbyKwh = standbyWatt * standbyStd / 1000;
    const gesamtKwh = betriebKwh + standbyKwh;
    const betriebKosten = betriebKwh * strompreis / 100;
    const standbyKosten = standbyKwh * strompreis / 100;
    const gesamtKosten = gesamtKwh * strompreis / 100;
    const anteil = haushalt > 0 ? gesamtKosten / haushalt * 100 : 0;
    const proStunde = leistung / 1000 * strompreis;
    return rows([
        ['Betriebsverbrauch', num.format(Math.round(betriebKwh)) + ' kWh/Jahr'],
        ['Standby-Stunden', num.format(Math.round(standbyStd)) + ' h/Jahr'],
        ['Standby-Verbrauch', num.format(Math.round(standbyKwh)) + ' kWh/Jahr'],
        ['Gesamtverbrauch', num.format(Math.round(gesamtKwh)) + ' kWh/Jahr'],
        ['Betriebskosten', euro.format(betriebKosten) + '/Jahr'],
        ['Standby-Kosten', euro.format(standbyKosten) + '/Jahr'],
        ['Gesamtkosten Gerät', euro.format(gesamtKosten) + '/Jahr'],
        ['Anteil an Stromrechnung', num.format(anteil) + ' %'],
        ['Sparpotenzial durch Standby-Abschaltung', euro.format(standbyKosten) + '/Jahr'],
        ['Kosten pro Monat', euro.format(gesamtKosten / 12)],
        ['Kosten pro Betriebsstunde', num.format(proStunde) + ' ct'],
    ]);
}
            case 'pv-degradation-langzeitertrag-rechner': {
    const anfang = n('anfangsertrag'); const degr = clamp(n('degradation'), 0, 100); const jahre = clamp(Math.round(n('jahre')), 0, 60); const wert = n('wert');
    const q = 1 - degr / 100;
    const letztesJahr = jahre > 0 ? anfang * Math.pow(q, jahre - 1) : 0;
    let kumuliert;
    if (jahre <= 0) { kumuliert = 0; }
    else if (Math.abs(1 - q) < 1e-9) { kumuliert = anfang * jahre; }
    else { kumuliert = anfang * (1 - Math.pow(q, jahre)) / (1 - q); }
    const avg = jahre > 0 ? kumuliert / jahre : 0;
    const restleistung = jahre > 0 ? Math.pow(q, jahre - 1) * 100 : 100;
    const verlustLetztes = anfang - letztesJahr;
    const gesamtwert = kumuliert * wert / 100;
    const verlustKwh = anfang * jahre - kumuliert;
    const verlustEur = verlustKwh * wert / 100;
    const proTag = anfang / 365;
    let jahreBis50 = 0;
    if (q > 0 && q < 1) { jahreBis50 = Math.ceil(Math.log(0.5) / Math.log(q) + 1); }
    return rows([
        ['Ertrag im letzten Jahr', num.format(Math.round(letztesJahr)) + ' kWh'],
        ['Kumulierter Gesamtertrag', num.format(Math.round(kumuliert)) + ' kWh'],
        ['Durchschnittlicher Jahresertrag', num.format(Math.round(avg)) + ' kWh'],
        ['Restleistung am Ende', num.format(restleistung) + ' %'],
        ['Ertragsverlust letztes vs. erstes Jahr', num.format(Math.round(verlustLetztes)) + ' kWh'],
        ['Gesamtertragswert', euro.format(gesamtwert)],
        ['Mindererzeugung durch Degradation', num.format(Math.round(verlustKwh)) + ' kWh'],
        ['Wertverlust durch Degradation', euro.format(verlustEur)],
        ['Ø Ertrag pro Tag im 1. Jahr', num.format(proTag) + ' kWh'],
        ['Jahre bis 50 % Restleistung', jahreBis50 > 0 ? num.format(jahreBis50) + ' Jahre' : 'keine Degradation'],
    ]);
}
            case 'dynamischer-stromtarif-ersparnis-rechner': {
    const verbrauch = n('verbrauch'); const festtarif = n('festtarif'); const boerse = n('boersenpreis');
    const aufschlag = n('aufschlag'); const verschiebbar = clamp(n('verschiebbar'), 0, 100); const guenstig = clamp(n('guenstig_faktor'), 0, 100);
    const grundDiff = n('grundgebuehr_diff');
    const festKosten = verbrauch * festtarif / 100;
    const effektivStd = boerse + aufschlag;
    const verschiebKwh = verbrauch * verschiebbar / 100;
    const restKwh = verbrauch - verschiebKwh;
    const kostenVerschieb = verschiebKwh * (boerse * guenstig / 100 + aufschlag) / 100;
    const kostenRest = restKwh * (boerse + aufschlag) / 100;
    const dynGesamt = kostenVerschieb + kostenRest + grundDiff;
    const ersparnis = festKosten - dynGesamt;
    const mischpreis = verbrauch > 0 ? (dynGesamt - grundDiff) / verbrauch * 100 : 0;
    const ersparnisProz = festKosten > 0 ? ersparnis / festKosten * 100 : 0;
    return rows([
        ['Festtarif-Kosten', euro.format(festKosten) + '/Jahr'],
        ['Dyn. Effektivpreis Standardverbrauch', num.format(effektivStd) + ' ct/kWh'],
        ['Verschiebbarer Verbrauch', num.format(Math.round(verschiebKwh)) + ' kWh'],
        ['Nicht verschiebbarer Verbrauch', num.format(Math.round(restKwh)) + ' kWh'],
        ['Kosten verschiebbarer Anteil', euro.format(kostenVerschieb)],
        ['Kosten Restanteil', euro.format(kostenRest)],
        ['Dynamische Gesamtkosten', euro.format(dynGesamt) + '/Jahr'],
        ['Jährliche Ersparnis', euro.format(ersparnis)],
        ['Effektiver Mischpreis dynamisch', num.format(mischpreis) + ' ct/kWh'],
        ['Ersparnis', num.format(ersparnisProz) + ' %'],
    ]);
}
            case 'rechenzentrum-pue-stromkosten-rechner': {
    const leistungW = n('leistung_w'); const pue = Math.max(0, n('pue')); const strompreis = n('strompreis'); const co2Faktor = n('co2_faktor');
    const effLeistung = leistungW * pue;
    const verbrauchServer = leistungW * 24 * 365 / 1000;
    const verbrauchPue = effLeistung * 24 * 365 / 1000;
    const kostenServer = verbrauchServer * strompreis / 100;
    const kostenPue = verbrauchPue * strompreis / 100;
    const mehrkosten = kostenPue - kostenServer;
    const proMonat = kostenPue / 12;
    const co2Jahr = verbrauchPue * co2Faktor / 1000;
    const co2Monat = co2Jahr / 12;
    const proTag = verbrauchPue / 365;
    const overhead = pue > 0 ? (pue - 1) / pue * 100 : 0;
    return rows([
        ['Effektive Leistung inkl. PUE', num.format(Math.round(effLeistung)) + ' W'],
        ['Verbrauch Server pur', num.format(Math.round(verbrauchServer)) + ' kWh/Jahr'],
        ['Verbrauch inkl. Kühlung', num.format(Math.round(verbrauchPue)) + ' kWh/Jahr'],
        ['Stromkosten Server pur', euro.format(kostenServer) + '/Jahr'],
        ['Stromkosten inkl. PUE', euro.format(kostenPue) + '/Jahr'],
        ['Mehrkosten durch Kühlung', euro.format(mehrkosten) + '/Jahr'],
        ['Stromkosten pro Monat', euro.format(proMonat)],
        ['CO2-Ausstoß pro Jahr', num.format(co2Jahr) + ' kg'],
        ['CO2 pro Monat', num.format(co2Monat) + ' kg'],
        ['Verbrauch pro Tag', num.format(proTag) + ' kWh'],
        ['Overhead-Anteil Infrastruktur', num.format(overhead) + ' %'],
    ]);
}
            case 'geg-65-prozent-erneuerbare-check-rechner': {
    const heizung = value('heizung'); const pvAnteil = clamp(n('pvAnteil'), 0, 100); const gasGruen = clamp(n('gasGruen'), 0, 100); const baujahrNeu = value('baujahrNeu');
    const baseMap = {
        'Wärmepumpe (Luft/Wasser)': 100,
        'Wärmepumpe (Sole/Erdwärme)': 100,
        'Fernwärme': 65,
        'Biomasse / Holzpellets': 100,
        'Gasheizung (fossil)': 0,
        'Gas-Hybrid mit Wärmepumpe': 50,
        'Stromdirektheizung': 0,
        'Solarthermie als Hauptheizung': 100
    };
    const base = baseMap[heizung] !== undefined ? baseMap[heizung] : 0;
    const istGas = heizung.indexOf('Gas') === 0;
    const zusatz = Math.min(35, pvAnteil) + (istGas ? Math.min(100, gasGruen) : 0);
    const gesamt = Math.min(100, base + zusatz);
    const erfuellt = gesamt >= 65 ? 'Ja, GEG-konform' : 'Nein';
    const fehlend = Math.max(0, 65 - gesamt);
    const neubauHinweis = baujahrNeu === 'ja' ? 'Im Neubaugebiet gilt die 65-%-Pflicht bereits vollumfänglich' : 'Im Bestand gelten ggf. Übergangsfristen und kommunale Wärmeplanung';
    const fossilRest = 100 - gesamt;
    const ampel = gesamt >= 65 ? 'grün' : gesamt >= 50 ? 'gelb' : 'rot';
    const empfehlung = gesamt >= 65 ? 'Diese Heizung erfüllt die Vorgabe – Einbau ohne weitere EE-Auflage möglich.' : istGas ? 'Erhöhe den Anteil grünes Gas/Wasserstoff oder kombiniere mit einer Wärmepumpe.' : 'Ergänze erneuerbare Komponenten (z. B. Solarthermie/PV-Wärme) oder wähle eine EE-Heizung.';
    return rows([
        ['Anerkannter EE-Basisanteil', num.format(base) + ' %'],
        ['Zusätzlicher EE-Anteil', num.format(zusatz) + ' %'],
        ['Gesamt-EE-Anteil', num.format(gesamt) + ' %'],
        ['65-%-Pflicht erfüllt?', erfuellt],
        ['Fehlender Anteil zur 65-%-Grenze', num.format(fehlend) + ' %'],
        ['Fossiler Restanteil', num.format(fossilRest) + ' %'],
        ['Ampel-Bewertung', ampel],
        ['Neubau-Hinweis', neubauHinweis],
        ['Empfehlung', empfehlung],
    ]);
}
            case 'beg-heizungsfoerderung-zuschuss-rechner': {
    const kosten = n('kosten'); const we = Math.max(0, Math.round(n('wohneinheiten')));
    const selbst = value('selbstgenutzt'); const einkommen = value('einkommen'); const geschw = value('geschwindigkeit'); const wp = value('waermepumpe');
    const hoechstgrenze = 30000 + Math.max(0, Math.min(we - 1, 5)) * 15000 + Math.max(0, we - 6) * 8000;
    const anrechenbar = Math.min(kosten, hoechstgrenze);
    const grund = 30;
    const einkBonus = (selbst === 'ja' && einkommen === 'ja') ? 30 : 0;
    const geschwBonus = (selbst === 'ja' && geschw === 'ja') ? 20 : 0;
    const effBonus = (wp === 'ja') ? 5 : 0;
    const quoteVor = grund + einkBonus + geschwBonus + effBonus;
    const quoteEff = Math.min(70, quoteVor);
    const zuschuss = anrechenbar * quoteEff / 100;
    const eigenanteil = kosten - zuschuss;
    const hinweis = quoteVor > 70 ? 'auf 70 % gedeckelt' : 'vollständig anrechenbar';
    return rows([
        ['Förderfähige Höchstgrenze', euro.format(hoechstgrenze)],
        ['Anrechenbare Kosten', euro.format(anrechenbar)],
        ['Grundförderung', num.format(grund) + ' %'],
        ['Einkommensbonus', num.format(einkBonus) + ' %'],
        ['Geschwindigkeitsbonus', num.format(geschwBonus) + ' %'],
        ['Effizienzbonus', num.format(effBonus) + ' %'],
        ['Förderquote vor Deckel', num.format(quoteVor) + ' %'],
        ['Effektive Förderquote', num.format(quoteEff) + ' %'],
        ['Voraussichtlicher Zuschuss', euro.format(zuschuss)],
        ['Eigenanteil', euro.format(eigenanteil)],
        ['Hinweis', hinweis],
    ]);
}
            case 'daemmung-einsparung-rechner': {
    const flaeche = n('flaeche'); const uAlt = n('uAlt'); const uNeu = n('uNeu'); const hgt = n('heizgradtage');
    const wirkungsgrad = n('wirkungsgrad'); const preis = n('preis'); const investition = n('investition');
    const verlustVor = flaeche * uAlt * hgt / 1000;
    const verlustNach = flaeche * uNeu * hgt / 1000;
    const einsparKwh = Math.max(0, (uAlt - uNeu)) * flaeche * hgt / 1000;
    const reduktion = uAlt > 0 ? (uAlt - uNeu) / uAlt * 100 : 0;
    const wgFaktor = wirkungsgrad > 0 ? wirkungsgrad / 100 : 1;
    const nutzEinspar = einsparKwh / wgFaktor;
    const kostenErspar = nutzEinspar * preis;
    const co2Einspar = nutzEinspar * 0.201;
    const amort = kostenErspar > 0 ? investition / kostenErspar : 0;
    const ersparnis30 = kostenErspar * 30 - investition;
    const bewertung = uNeu <= 0.24 ? 'erfüllt GEG-Neubau-Niveau' : 'über GEG-Anforderung';
    return rows([
        ['Wärmeverlust vorher', num.format(Math.round(verlustVor)) + ' kWh/Jahr'],
        ['Wärmeverlust nachher', num.format(Math.round(verlustNach)) + ' kWh/Jahr'],
        ['Transmissions-Einsparung', num.format(Math.round(einsparKwh)) + ' kWh/Jahr'],
        ['Reduktion Wärmeverlust', num.format(reduktion) + ' %'],
        ['Nutzenergie-Einsparung inkl. Heizung', num.format(Math.round(nutzEinspar)) + ' kWh/Jahr'],
        ['Kostenersparnis', euro.format(kostenErspar) + '/Jahr'],
        ['CO2-Einsparung', num.format(co2Einspar) + ' kg/Jahr'],
        ['Amortisationszeit', amort > 0 ? num.format(amort) + ' Jahre' : 'keine Ersparnis'],
        ['Ersparnis über 30 Jahre', euro.format(ersparnis30)],
        ['Bewertung U-Wert neu', bewertung],
    ]);
}
            case 'energieausweis-kennwert-rechner': {
    const verbrauch = n('verbrauch'); const flaeche = n('flaeche'); const warmwasser = value('warmwasser');
    const kennwert = flaeche > 0 ? verbrauch / flaeche : 0;
    const grenzen = [['A+', 30], ['A', 50], ['B', 75], ['C', 100], ['D', 130], ['E', 160], ['F', 200], ['G', 250]];
    let klasse = 'H'; let obereGrenze = 0; let naechstBesser = 0;
    for (let i = 0; i < grenzen.length; i++) {
        if (kennwert < grenzen[i][1]) { klasse = grenzen[i][0]; obereGrenze = grenzen[i][1]; naechstBesser = i > 0 ? grenzen[i - 1][1] : 0; break; }
    }
    if (klasse === 'H') { naechstBesser = 250; }
    const ampel = (klasse === 'A+' || klasse === 'A' || klasse === 'B') ? 'grün' : (klasse === 'C' || klasse === 'D') ? 'gelb' : (klasse === 'E' || klasse === 'F') ? 'orange' : 'rot';
    const bewertung = ampel === 'grün' ? 'energetisch sehr gut bis gut' : ampel === 'gelb' ? 'durchschnittlich, Sanierung lohnt teils' : ampel === 'orange' ? 'erhöhter Verbrauch, Sanierung empfohlen' : 'sehr hoher Verbrauch, dringend sanieren';
    const wwHinweis = warmwasser === 'nein' ? 'Ohne Warmwasser: +12,5 kWh/(m²·a) für Vergleichbarkeit ansetzen' : 'Warmwasser im Kennwert enthalten';
    const abstand = klasse === 'H' ? Math.max(0, kennwert - 250) : Math.max(0, kennwert - naechstBesser);
    const noetigeSenkung = abstand * flaeche;
    const vergleichNeubau = kennwert - 50;
    const heizkosten = verbrauch * 0.12;
    return rows([
        ['Energiekennwert', num.format(kennwert) + ' kWh/(m²·a)'],
        ['Energieeffizienzklasse', klasse],
        ['Ampelfarbe', ampel],
        ['Bewertung', bewertung],
        ['Warmwasser-Hinweis', wwHinweis],
        ['Abstand zur nächstbesseren Klasse', num.format(abstand) + ' kWh/(m²·a)'],
        ['Nötige Verbrauchssenkung für eine Klasse', num.format(Math.round(noetigeSenkung)) + ' kWh/Jahr'],
        ['Vergleich zum Neubau-Standard (50)', num.format(vergleichNeubau) + ' kWh/(m²·a)'],
        ['Heizkosten-Indikation', euro.format(heizkosten) + '/Jahr'],
    ]);
}
            case 'co2-heizung-haushalt-rechner': {
    const traeger = value('traeger'); const verbrauch = n('verbrauch'); const co2preis = n('co2preis'); const personen = Math.max(1, Math.round(n('personen')));
    const faktorMap = { 'Erdgas': 0.201, 'Heizöl': 0.266, 'Holzpellets': 0.036, 'Fernwärme': 0.180, 'Strom (Wärmepumpe)': 0.380 };
    const faktor = faktorMap[traeger] !== undefined ? faktorMap[traeger] : 0.201;
    const co2Kg = verbrauch * faktor;
    const co2T = co2Kg / 1000;
    const fossil = (traeger === 'Erdgas' || traeger === 'Heizöl');
    const co2Kosten = fossil ? co2T * co2preis : 0;
    const proPerson = co2Kg / personen;
    const zielVergleich = proPerson / 1000;
    const baeume = co2Kg / 10;
    const pkwKm = co2Kg / 0.15;
    const empfehlung = traeger === 'Holzpellets' ? 'Pellets gelten als weitgehend CO2-neutral – achte auf nachhaltige Herkunft.' : traeger === 'Strom (Wärmepumpe)' ? 'Mit Ökostrom sinkt der Faktor stark – prüfe einen Grünstromtarif.' : fossil ? 'Fossile Heizung: CO2-Preis steigt jährlich – Umstieg auf erneuerbar prüfen.' : 'Fernwärme dekarbonisiert sich mit dem Netzausbau zunehmend.';
    return rows([
        ['Emissionsfaktor', num.format(faktor) + ' kg/kWh'],
        ['CO2-Ausstoß', num.format(Math.round(co2Kg)) + ' kg/Jahr'],
        ['CO2-Ausstoß in Tonnen', num.format(co2T) + ' t/Jahr'],
        ['CO2-Preis betroffen?', fossil ? 'Ja (nationaler CO2-Preis)' : 'Nein'],
        ['CO2-Preis-Kosten', euro.format(co2Kosten) + '/Jahr'],
        ['CO2 pro Person', num.format(Math.round(proPerson)) + ' kg/Jahr'],
        ['Anteil am 1-Tonnen-Klimaziel pro Kopf', num.format(zielVergleich * 100) + ' %'],
        ['Bäume zur Kompensation', num.format(Math.round(baeume)) + ' Bäume'],
        ['Pkw-km-Äquivalent', num.format(Math.round(pkwKm)) + ' km'],
        ['Empfehlung', empfehlung],
    ]);
}
            case 'fenstertausch-einsparung-rechner': {
    const flaeche = n('flaeche'); const uAlt = n('uAlt'); const uNeu = n('uNeu'); const hgt = n('heizgradtage');
    const wirkungsgrad = n('wirkungsgrad'); const preis = n('preis'); const kosten = n('kosten');
    const verlustAlt = flaeche * uAlt * hgt / 1000;
    const verlustNeu = flaeche * uNeu * hgt / 1000;
    const transEinspar = Math.max(0, (uAlt - uNeu)) * flaeche * hgt / 1000;
    const wgFaktor = wirkungsgrad > 0 ? wirkungsgrad / 100 : 1;
    const nutzEinspar = transEinspar / wgFaktor;
    const reduktion = uAlt > 0 ? (uAlt - uNeu) / uAlt * 100 : 0;
    const kostenErspar = nutzEinspar * preis;
    const co2Einspar = nutzEinspar * 0.201;
    const amort = kostenErspar > 0 ? kosten / kostenErspar : 0;
    const ersparnis25 = kostenErspar * 25 - kosten;
    const bewertung = uNeu <= 1.1 ? 'erfüllt GEG-Anforderung' : 'über GEG-Grenzwert';
    return rows([
        ['Wärmeverlust alte Fenster', num.format(Math.round(verlustAlt)) + ' kWh/Jahr'],
        ['Wärmeverlust neue Fenster', num.format(Math.round(verlustNeu)) + ' kWh/Jahr'],
        ['Transmissions-Einsparung', num.format(Math.round(transEinspar)) + ' kWh/Jahr'],
        ['Nutzenergie-Einsparung inkl. Heizung', num.format(Math.round(nutzEinspar)) + ' kWh/Jahr'],
        ['Reduktion Fensterverlust', num.format(reduktion) + ' %'],
        ['Kostenersparnis', euro.format(kostenErspar) + '/Jahr'],
        ['CO2-Einsparung', num.format(co2Einspar) + ' kg/Jahr'],
        ['Amortisationszeit', amort > 0 ? num.format(amort) + ' Jahre' : 'keine Ersparnis'],
        ['Ersparnis über 25 Jahre', euro.format(ersparnis25)],
        ['Bewertung U-Wert neu', bewertung],
    ]);
}
            case 'co2-preis-heizkosten-prognose-rechner': {
    const traeger = value('traeger');
    const faktor = traeger === 'Heizöl' ? 0.000266 : 0.000201;
    const verbrauch = n('verbrauch');
    const co2preisHeute = n('co2preisHeute');
    const co2preis2030 = n('co2preis2030');
    const energiepreis = n('energiepreis');
    const co2Tonnen = verbrauch * faktor;
    const energieKosten = verbrauch * energiepreis;
    const co2KostenHeute = co2Tonnen * co2preisHeute;
    const gesamtHeute = energieKosten + co2KostenHeute;
    const co2Kosten2030 = co2Tonnen * co2preis2030;
    const gesamt2030 = energieKosten + co2Kosten2030;
    const mehrkosten = co2Kosten2030 - co2KostenHeute;
    const verteuerung = gesamtHeute ? (gesamt2030 - gesamtHeute) / gesamtHeute * 100 : 0;
    const aufschlag2030 = co2preis2030 * faktor * 100;
    let kumuliert = 0;
    for (let j = 0; j < 5; j++) { const p = co2preisHeute + (co2preis2030 - co2preisHeute) * (j / 4); kumuliert += co2Tonnen * p; }
    kumuliert -= 5 * co2KostenHeute;
    const empfehlung = verteuerung > 30 ? 'Heizungstausch prüfen' : verteuerung > 10 ? 'Verbrauch senken lohnt sich' : 'Belastung noch moderat';
    return rows([
        ['CO2-Ausstoß pro Jahr', num.format(co2Tonnen) + ' t'],
        ['Reine Energiekosten pro Jahr', euro.format(energieKosten)],
        ['CO2-Kosten heute pro Jahr', euro.format(co2KostenHeute)],
        ['Gesamtkosten heute pro Jahr', euro.format(gesamtHeute)],
        ['CO2-Kosten 2030 pro Jahr', euro.format(co2Kosten2030)],
        ['Gesamtkosten 2030 pro Jahr', euro.format(gesamt2030)],
        ['Mehrkosten durch CO2-Preis bis 2030', euro.format(mehrkosten)],
        ['Prozentuale Verteuerung bis 2030', num.format(verteuerung) + ' %'],
        ['CO2-Aufschlag pro kWh 2030', num.format(aufschlag2030) + ' ct'],
        ['Kumulierte CO2-Mehrkosten 2026-2030', euro.format(kumuliert)],
        ['Empfehlung', empfehlung],
    ]);
}
            case 'thg-quote-e-auto-erloes-rechner': {
    const praemie = n('praemie_jahr');
    const jahre = n('jahre');
    const fahrleistung = n('fahrleistung');
    const verbrauch = n('verbrauch');
    const strompreis = n('strompreis');
    const erloesGesamt = praemie * jahre;
    const erloesMonat = praemie / 12;
    const erloes100 = fahrleistung ? praemie / fahrleistung * 100 : 0;
    const kosten100 = verbrauch * strompreis / 100;
    const effektiv100 = kosten100 - erloes100;
    const senkung = kosten100 ? erloes100 / kosten100 * 100 : 0;
    const ladeJahr = fahrleistung / 100 * verbrauch * strompreis / 100;
    const nettoJahr = ladeJahr - praemie;
    const gratisKm = kosten100 ? praemie / kosten100 * 100 : 0;
    const energieMenge = fahrleistung / 100 * verbrauch;
    const effStrompreis = energieMenge ? (ladeJahr - praemie) / energieMenge * 100 : 0;
    return rows([
        ['THG-Erlös gesamt', euro.format(erloesGesamt)],
        ['THG-Erlös pro Monat', euro.format(erloesMonat)],
        ['THG-Erlös je 100 km', euro.format(erloes100)],
        ['Energiekosten je 100 km', euro.format(kosten100)],
        ['Effektive Energiekosten je 100 km', euro.format(effektiv100)],
        ['Senkung der Ladekosten', num.format(senkung) + ' %'],
        ['Reine Ladekosten pro Jahr', euro.format(ladeJahr)],
        ['Netto-Ladekosten pro Jahr nach Prämie', euro.format(nettoJahr)],
        ['Geförderte Gratis-Kilometer', num.format(gratisKm) + ' km'],
        ['Effektiver Strompreis nach Prämie', num.format(effStrompreis) + ' ct/kWh'],
    ]);
}
            case 'klimaanlage-split-betriebskosten-rechner': {
    const raumflaeche = n('raumflaeche');
    const raumhoehe = n('raumhoehe');
    const kuehlbedarf = n('kuehlbedarf_wm2');
    const betriebsstunden = n('betriebsstunden');
    const seer = n('seer');
    const scop = n('scop');
    const heizstunden = n('heizstunden');
    const strompreis = n('strompreis');
    const kuehlleistung = raumflaeche * kuehlbedarf / 1000 * raumhoehe / 2.5;
    const kaelteEnergie = kuehlleistung * betriebsstunden;
    const stromKuehlen = seer ? kaelteEnergie / seer : 0;
    const kostenKuehlen = stromKuehlen * strompreis / 100;
    const heizEnergie = kuehlleistung * heizstunden;
    const stromHeizen = scop ? heizEnergie / scop : 0;
    const kostenHeizen = stromHeizen * strompreis / 100;
    const gesamtkosten = kostenKuehlen + kostenHeizen;
    const proStunde = seer ? kuehlleistung / seer * strompreis : 0;
    const proTag = seer ? kuehlleistung / seer * 8 * strompreis / 100 : 0;
    const btu = kuehlleistung * 3412;
    return rows([
        ['Benötigte Kühlleistung', num.format(kuehlleistung) + ' kW'],
        ['Empfohlene Gerätegröße', num.format(btu) + ' BTU/h'],
        ['Kälteenergie pro Jahr', num.format(kaelteEnergie) + ' kWh'],
        ['Stromverbrauch Kühlen', num.format(stromKuehlen) + ' kWh/Jahr'],
        ['Kühlkosten pro Jahr', euro.format(kostenKuehlen)],
        ['Stromverbrauch Heizen', num.format(stromHeizen) + ' kWh/Jahr'],
        ['Heizkosten pro Jahr', euro.format(kostenHeizen)],
        ['Gesamtstromkosten pro Jahr', euro.format(gesamtkosten)],
        ['Kosten je Kühl-Betriebsstunde', num.format(proStunde) + ' ct'],
        ['Kosten pro Kühltag (8 h)', euro.format(proTag)],
    ]);
}
            case 'kindergeld-kinderfreibetrag-2026-rechner': {
    const kinder = Math.max(0, n('kinder'));
    const grenz = n('grenzsteuersatz');
    const g = grenz / 100;
    const KG_KIND_JAHR = 3060;
    const FRB_GESAMT = 9756;
    const kindergeldKind = KG_KIND_JAHR;
    const kindergeldGesamt = KG_KIND_JAHR * kinder;
    const freibetragGesamt = FRB_GESAMT * kinder;
    const ersparnisFreibetrag = freibetragGesamt * g;
    const ersparnisProKind = FRB_GESAMT * g;
    const guenstiger = ersparnisFreibetrag > kindergeldGesamt ? 'Kinderfreibetrag' : 'Kindergeld';
    const vorteil = Math.abs(ersparnisFreibetrag - kindergeldGesamt);
    const breakEven = KG_KIND_JAHR / FRB_GESAMT * 100;
    const effektivProKind = Math.max(KG_KIND_JAHR, FRB_GESAMT * g);
    const gesamtEntlastung = Math.max(kindergeldGesamt, ersparnisFreibetrag);
    const monatlich = gesamtEntlastung / 12;
    return rows([
        ['Kindergeld pro Kind/Jahr', euro.format(kindergeldKind)],
        ['Kindergeld gesamt/Jahr', euro.format(kindergeldGesamt)],
        ['Kinderfreibetrag gesamt', euro.format(freibetragGesamt)],
        ['Steuerersparnis durch Freibetrag', euro.format(ersparnisFreibetrag)],
        ['Steuerersparnis pro Kind', euro.format(ersparnisProKind)],
        ['Günstigere Variante', guenstiger],
        ['Vorteil günstigere Variante/Jahr', euro.format(vorteil)],
        ['Break-even-Grenzsteuersatz', num.format(breakEven) + ' %'],
        ['Effektive Entlastung pro Kind/Jahr', euro.format(effektivProKind)],
        ['Gesamtentlastung/Jahr', euro.format(gesamtEntlastung)],
        ['Monatliche Entlastung gesamt', euro.format(monatlich)],
    ]);
}
            case 'kurzarbeitergeld-2026-rechner': {
    const sollBrutto = n('soll_brutto');
    const istBrutto = n('ist_brutto');
    const nq = n('netto_quote') / 100;
    const satz = value('mit_kind') === 'ja' ? 0.67 : 0.60;
    const sollNetto = sollBrutto * nq;
    const istNetto = istBrutto * nq;
    const nettoDifferenz = Math.max(0, sollNetto - istNetto);
    const leistungssatz = satz * 100;
    const kug = nettoDifferenz * satz;
    const arbeitsausfall = sollBrutto ? (1 - istBrutto / sollBrutto) * 100 : 0;
    const gesamtbezug = istNetto + kug;
    const differenzNorm = sollNetto - gesamtbezug;
    const ersatzquote = sollNetto ? gesamtbezug / sollNetto * 100 : 0;
    const kug6Monate = kug * 6;
    return rows([
        ['Soll-Netto (normal)', euro.format(sollNetto)],
        ['Ist-Netto (reduziert)', euro.format(istNetto)],
        ['Nettoentgeltdifferenz', euro.format(nettoDifferenz)],
        ['Leistungssatz', num.format(leistungssatz) + ' %'],
        ['Kurzarbeitergeld', euro.format(kug)],
        ['Arbeitsausfall', num.format(arbeitsausfall) + ' %'],
        ['Gesamtbezug im Monat', euro.format(gesamtbezug)],
        ['Differenz zum normalen Netto', euro.format(differenzNorm)],
        ['Einkommensersatz-Quote', num.format(ersatzquote) + ' %'],
        ['Verlust pro Monat', euro.format(differenzNorm)],
        ['Kurzarbeitergeld über 6 Monate', euro.format(kug6Monate)],
    ]);
}
            case 'kfw-effizienzhaus-tilgungszuschuss-rechner': {
    const stufe = value('stufe');
    const mitEE = value('ee') === 'ja';
    const wohneinheiten = Math.max(0, n('wohneinheiten'));
    const kosten = n('kosten');
    const stufenMap = { 'EH 85': 5, 'EH 70': 10, 'EH 55': 15, 'EH 40': 20, 'EH Denkmal': 5 };
    const basisQuote = stufenMap[stufe] !== undefined ? stufenMap[stufe] : 5;
    const eeBonus = mitEE ? 5 : 0;
    const gesamtQuote = basisQuote + eeBonus;
    const maxKredit = (mitEE ? 150000 : 120000) * wohneinheiten;
    const anrechenbar = Math.min(kosten, maxKredit);
    const tilgungszuschuss = anrechenbar * gesamtQuote / 100;
    const effektivKredit = anrechenbar - tilgungszuschuss;
    const eigenanteil = Math.max(0, kosten - maxKredit);
    const zuschussProWE = wohneinheiten ? tilgungszuschuss / wohneinheiten : 0;
    const empfehlung = basisQuote >= 20 ? 'höchste Förderstufe, maximaler Zuschuss' : basisQuote >= 15 ? 'sehr gute Förderung' : 'höhere Effizienzstufe steigert den Zuschuss';
    return rows([
        ['Basis-Zuschussquote', num.format(basisQuote) + ' %'],
        ['EE-Bonus', num.format(eeBonus) + ' %'],
        ['Gesamt-Tilgungszuschuss', num.format(gesamtQuote) + ' %'],
        ['Maximaler Förderkredit', euro.format(maxKredit)],
        ['Anrechenbarer Kreditbetrag', euro.format(anrechenbar)],
        ['Tilgungszuschuss', euro.format(tilgungszuschuss)],
        ['Effektiv zurückzuzahlender Kredit', euro.format(effektivKredit)],
        ['Nicht förderfähiger Eigenanteil', euro.format(eigenanteil)],
        ['Zuschuss pro Wohneinheit', euro.format(zuschussProWE)],
        ['Einschätzung', empfehlung],
    ]);
}
            case 'hydraulischer-abgleich-einsparung-rechner': {
    const verbrauch = n('verbrauch');
    const einsparquote = n('einsparquote');
    const preis = n('preis');
    const traeger = value('traeger');
    const kosten = n('kosten');
    const foerderung = n('foerderung');
    const faktorMap = { 'Erdgas': 0.201, 'Heizöl': 0.266, 'Wärmepumpe': 0.380, 'Fernwärme': 0.180 };
    const faktor = faktorMap[traeger] !== undefined ? faktorMap[traeger] : 0.201;
    const energieErsparnis = verbrauch * einsparquote / 100;
    const kostenErsparnis = energieErsparnis * preis;
    const co2Ersparnis = energieErsparnis * faktor;
    const foerderbetrag = kosten * foerderung / 100;
    const eigenanteil = kosten - foerderbetrag;
    const amortisation = kostenErsparnis ? eigenanteil / kostenErsparnis : 0;
    const ersparnis10 = kostenErsparnis * 10 - eigenanteil;
    const restVerbrauch = verbrauch - energieErsparnis;
    const bewertung = (amortisation > 0 && amortisation < 3) ? 'lohnt sich fast immer' : 'dennoch sinnvoll, oft Pflicht bei WP-Förderung';
    return rows([
        ['Energieeinsparung pro Jahr', num.format(energieErsparnis) + ' kWh'],
        ['Kostenersparnis pro Jahr', euro.format(kostenErsparnis)],
        ['CO2-Einsparung pro Jahr', num.format(co2Ersparnis) + ' kg'],
        ['Förderbetrag', euro.format(foerderbetrag)],
        ['Eigenanteil nach Förderung', euro.format(eigenanteil)],
        ['Amortisationszeit', num.format(amortisation) + ' Jahre'],
        ['Ersparnis über 10 Jahre', euro.format(ersparnis10)],
        ['Verbleibender Verbrauch pro Jahr', num.format(restVerbrauch) + ' kWh'],
        ['CO2-Reduktion', num.format(einsparquote) + ' %'],
        ['Bewertung', bewertung],
    ]);
}
            case 'vorlauftemperatur-absenkung-rechner': {
    const system = value('system');
    const istWP = system === 'Wärmepumpe';
    const vlAlt = n('vlAlt');
    const vlNeu = n('vlNeu');
    const verbrauch = n('verbrauch');
    const jazAlt = n('jazAlt');
    const preis = n('preis');
    const deltaT = vlAlt - vlNeu;
    const faktorProK = istWP ? 2.5 : 1.0;
    const einsparquote = Math.min(40, Math.max(0, deltaT * faktorProK));
    const energieErsparnis = istWP ? (jazAlt ? (verbrauch / jazAlt) * einsparquote / 100 : 0) : verbrauch * einsparquote / 100;
    const kostenErsparnis = energieErsparnis * preis;
    const neueJaz = jazAlt + Math.max(0, deltaT) * 0.06;
    const jazVerbesserung = istWP ? (jazAlt ? (neueJaz - jazAlt) / jazAlt * 100 : 0) : null;
    const co2Ersparnis = istWP ? energieErsparnis * 0.380 : energieErsparnis * 0.201;
    const restVerbrauch = verbrauch * (1 - einsparquote / 100);
    const hinweis = vlNeu > 0 && vlNeu < 35 ? 'Flächenheizung nötig' : 'für Heizkörper meist machbar';
    return rows([
        ['Temperaturdifferenz', num.format(deltaT) + ' K'],
        ['Einsparfaktor pro Kelvin', num.format(faktorProK) + ' %'],
        ['Gesamte Einsparquote', num.format(einsparquote) + ' %'],
        ['Energieeinsparung pro Jahr', num.format(energieErsparnis) + ' kWh'],
        ['Kostenersparnis pro Jahr', euro.format(kostenErsparnis)],
        ['Neue JAZ (nur Wärmepumpe)', istWP ? num.format(neueJaz) : '—'],
        ['JAZ-Verbesserung', istWP ? num.format(jazVerbesserung) + ' %' : '—'],
        ['CO2-Einsparung pro Jahr', num.format(co2Ersparnis) + ' kg'],
        ['Verbleibender Verbrauch pro Jahr', num.format(restVerbrauch) + ' kWh'],
        ['Hinweis', hinweis],
    ]);
}
            case 'co2-fussabdruck-mobilitaet-rechner': {
    const autoKm = n('autoKm');
    const autoTyp = value('autoTyp');
    const bahnKm = n('bahnKm');
    const busKm = n('busKm');
    const flugKm = n('flugKm');
    const autoMap = { 'Benzin': 165, 'Diesel': 155, 'Hybrid': 110, 'Elektro': 55 };
    const autoFaktor = autoMap[autoTyp] !== undefined ? autoMap[autoTyp] : 165;
    const co2Auto = autoKm * autoFaktor / 1000;
    const co2Bahn = bahnKm * 32 / 1000;
    const co2Bus = busKm * 80 / 1000;
    const co2Flug = flugKm * 230 / 1000;
    const gesamt = co2Auto + co2Bahn + co2Bus + co2Flug;
    const tonnen = gesamt / 1000;
    const anteilBudget = gesamt / 1500 * 100;
    const posten = [['Auto', co2Auto], ['Bahn', co2Bahn], ['Bus/ÖPNV', co2Bus], ['Flug', co2Flug]];
    let groesster = posten[0];
    for (const p of posten) { if (p[1] > groesster[1]) groesster = p; }
    const baeume = gesamt / 10;
    const einsparAuto = autoKm * Math.max(0, autoFaktor - 55) / 1000;
    return rows([
        ['CO2 Auto pro Jahr', num.format(co2Auto) + ' kg'],
        ['CO2 Bahn pro Jahr', num.format(co2Bahn) + ' kg'],
        ['CO2 Bus/ÖPNV pro Jahr', num.format(co2Bus) + ' kg'],
        ['CO2 Flug pro Jahr', num.format(co2Flug) + ' kg'],
        ['Gesamt-CO2 Mobilität pro Jahr', num.format(gesamt) + ' kg'],
        ['In Tonnen', num.format(tonnen) + ' t'],
        ['Anteil am 1,5-t-Klimabudget', num.format(anteilBudget) + ' %'],
        ['Größter Emittent', groesster[0]],
        ['Bäume zur Kompensation', num.format(baeume)],
        ['Einsparpotenzial Auto auf E-Antrieb', num.format(einsparAuto) + ' kg/Jahr'],
    ]);
}
            case 'warmwasser-solarthermie-deckung-rechner': {
    const personen = n('personen');
    const liter = n('liter');
    const tempRise = n('tempRise');
    const kollektor = n('kollektor');
    const ertragProM2 = n('ertragProM2');
    const preis = n('preis');
    const bedarfLiter = personen * liter * 365;
    const energiebedarf = bedarfLiter * tempRise * 1.16 / 1000;
    const solarertrag = kollektor * ertragProM2;
    const nutzbar = Math.min(solarertrag, energiebedarf * 0.95);
    const deckungsgrad = energiebedarf ? nutzbar / energiebedarf * 100 : 0;
    const zugekauft = Math.max(0, energiebedarf - nutzbar);
    const ersparnis = nutzbar * preis;
    const co2Ersparnis = nutzbar * 0.201;
    const empfohleneFlaeche = personen * 1.2;
    const hinweis = deckungsgrad >= 55 ? 'gute Dimensionierung' : deckungsgrad > 0 ? 'Kollektorfläche knapp, mehr Fläche prüfen' : 'Eingaben ergänzen';
    return rows([
        ['Warmwasserbedarf pro Jahr', num.format(bedarfLiter) + ' Liter'],
        ['Energiebedarf Warmwasser pro Jahr', num.format(energiebedarf) + ' kWh'],
        ['Solarertrag pro Jahr', num.format(solarertrag) + ' kWh'],
        ['Nutzbarer Solarertrag pro Jahr', num.format(nutzbar) + ' kWh'],
        ['Solarer Deckungsgrad', num.format(deckungsgrad) + ' %'],
        ['Konventionell zugekauft pro Jahr', num.format(zugekauft) + ' kWh'],
        ['Jährliche Ersparnis', euro.format(ersparnis)],
        ['CO2-Einsparung pro Jahr', num.format(co2Ersparnis) + ' kg'],
        ['Empfohlene Kollektorfläche', num.format(empfohleneFlaeche) + ' m²'],
        ['Dimensionierungs-Hinweis', hinweis],
    ]);
}
        }
        return rows([['Status', 'Tool bereit']]);
    }
  try {
    return calculate();
  } catch (error) {
    return rows([["Fehler", "Bitte Eingaben pruefen."]]);
  }
}
