@@ -36,7 +36,7 @@
(block.allgemeineArgumente ?? []) as TextFragment[],
);
// Tag used to fetch allgemeineArgumente — exclude from grouping categories
// Tag used to fetch allgemeineArgumente, exclude from grouping categories
const fetchTagSlug = $derived(
typeof block.allgemeineArgumenteTag === "object" & & block.allgemeineArgumenteTag !== null
? (block.allgemeineArgumenteTag as { _slug : string } )._slug
@@ -251,11 +251,11 @@
const gebietsBez = $derived(
windArea
? `${ windArea . gebiets_nr } ${ windArea . bezeichnung ? " – " + windArea . bezeichnung : "" } `
? `${ windArea . gebiets_nr } ${ windArea . bezeichnung ? ", " + windArea . bezeichnung : "" } `
: "",
);
// Editable opening/closing — null = use reactive default
// Editable opening/closing, null = use reactive default
const defaultOpening = $derived(
block.defaultOpeningText
? block.defaultOpeningText.replace("{ gebiet } ", gebietsBez)
@@ -309,7 +309,7 @@
];
});
// Combined selection for output — respects drag order
// Combined selection for output, respects drag order
const selectedFragments = $derived.by(() => {
const allFrags = [...kriterien, ...allgemeineDeduped];
return fragmentOrder
@@ -332,7 +332,7 @@ Karl-Liebknecht-Straße 4
const mailtoLink = $derived.by(() => {
if (!outputText) return "";
const subject = `Einwendung Regionalplan Südwestthüringen – Vorranggebiet ${ gebietsBez } `;
const subject = `Einwendung Regionalplan Südwestthüringen, Vorranggebiet ${ gebietsBez } `;
const to = block.recipientEmail ?? "";
return `mailto:${ to } ?subject=${ encodeURIComponent ( subject )} &body=$ { encodeURIComponent ( outputText )} `;
});
@@ -349,7 +349,7 @@ Karl-Liebknecht-Straße 4
? `Mit freundlichen Grüßen\n\n${ absenderName . trim ()} `
: "Mit freundlichen Grüßen";
const disclaimer = `${ "=" . repeat ( 64 )}
!! ENTWURF – KEIN FERTIGER BRIEF !!
!! ENTWURF, KEIN FERTIGER BRIEF !!
Stichpunkte müssen vor dem Einreichen in eigene, ausformulierte
Sätze überführt werden. Text individuell anpassen.
${ "=" . repeat ( 64 )} `;
@@ -371,7 +371,11 @@ ${"=".repeat(64)}`;
return [disclaimer, "", sender, "", heute, "", "An:", RECIPIENT, "", "Betreff: " + betreff.split("\n")[0], betreff.split("\n")[1] ?? "", "", "Sehr geehrte Damen und Herren,", "", body.trimEnd()]
.join("\n")
.replace(/\n{ 3 ,} /g, "\n\n");
.replace(/\n{ 3 ,} /g, "\n\n")
// Em- und En-Dashes als Satz-Trenner durch Kommas ersetzen
// ("Wort, Wort" / "Wort, Wort" → "Wort, Wort").
// Bindestriche in Bezeichnungen wie "W-23" bleiben unangetastet.
.replace(/\s+[—–]\s+/g, ", ");
}
let sectionEl: HTMLElement | undefined;
@@ -398,6 +402,25 @@ ${"=".repeat(64)}`;
const aiToneLabels = ["sehr sachlich & rational", "eher sachlich", "ausgewogen", "eher emotional", "sehr emotional & persönlich betroffen"];
const aiSharpnessLabels = ["konstruktiv & kooperativ", "moderat", "bestimmt & klar", "kritisch", "fordernd & scharf"];
// Editierbare Inhalts-Regeln für den KI-Prompt. Default-Set unten,
// Nutzer kann anpassen/ergänzen, wirkt als Override im Prompt.
const DEFAULT_AI_RULES = `1. Alle Argumente, Fakten, Zahlen, Gesetzes- und Quellenverweise aus der Vorlage MÜSSEN vollständig erhalten bleiben. Reihenfolge im Hauptteil darfst du variieren, Inhalt nicht.
2. KEINE neuen Fakten, Zahlen, Gesetzesnormen, Studien oder Quellen erfinden. Was nicht in der Vorlage steht, kommt nicht in den Output. (Der oben angegebene Kontext zum Vorranggebiet und die persönliche Betroffenheit zählen ebenfalls als Vorlage.)
3. Stichpunkte, Bullet-Listen oder Aufzählungen aus der Vorlage werden in zusammenhängende Fließtext-Absätze überführt. Im fertigen Brief stehen keine "•"/"-"-Listen mehr im Argumentationsteil.
4. Mindestens 70 % des Fließtextes sind neu formuliert. Übernimm keine ganzen Sätze 1:1 aus der Vorlage. Behalte juristische Fachbegriffe und Zitate bei.
5. Wenn der Verfasser persönliche Betroffenheit angegeben hat, wird sie konkret und ich-bezogen in einen passenden Absatz des Hauptteils eingewoben, nicht als separater Block angehängt. Ohne Angabe verzichtest du auf erfundene persönliche Bezüge.`;
let aiRules = $state(DEFAULT_AI_RULES);
// Badges für "Brief im Detail anpassen"-Accordion
const tweakBadges = $derived(
[
selectedFragments.length > 1 ? "Reihenfolge" : null,
openingOverride !== null ? "Einleitung" : null,
closingOverride !== null ? "Abschluss" : null,
hasManualEdit ? "bearbeitet" : null,
].filter(Boolean) as string[],
);
const stepFilled = $derived([
!!windArea,
ortskonkreteSelectedCount > 0 || kriterien.length === 0,
@@ -439,8 +462,15 @@ ${"=".repeat(64)}`;
absenderOrt = "";
showOutput = false;
outputText = "";
hasManualEdit = false;
fragmentOrder = [];
hadSavedState = false;
maxStepReached = 0;
step = 0;
try { localStorage . removeItem ( storageKey ); } catch {}
if (typeof window !== "undefined") {
window.scrollTo({ top : 0 , behavior : "smooth" } );
}
}
let dragIdx = $state< number | null > (null);
@@ -480,7 +510,7 @@ ${"=".repeat(64)}`;
}
}
async function copy AiPrompt() {
function build AiPrompt(): string {
const toneDesc = aiToneLabels[aiTone - 1];
const sharpnessDesc = aiSharpnessLabels[aiSharpness - 1];
const customPart = aiCustomNote.trim()
@@ -490,42 +520,70 @@ ${"=".repeat(64)}`;
? `\nPersönliche Betroffenheit des Verfassers (zentral und authentisch einbinden, möglichst wörtlich verarbeiten, nichts dazuerfinden):\n${ persoenlicheEingabe . trim ()} \n`
: "";
const gebietPart = windArea
? `\nKontext zum betroffenen Windvorranggebiet: ${ windArea . gebiets_nr ?? "" } ${ windArea . bezeichnung ? " – " + windArea . bezeichnung : "" } ${ windArea . gemeinden ? . length ? " (Gemeinden: " + windArea . gemeinden . slice ( 0 , 5 ). join ( ", " ) + ")" : "" } \n`
? `\nKontext zum betroffenen Windvorranggebiet: ${ windArea . gebiets_nr ?? "" } ${ windArea . bezeichnung ? ", " + windArea . bezeichnung : "" } ${ windArea . gemeinden ? . length ? " (Gemeinden: " + windArea . gemeinden . slice ( 0 , 5 ). join ( ", " ) + ")" : "" } \n`
: "";
const prompt = `Rolle: Du bist ein erfahrener Verfasser von Bürger-Einwendungen im Raumordnungs- und Genehmigungsverfahren. Du formulierst eine individuelle, juristisch und sachlich saubere Stellungnahme im Namen des Verfassers.
return `# Rolle
Du bist erfahrener Verfasser von Bürger-Einwendungen im Raumordnungs- und Genehmigungsverfahren nach BauGB, ROG und BImSchG. Du kennst die Sprache und Konventionen behördlicher Anschreiben in Deutschland und schreibst rechtlich tragfähig, sachlich, ohne Pathos.
Aufgabe: Schreibe die unten als "MUSTER-STELLUNGNAHME" beigefügte Vorlage so um, dass sie wie eine eigenständig verfasste Einwendung dieser einen Person wirkt – nicht wie eine Vorlage und nicht wie eine generierte Massenstellungnahme.
# Aufgabe
Aus der unten als "MUSTER-STELLUNGNAHME" beigefügten Vorlage (= Argument-Gerüst mit Stichpunkten) entsteht ein vollständig ausformulierter Einwendungstext, der wie eine eigenständig verfasste Einwendung dieser konkreten Person wirkt, nicht wie eine Vorlage, nicht wie eine generierte Massenstellungnahme, nicht wie ein offensichtlich KI-erzeugter Text.
Stilvorgaben:
# Stilvorgaben
- Ton: ${ toneDesc }
- Haltung/Schärfe: ${ sharpnessDesc }
- Sprache: durchgängig Deutsch, Sie-Form gegenüber der Behörde, klar adressiert .
- Sprache: Deutsch, durchgängig Sie-Form gegenüber der Behörde. Sätze klar, Hauptsatz-dominiert, ohne Verschachtelungen über drei Ebenen .
- Verwende juristisch-sachliche Begriffe ("erheben Einwendung", "Abwägungsfehler", "ersatzlose Streichung"), aber keine Floskeln und keine Übertreibungen.
${ gebietPart } ${ personalPart } ${ customPart }
Zwingende Regeln:
1. ALLE inhaltlichen Argumente, Fakten, Zahlen, Gesetzes- und Quellenverweise aus der Muster-Stellungnahme MÜSSEN vollständig erhalten bleiben. Nichts weglassen, nichts inhaltlich abschwächen.
2. KEINE neuen Fakten, Zahlen, Gesetzesnormen oder Quellen erfinden. Wenn etwas in der Vorlage nicht steht, taucht es im Output nicht auf.
3. Briefkopf, Behörden-Adresse, Aktenzeichen/Betreff, Datum, Anrede und Schlussformel inkl. Absender bleiben strukturell erhalten (Reihenfolge, Felder, Angaben). Nur Formulierungen im Fließtext darfst du umarbeiten.
4. Argument-Reihenfolge im Hauptteil darfst du variieren, sofern Logik und Lesefluss stimmen.
5. Formulierungen variieren – keine Satzbausteine 1:1 übernehmen. Mindestens 70 % des Fließtextes neu formulieren, ohne Inhalt zu verändern.
6. Persönliche Betroffenheit (falls oben angegeben) wird konkret und ich-bezogen in den Text eingewoben, nicht als separater Absatz angehängt.
7. Länge: ungefähr im Rahmen der Vorlage (±20 %). Keine Aufblähung mit Floskeln, keine starke Kürzung.
# Inhaltliche Regeln (zwingend)
${ aiRules . trim ()}
Ausgabeformat:
- Gib AUSSCHLIESSLICH den fertigen Stellungnahme-Text aus. Kein Vorwort, keine Erklärung , k eine Markdown-Codeblöcke, keine Hinweise wie "Hier ist deine Stellungnahme:". D ire kt mit dem Briefkopf beginnen .
# Brief-Struktur (unveränderlich)
- Absender (Name, Anschrift, PLZ/Ort), Datum, Behörden-Adresse, Betreff und Schlussformel werden 1:1 aus der Vorlage übernommen , R eihenfolge, Felder, exakte Schreibweise. Auch dann, wenn ein Feld leer oder kurz w irkt (z.B. nur ein Vorname) .
- Anrede und Grußformel ("Sehr geehrte Damen und Herren," / "Mit freundlichen Grüßen") bleiben unverändert.
# Was du im Output ENTFERNST
- Alle Hinweis-Banner, Warnhinweise, Platzhalter oder Anweisungen für den Verfasser ("ENTWURF, KEIN FERTIGER BRIEF", "Stichpunkte ausformulieren", Trennstriche wie "====" oder "---"). Diese gehören nicht in den endgültigen Behördentext.
- Meta-Kommentare, Erklärungen, Markdown-Headlines (## …), Codeblöcke (\`\`\`).
# Länge & Qualität
- Länge ungefähr im Rahmen der Vorlage (±25 %). Keine Aufblähung mit Floskeln ("vor diesem Hintergrund möchte ich betonen, dass …"), keine starke Kürzung argumentativer Substanz.
- Vermeide KI-typische Muster: "Es ist wichtig zu beachten, dass …", "darüber hinaus möchte ich anmerken …", "in einer Welt, in der …", übermäßige Adverbien ("zudem", "weiterhin", "des Weiteren" in jedem Absatz).
# Zeichensetzung
- KEINE Gedankenstriche (em-dash "—" oder en-dash "– ") als Satz-Trenner verwenden. Stattdessen Kommas, Klammern oder Doppelpunkte nutzen. Bindestriche in Bezeichnungen ("W-23", "Karl-Liebknecht-Straße") bleiben.
- Bevorzuge typografisch deutsche Anführungszeichen („…") wo passend.
# Ausgabeformat
Gib AUSSCHLIESSLICH den fertigen Brieftext aus, beginnend mit dem Absenderblock, endend mit Namen unter der Grußformel. Kein Vorwort, keine Erklärung, keine Markdown-Codeblöcke, keine Schlussbemerkung wie "Bei Fragen stehe ich gerne zur Verfügung" (außer es steht so in der Vorlage).
MUSTER-STELLUNGNAHME:
---
${ outputText }
---`;
}
async function copyAiPrompt() {
const prompt = buildAiPrompt();
try {
await navigator.clipboard.writeText(prompt);
copyAiPromptFeedback = "K opiert! ";
setTimeout(() => (copyAiPromptFeedback = ""), 20 00);
copyAiPromptFeedback = "✓ In Zwischenablage k opiert";
setTimeout(() => (copyAiPromptFeedback = ""), 25 00);
} catch {
copyAiPromptFeedback = "Fehler";
}
}
function downloadAiPrompt() {
const prompt = buildAiPrompt();
const blob = new Blob([prompt], { type : "text/plain;charset=utf-8" } );
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = `ki-prompt-${ windArea ? . gebiets_nr ?? "stellungnahme" } .txt`;
a.click();
URL.revokeObjectURL(url);
}
function printText() {
const w = window.open("", "_blank");
if (!w) return;
@@ -658,9 +716,9 @@ ${outputText}
< Icon icon = "mdi:clock-alert-outline" class = "size-5 shrink-0" / >
< span >
Einwendungsfrist: < strong > { deadlineFormatted } </ strong >
{ #if deadlineDays === 0 } — < strong > heute letzter Tag!</ strong >
{ :else if deadlineDays === 1 } — < strong > morgen letzter Tag!</ strong >
{ : else } — noch { deadlineDays } Tage
{ #if deadlineDays === 0 } , < strong > heute letzter Tag!</ strong >
{ :else if deadlineDays === 1 } , < strong > morgen letzter Tag!</ strong >
{ : else } , noch { deadlineDays } Tage
{ /if }
< / span >
< / div >
@@ -701,48 +759,50 @@ ${outputText}
< div class = "prose prose-wald mb-6 max-w-none text-stein-700" > { @html md ( block . intro )} </ div >
{ /if }
<!-- Step indicator -->
< div class = "mb-2 flex items-center gap-1 text-xs font-medium " >
{ #each Array . from ({ length : TOTAL_STEPS }, ( _ , i ) => i ) as s }
< button
class="flex h-7 w-7 items-center justify-center rounded-full border transition-colors
{ s === step
? 'border-wald-700 bg-wald-700 text-white'
: s < = maxStepReached & & stepFilled[s]
? 'border-wald-500 bg-wald-100 text-wald-700'
: s < = maxStepReached
? 'border-erde-400 bg-erde-50 text-erde-600'
: 'border-stein-300 bg-white text-stein-4 00'}"
onclick={() => { if ( s <= maxStepReached ) goTo ( s as Step ); }}
disabled= { s > maxStepReached }
aria-current= { s === step ? "step" : undefined }
title= { STEP_LABELS [ s ] }
>
{ #if s !== step && s <= maxStepReached && stepFilled [ s ]}
< Icon icon = "mdi:check" class = "size-3.5" / >
{ : else }
{ s + 1 }
<!-- Step indicator (sticky) -->
< div class = "stellingnahme-stepper sticky top-2 z-[1100] -mx-3 mb-6 rounded-xl border border-wald-200 bg-wald-50/95 px-4 py-3 shadow-md backdrop-blur sm:mx-0 " >
< div class = "flex items-center gap-1 text-xs font-medium" >
{ #each Array . from ({ length : TOTAL_STEPS }, ( _ , i ) => i ) as s }
< button
class="flex h-8 w-8 items-center justify-center rounded-full border-2 font-semibold transition-all
{ s === step
? 'border-wald-700 bg-wald-700 text-white shadow-md scale-110'
: s < = maxStepReached & & stepFilled[s]
? 'border-wald-500 bg-wald-200 text-wald-800 hover:bg-wald-300'
: s < = maxStepReached
? 'border-erde-400 bg-erde-50 text-erde-700 hover:bg-erde-1 00'
: 'border-stein-300 bg-white text-stein-400'}"
onclick= {() = > { if ( s <= maxStepReached ) goTo ( s as Step ); } }
disabled= { s > maxStepReached }
aria-current= { s === step ? "step" : undefined }
title={ STEP_LABELS [ s ]}
>
{ #if s !== step && s <= maxStepReached && stepFilled [ s ]}
< Icon icon = "mdi:check" class = "size-4" / >
{ : else }
{ s + 1 }
{ /if }
< / button >
{ #if s < TOTAL_STEPS - 1 }
< div class = "h-0.5 flex-1 rounded { s < maxStepReached ? ( stepFilled [ s ] ? 'bg-wald-400' : 'bg-erde-300' ) : 'bg-stein-200' } " ></ div >
{ /if }
< / button >
{ #if s < TOTAL_STEPS - 1 }
< div class = "h-px flex-1 { s < maxStepReached ? ( stepFilled [ s ] ? 'bg-wald-400' : 'bg-erde-300' ) : 'bg-stein-200' } " ></ div >
{ /each }
< / div >
< div class = "mt-2 flex items-center justify-between gap-3 text-xs" >
< span class = "font-semibold text-wald-800" > Schritt { step + 1 } / { TOTAL_STEPS } , { STEP_LABELS [ step ]} </ span >
{ #if saveStatus === "saved" }
< span class = "flex items-center gap-1 text-wald-600" aria-live = "polite" >
< Icon icon = "mdi:check-circle" class = "size-3.5" aria-hidden = "true" / >
Gespeichert
< / span >
{ /if }
{ /each }
< / div >
< div class = "mb-6 flex items-center justify-end gap-3 text-xs" >
{ #if saveStatus === "saved" }
< span class = "flex items-center gap-1 text-wald-600 transition-opacity" aria-live = "polite" >
< Icon icon = "mdi:check-circle" class = "size-3.5" aria-hidden = "true" / >
Gespeichert
< / span >
{ /if }
< span class = "text-stein-400" > Schritt { step + 1 } von { TOTAL_STEPS } – { STEP_LABELS [ step ]} </ span >
< / div >
< / div >
<!-- ── Step 0: Gebietskontext ─────────────────────────────────────────────── -->
{ #if step === 0 }
< div >
< h3 class = "mb-4 text-lg font-semibold text-wald-800" > Schritt 1 – Ihr Vorranggebiet< / h3 >
< h3 class = "mb-4 text-lg font-semibold text-wald-800" > Schritt 1, Ihr Vorranggebiet< / h3 >
{ #if windArea }
< div class = "mb-6 rounded-xl border border-wald-200 bg-wald-50 p-5" >
@@ -769,8 +829,16 @@ ${outputText}
{ #if windArea . anlagen_geplant }
< div class = "rounded-lg border border-wald-200 bg-white/70 p-3 shadow-sm" >
< Icon icon = "mdi:wind-turbine" class = "mb-1 size-5 text-wald-600" aria-hidden = "true" / >
< dt class = "whitespace-nowrap text-sm font-semibold text-wald-800 sm:text-base" > Anlagen geplant < / dt >
< dd class = "mt-1 text-stein-600" > { windArea . anlagen_geplant } </ dd >
< dt class = "flex items-center gap-1 whitespace-nowrap text-sm font-semibold text-wald-800 sm:text-base" >
Anlagen geplant
< span
class="cursor-help text-stein-400 hover:text-stein-600"
title="Schätzung nach Faustregel ~4– 6 Anlagen je 100 ha. Keine amtliche Festlegung, tatsächliche Anlagenzahl wird erst im Genehmigungsverfahren bestimmt."
>
< Icon icon = "mdi:information-outline" class = "size-3.5" / >
< / span >
< / dt >
< dd class = "mt-1 text-stein-600" > ~{ windArea . anlagen_geplant } < span class = "ml-0.5 text-xs text-stein-400" > *</ span ></ dd >
< / div >
{ /if }
{ #if windArea . hoehe_max_m }
@@ -795,6 +863,15 @@ ${outputText}
< / div >
{ /if }
< / dl >
{ #if windArea . anlagen_geplant }
< p class = "mt-3 text-[11px] leading-snug text-stein-500" >
< span class = "font-semibold" > * Hinweis zur Anlagenzahl:< / span >
keine amtliche Festlegung, eigene Schätzung nach Faustregel
~4– 6 Anlagen je 100 ha. Aus dem Prüfbogen ZU-14 stammen nur
Flächengröße und Windhöffigkeit. Die tatsächliche Anlagenzahl
wird erst im Genehmigungsverfahren bestimmt.
< / p >
{ /if }
{ #if windArea . notizen }
< details class = "mt-4 border-t border-wald-200 pt-3" open >
< summary class = "flex cursor-pointer items-center gap-1 text-sm font-medium text-wald-700 list-none" >
@@ -867,7 +944,7 @@ ${outputText}
<!-- ── Step 1: Ortskonkrete Argumente ───────────────────────────────────── -->
{ :else if step === 1 }
< div >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 2 – Ortskonkrete Argumente< / h3 >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 2, Ortskonkrete Argumente< / h3 >
< p class = "mb-5 text-sm text-stein-600" >
Diese Argumente beziehen sich direkt auf das Gebiet < strong > { windArea ? . gebiets_nr ?? "" } </ strong > .
Wählen Sie die Punkte, die auf Ihre Situation zutreffen.
@@ -936,7 +1013,7 @@ ${outputText}
<!-- ── Step 2: Allgemeine Argumente ─────────────────────────────────────── -->
{ :else if step === 2 }
< div >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 3 – Grundsätzliche Einwände< / h3 >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 3, Grundsätzliche Einwände< / h3 >
< p class = "mb-1 text-sm text-stein-600" >
Welche allgemeinen Argumente teilen Sie? Diese gelten unabhängig vom konkreten Standort.
< / p >
@@ -1076,7 +1153,7 @@ ${outputText}
<!-- ── Step 3: Persönliche Betroffenheit ─────────────────────────────────── -->
{ :else if step === 3 }
< div >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 4 – Persönliche Betroffenheit< / h3 >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 4, Persönliche Betroffenheit< / h3 >
< p class = "mb-2 text-sm text-stein-600" >
Beschreiben Sie kurz, wie Sie persönlich betroffen sind.
< strong > Wichtig:< / strong > Individuelle Betroffenheit stärkt Ihre Einwendung erheblich
@@ -1106,8 +1183,25 @@ ${outputText}
<!-- ── Step 4: Absender + Ausgabe ────────────────────────────────────────── -->
{ :else if step === 4 }
< div >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 5 – Absender & Ausgabe< / h3 >
< p class = "mb-4 text-sm text-stein-600" > Ihre Daten werden nur lokal zur Texterstellung genutzt.< / p >
< h3 class = "mb-1 text-lg font-semibold text-wald-800" > Schritt 5, Absender & Ausgabe< / h3 >
< p class = "mb-3 text-sm text-stein-600" > Ihre Daten werden nur lokal zur Texterstellung genutzt.< / p >
< div class = "mb-3 flex flex-wrap items-center gap-2" >
< button
type="button"
class="inline-flex items-center gap-1.5 rounded-md border border-stein-300 bg-white px-2.5 py-1.5 text-[11px] font-medium text-stein-700 hover:bg-stein-50"
onclick={() => {
absenderName = "[Vor- und Nachname]";
absenderAnschrift = "[Straße und Hausnummer]";
absenderOrt = "[PLZ und Ort]";
}}
title="Felder mit Platzhaltern füllen, später im fertigen Text in eckigen Klammern suchen und ersetzen."
>
< Icon icon = "mdi:format-text-variant-outline" class = "size-3.5 text-stein-500" / >
Mit Platzhaltern füllen
< / button >
< span class = "text-[11px] text-stein-400" > Erzeugt z.B. < code class = "rounded bg-stein-100 px-1" > [Vor- und Nachname]< / code > , später ersetzen.< / span >
< / div >
< div class = "mb-5 grid gap-3 sm:grid-cols-2" >
< div >
@@ -1124,160 +1218,289 @@ ${outputText}
< / div >
< / div >
{ #if selectedFragments . length > 1 }
< details class = "mb-5 rounded-lg border border-stein-200 overflow-hidden" >
< summary class = "flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-2.5 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none" >
< span > Reihenfolge der Argumente ({ selectedFragments . length } )</ span >
< Icon icon = "mdi:drag-vertical" class = "size-4 text-stein-400" / >
< / summary >
< div class = "divide-y divide-stein-100" >
{ #each selectedFragments as frag , i }
< div
draggable="true"
ondragstart={() => onDragStart ( i )}
ondragover={( e ) => onDragOver ( e , i )}
ondrop={() => onDrop ( i )}
ondragend={ onDragEnd }
class="flex cursor-grab items-center gap-3 px-4 py-2.5 text-sm transition-colors active:cursor-grabbing
{ dragOverIdx === i && dragIdx !== i ? 'bg-wald-50 border-t-2 border-wald-400' : 'bg-white hover:bg-stein-50' } "
>
< Icon icon = "mdi:drag-vertical" class = "size-4 shrink-0 text-stein-400" / >
< span class = "flex-1 truncate text-stein-700" > { frag . title ?? frag . _slug . replace ( /_/g , " " )} </ span >
< span class = "shrink-0 text-[10px] text-stein-400" > { i + 1 } </ span >
< / div >
{ /each }
< / div >
< / details >
{ /if }
< div class = "mb-5 rounded-lg border border-stein-200 bg-stein-50 p-3 text-xs text-stein-600" >
< div class = "mb-1 font-semibold text-stein-700" > Einzureichen bei:< / div >
< div class = "whitespace-pre" > { RECIPIENT } </ div >
< / div >
<!-- Einleitung & Abschluss anpassen -->
< div class = "mb-4 space-y-2" >
< details class = "rounded-lg border border-stein-200 overflow-hidden" >
< summary class = "flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-2.5 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none" >
< span > Einleitung anpassen< / span >
< Icon icon = "mdi:chevron-down" class = "det-chevron size-4 text-stein-400" / >
< / summary >
< div class = "border-t border-stein-100 px-4 py-3" >
< textarea
class="w-full rounded border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-y"
rows="3"
value={ effectiveOpening }
oninput={( e ) => ( openingOverride = ( e . target as HTMLTextAreaElement ). value )}
>< / textarea >
{ #if openingOverride !== null }
< button class = "mt-1 text-xs text-stein-400 hover:text-stein-600" onclick = {() => ( openingOverride = null )} > Zurücksetzen</button >
{ /if }
< / div >
< / details >
< details class = "rounded-lg border border-stein-200 overflow-hidden" >
< summary class = "flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-2.5 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none" >
< span > Abschluss anpassen< / span >
< Icon icon = "mdi:chevron-down" class = "det-chevron size-4 text-stein-400" / >
< / summary >
< div class = "border-t border-stein-100 px-4 py-3" >
< textarea
class="w-full rounded border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200 resize-y"
rows="3"
value={ effectiveClosing }
oninput={( e ) => ( closingOverride = ( e . target as HTMLTextAreaElement ). value )}
>< / textarea >
{ #if closingOverride !== null }
< button class = "mt-1 text-xs text-stein-400 hover:text-stein-600" onclick = {() => ( closingOverride = null )} > Zurücksetzen</button >
{ /if }
< / div >
< / details >
< / div >
< div class = "sticky bottom-2 z-10 mb-2 flex gap-3 rounded-lg border border-stein-200 bg-white/95 p-3 shadow-lg backdrop-blur sm:static sm:border-0 sm:bg-transparent sm:p-0 sm:shadow-none sm:backdrop-blur-none" >
< button class = "rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick = {() => goTo ( 3 )} > ← Zurück </ button >
< button
class="btn-wiggle rounded-lg bg-wald-700 px-5 py-2.5 text-sm font-semibold text-white transition hover:bg-wald-800 disabled:opacity-40"
disabled={ ! absenderName . trim ()}
onclick={ generateOutput }
title={ ! absenderName . trim () ? "Bitte erst Namen eintragen" : hasManualEdit ? "Manuelle Änderungen verwerfen und neu generieren" : undefined }
>{ hasManualEdit ? "↺ Neu generieren" : "Text erstellen" } </ button >
< / div >
{ #if ! absenderName . trim ()}
< p class = "mb-4 text-xs text -erde-6 00" > Bitte Namen eintragen, um den Text zu erstellen. < / p >
{ : else }
< div class = "mb-4" > < / div >
< p class = "mb-4 rounded-lg border border -erde-2 00 bg-erde-50 px-3 py-2 text-xs text-erde-700" >
Bitte oben Ihren Namen eintragen, der Text wird automatisch erstellt.
< / p >
{ /if }
{ #if showOutput }
< div class = "rounded-xl border border-wald-300 bg-wald-50/30 shadow-sm" role = "region" aria-live = "polite" aria-label = "Generierte Einwendung" >
< div class = "flex flex-col gap-2 border-b border-wald-200 px-4 py-3 sm:flex-row sm:items-center sm:justify-between " >
< span class = "text-sm font-semibold text -wald-8 00" > Ihre Einwendung < / span >
< div class = "flex flex-wrap gap-2 " >
< button class = "rounded-md border border-stein-300 px-2.5 py-1 text-[11px] font-medium text-stein-700 hover:bg-stein-50 " onclick = { copyText } > { copyFeedback || "Kopieren" } </button >
< button class = "rounded-md border border-stein-300 px-2.5 py-1 text-[11px] font-medium text-stein-700 hover:bg-stein-50" onclick = { downloadText } > Herunterladen</button >
< button class = "rounded-md border border-stein-300 px-2.5 py-1 text-[11px] font-medium text-stein-700 hover:bg-stein-50" onclick = { printText } > Drucken</button >
{ #if mailtoLink && ! mailTooLong }
< a href = { mailtoLink } class="rounded-md border border-wald-400 bg-wald-50 px-2 . 5 py-1 text- [ 11px ] font-medium text-wald-700 no-underline hover:bg-wald-100 " > { block . recipientEmail ? "Per E-Mail senden" : "Per E-Mail (an mich)" } < /a >
{ /if }
<!-- ═══ Trenner: Ergebnis ═══ -->
< div class = "my-6 flex items-center gap-3 " >
< div class = "h-px flex-1 bg -wald-2 00" > < / div >
< span class = "flex items-center gap-1.5 rounded-full bg-wald-100 px-3 py-1 text-[11px] font-semibold uppercase tracking-wider text-wald-800 " >
< Icon icon = "mdi:check-decagram " class = "size-3.5" / >
Ergebnis
< / span >
< div class = "h-px flex-1 bg-wald-200" > < / div >
< / div >
<!-- Block 1: Schreibvorlage -->
< div class = "mb-4 rounded-xl border border-wald-200 bg-white p-4 shadow-sm" >
< div class = "mb-2 flex items-start gap-2" >
< Icon icon = "mdi:file-document-edit-outline" class = "mt-0.5 size-5 shrink-0 text-wald-700" / >
< div class = "flex-1" >
< h4 class = "text-sm font-semibold text-wald-800" > Schreibvorlage verwenden< / h4 >
< p class = "mt-0.5 text-xs text-stein-500" > Text direkt übernehmen, kopieren, herunterladen oder drucken.< / p >
< / div >
< / div >
< textarea
bind:value={ outputText }
spellcheck="false"
oninput={() => { hasManualEdit = true ; }}
class="w-full resize-none bg-transparent px-5 py-4 font-sans text-sm leading-relaxed text-stein-800 focus:outline-none"
style="min-height: 40rem; field-sizing: content;"
>< / textarea >
{ #if mailTooLong }
< div class = "border-t border-stein-100 px-5 py-2 text-xs text-stein-500" > Für E-Mail zu lang — bitte < strong > Kopieren< / strong > und in die Mail einfügen.< / div >
{ /if }
< div class = "border-t border-himmel-100 bg-himmel-50/40 px-4 py-3" >
< p class = "mb-1 text-[11px] font-semibold uppercase tracking-wide text-himmel-700" > Mit KI eine individuelle Version erstellen< / p >
< p class = "mb-3 text-[11px] text-stein-500" > Stichpunkte in einen persönlichen Fließtext verwandeln — mit ChatGPT, Claude oder einer anderen KI:< br > < span class = "font-medium text-stein-600" > ① Regler einstellen ② „KI-Prompt kopieren" klicken ③ Bei ChatGPT / Claude einfügen & senden< / span > < / p >
< div class = "mb-2 grid gap-3 sm:grid-cols-2" >
< div >
< div class = "mb-1.5" >
< label for = "ai-tone" class = "text-[11px] text-stein-600" > Ton: < span class = "font-semibold text-himmel-700" > { aiToneLabels [ aiTone - 1 ]} </ span ></ label >
< / div >
< div class = "flex items-center gap-2 overflow-visible py-1 px-2" >
< span class = "shrink-0 text-[10px] text-stein-400" > Sachlich< / span >
< input id = "ai-tone" type = "range" min = "1" max = "5" bind:value = { aiTone } class="h-3 w-full cursor-pointer accent-himmel-500 " />
< span class = "shrink-0 text-[10px] text-stein-400" > Emotional< / span >
< / div >
< / div >
< div >
< div class = "mb-1.5" >
< label for = "ai-sharpness" class = "text-[11px] text-stein-600" > Haltung: < span class = "font-semibold text-himmel-700" > { aiSharpnessLabels [ aiSharpness - 1 ]} </ span ></ label >
< / div >
< div class = "flex items-center gap-2 overflow-visible py-1 px-2" >
< span class = "shrink-0 text-[10px] text-stein-400" > Konstruktiv< / span >
< input id = "ai-sharpness" type = "range" min = "1" max = "5" bind:value = { aiSharpness } class="h-3 w-full cursor-pointer accent-himmel-500 " />
< span class = "shrink-0 text-[10px] text-stein-400" > Fordernd< / span >
< / div >
< / div >
< / div >
< div class = "mb-2.5" >
< label for = "ai-custom-note" class = "mb-1 block text-[11px] text-stein-600" > Persönlicher Hinweis < span class = "text-stein-400" > (optional)< / span > < / label >
< textarea
id="ai-custom-note"
bind:value={ aiCustomNote }
rows="2"
placeholder='z.B. „Ich bin Landwirt und direkt betroffen“ oder „Bitte sehr kurz halten“'
class="w-full resize-none rounded border border-himmel-200 bg-white px-2.5 py-1.5 text-[11px] text-stein-700 placeholder:text-stein-300 focus:border-himmel-400 focus:outline-none"
>< / textarea >
< / div >
< div class = "flex flex-wrap gap-2" >
< button
class="rounded-md border border-himmel-400 bg-himmel-10 0 px-3 py-1.5 text-[11px] font-medium text-himmel -800 hover:bg-himmel-2 00"
onclick={ copyAiPrompt }
>{ copyAiPromptFeedback || "KI-Prompt kopieren" } </ button >
class="inline-flex items-center gap-1.5 rounded-md border border-wald-300 bg-wald-5 0 px-3 py-1.5 text-xs font-medium text-wald -800 hover:bg-wald-1 00"
onclick={ copyText }
>
< Icon icon = "mdi:content-copy" class = "size-3.5" / >
{ copyFeedback || "Kopieren" }
< / button >
< button
class="inline-flex items-center gap-1.5 rounded-md border border-stein-300 bg-white px-3 py-1.5 text-xs font-medium text-stein-700 hover:bg-stein-50"
onclick={ downloadText }
>
< Icon icon = "mdi:download" class = "size-3.5" / >
Herunterladen
< / button >
< button
class="inline-flex items-center gap-1.5 rounded-md border border-stein-300 bg-white px-3 py-1.5 text-xs font-medium text-stein-700 hover:bg-stein-50"
onclick={ printText }
>
< Icon icon = "mdi:printer" class = "size-3.5" / >
Drucken
< / button >
{ #if mailtoLink && ! mailTooLong }
< a
href={ mailtoLink }
class="inline-flex items-center gap-1.5 rounded-md border border-wald-400 bg-wald-50 px-3 py-1.5 text-xs font-medium text-wald-700 no-underline hover:bg-wald-100"
>
< Icon icon = "mdi:email-outline" class = "size-3.5" / >
{ block . recipientEmail ? "Per E-Mail senden" : "Per E-Mail (an mich)" }
< / a >
{ /if }
< / div >
< / div >
< div class = "mt-4 rounded-lg border border-himmel-200 bg-himmel-50 px-4 py-3 text-sm text-himmel-800" >
<!-- Block 2: KI - Variante -->
< div class = "mb-4 rounded-xl border border-himmel-200 bg-himmel-50/40 p-4 shadow-sm" >
< div class = "mb-3 flex items-start gap-2" >
< Icon icon = "mdi:robot-outline" class = "mt-0.5 size-5 shrink-0 text-himmel-700" / >
< div class = "flex-1" >
< h4 class = "text-sm font-semibold text-himmel-800" > Mit KI eine individuelle Version erstellen< / h4 >
< p class = "mt-0.5 text-xs text-stein-500" >
Die Vorlage wird zu persönlichem Fließtext umformuliert, mit ChatGPT, Claude oder anderer KI.
< / p >
< / div >
< / div >
< ol class = "mb-3 space-y-1.5 rounded-lg border border-himmel-100 bg-white/60 p-3 text-[11px] text-stein-700" >
< li class = "flex gap-2" > < span class = "font-semibold text-himmel-700" > 1.< / span > < span > Regler unten einstellen (Ton, Schärfe).< / span > < / li >
< li class = "flex gap-2" > < span class = "font-semibold text-himmel-700" > 2.< / span > < span > Auf < strong > „In Zwischenablage kopieren“< / strong > klicken, der komplette Prompt wird automatisch kopiert. (Alternativ als < strong > .txt-Datei< / strong > herunterladen.)< / span > < / li >
< li class = "flex gap-2" > < span class = "font-semibold text-himmel-700" > 3.< / span > < span > ChatGPT, Claude o.ä. öffnen → ins Eingabefeld < strong > einfügen< / strong > (Strg+V / ⌘+V) → senden.< / span > < / li >
< li class = "flex gap-2" > < span class = "font-semibold text-himmel-700" > 4.< / span > < span > KI-Antwort prüfen, ggf. anpassen, fertig.< / span > < / li >
< / ol >
< div class = "mb-3 grid gap-3 sm:grid-cols-2" >
< div >
< div class = "mb-1.5" >
< label for = "ai-tone" class = "text-[11px] text-stein-600" > Ton: < span class = "font-semibold text-himmel-700" > { aiToneLabels [ aiTone - 1 ]} </ span ></ label >
< / div >
< div class = "flex items-center gap-2 overflow-visible py-1 px-2" >
< span class = "shrink-0 text-[10px] text-stein-400" > Sachlich< / span >
< input id = "ai-tone" type = "range" min = "1" max = "5" bind:value = { aiTone } class="h-3 w-full cursor-pointer accent-himmel-500 " />
< span class = "shrink-0 text-[10px] text-stein-400" > Emotional< / span >
< / div >
< / div >
< div >
< div class = "mb-1.5" >
< label for = "ai-sharpness" class = "text-[11px] text-stein-600" > Haltung: < span class = "font-semibold text-himmel-700" > { aiSharpnessLabels [ aiSharpness - 1 ]} </ span ></ label >
< / div >
< div class = "flex items-center gap-2 overflow-visible py-1 px-2" >
< span class = "shrink-0 text-[10px] text-stein-400" > Konstruktiv< / span >
< input id = "ai-sharpness" type = "range" min = "1" max = "5" bind:value = { aiSharpness } class="h-3 w-full cursor-pointer accent-himmel-500 " />
< span class = "shrink-0 text-[10px] text-stein-400" > Fordernd< / span >
< / div >
< / div >
< / div >
< div class = "mb-3" >
< label for = "ai-custom-note" class = "mb-1 block text-[11px] text-stein-600" > Persönlicher Hinweis < span class = "text-stein-400" > (optional)< / span > < / label >
< textarea
id="ai-custom-note"
bind:value={ aiCustomNote }
rows="2"
placeholder='z.B. „Ich bin Landwirt und direkt betroffen“ oder „Bitte sehr kurz halten“'
class="w-full resize-none rounded border border-himmel-200 bg-white px-2.5 py-1.5 text-[11px] text-stein-700 placeholder:text-stein-300 focus:border-himmel-400 focus:outline-none"
>< / textarea >
< / div >
< details class = "mb-3 rounded-lg border border-himmel-200 bg-white/70 overflow-hidden" >
< summary class = "flex cursor-pointer items-center justify-between bg-himmel-50 px-3 py-2 text-[11px] font-medium text-himmel-800 hover:bg-himmel-100 list-none" >
< span class = "flex items-center gap-1.5" >
< Icon icon = "mdi:tune-variant" class = "size-3.5" / >
Inhaltliche Regeln für die KI feinjustieren
{ #if aiRules . trim () !== DEFAULT_AI_RULES . trim ()}
< span class = "rounded-full bg-himmel-200 px-1.5 py-0.5 text-[9px] text-himmel-800" > angepasst< / span >
{ /if }
< / span >
< Icon icon = "mdi:chevron-down" class = "det-chevron size-4 text-himmel-600" / >
< / summary >
< div class = "border-t border-himmel-100 p-3" >
< p class = "mb-2 text-[10px] text-stein-500" >
Diese Regeln werden 1:1 in den KI-Prompt übernommen. Anpassen, wenn die KI z.B. zu locker mit Fakten umgeht oder zu sehr abweicht. Bei Unsicherheit → zurücksetzen.
< / p >
< textarea
bind:value={ aiRules }
rows="8"
spellcheck="false"
class="w-full resize-y rounded border border-himmel-200 bg-white px-2.5 py-1.5 font-mono text-[11px] leading-snug text-stein-700 focus:border-himmel-400 focus:outline-none"
>< / textarea >
< div class = "mt-1.5 flex items-center justify-between" >
< span class = "text-[10px] text-stein-400" > { aiRules . length } Zeichen</ span >
< button
type="button"
class="text-[10px] text-stein-500 underline-offset-2 hover:text-stein-700 hover:underline"
onclick={() => ( aiRules = DEFAULT_AI_RULES )}
disabled={ aiRules . trim () === DEFAULT_AI_RULES . trim ()}
>Auf Standard zurücksetzen< / button >
< / div >
< / div >
< / details >
< div class = "flex flex-wrap gap-2" >
< button
class="inline-flex items-center gap-1.5 rounded-md border border-himmel-500 bg-himmel-600 px-3.5 py-2 text-xs font-semibold text-white shadow-sm hover:bg-himmel-700"
onclick={ copyAiPrompt }
>
< Icon icon = "mdi:content-copy" class = "size-3.5" / >
{ copyAiPromptFeedback || "In Zwischenablage kopieren" }
< / button >
< button
class="inline-flex items-center gap-1.5 rounded-md border border-himmel-300 bg-white px-3.5 py-2 text-xs font-medium text-himmel-800 hover:bg-himmel-50"
onclick={ downloadAiPrompt }
title="Prompt als Textdatei speichern, alternativ zur Zwischenablage."
>
< Icon icon = "mdi:download" class = "size-3.5" / >
Als .txt herunterladen
< / button >
< / div >
< / div >
<!-- Brief im Detail anpassen, alle Feintuning - Optionen kompakt -->
< details class = "mb-4 rounded-xl border border-stein-200 bg-white overflow-hidden" >
< summary class = "flex cursor-pointer items-center justify-between bg-stein-50 px-4 py-3 text-sm font-medium text-stein-700 hover:bg-stein-100 list-none" >
< span class = "flex items-center gap-2" >
< Icon icon = "mdi:tune" class = "size-4 text-stein-500" / >
Brief im Detail anpassen
{ #if tweakBadges . length > 0 }
< span class = "text-[10px] text-stein-400" > ({ tweakBadges . join ( " · " )} )</ span >
{ /if }
< / span >
< Icon icon = "mdi:chevron-down" class = "det-chevron size-4 text-stein-400" / >
< / summary >
< div class = "border-t border-stein-200 p-4 space-y-4" >
<!-- Vorschau -->
< div >
< p class = "mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-stein-500" >
< Icon icon = "mdi:file-document-outline" class = "size-3.5" / >
Vorschau
{ #if hasManualEdit }
< span class = "rounded-full bg-himmel-100 px-2 py-0.5 text-[9px] font-normal text-himmel-700" > bearbeitet< / span >
{ /if }
< / p >
< textarea
bind:value={ outputText }
spellcheck="false"
oninput={() => { hasManualEdit = true ; }}
class="w-full resize-none rounded-lg border border-stein-200 bg-wald-50/30 px-4 py-3 font-sans text-sm leading-relaxed text-stein-800 focus:border-wald-400 focus:outline-none"
style="min-height: 20rem; field-sizing: content;"
>< / textarea >
{ #if hasManualEdit }
< div class = "mt-1 flex items-center justify-between text-xs" >
< span class = "text-stein-500" > Manuelle Änderungen aktiv< / span >
< button
class="rounded border border-stein-300 bg-white px-2 py-0.5 text-[11px] text-stein-600 hover:bg-stein-50"
onclick={ generateOutput }
>↺ Neu aus Vorlage< / button >
< / div >
{ /if }
{ #if mailTooLong }
< p class = "mt-1 text-xs text-stein-500" > Für E-Mail zu lang, bitte < strong > Kopieren< / strong > und in die Mail einfügen.< / p >
{ /if }
< / div >
{ #if selectedFragments . length > 1 }
< div >
< p class = "mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-stein-500" >
< Icon icon = "mdi:drag-vertical" class = "size-3.5" / >
Reihenfolge der Argumente ({ selectedFragments . length } )
< / p >
< div class = "divide-y divide-stein-100 rounded-lg border border-stein-200" >
{ #each selectedFragments as frag , i }
< div
draggable="true"
ondragstart={() => onDragStart ( i )}
ondragover={( e ) => onDragOver ( e , i )}
ondrop={() => onDrop ( i )}
ondragend={ onDragEnd }
class="flex cursor-grab items-center gap-3 px-3 py-2 text-sm transition-colors active:cursor-grabbing
{ dragOverIdx === i && dragIdx !== i ? 'bg-wald-50 border-t-2 border-wald-400' : 'bg-white hover:bg-stein-50' } "
>
< Icon icon = "mdi:drag-vertical" class = "size-4 shrink-0 text-stein-400" / >
< span class = "flex-1 truncate text-stein-700" > { frag . title ?? frag . _slug . replace ( /_/g , " " )} </ span >
< span class = "shrink-0 text-[10px] text-stein-400" > { i + 1 } </ span >
< / div >
{ /each }
< / div >
< / div >
{ /if }
< div class = "grid gap-3 sm:grid-cols-2" >
< div >
< p class = "mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-stein-500" >
Einleitung
{ #if openingOverride !== null }
< button class = "text-[10px] text-stein-400 underline hover:text-stein-600" onclick = {() => ( openingOverride = null )} > zurücksetzen</button >
{ /if }
< / p >
< textarea
class="w-full resize-y rounded-lg border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200"
rows="3"
value={ effectiveOpening }
oninput={( e ) => ( openingOverride = ( e . target as HTMLTextAreaElement ). value )}
>< / textarea >
< / div >
< div >
< p class = "mb-1.5 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-wide text-stein-500" >
Abschluss
{ #if closingOverride !== null }
< button class = "text-[10px] text-stein-400 underline hover:text-stein-600" onclick = {() => ( closingOverride = null )} > zurücksetzen</button >
{ /if }
< / p >
< textarea
class="w-full resize-y rounded-lg border border-stein-200 px-3 py-2 text-sm text-stein-800 focus:border-wald-400 focus:outline-none focus:ring-1 focus:ring-wald-200"
rows="3"
value={ effectiveClosing }
oninput={( e ) => ( closingOverride = ( e . target as HTMLTextAreaElement ). value )}
>< / textarea >
< / div >
< / div >
< / div >
< / details >
< div class = "mb-4 rounded-lg border border-himmel-200 bg-himmel-50 px-4 py-3 text-sm text-himmel-800" >
< strong > Hinweis:< / strong > Bitte passen Sie den Text vor dem Einreichen individuell an.
Einwendungen mit persönlichen Formulierungen sind wirksamer als identische Kopien.
< / div >
{ /if }
<!-- Bottom navigation -->
< div class = "flex flex-wrap gap-3 border-t border-stein-200 pt-4" >
< button class = "rounded-lg border border-stein-300 px-4 py-2 text-sm text-stein-600 hover:bg-stein-50" onclick = {() => goTo ( 3 )} > ← Zurück </ button >
< button class = "rounded-lg border border-erde-300 px-4 py-2 text-sm text-erde-700 hover:bg-erde-50" onclick = { resetAll } >
↻ Neustart
< / button >
< / div >
<!-- History -->
{ #if outputHistory . length > 0 }
< div class = "mt-6" >