/**
Hebrew verb β English translation with pronoun */ (function() { βuse strictβ;
const AudioManager = { ctx: null, init() { try { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {} }, play(type) { if (!this.ctx) return; const osc = this.ctx.createOscillator(); const gain = this.ctx.createGain(); osc.connect(gain); gain.connect(this.ctx.destination);
if (type === 'correct') {
osc.frequency.setValueAtTime(880, this.ctx.currentTime);
gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.15);
osc.start(); osc.stop(this.ctx.currentTime + 0.15);
} else if (type === 'wrong') {
osc.type = 'sawtooth';
osc.frequency.setValueAtTime(200, this.ctx.currentTime);
gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.2);
osc.start(); osc.stop(this.ctx.currentTime + 0.2);
} else if (type === 'streak') {
// Celebration sound for streak milestones
osc.type = 'sine';
osc.frequency.setValueAtTime(523, this.ctx.currentTime);
osc.frequency.setValueAtTime(659, this.ctx.currentTime + 0.1);
osc.frequency.setValueAtTime(784, this.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0.15, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.3);
osc.start(); osc.stop(this.ctx.currentTime + 0.3);
} else if (type === 'levelup') {
// Level up sound
osc.type = 'square';
osc.frequency.setValueAtTime(440, this.ctx.currentTime);
osc.frequency.setValueAtTime(554, this.ctx.currentTime + 0.1);
osc.frequency.setValueAtTime(659, this.ctx.currentTime + 0.2);
gain.gain.setValueAtTime(0.2, this.ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.01, this.ctx.currentTime + 0.4);
osc.start(); osc.stop(this.ctx.currentTime + 0.4);
}
} };
class TranslationQuiz { constructor() { this.allForms = []; this.currentQuestion = null; this.score = 0; this.correct = 0; this.total = 0; this.streak = 0; this.bestStreak = 0; this.container = null; this.startTime = null; }
async init(container, topic, topicName) {
this.container = container;
this.topic = topic;
this.topicName = topicName;
this.startTime = Date.now();
AudioManager.init();
try {
await this.loadVerbs();
this.render();
} catch (error) {
this.showError(error.message);
}
}
async loadVerbs() {
const verbSystem = new window.VerbConjugationSystem();
const data = await verbSystem.initialize();
this.allForms = data.allForms;
if (this.allForms.length === 0) throw new Error('No verb forms loaded');
console.log(`Loaded ${this.allForms.length} verb forms for quiz`);
}
render() {
this.container.innerHTML = `
<style>
.tq-game { max-width: 750px; margin: 0 auto; text-align: center; }
.tq-stats { display: flex; justify-content: center; gap: 2rem; margin-bottom: 1.5rem; }
.tq-stat-value { font-size: 1.5rem; color: #ffd700; }
.tq-stat-label { font-size: 0.8rem; color: #e5c98d; }
.tq-card {
background: #26211A;
border: 3px solid #5c3e17;
border-radius: 20px;
padding: 2.5rem 2rem;
margin-bottom: 1.5rem;
min-height: 400px;
display: flex;
flex-direction: column;
justify-content: center;
position: relative;
overflow: hidden;
}
.tq-card.shake {
animation: shake 0.3s ease;
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-10px); }
75% { transform: translateX(10px); }
}
.tq-card.glow {
animation: glow 0.5s ease;
}
@keyframes glow {
0%, 100% { box-shadow: 0 0 0 rgba(74, 222, 128, 0); }
50% { box-shadow: 0 0 30px rgba(74, 222, 128, 0.6); }
}
.confetti {
position: absolute;
width: 10px;
height: 10px;
background: #ffd700;
animation: confetti-fall 1s ease-out forwards;
pointer-events: none;
}
@keyframes confetti-fall {
to {
transform: translateY(400px) rotate(360deg);
opacity: 0;
}
}
.tq-hebrew {
font-family: 'Manakahthey';
font-size: 4rem;
color: white;
direction: rtl;
margin-bottom: 1rem;
text-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
}
.tq-verb-info {
font-size: 1rem;
color: #c084fc;
margin-bottom: 2rem;
}
.tq-question {
color: #f8e49b;
margin-bottom: 2rem;
font-size: 1.2rem;
font-weight: 600;
}
.tq-choices {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
margin: 0 auto;
max-width: 600px;
}
.tq-choice {
padding: 1.2rem;
font-size: 1.1rem;
border: 2px solid #444;
border-radius: 12px;
cursor: pointer;
font-family: 'Manakahthey';
transition: all 0.3s;
background: #232323;
color: #e5c98d;
}
.tq-choice:hover {
border-color: #8a4fff;
background: #2e2e2e;
transform: translateY(-2px);
}
.tq-choice.correct {
border-color: #4ade80;
background: rgba(74, 222, 128, 0.2);
color: #4ade80;
animation: pulse-correct 0.5s ease;
}
@keyframes pulse-correct {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.tq-choice.wrong {
border-color: #e24a4a;
background: rgba(226, 74, 74, 0.2);
color: #e24a4a;
animation: shake 0.3s ease;
}
.tq-choice:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.tq-feedback {
margin-top: 1.5rem;
padding: 1rem;
border-radius: 12px;
font-size: 1rem;
display: none;
animation: slideIn 0.3s ease;
}
.tq-feedback.show { display: block; }
.tq-feedback.correct {
background: rgba(74, 222, 128, 0.2);
border: 2px solid #4ade80;
color: #4ade80;
}
.tq-feedback.wrong {
background: rgba(226, 74, 74, 0.2);
border: 2px solid #e24a4a;
color: #e24a4a;
}
@keyframes slideIn {
from { opacity: 0; transform: translateY(-10px); }
to { opacity: 1; transform: translateY(0); }
}
.tq-next-btn {
margin-top: 1rem;
padding: 0.8rem 2rem;
background: #232323;
border: 2px solid #ffd700;
color: #ffd700;
font-family: 'Manakahthey';
font-size: 1rem;
border-radius: 10px;
cursor: pointer;
transition: all 0.3s;
}
.tq-next-btn:hover {
background: #ffd700;
color: #181818;
transform: scale(1.05);
}
.tq-start-btn {
padding: 1.5rem 4rem;
background: linear-gradient(135deg, #8a4fff, #c084fc);
color: white;
border: none;
border-radius: 15px;
font-size: 1.5rem;
cursor: pointer;
font-family: 'Manakahthey';
font-weight: bold;
transition: all 0.3s;
}
.tq-start-btn:hover {
transform: translateY(-3px);
box-shadow: 0 8px 25px rgba(138, 79, 255, 0.5);
}
.tq-result { padding: 2rem; }
.tq-result h3 { font-size: 2rem; color: #ffd700; margin-bottom: 1rem; }
.streak-milestone {
animation: celebration 0.6s ease;
font-size: 2rem !important;
}
@keyframes celebration {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
@media (max-width: 600px) {
.tq-hebrew { font-size: 3rem; }
.tq-choices { grid-template-columns: 1fr; }
}
</style>
<div class="tq-game">
<div class="tq-stats">
<div><div class="tq-stat-value" id="tq-score">0</div><div class="tq-stat-label">Score</div></div>
<div><div class="tq-stat-value" id="tq-streak">π₯ 0</div><div class="tq-stat-label">Streak</div></div>
<div><div class="tq-stat-value" id="tq-correct">0/0</div><div class="tq-stat-label">Correct</div></div>
</div>
<div class="tq-card" id="tq-card">
<p style="font-size:1.1rem;margin-bottom:2rem;">Choose the correct translation!</p>
<p style="color:#e5c98d;margin-bottom:2rem;">You'll see a Hebrew verb form. Pick the correct English translation with the pronoun.</p>
<button class="tq-start-btn" id="tq-start">βΆοΈ Start Quiz</button>
</div>
</div>
`;
document.getElementById('tq-start').addEventListener('click', () => this.startGame());
}
startGame() {
this.score = 0;
this.correct = 0;
this.total = 0;
this.streak = 0;
this.bestStreak = 0;
this.showQuestion();
}
showQuestion() {
// Get random form
this.currentQuestion = this.allForms[Math.floor(Math.random() * this.allForms.length)];
// Generate wrong answers (different persons of same verb or different verbs)
const wrongAnswers = this.generateWrongAnswers(3);
// Combine and shuffle
const allChoices = [
{ text: this.currentQuestion.translation, correct: true },
...wrongAnswers.map(wa => ({ text: wa, correct: false }))
];
this.shuffleArray(allChoices);
const choicesHTML = allChoices.map((choice, index) =>
`<button class="tq-choice" data-index="${index}" data-correct="${choice.correct}">
${choice.text}
</button>`
).join('');
document.getElementById('tq-card').innerHTML = `
<div class="tq-hebrew">${this.currentQuestion.form}</div>
<div class="tq-verb-info">Root: ${this.currentQuestion.lemma} β’ ${this.currentQuestion.tense === 'perfect' ? 'Perfect' : 'Imperfect'}</div>
<p class="tq-question">What is the translation?</p>
<div class="tq-choices" id="tq-choices">
${choicesHTML}
</div>
<div class="tq-feedback" id="tq-feedback"></div>
`;
// Add event listeners
document.querySelectorAll('.tq-choice').forEach(btn => {
btn.addEventListener('click', (e) => this.answer(e.target));
});
}
generateWrongAnswers(count) {
const wrong = [];
const usedTranslations = new Set([this.currentQuestion.translation]);
// Try to get wrong answers from same verb (different persons)
const sameVerbForms = this.allForms.filter(f =>
f.lemma === this.currentQuestion.lemma &&
f.tense === this.currentQuestion.tense &&
f.person !== this.currentQuestion.person
);
// Add some from same verb
sameVerbForms.forEach(form => {
if (wrong.length < count && !usedTranslations.has(form.translation)) {
wrong.push(form.translation);
usedTranslations.add(form.translation);
}
});
// Fill remaining with random other forms
while (wrong.length < count) {
const randomForm = this.allForms[Math.floor(Math.random() * this.allForms.length)];
if (!usedTranslations.has(randomForm.translation)) {
wrong.push(randomForm.translation);
usedTranslations.add(randomForm.translation);
}
}
return wrong.slice(0, count);
}
shuffleArray(array) {
for (let i = array.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]];
}
}
createConfetti(count = 30) {
const card = document.getElementById('tq-card');
const colors = ['#ffd700', '#4ade80', '#8a4fff', '#ff69b4', '#ffa500'];
for (let i = 0; i < count; i++) {
const confetti = document.createElement('div');
confetti.className = 'confetti';
confetti.style.left = Math.random() * 100 + '%';
confetti.style.background = colors[Math.floor(Math.random() * colors.length)];
confetti.style.animationDelay = (Math.random() * 0.3) + 's';
confetti.style.animationDuration = (0.8 + Math.random() * 0.4) + 's';
card.appendChild(confetti);
setTimeout(() => confetti.remove(), 1200);
}
}
celebrateCorrect() {
const card = document.getElementById('tq-card');
card.classList.add('glow');
setTimeout(() => card.classList.remove('glow'), 500);
// Confetti on streak milestones
if (this.streak % 5 === 0 && this.streak > 0) {
this.createConfetti(50);
AudioManager.play('streak');
}
}
shakeWrong() {
const card = document.getElementById('tq-card');
card.classList.add('shake');
setTimeout(() => card.classList.remove('shake'), 300);
}
answer(btnElement) {
this.total++;
const isCorrect = btnElement.dataset.correct === 'true';
const feedbackEl = document.getElementById('tq-feedback');
// Disable all buttons
document.querySelectorAll('.tq-choice').forEach(btn => {
btn.disabled = true;
if (btn.dataset.correct === 'true') {
btn.classList.add('correct');
}
});
if (isCorrect) {
this.correct++;
this.streak++;
this.score += 10 + (this.streak * 2);
if (this.streak > this.bestStreak) this.bestStreak = this.streak;
// Celebration effects
this.celebrateCorrect();
AudioManager.play('correct');
// Streak milestone message
let streakMsg = '';
if (this.streak === 5) streakMsg = '<br>π₯ <strong>5-STREAK! Amazing!</strong>';
if (this.streak === 10) streakMsg = '<br>π₯π₯ <strong>10-STREAK! Incredible!</strong>';
if (this.streak === 15) streakMsg = '<br>π₯π₯π₯ <strong>15-STREAK! Unstoppable!</strong>';
feedbackEl.className = 'tq-feedback correct show';
feedbackEl.innerHTML = `
β Correct! ${this.currentQuestion.personLabel}<br>
<span style="font-size:0.9rem;opacity:0.9;">+${10 + (this.streak * 2)} points β’ ${this.currentQuestion.parsing}</span>
${streakMsg}
`;
} else {
btnElement.classList.add('wrong');
this.streak = 0;
// Shake effect
this.shakeWrong();
AudioManager.play('wrong');
feedbackEl.className = 'tq-feedback wrong show';
feedbackEl.innerHTML = `
β Wrong. Correct answer: <strong>${this.currentQuestion.translation}</strong><br>
<span style="font-size:0.9rem;opacity:0.9;">${this.currentQuestion.personLabel} β’ ${this.currentQuestion.parsing}</span>
`;
}
this.updateStats();
// Add next button
feedbackEl.innerHTML += '<br><button class="tq-next-btn" id="tq-next-btn">Next Question β</button>';
document.getElementById('tq-next-btn').addEventListener('click', () => this.showQuestion());
// Check if reached 20 questions
if (this.total >= 20) {
setTimeout(() => this.endGame(), 1500);
}
}
updateStats() {
document.getElementById('tq-score').textContent = this.score;
document.getElementById('tq-streak').textContent = `π₯ ${this.streak}`;
document.getElementById('tq-correct').textContent = `${this.correct}/${this.total}`;
}
async endGame() {
const accuracy = this.total > 0 ? Math.round((this.correct / this.total) * 100) : 0;
const studyTime = Math.round((Date.now() - this.startTime) / 60000);
await this.saveStats(accuracy, studyTime);
// Performance-based celebration
let celebrationEmoji = 'π';
let celebrationMsg = 'Great job!';
if (accuracy >= 90) {
celebrationEmoji = 'π';
celebrationMsg = 'Outstanding! You\'re a master!';
this.createConfetti(80);
AudioManager.play('levelup');
} else if (accuracy >= 75) {
celebrationEmoji = 'β';
celebrationMsg = 'Excellent work!';
this.createConfetti(40);
} else if (accuracy >= 60) {
celebrationEmoji = 'π';
celebrationMsg = 'Good effort!';
}
document.querySelector('.tq-game').innerHTML = `
<div class="tq-result">
<h3>${celebrationEmoji} Quiz Complete! ${celebrationEmoji}</h3>
<p style="font-size:1.1rem;color:#c084fc;margin-bottom:1.5rem;">${celebrationMsg}</p>
<p style="font-size:1.3rem;color:#e5c98d;line-height:2;margin-bottom:2rem;">
Final Score: <strong style="color:#ffd700">${this.score}</strong><br>
Correct: <strong style="color:#4ade80">${this.correct}/${this.total}</strong> (${accuracy}%)<br>
Best Streak: <strong>π₯ ${this.bestStreak}</strong>
</p>
<button class="tq-start-btn" onclick="location.reload()">π New Quiz</button>
<button class="tq-start-btn" style="background:#232323;margin-left:1rem;margin-top:1rem;" onclick="location.href='/games/grammar.html'">β Back to Grammar</button>
</div>
`;
}
async saveStats(accuracy, studyTime) {
try {
if (typeof firebase === 'undefined' || !firebase.auth) return;
const user = firebase.auth().currentUser;
if (!user) return;
const db = firebase.firestore();
await db.collection('activities').add({
userId: user.uid,
type: 'game_played',
gameName: 'Translation Quiz',
gameType: 'grammar',
topic: this.topicName,
accuracy,
score: this.score,
bestStreak: this.bestStreak,
studyTime,
timestamp: firebase.firestore.FieldValue.serverTimestamp()
});
await db.collection('users').doc(user.uid).update({
'stats.totalGames': firebase.firestore.FieldValue.increment(1),
'stats.totalStudyTime': firebase.firestore.FieldValue.increment(studyTime)
});
} catch (e) {
console.error('Stats error:', e);
}
}
showError(msg) {
this.container.innerHTML = `<div style="text-align:center;padding:2rem;color:#e24a4a;">Error: ${msg}<br><a href="/games/grammar.html" style="color:#c084fc;">β Back</a></div>`;
} }
window.TranslationQuiz = TranslationQuiz; })();