1949 lines
92 KiB
HTML
1949 lines
92 KiB
HTML
<!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> |