@@ -1955,6 +2053,7 @@
if (isMastered) {
setItemLevel(item.id, currentLvl + 1);
playTone('success');
+ addXP(20, 'Karte gemeistert');
cardEl.classList.add('swipe-right');
showToast(`„${item.term}“ aufgestiegen in Kasten ${Math.min(5, currentLvl + 1)}!`, 'fa-circle-up');
} else {
@@ -2049,6 +2148,10 @@
utterance.rate = 0.95;
utterance.pitch = 1.0;
window.speechSynthesis.speak(utterance);
+
+ audioListenCount++;
+ localStorage.setItem('rhetorik_audio_count', audioListenCount.toString());
+ addXP(5);
} catch (err) {
console.warn('SpeechSynthesis error:', err);
}
@@ -2572,6 +2675,13 @@
if (isCorrect) {
quizScore += 100 + (quizStreak * 20);
quizStreak++;
+ if (currentQuizInputType === 'text') {
+ freitextCorrectCount++;
+ localStorage.setItem('rhetorik_freitext_count', freitextCorrectCount.toString());
+ addXP(25, 'Freitext richtig');
+ } else {
+ addXP(15, 'Quiz richtig');
+ }
if (quizStreak > quizMaxStreak) quizMaxStreak = quizStreak;
playTone('success');
@@ -2712,6 +2822,15 @@
const gradeInfo = calculateExamGrade(correctCount, quizQuestions.length);
playTone(gradeInfo.grade <= 3 ? 'win' : 'error');
+ // Grant Exam XP & Badges
+ if (gradeInfo.grade === 1) addXP(250, 'Note 1');
+ else if (gradeInfo.grade === 2) addXP(180, 'Note 2');
+ else if (gradeInfo.grade === 3) addXP(100, 'Note 3');
+ else if (gradeInfo.grade === 4) addXP(50, 'Note 4');
+
+ if (gradeInfo.grade <= 2) unlockBadge('exam_ace');
+ if (elapsedSec <= 300 && gradeInfo.grade <= 3) unlockBadge('speed_demon');
+
// Populate Exam Screen
document.getElementById('quizPlayArea').classList.add('hidden');
document.getElementById('examTimerBar').classList.add('hidden');
@@ -3297,6 +3416,7 @@
const payload = JSON.parse(evt.target.result);
applySyncData(payload);
closeSyncModal();
+ closeAchievementsModal();
showToast('Lernstand aus JSON-Datei importiert!', 'fa-circle-check');
} catch (err) {
alert('Ungültiges Backup-Format in der ausgewählten JSON-Datei.');
@@ -3316,6 +3436,7 @@
}
applySyncData(payload);
closeSyncModal();
+ closeAchievementsModal();
document.getElementById('syncImportText').value = '';
showToast('Lernstand erfolgreich aktualisiert!', 'fa-circle-check');
}
@@ -3375,6 +3496,319 @@
}
}
+
+ // ==========================================
+ // GAMIFICATION, XP, BADGES & SOCIAL SHARING
+ // ==========================================
+ const ACHIEVEMENTS_DEF = [
+ {
+ id: "first_step",
+ title: "Erster Schritt",
+ desc: "Erstes Quiz absolviert oder 5 Karteikarten gelernt.",
+ icon: "fa-seedling",
+ color: "emerald",
+ xpReward: 50
+ },
+ {
+ id: "streak_10",
+ title: "Feuer & Flamme",
+ desc: "Einen 10er Streak im Quiz-Modus erreicht.",
+ icon: "fa-fire",
+ color: "amber",
+ xpReward: 100
+ },
+ {
+ id: "freitext_pro",
+ title: "Freitext-Virtuose",
+ desc: "5 Fragen ohne Multiple Choice per Freitext getippt.",
+ icon: "fa-keyboard",
+ color: "indigo",
+ xpReward: 150
+ },
+ {
+ id: "exam_ace",
+ title: "Klausur-Ass",
+ desc: "Eine Klausursimulation mit Note 1 oder 2 bestanden.",
+ icon: "fa-crown",
+ color: "amber",
+ xpReward: 200
+ },
+ {
+ id: "speed_demon",
+ title: "Speed-Denker",
+ desc: "Eine 15-Fragen-Prüfung in unter 5 Minuten absolviert.",
+ icon: "fa-bolt",
+ color: "violet",
+ xpReward: 120
+ },
+ {
+ id: "audio_listener",
+ title: "Hörversteher",
+ desc: "Mindestens 3 Zitate über die Sprachausgabe angehört.",
+ icon: "fa-headphones",
+ color: "blue",
+ xpReward: 60
+ },
+ {
+ id: "weakness_slayer",
+ title: "Fehlerjäger",
+ desc: "Alle offenen Schwachstellen im Training abgebaut.",
+ icon: "fa-shield-halved",
+ color: "rose",
+ xpReward: 150
+ },
+ {
+ id: "sound_master",
+ title: "Klangmeister",
+ desc: "Alle Klangfiguren in Leitner-Kasten 5 gemeistert.",
+ icon: "fa-music",
+ color: "cyan",
+ xpReward: 180
+ },
+ {
+ id: "grandmaster",
+ title: "Cicero der Neuzeit",
+ desc: "Alle 23 rhetorischen Stilmittel in Kasten 5 gemeistert.",
+ icon: "fa-trophy",
+ color: "amber",
+ xpReward: 500
+ }
+ ];
+
+ const USER_RANKS = [
+ { minXp: 0, name: "Novize", level: 1, icon: "fa-seedling" },
+ { minXp: 500, name: "Textanalytiker", level: 2, icon: "fa-magnifying-glass" },
+ { minXp: 1500, name: "Wortfechter", level: 3, icon: "fa-feather-pointed" },
+ { minXp: 3000, name: "Meister der Rhetorik", level: 4, icon: "fa-award" },
+ { minXp: 6000, name: "Cicero / Großmeister", level: 5, icon: "fa-crown" }
+ ];
+
+ let userXP = parseInt(localStorage.getItem('rhetorik_user_xp') || '0', 10);
+ let unlockedBadges = JSON.parse(localStorage.getItem('rhetorik_unlocked_badges') || '[]');
+ let freitextCorrectCount = parseInt(localStorage.getItem('rhetorik_freitext_count') || '0', 10);
+ let audioListenCount = parseInt(localStorage.getItem('rhetorik_audio_count') || '0', 10);
+
+ function addXP(amount, reason = '') {
+ if (amount <= 0) return;
+ const prevRank = getCurrentRank(userXP);
+ userXP += amount;
+ localStorage.setItem('rhetorik_user_xp', userXP.toString());
+ updateGamificationHeader();
+
+ const newRank = getCurrentRank(userXP);
+ if (newRank.level > prevRank.level) {
+ playTone('win');
+ showToast(`🎉 RANG-AUFSTIEG! Du bist jetzt: ${newRank.name}!`, 'fa-crown');
+ }
+
+ checkAchievements();
+ }
+
+ function getCurrentRank(xp) {
+ let current = USER_RANKS[0];
+ for (let i = 0; i < USER_RANKS.length; i++) {
+ if (xp >= USER_RANKS[i].minXp) {
+ current = USER_RANKS[i];
+ } else {
+ break;
+ }
+ }
+ return current;
+ }
+
+ function getNextRank(xp) {
+ for (let i = 0; i < USER_RANKS.length; i++) {
+ if (xp < USER_RANKS[i].minXp) {
+ return USER_RANKS[i];
+ }
+ }
+ return null;
+ }
+
+ function updateGamificationHeader() {
+ const rank = getCurrentRank(userXP);
+ const titleEl = document.getElementById('userRankTitle');
+ if (titleEl) titleEl.innerText = rank.name;
+
+ const pillEl = document.getElementById('badgeCountPill');
+ if (pillEl) pillEl.innerText = `${unlockedBadges.length}/9`;
+ }
+
+ function unlockBadge(badgeId) {
+ if (unlockedBadges.includes(badgeId)) return;
+ const bDef = ACHIEVEMENTS_DEF.find(b => b.id === badgeId);
+ if (!bDef) return;
+
+ unlockedBadges.push(badgeId);
+ localStorage.setItem('rhetorik_unlocked_badges', JSON.stringify(unlockedBadges));
+ updateGamificationHeader();
+
+ playTone('win');
+ showToast(`🏆 Trophäe freigeschaltet: ${bDef.title}! (+${bDef.xpReward} XP)`, 'fa-trophy');
+ addXP(bDef.xpReward);
+ }
+
+ function checkAchievements() {
+ const masteredCount = STILMITTEL_DATA.filter(item => (cardLevels[item.id] || 1) >= 5).length;
+ const klangMastered = STILMITTEL_DATA.filter(item => item.category === 'klang' && (cardLevels[item.id] || 1) >= 5).length;
+ const totalKlang = STILMITTEL_DATA.filter(item => item.category === 'klang').length;
+
+ // 1. Erster Schritt
+ if (masteredCount >= 5 || userXP >= 50) {
+ unlockBadge('first_step');
+ }
+
+ // 2. Feuer & Flamme
+ if (quizMaxStreak >= 10) {
+ unlockBadge('streak_10');
+ }
+
+ // 3. Freitext-Virtuose
+ if (freitextCorrectCount >= 5) {
+ unlockBadge('freitext_pro');
+ }
+
+ // 6. Hörversteher
+ if (audioListenCount >= 3) {
+ unlockBadge('audio_listener');
+ }
+
+ // 7. Fehlerjäger
+ if (quizMistakes.length === 0 && userXP >= 300) {
+ unlockBadge('weakness_slayer');
+ }
+
+ // 8. Klangmeister
+ if (klangMastered === totalKlang && totalKlang > 0) {
+ unlockBadge('sound_master');
+ }
+
+ // 9. Großmeister
+ if (masteredCount >= STILMITTEL_DATA.length) {
+ unlockBadge('grandmaster');
+ }
+ }
+
+ function openAchievementsModal() {
+ const rank = getCurrentRank(userXP);
+ const nextRank = getNextRank(userXP);
+
+ document.getElementById('modalUserRankName').innerText = `Rang ${rank.level}: ${rank.name}`;
+ document.getElementById('modalUserRankIcon').className = `fa-solid ${rank.icon} text-white`;
+
+ const xpTextEl = document.getElementById('modalUserXpText');
+ const nextTextEl = document.getElementById('modalNextRankText');
+ const barEl = document.getElementById('modalXpProgressBar');
+
+ if (nextRank) {
+ xpTextEl.innerText = `${userXP} XP gesammelt`;
+ const diff = nextRank.minXp - rank.minXp;
+ const currentProgress = userXP - rank.minXp;
+ const percent = Math.min(100, Math.round((currentProgress / diff) * 100));
+ nextTextEl.innerText = `${nextRank.minXp - userXP} XP bis ${nextRank.name}`;
+ barEl.style.width = `${percent}%`;
+ } else {
+ xpTextEl.innerText = `${userXP} XP (Maximalrang erreicht)`;
+ nextTextEl.innerText = 'Cicero / Höchster Rang!';
+ barEl.style.width = '100%';
+ }
+
+ document.getElementById('modalBadgesUnlockedCount').innerText = unlockedBadges.length;
+
+ // Render 9 Badges
+ const grid = document.getElementById('achievementsGrid');
+ grid.innerHTML = '';
+
+ ACHIEVEMENTS_DEF.forEach(b => {
+ const isUnlocked = unlockedBadges.includes(b.id);
+ const card = document.createElement('div');
+ card.className = `p-3.5 rounded-2xl border transition flex items-start gap-3 ${
+ isUnlocked
+ ? 'bg-amber-50/70 border-amber-300 shadow-sm text-slate-900'
+ : 'bg-slate-50 border-slate-200 text-slate-400 opacity-75'
+ }`;
+
+ card.innerHTML = `
+
+
+
+
+
+
+ ${isUnlocked ? 'Frei' : '+' + b.xpReward + ' XP'}
+
+
${b.desc}
+
+ `;
+ grid.appendChild(card);
+ });
+
+ playTone('flip');
+ document.getElementById('achievementsModal').classList.remove('hidden');
+ }
+
+ function closeAchievementsModal() {
+ document.getElementById('achievementsModal').classList.add('hidden');
+ }
+
+ // SOCIAL SHARING FUNCTIONS
+ function shareApp() {
+ const shareData = {
+ title: 'RhetorikMaster - Stilmittel Trainer & Klausur-Spickzettel',
+ text: '🏛️ Meistere 23 rhetorische Stilmittel der Textanalyse mit Karteikarten, interaktivem Quiz und Klausursimulation!',
+ url: 'https://rhetorikmaster.pansi.eu/'
+ };
+
+ if (navigator.share) {
+ navigator.share(shareData).catch(() => {});
+ } else {
+ navigator.clipboard.writeText(`${shareData.text} ${shareData.url}`).then(() => {
+ showToast('Empfehlungslink in die Zwischenablage kopiert!', 'fa-share-nodes');
+ });
+ }
+ }
+
+ function shareExamResult() {
+ const gradeTitle = document.getElementById('examGradeTitle')?.innerText || 'Note: 1';
+ const scoreText = document.getElementById('examScoreDisplay')?.innerText || '14 / 15';
+ const percentText = document.getElementById('examPercentDisplay')?.innerText || '93%';
+
+ const shareData = {
+ title: 'Mein Klausur-Ergebnis im RhetorikMaster',
+ text: `🎓 Ich habe gerade die RhetorikMaster Klausursimulation mit ${gradeTitle} (${scoreText}, ${percentText}) bestanden! 🏛️ Teste dein Stilmittel-Wissen für die Textanalyse:`,
+ url: 'https://rhetorikmaster.pansi.eu/'
+ };
+
+ if (navigator.share) {
+ navigator.share(shareData).catch(() => {});
+ } else {
+ navigator.clipboard.writeText(`${shareData.text} ${shareData.url}`).then(() => {
+ showToast('Klausur-Zeugnis zum Teilen kopiert!', 'fa-copy');
+ });
+ }
+ }
+
+ function shareQuizResult() {
+ const scoreText = document.getElementById('resultScore')?.innerText || '0 Pkt';
+ const streakText = document.getElementById('resultMaxStreak')?.innerText || '0';
+
+ const shareData = {
+ title: 'Mein Highscore im RhetorikMaster',
+ text: `🏆 Ich habe im RhetorikMaster Quiz ${scoreText} und einen Streak von ${streakText} erreicht! Schlägst du mein Ergebnis?`,
+ url: 'https://rhetorikmaster.pansi.eu/'
+ };
+
+ if (navigator.share) {
+ navigator.share(shareData).catch(() => {});
+ } else {
+ navigator.clipboard.writeText(`${shareData.text} ${shareData.url}`).then(() => {
+ showToast('Quiz-Ergebnis zum Teilen kopiert!', 'fa-copy');
+ });
+ }
+ }
+
// Keyboard Shortcuts
window.addEventListener('keydown', (e) => {
if (document.activeElement && document.activeElement.tagName === 'INPUT') {
@@ -3403,6 +3837,7 @@
if (e.key === 'Escape') {
closeDetailModal();
closeSyncModal();
+ closeAchievementsModal();
cancelIncomingSync();
return;
}
@@ -3501,6 +3936,8 @@
applyCardFilters();
updateMasteredHeader();
renderPrintCheatsheet();
+ updateGamificationHeader();
+ checkAchievements();
checkUrlSync();
};
diff --git a/www/sw.js b/www/sw.js
index c6565bd..0d79865 100644
--- a/www/sw.js
+++ b/www/sw.js
@@ -1,4 +1,4 @@
-const CACHE_NAME = 'rhetorikmaster-v8';
+const CACHE_NAME = 'rhetorikmaster-v9';
const STATIC_ASSETS = [
'/',
'/index.html',