echo-light / app.js
moshabann's picture
Update app.js
2f6457f verified
// Application Data
const appData = {
modes: [
{
id: "conversation",
name: "المحادثة الحرة",
icon: "💬",
description: "دردشة مفتوحة مع Echo Light لممارسة المهارات اللغوية"
},
{
id: "vocabulary",
name: "بناء المفردات",
icon: "📚",
description: "نظام بطاقات تعليمية ذكي مع تكرار مباعد"
},
{
id: "pronunciation",
name: "تدريب النطق",
icon: "🎤",
description: "تقييم النطق الفوري مع تعليقات مفصلة"
},
{
id: "grammar",
name: "ألعاب القواعد",
icon: "🎯",
description: "تمارين تفاعلية لتعلم القواعد بطريقة ممتعة"
},
{
id: "scenarios",
name: "سيناريوهات واقعية",
icon: "🌍",
description: "محاكاة مواقف حياتية مثل المطاعم والمقابلات"
},
{
id: "daily",
name: "التحدي اليومي",
icon: "⭐",
description: "تمارين يومية قصيرة للحفاظ على استمرارية التعلم"
}
],
achievements: [
{
id: "first_conversation",
name: "المحادثة الأولى",
description: "أكمل أول محادثة مع Echo Light",
icon: "🎉"
},
{
id: "vocabulary_master",
name: "خبير المفردات",
description: "تعلم 50 كلمة جديدة",
icon: "🏆"
},
{
id: "pronunciation_expert",
name: "خبير النطق",
description: "احصل على تقييم ممتاز في النطق 10 مرات",
icon: "🎯"
},
{
id: "grammar_guru",
name: "معلم القواعد",
description: "أكمل جميع تمارين القواعد بنجاح",
icon: "📖"
},
{
id: "daily_streak",
name: "المواظبة",
description: "أكمل التحدي اليومي لمدة 7 أيام متتالية",
icon: "🔥"
}
],
vocabulary_cards: [
{
word: "Hello",
translation: "مرحبا",
pronunciation: "/həˈloʊ/",
example: "Hello, how are you?"
},
{
word: "Thank you",
translation: "شكراً لك",
pronunciation: "/θæŋk juː/",
example: "Thank you for your help."
},
{
word: "Beautiful",
translation: "جميل",
pronunciation: "/ˈbjuːtɪfəl/",
example: "The sunset is beautiful."
},
{
word: "Important",
translation: "مهم",
pronunciation: "/ɪmˈpɔːrtənt/",
example: "This is very important."
},
{
word: "Learning",
translation: "تعلم",
pronunciation: "/ˈlɜːrnɪŋ/",
example: "Learning English is fun."
}
],
grammar_exercises: [
{
question: "Choose the correct form: I ___ to school every day.",
options: ["go", "goes", "going", "went"],
correct: 0,
explanation: "نستخدم 'go' مع الضمير 'I' في المضارع البسيط"
},
{
question: "Complete: She ___ reading a book now.",
options: ["is", "are", "was", "were"],
correct: 0,
explanation: "نستخدم 'is' مع الضمير 'She' في المضارع المستمر"
}
],
scenarios: [
{
title: "في المطعم",
description: "تعلم كيفية طلب الطعام في المطعم",
dialogue: [
{
speaker: "waiter",
text: "Good evening! Welcome to our restaurant. How can I help you?"
},
{
speaker: "customer",
text: "Good evening! I'd like to see the menu, please."
}
]
},
{
title: "مقابلة عمل",
description: "تدرب على أسئلة مقابلة العمل الشائعة",
dialogue: [
{
speaker: "interviewer",
text: "Tell me about yourself."
},
{
speaker: "candidate",
text: "I'm a motivated professional with experience in..."
}
]
}
],
echoResponses: [
"That's wonderful! Your English is getting better every day.",
"Great job! Can you tell me more about that topic?",
"Excellent! I love how you expressed that idea.",
"Perfect! Let's try using some advanced vocabulary now.",
"Amazing progress! What would you like to talk about next?",
"Fantastic! Your grammar is really improving.",
"Well done! That was a complete and clear sentence.",
"Impressive! You're becoming more confident in English.",
"Brilliant! Let's explore this topic further.",
"Outstanding! Keep up the great work!"
]
};
// Application State
const appState = {
currentScreen: 'homeScreen',
userProgress: {
streak: 5,
totalPoints: 125,
wordsLearned: 12,
conversationsCompleted: 3,
pronunciationAccuracy: 78,
grammarScore: 85,
unlockedAchievements: ['first_conversation']
},
currentVocabIndex: 0,
currentGrammarIndex: 0,
currentScenario: null,
isRecording: false,
soundEnabled: true,
dailyProgress: 1,
selectedChallengeWords: [],
recordingTimer: null
};
// Character Animation Controller
class CharacterController {
constructor() {
this.character = document.getElementById('echoCharacter');
this.mouth = document.getElementById('characterMouth');
this.soundWaves = document.getElementById('soundWaves');
this.isAnimating = false;
this.mouthStates = ['happy', 'speaking', 'surprised', 'neutral'];
this.currentMoodIndex = 0;
this.startIdleAnimation();
}
startIdleAnimation() {
// Eye blinking animation
setInterval(() => {
this.blink();
}, 3000 + Math.random() * 2000);
// Mood changes
setInterval(() => {
this.changeMood();
}, 10000);
}
blink() {
const eyes = document.querySelectorAll('.eye');
eyes.forEach(eye => {
eye.style.transform = 'scaleY(0.1)';
setTimeout(() => {
eye.style.transform = 'scaleY(1)';
}, 150);
});
}
changeMood() {
if (this.isAnimating) return;
const moods = ['happy', 'excited', 'thinking', 'encouraging'];
const randomMood = moods[Math.floor(Math.random() * moods.length)];
this.setMood(randomMood);
}
setMood(mood) {
const character = this.character;
const mouth = this.mouth;
// Remove existing mood classes
character.classList.remove('happy', 'excited', 'thinking', 'encouraging');
character.classList.add(mood);
switch(mood) {
case 'happy':
mouth.style.borderRadius = '0 0 40px 40px';
mouth.style.width = '40px';
mouth.style.background = '#ff6b9d';
break;
case 'excited':
mouth.style.borderRadius = '50%';
mouth.style.width = '30px';
mouth.style.background = '#32b8c6';
break;
case 'thinking':
mouth.style.borderRadius = '40px 40px 0 0';
mouth.style.width = '20px';
mouth.style.background = '#8a2be2';
break;
case 'encouraging':
mouth.style.borderRadius = '0 0 50px 50px';
mouth.style.width = '50px';
mouth.style.background = '#ff6b9d';
break;
}
}
speak(text) {
this.isAnimating = true;
this.setMood('happy');
// Animate sound waves
if (this.soundWaves) {
this.soundWaves.style.display = 'block';
}
// Simulate mouth movement
let speakingInterval = setInterval(() => {
const randomWidth = 20 + Math.random() * 30;
if (this.mouth) {
this.mouth.style.width = randomWidth + 'px';
}
}, 150);
// Use Web Speech API if available and enabled
if ('speechSynthesis' in window && appState.soundEnabled) {
const utterance = new SpeechSynthesisUtterance(text);
utterance.lang = 'en-US';
utterance.rate = 0.9;
utterance.pitch = 1.1;
utterance.onend = () => {
clearInterval(speakingInterval);
this.stopSpeaking();
};
utterance.onerror = () => {
clearInterval(speakingInterval);
this.stopSpeaking();
};
speechSynthesis.speak(utterance);
} else {
// Fallback animation
setTimeout(() => {
clearInterval(speakingInterval);
this.stopSpeaking();
}, Math.min(text.length * 50, 3000));
}
}
stopSpeaking() {
this.isAnimating = false;
if (this.soundWaves) {
this.soundWaves.style.display = 'none';
}
if (this.mouth) {
this.mouth.style.width = '40px';
}
this.setMood('happy');
}
celebrate() {
this.setMood('excited');
if (this.character) {
this.character.style.animation = 'float 0.5s ease-in-out 3';
setTimeout(() => {
this.character.style.animation = 'float 3s ease-in-out infinite';
this.setMood('happy');
}, 1500);
}
}
encourage() {
this.setMood('encouraging');
setTimeout(() => {
this.setMood('happy');
}, 2000);
}
}
// Initialize character controller
let characterController;
// Screen Management
function showScreen(screenId) {
// Hide all screens
document.querySelectorAll('.screen').forEach(screen => {
screen.classList.remove('active');
});
// Show target screen
const targetScreen = document.getElementById(screenId);
if (targetScreen) {
targetScreen.classList.add('active');
appState.currentScreen = screenId;
// Update character mood based on screen
if (characterController) {
switch(screenId) {
case 'homeScreen':
characterController.setMood('happy');
break;
case 'conversationScreen':
characterController.setMood('encouraging');
break;
case 'vocabularyScreen':
case 'grammarScreen':
characterController.setMood('thinking');
break;
case 'pronunciationScreen':
characterController.setMood('excited');
break;
case 'progressScreen':
initializeAchievements();
break;
}
}
}
}
// Mode Management
function initializeModes() {
const modesGrid = document.getElementById('modesGrid');
if (!modesGrid) return;
modesGrid.innerHTML = '';
appData.modes.forEach(mode => {
const modeCard = document.createElement('div');
modeCard.className = 'mode-card';
modeCard.onclick = () => openMode(mode.id);
modeCard.innerHTML = `
<span class="mode-icon">${mode.icon}</span>
<h3 class="mode-title">${mode.name}</h3>
<p class="mode-description">${mode.description}</p>
`;
modesGrid.appendChild(modeCard);
});
}
function openMode(modeId) {
const screenMap = {
'conversation': 'conversationScreen',
'vocabulary': 'vocabularyScreen',
'pronunciation': 'pronunciationScreen',
'grammar': 'grammarScreen',
'scenarios': 'scenariosScreen',
'daily': 'dailyScreen'
};
const screenId = screenMap[modeId];
if (screenId) {
showScreen(screenId);
// Initialize mode-specific content
switch(modeId) {
case 'vocabulary':
initializeVocabulary();
break;
case 'grammar':
initializeGrammar();
break;
case 'scenarios':
initializeScenarios();
break;
case 'daily':
initializeDailyChallenge();
break;
case 'pronunciation':
initializePronunciation();
break;
}
}
}
// Conversation Mode
function sendMessage() {
const input = document.getElementById('messageInput');
if (!input) return;
const message = input.value.trim();
if (!message) return;
addMessage(message, 'user');
input.value = '';
// استدعاء الذكاء الاصطناعي عبر الخادم
fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message})
})
.then(res => res.json())
.then(data => {
addMessage(data.reply, 'echo');
if (characterController) characterController.speak(data.reply);
})
.catch(() => {
addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
});
// تحديث النقاط كما هو
appState.userProgress.conversationsCompleted++;
appState.userProgress.totalPoints += 25;
updateProgressDisplay();
}
function addMessage(text, sender) {
const messagesContainer = document.getElementById('chatMessages');
if (!messagesContainer) return;
const messageDiv = document.createElement('div');
messageDiv.className = `message ${sender}`;
messageDiv.textContent = text;
messagesContainer.appendChild(messageDiv);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
}
function startVoiceInput() {
if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
const recognition = new SpeechRecognition();
recognition.lang = 'en-US';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
const input = document.getElementById('messageInput');
if (input) {
input.value = transcript;
}
};
recognition.onerror = function(event) {
console.log('Speech recognition error:', event.error);
alert('حدث خطأ في التعرف على الصوت. تأكد من السماح بالوصول للميكروفون.');
};
recognition.start();
} else {
alert('متصفحك لا يدعم تقنية التعرف على الصوت.');
}
}
// Vocabulary Mode
function initializeVocabulary() {
appState.currentVocabIndex = 0;
updateVocabularyCard();
updateVocabularyProgress();
}
function updateVocabularyCard() {
const card = appData.vocabulary_cards[appState.currentVocabIndex];
const elements = {
'cardWord': card.word,
'cardPronunciation': card.pronunciation,
'cardTranslation': card.translation,
'cardExample': card.example
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
});
// Reset card flip
const flashcard = document.getElementById('flashcard');
if (flashcard) {
flashcard.classList.remove('flipped');
}
}
function updateVocabularyProgress() {
const progress = ((appState.currentVocabIndex + 1) / appData.vocabulary_cards.length) * 100;
const progressFill = document.getElementById('vocabProgress');
const progressText = document.getElementById('vocabProgressText');
if (progressFill) {
progressFill.style.width = progress + '%';
}
if (progressText) {
progressText.textContent = `${appState.currentVocabIndex + 1}/${appData.vocabulary_cards.length}`;
}
}
function flipCard() {
const flashcard = document.getElementById('flashcard');
if (flashcard) {
flashcard.classList.toggle('flipped');
}
}
function nextCard() {
appState.currentVocabIndex = (appState.currentVocabIndex + 1) % appData.vocabulary_cards.length;
updateVocabularyCard();
updateVocabularyProgress();
// Update progress
appState.userProgress.wordsLearned++;
appState.userProgress.totalPoints += 10;
updateProgressDisplay();
if (characterController) {
characterController.encourage();
}
}
function playPronunciation() {
const card = appData.vocabulary_cards[appState.currentVocabIndex];
if (characterController && appState.soundEnabled) {
characterController.speak(card.word);
}
}
// Pronunciation Mode
function initializePronunciation() {
const words = appData.vocabulary_cards;
const randomWord = words[Math.floor(Math.random() * words.length)];
const practiceWord = document.getElementById('practiceWord');
const practicePronunciation = document.getElementById('practicePronunciation');
if (practiceWord) practiceWord.textContent = randomWord.word;
if (practicePronunciation) practicePronunciation.textContent = randomWord.pronunciation;
// Clear previous feedback
const feedback = document.getElementById('pronunciationFeedback');
if (feedback) {
feedback.innerHTML = '';
}
}
function playTargetPronunciation() {
const word = document.getElementById('practiceWord');
if (word && characterController && appState.soundEnabled) {
characterController.speak(word.textContent);
}
}
function toggleRecording() {
const recordBtn = document.getElementById('recordBtn');
const visualizer = document.getElementById('recordVisualizer');
if (!recordBtn || !visualizer) return;
if (!appState.isRecording) {
// Start recording
appState.isRecording = true;
recordBtn.textContent = '⏹️ إيقاف التسجيل';
recordBtn.classList.add('recording');
// Clear previous content
visualizer.innerHTML = '';
// Create recording visualization
const bars = [];
for (let i = 0; i < 10; i++) {
const bar = document.createElement('div');
bar.style.cssText = `
width: 15px;
background: #32b8c6;
margin: 2px;
border-radius: 2px;
display: inline-block;
height: 20px;
animation: record-bar 0.8s infinite alternate;
animation-delay: ${i * 0.1}s;
`;
bars.push(bar);
visualizer.appendChild(bar);
}
// Add CSS for bars animation if not exists
if (!document.getElementById('recordBarStyles')) {
const style = document.createElement('style');
style.id = 'recordBarStyles';
style.textContent = `
@keyframes record-bar {
0% { height: 10px; opacity: 0.5; }
100% { height: 50px; opacity: 1; }
}
`;
document.head.appendChild(style);
}
// Auto-stop after 5 seconds
appState.recordingTimer = setTimeout(() => {
if (appState.isRecording) {
toggleRecording();
}
}, 5000);
} else {
// Stop recording
appState.isRecording = false;
recordBtn.textContent = '🎤 ابدأ التسجيل';
recordBtn.classList.remove('recording');
// Clear timer
if (appState.recordingTimer) {
clearTimeout(appState.recordingTimer);
appState.recordingTimer = null;
}
// Clear visualizer
visualizer.innerHTML = '';
// Show processing message then feedback
const feedback = document.getElementById('pronunciationFeedback');
if (feedback) {
feedback.innerHTML = '<p>جاري تحليل النطق...</p>';
setTimeout(() => {
showPronunciationFeedback();
}, 1500);
}
}
}
function showPronunciationFeedback() {
const feedback = document.getElementById('pronunciationFeedback');
if (!feedback) return;
const accuracy = 65 + Math.random() * 30; // Simulate accuracy between 65-95%
const roundedAccuracy = Math.round(accuracy);
let message, color;
if (roundedAccuracy >= 85) {
message = 'ممتاز! نطقك رائع 🎉';
color = '#32b8c6';
} else if (roundedAccuracy >= 70) {
message = 'جيد جداً! يمكنك تحسينه أكثر 👍';
color = '#8a2be2';
} else {
message = 'يحتاج تحسين، استمع للنطق الصحيح 🎯';
color = '#ff6b9d';
}
feedback.innerHTML = `
<h4>نتيجة النطق</h4>
<div style="font-size: 3em; color: ${color}; margin: 16px 0; text-shadow: 0 0 10px ${color}40;">
${roundedAccuracy}%
</div>
<p style="color: ${color}; font-weight: bold;">${message}</p>
<button class="btn btn--primary" onclick="initializePronunciation()">كلمة جديدة</button>
`;
// Update progress
appState.userProgress.pronunciationAccuracy = Math.round(
(appState.userProgress.pronunciationAccuracy + accuracy) / 2
);
appState.userProgress.totalPoints += Math.round(accuracy / 10);
updateProgressDisplay();
if (characterController) {
if (roundedAccuracy >= 85) {
characterController.celebrate();
} else {
characterController.encourage();
}
}
}
// Grammar Mode
function initializeGrammar() {
appState.currentGrammarIndex = 0;
showGrammarQuestion();
}
function showGrammarQuestion() {
const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
const questionElement = document.getElementById('grammarQuestion');
if (questionElement) {
questionElement.textContent = exercise.question;
}
const optionsContainer = document.getElementById('grammarOptions');
if (optionsContainer) {
optionsContainer.innerHTML = '';
exercise.options.forEach((option, index) => {
const optionBtn = document.createElement('button');
optionBtn.className = 'option-btn';
optionBtn.textContent = option;
optionBtn.onclick = () => selectGrammarOption(index);
optionsContainer.appendChild(optionBtn);
});
}
// Clear previous feedback
const feedback = document.getElementById('grammarFeedback');
if (feedback) {
feedback.innerHTML = '';
}
}
function selectGrammarOption(selectedIndex) {
const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
const options = document.querySelectorAll('.option-btn');
const feedback = document.getElementById('grammarFeedback');
if (!feedback) return;
// Disable all options
options.forEach((btn, index) => {
btn.disabled = true;
if (index === exercise.correct) {
btn.classList.add('correct');
} else if (index === selectedIndex && index !== exercise.correct) {
btn.classList.add('incorrect');
}
});
// Show feedback
const isCorrect = selectedIndex === exercise.correct;
feedback.innerHTML = `
<h4>${isCorrect ? '✅ إجابة صحيحة!' : '❌ إجابة خاطئة'}</h4>
<p>${exercise.explanation}</p>
<button class="btn btn--primary" onclick="nextGrammarQuestion()">السؤال التالي</button>
`;
// Update progress
if (isCorrect) {
appState.userProgress.grammarScore += 10;
appState.userProgress.totalPoints += 15;
updateProgressDisplay();
if (characterController) {
characterController.celebrate();
}
} else {
if (characterController) {
characterController.encourage();
}
}
}
function nextGrammarQuestion() {
appState.currentGrammarIndex = (appState.currentGrammarIndex + 1) % appData.grammar_exercises.length;
showGrammarQuestion();
}
// Scenarios Mode
function initializeScenarios() {
const buttonsContainer = document.getElementById('scenarioButtons');
if (!buttonsContainer) return;
buttonsContainer.innerHTML = '';
appData.scenarios.forEach((scenario, index) => {
const btn = document.createElement('button');
btn.className = 'scenario-btn';
btn.textContent = scenario.title;
btn.onclick = () => showScenario(index);
buttonsContainer.appendChild(btn);
});
// Show first scenario by default
if (appData.scenarios.length > 0) {
showScenario(0);
}
}
function showScenario(scenarioIndex) {
const scenario = appData.scenarios[scenarioIndex];
appState.currentScenario = scenarioIndex;
// Update button states
document.querySelectorAll('.scenario-btn').forEach((btn, index) => {
btn.classList.toggle('active', index === scenarioIndex);
});
// Show dialogue
const dialogueContainer = document.getElementById('scenarioDialogue');
if (dialogueContainer) {
dialogueContainer.innerHTML = `
<h4>${scenario.title}</h4>
<p>${scenario.description}</p>
<div class="dialogue-content">
${scenario.dialogue.map(line => `
<div class="dialogue-line ${line.speaker}">
<strong>${line.speaker === 'waiter' ? 'النادل:' :
line.speaker === 'customer' ? 'الزبون:' :
line.speaker === 'interviewer' ? 'المحاور:' : 'المرشح:'}</strong>
<p>${line.text}</p>
</div>
`).join('')}
</div>
<button class="btn btn--primary" onclick="practiceScenario()">🎤 تدرب على هذا السيناريو</button>
`;
}
}
function practiceScenario() {
if (characterController && appState.currentScenario !== null) {
const scenario = appData.scenarios[appState.currentScenario];
const firstLine = scenario.dialogue[0].text;
characterController.speak(firstLine);
}
// Show practice message
const dialogueContainer = document.getElementById('scenarioDialogue');
if (dialogueContainer) {
const practiceDiv = document.createElement('div');
practiceDiv.style.cssText = `
background: rgba(50, 184, 198, 0.1);
border: 1px solid #32b8c6;
border-radius: 12px;
padding: 16px;
margin-top: 16px;
text-align: center;
`;
practiceDiv.innerHTML = '<p>🎤 الآن دورك! تدرب على الرد باللغة الإنجليزية</p>';
dialogueContainer.appendChild(practiceDiv);
}
}
// Daily Challenge Mode
function initializeDailyChallenge() {
appState.dailyProgress = 1;
appState.selectedChallengeWords = [];
updateDailyProgress();
startChallengeTimer();
}
function updateDailyProgress() {
const progress = (appState.dailyProgress / 3) * 100;
const progressFill = document.getElementById('dailyProgress');
const progressText = document.getElementById('dailyProgressText');
if (progressFill) {
progressFill.style.width = progress + '%';
}
if (progressText) {
progressText.textContent = `${appState.dailyProgress}/3`;
}
}
function selectWord(wordElement) {
const word = wordElement.textContent;
// Toggle selection
if (wordElement.classList.contains('selected')) {
wordElement.classList.remove('selected');
appState.selectedChallengeWords = appState.selectedChallengeWords.filter(w => w !== word);
} else {
// Limit to 2 selections
if (appState.selectedChallengeWords.length < 2) {
wordElement.classList.add('selected');
appState.selectedChallengeWords.push(word);
}
}
// Check if challenge is complete
if (appState.selectedChallengeWords.length === 2) {
setTimeout(() => {
checkChallengeAnswer();
}, 500);
}
}
function checkChallengeAnswer() {
const correctWords = ['study', 'important'];
const isCorrect = correctWords.every(word => appState.selectedChallengeWords.includes(word));
if (isCorrect) {
appState.dailyProgress++;
updateDailyProgress();
if (characterController) {
characterController.celebrate();
}
// Update streak and points
appState.userProgress.streak++;
appState.userProgress.totalPoints += 20;
updateProgressDisplay();
if (appState.dailyProgress >= 3) {
setTimeout(() => {
alert('🎉 أكملت التحدي اليومي! أحسنت!');
checkAchievements();
}, 1000);
} else {
setTimeout(() => {
alert('✅ إجابة صحيحة! استمر...');
}, 500);
}
} else {
if (characterController) {
characterController.encourage();
}
setTimeout(() => {
alert('❌ حاول مرة أخرى!');
}, 500);
}
// Reset selections
document.querySelectorAll('.word-option').forEach(option => {
option.classList.remove('selected');
});
appState.selectedChallengeWords = [];
}
function startChallengeTimer() {
let timeLeft = 300; // 5 minutes in seconds
const timerElement = document.getElementById('challengeTimer');
if (!timerElement) return;
const timer = setInterval(() => {
const minutes = Math.floor(timeLeft / 60);
const seconds = timeLeft % 60;
timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
timeLeft--;
if (timeLeft < 0) {
clearInterval(timer);
alert('⏰ انتهى الوقت! حاول مرة أخرى غداً.');
}
}, 1000);
}
// Progress Tracking
function updateProgressDisplay() {
const elements = {
'streakCount': appState.userProgress.streak,
'totalPoints': appState.userProgress.totalPoints,
'wordsLearned': appState.userProgress.wordsLearned,
'conversationsCompleted': appState.userProgress.conversationsCompleted,
'pronunciationAccuracy': appState.userProgress.pronunciationAccuracy + '%',
'grammarScore': appState.userProgress.grammarScore
};
Object.entries(elements).forEach(([id, value]) => {
const element = document.getElementById(id);
if (element) {
element.textContent = value;
}
});
}
// Achievements System
function checkAchievements() {
const progress = appState.userProgress;
const achievements = [
{
id: 'first_conversation',
condition: progress.conversationsCompleted >= 1
},
{
id: 'vocabulary_master',
condition: progress.wordsLearned >= 50
},
{
id: 'pronunciation_expert',
condition: progress.pronunciationAccuracy >= 85
},
{
id: 'grammar_guru',
condition: progress.grammarScore >= 100
},
{
id: 'daily_streak',
condition: progress.streak >= 7
}
];
achievements.forEach(achievement => {
if (achievement.condition && !progress.unlockedAchievements.includes(achievement.id)) {
unlockAchievement(achievement.id);
}
});
}
function unlockAchievement(achievementId) {
const achievement = appData.achievements.find(a => a.id === achievementId);
if (!achievement) return;
appState.userProgress.unlockedAchievements.push(achievementId);
appState.userProgress.totalPoints += 50;
updateProgressDisplay();
// Show achievement notification
showAchievementNotification(achievement);
if (characterController) {
characterController.celebrate();
}
}
function showAchievementNotification(achievement) {
const notification = document.createElement('div');
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: linear-gradient(135deg, #32b8c6, #8a2be2);
color: white;
padding: 20px;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
z-index: 1000;
animation: slideIn 0.5s ease-out;
max-width: 300px;
`;
notification.innerHTML = `
<div style="font-size: 2em; margin-bottom: 8px; text-align: center;">${achievement.icon}</div>
<h4 style="margin: 0 0 8px 0; text-align: center;">إنجاز جديد!</h4>
<p style="margin: 0; font-size: 14px; text-align: center;">${achievement.name}</p>
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.remove();
}, 5000);
}
function initializeAchievements() {
const achievementsGrid = document.getElementById('achievementsGrid');
if (!achievementsGrid) return;
achievementsGrid.innerHTML = '';
appData.achievements.forEach(achievement => {
const isUnlocked = appState.userProgress.unlockedAchievements.includes(achievement.id);
const achievementCard = document.createElement('div');
achievementCard.className = `achievement-card ${isUnlocked ? 'unlocked' : ''}`;
achievementCard.innerHTML = `
<span class="achievement-icon">${achievement.icon}</span>
<div class="achievement-name">${achievement.name}</div>
<div class="achievement-description">${achievement.description}</div>
`;
achievementsGrid.appendChild(achievementCard);
});
}
// Sound Management
function toggleSound() {
appState.soundEnabled = !appState.soundEnabled;
const soundToggle = document.getElementById('soundToggle');
if (soundToggle) {
soundToggle.textContent = appState.soundEnabled ? '🔊' : '🔇';
soundToggle.title = appState.soundEnabled ? 'إيقاف الصوت' : 'تشغيل الصوت';
// Visual feedback
soundToggle.style.color = appState.soundEnabled ? '#32b8c6' : '#ff6b9d';
}
if (!appState.soundEnabled && 'speechSynthesis' in window) {
speechSynthesis.cancel();
}
// Show feedback message
const message = appState.soundEnabled ? 'تم تشغيل الصوت' : 'تم إيقاف الصوت';
showTemporaryMessage(message);
}
function showTemporaryMessage(message) {
const messageDiv = document.createElement('div');
messageDiv.style.cssText = `
position: fixed;
bottom: 100px;
right: 20px;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 16px;
border-radius: 8px;
font-size: 14px;
z-index: 1000;
animation: slideIn 0.5s ease-out;
`;
messageDiv.textContent = message;
document.body.appendChild(messageDiv);
setTimeout(() => {
messageDiv.remove();
}, 2000);
}
// Add slide-in animation CSS
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
`;
document.head.appendChild(style);
// === Conversational Voice Loop & Avatar State ===
let isMuted = false;
let isConversationActive = false;
let isAutoLoop = false;
let recognition = null;
let isSpeakingVoiceLoop = false; // حماية من التكرار
function setVoiceControlButtonsState() {
const btnStart = document.querySelector('.voice-controls .btn--primary');
const btnMute = document.querySelector('.voice-controls .btn--secondary');
const btnEnd = document.querySelector('.voice-controls .btn--danger');
if (!btnStart || !btnMute || !btnEnd) return;
btnStart.disabled = isConversationActive || isSpeakingVoiceLoop;
btnEnd.disabled = !isConversationActive && !isSpeakingVoiceLoop;
btnMute.disabled = !isConversationActive && !isSpeakingVoiceLoop;
btnMute.textContent = isMuted ? '🔇 كتم' : '🔊 كتم';
btnMute.classList.toggle('muted', isMuted);
}
function setAvatarState(state) {
if (!characterController) return;
switch(state) {
case 'idle':
characterController.setMood('happy');
break;
case 'listening':
characterController.setMood('encouraging');
break;
case 'speaking':
characterController.setMood('excited');
break;
}
}
function startVoiceLoop() {
if (isMuted || isConversationActive || isSpeakingVoiceLoop) return;
isConversationActive = true;
isAutoLoop = true;
setAvatarState('listening');
setVoiceControlButtonsState();
startListening();
}
function stopVoiceLoop() {
isConversationActive = false;
isAutoLoop = false;
if (recognition) recognition.stop();
if ('speechSynthesis' in window) speechSynthesis.cancel();
isSpeakingVoiceLoop = false;
setAvatarState('idle');
setVoiceControlButtonsState();
}
function toggleMute() {
isMuted = !isMuted;
if (isMuted) {
if (recognition) recognition.stop();
if ('speechSynthesis' in window) speechSynthesis.cancel();
setAvatarState('idle');
isConversationActive = false;
isAutoLoop = false;
isSpeakingVoiceLoop = false;
} else if (!isConversationActive && !isSpeakingVoiceLoop) {
isConversationActive = true;
isAutoLoop = true;
setAvatarState('listening');
startListening();
}
setVoiceControlButtonsState();
}
function startListening() {
if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) {
alert('Speech recognition not supported.');
return;
}
if (isSpeakingVoiceLoop) return; // لا تستمع أثناء النطق
setAvatarState('listening');
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
recognition = new SpeechRecognition();
recognition.lang = 'en-US';
recognition.continuous = false;
recognition.interimResults = false;
recognition.onresult = function(event) {
const transcript = event.results[0][0].transcript;
addMessage(transcript, 'user');
handleAIResponse(transcript);
};
recognition.onerror = function(event) {
setAvatarState('idle');
if (isAutoLoop && !isMuted && isConversationActive) {
setTimeout(startListening, 1000);
}
};
recognition.onend = function() {
// Do nothing, will be restarted after AI response
};
recognition.start();
}
function handleAIResponse(userText) {
setAvatarState('thinking');
fetch('/chat', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({message: userText})
})
.then(res => res.json())
.then(data => {
addMessage(data.reply, 'echo');
setAvatarState('speaking');
if (characterController && appState.soundEnabled) {
const utterance = new SpeechSynthesisUtterance(data.reply);
utterance.lang = 'en-US';
utterance.onend = () => {
setAvatarState('idle');
if (isAutoLoop && !isMuted && isConversationActive) {
setTimeout(startListening, 500);
}
};
speechSynthesis.speak(utterance);
characterController.speak(data.reply);
} else {
setAvatarState('idle');
if (isAutoLoop && !isMuted && isConversationActive) {
setTimeout(startListening, 500);
}
}
})
.catch(() => {
addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
setAvatarState('idle');
if (isAutoLoop && !isMuted && isConversationActive) {
setTimeout(startListening, 1500);
}
});
}
document.addEventListener('DOMContentLoaded', () => {
// Initialize character controller
characterController = new CharacterController();
// Initialize modes
initializeModes();
// Initialize achievements
initializeAchievements();
// Update progress display
updateProgressDisplay();
// Welcome message
setTimeout(() => {
if (characterController) {
characterController.speak("Welcome to Echo Light! I'm here to help you learn English in a fun and interactive way.");
}
}, 2000);
// Set up Enter key for chat
const messageInput = document.getElementById('messageInput');
if (messageInput) {
messageInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage();
}
});
}
// Check for achievements periodically
setInterval(() => {
checkAchievements();
}, 10000);
// ربط الأزرار يدويًا بعد تحميل الصفحة
const btnStart = document.querySelector('.voice-controls .btn--primary');
const btnMute = document.querySelector('.voice-controls .btn--secondary');
const btnEnd = document.querySelector('.voice-controls .btn--danger');
if (btnStart) btnStart.onclick = startVoiceLoop;
if (btnMute) btnMute.onclick = toggleMute;
if (btnEnd) btnEnd.onclick = stopVoiceLoop;
setVoiceControlButtonsState();
});
// Export functions for global access
window.showScreen = showScreen;
window.sendMessage = sendMessage;
window.startVoiceInput = startVoiceInput;
window.flipCard = flipCard;
window.nextCard = nextCard;
window.playPronunciation = playPronunciation;
window.playTargetPronunciation = playTargetPronunciation;
window.toggleRecording = toggleRecording;
window.selectGrammarOption = selectGrammarOption;
window.nextGrammarQuestion = nextGrammarQuestion;
window.showScenario = showScenario;
window.practiceScenario = practiceScenario;
window.selectWord = selectWord;
window.toggleSound = toggleSound;