Files
RhetorikMaster/www/rhetorik_trainer_quiz.html
T

1949 lines
92 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de" class="h-full">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0, viewport-fit=cover">
<!-- PWA & Mobile Meta Tags -->
<meta name="description" content="Interaktiver Trainer für rhetorische Stilmittel der Textanalyse mit Karteikarten, Leitner-System, Quiz, Schwachstellen-Trainer und Spickzettel-Druck.">
<meta name="theme-color" content="#4f46e5">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
<meta name="apple-mobile-web-app-title" content="RhetorikMaster">
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<link rel="apple-touch-icon" href="/icon-192.png">
<link rel="manifest" href="/manifest.json">
<title>RhetorikMaster - Stilmittel Trainer, Quiz & Klausur-Spickzettel</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Google Fonts Inter -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
fontFamily: {
sans: ['"Plus Jakarta Sans"', 'system-ui', '-apple-system', 'sans-serif'],
},
colors: {
brand: {
50: '#eef2ff',
100: '#e0e7ff',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
}
}
}
}
}
</script>
<style>
/* 3D Flip Card Styles */
.perspective-1000 {
perspective: 1000px;
}
.transform-style-3d {
transform-style: preserve-3d;
}
.backface-hidden {
backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
.rotate-y-180 {
transform: rotateY(180deg);
}
/* Custom scrollbar */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: #f1f5f9;
}
::-webkit-scrollbar-thumb {
background: #cbd5e1;
border-radius: 9999px;
}
::-webkit-scrollbar-thumb:hover {
background: #94a3b8;
}
/* Swipe animations */
.swipe-left {
transform: translateX(-150%) rotate(-20deg) !important;
opacity: 0 !important;
transition: all 0.35s ease-out !important;
}
.swipe-right {
transform: translateX(150%) rotate(20deg) !important;
opacity: 0 !important;
transition: all 0.35s ease-out !important;
}
/* Print Stylesheet for Klausur-Spickzettel */
@media print {
body {
background: #ffffff !important;
color: #000000 !important;
font-size: 10pt !important;
}
header, nav, #view-cards, #view-quiz, #view-matcher, #toastNotification, #detailModal, .no-print {
display: none !important;
}
#view-glossary {
display: none !important;
}
#printCheatsheetSection {
display: block !important;
}
.page-break {
page-break-after: always;
}
table {
border-collapse: collapse !important;
width: 100% !important;
}
th, td {
border: 1px solid #cbd5e1 !important;
padding: 6px 8px !important;
}
th {
background-color: #f1f5f9 !important;
}
}
@media screen {
#printCheatsheetSection {
display: none;
}
}
</style>
</head>
<body class="bg-slate-50 text-slate-800 font-sans min-h-full flex flex-col selection:bg-indigo-100 selection:text-indigo-800">
<header class="bg-white border-b border-slate-200 sticky top-0 z-40 shadow-sm no-print">
<div class="max-w-6xl mx-auto px-3.5 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16 sm:h-20 gap-2">
<!-- Brand Logo -->
<div class="flex items-center space-x-2.5 sm:space-x-3 cursor-pointer select-none" onclick="switchTab('cards')">
<div class="w-10 h-10 rounded-2xl bg-gradient-to-tr from-indigo-600 to-violet-600 flex items-center justify-center text-white shadow-md shadow-indigo-200">
<i class="fa-solid fa-feather-pointed text-lg"></i>
</div>
<div>
<div class="flex items-center gap-1.5 sm:gap-2">
<span class="text-xl sm:text-2xl font-black tracking-tight text-slate-900">Rhetorik<span class="text-indigo-600">Master</span></span>
<span class="px-2 py-0.5 text-[11px] sm:text-xs font-bold bg-indigo-50 text-indigo-700 rounded-full border border-indigo-100">Pro</span>
</div>
<p class="text-xs text-slate-500 hidden sm:block">Stilmittel sicher erkennen, lernen & anwenden</p>
</div>
</div>
<!-- Global Status / Actions -->
<div class="flex items-center gap-1.5 sm:gap-3">
<!-- Sound Toggle -->
<button id="soundToggleBtn" onclick="toggleSound()" title="Soundeffekte umschalten (M)" class="p-2 sm:px-3 sm:py-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs sm:text-sm font-bold border border-slate-200/80 transition flex items-center gap-1.5">
<i id="soundIcon" class="fa-solid fa-volume-high text-indigo-600"></i>
<span class="hidden md:inline" id="soundLabel">Ton An</span>
</button>
<!-- PWA Install Button -->
<button id="pwaInstallBtn" onclick="installPWA()" class="hidden px-3 py-2 rounded-xl bg-indigo-50 hover:bg-indigo-100 text-indigo-700 text-xs sm:text-sm font-bold border border-indigo-200 transition items-center gap-1.5">
<i class="fa-solid fa-download text-indigo-600"></i>
<span>App installieren</span>
</button>
<!-- Print Cheatsheet -->
<button onclick="printCheatsheet()" title="Klausur-Spickzettel drucken / als PDF (P)" class="p-2 sm:px-3 sm:py-2 rounded-xl bg-slate-100 hover:bg-slate-200 text-slate-700 text-xs sm:text-sm font-bold border border-slate-200/80 transition flex items-center gap-1.5">
<i class="fa-solid fa-print text-indigo-600"></i>
<span class="hidden sm:inline">Spickzettel</span>
</button>
<!-- Mastered Stat Badge -->
<div class="bg-indigo-50/90 px-2.5 sm:px-3.5 py-1.5 sm:py-2 rounded-xl flex items-center space-x-1.5 sm:space-x-2 text-xs sm:text-sm font-bold text-indigo-900 border border-indigo-100/90">
<i class="fa-solid fa-graduation-cap text-indigo-600 text-sm"></i>
<span id="globalMasteredStat">0 / 23 gelernt</span>
</div>
</div>
</div>
<!-- Navigation Tabs -->
<nav class="flex space-x-2 overflow-x-auto py-2.5 scrollbar-none border-t border-slate-100 text-xs sm:text-sm font-bold">
<button onclick="switchTab('cards')" id="tab-btn-cards" class="tab-btn active px-3.5 sm:px-4 py-2 rounded-xl flex items-center gap-2 transition duration-150 whitespace-nowrap bg-indigo-50 text-indigo-700 font-bold shadow-sm">
<i class="fa-regular fa-clone text-sm"></i>
<span>Karteikarten</span>
</button>
<button onclick="switchTab('quiz')" id="tab-btn-quiz" class="tab-btn px-3.5 sm:px-4 py-2 rounded-xl flex items-center gap-2 transition duration-150 whitespace-nowrap text-slate-600 hover:text-slate-900 hover:bg-slate-100 font-semibold">
<i class="fa-solid fa-brain text-sm"></i>
<span>Quiz-Modus</span>
</button>
<button onclick="switchTab('matcher')" id="tab-btn-matcher" class="tab-btn px-3.5 sm:px-4 py-2 rounded-xl flex items-center gap-2 transition duration-150 whitespace-nowrap text-slate-600 hover:text-slate-900 hover:bg-slate-100 font-semibold">
<i class="fa-solid fa-puzzle-piece text-sm"></i>
<span>Phrasen-Matcher</span>
</button>
<button onclick="switchTab('glossary')" id="tab-btn-glossary" class="tab-btn px-3.5 sm:px-4 py-2 rounded-xl flex items-center gap-2 transition duration-150 whitespace-nowrap text-slate-600 hover:text-slate-900 hover:bg-slate-100 font-semibold">
<i class="fa-solid fa-book-open text-sm"></i>
<span>Glossar & Suche</span>
</button>
</nav>
</div>
</header>
<main class="flex-1 max-w-6xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-6 no-print">
<!-- ========================================== -->
<!-- VIEW 1: FLASHCARDS (Leitner 5-Box-System) -->
<!-- ========================================== -->
<section id="view-cards" class="space-y-4 sm:space-y-5">
<!-- Filter Bar: Categories & Leitner Boxes -->
<div class="bg-white p-4 sm:p-5 rounded-2xl border border-slate-200/80 shadow-sm space-y-3.5">
<div class="flex flex-col lg:flex-row justify-between items-start lg:items-center gap-3 sm:gap-4">
<div>
<h2 class="text-base sm:text-xl font-extrabold text-slate-900 flex items-center gap-2">
<span>Leitner-Karteikarten (5-Kasten-System)</span>
</h2>
<p class="text-xs sm:text-sm text-slate-500 mt-0.5">Wische nach links (wiederholen) oder rechts (gekonnt) oder tippe zum Wenden.</p>
</div>
<div class="flex flex-wrap items-center gap-2 w-full lg:w-auto justify-between lg:justify-end">
<!-- Card Face Toggle -->
<div class="inline-flex p-1 bg-slate-100 rounded-xl border border-slate-200 text-xs sm:text-sm font-bold">
<button id="cardModeTermBtn" onclick="setCardFaceMode('term')" class="px-3 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm transition font-bold">
Begriff zuerst
</button>
<button id="cardModeExampleBtn" onclick="setCardFaceMode('example')" class="px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold">
Beispiel zuerst
</button>
</div>
<!-- Shuffle & Reset -->
<div class="flex items-center gap-1.5">
<button onclick="shuffleCards()" title="Karten mischen" class="p-2 sm:px-3 sm:py-2 rounded-xl border border-slate-200 hover:bg-slate-100 text-slate-600 text-xs sm:text-sm font-bold transition flex items-center gap-1">
<i class="fa-solid fa-shuffle"></i>
</button>
<button onclick="resetLeitnerProgress()" title="Lernstand für Karten zurücksetzen" class="p-2 sm:px-3 sm:py-2 rounded-xl border border-slate-200 hover:bg-rose-50 hover:text-rose-600 text-slate-500 text-xs sm:text-sm font-bold transition flex items-center gap-1">
<i class="fa-solid fa-rotate-left"></i>
</button>
</div>
</div>
</div>
<!-- Filter Pills -->
<div class="pt-2 border-t border-slate-100 flex flex-wrap items-center justify-between gap-2.5 text-xs sm:text-sm">
<!-- Selection & Category Selector -->
<div class="flex flex-wrap items-center gap-1.5">
<span class="font-bold text-slate-400 uppercase text-[11px] mr-1">Auswahl:</span>
<button onclick="setCardCategoryFilter('highlighted')" id="cardCatBtn-highlighted" class="card-cat-pill px-3 py-1.5 rounded-xl font-bold bg-indigo-50 text-indigo-700 border border-indigo-200">🌟 Kernstoff (17)</button>
<button onclick="setCardCategoryFilter('all')" id="cardCatBtn-all" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">Alle (23)</button>
<button onclick="setCardCategoryFilter('zusatz')" id="cardCatBtn-zusatz" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">🔍 Zusatzinfo (6)</button>
<span class="text-slate-300 mx-0.5">|</span>
<button onclick="setCardCategoryFilter('klang')" id="cardCatBtn-klang" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">Klang</button>
<button onclick="setCardCategoryFilter('syntax')" id="cardCatBtn-syntax" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">Syntax</button>
<button onclick="setCardCategoryFilter('wort')" id="cardCatBtn-wort" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">Wort / Tropen</button>
<button onclick="setCardCategoryFilter('gedanke')" id="cardCatBtn-gedanke" class="card-cat-pill px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200">Gedanken</button>
</div>
<!-- Leitner Box Filter -->
<div class="flex items-center gap-1.5">
<span class="font-bold text-slate-400 uppercase text-[11px] mr-1">Kasten:</span>
<select id="leitnerBoxFilter" onchange="filterByLeitnerBox(this.value)" class="bg-slate-100 border border-slate-200 text-slate-700 text-xs sm:text-sm rounded-xl px-3 py-1.5 font-bold focus:outline-none focus:ring-1 focus:ring-indigo-500">
<option value="all">Alle Kästen (1-5)</option>
<option value="1">Kasten 1 (Neu / Unsicher)</option>
<option value="2">Kasten 2 (Im Aufbau)</option>
<option value="3">Kasten 3 (Vertieft)</option>
<option value="4">Kasten 4 (Fast sicher)</option>
<option value="5">Kasten 5 (Gemeistert)</option>
</select>
</div>
</div>
</div>
<!-- Progress bar & Leitner Box Indicators -->
<div class="space-y-1.5">
<div class="flex justify-between items-center text-xs sm:text-sm text-slate-600 font-bold px-1">
<span id="cardProgressLabel">Karte 1 von 17 (🌟 Kernstoff)</span>
<div class="flex items-center gap-2">
<span id="cardBoxBadge" class="px-2.5 py-0.5 rounded-lg bg-amber-50 text-amber-800 border border-amber-200 font-bold text-xs">Kasten 1</span>
<div id="leitnerBoxesVisual" class="flex gap-1">
<span class="w-2.5 h-2.5 rounded-full bg-indigo-600"></span>
<span class="w-2.5 h-2.5 rounded-full bg-slate-200"></span>
<span class="w-2.5 h-2.5 rounded-full bg-slate-200"></span>
<span class="w-2.5 h-2.5 rounded-full bg-slate-200"></span>
<span class="w-2.5 h-2.5 rounded-full bg-slate-200"></span>
</div>
</div>
</div>
<div class="w-full bg-slate-200 h-2.5 rounded-full overflow-hidden">
<div id="cardProgressBar" class="bg-indigo-600 h-full transition-all duration-300 w-0"></div>
</div>
</div>
<!-- Flashcard 3D Stage -->
<div class="max-w-xl mx-auto py-2 relative">
<div class="perspective-1000 w-full h-[380px] sm:h-[410px] cursor-pointer touch-pan-y" id="flashcardContainer">
<div id="flashcardElement" class="relative w-full h-full transition-transform duration-500 transform-style-3d select-none">
<!-- CARD FRONT -->
<div class="absolute inset-0 w-full h-full backface-hidden bg-white rounded-3xl border-2 border-indigo-100 shadow-xl p-6 sm:p-8 flex flex-col justify-between items-center text-center select-none hover:border-indigo-300 transition-colors">
<div class="w-full flex justify-between items-center text-xs sm:text-sm font-bold text-slate-400 uppercase tracking-wider">
<span id="cardCounter">Karte 1 von 17</span>
<span id="cardBadgeFront" class="px-3 py-1 rounded-full bg-indigo-50 text-indigo-700 border border-indigo-100 font-bold lowercase">Klangfigur</span>
</div>
<!-- Main Front Content -->
<div class="my-auto px-2 w-full">
<div id="cardFrontHint" class="text-xs sm:text-sm font-extrabold uppercase tracking-wider text-indigo-600 mb-2">Rhetorisches Stilmittel</div>
<div id="cardFrontMain" class="text-3xl sm:text-4xl font-black text-slate-900 leading-tight tracking-tight">
Alliteration
</div>
<div id="cardFrontSub" class="text-sm sm:text-base text-slate-500 mt-4 font-medium italic">
Tippe oder drücke [Leertaste] zum Aufdecken
</div>
</div>
<div class="text-xs sm:text-sm text-slate-400 flex items-center gap-2 font-semibold">
<span class="hidden sm:inline-block px-1.5 py-0.5 bg-slate-100 border border-slate-200 rounded font-mono text-[10px]">Space</span>
<span><i class="fa-solid fa-arrow-rotate-right text-indigo-400"></i> Antippen zum Wenden</span>
</div>
</div>
<!-- CARD BACK -->
<div class="absolute inset-0 w-full h-full backface-hidden rotate-y-180 bg-gradient-to-b from-indigo-950 via-slate-900 to-slate-950 text-white rounded-3xl border-2 border-indigo-500/30 shadow-2xl p-6 sm:p-8 flex flex-col justify-between text-left select-none">
<div class="flex justify-between items-center">
<span id="cardBackTerm" class="text-xl sm:text-2xl font-black text-indigo-300 flex items-center gap-2">
<i class="fa-regular fa-lightbulb text-amber-400"></i>
<span>Alliteration</span>
</span>
<span id="cardHighlightBadge" class="text-xs font-bold uppercase px-3 py-1 rounded-full bg-indigo-500/30 text-indigo-200 border border-indigo-400/40">
🌟 Kernstoff
</span>
</div>
<!-- Content Back -->
<div class="space-y-3.5 my-auto overflow-y-auto pr-1">
<div>
<span class="text-xs font-bold text-slate-400 uppercase tracking-wider block">Definition & Wirkung</span>
<p id="cardBackDefinition" class="text-base sm:text-lg text-slate-100 font-medium mt-1 leading-relaxed">
Mehrere Wörter beginnen mit demselben Anlaut.
</p>
</div>
<div class="bg-white/10 p-3.5 rounded-2xl border border-white/10 backdrop-blur-sm">
<span class="text-xs font-bold text-indigo-300 uppercase tracking-wider block">Typische Phrasen & Zitate</span>
<p id="cardBackExamples" class="text-base sm:text-lg font-serif italic text-amber-200 mt-1">
„Fischers Fritz fischt frische Fische.“
</p>
</div>
<div id="cardBackTipContainer" class="bg-amber-950/40 p-3 rounded-2xl border border-amber-500/20">
<span class="text-xs font-bold text-amber-400 uppercase tracking-wider block flex items-center gap-1">
<i class="fa-solid fa-wand-magic-sparkles"></i> Merkhilfe & Tipp
</span>
<p id="cardBackTip" class="text-xs sm:text-sm text-amber-100 mt-0.5 leading-relaxed"></p>
</div>
</div>
<div class="text-xs sm:text-sm text-slate-400 flex justify-between items-center pt-2 border-t border-white/10">
<span id="cardBackCategory" class="font-semibold">Klangfigur</span>
<span class="text-slate-300 font-medium"><i class="fa-solid fa-repeat"></i> Nochmal tippen</span>
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex items-center justify-between gap-2.5 sm:gap-4 mt-6">
<button onclick="prevCard()" class="px-4 py-3 sm:py-3.5 rounded-2xl bg-white border border-slate-200 hover:bg-slate-50 text-slate-700 font-bold text-sm sm:text-base shadow-sm transition flex items-center gap-1.5">
<i class="fa-solid fa-chevron-left"></i>
<span class="hidden sm:inline">Zurück</span>
</button>
<div class="flex items-center gap-2.5 sm:gap-3">
<button onclick="markCardMastered(false)" class="px-4 sm:px-6 py-3 sm:py-3.5 rounded-2xl bg-rose-50 hover:bg-rose-100 text-rose-700 border border-rose-200 font-extrabold text-sm sm:text-base transition flex items-center gap-2 shadow-sm">
<span class="hidden md:inline-block px-1.5 py-0.5 bg-rose-200/80 rounded font-mono text-xs">1</span>
<i class="fa-solid fa-rotate-left"></i>
<span>Noch üben</span>
</button>
<button onclick="markCardMastered(true)" class="px-5 sm:px-7 py-3 sm:py-3.5 rounded-2xl bg-emerald-600 hover:bg-emerald-700 text-white font-extrabold text-sm sm:text-base shadow-lg shadow-emerald-600/25 transition flex items-center gap-2">
<span class="hidden md:inline-block px-1.5 py-0.5 bg-emerald-800 rounded font-mono text-xs">2</span>
<i class="fa-solid fa-check"></i>
<span>Gekonnt!</span>
</button>
</div>
<button onclick="nextCard()" class="px-4 py-3 sm:py-3.5 rounded-2xl bg-white border border-slate-200 hover:bg-slate-50 text-slate-700 font-bold text-sm sm:text-base shadow-sm transition flex items-center gap-1.5">
<span class="hidden sm:inline">Weiter</span>
<i class="fa-solid fa-chevron-right"></i>
</button>
</div>
</div>
</section>
<!-- ========================================== -->
<!-- VIEW 2: QUIZ MODUS & SCHWACHSTELLENTRAINER -->
<!-- ========================================== -->
<section id="view-quiz" class="hidden space-y-4 sm:space-y-6">
<!-- Quiz Header / Stat Bar -->
<div id="quizActiveHeader" class="bg-white p-4 sm:p-5 rounded-2xl border border-slate-200 shadow-sm flex flex-wrap items-center justify-between gap-3 sm:gap-4">
<div class="flex items-center gap-3">
<div class="w-11 h-11 rounded-2xl bg-violet-100 text-violet-700 flex items-center justify-center font-bold text-xl shadow-sm">
<i class="fa-solid fa-brain"></i>
</div>
<div>
<div class="text-xs sm:text-sm font-bold text-slate-500 uppercase tracking-wider flex items-center gap-2">
<span id="quizModeName">🌟 Kernstoff-Challenge (17 Stilmittel)</span>
</div>
<div id="quizQuestionNumber" class="text-lg sm:text-2xl font-black text-slate-900">Frage 1 von 10</div>
</div>
</div>
<!-- Quiz Mode Selector -->
<div class="flex flex-wrap items-center gap-2 w-full lg:w-auto justify-between lg:justify-end">
<div class="inline-flex p-1 bg-slate-100 rounded-xl border border-slate-200 text-xs sm:text-sm font-bold">
<button id="quizModeKernBtn" onclick="startQuiz('highlighted')" class="px-3 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm font-bold transition">🌟 Kernstoff (17)</button>
<button id="quizModeAllBtn" onclick="startQuiz('all')" class="px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold">Alle (23)</button>
<button id="quizModeZusatzBtn" onclick="startQuiz('zusatz')" class="px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold">🔍 Zusatz (6)</button>
</div>
<!-- Streak, Score & Mistakes counter -->
<div class="flex items-center gap-2 text-xs sm:text-sm font-bold">
<div id="quizMistakesBadge" onclick="startWeakSpotsQuiz()" title="Gezielt Schwachstellen trainieren" class="cursor-pointer hidden flex items-center gap-1.5 text-rose-700 bg-rose-50 px-3 py-1.5 rounded-xl border border-rose-200">
<i class="fa-solid fa-triangle-exclamation"></i>
<span><span id="quizMistakesCount">0</span> Fehler</span>
</div>
<div class="flex items-center gap-1.5 text-amber-700 bg-amber-50 px-3 py-1.5 rounded-xl border border-amber-200/70">
<i class="fa-solid fa-fire text-amber-500"></i>
<span>Streak: <span id="quizStreak">0</span></span>
</div>
<div class="flex items-center gap-1.5 text-indigo-700 bg-indigo-50 px-3 py-1.5 rounded-xl border border-indigo-200/70">
<i class="fa-solid fa-trophy text-indigo-600"></i>
<span>Punkte: <span id="quizScore">0</span></span>
</div>
</div>
</div>
</div>
<!-- Active Question Container -->
<div id="quizPlayArea" class="bg-white rounded-3xl border border-slate-200 shadow-xl p-5 sm:p-8 max-w-2xl mx-auto space-y-5 sm:space-y-6">
<div>
<div class="flex items-center justify-between">
<span id="quizCategoryTag" class="px-3.5 py-1.5 rounded-full text-xs sm:text-sm font-extrabold uppercase tracking-wider bg-violet-100 text-violet-800 border border-violet-200">
Beispiel-Zuordnung
</span>
<span class="text-xs text-slate-400 hidden sm:inline">Tastatur: [1], [2], [3], [4]</span>
</div>
<h3 id="quizQuestionPrompt" class="text-xl sm:text-2xl font-black text-slate-900 mt-3 leading-snug">
Welches rhetorische Stilmittel liegt in diesem Zitat vor?
</h3>
<div id="quizPhraseBox" class="mt-4 p-5 sm:p-6 bg-slate-50 border-l-4 border-indigo-600 rounded-2xl font-serif text-xl sm:text-2xl text-slate-900 italic shadow-inner">
„Die Kunst ist lang, und kurz ist unser Leben.“
</div>
</div>
<!-- 4 Multiple Choice Answers -->
<div id="quizOptionsContainer" class="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-3.5 pt-2">
<!-- Options dynamically generated here -->
</div>
<!-- Inline Explanation Feedback -->
<div id="quizFeedbackBox" class="hidden rounded-2xl p-4 sm:p-6 border transition-all">
<div class="flex items-start gap-3.5">
<div id="quizFeedbackIcon" class="text-3xl mt-0.5"></div>
<div class="flex-1">
<h4 id="quizFeedbackTitle" class="font-extrabold text-base sm:text-lg">Richtig gelöst!</h4>
<p id="quizFeedbackText" class="text-sm sm:text-base mt-1.5 leading-relaxed"></p>
</div>
</div>
<div class="mt-5 flex justify-end">
<button id="quizNextBtn" onclick="nextQuizQuestion()" class="px-7 py-3 rounded-2xl bg-slate-900 hover:bg-slate-800 text-white font-extrabold text-base shadow-lg transition flex items-center gap-2.5">
<span>Nächste Frage</span>
<span class="hidden sm:inline-block px-1.5 py-0.5 bg-slate-700 rounded font-mono text-xs">Enter</span>
<i class="fa-solid fa-arrow-right"></i>
</button>
</div>
</div>
</div>
<!-- Quiz Results Screen -->
<div id="quizResultScreen" class="hidden bg-white rounded-3xl border border-slate-200 shadow-xl p-6 sm:p-8 max-w-xl mx-auto text-center space-y-6">
<div id="quizResultTrophy" class="w-20 h-20 sm:w-24 sm:h-24 mx-auto rounded-3xl bg-amber-100 text-amber-600 flex items-center justify-center text-4xl sm:text-5xl shadow-inner">
<i class="fa-solid fa-crown"></i>
</div>
<div>
<h3 class="text-2xl sm:text-3xl font-black text-slate-900">Quiz beendet!</h3>
<p id="quizResultMessage" class="text-sm sm:text-base text-slate-500 mt-1 font-medium">Hervorragende Leistung bei der Textanalyse!</p>
</div>
<div class="bg-slate-50 p-5 rounded-2xl border border-slate-200 grid grid-cols-3 gap-3 text-center">
<div>
<div class="text-xs font-bold text-slate-400 uppercase">Ergebnis</div>
<div id="resultScore" class="text-2xl sm:text-3xl font-black text-indigo-600 mt-1">8 / 10</div>
</div>
<div>
<div class="text-xs font-bold text-slate-400 uppercase">Quote</div>
<div id="resultPercent" class="text-2xl sm:text-3xl font-black text-emerald-600 mt-1">80%</div>
</div>
<div>
<div class="text-xs font-bold text-slate-400 uppercase">Max Streak</div>
<div id="resultMaxStreak" class="text-2xl sm:text-3xl font-black text-amber-600 mt-1">5</div>
</div>
</div>
<!-- Mistake Review Section -->
<div id="quizMistakesContainer" class="hidden text-left space-y-2">
<h4 class="text-xs sm:text-sm font-bold text-slate-400 uppercase tracking-wider">Zu wiederholen:</h4>
<div id="quizMistakesList" class="space-y-2 max-h-48 overflow-y-auto pr-1"></div>
</div>
<div class="flex flex-col sm:flex-row gap-3 justify-center pt-2">
<button onclick="startQuiz(currentQuizMode)" class="px-6 py-3.5 rounded-2xl bg-indigo-600 hover:bg-indigo-700 text-white font-extrabold text-sm sm:text-base shadow-lg shadow-indigo-600/30 transition flex items-center justify-center gap-2">
<i class="fa-solid fa-rotate-right"></i>
<span>Nochmal spielen</span>
</button>
<button id="repeatMistakesBtn" onclick="startWeakSpotsQuiz()" class="hidden px-6 py-3.5 rounded-2xl bg-rose-600 hover:bg-rose-700 text-white font-extrabold text-sm sm:text-base shadow-lg shadow-rose-600/20 transition flex items-center justify-center gap-2">
<i class="fa-solid fa-triangle-exclamation"></i>
<span>Fehler wiederholen</span>
</button>
<button onclick="switchTab('cards')" class="px-6 py-3.5 rounded-2xl bg-slate-100 hover:bg-slate-200 text-slate-700 font-bold text-sm sm:text-base transition">
Karteikarten
</button>
</div>
</div>
</section>
<!-- ========================================== -->
<!-- VIEW 3: PHRASEN-MATCHER (Mini-Game) -->
<!-- ========================================== -->
<section id="view-matcher" class="hidden space-y-4 sm:space-y-6">
<div class="bg-white p-4 sm:p-5 rounded-2xl border border-slate-200 shadow-sm flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 sm:gap-4">
<div>
<h2 class="text-base sm:text-xl font-extrabold text-slate-900 flex items-center gap-2">
<i class="fa-solid fa-puzzle-piece text-indigo-600"></i>
<span>Phrasen-Matcher (Schnell-Zuordnung)</span>
</h2>
<p class="text-xs sm:text-sm text-slate-500 mt-0.5">Tippe zuerst auf ein Zitat und anschließend auf das passende Stilmittel.</p>
</div>
<div class="flex items-center gap-2.5 w-full sm:w-auto justify-between sm:justify-end">
<div class="px-3.5 py-1.5 bg-emerald-50 text-emerald-800 border border-emerald-200 rounded-xl text-xs sm:text-sm font-bold">
Gelöst: <span id="matchScore">0</span> / 5 Paare
</div>
<button onclick="initMatcherGame()" class="px-3.5 py-1.5 bg-slate-100 hover:bg-slate-200 text-slate-700 rounded-xl text-xs sm:text-sm font-bold transition flex items-center gap-1.5">
<i class="fa-solid fa-arrows-rotate"></i> Neu mischen
</button>
</div>
</div>
<!-- Matcher Board -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 max-w-4xl mx-auto">
<!-- Left: Phrases -->
<div class="space-y-3">
<h3 class="text-xs sm:text-sm font-extrabold text-slate-400 uppercase tracking-wider px-1">Phrasen & Zitate</h3>
<div id="matcherPhrasesContainer" class="space-y-3">
<!-- Dynamically populated -->
</div>
</div>
<!-- Right: Terms -->
<div class="space-y-3">
<h3 class="text-xs sm:text-sm font-extrabold text-slate-400 uppercase tracking-wider px-1">Rhetorische Stilmittel</h3>
<div id="matcherTermsContainer" class="space-y-3">
<!-- Dynamically populated -->
</div>
</div>
</div>
<div id="matcherWinBanner" class="hidden bg-emerald-500 text-white p-6 sm:p-8 rounded-3xl text-center space-y-3 shadow-xl max-w-md mx-auto">
<div class="text-4xl"><i class="fa-solid fa-circle-check"></i></div>
<h4 class="text-2xl font-black">Perfekt zugeordnet!</h4>
<p class="text-sm sm:text-base text-emerald-100">Du hast alle 5 Paare fehlerfrei zusammengeführt.</p>
<button onclick="initMatcherGame()" class="px-6 py-3 rounded-2xl bg-white text-emerald-800 font-black text-base shadow-md hover:bg-emerald-50 transition">
Nächste Runde spielen
</button>
</div>
</section>
<!-- ========================================== -->
<!-- VIEW 4: GLOSSAR & SUCHE MIT KATEGORIEN -->
<!-- ========================================== -->
<section id="view-glossary" class="hidden space-y-4 sm:space-y-5">
<!-- Search & Filters -->
<div class="bg-white p-4 sm:p-5 rounded-2xl border border-slate-200 shadow-sm space-y-3.5">
<div class="flex flex-col sm:flex-row gap-3">
<div class="relative flex-1">
<i class="fa-solid fa-magnifying-glass absolute left-4 top-3.5 text-slate-400 text-base"></i>
<input type="text" id="glossarySearchInput" oninput="renderGlossary()" placeholder="Suche nach Begriff, Definition oder Beispiel (z. B. 'Chiasmus', 'Fug und Recht', 'Faust')... [/]" class="w-full pl-11 pr-4 py-3 rounded-2xl border border-slate-300 focus:outline-none focus:ring-2 focus:ring-indigo-500 text-sm sm:text-base">
</div>
<button onclick="printCheatsheet()" class="px-4 py-3 rounded-2xl text-xs sm:text-sm font-bold bg-slate-900 text-white hover:bg-slate-800 transition flex items-center justify-center gap-2 whitespace-nowrap shadow-sm">
<i class="fa-solid fa-print"></i>
<span>Als PDF / Druck</span>
</button>
</div>
<!-- Category Filters -->
<div class="flex flex-wrap items-center gap-1.5 pt-2 border-t border-slate-100 text-xs sm:text-sm">
<button id="filterAllBtn" onclick="setGlossaryFilter('all')" class="px-3 py-1.5 rounded-xl font-bold bg-indigo-50 text-indigo-700 border border-indigo-200">
Alle (<span id="countAllBadge">23</span>)
</button>
<button id="filterKernBtn" onclick="setGlossaryFilter('highlight')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
🌟 Kernstoff (17)
</button>
<button id="filterZusatzBtn" onclick="setGlossaryFilter('zusatz')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
🔍 Zusatzinfo (6)
</button>
<button id="filterKlangBtn" onclick="setGlossaryFilter('klang')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
Klangfiguren
</button>
<button id="filterSyntaxBtn" onclick="setGlossaryFilter('syntax')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
Satzfiguren / Syntax
</button>
<button id="filterWortBtn" onclick="setGlossaryFilter('wort')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
Wortfiguren & Tropen
</button>
<button id="filterGedankeBtn" onclick="setGlossaryFilter('gedanke')" class="px-3 py-1.5 rounded-xl font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200">
Gedankenfiguren
</button>
</div>
<div class="flex items-center justify-between text-xs sm:text-sm text-slate-500 px-1 pt-1">
<span id="glossaryResultCount" class="font-bold">23 Stilmittel gelistet</span>
<span class="hidden sm:inline">Tippe auf ein Stilmittel für Details, Merkhilfen und Klausur-Tipps</span>
</div>
</div>
<!-- Glossary Card Grid -->
<div id="glossaryGrid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3.5 sm:gap-4">
<!-- Rendered by JS -->
</div>
</section>
</main>
<!-- ========================================== -->
<!-- PRINT-READY CHEATSHEET SECTION (@media print) -->
<!-- ========================================== -->
<section id="printCheatsheetSection" class="hidden print:block p-6">
<div class="border-b-2 border-slate-800 pb-3 mb-4 flex justify-between items-end">
<div>
<h1 class="text-2xl font-black uppercase tracking-tight">RhetorikMaster - Klausur-Spickzettel</h1>
<p class="text-sm text-slate-600 mt-1">Rhetorische Stilmittel der Textanalyse • Definitionen, Merkhilfen & typische Zitate</p>
</div>
<div class="text-xs text-slate-500 font-mono text-right">
rhetorikmaster.pansi.eu
</div>
</div>
<table class="w-full text-xs text-left border border-slate-300">
<thead>
<tr class="bg-slate-100 text-slate-900 uppercase font-bold text-[10px] tracking-wider">
<th class="p-2 w-1/4">Stilmittel & Kategorie</th>
<th class="p-2 w-1/3">Definition & Wirkung</th>
<th class="p-2 w-1/4">Typisches Beispiel / Zitat</th>
<th class="p-2">Signal / Merkhilfe</th>
</tr>
</thead>
<tbody id="printTableBody">
<!-- Dynamically rendered -->
</tbody>
</table>
</section>
<!-- DETAIL MODAL -->
<div id="detailModal" class="fixed inset-0 bg-slate-900/60 backdrop-blur-sm z-50 hidden flex items-center justify-center p-4 no-print">
<div class="bg-white rounded-3xl max-w-lg w-full p-6 sm:p-8 shadow-2xl relative border border-slate-200 animate-in fade-in zoom-in-95 duration-150 max-h-[90vh] overflow-y-auto">
<button onclick="closeDetailModal()" class="absolute top-5 right-5 w-9 h-9 rounded-full bg-slate-100 hover:bg-slate-200 text-slate-600 flex items-center justify-center transition">
<i class="fa-solid fa-xmark text-base"></i>
</button>
<div class="space-y-4">
<div class="flex items-center gap-2">
<span id="modalHighlightBadge" class="text-xs font-bold px-2.5 py-0.5 rounded-full bg-indigo-50 text-indigo-700 border border-indigo-200 uppercase tracking-wider">
Stilmittel
</span>
<span id="modalCategoryBadge" class="text-xs font-bold px-2.5 py-0.5 rounded-full bg-slate-100 text-slate-700">
Satzfigur
</span>
</div>
<div>
<h3 id="modalTerm" class="text-2xl sm:text-3xl font-black text-slate-900">Chiasmus</h3>
<p id="modalCategory" class="text-xs sm:text-sm text-indigo-600 font-bold uppercase tracking-wider mt-0.5">Satzbaustilmittel / Überkreuzstellung</p>
</div>
<div class="space-y-3 pt-2">
<div class="bg-slate-50 p-4 sm:p-5 rounded-2xl border border-slate-100">
<div class="text-xs font-bold text-slate-400 uppercase tracking-wider">Definition & Funktion</div>
<p id="modalDefinition" class="text-sm sm:text-base text-slate-800 font-medium mt-1 leading-relaxed"></p>
</div>
<div class="bg-indigo-50/70 p-4 sm:p-5 rounded-2xl border border-indigo-100">
<div class="text-xs font-bold text-indigo-800 uppercase tracking-wider">Beispiele aus der Textanalyse</div>
<div id="modalExamples" class="text-sm sm:text-base font-serif italic text-slate-800 mt-1 space-y-1"></div>
</div>
<div class="bg-amber-50 p-4 sm:p-5 rounded-2xl border border-amber-200/70">
<div class="text-xs font-bold text-amber-800 uppercase tracking-wider flex items-center gap-1.5">
<i class="fa-solid fa-lightbulb"></i>
<span>Merkhilfe & Klausur-Tipp</span>
</div>
<p id="modalTip" class="text-xs sm:text-sm text-amber-950 mt-1 leading-relaxed font-medium"></p>
</div>
</div>
<div class="pt-3 flex justify-between items-center">
<span class="text-xs text-slate-400 font-mono">[Esc] zum Schließen</span>
<button onclick="closeDetailModal()" class="px-6 py-2.5 rounded-xl bg-slate-900 hover:bg-slate-800 text-white font-bold text-sm sm:text-base transition shadow-sm">
Verstanden
</button>
</div>
</div>
</div>
</div>
<!-- Notification Toast -->
<div id="toastNotification" class="fixed bottom-6 right-6 z-50 transform translate-y-20 opacity-0 transition-all duration-300 pointer-events-none no-print">
<div class="bg-slate-900 text-white px-5 py-3 rounded-2xl shadow-xl flex items-center gap-3 border border-slate-700 text-sm sm:text-base font-medium">
<i id="toastIcon" class="fa-solid fa-circle-check text-emerald-400 text-base"></i>
<span id="toastMessage">Fortschritt gespeichert!</span>
</div>
</div>
<script>
/* Complete Dataset with Categories & Classification */
const STILMITTEL_DATA = [
{
id: "allegorie",
term: "Allegorie",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Verbildlichung von Abstraktem; eine konkrete Gestalt oder Handlung steht stellvertretend für einen abstrakten Begriff.",
examples: ["Justitia (für Gerechtigkeit)", "Sensenmann (für Tod)", "Uncle Sam (für USA)"],
highlighted: true,
tip: "Achte auf personifizierte Symbolfiguren mit typischen Attributen (Waage, Sense, Zylinder)."
},
{
id: "alliteration",
term: "Alliteration",
category: "klang",
categoryLabel: "Klangfigur",
definition: "Mehrere aufeinanderfolgende Wörter beginnen mit demselben Anlaut (betonten Konsonanten oder Vokal).",
examples: ["Fischers Fritz fischt frische Fische.", "Milch macht müde Männer munter."],
highlighted: true,
tip: "Häufig in Zungenbrechern, Werbeslogans und Sprichwörtern. Erzeugt Rhythmus und Einprägsamkeit."
},
{
id: "anapher",
term: "Anapher",
category: "syntax",
categoryLabel: "Satzfigur / Syntax",
definition: "Wiederholung von einem oder mehreren Wörtern zu Beginn von mindestens zwei aufeinanderfolgenden Sätzen oder Satzteilen.",
examples: ["Du hast mich x-mal belogen, du hast mich x-mal betrogen ...", "Das Wasser rauscht', das Wasser schwoll."],
highlighted: true,
tip: "Erzeugt starken Nachdruck und Rhythmus am Satzanfang. Gegenteil: Epipher."
},
{
id: "antiklimax",
term: "Antiklimax",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Einzelne Wörter oder Satzteile sind mit abnehmender Wichtigkeit oder Intensität aneinandergereiht (Stufenfolge nach unten).",
examples: ["Kaiser, König, Edelmann; Bürger, Bauer, Bettelmann", "Doktoren, Magister, Schreiber und Pfaffen"],
highlighted: true,
tip: "Gegenteil der Klimax. Häufig verwendet zur Ernüchterung, Enttäuschung oder Ironie."
},
{
id: "antithese",
term: "Antithese",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Gegensätzliche Begriffe, Gedanken oder Behauptungen werden einander direkt gegenübergestellt.",
examples: ["lieben und hassen", "Himmel und Hölle", "Der Wahn ist kurz, die Reu ist lang."],
highlighted: true,
tip: "Betont Kontraste und innere Zerrissenheit durch zwei logische Gegenpole."
},
{
id: "chiasmus",
term: "Chiasmus",
category: "syntax",
categoryLabel: "Satzfigur / Syntax",
definition: "Überkreuzstellung von einander entsprechenden Satzgliedern oder Wörtern (nach dem griechischen Buchstaben Chi: Χ).",
examples: ["Die Kunst ist lang, und kurz ist unser Leben. („Faust“ / Goethe)", "Ich schlafe am Tag, in der Nacht wache ich."],
highlighted: false,
tip: "Formelschema: A - B : B - A (z. B. Subjekt Prädikat : Prädikat Subjekt). Wirkt antithetisch und geistreich."
},
{
id: "ellipse",
term: "Ellipse",
category: "syntax",
categoryLabel: "Satzfigur / Syntax",
definition: "Auslassung eines leicht ergänzbaren Satzteils, sodass der Satz grammatikalisch unvollständig, aber verständlich bleibt.",
examples: ["Kann ich ein Glas Milch [haben]?", "[Das] Ende [ist] gut, alles [ist] gut!", "Je früher [du kommst], desto besser [ist es]."],
highlighted: true,
tip: "Spiegelt oft mündliche Spontaneität, Erregung oder Eile wider."
},
{
id: "epipher",
term: "Epipher",
category: "syntax",
categoryLabel: "Satzfigur / Syntax",
definition: "Mehrere aufeinanderfolgende Sätze oder Verse enden mit demselben Wort oder denselben Wörtern.",
examples: ["Unser Hotel: fantastisch! Unsere Preise: fantastisch!", "Ende gut, alles gut."],
highlighted: true,
tip: "Verstärkt die Eindringlichkeit am Vers- bzw. Satzende. Gegenteil: Anapher."
},
{
id: "euphemismus",
term: "Euphemismus",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Sprachliche Beschönigung, Milderung oder Verschleierung eines unangenehmen, tabuisierten Sachverhalts.",
examples: ["verhaltenskreativ (für verhaltensauffällig)", "ableben / entschlafen (für sterben)", "Freisetzung (für Kündigung)"],
highlighted: true,
tip: "Wird oft in Politik, Verwaltung und Diplomatie eingesetzt, um Härte abzumildern."
},
{
id: "hendiadyoin",
term: "Hendiadyoin",
category: "wort",
categoryLabel: "Wortfigur / Zwillingsformel",
definition: "Zwei gleichbedeutende oder sinnverwandte Wörter (oft Nomen) werden mit 'und' aneinandergereiht und ergeben gemeinsam eine Gesamtbedeutung.",
examples: ["mit Fug und Recht (= mit voller Berechtigung)", "in Bausch und Bogen (= alles zusammen)", "in Amt und Würden (= amtierend)", "Feuer und Flamme (= begeistert)"],
highlighted: false,
tip: "Wörtlich: 'Eins durch zwei'. Typische feste Zwillingsformeln der deutschen Sprache."
},
{
id: "hyperbel",
term: "Hyperbel",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Starke, oft unglaubwürdige sprachliche Übertreibung zur Verdeutlichung oder Emotionalisierung.",
examples: ["todmüde sein", "ein Meer von Tränen vergießen", "Ich habe dir das schon tausendmal gesagt!"],
highlighted: true,
tip: "Wird wörtlich genommen absurd, soll aber das Gefühl intensivieren."
},
{
id: "ironie",
term: "Ironie",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Feiner Spott; man meint das genaue Gegenteil von dem, was wörtlich gesagt wird (oft durch Tonfall/Kontext erkennbar).",
examples: ["Du bist ja sooo klug! (nach einem Missgeschick)", "Das hast du ja mal wieder toll hingekriegt."],
highlighted: true,
tip: "Der Rezipient muss das 'Codewort' entziffern. Kann humorvoll oder verletzend (Sarkasmus) sein."
},
{
id: "klimax",
term: "Klimax",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Sich stufenweise steigernde Reihe von Wörtern oder Satzteilen (vom Schwächeren zum Stärkeren).",
examples: ["Heute back ich, morgen brau ich, übermorgen hol ich der Königin ihr Kind. („Rumpelstilzchen“)", "Er kam, sah und siegte."],
highlighted: true,
tip: "Baut systematische Dramatik und Spannung auf. Gegenteil: Antiklimax."
},
{
id: "metapher",
term: "Metapher",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Bildhafter Ausdruck; Bedeutungsübertragung aus einem Bereich in einen anderen ohne das Vergleichswort 'wie'.",
examples: ["Wüstenschiff (für Kamel)", "jemandem das Herz brechen", "eine Mauer des Schweigens", "Rabenmutter"],
highlighted: true,
tip: "Ein 'Vergleich ohne wie'. Das Bild verschmilzt direkt mit der übertragenen Bedeutung."
},
{
id: "neologismus",
term: "Neologismus",
category: "wort",
categoryLabel: "Wortfigur / Neubildung",
definition: "Sprachliche Neubildung von Begriffen oder Wörtern, die neu in den Sprachgebrauch eingehen.",
examples: ["abhängen (für verweilen / chillen)", "Technologiepark", "Fake News", "googeln", "Klimakleber"],
highlighted: true,
tip: "Zeigt Zeitgeist, Jugendkultur oder technischen Fortschritt an."
},
{
id: "onomatopoesie",
term: "Onomatopoesie",
category: "klang",
categoryLabel: "Klangfigur",
definition: "Lautmalerei bzw. Schallnachahmung von Geräuschen, Lauten oder Tönen durch sprachliche Laute.",
examples: ["Summ, summ; Wuff! Miau!", "Knistern, Knacken, Kuckuck", "Zack! Bumm! Peng!"],
highlighted: true,
tip: "Häufig in Comics, Kinderreimen und Naturlyrik ('Es knistert im Ofen')."
},
{
id: "oxymoron",
term: "Oxymoron",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Verbindung zweier Vorstellungen oder Begriffe, die sich logisch direkt widersprechen oder gegenseitig ausschließen.",
examples: ["beredtes Schweigen", "offenes Geheimnis", "junger Greis", "stiller Schrei", "alter Knabe", "bittersüß"],
highlighted: false,
tip: "Meistens eine Adjektiv-Nomen-Kombination. Erzeugt Verblüffung und zwingt zum Nachdenken."
},
{
id: "paradoxon",
term: "Paradoxon",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Eine scheinbar widersprüchliche oder widersinnige Aussage, die bei genauerem Hinsehen eine tiefere Wahrheit birgt.",
examples: ["Ich weiß, dass ich nichts weiß. (Sokrates)", "Umsonst ist der Tod, und der kostet das Leben.", "Weniger ist mehr."],
highlighted: false,
tip: "Im Unterschied zum Oxymoron (zwei Wörter) meist ein ganzer zusammenhängender Gedanke/Satz."
},
{
id: "pars_pro_toto",
term: "Pars pro Toto",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Ein Teil steht für das Ganze (Sonderform der Synekdoche) oder eine einzelne Marke/Zentrale für ein ganzes System.",
examples: ["Brüssel (steht für die EU samt Verwaltung)", "Felix (für eine ganze Ketchup-Produktreihe)", "pro Kopf (für pro Person)", "ein Dach über dem Kopf (für ein Haus)"],
highlighted: false,
tip: "Wörtlich lateinisch: 'Ein Teil für das Ganze'. Sehr häufig in den Nachrichten und in der Politik."
},
{
id: "personifikation",
term: "Personifikation",
category: "wort",
categoryLabel: "Wortfigur / Trope",
definition: "Vermenschlichung einer Idee, einer Sache oder eines Naturphänomens; Abstraktes handelt wie eine Person.",
examples: ["die Sonne lacht", "der Himmel weint", "die Natur schläft", "der Wind heult", "die Zeit rennt"],
highlighted: true,
tip: "Verleiht unbelebten Dingen Gefühle, menschliche Organe oder Absichten."
},
{
id: "rhetorische_frage",
term: "Rhetorische Frage",
category: "gedanke",
categoryLabel: "Gedankenfigur",
definition: "Scheinfrage, auf die keine Antwort erwartet wird, weil die Antwort ohnehin offensichtlich oder unausweichlich ist.",
examples: ["Wer weiß das schon?", "Wer kennt das nicht?", "Machen wir nicht alle Fehler?", "Bist du verrückt?"],
highlighted: true,
tip: "Dient zur Beeinflussung des Lesers / Zuhörers und stellt Einvernehmen her."
},
{
id: "tautologie_pleonasmus",
term: "Tautologie oder Pleonasmus",
category: "wort",
categoryLabel: "Wortfigur / Häufung",
definition: "Zwei Wörter, die dasselbe ausdrücken, aber gemeinsam keine neue Bedeutung ergeben (Häufung sinnverwandter Wörter).",
examples: ["der kleine Zwerg", "nie und nimmer", "die tote Leiche", "weißer Schimmel", "voll und ganz"],
highlighted: false,
tip: "Pleonasmus: Eigenschaft ist schon im Begriff enthalten ('nasses Wasser'). Tautologie: Wiederholung mit gleicher Wortart."
},
{
id: "vergleich",
term: "Vergleich",
category: "wort",
categoryLabel: "Wortfigur / Vergleich",
definition: "Verknüpfung zweier Bedeutungsbereiche durch ein ausdrückliches Vergleichswort ('wie' oder 'als').",
examples: ["stark wie ein Löwe", "frei wie der Wind", "fleißig wie eine Biene", "schneller als die Polizei erlaubt"],
highlighted: true,
tip: "Immer am Signalwort 'wie' oder 'als' erkennbar. Fehlt das 'wie', wird daraus eine Metapher!"
}
];
// Web Audio Synthesizer Engine
let soundEnabled = localStorage.getItem('rhetorik_sound_enabled') !== 'false';
let audioCtx = null;
function getAudioContext() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) audioCtx = new AudioContext();
}
if (audioCtx && audioCtx.state === 'suspended') {
audioCtx.resume();
}
return audioCtx;
}
function playTone(type) {
if (!soundEnabled) return;
try {
const ctx = getAudioContext();
if (!ctx) return;
const now = ctx.currentTime;
if (type === 'flip') {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sine';
osc.frequency.setValueAtTime(320, now);
osc.frequency.exponentialRampToValueAtTime(560, now + 0.08);
gain.gain.setValueAtTime(0.06, now);
gain.gain.exponentialRampToValueAtTime(0.001, now + 0.08);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now);
osc.stop(now + 0.08);
} else if (type === 'success') {
[523.25, 659.25, 783.99].forEach((freq, idx) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, now + idx * 0.06);
gain.gain.setValueAtTime(0.12, now + idx * 0.06);
gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.06 + 0.18);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now + idx * 0.06);
osc.stop(now + idx * 0.06 + 0.18);
});
} else if (type === 'error') {
[330, 246.94].forEach((freq, idx) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(freq, now + idx * 0.1);
gain.gain.setValueAtTime(0.1, now + idx * 0.1);
gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.1 + 0.16);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now + idx * 0.1);
osc.stop(now + idx * 0.1 + 0.16);
});
} else if (type === 'win') {
[523.25, 659.25, 783.99, 1046.50].forEach((freq, idx) => {
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.type = 'triangle';
osc.frequency.setValueAtTime(freq, now + idx * 0.08);
gain.gain.setValueAtTime(0.15, now + idx * 0.08);
gain.gain.exponentialRampToValueAtTime(0.001, now + idx * 0.08 + 0.28);
osc.connect(gain);
gain.connect(ctx.destination);
osc.start(now + idx * 0.08);
osc.stop(now + idx * 0.08 + 0.28);
});
}
} catch (e) {
console.warn('Audio tone error:', e);
}
}
function toggleSound() {
soundEnabled = !soundEnabled;
localStorage.setItem('rhetorik_sound_enabled', soundEnabled);
updateSoundUI();
if (soundEnabled) playTone('flip');
}
function updateSoundUI() {
const icon = document.getElementById('soundIcon');
const label = document.getElementById('soundLabel');
if (soundEnabled) {
icon.className = 'fa-solid fa-volume-high text-indigo-600';
if (label) label.innerText = 'Ton An';
} else {
icon.className = 'fa-solid fa-volume-xmark text-slate-400';
if (label) label.innerText = 'Stumm';
}
}
// Leitner 5-Box State
let cardLevels = JSON.parse(localStorage.getItem('rhetorik_leitner_levels') || '{}');
let activeCardCategory = 'highlighted';
let activeLeitnerBox = 'all';
let filteredCards = [...STILMITTEL_DATA];
let currentTabIndex = 'cards';
let currentCardIndex = 0;
let cardIsFlipped = false;
let cardFaceMode = 'term';
// Quiz State & Mistakes Tracking
let quizQuestions = [];
let currentQuizIndex = 0;
let quizScore = 0;
let quizStreak = 0;
let quizMaxStreak = 0;
let quizMistakes = JSON.parse(localStorage.getItem('rhetorik_quiz_mistakes') || '[]');
let isQuestionAnswered = false;
let currentQuizMode = 'highlighted';
// Matcher Game State
let matcherSelectedPhrase = null;
let matcherSelectedTerm = null;
let matcherMatchedCount = 0;
let currentMatcherPairs = [];
// Glossary State
let currentGlossaryFilter = 'all';
function getItemLevel(id) {
return cardLevels[id] || 1;
}
function setItemLevel(id, level) {
cardLevels[id] = Math.max(1, Math.min(5, level));
localStorage.setItem('rhetorik_leitner_levels', JSON.stringify(cardLevels));
updateMasteredHeader();
}
function updateMasteredHeader() {
const masteredCount = STILMITTEL_DATA.filter(item => (cardLevels[item.id] || 1) >= 5).length;
document.getElementById('globalMasteredStat').innerText = `${masteredCount} / ${STILMITTEL_DATA.length} gelernt`;
updateQuizMistakesBadge();
}
function updateQuizMistakesBadge() {
const badge = document.getElementById('quizMistakesBadge');
const countEl = document.getElementById('quizMistakesCount');
if (!badge || !countEl) return;
if (quizMistakes.length > 0) {
badge.classList.remove('hidden');
countEl.innerText = quizMistakes.length;
} else {
badge.classList.add('hidden');
}
}
function resetLeitnerProgress() {
if (confirm('Möchtest du den Lernfortschritt für alle Karteikarten wirklich auf Kasten 1 zurücksetzen?')) {
cardLevels = {};
localStorage.removeItem('rhetorik_leitner_levels');
applyCardFilters();
updateMasteredHeader();
showToast('Lernstand zurückgesetzt!', 'fa-rotate-left');
}
}
function applyCardFilters() {
filteredCards = STILMITTEL_DATA.filter(item => {
if (activeCardCategory === 'highlighted') {
if (!item.highlighted) return false;
} else if (activeCardCategory === 'zusatz') {
if (item.highlighted) return false;
} else if (activeCardCategory !== 'all' && item.category !== activeCardCategory) {
return false;
}
if (activeLeitnerBox !== 'all') {
const lvl = getItemLevel(item.id);
if (lvl.toString() !== activeLeitnerBox.toString()) return false;
}
return true;
});
if (filteredCards.length === 0) {
filteredCards = [...STILMITTEL_DATA];
}
currentCardIndex = 0;
renderCurrentCard();
}
function setCardCategoryFilter(cat) {
activeCardCategory = cat;
document.querySelectorAll('.card-cat-pill').forEach(btn => {
btn.className = 'card-cat-pill px-2.5 py-1 rounded-lg font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200';
});
const activeBtn = document.getElementById(`cardCatBtn-${cat}`);
if (activeBtn) {
activeBtn.className = 'card-cat-pill px-2.5 py-1 rounded-lg font-bold bg-indigo-50 text-indigo-700 border border-indigo-200';
}
applyCardFilters();
}
function filterByLeitnerBox(box) {
activeLeitnerBox = box;
applyCardFilters();
}
function renderCurrentCard() {
if (filteredCards.length === 0) return;
if (currentCardIndex >= filteredCards.length) currentCardIndex = 0;
const item = filteredCards[currentCardIndex];
const level = getItemLevel(item.id);
cardIsFlipped = false;
const cardEl = document.getElementById('flashcardElement');
cardEl.classList.remove('rotate-y-180', 'swipe-left', 'swipe-right');
const progressPercent = ((currentCardIndex + 1) / filteredCards.length) * 100;
document.getElementById('cardProgressBar').style.width = `${progressPercent}%`;
document.getElementById('cardCounter').innerText = `Karte ${currentCardIndex + 1} von ${filteredCards.length}`;
document.getElementById('cardProgressLabel').innerText = `Karte ${currentCardIndex + 1} von ${filteredCards.length} (${item.categoryLabel})`;
const boxNames = ['', 'Kasten 1 (Neu)', 'Kasten 2 (Aufbau)', 'Kasten 3 (Vertieft)', 'Kasten 4 (Fast sicher)', 'Kasten 5 (Gemeistert)'];
document.getElementById('cardBoxBadge').innerText = boxNames[level] || `Kasten ${level}`;
const dotsHtml = [1, 2, 3, 4, 5].map(i => {
const bg = i <= level ? 'bg-indigo-600' : 'bg-slate-200';
return `<span class="w-2.5 h-2.5 rounded-full ${bg} transition-colors"></span>`;
}).join('');
document.getElementById('leitnerBoxesVisual').innerHTML = dotsHtml;
document.getElementById('cardBadgeFront').innerText = item.categoryLabel;
document.getElementById('cardBackCategory').innerText = item.categoryLabel;
if (cardFaceMode === 'term') {
document.getElementById('cardFrontHint').innerText = 'Rhetorisches Stilmittel';
document.getElementById('cardFrontMain').innerText = item.term;
document.getElementById('cardFrontSub').innerText = 'Tippe zum Aufdecken der Definition & Beispiele';
} else {
document.getElementById('cardFrontHint').innerText = 'Beispiel & Signal';
document.getElementById('cardFrontMain').innerText = `„${item.examples[0]}“`;
document.getElementById('cardFrontSub').innerText = 'Welches Stilmittel liegt hier vor?';
}
document.getElementById('cardBackTerm').querySelector('span').innerText = item.term;
document.getElementById('cardBackDefinition').innerText = item.definition;
document.getElementById('cardBackExamples').innerText = item.examples.map(e => `„${e}“`).join(' • ');
const tipEl = document.getElementById('cardBackTip');
if (item.tip) {
document.getElementById('cardBackTipContainer').classList.remove('hidden');
tipEl.innerText = item.tip;
} else {
document.getElementById('cardBackTipContainer').classList.add('hidden');
}
const hlBadge = document.getElementById('cardHighlightBadge');
if (item.highlighted) {
hlBadge.classList.remove('hidden');
hlBadge.innerText = '🌟 Kernstoff';
hlBadge.className = 'text-[11px] font-bold uppercase px-2.5 py-0.5 rounded-full bg-indigo-500/30 text-indigo-200 border border-indigo-400/40';
} else {
hlBadge.classList.remove('hidden');
hlBadge.innerText = '🔍 Zusatzinfo';
hlBadge.className = 'text-[11px] font-bold uppercase px-2.5 py-0.5 rounded-full bg-slate-700/50 text-slate-300 border border-slate-600/50';
}
}
function flipCard() {
cardIsFlipped = !cardIsFlipped;
const cardEl = document.getElementById('flashcardElement');
if (cardIsFlipped) {
cardEl.classList.add('rotate-y-180');
} else {
cardEl.classList.remove('rotate-y-180');
}
playTone('flip');
}
function markCardMastered(isMastered) {
if (filteredCards.length === 0) return;
const item = filteredCards[currentCardIndex];
const currentLvl = getItemLevel(item.id);
const cardEl = document.getElementById('flashcardElement');
if (isMastered) {
setItemLevel(item.id, currentLvl + 1);
playTone('success');
cardEl.classList.add('swipe-right');
showToast(`„${item.term}“ aufgestiegen in Kasten ${Math.min(5, currentLvl + 1)}!`, 'fa-circle-up');
} else {
setItemLevel(item.id, Math.max(1, currentLvl - 1));
playTone('error');
cardEl.classList.add('swipe-left');
showToast(`„${item.term}“ wiederholt in Kasten ${Math.max(1, currentLvl - 1)}`, 'fa-rotate-left');
}
setTimeout(() => {
nextCard();
}, 250);
}
function nextCard() {
if (filteredCards.length === 0) return;
currentCardIndex = (currentCardIndex + 1) % filteredCards.length;
renderCurrentCard();
}
function prevCard() {
if (filteredCards.length === 0) return;
currentCardIndex = (currentCardIndex - 1 + filteredCards.length) % filteredCards.length;
renderCurrentCard();
}
function shuffleCards() {
for (let i = filteredCards.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[filteredCards[i], filteredCards[j]] = [filteredCards[j], filteredCards[i]];
}
currentCardIndex = 0;
renderCurrentCard();
playTone('flip');
showToast('Karten gemischt!', 'fa-shuffle');
}
function setCardFaceMode(mode) {
cardFaceMode = mode;
document.getElementById('cardModeTermBtn').className = mode === 'term'
? 'px-2.5 sm:px-3 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm transition font-bold'
: 'px-2.5 sm:px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold';
document.getElementById('cardModeExampleBtn').className = mode === 'example'
? 'px-2.5 sm:px-3 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm transition font-bold'
: 'px-2.5 sm:px-3 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold';
renderCurrentCard();
}
// Touch Swipe Gestures
let touchStartX = 0;
let touchStartY = 0;
const cardTouchContainer = document.getElementById('flashcardContainer');
cardTouchContainer.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
touchStartY = e.changedTouches[0].screenY;
}, { passive: true });
cardTouchContainer.addEventListener('touchend', (e) => {
const touchEndX = e.changedTouches[0].screenX;
const touchEndY = e.changedTouches[0].screenY;
const diffX = touchEndX - touchStartX;
const diffY = touchEndY - touchStartY;
if (Math.abs(diffX) > 50 && Math.abs(diffX) > Math.abs(diffY)) {
if (diffX > 0) {
markCardMastered(true);
} else {
markCardMastered(false);
}
} else if (Math.abs(diffX) < 15 && Math.abs(diffY) < 15) {
flipCard();
}
}, { passive: true });
// QUIZ ENGINE
function startQuiz(mode = 'highlighted') {
currentQuizMode = mode;
let pool = [];
// Update Quiz Mode buttons UI
const btnMap = {
'highlighted': 'quizModeKernBtn',
'all': 'quizModeAllBtn',
'zusatz': 'quizModeZusatzBtn'
};
['quizModeKernBtn', 'quizModeAllBtn', 'quizModeZusatzBtn'].forEach(id => {
const b = document.getElementById(id);
if (b) b.className = 'px-2.5 py-1.5 rounded-lg text-slate-600 hover:text-slate-900 transition font-semibold';
});
const activeBtn = document.getElementById(btnMap[mode]);
if (activeBtn) {
activeBtn.className = 'px-2.5 py-1.5 rounded-lg bg-white text-indigo-700 shadow-sm font-bold transition';
}
if (mode === 'weakspots' && quizMistakes.length > 0) {
pool = STILMITTEL_DATA.filter(item => quizMistakes.includes(item.id));
document.getElementById('quizModeName').innerText = `Schwachstellen-Training (${pool.length} Begriffe)`;
} else if (mode === 'all') {
pool = [...STILMITTEL_DATA];
document.getElementById('quizModeName').innerText = 'Gesamt-Challenge (Alle 23 Stilmittel)';
} else if (mode === 'zusatz') {
pool = STILMITTEL_DATA.filter(item => !item.highlighted);
document.getElementById('quizModeName').innerText = 'Zusatzinfo-Training (6 erweiterte Stilmittel)';
} else {
// Default: highlighted / Kernstoff (17)
currentQuizMode = 'highlighted';
pool = STILMITTEL_DATA.filter(item => item.highlighted);
document.getElementById('quizModeName').innerText = '🌟 Kernstoff-Challenge (17 Stilmittel)';
}
if (pool.length === 0) pool = [...STILMITTEL_DATA];
pool.sort(() => Math.random() - 0.5);
const questionCount = Math.min(10, pool.length);
quizQuestions = [];
for (let i = 0; i < questionCount; i++) {
const correct = pool[i];
const randomExample = correct.examples[Math.floor(Math.random() * correct.examples.length)];
const distractors = STILMITTEL_DATA
.filter(x => x.id !== correct.id)
.sort(() => Math.random() - 0.5)
.slice(0, 3);
const options = [correct, ...distractors].sort(() => Math.random() - 0.5);
quizQuestions.push({
correctItem: correct,
example: randomExample,
options: options
});
}
currentQuizIndex = 0;
quizScore = 0;
quizStreak = 0;
quizMaxStreak = 0;
isQuestionAnswered = false;
document.getElementById('quizScore').innerText = '0';
document.getElementById('quizStreak').innerText = '0';
document.getElementById('quizPlayArea').classList.remove('hidden');
document.getElementById('quizResultScreen').classList.add('hidden');
document.getElementById('quizFeedbackBox').classList.add('hidden');
renderQuizQuestion();
}
function startWeakSpotsQuiz() {
if (quizMistakes.length === 0) {
showToast('Keine Schwachstellen vorhanden! Starte Kernstoff-Quiz.', 'fa-star');
startQuiz('highlighted');
return;
}
startQuiz('weakspots');
}
function renderQuizQuestion() {
if (currentQuizIndex >= quizQuestions.length) {
finishQuiz();
return;
}
const q = quizQuestions[currentQuizIndex];
isQuestionAnswered = false;
document.getElementById('quizQuestionNumber').innerText = `Frage ${currentQuizIndex + 1} von ${quizQuestions.length}`;
document.getElementById('quizCategoryTag').innerText = q.correctItem.categoryLabel;
document.getElementById('quizPhraseBox').innerText = `„${q.example}“`;
document.getElementById('quizFeedbackBox').classList.add('hidden');
const container = document.getElementById('quizOptionsContainer');
container.innerHTML = '';
q.options.forEach((opt, idx) => {
const btn = document.createElement('button');
btn.id = `quiz-opt-btn-${idx}`;
btn.className = 'quiz-opt-btn w-full p-4 rounded-2xl bg-white border-2 border-slate-200 hover:border-indigo-500 hover:bg-indigo-50/50 font-bold text-slate-800 text-sm transition shadow-sm text-left flex items-center justify-between group';
btn.innerHTML = `
<div class="flex items-center gap-3">
<span class="w-7 h-7 rounded-xl bg-slate-100 group-hover:bg-indigo-600 group-hover:text-white flex items-center justify-center text-xs font-mono text-slate-600 transition">${idx + 1}</span>
<span>${opt.term}</span>
</div>
<i class="fa-solid fa-chevron-right text-slate-300 group-hover:text-indigo-600 text-xs transition"></i>
`;
btn.onclick = () => answerQuiz(opt.id, btn);
container.appendChild(btn);
});
}
function answerQuiz(selectedId, buttonEl) {
if (isQuestionAnswered) return;
isQuestionAnswered = true;
const q = quizQuestions[currentQuizIndex];
const isCorrect = selectedId === q.correctItem.id;
const feedbackBox = document.getElementById('quizFeedbackBox');
const feedbackIcon = document.getElementById('quizFeedbackIcon');
const feedbackTitle = document.getElementById('quizFeedbackTitle');
const feedbackText = document.getElementById('quizFeedbackText');
const buttons = document.querySelectorAll('.quiz-opt-btn');
buttons.forEach(btn => {
btn.disabled = true;
btn.classList.add('pointer-events-none');
});
if (isCorrect) {
quizScore += 100 + (quizStreak * 20);
quizStreak++;
if (quizStreak > quizMaxStreak) quizMaxStreak = quizStreak;
playTone('success');
quizMistakes = quizMistakes.filter(id => id !== q.correctItem.id);
localStorage.setItem('rhetorik_quiz_mistakes', JSON.stringify(quizMistakes));
buttonEl.className = 'quiz-opt-btn w-full p-4 rounded-2xl bg-emerald-50 border-2 border-emerald-500 font-bold text-emerald-950 text-sm shadow-md text-left flex items-center justify-between';
buttonEl.innerHTML += `<i class="fa-solid fa-check text-emerald-600 text-lg"></i>`;
feedbackBox.className = 'rounded-2xl p-4 sm:p-5 border bg-emerald-50 border-emerald-200 text-emerald-950 block';
feedbackIcon.className = 'fa-solid fa-circle-check text-emerald-600 text-2xl mt-0.5';
feedbackTitle.innerText = `Exakt richtig! Es ist eine ${q.correctItem.term}.`;
feedbackText.innerText = `${q.correctItem.definition} (${q.correctItem.tip || ''})`;
} else {
quizStreak = 0;
playTone('error');
if (!quizMistakes.includes(q.correctItem.id)) {
quizMistakes.push(q.correctItem.id);
localStorage.setItem('rhetorik_quiz_mistakes', JSON.stringify(quizMistakes));
}
buttonEl.className = 'quiz-opt-btn w-full p-4 rounded-2xl bg-rose-50 border-2 border-rose-500 font-bold text-rose-950 text-sm shadow-md text-left flex items-center justify-between';
buttonEl.innerHTML += `<i class="fa-solid fa-xmark text-rose-600 text-lg"></i>`;
buttons.forEach((btn, idx) => {
if (q.options[idx].id === q.correctItem.id) {
btn.className = 'quiz-opt-btn w-full p-4 rounded-2xl bg-emerald-50 border-2 border-emerald-400 font-bold text-emerald-900 text-sm shadow-sm text-left flex items-center justify-between';
btn.innerHTML += `<i class="fa-solid fa-check text-emerald-600 text-base"></i>`;
}
});
feedbackBox.className = 'rounded-2xl p-4 sm:p-5 border bg-rose-50 border-rose-200 text-rose-950 block';
feedbackIcon.className = 'fa-solid fa-circle-xmark text-rose-600 text-2xl mt-0.5';
feedbackTitle.innerText = `Leider falsch. Richtig wäre: ${q.correctItem.term}.`;
feedbackText.innerText = `${q.correctItem.definition} Merke: ${q.correctItem.tip || ''}`;
}
document.getElementById('quizScore').innerText = quizScore;
document.getElementById('quizStreak').innerText = quizStreak;
updateQuizMistakesBadge();
}
function nextQuizQuestion() {
currentQuizIndex++;
renderQuizQuestion();
}
function finishQuiz() {
playTone('win');
document.getElementById('quizPlayArea').classList.add('hidden');
const resultScreen = document.getElementById('quizResultScreen');
resultScreen.classList.remove('hidden');
const correctCount = Math.round(quizScore / 100);
document.getElementById('resultScore').innerText = `${quizScore} Pkt`;
document.getElementById('resultPercent').innerText = `${Math.min(100, Math.round((correctCount / quizQuestions.length) * 100))}%`;
document.getElementById('resultMaxStreak').innerText = quizMaxStreak;
const repeatBtn = document.getElementById('repeatMistakesBtn');
if (quizMistakes.length > 0) {
repeatBtn.classList.remove('hidden');
} else {
repeatBtn.classList.add('hidden');
}
}
// MATCHER GAME
function initMatcherGame() {
matcherSelectedPhrase = null;
matcherSelectedTerm = null;
matcherMatchedCount = 0;
document.getElementById('matchScore').innerText = '0';
document.getElementById('matcherWinBanner').classList.add('hidden');
const shuffled = [...STILMITTEL_DATA].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);
const phrasesContainer = document.getElementById('matcherPhrasesContainer');
const termsContainer = document.getElementById('matcherTermsContainer');
phrasesContainer.innerHTML = '';
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);
});
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 = `<span>${t.name}</span><i class="fa-solid fa-link text-slate-300 text-xs"></i>`;
btn.onclick = () => selectMatcherTerm(t.id, btn);
termsContainer.appendChild(btn);
});
}
function selectMatcherPhrase(id, el) {
if (el.classList.contains('opacity-40')) return;
document.querySelectorAll('.match-phrase-card').forEach(c => {
if (!c.classList.contains('opacity-40')) {
c.classList.remove('border-indigo-600', 'bg-indigo-50');
c.classList.add('border-slate-200');
}
});
matcherSelectedPhrase = { id, el };
el.classList.remove('border-slate-200');
el.classList.add('border-indigo-600', 'bg-indigo-50');
playTone('flip');
checkMatcherMatch();
}
function selectMatcherTerm(id, el) {
if (el.classList.contains('opacity-40')) return;
document.querySelectorAll('.match-term-card').forEach(c => {
if (!c.classList.contains('opacity-40')) {
c.classList.remove('border-indigo-600', 'bg-indigo-50');
c.classList.add('border-slate-200');
}
});
matcherSelectedTerm = { id, el };
el.classList.remove('border-slate-200');
el.classList.add('border-indigo-600', 'bg-indigo-50');
playTone('flip');
checkMatcherMatch();
}
function checkMatcherMatch() {
if (matcherSelectedPhrase && matcherSelectedTerm) {
if (matcherSelectedPhrase.id === matcherSelectedTerm.id) {
playTone('success');
const pEl = matcherSelectedPhrase.el;
const tEl = matcherSelectedTerm.el;
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 += `<i class="fa-solid fa-check text-emerald-600"></i>`;
matcherMatchedCount++;
document.getElementById('matchScore').innerText = matcherMatchedCount;
matcherSelectedPhrase = null;
matcherSelectedTerm = null;
if (matcherMatchedCount === 5) {
playTone('win');
document.getElementById('matcherWinBanner').classList.remove('hidden');
}
} else {
playTone('error');
const pEl = matcherSelectedPhrase.el;
const tEl = matcherSelectedTerm.el;
pEl.classList.add('border-rose-500', 'bg-rose-50');
tEl.classList.add('border-rose-500', 'bg-rose-50');
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);
}
}
}
// GLOSSAR & SUCHE
function setGlossaryFilter(filter) {
currentGlossaryFilter = filter;
const btns = ['filterAllBtn', 'filterKernBtn', 'filterZusatzBtn', 'filterKlangBtn', 'filterSyntaxBtn', 'filterWortBtn', 'filterGedankeBtn'];
btns.forEach(id => {
const el = document.getElementById(id);
if (el) el.className = 'px-3 py-1.5 rounded-lg text-xs font-semibold bg-slate-100 text-slate-600 hover:bg-slate-200 border border-slate-200';
});
const activeMap = {
'all': 'filterAllBtn',
'highlight': 'filterKernBtn',
'zusatz': 'filterZusatzBtn',
'klang': 'filterKlangBtn',
'syntax': 'filterSyntaxBtn',
'wort': 'filterWortBtn',
'gedanke': 'filterGedankeBtn'
};
const activeEl = document.getElementById(activeMap[filter]);
if (activeEl) {
activeEl.className = 'px-3 py-1.5 rounded-lg text-xs font-bold bg-indigo-50 text-indigo-700 border border-indigo-200';
}
renderGlossary();
}
function renderGlossary() {
const searchVal = document.getElementById('glossarySearchInput').value.toLowerCase().trim();
const grid = document.getElementById('glossaryGrid');
let filtered = STILMITTEL_DATA.filter(item => {
if (currentGlossaryFilter === 'highlight' && !item.highlighted) return false;
if (currentGlossaryFilter === 'zusatz' && item.highlighted) return false;
if (['klang', 'syntax', 'wort', 'gedanke'].includes(currentGlossaryFilter) && item.category !== currentGlossaryFilter) return false;
if (!searchVal) return true;
const inTerm = item.term.toLowerCase().includes(searchVal);
const inDef = item.definition.toLowerCase().includes(searchVal);
const inEx = item.examples.some(e => e.toLowerCase().includes(searchVal));
const inTip = (item.tip || '').toLowerCase().includes(searchVal);
return inTerm || inDef || inEx || inTip;
});
document.getElementById('glossaryResultCount').innerText = `${filtered.length} von ${STILMITTEL_DATA.length} Stilmitteln gefunden`;
grid.innerHTML = '';
if (filtered.length === 0) {
grid.innerHTML = `
<div class="col-span-full py-12 text-center text-slate-400">
<i class="fa-solid fa-search text-3xl mb-2"></i>
<p>Kein passendes Stilmittel gefunden für „${searchVal}“.</p>
</div>
`;
return;
}
filtered.forEach(item => {
const card = document.createElement('div');
const level = getItemLevel(item.id);
card.className = 'bg-white rounded-2xl p-5 border border-slate-200 shadow-sm hover:shadow-md hover:border-indigo-300 transition duration-150 flex flex-col justify-between cursor-pointer';
card.onclick = () => openDetailModal(item.id);
card.innerHTML = `
<div>
<div class="flex items-center justify-between mb-2">
<span class="text-base font-extrabold text-slate-900 group-hover:text-indigo-600">${item.term}</span>
<div class="flex items-center gap-1.5">
<span class="text-[10px] font-semibold px-2 py-0.5 rounded-full bg-slate-100 text-slate-600">${item.categoryLabel}</span>
${item.highlighted
? '<span class="text-[10px] uppercase font-bold px-2 py-0.5 rounded-full bg-indigo-50 text-indigo-700 border border-indigo-200">🌟 Kernstoff</span>'
: '<span class="text-[10px] uppercase font-bold px-2 py-0.5 rounded-full bg-slate-100 text-slate-500 border border-slate-200">🔍 Zusatz</span>'
}
</div>
</div>
<p class="text-xs text-slate-600 line-clamp-2 leading-relaxed mb-3">${item.definition}</p>
</div>
<div class="pt-3 border-t border-slate-100 flex items-center justify-between text-xs">
<p class="font-serif italic text-indigo-900 truncate max-w-[70%]">„${item.examples[0]}“</p>
<span class="text-[10px] font-bold px-2 py-0.5 rounded-md ${level >= 5 ? 'bg-emerald-100 text-emerald-800' : 'bg-slate-100 text-slate-500'}">
Kasten ${level}
</span>
</div>
`;
grid.appendChild(card);
});
}
function openDetailModal(id) {
const item = STILMITTEL_DATA.find(x => x.id === id);
if (!item) return;
document.getElementById('modalTerm').innerText = item.term;
document.getElementById('modalDefinition').innerText = item.definition;
document.getElementById('modalCategoryBadge').innerText = item.categoryLabel;
document.getElementById('modalTip').innerText = item.tip || 'Achte bei der Textanalyse auf den Wirkungszusammenhang mit dem Hauptthema.';
const badge = document.getElementById('modalHighlightBadge');
if (item.highlighted) {
badge.innerText = '🌟 Kernstoff (Prüfungsrelevant)';
badge.className = 'text-xs font-bold px-2.5 py-0.5 rounded-full bg-indigo-50 text-indigo-700 border border-indigo-200 uppercase tracking-wider';
} else {
badge.innerText = '🔍 Zusatzinformation (Vertiefung)';
badge.className = 'text-xs font-bold px-2.5 py-0.5 rounded-full bg-slate-100 text-slate-600 border border-slate-200 uppercase tracking-wider';
}
const exHtml = item.examples.map(ex => `<div class="py-0.5">• „${ex}“</div>`).join('');
document.getElementById('modalExamples').innerHTML = exHtml;
playTone('flip');
document.getElementById('detailModal').classList.remove('hidden');
}
function closeDetailModal() {
document.getElementById('detailModal').classList.add('hidden');
}
// PRINT & CHEATSHEET
function renderPrintCheatsheet() {
const tbody = document.getElementById('printTableBody');
if (!tbody) return;
tbody.innerHTML = '';
STILMITTEL_DATA.forEach(item => {
const row = document.createElement('tr');
row.innerHTML = `
<td class="font-bold text-slate-900 align-top">
<div class="flex items-center gap-1.5">
<span>${item.term}</span>
${item.highlighted ? '<span class="text-[8px] bg-indigo-100 text-indigo-800 px-1 py-0.2 rounded font-bold">KERN</span>' : '<span class="text-[8px] bg-slate-100 text-slate-500 px-1 py-0.2 rounded">ZUSATZ</span>'}
</div>
<div class="text-[9px] font-normal text-slate-500 uppercase">${item.categoryLabel}</div>
</td>
<td class="align-top leading-tight text-slate-800">${item.definition}</td>
<td class="font-serif italic align-top text-slate-800 leading-tight">${item.examples.map(e => `„${e}“`).join('<br>')}</td>
<td class="text-slate-600 align-top text-[9px]">${item.tip || '-'}</td>
`;
tbody.appendChild(row);
});
}
function printCheatsheet() {
renderPrintCheatsheet();
window.print();
}
function showToast(text, iconClass = 'fa-circle-check') {
const toast = document.getElementById('toastNotification');
document.getElementById('toastMessage').innerText = text;
document.getElementById('toastIcon').className = `fa-solid ${iconClass} text-emerald-400 text-base`;
toast.classList.remove('translate-y-20', 'opacity-0');
setTimeout(() => {
toast.classList.add('translate-y-20', 'opacity-0');
}, 2500);
}
function switchTab(tabId) {
currentTabIndex = tabId;
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('bg-indigo-50', 'text-indigo-700', 'font-semibold', 'shadow-sm');
btn.classList.add('text-slate-600');
});
const activeBtn = document.getElementById(`tab-btn-${tabId}`);
if (activeBtn) {
activeBtn.classList.add('bg-indigo-50', 'text-indigo-700', 'font-semibold', 'shadow-sm');
activeBtn.classList.remove('text-slate-600');
}
['cards', 'quiz', 'matcher', 'glossary'].forEach(id => {
const view = document.getElementById(`view-${id}`);
if (view) view.classList.add('hidden');
});
const targetView = document.getElementById(`view-${tabId}`);
if (targetView) targetView.classList.remove('hidden');
if (tabId === 'quiz' && quizQuestions.length === 0) {
startQuiz('highlighted');
} else if (tabId === 'matcher' && currentMatcherPairs.length === 0) {
initMatcherGame();
} else if (tabId === 'glossary') {
renderGlossary();
}
}
// Keyboard Shortcuts
window.addEventListener('keydown', (e) => {
if (document.activeElement && document.activeElement.tagName === 'INPUT') {
if (e.key === 'Escape') document.activeElement.blur();
return;
}
if (e.key === 'm' || e.key === 'M') {
toggleSound();
return;
}
if (e.key === 'p' || e.key === 'P') {
printCheatsheet();
return;
}
if (e.key === '/') {
e.preventDefault();
switchTab('glossary');
const input = document.getElementById('glossarySearchInput');
if (input) input.focus();
return;
}
if (e.key === 'Escape') {
closeDetailModal();
return;
}
if (currentTabIndex === 'cards') {
if (e.code === 'Space') {
e.preventDefault();
flipCard();
} else if (e.code === 'ArrowRight') {
nextCard();
} else if (e.code === 'ArrowLeft') {
prevCard();
} else if (e.key === '1') {
markCardMastered(false);
} else if (e.key === '2') {
markCardMastered(true);
}
} else if (currentTabIndex === 'quiz') {
if (['1', '2', '3', '4'].includes(e.key)) {
const idx = parseInt(e.key) - 1;
const btn = document.getElementById(`quiz-opt-btn-${idx}`);
if (btn && !isQuestionAnswered) btn.click();
} else if (e.key === 'Enter' || e.code === 'Space') {
if (isQuestionAnswered) {
e.preventDefault();
nextQuizQuestion();
}
}
}
});
document.getElementById('detailModal').addEventListener('click', (e) => {
if (e.target.id === 'detailModal') closeDetailModal();
});
// PWA Service Worker Registration & Live Update Detection
let deferredPrompt = null;
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js').then(reg => {
reg.update();
}).catch(err => {
console.warn('SW registration failed:', err);
});
});
let refreshing = false;
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (!refreshing) {
refreshing = true;
window.location.reload();
}
});
}
function forceAppUpdate() {
if ('caches' in window) {
caches.keys().then(names => Promise.all(names.map(name => caches.delete(name)))).then(() => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(regs => {
for (let reg of regs) reg.unregister();
window.location.reload(true);
});
} else {
window.location.reload(true);
}
});
} else {
window.location.reload(true);
}
}
window.addEventListener('beforeinstallprompt', (e) => {
e.preventDefault();
deferredPrompt = e;
const installBtn = document.getElementById('pwaInstallBtn');
if (installBtn) {
installBtn.classList.remove('hidden');
installBtn.classList.add('flex');
}
});
function installPWA() {
if (deferredPrompt) {
deferredPrompt.prompt();
deferredPrompt.userChoice.then(() => {
deferredPrompt = null;
const installBtn = document.getElementById('pwaInstallBtn');
if (installBtn) installBtn.classList.add('hidden');
});
}
}
window.onload = function() {
updateSoundUI();
applyCardFilters();
updateMasteredHeader();
renderPrintCheatsheet();
};
</script>
</body>
</html>