diff --git a/frontend/src/components/BausteinPanel.vue b/frontend/src/components/BausteinPanel.vue
index 5283078..ecadca8 100644
--- a/frontend/src/components/BausteinPanel.vue
+++ b/frontend/src/components/BausteinPanel.vue
@@ -33,24 +33,26 @@ const st = computed(() => props.status || { gute_antworten: 0, streak: 0, absolv
const MODI = ['quiz_leicht', 'quiz_schwer', 'lueck_leicht', 'lueck_schwer', 'erklaeren']
const score = computed(() => st.value.gute_antworten || 0)
const schwerWahl = ref(false) // leicht/schwer in den Bändern unter 19 (ALT+E)
-// Form wechselt NACH dem Meilenstein (>): Quiz 0–6, Lückentext 7–12, Erklären 13–18,
-// Zufall 19–30 (jede Frage ein zufälliger Modus). 'zufall' = Platzhalter; das gezogene
-// Objekt trägt die echte Form/Schwierigkeit.
-const aktiverModus = computed(() => {
- if (!musterModus.value) return 'erklaeren'
- if (score.value > GEMEISTERT) return 'zufall'
- if (score.value > VERSTANDEN) return 'erklaeren'
- if (score.value > ABSOLVIERT) return schwerWahl.value ? 'lueck_schwer' : 'lueck_leicht'
- return schwerWahl.value ? 'quiz_schwer' : 'quiz_leicht'
-})
-const imZufall = computed(() => aktiverModus.value === 'zufall')
-// Reines Band aus dem aktuellen Score (ohne Schwierigkeit) — für den Abschnittswechsel.
-const aktuellesBand = computed(() => {
- if (score.value > GEMEISTERT) return 'zufall'
- if (score.value > VERSTANDEN) return 'erklaeren'
- if (score.value > ABSOLVIERT) return 'lueckentext'
+// Band aus einem Score: Lückentext ab 6, Erklären ab 12, Zufall ab 18 (Meilenstein-inklusiv).
+function bandFuer(s) {
+ if (s >= GEMEISTERT) return 'zufall'
+ if (s >= VERSTANDEN) return 'erklaeren'
+ if (s >= ABSOLVIERT) return 'lueckentext'
return 'quiz'
+}
+// Am Format-Max: Score eingefroren („Max"). Gilt auch bei cap 30 (FullGuide, endgültig).
+const amDeckel = computed(() => score.value >= props.cap)
+// Aktuelles Band: am Deckel bleibt das Band des erreichten Formats (cap−1), sonst score-basiert.
+// Ohne Frage-Muster (alter Baustein) gibt es nur Erklären.
+const aktuellesBand = computed(() => musterModus.value ? bandFuer(amDeckel.value ? props.cap - 1 : score.value) : 'erklaeren')
+// 'zufall' = Platzhalter; das gezogene Objekt trägt die echte Form/Schwierigkeit.
+const aktiverModus = computed(() => {
+ const b = aktuellesBand.value
+ if (b === 'lueckentext') return schwerWahl.value ? 'lueck_schwer' : 'lueck_leicht'
+ if (b === 'quiz') return schwerWahl.value ? 'quiz_schwer' : 'quiz_leicht'
+ return b // 'erklaeren' | 'zufall'
})
+const imZufall = computed(() => aktuellesBand.value === 'zufall')
const aktiveForm = computed(() => {
const m = aktiverModus.value
if (m === 'zufall') return 'quiz' // Platzhalter fürs idle-Widget; Objekt überschreibt
@@ -76,15 +78,13 @@ const modusRegel = computed(() => {
if (zeigeForm.value === 'lueckentext') return aktSchwer.value ? 'frei tippen · +3/−1' : 'Begriff aus 4 · +1/−1'
return '+1/+2/+3 · Nachfragen max +1 · schwach −1'
})
-// Ende des aktuellen Bands (nächster Meilenstein) und ob der Score am Format-Deckel klebt.
+// Ende des aktuellen Bands (nächster Meilenstein).
const bandZiel = computed(() => {
- if (score.value > GEMEISTERT) return VOLL
- if (score.value > VERSTANDEN) return GEMEISTERT
- if (score.value > ABSOLVIERT) return VERSTANDEN
+ if (score.value >= GEMEISTERT) return VOLL
+ if (score.value >= VERSTANDEN) return GEMEISTERT
+ if (score.value >= ABSOLVIERT) return VERSTANDEN
return ABSOLVIERT
})
-// Am Format-Max: Score eingefroren („Max"). Gilt auch bei cap 30 (FullGuide, endgültig).
-const amDeckel = computed(() => score.value >= props.cap)
const FORM_NAME = { quiz: 'Quiz', lueckentext: 'Lückentext', erklaeren: 'Erklären' }
const NAECHSTES_FORMAT = { 6: 'Guide', 12: 'ProGuide', 18: 'FullGuide' }
@@ -207,7 +207,15 @@ function objText(o) { return (o && (o.frage || o.satz)) || '' }
// Lückensatz rendern: an „___" splitten, Teile inline (mit $…$-Mathe), Lücke als Span.
// So bleibt die Lücke erhalten und Markdown fasst die Unterstriche nicht an.
function lueckSatzHtml(satz) {
- return String(satz || '').split('___').map((t) => renderMarkdownInline(t)).join('______')
+ satz = String(satz || '')
+ const m = satz.match(/_{3,}/) // erste Lücke (drei oder mehr Unterstriche)
+ if (!m) return renderMarkdownInline(satz)
+ // Liegt die Lücke INNERHALB einer $…$-Formel (ungerade Zahl $ davor)? Dann nicht splitten
+ // (das zerreißt die Formel) — stattdessen durch eine KaTeX-Linie ersetzen, Satz am Stück rendern.
+ const inFormel = ((satz.slice(0, m.index).match(/\$/g) || []).length % 2) === 1
+ if (inFormel) return renderMarkdownInline(satz.replace(/_{3,}/, '\\rule{2.5em}{0.4pt}'))
+ // Lücke im Text: splitten + Lücken-Span.
+ return satz.split(/_{3,}/).map((t) => renderMarkdownInline(t)).join('______')
}
function istBekannt(o) {
diff --git a/templates/Prompt/Baustein-Lueckentext.md b/templates/Prompt/Baustein-Lueckentext.md
index 80878ad..c0d9c66 100644
--- a/templates/Prompt/Baustein-Lueckentext.md
+++ b/templates/Prompt/Baustein-Lueckentext.md
@@ -12,6 +12,9 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden):
AUFGABE:
- Schreibe EINEN kurzen Aussagesatz (höchstens ~15 Wörter, keine Nebensatz-Kaskade).
- Ersetze den **Schlüsselbegriff** durch `___` (genau eine Lücke).
+- Die Lücke ist ein **Fachbegriff als Wort** (z. B. „Alphabet", „polynomiell") — KEIN
+ mathematisches Symbol oder Formelteil (nicht `L_2`, nicht `f(w)`). Sie steht **außerhalb**
+ von `$…$` (nie innerhalb einer Formel).
- Die Lücke prüft aktives Abrufen — sie steht für einen zentralen Fachbegriff, nicht für ein Füllwort.
- Der Satz gibt genug Kontext, dass die Lösung eindeutig ist (keine Raterei).
- Fachbegriffe/Formeln dürfen als $…$ gesetzt werden (z. B. $V(w,c)$).
diff --git a/templates/Prompt/Baustein-Lueckwahl.md b/templates/Prompt/Baustein-Lueckwahl.md
index e42cbc0..52b3a06 100644
--- a/templates/Prompt/Baustein-Lueckwahl.md
+++ b/templates/Prompt/Baustein-Lueckwahl.md
@@ -11,6 +11,8 @@ KOMPAKTE FASSUNG (Merksätze, falls vorhanden):
AUFGABE:
- Schreibe EINEN kurzen Aussagesatz (höchstens ~15 Wörter). Ersetze den **Schlüsselbegriff** durch `___` (genau eine Lücke).
+- Die Lücke ist ein **Fachbegriff als Wort** — KEIN mathematisches Symbol/Formelteil
+ (nicht `L_2`, nicht `f(w)`). Sie steht **außerhalb** von `$…$` (nie innerhalb einer Formel).
- Biete GENAU VIER Begriffe als Optionen — **genau einer** passt in die Lücke.
- Die drei falschen sind plausibel, aber klar falsch (Verwechslung, verwandter Begriff) — keine Albernheiten.
- Optionen kurz (Begriff oder kurzer Halbsatz), ähnlich lang.