From eb0c46411274c68c8f15543b6473f47ecd05033b Mon Sep 17 00:00:00 2001 From: Harald Pansi Date: Mon, 28 Sep 2026 16:51:31 +0200 Subject: [PATCH] Enhance Phrasen-Matcher: add Kernstoff/All/Zusatz filter and strict 'falsch ist falsch' rules with no trial-and-error cheating --- www/rhetorik_trainer_quiz.html | 298 ++++++++++++++++++++++++--------- www/sw.js | 2 +- 2 files changed, 221 insertions(+), 79 deletions(-) diff --git a/www/rhetorik_trainer_quiz.html b/www/rhetorik_trainer_quiz.html index f98693a..18155be 100644 --- a/www/rhetorik_trainer_quiz.html +++ b/www/rhetorik_trainer_quiz.html @@ -720,21 +720,39 @@ @@ -1877,10 +1904,6 @@ let isQuestionAnswered = false; let currentQuizMode = 'highlighted'; - // Matcher Game State - let matcherSelectedPhrase = null; - let matcherSelectedTerm = null; - let matcherMatchedCount = 0; let currentMatcherPairs = []; // Glossary State @@ -2917,19 +2940,68 @@ repeatBtn.classList.add('hidden'); } } - // MATCHER GAME + // ========================================== + // PHRASEN-MATCHER GAME ENGINE ("Falsch ist falsch") + // ========================================== + let matcherFilter = 'highlighted'; // 'highlighted' (bevorzugt) | 'all' | 'zusatz' + let matcherSelectedPhrase = null; + let matcherSelectedTerm = null; + let matcherCorrectCount = 0; + let matcherFailedCount = 0; + + function setMatcherFilter(filter) { + matcherFilter = filter; + ['matcherFilterKernBtn', 'matcherFilterAllBtn', 'matcherFilterZusatzBtn'].forEach(id => { + const el = document.getElementById(id); + if (el) el.className = 'px-2.5 sm:px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold'; + }); + + const activeMap = { + 'highlighted': 'matcherFilterKernBtn', + 'all': 'matcherFilterAllBtn', + 'zusatz': 'matcherFilterZusatzBtn' + }; + const activeEl = document.getElementById(activeMap[filter]); + if (activeEl) { + activeEl.className = 'px-2.5 sm:px-3 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm font-bold transition'; + } + + initMatcherGame(); + } + function initMatcherGame() { matcherSelectedPhrase = null; matcherSelectedTerm = null; - matcherMatchedCount = 0; + matcherCorrectCount = 0; + matcherFailedCount = 0; document.getElementById('matchScore').innerText = '0'; - document.getElementById('matcherWinBanner').classList.add('hidden'); + document.getElementById('matchFailScore').innerText = '0'; + document.getElementById('matcherResultBanner').classList.add('hidden'); - const shuffled = [...STILMITTEL_DATA].sort(() => Math.random() - 0.5).slice(0, 5); + let pool = STILMITTEL_DATA; + if (matcherFilter === 'highlighted') { + pool = STILMITTEL_DATA.filter(x => x.highlighted); + } else if (matcherFilter === 'zusatz') { + pool = STILMITTEL_DATA.filter(x => !x.highlighted); + } + if (pool.length < 5) pool = [...STILMITTEL_DATA]; + + const shuffled = [...pool].sort(() => Math.random() - 0.5).slice(0, 5); currentMatcherPairs = shuffled; - const phrases = shuffled.map(x => ({ id: x.id, text: x.examples[0] })).sort(() => Math.random() - 0.5); - const terms = shuffled.map(x => ({ id: x.id, name: x.term })).sort(() => Math.random() - 0.5); + // Pick randomly from all 10 examples for rich variation + const phrases = shuffled.map(x => ({ + id: x.id, + term: x.term, + text: x.examples[Math.floor(Math.random() * x.examples.length)], + resolved: false + })).sort(() => Math.random() - 0.5); + + const terms = shuffled.map(x => ({ + id: x.id, + name: x.term, + resolved: false + })).sort(() => Math.random() - 0.5); const phrasesContainer = document.getElementById('matcherPhrasesContainer'); const termsContainer = document.getElementById('matcherTermsContainer'); @@ -2938,93 +3010,163 @@ termsContainer.innerHTML = ''; phrases.forEach(p => { - const btn = document.createElement('div'); - btn.className = 'match-phrase-card p-4 bg-white rounded-2xl border-2 border-slate-200 text-sm font-serif italic text-slate-800 cursor-pointer hover:border-indigo-400 hover:bg-slate-50 transition shadow-sm select-none'; - btn.innerText = `„${p.text}“`; - btn.onclick = () => selectMatcherPhrase(p.id, btn); - phrasesContainer.appendChild(btn); + const card = document.createElement('div'); + card.id = `match-phrase-${p.id}`; + card.className = 'match-phrase-card p-4 bg-white rounded-2xl border-2 border-slate-200 text-sm font-serif italic text-slate-800 cursor-pointer hover:border-indigo-400 hover:bg-slate-50 transition shadow-sm select-none flex items-start justify-between gap-2'; + + const cleanSpeechText = p.text.replace(/'/g, "\\'").replace(/"/g, '"'); + card.innerHTML = ` +
„${p.text}“
+ + `; + card.onclick = () => selectMatcherPhrase(p.id, card, p.term, p.text); + phrasesContainer.appendChild(card); }); terms.forEach(t => { - const btn = document.createElement('div'); - btn.className = 'match-term-card p-4 bg-white rounded-2xl border-2 border-slate-200 text-sm font-sans font-bold text-slate-800 cursor-pointer hover:border-indigo-400 hover:bg-slate-50 transition shadow-sm select-none flex items-center justify-between'; - btn.innerHTML = `${t.name}`; - btn.onclick = () => selectMatcherTerm(t.id, btn); - termsContainer.appendChild(btn); + const card = document.createElement('div'); + card.id = `match-term-${t.id}`; + card.className = 'match-term-card p-4 bg-white rounded-2xl border-2 border-slate-200 text-sm font-sans font-bold text-slate-800 cursor-pointer hover:border-indigo-400 hover:bg-slate-50 transition shadow-sm select-none flex items-center justify-between'; + card.innerHTML = `${t.name}`; + card.onclick = () => selectMatcherTerm(t.id, card, t.name); + termsContainer.appendChild(card); }); } - function selectMatcherPhrase(id, el) { - if (el.classList.contains('opacity-40')) return; + function selectMatcherPhrase(id, el, term, text) { + if (el.classList.contains('pointer-events-none')) return; document.querySelectorAll('.match-phrase-card').forEach(c => { - if (!c.classList.contains('opacity-40')) { - c.classList.remove('border-indigo-600', 'bg-indigo-50'); + if (!c.classList.contains('pointer-events-none')) { + c.classList.remove('border-indigo-600', 'bg-indigo-50', 'ring-2', 'ring-indigo-300'); c.classList.add('border-slate-200'); } }); - matcherSelectedPhrase = { id, el }; + matcherSelectedPhrase = { id, el, term, text }; el.classList.remove('border-slate-200'); - el.classList.add('border-indigo-600', 'bg-indigo-50'); + el.classList.add('border-indigo-600', 'bg-indigo-50', 'ring-2', 'ring-indigo-300'); playTone('flip'); checkMatcherMatch(); } - function selectMatcherTerm(id, el) { - if (el.classList.contains('opacity-40')) return; + function selectMatcherTerm(id, el, name) { + if (el.classList.contains('pointer-events-none')) return; document.querySelectorAll('.match-term-card').forEach(c => { - if (!c.classList.contains('opacity-40')) { - c.classList.remove('border-indigo-600', 'bg-indigo-50'); + if (!c.classList.contains('pointer-events-none')) { + c.classList.remove('border-indigo-600', 'bg-indigo-50', 'ring-2', 'ring-indigo-300'); c.classList.add('border-slate-200'); } }); - matcherSelectedTerm = { id, el }; + matcherSelectedTerm = { id, el, name }; el.classList.remove('border-slate-200'); - el.classList.add('border-indigo-600', 'bg-indigo-50'); + el.classList.add('border-indigo-600', 'bg-indigo-50', 'ring-2', 'ring-indigo-300'); playTone('flip'); checkMatcherMatch(); } function checkMatcherMatch() { - if (matcherSelectedPhrase && matcherSelectedTerm) { - if (matcherSelectedPhrase.id === matcherSelectedTerm.id) { - playTone('success'); - const pEl = matcherSelectedPhrase.el; - const tEl = matcherSelectedTerm.el; + if (!matcherSelectedPhrase || !matcherSelectedTerm) return; - pEl.className = 'p-4 bg-emerald-50 border-2 border-emerald-400 text-sm font-serif italic text-emerald-900 rounded-2xl opacity-40 pointer-events-none transition'; - tEl.className = 'p-4 bg-emerald-50 border-2 border-emerald-400 text-sm font-sans font-bold text-emerald-900 rounded-2xl opacity-40 pointer-events-none transition flex items-center justify-between'; - tEl.innerHTML += ``; + const pObj = matcherSelectedPhrase; + const tObj = matcherSelectedTerm; + const isMatch = pObj.id === tObj.id; - matcherMatchedCount++; - document.getElementById('matchScore').innerText = matcherMatchedCount; + if (isMatch) { + // Richtig zugeordnet! + playTone('success'); + matcherCorrectCount++; + document.getElementById('matchScore').innerText = matcherCorrectCount; - matcherSelectedPhrase = null; - matcherSelectedTerm = null; + // Phrase dauerhaft als RICHTIG sperren + pObj.el.className = 'p-4 bg-emerald-50 border-2 border-emerald-500 text-sm font-serif italic text-emerald-950 rounded-2xl transition shadow-sm pointer-events-none flex items-start justify-between gap-2'; + pObj.el.innerHTML = ` +
„${pObj.text}“
+ + Richtig + + `; - if (matcherMatchedCount === 5) { - playTone('win'); - document.getElementById('matcherWinBanner').classList.remove('hidden'); - } - } else { - playTone('error'); - const pEl = matcherSelectedPhrase.el; - const tEl = matcherSelectedTerm.el; + // Begriff dauerhaft als RICHTIG sperren + tObj.el.className = 'p-4 bg-emerald-50 border-2 border-emerald-500 text-sm font-sans font-bold text-emerald-950 rounded-2xl transition shadow-sm pointer-events-none flex items-center justify-between'; + tObj.el.innerHTML = `${tObj.name}`; - pEl.classList.add('border-rose-500', 'bg-rose-50'); - tEl.classList.add('border-rose-500', 'bg-rose-50'); + addXP(20, 'Matcher Paar gelöst'); + matcherSelectedPhrase = null; + matcherSelectedTerm = null; + } else { + // "FALSCH IST FALSCH!" - Ein Fehlversuch verbrennt dieses Zitat unwiderruflich! + playTone('error'); + matcherFailedCount++; + document.getElementById('matchFailScore').innerText = matcherFailedCount; - setTimeout(() => { - pEl.classList.remove('border-rose-500', 'bg-rose-50', 'border-indigo-600', 'bg-indigo-50'); - tEl.classList.remove('border-rose-500', 'bg-rose-50', 'border-indigo-600', 'bg-indigo-50'); - pEl.classList.add('border-slate-200'); - tEl.classList.add('border-slate-200'); - matcherSelectedPhrase = null; - matcherSelectedTerm = null; - }, 350); + // Phrase DAUERHAFT als FALSCH sperren und Lösung aufdecken + pObj.el.className = 'p-4 bg-rose-50 border-2 border-rose-500 text-sm font-serif italic text-rose-950 rounded-2xl transition shadow-sm pointer-events-none space-y-1.5'; + pObj.el.innerHTML = ` +
„${pObj.text}“
+
+ Falsch! + Lösung: ${pObj.term} +
+ `; + + // Begriff blinkt kurz rot auf und wird dann wieder neutral freigegeben (für andere Zitate) + tObj.el.classList.add('border-rose-500', 'bg-rose-50'); + setTimeout(() => { + tObj.el.classList.remove('border-rose-500', 'bg-rose-50', 'border-indigo-600', 'bg-indigo-50', 'ring-2', 'ring-indigo-300'); + tObj.el.classList.add('border-slate-200'); + }, 450); + + // Als Schwachstelle speichern für gezieltes Nachlernen + if (!quizMistakes.includes(pObj.id)) { + quizMistakes.push(pObj.id); + localStorage.setItem('rhetorik_quiz_mistakes', JSON.stringify(quizMistakes)); + updateQuizMistakesBadge(); } + + matcherSelectedPhrase = null; + matcherSelectedTerm = null; + } + + // Prüfen, ob alle 5 Paare versucht wurden (richtig + falsch = 5) + if (matcherCorrectCount + matcherFailedCount === 5) { + setTimeout(finishMatcherRound, 500); } } + function finishMatcherRound() { + const banner = document.getElementById('matcherResultBanner'); + const title = document.getElementById('matcherResultTitle'); + const subtitle = document.getElementById('matcherResultSubtitle'); + const icon = document.getElementById('matcherResultIcon'); + const weakBtn = document.getElementById('matcherWeakspotsBtn'); + + banner.classList.remove('hidden', 'bg-emerald-600', 'bg-indigo-600', 'bg-slate-900', 'bg-rose-600'); + + if (matcherCorrectCount === 5) { + playTone('win'); + banner.classList.add('bg-emerald-600'); + icon.innerHTML = ''; + title.innerText = 'Perfekt zugeordnet! 🏆'; + subtitle.innerText = 'Alle 5 Paare auf Anhieb fehlerfrei erkannt. Keine Fehler gemacht!'; + addXP(60, 'Matcher fehlerfrei'); + weakBtn.classList.add('hidden'); + } else if (matcherCorrectCount >= 3) { + playTone(matcherCorrectCount >= 4 ? 'win' : 'flip'); + banner.classList.add('bg-indigo-600'); + icon.innerHTML = ''; + title.innerText = `Runde beendet: ${matcherCorrectCount} von 5 richtig!`; + subtitle.innerText = `Falsch ist falsch: ${matcherFailedCount} Fehler gemacht. Die verpassten Begriffe wurden in deine Schwachstellen übernommen.`; + weakBtn.classList.remove('hidden'); + } else { + playTone('error'); + banner.classList.add('bg-slate-900'); + icon.innerHTML = ''; + title.innerText = `Ergebnis: ${matcherCorrectCount} von 5 Paaren richtig`; + subtitle.innerText = `Falsch ist falsch: ${matcherFailedCount} Fehlversuche. Wiederhole die Begriffe gezielt im Schwachstellen-Trainer oder Glossar.`; + weakBtn.classList.remove('hidden'); + } + } // GLOSSAR & SUCHE function setGlossaryFilter(filter) { currentGlossaryFilter = filter; diff --git a/www/sw.js b/www/sw.js index 0d79865..7082007 100644 --- a/www/sw.js +++ b/www/sw.js @@ -1,4 +1,4 @@ -const CACHE_NAME = 'rhetorikmaster-v9'; +const CACHE_NAME = 'rhetorikmaster-v10'; const STATIC_ASSETS = [ '/', '/index.html',