Nanny7 commited on
Commit
0b5541b
·
0 Parent(s):

Echo Light initial Docker Space

Browse files
Files changed (10) hide show
  1. .dockerignore +20 -0
  2. .env +1 -0
  3. Dockerfile +25 -0
  4. ai_server.py +38 -0
  5. app.js +1373 -0
  6. development_guide.md +229 -0
  7. index.html +276 -0
  8. requirements.txt +5 -0
  9. style.css +1647 -0
  10. test_echo_light_app.py +89 -0
.dockerignore ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # .dockerignore
2
+ __pycache__/
3
+ *.pyc
4
+ *.pyo
5
+ *.pyd
6
+ .env
7
+ .venv/
8
+ build/
9
+ dist/
10
+ node_modules/
11
+ .DS_Store
12
+ *.db
13
+ *.sqlite3
14
+ selenium/
15
+ chromedriver*
16
+ *.log
17
+ *.swp
18
+ .idea/
19
+ .vscode/
20
+ .git/
.env ADDED
@@ -0,0 +1 @@
 
 
1
+ NVIDIA_API_KEY=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
Dockerfile ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Python runtime as a parent image
2
+ FROM python:3.11-slim
3
+
4
+ # Create and switch to a new user
5
+ RUN useradd -m -u 1000 user
6
+ USER user
7
+ ENV PATH="/home/user/.local/bin:$PATH"
8
+
9
+ # Set work directory
10
+ WORKDIR /app
11
+
12
+ # Copy requirements
13
+ COPY --chown=user ./requirements.txt requirements.txt
14
+
15
+ # Install Python dependencies
16
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
17
+
18
+ # Copy application code
19
+ COPY --chown=user . /app
20
+
21
+ # Expose port for Flask
22
+ EXPOSE 7860
23
+
24
+ # Start the application
25
+ CMD ["python", "ai_server.py"]
ai_server.py ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ from flask import Flask, request, jsonify
3
+ from flask_cors import CORS
4
+ import openai
5
+ from dotenv import load_dotenv
6
+
7
+ load_dotenv()
8
+
9
+ app = Flask(__name__)
10
+ CORS(app)
11
+
12
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") or os.getenv("NVIDIA_API_KEY")
13
+ openai.api_key = OPENAI_API_KEY
14
+
15
+ @app.route("/chat", methods=["POST"])
16
+ def chat():
17
+ data = request.get_json(force=True)
18
+ user_message = data.get("message", "")
19
+ if not user_message:
20
+ return jsonify({"reply": "Please say something!"})
21
+ try:
22
+ # Compatible with openai>=1.0.0
23
+ response = openai.chat.completions.create(
24
+ model="gpt-3.5-turbo", # or your NVIDIA LLM if available
25
+ messages=[{"role": "user", "content": user_message}]
26
+ )
27
+ reply = response.choices[0].message.content
28
+ return jsonify({"reply": reply})
29
+ except Exception as e:
30
+ return jsonify({"reply": f"AI error: {str(e)}"})
31
+
32
+ @app.route("/")
33
+ def root():
34
+ return {"Hello": "World!", "status": "Echo Light API is running"}
35
+
36
+ if __name__ == "__main__":
37
+ port = int(os.environ.get("PORT", 7860))
38
+ app.run(host="0.0.0.0", port=port)
app.js ADDED
@@ -0,0 +1,1373 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Application Data
2
+ const appData = {
3
+ modes: [
4
+ {
5
+ id: "conversation",
6
+ name: "المحادثة الحرة",
7
+ icon: "💬",
8
+ description: "دردشة مفتوحة مع Echo Light لممارسة المهارات اللغوية"
9
+ },
10
+ {
11
+ id: "vocabulary",
12
+ name: "بناء المفردات",
13
+ icon: "📚",
14
+ description: "نظام بطاقات تعليمية ذكي مع تكرار مباعد"
15
+ },
16
+ {
17
+ id: "pronunciation",
18
+ name: "تدريب النطق",
19
+ icon: "🎤",
20
+ description: "تقييم النطق الفوري مع تعليقات مفصلة"
21
+ },
22
+ {
23
+ id: "grammar",
24
+ name: "ألعاب القواعد",
25
+ icon: "🎯",
26
+ description: "تمارين تفاعلية لتعلم القواعد بطريقة ممتعة"
27
+ },
28
+ {
29
+ id: "scenarios",
30
+ name: "سيناريوهات واقعية",
31
+ icon: "🌍",
32
+ description: "محاكاة مواقف حياتية مثل المطاعم والمقابلات"
33
+ },
34
+ {
35
+ id: "daily",
36
+ name: "التحدي اليومي",
37
+ icon: "⭐",
38
+ description: "تمارين يومية قصيرة للحفاظ على استمرارية التعلم"
39
+ }
40
+ ],
41
+ achievements: [
42
+ {
43
+ id: "first_conversation",
44
+ name: "المحادثة الأولى",
45
+ description: "أكمل أول محادثة مع Echo Light",
46
+ icon: "🎉"
47
+ },
48
+ {
49
+ id: "vocabulary_master",
50
+ name: "خبير المفردات",
51
+ description: "تعلم 50 كلمة جديدة",
52
+ icon: "🏆"
53
+ },
54
+ {
55
+ id: "pronunciation_expert",
56
+ name: "خبير النطق",
57
+ description: "احصل على تقييم ممتاز في النطق 10 مرات",
58
+ icon: "🎯"
59
+ },
60
+ {
61
+ id: "grammar_guru",
62
+ name: "معلم القواعد",
63
+ description: "أكمل جميع تمارين القواعد بنجاح",
64
+ icon: "📖"
65
+ },
66
+ {
67
+ id: "daily_streak",
68
+ name: "المواظبة",
69
+ description: "أكمل التحدي اليومي لمدة 7 أيام متتالية",
70
+ icon: "🔥"
71
+ }
72
+ ],
73
+ vocabulary_cards: [
74
+ {
75
+ word: "Hello",
76
+ translation: "مرحبا",
77
+ pronunciation: "/həˈloʊ/",
78
+ example: "Hello, how are you?"
79
+ },
80
+ {
81
+ word: "Thank you",
82
+ translation: "شكراً لك",
83
+ pronunciation: "/θæŋk juː/",
84
+ example: "Thank you for your help."
85
+ },
86
+ {
87
+ word: "Beautiful",
88
+ translation: "جميل",
89
+ pronunciation: "/ˈbjuːtɪfəl/",
90
+ example: "The sunset is beautiful."
91
+ },
92
+ {
93
+ word: "Important",
94
+ translation: "مهم",
95
+ pronunciation: "/ɪmˈpɔːrtənt/",
96
+ example: "This is very important."
97
+ },
98
+ {
99
+ word: "Learning",
100
+ translation: "تعلم",
101
+ pronunciation: "/ˈlɜːrnɪŋ/",
102
+ example: "Learning English is fun."
103
+ }
104
+ ],
105
+ grammar_exercises: [
106
+ {
107
+ question: "Choose the correct form: I ___ to school every day.",
108
+ options: ["go", "goes", "going", "went"],
109
+ correct: 0,
110
+ explanation: "نستخدم 'go' مع الضمير 'I' في المضارع البسيط"
111
+ },
112
+ {
113
+ question: "Complete: She ___ reading a book now.",
114
+ options: ["is", "are", "was", "were"],
115
+ correct: 0,
116
+ explanation: "نستخدم 'is' مع الضمير 'She' في المضارع المستمر"
117
+ }
118
+ ],
119
+ scenarios: [
120
+ {
121
+ title: "في المطعم",
122
+ description: "تعلم كيفية طلب الطعام في المطعم",
123
+ dialogue: [
124
+ {
125
+ speaker: "waiter",
126
+ text: "Good evening! Welcome to our restaurant. How can I help you?"
127
+ },
128
+ {
129
+ speaker: "customer",
130
+ text: "Good evening! I'd like to see the menu, please."
131
+ }
132
+ ]
133
+ },
134
+ {
135
+ title: "مقابلة عمل",
136
+ description: "تدرب على أسئلة مقابلة العمل الشائعة",
137
+ dialogue: [
138
+ {
139
+ speaker: "interviewer",
140
+ text: "Tell me about yourself."
141
+ },
142
+ {
143
+ speaker: "candidate",
144
+ text: "I'm a motivated professional with experience in..."
145
+ }
146
+ ]
147
+ }
148
+ ],
149
+ echoResponses: [
150
+ "That's wonderful! Your English is getting better every day.",
151
+ "Great job! Can you tell me more about that topic?",
152
+ "Excellent! I love how you expressed that idea.",
153
+ "Perfect! Let's try using some advanced vocabulary now.",
154
+ "Amazing progress! What would you like to talk about next?",
155
+ "Fantastic! Your grammar is really improving.",
156
+ "Well done! That was a complete and clear sentence.",
157
+ "Impressive! You're becoming more confident in English.",
158
+ "Brilliant! Let's explore this topic further.",
159
+ "Outstanding! Keep up the great work!"
160
+ ]
161
+ };
162
+
163
+ // Application State
164
+ const appState = {
165
+ currentScreen: 'homeScreen',
166
+ userProgress: {
167
+ streak: 5,
168
+ totalPoints: 125,
169
+ wordsLearned: 12,
170
+ conversationsCompleted: 3,
171
+ pronunciationAccuracy: 78,
172
+ grammarScore: 85,
173
+ unlockedAchievements: ['first_conversation']
174
+ },
175
+ currentVocabIndex: 0,
176
+ currentGrammarIndex: 0,
177
+ currentScenario: null,
178
+ isRecording: false,
179
+ soundEnabled: true,
180
+ dailyProgress: 1,
181
+ selectedChallengeWords: [],
182
+ recordingTimer: null
183
+ };
184
+
185
+ // Character Animation Controller
186
+ class CharacterController {
187
+ constructor() {
188
+ this.character = document.getElementById('echoCharacter');
189
+ this.mouth = document.getElementById('characterMouth');
190
+ this.soundWaves = document.getElementById('soundWaves');
191
+ this.isAnimating = false;
192
+ this.mouthStates = ['happy', 'speaking', 'surprised', 'neutral'];
193
+ this.currentMoodIndex = 0;
194
+
195
+ this.startIdleAnimation();
196
+ }
197
+
198
+ startIdleAnimation() {
199
+ // Eye blinking animation
200
+ setInterval(() => {
201
+ this.blink();
202
+ }, 3000 + Math.random() * 2000);
203
+
204
+ // Mood changes
205
+ setInterval(() => {
206
+ this.changeMood();
207
+ }, 10000);
208
+ }
209
+
210
+ blink() {
211
+ const eyes = document.querySelectorAll('.eye');
212
+ eyes.forEach(eye => {
213
+ eye.style.transform = 'scaleY(0.1)';
214
+ setTimeout(() => {
215
+ eye.style.transform = 'scaleY(1)';
216
+ }, 150);
217
+ });
218
+ }
219
+
220
+ changeMood() {
221
+ if (this.isAnimating) return;
222
+
223
+ const moods = ['happy', 'excited', 'thinking', 'encouraging'];
224
+ const randomMood = moods[Math.floor(Math.random() * moods.length)];
225
+ this.setMood(randomMood);
226
+ }
227
+
228
+ setMood(mood) {
229
+ const character = this.character;
230
+ const mouth = this.mouth;
231
+
232
+ // Remove existing mood classes
233
+ character.classList.remove('happy', 'excited', 'thinking', 'encouraging');
234
+ character.classList.add(mood);
235
+
236
+ switch(mood) {
237
+ case 'happy':
238
+ mouth.style.borderRadius = '0 0 40px 40px';
239
+ mouth.style.width = '40px';
240
+ mouth.style.background = '#ff6b9d';
241
+ break;
242
+ case 'excited':
243
+ mouth.style.borderRadius = '50%';
244
+ mouth.style.width = '30px';
245
+ mouth.style.background = '#32b8c6';
246
+ break;
247
+ case 'thinking':
248
+ mouth.style.borderRadius = '40px 40px 0 0';
249
+ mouth.style.width = '20px';
250
+ mouth.style.background = '#8a2be2';
251
+ break;
252
+ case 'encouraging':
253
+ mouth.style.borderRadius = '0 0 50px 50px';
254
+ mouth.style.width = '50px';
255
+ mouth.style.background = '#ff6b9d';
256
+ break;
257
+ }
258
+ }
259
+
260
+ speak(text) {
261
+ this.isAnimating = true;
262
+ this.setMood('happy');
263
+
264
+ // Animate sound waves
265
+ if (this.soundWaves) {
266
+ this.soundWaves.style.display = 'block';
267
+ }
268
+
269
+ // Simulate mouth movement
270
+ let speakingInterval = setInterval(() => {
271
+ const randomWidth = 20 + Math.random() * 30;
272
+ if (this.mouth) {
273
+ this.mouth.style.width = randomWidth + 'px';
274
+ }
275
+ }, 150);
276
+
277
+ // Use Web Speech API if available and enabled
278
+ if ('speechSynthesis' in window && appState.soundEnabled) {
279
+ const utterance = new SpeechSynthesisUtterance(text);
280
+ utterance.lang = 'en-US';
281
+ utterance.rate = 0.9;
282
+ utterance.pitch = 1.1;
283
+
284
+ utterance.onend = () => {
285
+ clearInterval(speakingInterval);
286
+ this.stopSpeaking();
287
+ };
288
+
289
+ utterance.onerror = () => {
290
+ clearInterval(speakingInterval);
291
+ this.stopSpeaking();
292
+ };
293
+
294
+ speechSynthesis.speak(utterance);
295
+ } else {
296
+ // Fallback animation
297
+ setTimeout(() => {
298
+ clearInterval(speakingInterval);
299
+ this.stopSpeaking();
300
+ }, Math.min(text.length * 50, 3000));
301
+ }
302
+ }
303
+
304
+ stopSpeaking() {
305
+ this.isAnimating = false;
306
+ if (this.soundWaves) {
307
+ this.soundWaves.style.display = 'none';
308
+ }
309
+ if (this.mouth) {
310
+ this.mouth.style.width = '40px';
311
+ }
312
+ this.setMood('happy');
313
+ }
314
+
315
+ celebrate() {
316
+ this.setMood('excited');
317
+ if (this.character) {
318
+ this.character.style.animation = 'float 0.5s ease-in-out 3';
319
+
320
+ setTimeout(() => {
321
+ this.character.style.animation = 'float 3s ease-in-out infinite';
322
+ this.setMood('happy');
323
+ }, 1500);
324
+ }
325
+ }
326
+
327
+ encourage() {
328
+ this.setMood('encouraging');
329
+ setTimeout(() => {
330
+ this.setMood('happy');
331
+ }, 2000);
332
+ }
333
+ }
334
+
335
+ // Initialize character controller
336
+ let characterController;
337
+
338
+ // Screen Management
339
+ function showScreen(screenId) {
340
+ // Hide all screens
341
+ document.querySelectorAll('.screen').forEach(screen => {
342
+ screen.classList.remove('active');
343
+ });
344
+
345
+ // Show target screen
346
+ const targetScreen = document.getElementById(screenId);
347
+ if (targetScreen) {
348
+ targetScreen.classList.add('active');
349
+ appState.currentScreen = screenId;
350
+
351
+ // Update character mood based on screen
352
+ if (characterController) {
353
+ switch(screenId) {
354
+ case 'homeScreen':
355
+ characterController.setMood('happy');
356
+ break;
357
+ case 'conversationScreen':
358
+ characterController.setMood('encouraging');
359
+ break;
360
+ case 'vocabularyScreen':
361
+ case 'grammarScreen':
362
+ characterController.setMood('thinking');
363
+ break;
364
+ case 'pronunciationScreen':
365
+ characterController.setMood('excited');
366
+ break;
367
+ case 'progressScreen':
368
+ initializeAchievements();
369
+ break;
370
+ }
371
+ }
372
+ }
373
+ }
374
+
375
+ // Mode Management
376
+ function initializeModes() {
377
+ const modesGrid = document.getElementById('modesGrid');
378
+ if (!modesGrid) return;
379
+
380
+ modesGrid.innerHTML = '';
381
+
382
+ appData.modes.forEach(mode => {
383
+ const modeCard = document.createElement('div');
384
+ modeCard.className = 'mode-card';
385
+ modeCard.onclick = () => openMode(mode.id);
386
+
387
+ modeCard.innerHTML = `
388
+ <span class="mode-icon">${mode.icon}</span>
389
+ <h3 class="mode-title">${mode.name}</h3>
390
+ <p class="mode-description">${mode.description}</p>
391
+ `;
392
+
393
+ modesGrid.appendChild(modeCard);
394
+ });
395
+ }
396
+
397
+ function openMode(modeId) {
398
+ const screenMap = {
399
+ 'conversation': 'conversationScreen',
400
+ 'vocabulary': 'vocabularyScreen',
401
+ 'pronunciation': 'pronunciationScreen',
402
+ 'grammar': 'grammarScreen',
403
+ 'scenarios': 'scenariosScreen',
404
+ 'daily': 'dailyScreen'
405
+ };
406
+
407
+ const screenId = screenMap[modeId];
408
+ if (screenId) {
409
+ showScreen(screenId);
410
+
411
+ // Initialize mode-specific content
412
+ switch(modeId) {
413
+ case 'vocabulary':
414
+ initializeVocabulary();
415
+ break;
416
+ case 'grammar':
417
+ initializeGrammar();
418
+ break;
419
+ case 'scenarios':
420
+ initializeScenarios();
421
+ break;
422
+ case 'daily':
423
+ initializeDailyChallenge();
424
+ break;
425
+ case 'pronunciation':
426
+ initializePronunciation();
427
+ break;
428
+ }
429
+ }
430
+ }
431
+
432
+ // Conversation Mode
433
+ function sendMessage() {
434
+ const input = document.getElementById('messageInput');
435
+ if (!input) return;
436
+ const message = input.value.trim();
437
+ if (!message) return;
438
+ addMessage(message, 'user');
439
+ input.value = '';
440
+
441
+ // استدعاء الذكاء الاصطناعي عبر الخادم
442
+ fetch('http://localhost:5000/chat', {
443
+ method: 'POST',
444
+ headers: {'Content-Type': 'application/json'},
445
+ body: JSON.stringify({message})
446
+ })
447
+ .then(res => res.json())
448
+ .then(data => {
449
+ addMessage(data.reply, 'echo');
450
+ if (characterController) characterController.speak(data.reply);
451
+ })
452
+ .catch(() => {
453
+ addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
454
+ });
455
+
456
+ // تحديث النقاط كما هو
457
+ appState.userProgress.conversationsCompleted++;
458
+ appState.userProgress.totalPoints += 25;
459
+ updateProgressDisplay();
460
+ }
461
+
462
+ function addMessage(text, sender) {
463
+ const messagesContainer = document.getElementById('chatMessages');
464
+ if (!messagesContainer) return;
465
+
466
+ const messageDiv = document.createElement('div');
467
+ messageDiv.className = `message ${sender}`;
468
+ messageDiv.textContent = text;
469
+
470
+ messagesContainer.appendChild(messageDiv);
471
+ messagesContainer.scrollTop = messagesContainer.scrollHeight;
472
+ }
473
+
474
+ function startVoiceInput() {
475
+ if ('webkitSpeechRecognition' in window || 'SpeechRecognition' in window) {
476
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
477
+ const recognition = new SpeechRecognition();
478
+
479
+ recognition.lang = 'en-US';
480
+ recognition.continuous = false;
481
+ recognition.interimResults = false;
482
+
483
+ recognition.onresult = function(event) {
484
+ const transcript = event.results[0][0].transcript;
485
+ const input = document.getElementById('messageInput');
486
+ if (input) {
487
+ input.value = transcript;
488
+ }
489
+ };
490
+
491
+ recognition.onerror = function(event) {
492
+ console.log('Speech recognition error:', event.error);
493
+ alert('حدث خطأ في التعرف على الصوت. تأكد من السماح بالوصول للميكروفون.');
494
+ };
495
+
496
+ recognition.start();
497
+ } else {
498
+ alert('متصفحك لا يدعم تقنية التعرف على الصوت.');
499
+ }
500
+ }
501
+
502
+ // Vocabulary Mode
503
+ function initializeVocabulary() {
504
+ appState.currentVocabIndex = 0;
505
+ updateVocabularyCard();
506
+ updateVocabularyProgress();
507
+ }
508
+
509
+ function updateVocabularyCard() {
510
+ const card = appData.vocabulary_cards[appState.currentVocabIndex];
511
+
512
+ const elements = {
513
+ 'cardWord': card.word,
514
+ 'cardPronunciation': card.pronunciation,
515
+ 'cardTranslation': card.translation,
516
+ 'cardExample': card.example
517
+ };
518
+
519
+ Object.entries(elements).forEach(([id, value]) => {
520
+ const element = document.getElementById(id);
521
+ if (element) {
522
+ element.textContent = value;
523
+ }
524
+ });
525
+
526
+ // Reset card flip
527
+ const flashcard = document.getElementById('flashcard');
528
+ if (flashcard) {
529
+ flashcard.classList.remove('flipped');
530
+ }
531
+ }
532
+
533
+ function updateVocabularyProgress() {
534
+ const progress = ((appState.currentVocabIndex + 1) / appData.vocabulary_cards.length) * 100;
535
+ const progressFill = document.getElementById('vocabProgress');
536
+ const progressText = document.getElementById('vocabProgressText');
537
+
538
+ if (progressFill) {
539
+ progressFill.style.width = progress + '%';
540
+ }
541
+ if (progressText) {
542
+ progressText.textContent = `${appState.currentVocabIndex + 1}/${appData.vocabulary_cards.length}`;
543
+ }
544
+ }
545
+
546
+ function flipCard() {
547
+ const flashcard = document.getElementById('flashcard');
548
+ if (flashcard) {
549
+ flashcard.classList.toggle('flipped');
550
+ }
551
+ }
552
+
553
+ function nextCard() {
554
+ appState.currentVocabIndex = (appState.currentVocabIndex + 1) % appData.vocabulary_cards.length;
555
+ updateVocabularyCard();
556
+ updateVocabularyProgress();
557
+
558
+ // Update progress
559
+ appState.userProgress.wordsLearned++;
560
+ appState.userProgress.totalPoints += 10;
561
+ updateProgressDisplay();
562
+
563
+ if (characterController) {
564
+ characterController.encourage();
565
+ }
566
+ }
567
+
568
+ function playPronunciation() {
569
+ const card = appData.vocabulary_cards[appState.currentVocabIndex];
570
+ if (characterController && appState.soundEnabled) {
571
+ characterController.speak(card.word);
572
+ }
573
+ }
574
+
575
+ // Pronunciation Mode
576
+ function initializePronunciation() {
577
+ const words = appData.vocabulary_cards;
578
+ const randomWord = words[Math.floor(Math.random() * words.length)];
579
+
580
+ const practiceWord = document.getElementById('practiceWord');
581
+ const practicePronunciation = document.getElementById('practicePronunciation');
582
+
583
+ if (practiceWord) practiceWord.textContent = randomWord.word;
584
+ if (practicePronunciation) practicePronunciation.textContent = randomWord.pronunciation;
585
+
586
+ // Clear previous feedback
587
+ const feedback = document.getElementById('pronunciationFeedback');
588
+ if (feedback) {
589
+ feedback.innerHTML = '';
590
+ }
591
+ }
592
+
593
+ function playTargetPronunciation() {
594
+ const word = document.getElementById('practiceWord');
595
+ if (word && characterController && appState.soundEnabled) {
596
+ characterController.speak(word.textContent);
597
+ }
598
+ }
599
+
600
+ function toggleRecording() {
601
+ const recordBtn = document.getElementById('recordBtn');
602
+ const visualizer = document.getElementById('recordVisualizer');
603
+
604
+ if (!recordBtn || !visualizer) return;
605
+
606
+ if (!appState.isRecording) {
607
+ // Start recording
608
+ appState.isRecording = true;
609
+ recordBtn.textContent = '⏹️ إيقاف التسجيل';
610
+ recordBtn.classList.add('recording');
611
+
612
+ // Clear previous content
613
+ visualizer.innerHTML = '';
614
+
615
+ // Create recording visualization
616
+ const bars = [];
617
+ for (let i = 0; i < 10; i++) {
618
+ const bar = document.createElement('div');
619
+ bar.style.cssText = `
620
+ width: 15px;
621
+ background: #32b8c6;
622
+ margin: 2px;
623
+ border-radius: 2px;
624
+ display: inline-block;
625
+ height: 20px;
626
+ animation: record-bar 0.8s infinite alternate;
627
+ animation-delay: ${i * 0.1}s;
628
+ `;
629
+ bars.push(bar);
630
+ visualizer.appendChild(bar);
631
+ }
632
+
633
+ // Add CSS for bars animation if not exists
634
+ if (!document.getElementById('recordBarStyles')) {
635
+ const style = document.createElement('style');
636
+ style.id = 'recordBarStyles';
637
+ style.textContent = `
638
+ @keyframes record-bar {
639
+ 0% { height: 10px; opacity: 0.5; }
640
+ 100% { height: 50px; opacity: 1; }
641
+ }
642
+ `;
643
+ document.head.appendChild(style);
644
+ }
645
+
646
+ // Auto-stop after 5 seconds
647
+ appState.recordingTimer = setTimeout(() => {
648
+ if (appState.isRecording) {
649
+ toggleRecording();
650
+ }
651
+ }, 5000);
652
+
653
+ } else {
654
+ // Stop recording
655
+ appState.isRecording = false;
656
+ recordBtn.textContent = '🎤 ابدأ التسجيل';
657
+ recordBtn.classList.remove('recording');
658
+
659
+ // Clear timer
660
+ if (appState.recordingTimer) {
661
+ clearTimeout(appState.recordingTimer);
662
+ appState.recordingTimer = null;
663
+ }
664
+
665
+ // Clear visualizer
666
+ visualizer.innerHTML = '';
667
+
668
+ // Show processing message then feedback
669
+ const feedback = document.getElementById('pronunciationFeedback');
670
+ if (feedback) {
671
+ feedback.innerHTML = '<p>جاري تحليل النطق...</p>';
672
+
673
+ setTimeout(() => {
674
+ showPronunciationFeedback();
675
+ }, 1500);
676
+ }
677
+ }
678
+ }
679
+
680
+ function showPronunciationFeedback() {
681
+ const feedback = document.getElementById('pronunciationFeedback');
682
+ if (!feedback) return;
683
+
684
+ const accuracy = 65 + Math.random() * 30; // Simulate accuracy between 65-95%
685
+ const roundedAccuracy = Math.round(accuracy);
686
+
687
+ let message, color;
688
+ if (roundedAccuracy >= 85) {
689
+ message = 'ممتاز! نطقك رائع 🎉';
690
+ color = '#32b8c6';
691
+ } else if (roundedAccuracy >= 70) {
692
+ message = 'جيد جداً! يمكنك تحسينه أكثر 👍';
693
+ color = '#8a2be2';
694
+ } else {
695
+ message = 'يحتاج تحسين، استمع للنطق الصحيح 🎯';
696
+ color = '#ff6b9d';
697
+ }
698
+
699
+ feedback.innerHTML = `
700
+ <h4>نتيجة النطق</h4>
701
+ <div style="font-size: 3em; color: ${color}; margin: 16px 0; text-shadow: 0 0 10px ${color}40;">
702
+ ${roundedAccuracy}%
703
+ </div>
704
+ <p style="color: ${color}; font-weight: bold;">${message}</p>
705
+ <button class="btn btn--primary" onclick="initializePronunciation()">كلمة جديدة</button>
706
+ `;
707
+
708
+ // Update progress
709
+ appState.userProgress.pronunciationAccuracy = Math.round(
710
+ (appState.userProgress.pronunciationAccuracy + accuracy) / 2
711
+ );
712
+ appState.userProgress.totalPoints += Math.round(accuracy / 10);
713
+ updateProgressDisplay();
714
+
715
+ if (characterController) {
716
+ if (roundedAccuracy >= 85) {
717
+ characterController.celebrate();
718
+ } else {
719
+ characterController.encourage();
720
+ }
721
+ }
722
+ }
723
+
724
+ // Grammar Mode
725
+ function initializeGrammar() {
726
+ appState.currentGrammarIndex = 0;
727
+ showGrammarQuestion();
728
+ }
729
+
730
+ function showGrammarQuestion() {
731
+ const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
732
+
733
+ const questionElement = document.getElementById('grammarQuestion');
734
+ if (questionElement) {
735
+ questionElement.textContent = exercise.question;
736
+ }
737
+
738
+ const optionsContainer = document.getElementById('grammarOptions');
739
+ if (optionsContainer) {
740
+ optionsContainer.innerHTML = '';
741
+
742
+ exercise.options.forEach((option, index) => {
743
+ const optionBtn = document.createElement('button');
744
+ optionBtn.className = 'option-btn';
745
+ optionBtn.textContent = option;
746
+ optionBtn.onclick = () => selectGrammarOption(index);
747
+
748
+ optionsContainer.appendChild(optionBtn);
749
+ });
750
+ }
751
+
752
+ // Clear previous feedback
753
+ const feedback = document.getElementById('grammarFeedback');
754
+ if (feedback) {
755
+ feedback.innerHTML = '';
756
+ }
757
+ }
758
+
759
+ function selectGrammarOption(selectedIndex) {
760
+ const exercise = appData.grammar_exercises[appState.currentGrammarIndex];
761
+ const options = document.querySelectorAll('.option-btn');
762
+ const feedback = document.getElementById('grammarFeedback');
763
+
764
+ if (!feedback) return;
765
+
766
+ // Disable all options
767
+ options.forEach((btn, index) => {
768
+ btn.disabled = true;
769
+ if (index === exercise.correct) {
770
+ btn.classList.add('correct');
771
+ } else if (index === selectedIndex && index !== exercise.correct) {
772
+ btn.classList.add('incorrect');
773
+ }
774
+ });
775
+
776
+ // Show feedback
777
+ const isCorrect = selectedIndex === exercise.correct;
778
+ feedback.innerHTML = `
779
+ <h4>${isCorrect ? '✅ إجابة صحيحة!' : '❌ إجابة خاطئة'}</h4>
780
+ <p>${exercise.explanation}</p>
781
+ <button class="btn btn--primary" onclick="nextGrammarQuestion()">السؤال التالي</button>
782
+ `;
783
+
784
+ // Update progress
785
+ if (isCorrect) {
786
+ appState.userProgress.grammarScore += 10;
787
+ appState.userProgress.totalPoints += 15;
788
+ updateProgressDisplay();
789
+
790
+ if (characterController) {
791
+ characterController.celebrate();
792
+ }
793
+ } else {
794
+ if (characterController) {
795
+ characterController.encourage();
796
+ }
797
+ }
798
+ }
799
+
800
+ function nextGrammarQuestion() {
801
+ appState.currentGrammarIndex = (appState.currentGrammarIndex + 1) % appData.grammar_exercises.length;
802
+ showGrammarQuestion();
803
+ }
804
+
805
+ // Scenarios Mode
806
+ function initializeScenarios() {
807
+ const buttonsContainer = document.getElementById('scenarioButtons');
808
+ if (!buttonsContainer) return;
809
+
810
+ buttonsContainer.innerHTML = '';
811
+
812
+ appData.scenarios.forEach((scenario, index) => {
813
+ const btn = document.createElement('button');
814
+ btn.className = 'scenario-btn';
815
+ btn.textContent = scenario.title;
816
+ btn.onclick = () => showScenario(index);
817
+
818
+ buttonsContainer.appendChild(btn);
819
+ });
820
+
821
+ // Show first scenario by default
822
+ if (appData.scenarios.length > 0) {
823
+ showScenario(0);
824
+ }
825
+ }
826
+
827
+ function showScenario(scenarioIndex) {
828
+ const scenario = appData.scenarios[scenarioIndex];
829
+ appState.currentScenario = scenarioIndex;
830
+
831
+ // Update button states
832
+ document.querySelectorAll('.scenario-btn').forEach((btn, index) => {
833
+ btn.classList.toggle('active', index === scenarioIndex);
834
+ });
835
+
836
+ // Show dialogue
837
+ const dialogueContainer = document.getElementById('scenarioDialogue');
838
+ if (dialogueContainer) {
839
+ dialogueContainer.innerHTML = `
840
+ <h4>${scenario.title}</h4>
841
+ <p>${scenario.description}</p>
842
+ <div class="dialogue-content">
843
+ ${scenario.dialogue.map(line => `
844
+ <div class="dialogue-line ${line.speaker}">
845
+ <strong>${line.speaker === 'waiter' ? 'النادل:' :
846
+ line.speaker === 'customer' ? 'الزبون:' :
847
+ line.speaker === 'interviewer' ? 'المحاور:' : 'المرشح:'}</strong>
848
+ <p>${line.text}</p>
849
+ </div>
850
+ `).join('')}
851
+ </div>
852
+ <button class="btn btn--primary" onclick="practiceScenario()">🎤 تدرب على هذا السيناريو</button>
853
+ `;
854
+ }
855
+ }
856
+
857
+ function practiceScenario() {
858
+ if (characterController && appState.currentScenario !== null) {
859
+ const scenario = appData.scenarios[appState.currentScenario];
860
+ const firstLine = scenario.dialogue[0].text;
861
+ characterController.speak(firstLine);
862
+ }
863
+
864
+ // Show practice message
865
+ const dialogueContainer = document.getElementById('scenarioDialogue');
866
+ if (dialogueContainer) {
867
+ const practiceDiv = document.createElement('div');
868
+ practiceDiv.style.cssText = `
869
+ background: rgba(50, 184, 198, 0.1);
870
+ border: 1px solid #32b8c6;
871
+ border-radius: 12px;
872
+ padding: 16px;
873
+ margin-top: 16px;
874
+ text-align: center;
875
+ `;
876
+ practiceDiv.innerHTML = '<p>🎤 الآن دورك! تدرب على الرد باللغة الإنجليزية</p>';
877
+ dialogueContainer.appendChild(practiceDiv);
878
+ }
879
+ }
880
+
881
+ // Daily Challenge Mode
882
+ function initializeDailyChallenge() {
883
+ appState.dailyProgress = 1;
884
+ appState.selectedChallengeWords = [];
885
+ updateDailyProgress();
886
+ startChallengeTimer();
887
+ }
888
+
889
+ function updateDailyProgress() {
890
+ const progress = (appState.dailyProgress / 3) * 100;
891
+ const progressFill = document.getElementById('dailyProgress');
892
+ const progressText = document.getElementById('dailyProgressText');
893
+
894
+ if (progressFill) {
895
+ progressFill.style.width = progress + '%';
896
+ }
897
+ if (progressText) {
898
+ progressText.textContent = `${appState.dailyProgress}/3`;
899
+ }
900
+ }
901
+
902
+ function selectWord(wordElement) {
903
+ const word = wordElement.textContent;
904
+
905
+ // Toggle selection
906
+ if (wordElement.classList.contains('selected')) {
907
+ wordElement.classList.remove('selected');
908
+ appState.selectedChallengeWords = appState.selectedChallengeWords.filter(w => w !== word);
909
+ } else {
910
+ // Limit to 2 selections
911
+ if (appState.selectedChallengeWords.length < 2) {
912
+ wordElement.classList.add('selected');
913
+ appState.selectedChallengeWords.push(word);
914
+ }
915
+ }
916
+
917
+ // Check if challenge is complete
918
+ if (appState.selectedChallengeWords.length === 2) {
919
+ setTimeout(() => {
920
+ checkChallengeAnswer();
921
+ }, 500);
922
+ }
923
+ }
924
+
925
+ function checkChallengeAnswer() {
926
+ const correctWords = ['study', 'important'];
927
+ const isCorrect = correctWords.every(word => appState.selectedChallengeWords.includes(word));
928
+
929
+ if (isCorrect) {
930
+ appState.dailyProgress++;
931
+ updateDailyProgress();
932
+
933
+ if (characterController) {
934
+ characterController.celebrate();
935
+ }
936
+
937
+ // Update streak and points
938
+ appState.userProgress.streak++;
939
+ appState.userProgress.totalPoints += 20;
940
+ updateProgressDisplay();
941
+
942
+ if (appState.dailyProgress >= 3) {
943
+ setTimeout(() => {
944
+ alert('🎉 أكملت التحدي اليومي! أحسنت!');
945
+ checkAchievements();
946
+ }, 1000);
947
+ } else {
948
+ setTimeout(() => {
949
+ alert('✅ إجابة صحيحة! استمر...');
950
+ }, 500);
951
+ }
952
+ } else {
953
+ if (characterController) {
954
+ characterController.encourage();
955
+ }
956
+ setTimeout(() => {
957
+ alert('❌ حاول مرة أخرى!');
958
+ }, 500);
959
+ }
960
+
961
+ // Reset selections
962
+ document.querySelectorAll('.word-option').forEach(option => {
963
+ option.classList.remove('selected');
964
+ });
965
+ appState.selectedChallengeWords = [];
966
+ }
967
+
968
+ function startChallengeTimer() {
969
+ let timeLeft = 300; // 5 minutes in seconds
970
+ const timerElement = document.getElementById('challengeTimer');
971
+ if (!timerElement) return;
972
+
973
+ const timer = setInterval(() => {
974
+ const minutes = Math.floor(timeLeft / 60);
975
+ const seconds = timeLeft % 60;
976
+ timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
977
+
978
+ timeLeft--;
979
+
980
+ if (timeLeft < 0) {
981
+ clearInterval(timer);
982
+ alert('⏰ انتهى الوقت! حاول مرة أخرى غداً.');
983
+ }
984
+ }, 1000);
985
+ }
986
+
987
+ // Progress Tracking
988
+ function updateProgressDisplay() {
989
+ const elements = {
990
+ 'streakCount': appState.userProgress.streak,
991
+ 'totalPoints': appState.userProgress.totalPoints,
992
+ 'wordsLearned': appState.userProgress.wordsLearned,
993
+ 'conversationsCompleted': appState.userProgress.conversationsCompleted,
994
+ 'pronunciationAccuracy': appState.userProgress.pronunciationAccuracy + '%',
995
+ 'grammarScore': appState.userProgress.grammarScore
996
+ };
997
+
998
+ Object.entries(elements).forEach(([id, value]) => {
999
+ const element = document.getElementById(id);
1000
+ if (element) {
1001
+ element.textContent = value;
1002
+ }
1003
+ });
1004
+ }
1005
+
1006
+ // Achievements System
1007
+ function checkAchievements() {
1008
+ const progress = appState.userProgress;
1009
+ const achievements = [
1010
+ {
1011
+ id: 'first_conversation',
1012
+ condition: progress.conversationsCompleted >= 1
1013
+ },
1014
+ {
1015
+ id: 'vocabulary_master',
1016
+ condition: progress.wordsLearned >= 50
1017
+ },
1018
+ {
1019
+ id: 'pronunciation_expert',
1020
+ condition: progress.pronunciationAccuracy >= 85
1021
+ },
1022
+ {
1023
+ id: 'grammar_guru',
1024
+ condition: progress.grammarScore >= 100
1025
+ },
1026
+ {
1027
+ id: 'daily_streak',
1028
+ condition: progress.streak >= 7
1029
+ }
1030
+ ];
1031
+
1032
+ achievements.forEach(achievement => {
1033
+ if (achievement.condition && !progress.unlockedAchievements.includes(achievement.id)) {
1034
+ unlockAchievement(achievement.id);
1035
+ }
1036
+ });
1037
+ }
1038
+
1039
+ function unlockAchievement(achievementId) {
1040
+ const achievement = appData.achievements.find(a => a.id === achievementId);
1041
+ if (!achievement) return;
1042
+
1043
+ appState.userProgress.unlockedAchievements.push(achievementId);
1044
+ appState.userProgress.totalPoints += 50;
1045
+ updateProgressDisplay();
1046
+
1047
+ // Show achievement notification
1048
+ showAchievementNotification(achievement);
1049
+
1050
+ if (characterController) {
1051
+ characterController.celebrate();
1052
+ }
1053
+ }
1054
+
1055
+ function showAchievementNotification(achievement) {
1056
+ const notification = document.createElement('div');
1057
+ notification.style.cssText = `
1058
+ position: fixed;
1059
+ top: 20px;
1060
+ right: 20px;
1061
+ background: linear-gradient(135deg, #32b8c6, #8a2be2);
1062
+ color: white;
1063
+ padding: 20px;
1064
+ border-radius: 12px;
1065
+ box-shadow: 0 10px 30px rgba(0,0,0,0.3);
1066
+ z-index: 1000;
1067
+ animation: slideIn 0.5s ease-out;
1068
+ max-width: 300px;
1069
+ `;
1070
+ notification.innerHTML = `
1071
+ <div style="font-size: 2em; margin-bottom: 8px; text-align: center;">${achievement.icon}</div>
1072
+ <h4 style="margin: 0 0 8px 0; text-align: center;">إنجاز جديد!</h4>
1073
+ <p style="margin: 0; font-size: 14px; text-align: center;">${achievement.name}</p>
1074
+ `;
1075
+
1076
+ document.body.appendChild(notification);
1077
+
1078
+ setTimeout(() => {
1079
+ notification.remove();
1080
+ }, 5000);
1081
+ }
1082
+
1083
+ function initializeAchievements() {
1084
+ const achievementsGrid = document.getElementById('achievementsGrid');
1085
+ if (!achievementsGrid) return;
1086
+
1087
+ achievementsGrid.innerHTML = '';
1088
+
1089
+ appData.achievements.forEach(achievement => {
1090
+ const isUnlocked = appState.userProgress.unlockedAchievements.includes(achievement.id);
1091
+
1092
+ const achievementCard = document.createElement('div');
1093
+ achievementCard.className = `achievement-card ${isUnlocked ? 'unlocked' : ''}`;
1094
+ achievementCard.innerHTML = `
1095
+ <span class="achievement-icon">${achievement.icon}</span>
1096
+ <div class="achievement-name">${achievement.name}</div>
1097
+ <div class="achievement-description">${achievement.description}</div>
1098
+ `;
1099
+
1100
+ achievementsGrid.appendChild(achievementCard);
1101
+ });
1102
+ }
1103
+
1104
+ // Sound Management
1105
+ function toggleSound() {
1106
+ appState.soundEnabled = !appState.soundEnabled;
1107
+ const soundToggle = document.getElementById('soundToggle');
1108
+
1109
+ if (soundToggle) {
1110
+ soundToggle.textContent = appState.soundEnabled ? '🔊' : '🔇';
1111
+ soundToggle.title = appState.soundEnabled ? 'إيقاف الصوت' : 'تشغيل الصوت';
1112
+
1113
+ // Visual feedback
1114
+ soundToggle.style.color = appState.soundEnabled ? '#32b8c6' : '#ff6b9d';
1115
+ }
1116
+
1117
+ if (!appState.soundEnabled && 'speechSynthesis' in window) {
1118
+ speechSynthesis.cancel();
1119
+ }
1120
+
1121
+ // Show feedback message
1122
+ const message = appState.soundEnabled ? 'تم تشغيل الصوت' : 'تم إيقاف الصوت';
1123
+ showTemporaryMessage(message);
1124
+ }
1125
+
1126
+ function showTemporaryMessage(message) {
1127
+ const messageDiv = document.createElement('div');
1128
+ messageDiv.style.cssText = `
1129
+ position: fixed;
1130
+ bottom: 100px;
1131
+ right: 20px;
1132
+ background: rgba(0, 0, 0, 0.8);
1133
+ color: white;
1134
+ padding: 12px 16px;
1135
+ border-radius: 8px;
1136
+ font-size: 14px;
1137
+ z-index: 1000;
1138
+ animation: slideIn 0.5s ease-out;
1139
+ `;
1140
+ messageDiv.textContent = message;
1141
+
1142
+ document.body.appendChild(messageDiv);
1143
+
1144
+ setTimeout(() => {
1145
+ messageDiv.remove();
1146
+ }, 2000);
1147
+ }
1148
+
1149
+ // Add slide-in animation CSS
1150
+ const style = document.createElement('style');
1151
+ style.textContent = `
1152
+ @keyframes slideIn {
1153
+ from { transform: translateX(100%); opacity: 0; }
1154
+ to { transform: translateX(0); opacity: 1; }
1155
+ }
1156
+ `;
1157
+ document.head.appendChild(style);
1158
+
1159
+ // === Conversational Voice Loop & Avatar State ===
1160
+ let isMuted = false;
1161
+ let isConversationActive = false;
1162
+ let isAutoLoop = false;
1163
+ let recognition = null;
1164
+ let isSpeakingVoiceLoop = false; // حماية من التكرار
1165
+
1166
+ function setVoiceControlButtonsState() {
1167
+ const btnStart = document.querySelector('.voice-controls .btn--primary');
1168
+ const btnMute = document.querySelector('.voice-controls .btn--secondary');
1169
+ const btnEnd = document.querySelector('.voice-controls .btn--danger');
1170
+ if (!btnStart || !btnMute || !btnEnd) return;
1171
+ btnStart.disabled = isConversationActive || isSpeakingVoiceLoop;
1172
+ btnEnd.disabled = !isConversationActive && !isSpeakingVoiceLoop;
1173
+ btnMute.disabled = !isConversationActive && !isSpeakingVoiceLoop;
1174
+ btnMute.textContent = isMuted ? '🔇 كتم' : '🔊 كتم';
1175
+ btnMute.classList.toggle('muted', isMuted);
1176
+ }
1177
+
1178
+ function setAvatarState(state) {
1179
+ if (!characterController) return;
1180
+ switch(state) {
1181
+ case 'idle':
1182
+ characterController.setMood('happy');
1183
+ break;
1184
+ case 'listening':
1185
+ characterController.setMood('encouraging');
1186
+ break;
1187
+ case 'speaking':
1188
+ characterController.setMood('excited');
1189
+ break;
1190
+ }
1191
+ }
1192
+
1193
+ function startVoiceLoop() {
1194
+ if (isMuted || isConversationActive || isSpeakingVoiceLoop) return;
1195
+ isConversationActive = true;
1196
+ isAutoLoop = true;
1197
+ setAvatarState('listening');
1198
+ setVoiceControlButtonsState();
1199
+ startListening();
1200
+ }
1201
+
1202
+ function stopVoiceLoop() {
1203
+ isConversationActive = false;
1204
+ isAutoLoop = false;
1205
+ if (recognition) recognition.stop();
1206
+ if ('speechSynthesis' in window) speechSynthesis.cancel();
1207
+ isSpeakingVoiceLoop = false;
1208
+ setAvatarState('idle');
1209
+ setVoiceControlButtonsState();
1210
+ }
1211
+
1212
+ function toggleMute() {
1213
+ isMuted = !isMuted;
1214
+ if (isMuted) {
1215
+ if (recognition) recognition.stop();
1216
+ if ('speechSynthesis' in window) speechSynthesis.cancel();
1217
+ setAvatarState('idle');
1218
+ isConversationActive = false;
1219
+ isAutoLoop = false;
1220
+ isSpeakingVoiceLoop = false;
1221
+ } else if (!isConversationActive && !isSpeakingVoiceLoop) {
1222
+ isConversationActive = true;
1223
+ isAutoLoop = true;
1224
+ setAvatarState('listening');
1225
+ startListening();
1226
+ }
1227
+ setVoiceControlButtonsState();
1228
+ }
1229
+
1230
+ function startListening() {
1231
+ if (!('webkitSpeechRecognition' in window || 'SpeechRecognition' in window)) {
1232
+ alert('Speech recognition not supported.');
1233
+ return;
1234
+ }
1235
+ if (isSpeakingVoiceLoop) return; // لا تستمع أثناء النطق
1236
+ setAvatarState('listening');
1237
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
1238
+ recognition = new SpeechRecognition();
1239
+ recognition.lang = 'en-US';
1240
+ recognition.continuous = false;
1241
+ recognition.interimResults = false;
1242
+ recognition.onresult = function(event) {
1243
+ const transcript = event.results[0][0].transcript;
1244
+ addMessage(transcript, 'user');
1245
+ handleAIResponse(transcript);
1246
+ };
1247
+ recognition.onerror = function(event) {
1248
+ setAvatarState('idle');
1249
+ if (isAutoLoop && !isMuted && isConversationActive) {
1250
+ setTimeout(startListening, 1000);
1251
+ }
1252
+ };
1253
+ recognition.onend = function() {
1254
+ // Do nothing, will be restarted after AI response
1255
+ };
1256
+ recognition.start();
1257
+ }
1258
+
1259
+ function handleAIResponse(userText) {
1260
+ setAvatarState('thinking');
1261
+ isSpeakingVoiceLoop = true;
1262
+ setVoiceControlButtonsState();
1263
+ fetch('http://127.0.0.1:5000/chat', {
1264
+ method: 'POST',
1265
+ headers: {'Content-Type': 'application/json'},
1266
+ body: JSON.stringify({message: userText})
1267
+ })
1268
+ .then(res => res.json())
1269
+ .then(data => {
1270
+ addMessage(data.reply, 'echo');
1271
+ setAvatarState('speaking');
1272
+ if (characterController && appState.soundEnabled) {
1273
+ // استخدم SpeechSynthesisUtterance مع حدث onend
1274
+ const utterance = new SpeechSynthesisUtterance(data.reply);
1275
+ utterance.lang = 'en-US';
1276
+ utterance.onend = () => {
1277
+ isSpeakingVoiceLoop = false;
1278
+ setAvatarState('idle');
1279
+ setVoiceControlButtonsState();
1280
+ if (isAutoLoop && !isMuted && isConversationActive) {
1281
+ setTimeout(startListening, 500);
1282
+ }
1283
+ };
1284
+ utterance.onerror = () => {
1285
+ isSpeakingVoiceLoop = false;
1286
+ setAvatarState('idle');
1287
+ setVoiceControlButtonsState();
1288
+ if (isAutoLoop && !isMuted && isConversationActive) {
1289
+ setTimeout(startListening, 500);
1290
+ }
1291
+ };
1292
+ speechSynthesis.speak(utterance);
1293
+ characterController.speak(data.reply); // لتحريك الفم
1294
+ } else {
1295
+ isSpeakingVoiceLoop = false;
1296
+ setAvatarState('idle');
1297
+ setVoiceControlButtonsState();
1298
+ if (isAutoLoop && !isMuted && isConversationActive) {
1299
+ setTimeout(startListening, 500);
1300
+ }
1301
+ }
1302
+ })
1303
+ .catch(() => {
1304
+ addMessage("Sorry, I couldn't connect to the AI server.", 'echo');
1305
+ isSpeakingVoiceLoop = false;
1306
+ setAvatarState('idle');
1307
+ setVoiceControlButtonsState();
1308
+ if (isAutoLoop && !isMuted && isConversationActive) {
1309
+ setTimeout(startListening, 1500);
1310
+ }
1311
+ });
1312
+ }
1313
+
1314
+ document.addEventListener('DOMContentLoaded', () => {
1315
+ // Initialize character controller
1316
+ characterController = new CharacterController();
1317
+
1318
+ // Initialize modes
1319
+ initializeModes();
1320
+
1321
+ // Initialize achievements
1322
+ initializeAchievements();
1323
+
1324
+ // Update progress display
1325
+ updateProgressDisplay();
1326
+
1327
+ // Welcome message
1328
+ setTimeout(() => {
1329
+ if (characterController) {
1330
+ characterController.speak("Welcome to Echo Light! I'm here to help you learn English in a fun and interactive way.");
1331
+ }
1332
+ }, 2000);
1333
+
1334
+ // Set up Enter key for chat
1335
+ const messageInput = document.getElementById('messageInput');
1336
+ if (messageInput) {
1337
+ messageInput.addEventListener('keypress', (e) => {
1338
+ if (e.key === 'Enter') {
1339
+ sendMessage();
1340
+ }
1341
+ });
1342
+ }
1343
+
1344
+ // Check for achievements periodically
1345
+ setInterval(() => {
1346
+ checkAchievements();
1347
+ }, 10000);
1348
+
1349
+ // ربط الأزرار يدويًا بعد تحميل الصفحة
1350
+ const btnStart = document.querySelector('.voice-controls .btn--primary');
1351
+ const btnMute = document.querySelector('.voice-controls .btn--secondary');
1352
+ const btnEnd = document.querySelector('.voice-controls .btn--danger');
1353
+ if (btnStart) btnStart.onclick = startVoiceLoop;
1354
+ if (btnMute) btnMute.onclick = toggleMute;
1355
+ if (btnEnd) btnEnd.onclick = stopVoiceLoop;
1356
+ setVoiceControlButtonsState();
1357
+ });
1358
+
1359
+ // Export functions for global access
1360
+ window.showScreen = showScreen;
1361
+ window.sendMessage = sendMessage;
1362
+ window.startVoiceInput = startVoiceInput;
1363
+ window.flipCard = flipCard;
1364
+ window.nextCard = nextCard;
1365
+ window.playPronunciation = playPronunciation;
1366
+ window.playTargetPronunciation = playTargetPronunciation;
1367
+ window.toggleRecording = toggleRecording;
1368
+ window.selectGrammarOption = selectGrammarOption;
1369
+ window.nextGrammarQuestion = nextGrammarQuestion;
1370
+ window.showScenario = showScenario;
1371
+ window.practiceScenario = practiceScenario;
1372
+ window.selectWord = selectWord;
1373
+ window.toggleSound = toggleSound;
development_guide.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # دليل المطور لتطبيق Echo Light المتقدم
2
+
3
+ ## نظرة عامة على البنية التقنية
4
+
5
+ ### 1. شخصية Echo Light التفاعلية
6
+
7
+ #### الحركة العائمة المنتظمة
8
+ ```css
9
+ @keyframes float {
10
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
11
+ 50% { transform: translateY(-20px) rotate(2deg); }
12
+ }
13
+
14
+ .echo-character {
15
+ animation: float 4s ease-in-out infinite;
16
+ will-change: transform;
17
+ }
18
+ ```
19
+
20
+ #### نظام الحالات التفاعلية
21
+ ```javascript
22
+ const characterStates = {
23
+ idle: { animation: 'float', expression: 'neutral' },
24
+ listening: { animation: 'float-focus', expression: 'attentive' },
25
+ speaking: { animation: 'float-talk', expression: 'animated' },
26
+ thinking: { animation: 'float-think', expression: 'thoughtful' }
27
+ };
28
+ ```
29
+
30
+ ### 2. تقنيات تحسين الأداء للهواتف
31
+
32
+ #### CSS Transforms للحركات السلسة
33
+ - استخدام `transform` بدلاً من `left/top`
34
+ - تفعيل `will-change` للعناصر المتحركة
35
+ - استخدام `translate3d` لتفعيل تسريع GPU
36
+
37
+ #### إدارة الذاكرة
38
+ ```javascript
39
+ // تحسين استهلاك الذاكرة
40
+ const performanceOptimizer = {
41
+ reducedMotion: window.matchMedia('(prefers-reduced-motion: reduce)').matches,
42
+ lowPowerMode: navigator.hardwareConcurrency <= 2,
43
+
44
+ adaptAnimations() {
45
+ if (this.reducedMotion || this.lowPowerMode) {
46
+ document.body.classList.add('reduced-motion');
47
+ }
48
+ }
49
+ };
50
+ ```
51
+
52
+ ### 3. نظام التعرف على الصوت المتقدم
53
+
54
+ #### تكامل Web Speech API
55
+ ```javascript
56
+ class VoiceRecognition {
57
+ constructor() {
58
+ this.recognition = new webkitSpeechRecognition();
59
+ this.recognition.continuous = true;
60
+ this.recognition.interimResults = true;
61
+ this.recognition.lang = 'en-US';
62
+ }
63
+
64
+ startListening() {
65
+ this.recognition.start();
66
+ this.updateCharacterState('listening');
67
+ }
68
+
69
+ assessPronunciation(text, audio) {
70
+ // تقييم النطق باستخدام خوارزميات متقدمة
71
+ return this.calculateAccuracyScore(text, audio);
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### 4. خوارزمية التكرار المباعد
77
+
78
+ #### تنفيذ نظام SM-2 المحسن
79
+ ```javascript
80
+ class SpacedRepetition {
81
+ calculateNextReview(quality, interval, easeFactor) {
82
+ if (quality >= 3) {
83
+ if (interval === 0) {
84
+ interval = 1;
85
+ } else if (interval === 1) {
86
+ interval = 6;
87
+ } else {
88
+ interval = Math.round(interval * easeFactor);
89
+ }
90
+ } else {
91
+ interval = 1;
92
+ }
93
+
94
+ easeFactor = easeFactor + (0.1 - (5 - quality) * (0.08 + (5 - quality) * 0.02));
95
+
96
+ return { interval, easeFactor };
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### 5. نظام التحليل والإحصائيات
102
+
103
+ #### تتبع التقدم في الوقت الفعلي
104
+ ```javascript
105
+ class ProgressTracker {
106
+ constructor() {
107
+ this.metrics = {
108
+ vocabularyLearned: 0,
109
+ pronunciationAccuracy: [],
110
+ grammarScores: [],
111
+ dailyStreaks: 0,
112
+ timeSpent: 0
113
+ };
114
+ }
115
+
116
+ updateProgress(category, value) {
117
+ this.metrics[category] = value;
118
+ this.generateInsights();
119
+ this.updateCharacterFeedback();
120
+ }
121
+
122
+ generateInsights() {
123
+ // تحليل البيانات وإنشاء رؤى تعليمية
124
+ const insights = this.analyzePerformance();
125
+ return insights;
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### 6. نظام الإنجازات والتحفيز
131
+
132
+ #### آلية الشارات الذكية
133
+ ```javascript
134
+ class AchievementSystem {
135
+ checkAchievements(userStats) {
136
+ const achievements = [];
137
+
138
+ // فحص الإنجازات المختلفة
139
+ if (userStats.vocabularyLearned >= 50) {
140
+ achievements.push('vocabulary_master');
141
+ }
142
+
143
+ if (userStats.dailyStreaks >= 7) {
144
+ achievements.push('daily_streak');
145
+ }
146
+
147
+ return achievements;
148
+ }
149
+
150
+ celebrateAchievement(achievementId) {
151
+ // تفعيل الاحتفال بالإنجاز
152
+ this.showAchievementAnimation(achievementId);
153
+ this.updateCharacterExpression('celebrating');
154
+ }
155
+ }
156
+ ```
157
+
158
+ ### 7. التكيف مع مستوى المتعلم
159
+
160
+ #### نظام التعلم المكيف
161
+ ```javascript
162
+ class AdaptiveLearning {
163
+ assessLearnerLevel(userHistory) {
164
+ const level = this.calculateLevel(userHistory);
165
+ return {
166
+ vocabulary: level.vocabulary,
167
+ grammar: level.grammar,
168
+ pronunciation: level.pronunciation,
169
+ comprehension: level.comprehension
170
+ };
171
+ }
172
+
173
+ adaptContent(currentLevel, performance) {
174
+ // تكييف المحتوى حسب مستوى المتعلم
175
+ if (performance.accuracy < 0.7) {
176
+ return this.getEasierContent(currentLevel);
177
+ } else if (performance.accuracy > 0.9) {
178
+ return this.getHarderContent(currentLevel);
179
+ }
180
+ return this.getCurrentLevelContent(currentLevel);
181
+ }
182
+ }
183
+ ```
184
+
185
+ ### 8. التحسينات الأمنية
186
+
187
+ #### حماية البيانات الشخصية
188
+ ```javascript
189
+ class DataSecurity {
190
+ encryptUserData(data) {
191
+ // تشفير بيانات المستخدم
192
+ return CryptoJS.AES.encrypt(JSON.stringify(data), 'secret-key').toString();
193
+ }
194
+
195
+ validateInput(userInput) {
196
+ // التحقق من صحة المدخلات
197
+ return this.sanitizeInput(userInput);
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### 9. إرشادات التطوير المستقبلي
203
+
204
+ #### إضافة ميزات جديدة
205
+ 1. **تعلم الآلة المحلي**: استخدام TensorFlow.js للتحليل المحلي
206
+ 2. **الواقع المعزز**: دمج تقنيات AR للتعلم التفاعلي
207
+ 3. **التعلم الاجتماعي**: إضافة ميزات التعلم الجماعي
208
+ 4. **دعم لغات متعددة**: توسيع التطبيق ليشمل لغات أخرى
209
+
210
+ #### أفضل الممارسات
211
+ - استخدام مبدأ Progressive Web App
212
+ - تنفيذ Service Workers للعمل دون اتصال
213
+ - تحسين تجربة المستخدم على الشاشات المختلفة
214
+ - إجراء اختبارات الأداء المنتظمة
215
+
216
+ ### 10. متطلبات النشر
217
+
218
+ #### البيئة المطلوبة
219
+ - Node.js 16+
220
+ - متصفح يدعم Web Speech API
221
+ - اتصال إنترنت للميزات المتقدمة
222
+ - ذاكرة RAM: 2GB على الأقل للهواتف
223
+
224
+ #### التوافق
225
+ - iOS Safari 12+
226
+ - Android Chrome 80+
227
+ - Desktop Chrome/Firefox/Edge الأحدث
228
+
229
+ هذا الدليل يوفر الأساس التقني لتطوير وتحسين تطبيق Echo Light المتقدم مع ضمان الأداء الأمثل والتجربة المتميزة للمستخدمين.
index.html ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ar" dir="rtl">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Echo Light - تعلم الإنجليزية التفاعلي</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body>
10
+ <!-- Animated Background -->
11
+ <div class="space-background">
12
+ <div class="stars"></div>
13
+ <div class="moving-particles"></div>
14
+ </div>
15
+
16
+ <!-- Main Application Container -->
17
+ <div class="app-container">
18
+ <!-- Header -->
19
+ <header class="app-header">
20
+ <div class="container flex items-center justify-between">
21
+ <div class="logo">
22
+ <h1 class="glow-text">Echo Light</h1>
23
+ <span class="tagline">معلمك الذكي لتعلم الإنجليزية</span>
24
+ </div>
25
+ <div class="user-stats">
26
+ <div class="stat-item">
27
+ <span class="stat-value" id="streakCount">0</span>
28
+ <span class="stat-label">أيام متتالية</span>
29
+ </div>
30
+ <div class="stat-item">
31
+ <span class="stat-value" id="totalPoints">0</span>
32
+ <span class="stat-label">نقاط</span>
33
+ </div>
34
+ </div>
35
+ </div>
36
+ </header>
37
+
38
+ <!-- Main Content -->
39
+ <main class="main-content">
40
+ <div class="container">
41
+ <!-- Home Screen -->
42
+ <section id="homeScreen" class="screen active">
43
+ <div class="home-layout">
44
+ <!-- Echo Light Character -->
45
+ <div class="character-container">
46
+ <div class="echo-character" id="echoCharacter">
47
+ <div class="character-body">
48
+ <div class="character-head">
49
+ <div class="eyes">
50
+ <div class="eye left-eye">
51
+ <div class="pupil"></div>
52
+ </div>
53
+ <div class="eye right-eye">
54
+ <div class="pupil"></div>
55
+ </div>
56
+ </div>
57
+ <div class="mouth" id="characterMouth"></div>
58
+ </div>
59
+ <div class="character-glow"></div>
60
+ </div>
61
+ <div class="sound-waves" id="soundWaves"></div>
62
+ <div class="light-particles"></div>
63
+ </div>
64
+ <div class="welcome-message">
65
+ <p id="welcomeText">مرحباً! أنا Echo Light، معلمك الذكي لتعلم الإنجليزية</p>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Learning Modes -->
70
+ <div class="learning-modes">
71
+ <h2 class="section-title glow-text">اختر طريقة التعلم</h2>
72
+ <div class="modes-grid" id="modesGrid">
73
+ <!-- Modes will be generated by JavaScript -->
74
+ </div>
75
+ </div>
76
+ </div>
77
+ </section>
78
+
79
+ <!-- Conversation Mode -->
80
+ <section id="conversationScreen" class="screen">
81
+ <div class="mode-header">
82
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
83
+ <h2 class="glow-text">المحادثة الحرة</h2>
84
+ </div>
85
+ <div class="conversation-layout">
86
+ <div class="character-side">
87
+ <div class="echo-character mini">
88
+ <div class="character-body">
89
+ <div class="character-head">
90
+ <div class="eyes">
91
+ <div class="eye left-eye"><div class="pupil"></div></div>
92
+ <div class="eye right-eye"><div class="pupil"></div></div>
93
+ </div>
94
+ <div class="mouth"></div>
95
+ </div>
96
+ <div class="character-glow"></div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+ <div class="chat-area">
101
+ <div class="chat-messages" id="chatMessages"></div>
102
+ <div class="chat-input">
103
+ <input type="text" id="messageInput" placeholder="اكتب رسالتك هنا..." class="form-control">
104
+ <button class="btn btn--primary" onclick="sendMessage()">إرسال</button>
105
+ <button class="btn btn--outline voice-btn" onclick="startVoiceInput()">🎤</button>
106
+ </div>
107
+ </div>
108
+ </div>
109
+
110
+ <!-- Voice Conversation Controls -->
111
+ <div class="voice-controls" style="display: flex; gap: 10px; justify-content: center; margin: 20px 0;">
112
+ <button class="btn btn--primary" onclick="startVoiceLoop()" title="Start Voice Conversation">🎤 ابدأ المحادثة</button>
113
+ <button class="btn btn--secondary" onclick="toggleMute()" title="Mute/Unmute">🔇 كتم/إلغاء كتم</button>
114
+ <button class="btn btn--danger" onclick="stopVoiceLoop()" title="End Conversation">⏹️ إنهاء المحادثة</button>
115
+ </div>
116
+ </section>
117
+
118
+ <!-- Vocabulary Mode -->
119
+ <section id="vocabularyScreen" class="screen">
120
+ <div class="mode-header">
121
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
122
+ <h2 class="glow-text">بناء المفردات</h2>
123
+ </div>
124
+ <div class="vocabulary-layout">
125
+ <div class="progress-bar">
126
+ <div class="progress-fill" id="vocabProgress"></div>
127
+ <span class="progress-text" id="vocabProgressText">0/5</span>
128
+ </div>
129
+ <div class="flashcard" id="flashcard">
130
+ <div class="card-inner">
131
+ <div class="card-front">
132
+ <h3 id="cardWord">Hello</h3>
133
+ <p id="cardPronunciation">/həˈloʊ/</p>
134
+ <button class="btn btn--outline" onclick="playPronunciation()">🔊 استمع</button>
135
+ </div>
136
+ <div class="card-back">
137
+ <h3 id="cardTranslation">مرحبا</h3>
138
+ <p id="cardExample">Hello, how are you?</p>
139
+ </div>
140
+ </div>
141
+ </div>
142
+ <div class="card-controls">
143
+ <button class="btn btn--secondary" onclick="flipCard()">قلب البطاقة</button>
144
+ <button class="btn btn--primary" onclick="nextCard()">التالي</button>
145
+ </div>
146
+ </div>
147
+ </section>
148
+
149
+ <!-- Pronunciation Mode -->
150
+ <section id="pronunciationScreen" class="screen">
151
+ <div class="mode-header">
152
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
153
+ <h2 class="glow-text">تدريب النطق</h2>
154
+ </div>
155
+ <div class="pronunciation-layout">
156
+ <div class="practice-word">
157
+ <h3 id="practiceWord">Beautiful</h3>
158
+ <p id="practicePronunciation">/ˈbjuːtɪfəl/</p>
159
+ <button class="btn btn--outline" onclick="playTargetPronunciation()">🔊 النطق الصحيح</button>
160
+ </div>
161
+ <div class="recording-area">
162
+ <div class="record-visualizer" id="recordVisualizer"></div>
163
+ <button class="btn btn--primary record-btn" id="recordBtn" onclick="toggleRecording()">🎤 ابدأ التسجيل</button>
164
+ </div>
165
+ <div class="pronunciation-feedback" id="pronunciationFeedback"></div>
166
+ </div>
167
+ </section>
168
+
169
+ <!-- Grammar Mode -->
170
+ <section id="grammarScreen" class="screen">
171
+ <div class="mode-header">
172
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
173
+ <h2 class="glow-text">ألعاب القواعد</h2>
174
+ </div>
175
+ <div class="grammar-layout">
176
+ <div class="question-card">
177
+ <h3 id="grammarQuestion">Choose the correct form: I ___ to school every day.</h3>
178
+ <div class="options-grid" id="grammarOptions"></div>
179
+ </div>
180
+ <div class="grammar-feedback" id="grammarFeedback"></div>
181
+ </div>
182
+ </section>
183
+
184
+ <!-- Scenarios Mode -->
185
+ <section id="scenariosScreen" class="screen">
186
+ <div class="mode-header">
187
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
188
+ <h2 class="glow-text">سيناريوهات واقعية</h2>
189
+ </div>
190
+ <div class="scenarios-layout">
191
+ <div class="scenario-selector">
192
+ <h3>اختر السيناريو:</h3>
193
+ <div class="scenario-buttons" id="scenarioButtons"></div>
194
+ </div>
195
+ <div class="scenario-dialogue" id="scenarioDialogue"></div>
196
+ </div>
197
+ </section>
198
+
199
+ <!-- Daily Challenge Mode -->
200
+ <section id="dailyScreen" class="screen">
201
+ <div class="mode-header">
202
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
203
+ <h2 class="glow-text">التحدي اليومي</h2>
204
+ </div>
205
+ <div class="daily-layout">
206
+ <div class="challenge-header">
207
+ <h3>تحدي اليوم</h3>
208
+ <div class="challenge-timer" id="challengeTimer">05:00</div>
209
+ </div>
210
+ <div class="challenge-content" id="challengeContent">
211
+ <p>أكمل الجملة التالية:</p>
212
+ <p class="challenge-sentence">"I ____ English every day because it's ____."</p>
213
+ <div class="word-bank">
214
+ <span class="word-option" onclick="selectWord(this)">study</span>
215
+ <span class="word-option" onclick="selectWord(this)">important</span>
216
+ <span class="word-option" onclick="selectWord(this)">learn</span>
217
+ <span class="word-option" onclick="selectWord(this)">interesting</span>
218
+ </div>
219
+ </div>
220
+ <div class="daily-progress">
221
+ <div class="progress-bar">
222
+ <div class="progress-fill" id="dailyProgress"></div>
223
+ </div>
224
+ <p>التقدم: <span id="dailyProgressText">0/3</span></p>
225
+ </div>
226
+ </div>
227
+ </section>
228
+
229
+ <!-- Progress Screen -->
230
+ <section id="progressScreen" class="screen">
231
+ <div class="mode-header">
232
+ <button class="btn btn--secondary back-btn" onclick="showScreen('homeScreen')">← العودة</button>
233
+ <h2 class="glow-text">تتبع التقدم</h2>
234
+ </div>
235
+ <div class="progress-layout">
236
+ <div class="stats-grid">
237
+ <div class="stat-card">
238
+ <h3>الكلمات المتعلمة</h3>
239
+ <div class="stat-number" id="wordsLearned">0</div>
240
+ </div>
241
+ <div class="stat-card">
242
+ <h3>المحادثات المكتملة</h3>
243
+ <div class="stat-number" id="conversationsCompleted">0</div>
244
+ </div>
245
+ <div class="stat-card">
246
+ <h3>دقة النطق</h3>
247
+ <div class="stat-number" id="pronunciationAccuracy">0%</div>
248
+ </div>
249
+ <div class="stat-card">
250
+ <h3>نقاط القواعد</h3>
251
+ <div class="stat-number" id="grammarScore">0</div>
252
+ </div>
253
+ </div>
254
+ <div class="achievements-section">
255
+ <h3>الإنجازات</h3>
256
+ <div class="achievements-grid" id="achievementsGrid"></div>
257
+ </div>
258
+ </div>
259
+ </section>
260
+ </div>
261
+ </main>
262
+
263
+ <!-- Quick Actions -->
264
+ <div class="quick-actions">
265
+ <button class="quick-btn" onclick="showScreen('progressScreen')" title="تتبع التقدم">
266
+ 📊
267
+ </button>
268
+ <button class="quick-btn" onclick="toggleSound()" title="الصوت" id="soundToggle">
269
+ 🔊
270
+ </button>
271
+ </div>
272
+ </div>
273
+
274
+ <script src="app.js"></script>
275
+ </body>
276
+ </html>
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ # requirements.txt for Docker/HuggingFace deployment
2
+ Flask
3
+ flask-cors
4
+ openai
5
+ python-dotenv
style.css ADDED
@@ -0,0 +1,1647 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ :root {
3
+ /* Colors */
4
+ --color-background: rgba(252, 252, 249, 1);
5
+ --color-surface: rgba(255, 255, 253, 1);
6
+ --color-text: rgba(19, 52, 59, 1);
7
+ --color-text-secondary: rgba(98, 108, 113, 1);
8
+ --color-primary: rgba(33, 128, 141, 1);
9
+ --color-primary-hover: rgba(29, 116, 128, 1);
10
+ --color-primary-active: rgba(26, 104, 115, 1);
11
+ --color-secondary: rgba(94, 82, 64, 0.12);
12
+ --color-secondary-hover: rgba(94, 82, 64, 0.2);
13
+ --color-secondary-active: rgba(94, 82, 64, 0.25);
14
+ --color-border: rgba(94, 82, 64, 0.2);
15
+ --color-btn-primary-text: rgba(252, 252, 249, 1);
16
+ --color-card-border: rgba(94, 82, 64, 0.12);
17
+ --color-card-border-inner: rgba(94, 82, 64, 0.12);
18
+ --color-error: rgba(192, 21, 47, 1);
19
+ --color-success: rgba(33, 128, 141, 1);
20
+ --color-warning: rgba(168, 75, 47, 1);
21
+ --color-info: rgba(98, 108, 113, 1);
22
+ --color-focus-ring: rgba(33, 128, 141, 0.4);
23
+ --color-select-caret: rgba(19, 52, 59, 0.8);
24
+
25
+ /* Common style patterns */
26
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
27
+ --focus-outline: 2px solid var(--color-primary);
28
+ --status-bg-opacity: 0.15;
29
+ --status-border-opacity: 0.25;
30
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
31
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
32
+
33
+ /* RGB versions for opacity control */
34
+ --color-success-rgb: 33, 128, 141;
35
+ --color-error-rgb: 192, 21, 47;
36
+ --color-warning-rgb: 168, 75, 47;
37
+ --color-info-rgb: 98, 108, 113;
38
+
39
+ /* Typography */
40
+ --font-family-base: "FKGroteskNeue", "Geist", "Inter", -apple-system,
41
+ BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
42
+ --font-family-mono: "Berkeley Mono", ui-monospace, SFMono-Regular, Menlo,
43
+ Monaco, Consolas, monospace;
44
+ --font-size-xs: 11px;
45
+ --font-size-sm: 12px;
46
+ --font-size-base: 14px;
47
+ --font-size-md: 14px;
48
+ --font-size-lg: 16px;
49
+ --font-size-xl: 18px;
50
+ --font-size-2xl: 20px;
51
+ --font-size-3xl: 24px;
52
+ --font-size-4xl: 30px;
53
+ --font-weight-normal: 400;
54
+ --font-weight-medium: 500;
55
+ --font-weight-semibold: 550;
56
+ --font-weight-bold: 600;
57
+ --line-height-tight: 1.2;
58
+ --line-height-normal: 1.5;
59
+ --letter-spacing-tight: -0.01em;
60
+
61
+ /* Spacing */
62
+ --space-0: 0;
63
+ --space-1: 1px;
64
+ --space-2: 2px;
65
+ --space-4: 4px;
66
+ --space-6: 6px;
67
+ --space-8: 8px;
68
+ --space-10: 10px;
69
+ --space-12: 12px;
70
+ --space-16: 16px;
71
+ --space-20: 20px;
72
+ --space-24: 24px;
73
+ --space-32: 32px;
74
+
75
+ /* Border Radius */
76
+ --radius-sm: 6px;
77
+ --radius-base: 8px;
78
+ --radius-md: 10px;
79
+ --radius-lg: 12px;
80
+ --radius-full: 9999px;
81
+
82
+ /* Shadows */
83
+ --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.02);
84
+ --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02);
85
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.04),
86
+ 0 2px 4px -1px rgba(0, 0, 0, 0.02);
87
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.04),
88
+ 0 4px 6px -2px rgba(0, 0, 0, 0.02);
89
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.15),
90
+ inset 0 -1px 0 rgba(0, 0, 0, 0.03);
91
+
92
+ /* Animation */
93
+ --duration-fast: 150ms;
94
+ --duration-normal: 250ms;
95
+ --ease-standard: cubic-bezier(0.16, 1, 0.3, 1);
96
+
97
+ /* Layout */
98
+ --container-sm: 640px;
99
+ --container-md: 768px;
100
+ --container-lg: 1024px;
101
+ --container-xl: 1280px;
102
+ }
103
+
104
+ /* Dark mode colors */
105
+ @media (prefers-color-scheme: dark) {
106
+ :root {
107
+ --color-background: rgba(31, 33, 33, 1);
108
+ --color-surface: rgba(38, 40, 40, 1);
109
+ --color-text: rgba(245, 245, 245, 1);
110
+ --color-text-secondary: rgba(167, 169, 169, 0.7);
111
+ --color-primary: rgba(50, 184, 198, 1);
112
+ --color-primary-hover: rgba(45, 166, 178, 1);
113
+ --color-primary-active: rgba(41, 150, 161, 1);
114
+ --color-secondary: rgba(119, 124, 124, 0.15);
115
+ --color-secondary-hover: rgba(119, 124, 124, 0.25);
116
+ --color-secondary-active: rgba(119, 124, 124, 0.3);
117
+ --color-border: rgba(119, 124, 124, 0.3);
118
+ --color-error: rgba(255, 84, 89, 1);
119
+ --color-success: rgba(50, 184, 198, 1);
120
+ --color-warning: rgba(230, 129, 97, 1);
121
+ --color-info: rgba(167, 169, 169, 1);
122
+ --color-focus-ring: rgba(50, 184, 198, 0.4);
123
+ --color-btn-primary-text: rgba(19, 52, 59, 1);
124
+ --color-card-border: rgba(119, 124, 124, 0.2);
125
+ --color-card-border-inner: rgba(119, 124, 124, 0.15);
126
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1),
127
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
128
+ --button-border-secondary: rgba(119, 124, 124, 0.2);
129
+ --color-border-secondary: rgba(119, 124, 124, 0.2);
130
+ --color-select-caret: rgba(245, 245, 245, 0.8);
131
+
132
+ /* Common style patterns - updated for dark mode */
133
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
134
+ --focus-outline: 2px solid var(--color-primary);
135
+ --status-bg-opacity: 0.15;
136
+ --status-border-opacity: 0.25;
137
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
138
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
139
+
140
+ /* RGB versions for dark mode */
141
+ --color-success-rgb: 50, 184, 198;
142
+ --color-error-rgb: 255, 84, 89;
143
+ --color-warning-rgb: 230, 129, 97;
144
+ --color-info-rgb: 167, 169, 169;
145
+ }
146
+ }
147
+
148
+ /* Data attribute for manual theme switching */
149
+ [data-color-scheme="dark"] {
150
+ --color-background: rgba(31, 33, 33, 1);
151
+ --color-surface: rgba(38, 40, 40, 1);
152
+ --color-text: rgba(245, 245, 245, 1);
153
+ --color-text-secondary: rgba(167, 169, 169, 0.7);
154
+ --color-primary: rgba(50, 184, 198, 1);
155
+ --color-primary-hover: rgba(45, 166, 178, 1);
156
+ --color-primary-active: rgba(41, 150, 161, 1);
157
+ --color-secondary: rgba(119, 124, 124, 0.15);
158
+ --color-secondary-hover: rgba(119, 124, 124, 0.25);
159
+ --color-secondary-active: rgba(119, 124, 124, 0.3);
160
+ --color-border: rgba(119, 124, 124, 0.3);
161
+ --color-error: rgba(255, 84, 89, 1);
162
+ --color-success: rgba(50, 184, 198, 1);
163
+ --color-warning: rgba(230, 129, 97, 1);
164
+ --color-info: rgba(167, 169, 169, 1);
165
+ --color-focus-ring: rgba(50, 184, 198, 0.4);
166
+ --color-btn-primary-text: rgba(19, 52, 59, 1);
167
+ --color-card-border: rgba(119, 124, 124, 0.15);
168
+ --color-card-border-inner: rgba(119, 124, 124, 0.15);
169
+ --shadow-inset-sm: inset 0 1px 0 rgba(255, 255, 255, 0.1),
170
+ inset 0 -1px 0 rgba(0, 0, 0, 0.15);
171
+ --color-border-secondary: rgba(119, 124, 124, 0.2);
172
+ --color-select-caret: rgba(245, 245, 245, 0.8);
173
+
174
+ /* Common style patterns - updated for dark mode */
175
+ --focus-ring: 0 0 0 3px var(--color-focus-ring);
176
+ --focus-outline: 2px solid var(--color-primary);
177
+ --status-bg-opacity: 0.15;
178
+ --status-border-opacity: 0.25;
179
+ --select-caret-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23134252' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
180
+ --select-caret-dark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23f5f5f5' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'%3E%3C/polyline%3E%3C/svg%3E");
181
+
182
+ /* RGB versions for dark mode */
183
+ --color-success-rgb: 50, 184, 198;
184
+ --color-error-rgb: 255, 84, 89;
185
+ --color-warning-rgb: 230, 129, 97;
186
+ --color-info-rgb: 167, 169, 169;
187
+ }
188
+
189
+ [data-color-scheme="light"] {
190
+ --color-background: rgba(252, 252, 249, 1);
191
+ --color-surface: rgba(255, 255, 253, 1);
192
+ --color-text: rgba(19, 52, 59, 1);
193
+ --color-text-secondary: rgba(98, 108, 113, 1);
194
+ --color-primary: rgba(33, 128, 141, 1);
195
+ --color-primary-hover: rgba(29, 116, 128, 1);
196
+ --color-primary-active: rgba(26, 104, 115, 1);
197
+ --color-secondary: rgba(94, 82, 64, 0.12);
198
+ --color-secondary-hover: rgba(94, 82, 64, 0.2);
199
+ --color-secondary-active: rgba(94, 82, 64, 0.25);
200
+ --color-border: rgba(94, 82, 64, 0.2);
201
+ --color-btn-primary-text: rgba(252, 252, 249, 1);
202
+ --color-card-border: rgba(94, 82, 64, 0.12);
203
+ --color-card-border-inner: rgba(94, 82, 64, 0.12);
204
+ --color-error: rgba(192, 21, 47, 1);
205
+ --color-success: rgba(33, 128, 141, 1);
206
+ --color-warning: rgba(168, 75, 47, 1);
207
+ --color-info: rgba(98, 108, 113, 1);
208
+ --color-focus-ring: rgba(33, 128, 141, 0.4);
209
+
210
+ /* RGB versions for light mode */
211
+ --color-success-rgb: 33, 128, 141;
212
+ --color-error-rgb: 192, 21, 47;
213
+ --color-warning-rgb: 168, 75, 47;
214
+ --color-info-rgb: 98, 108, 113;
215
+ }
216
+
217
+ /* Base styles */
218
+ html {
219
+ font-size: var(--font-size-base);
220
+ font-family: var(--font-family-base);
221
+ line-height: var(--line-height-normal);
222
+ color: var(--color-text);
223
+ background-color: var(--color-background);
224
+ -webkit-font-smoothing: antialiased;
225
+ box-sizing: border-box;
226
+ }
227
+
228
+ body {
229
+ margin: 0;
230
+ padding: 0;
231
+ }
232
+
233
+ *,
234
+ *::before,
235
+ *::after {
236
+ box-sizing: inherit;
237
+ }
238
+
239
+ /* Typography */
240
+ h1,
241
+ h2,
242
+ h3,
243
+ h4,
244
+ h5,
245
+ h6 {
246
+ margin: 0;
247
+ font-weight: var(--font-weight-semibold);
248
+ line-height: var(--line-height-tight);
249
+ color: var(--color-text);
250
+ letter-spacing: var(--letter-spacing-tight);
251
+ }
252
+
253
+ h1 {
254
+ font-size: var(--font-size-4xl);
255
+ }
256
+ h2 {
257
+ font-size: var(--font-size-3xl);
258
+ }
259
+ h3 {
260
+ font-size: var(--font-size-2xl);
261
+ }
262
+ h4 {
263
+ font-size: var(--font-size-xl);
264
+ }
265
+ h5 {
266
+ font-size: var(--font-size-lg);
267
+ }
268
+ h6 {
269
+ font-size: var(--font-size-md);
270
+ }
271
+
272
+ p {
273
+ margin: 0 0 var(--space-16) 0;
274
+ }
275
+
276
+ a {
277
+ color: var(--color-primary);
278
+ text-decoration: none;
279
+ transition: color var(--duration-fast) var(--ease-standard);
280
+ }
281
+
282
+ a:hover {
283
+ color: var(--color-primary-hover);
284
+ }
285
+
286
+ code,
287
+ pre {
288
+ font-family: var(--font-family-mono);
289
+ font-size: calc(var(--font-size-base) * 0.95);
290
+ background-color: var(--color-secondary);
291
+ border-radius: var(--radius-sm);
292
+ }
293
+
294
+ code {
295
+ padding: var(--space-1) var(--space-4);
296
+ }
297
+
298
+ pre {
299
+ padding: var(--space-16);
300
+ margin: var(--space-16) 0;
301
+ overflow: auto;
302
+ border: 1px solid var(--color-border);
303
+ }
304
+
305
+ pre code {
306
+ background: none;
307
+ padding: 0;
308
+ }
309
+
310
+ /* Buttons */
311
+ .btn {
312
+ display: inline-flex;
313
+ align-items: center;
314
+ justify-content: center;
315
+ padding: var(--space-8) var(--space-16);
316
+ border-radius: var(--radius-base);
317
+ font-size: var(--font-size-base);
318
+ font-weight: 500;
319
+ line-height: 1.5;
320
+ cursor: pointer;
321
+ transition: all var(--duration-normal) var(--ease-standard);
322
+ border: none;
323
+ text-decoration: none;
324
+ position: relative;
325
+ }
326
+
327
+ .btn:focus-visible {
328
+ outline: none;
329
+ box-shadow: var(--focus-ring);
330
+ }
331
+
332
+ .btn--primary {
333
+ background: var(--color-primary);
334
+ color: var(--color-btn-primary-text);
335
+ }
336
+
337
+ .btn--primary:hover {
338
+ background: var(--color-primary-hover);
339
+ }
340
+
341
+ .btn--primary:active {
342
+ background: var(--color-primary-active);
343
+ }
344
+
345
+ .btn--secondary {
346
+ background: var(--color-secondary);
347
+ color: var(--color-text);
348
+ }
349
+
350
+ .btn--secondary:hover {
351
+ background: var(--color-secondary-hover);
352
+ }
353
+
354
+ .btn--secondary:active {
355
+ background: var(--color-secondary-active);
356
+ }
357
+
358
+ .btn--outline {
359
+ background: transparent;
360
+ border: 1px solid var(--color-border);
361
+ color: var(--color-text);
362
+ }
363
+
364
+ .btn--outline:hover {
365
+ background: var(--color-secondary);
366
+ }
367
+
368
+ .btn--sm {
369
+ padding: var(--space-4) var(--space-12);
370
+ font-size: var(--font-size-sm);
371
+ border-radius: var(--radius-sm);
372
+ }
373
+
374
+ .btn--lg {
375
+ padding: var(--space-10) var(--space-20);
376
+ font-size: var(--font-size-lg);
377
+ border-radius: var(--radius-md);
378
+ }
379
+
380
+ .btn--full-width {
381
+ width: 100%;
382
+ }
383
+
384
+ .btn:disabled {
385
+ opacity: 0.5;
386
+ cursor: not-allowed;
387
+ }
388
+
389
+ /* Form elements */
390
+ .form-control {
391
+ display: block;
392
+ width: 100%;
393
+ padding: var(--space-8) var(--space-12);
394
+ font-size: var(--font-size-md);
395
+ line-height: 1.5;
396
+ color: var(--color-text);
397
+ background-color: var(--color-surface);
398
+ border: 1px solid var(--color-border);
399
+ border-radius: var(--radius-base);
400
+ transition: border-color var(--duration-fast) var(--ease-standard),
401
+ box-shadow var(--duration-fast) var(--ease-standard);
402
+ }
403
+
404
+ textarea.form-control {
405
+ font-family: var(--font-family-base);
406
+ font-size: var(--font-size-base);
407
+ }
408
+
409
+ select.form-control {
410
+ padding: var(--space-8) var(--space-12);
411
+ -webkit-appearance: none;
412
+ -moz-appearance: none;
413
+ appearance: none;
414
+ background-image: var(--select-caret-light);
415
+ background-repeat: no-repeat;
416
+ background-position: right var(--space-12) center;
417
+ background-size: 16px;
418
+ padding-right: var(--space-32);
419
+ }
420
+
421
+ /* Add a dark mode specific caret */
422
+ @media (prefers-color-scheme: dark) {
423
+ select.form-control {
424
+ background-image: var(--select-caret-dark);
425
+ }
426
+ }
427
+
428
+ /* Also handle data-color-scheme */
429
+ [data-color-scheme="dark"] select.form-control {
430
+ background-image: var(--select-caret-dark);
431
+ }
432
+
433
+ [data-color-scheme="light"] select.form-control {
434
+ background-image: var(--select-caret-light);
435
+ }
436
+
437
+ .form-control:focus {
438
+ border-color: var(--color-primary);
439
+ outline: var(--focus-outline);
440
+ }
441
+
442
+ .form-label {
443
+ display: block;
444
+ margin-bottom: var(--space-8);
445
+ font-weight: var(--font-weight-medium);
446
+ font-size: var(--font-size-sm);
447
+ }
448
+
449
+ .form-group {
450
+ margin-bottom: var(--space-16);
451
+ }
452
+
453
+ /* Card component */
454
+ .card {
455
+ background-color: var(--color-surface);
456
+ border-radius: var(--radius-lg);
457
+ border: 1px solid var(--color-card-border);
458
+ box-shadow: var(--shadow-sm);
459
+ overflow: hidden;
460
+ transition: box-shadow var(--duration-normal) var(--ease-standard);
461
+ }
462
+
463
+ .card:hover {
464
+ box-shadow: var(--shadow-md);
465
+ }
466
+
467
+ .card__body {
468
+ padding: var(--space-16);
469
+ }
470
+
471
+ .card__header,
472
+ .card__footer {
473
+ padding: var(--space-16);
474
+ border-bottom: 1px solid var(--color-card-border-inner);
475
+ }
476
+
477
+ /* Status indicators - simplified with CSS variables */
478
+ .status {
479
+ display: inline-flex;
480
+ align-items: center;
481
+ padding: var(--space-6) var(--space-12);
482
+ border-radius: var(--radius-full);
483
+ font-weight: var(--font-weight-medium);
484
+ font-size: var(--font-size-sm);
485
+ }
486
+
487
+ .status--success {
488
+ background-color: rgba(
489
+ var(--color-success-rgb, 33, 128, 141),
490
+ var(--status-bg-opacity)
491
+ );
492
+ color: var(--color-success);
493
+ border: 1px solid
494
+ rgba(var(--color-success-rgb, 33, 128, 141), var(--status-border-opacity));
495
+ }
496
+
497
+ .status--error {
498
+ background-color: rgba(
499
+ var(--color-error-rgb, 192, 21, 47),
500
+ var(--status-bg-opacity)
501
+ );
502
+ color: var(--color-error);
503
+ border: 1px solid
504
+ rgba(var(--color-error-rgb, 192, 21, 47), var(--status-border-opacity));
505
+ }
506
+
507
+ .status--warning {
508
+ background-color: rgba(
509
+ var(--color-warning-rgb, 168, 75, 47),
510
+ var(--status-bg-opacity)
511
+ );
512
+ color: var(--color-warning);
513
+ border: 1px solid
514
+ rgba(var(--color-warning-rgb, 168, 75, 47), var(--status-border-opacity));
515
+ }
516
+
517
+ .status--info {
518
+ background-color: rgba(
519
+ var(--color-info-rgb, 98, 108, 113),
520
+ var(--status-bg-opacity)
521
+ );
522
+ color: var(--color-info);
523
+ border: 1px solid
524
+ rgba(var(--color-info-rgb, 98, 108, 113), var(--status-border-opacity));
525
+ }
526
+
527
+ /* Container layout */
528
+ .container {
529
+ width: 100%;
530
+ margin-right: auto;
531
+ margin-left: auto;
532
+ padding-right: var(--space-16);
533
+ padding-left: var(--space-16);
534
+ }
535
+
536
+ @media (min-width: 640px) {
537
+ .container {
538
+ max-width: var(--container-sm);
539
+ }
540
+ }
541
+ @media (min-width: 768px) {
542
+ .container {
543
+ max-width: var(--container-md);
544
+ }
545
+ }
546
+ @media (min-width: 1024px) {
547
+ .container {
548
+ max-width: var(--container-lg);
549
+ }
550
+ }
551
+ @media (min-width: 1280px) {
552
+ .container {
553
+ max-width: var(--container-xl);
554
+ }
555
+ }
556
+
557
+ /* Utility classes */
558
+ .flex {
559
+ display: flex;
560
+ }
561
+ .flex-col {
562
+ flex-direction: column;
563
+ }
564
+ .items-center {
565
+ align-items: center;
566
+ }
567
+ .justify-center {
568
+ justify-content: center;
569
+ }
570
+ .justify-between {
571
+ justify-content: space-between;
572
+ }
573
+ .gap-4 {
574
+ gap: var(--space-4);
575
+ }
576
+ .gap-8 {
577
+ gap: var(--space-8);
578
+ }
579
+ .gap-16 {
580
+ gap: var(--space-16);
581
+ }
582
+
583
+ .m-0 {
584
+ margin: 0;
585
+ }
586
+ .mt-8 {
587
+ margin-top: var(--space-8);
588
+ }
589
+ .mb-8 {
590
+ margin-bottom: var(--space-8);
591
+ }
592
+ .mx-8 {
593
+ margin-left: var(--space-8);
594
+ margin-right: var(--space-8);
595
+ }
596
+ .my-8 {
597
+ margin-top: var(--space-8);
598
+ margin-bottom: var(--space-8);
599
+ }
600
+
601
+ .p-0 {
602
+ padding: 0;
603
+ }
604
+ .py-8 {
605
+ padding-top: var(--space-8);
606
+ padding-bottom: var(--space-8);
607
+ }
608
+ .px-8 {
609
+ padding-left: var(--space-8);
610
+ padding-right: var(--space-8);
611
+ }
612
+ .py-16 {
613
+ padding-top: var(--space-16);
614
+ padding-bottom: var(--space-16);
615
+ }
616
+ .px-16 {
617
+ padding-left: var(--space-16);
618
+ padding-right: var(--space-16);
619
+ }
620
+
621
+ .block {
622
+ display: block;
623
+ }
624
+ .hidden {
625
+ display: none;
626
+ }
627
+
628
+ /* Accessibility */
629
+ .sr-only {
630
+ position: absolute;
631
+ width: 1px;
632
+ height: 1px;
633
+ padding: 0;
634
+ margin: -1px;
635
+ overflow: hidden;
636
+ clip: rect(0, 0, 0, 0);
637
+ white-space: nowrap;
638
+ border-width: 0;
639
+ }
640
+
641
+ :focus-visible {
642
+ outline: var(--focus-outline);
643
+ outline-offset: 2px;
644
+ }
645
+
646
+ /* Dark mode specifics */
647
+ [data-color-scheme="dark"] .btn--outline {
648
+ border: 1px solid var(--color-border-secondary);
649
+ }
650
+
651
+ @font-face {
652
+ font-family: 'FKGroteskNeue';
653
+ src: url('https://r2cdn.perplexity.ai/fonts/FKGroteskNeue.woff2')
654
+ format('woff2');
655
+ }
656
+
657
+ /* Space Background Animation */
658
+ .space-background {
659
+ position: fixed;
660
+ top: 0;
661
+ left: 0;
662
+ width: 100%;
663
+ height: 100%;
664
+ background: linear-gradient(135deg, #0a0a1a 0%, #1a0a2e 50%, #16213e 100%);
665
+ z-index: -1;
666
+ overflow: hidden;
667
+ }
668
+
669
+ .stars {
670
+ position: absolute;
671
+ width: 100%;
672
+ height: 100%;
673
+ background-image:
674
+ radial-gradient(2px 2px at 20px 30px, rgba(255,255,255,0.8), transparent),
675
+ radial-gradient(2px 2px at 40px 70px, rgba(255,255,255,0.6), transparent),
676
+ radial-gradient(1px 1px at 90px 40px, rgba(255,255,255,0.4), transparent),
677
+ radial-gradient(1px 1px at 130px 80px, rgba(255,255,255,0.7), transparent),
678
+ radial-gradient(2px 2px at 160px 30px, rgba(255,255,255,0.5), transparent);
679
+ background-repeat: repeat;
680
+ background-size: 200px 100px;
681
+ animation: twinkle 4s linear infinite;
682
+ }
683
+
684
+ .moving-particles {
685
+ position: absolute;
686
+ width: 100%;
687
+ height: 100%;
688
+ }
689
+
690
+ .moving-particles::before,
691
+ .moving-particles::after {
692
+ content: '';
693
+ position: absolute;
694
+ width: 4px;
695
+ height: 4px;
696
+ background: rgba(50, 184, 198, 0.6);
697
+ border-radius: 50%;
698
+ animation: float-particle 8s linear infinite;
699
+ }
700
+
701
+ .moving-particles::before {
702
+ top: 20%;
703
+ left: 10%;
704
+ animation-delay: -2s;
705
+ }
706
+
707
+ .moving-particles::after {
708
+ top: 60%;
709
+ right: 15%;
710
+ animation-delay: -4s;
711
+ background: rgba(138, 43, 226, 0.6);
712
+ }
713
+
714
+ @keyframes twinkle {
715
+ 0%, 100% { opacity: 1; }
716
+ 50% { opacity: 0.5; }
717
+ }
718
+
719
+ @keyframes float-particle {
720
+ 0% { transform: translateY(100vh) scale(0); }
721
+ 10% { transform: translateY(90vh) scale(1); }
722
+ 90% { transform: translateY(-10vh) scale(1); }
723
+ 100% { transform: translateY(-20vh) scale(0); }
724
+ }
725
+
726
+ /* App Layout */
727
+ .app-container {
728
+ min-height: 100vh;
729
+ position: relative;
730
+ z-index: 1;
731
+ }
732
+
733
+ .app-header {
734
+ padding: var(--space-16) 0;
735
+ background: rgba(0, 0, 0, 0.3);
736
+ backdrop-filter: blur(10px);
737
+ border-bottom: 1px solid rgba(50, 184, 198, 0.3);
738
+ }
739
+
740
+ .logo h1 {
741
+ color: #32b8c6;
742
+ text-shadow: 0 0 20px rgba(50, 184, 198, 0.6);
743
+ margin: 0;
744
+ font-size: var(--font-size-3xl);
745
+ }
746
+
747
+ .tagline {
748
+ color: var(--color-text-secondary);
749
+ font-size: var(--font-size-sm);
750
+ display: block;
751
+ margin-top: var(--space-4);
752
+ }
753
+
754
+ .user-stats {
755
+ display: flex;
756
+ gap: var(--space-24);
757
+ }
758
+
759
+ .stat-item {
760
+ text-align: center;
761
+ color: var(--color-text);
762
+ }
763
+
764
+ .stat-value {
765
+ display: block;
766
+ font-size: var(--font-size-2xl);
767
+ font-weight: var(--font-weight-bold);
768
+ color: #32b8c6;
769
+ text-shadow: 0 0 10px rgba(50, 184, 198, 0.4);
770
+ }
771
+
772
+ .stat-label {
773
+ font-size: var(--font-size-xs);
774
+ opacity: 0.8;
775
+ }
776
+
777
+ /* Glow Text Effect */
778
+ .glow-text {
779
+ color: #32b8c6;
780
+ text-shadow: 0 0 20px rgba(50, 184, 198, 0.6);
781
+ }
782
+
783
+ /* Screen Management */
784
+ .screen {
785
+ display: none;
786
+ padding: var(--space-32) 0;
787
+ min-height: calc(100vh - 120px);
788
+ }
789
+
790
+ .screen.active {
791
+ display: block;
792
+ }
793
+
794
+ /* Home Screen Layout */
795
+ .home-layout {
796
+ display: grid;
797
+ grid-template-columns: 1fr 1fr;
798
+ gap: var(--space-32);
799
+ align-items: center;
800
+ min-height: 70vh;
801
+ }
802
+
803
+ /* Echo Light Character */
804
+ .character-container {
805
+ display: flex;
806
+ flex-direction: column;
807
+ align-items: center;
808
+ justify-content: center;
809
+ }
810
+
811
+ .echo-character {
812
+ width: 300px;
813
+ height: 300px;
814
+ position: relative;
815
+ animation: float 3s ease-in-out infinite;
816
+ }
817
+
818
+ .echo-character.mini {
819
+ width: 150px;
820
+ height: 150px;
821
+ }
822
+
823
+ .character-body {
824
+ width: 100%;
825
+ height: 100%;
826
+ position: relative;
827
+ background: linear-gradient(135deg, #32b8c6 0%, #8a2be2 100%);
828
+ border-radius: 50%;
829
+ box-shadow:
830
+ 0 0 50px rgba(50, 184, 198, 0.4),
831
+ inset 0 0 50px rgba(138, 43, 226, 0.2);
832
+ animation: character-rotate 6s linear infinite;
833
+ }
834
+
835
+ .character-head {
836
+ position: absolute;
837
+ top: 20%;
838
+ left: 50%;
839
+ transform: translateX(-50%);
840
+ width: 80%;
841
+ height: 60%;
842
+ }
843
+
844
+ .eyes {
845
+ display: flex;
846
+ justify-content: space-between;
847
+ width: 60%;
848
+ margin: 0 auto;
849
+ padding-top: 20px;
850
+ }
851
+
852
+ .eye {
853
+ width: 30px;
854
+ height: 30px;
855
+ background: white;
856
+ border-radius: 50%;
857
+ position: relative;
858
+ animation: blink 4s infinite;
859
+ }
860
+
861
+ .pupil {
862
+ width: 15px;
863
+ height: 15px;
864
+ background: #1a0a2e;
865
+ border-radius: 50%;
866
+ position: absolute;
867
+ top: 50%;
868
+ left: 50%;
869
+ transform: translate(-50%, -50%);
870
+ animation: eye-movement 3s ease-in-out infinite;
871
+ }
872
+
873
+ .mouth {
874
+ width: 40px;
875
+ height: 20px;
876
+ background: #ff6b9d;
877
+ border-radius: 0 0 40px 40px;
878
+ position: absolute;
879
+ bottom: 30%;
880
+ left: 50%;
881
+ transform: translateX(-50%);
882
+ animation: mouth-talk 2s ease-in-out infinite;
883
+ }
884
+
885
+ .character-glow {
886
+ position: absolute;
887
+ top: -20px;
888
+ left: -20px;
889
+ right: -20px;
890
+ bottom: -20px;
891
+ background: radial-gradient(circle, rgba(50, 184, 198, 0.3) 0%, transparent 70%);
892
+ border-radius: 50%;
893
+ animation: glow-pulse 2s ease-in-out infinite alternate;
894
+ }
895
+
896
+ .sound-waves {
897
+ position: absolute;
898
+ top: 50%;
899
+ left: 50%;
900
+ transform: translate(-50%, -50%);
901
+ width: 100%;
902
+ height: 100%;
903
+ pointer-events: none;
904
+ }
905
+
906
+ .sound-waves::before,
907
+ .sound-waves::after {
908
+ content: '';
909
+ position: absolute;
910
+ border: 2px solid rgba(50, 184, 198, 0.4);
911
+ border-radius: 50%;
912
+ animation: sound-wave 2s ease-out infinite;
913
+ }
914
+
915
+ .sound-waves::before {
916
+ width: 120%;
917
+ height: 120%;
918
+ top: -10%;
919
+ left: -10%;
920
+ }
921
+
922
+ .sound-waves::after {
923
+ width: 140%;
924
+ height: 140%;
925
+ top: -20%;
926
+ left: -20%;
927
+ animation-delay: 0.5s;
928
+ }
929
+
930
+ .light-particles {
931
+ position: absolute;
932
+ width: 100%;
933
+ height: 100%;
934
+ pointer-events: none;
935
+ }
936
+
937
+ .light-particles::before,
938
+ .light-particles::after {
939
+ content: '';
940
+ position: absolute;
941
+ width: 4px;
942
+ height: 4px;
943
+ background: rgba(255, 107, 157, 0.8);
944
+ border-radius: 50%;
945
+ animation: particle-orbit 4s linear infinite;
946
+ }
947
+
948
+ .light-particles::before {
949
+ top: 20%;
950
+ left: 20%;
951
+ }
952
+
953
+ .light-particles::after {
954
+ bottom: 20%;
955
+ right: 20%;
956
+ animation-delay: 2s;
957
+ background: rgba(138, 43, 226, 0.8);
958
+ }
959
+
960
+ /* Character Animations */
961
+ @keyframes float {
962
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
963
+ 50% { transform: translateY(-20px) rotate(2deg); }
964
+ }
965
+
966
+ @keyframes character-rotate {
967
+ 0% { transform: rotate(0deg); }
968
+ 100% { transform: rotate(360deg); }
969
+ }
970
+
971
+ @keyframes blink {
972
+ 0%, 90%, 100% { transform: scaleY(1); }
973
+ 95% { transform: scaleY(0.1); }
974
+ }
975
+
976
+ @keyframes eye-movement {
977
+ 0%, 100% { transform: translate(-50%, -50%); }
978
+ 25% { transform: translate(-30%, -50%); }
979
+ 75% { transform: translate(-70%, -50%); }
980
+ }
981
+
982
+ @keyframes mouth-talk {
983
+ 0%, 100% { transform: translateX(-50%) scaleY(1); }
984
+ 50% { transform: translateX(-50%) scaleY(0.5); }
985
+ }
986
+
987
+ @keyframes glow-pulse {
988
+ 0% { opacity: 0.3; }
989
+ 100% { opacity: 0.7; }
990
+ }
991
+
992
+ @keyframes sound-wave {
993
+ 0% { transform: scale(1); opacity: 1; }
994
+ 100% { transform: scale(1.5); opacity: 0; }
995
+ }
996
+
997
+ @keyframes particle-orbit {
998
+ 0% { transform: rotate(0deg) translateX(150px) rotate(0deg); }
999
+ 100% { transform: rotate(360deg) translateX(150px) rotate(-360deg); }
1000
+ }
1001
+
1002
+ /* Welcome Message */
1003
+ .welcome-message {
1004
+ margin-top: var(--space-32);
1005
+ text-align: center;
1006
+ max-width: 400px;
1007
+ }
1008
+
1009
+ .welcome-message p {
1010
+ color: var(--color-text);
1011
+ font-size: var(--font-size-lg);
1012
+ background: rgba(0, 0, 0, 0.3);
1013
+ padding: var(--space-16);
1014
+ border-radius: var(--radius-lg);
1015
+ backdrop-filter: blur(10px);
1016
+ border: 1px solid rgba(50, 184, 198, 0.3);
1017
+ }
1018
+
1019
+ /* Learning Modes */
1020
+ .learning-modes {
1021
+ text-align: center;
1022
+ }
1023
+
1024
+ .section-title {
1025
+ margin-bottom: var(--space-32);
1026
+ font-size: var(--font-size-3xl);
1027
+ }
1028
+
1029
+ .modes-grid {
1030
+ display: grid;
1031
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1032
+ gap: var(--space-24);
1033
+ }
1034
+
1035
+ .mode-card {
1036
+ background: rgba(0, 0, 0, 0.4);
1037
+ backdrop-filter: blur(10px);
1038
+ border: 1px solid rgba(50, 184, 198, 0.3);
1039
+ border-radius: var(--radius-lg);
1040
+ padding: var(--space-24);
1041
+ cursor: pointer;
1042
+ transition: all var(--duration-normal) var(--ease-standard);
1043
+ text-align: center;
1044
+ }
1045
+
1046
+ .mode-card:hover {
1047
+ transform: translateY(-5px);
1048
+ border-color: rgba(50, 184, 198, 0.6);
1049
+ box-shadow: 0 10px 30px rgba(50, 184, 198, 0.2);
1050
+ }
1051
+
1052
+ .mode-icon {
1053
+ font-size: var(--font-size-4xl);
1054
+ margin-bottom: var(--space-16);
1055
+ display: block;
1056
+ }
1057
+
1058
+ .mode-title {
1059
+ color: #32b8c6;
1060
+ font-size: var(--font-size-xl);
1061
+ margin-bottom: var(--space-8);
1062
+ }
1063
+
1064
+ .mode-description {
1065
+ color: var(--color-text-secondary);
1066
+ font-size: var(--font-size-sm);
1067
+ line-height: 1.6;
1068
+ }
1069
+
1070
+ /* Mode Headers */
1071
+ .mode-header {
1072
+ display: flex;
1073
+ align-items: center;
1074
+ gap: var(--space-16);
1075
+ margin-bottom: var(--space-32);
1076
+ padding-bottom: var(--space-16);
1077
+ border-bottom: 1px solid rgba(50, 184, 198, 0.3);
1078
+ }
1079
+
1080
+ .mode-header h2 {
1081
+ flex: 1;
1082
+ text-align: center;
1083
+ margin: 0;
1084
+ }
1085
+
1086
+ .back-btn {
1087
+ background: rgba(0, 0, 0, 0.3) !important;
1088
+ border: 1px solid rgba(50, 184, 198, 0.3) !important;
1089
+ color: var(--color-text) !important;
1090
+ }
1091
+
1092
+ /* Conversation Layout */
1093
+ .conversation-layout {
1094
+ display: grid;
1095
+ grid-template-columns: 200px 1fr;
1096
+ gap: var(--space-32);
1097
+ height: 60vh;
1098
+ }
1099
+
1100
+ .character-side {
1101
+ display: flex;
1102
+ justify-content: center;
1103
+ align-items: flex-start;
1104
+ padding-top: var(--space-32);
1105
+ }
1106
+
1107
+ .chat-area {
1108
+ display: flex;
1109
+ flex-direction: column;
1110
+ background: rgba(0, 0, 0, 0.3);
1111
+ border-radius: var(--radius-lg);
1112
+ border: 1px solid rgba(50, 184, 198, 0.3);
1113
+ overflow: hidden;
1114
+ }
1115
+
1116
+ .chat-messages {
1117
+ flex: 1;
1118
+ padding: var(--space-16);
1119
+ overflow-y: auto;
1120
+ display: flex;
1121
+ flex-direction: column;
1122
+ gap: var(--space-12);
1123
+ }
1124
+
1125
+ .message {
1126
+ max-width: 80%;
1127
+ padding: var(--space-12) var(--space-16);
1128
+ border-radius: var(--radius-lg);
1129
+ word-wrap: break-word;
1130
+ }
1131
+
1132
+ .message.user {
1133
+ align-self: flex-end;
1134
+ background: linear-gradient(135deg, #32b8c6, #8a2be2);
1135
+ color: white;
1136
+ }
1137
+
1138
+ .message.echo {
1139
+ align-self: flex-start;
1140
+ background: rgba(50, 184, 198, 0.1);
1141
+ border: 1px solid rgba(50, 184, 198, 0.3);
1142
+ color: var(--color-text);
1143
+ }
1144
+
1145
+ .chat-input {
1146
+ display: flex;
1147
+ gap: var(--space-8);
1148
+ padding: var(--space-16);
1149
+ border-top: 1px solid rgba(50, 184, 198, 0.3);
1150
+ }
1151
+
1152
+ .chat-input .form-control {
1153
+ flex: 1;
1154
+ background: rgba(0, 0, 0, 0.3);
1155
+ border-color: rgba(50, 184, 198, 0.3);
1156
+ color: var(--color-text);
1157
+ }
1158
+
1159
+ .voice-btn {
1160
+ min-width: 50px;
1161
+ }
1162
+
1163
+ /* Vocabulary Layout */
1164
+ .vocabulary-layout {
1165
+ display: flex;
1166
+ flex-direction: column;
1167
+ align-items: center;
1168
+ gap: var(--space-32);
1169
+ max-width: 600px;
1170
+ margin: 0 auto;
1171
+ }
1172
+
1173
+ .progress-bar {
1174
+ width: 100%;
1175
+ height: 10px;
1176
+ background: rgba(0, 0, 0, 0.3);
1177
+ border-radius: var(--radius-full);
1178
+ overflow: hidden;
1179
+ position: relative;
1180
+ }
1181
+
1182
+ .progress-fill {
1183
+ height: 100%;
1184
+ background: linear-gradient(90deg, #32b8c6, #8a2be2);
1185
+ border-radius: var(--radius-full);
1186
+ transition: width var(--duration-normal) var(--ease-standard);
1187
+ width: 0%;
1188
+ }
1189
+
1190
+ .progress-text {
1191
+ position: absolute;
1192
+ top: 50%;
1193
+ left: 50%;
1194
+ transform: translate(-50%, -50%);
1195
+ color: var(--color-text);
1196
+ font-size: var(--font-size-sm);
1197
+ font-weight: var(--font-weight-bold);
1198
+ }
1199
+
1200
+ .flashcard {
1201
+ width: 400px;
1202
+ height: 250px;
1203
+ perspective: 1000px;
1204
+ cursor: pointer;
1205
+ }
1206
+
1207
+ .card-inner {
1208
+ position: relative;
1209
+ width: 100%;
1210
+ height: 100%;
1211
+ text-align: center;
1212
+ transition: transform 0.6s;
1213
+ transform-style: preserve-3d;
1214
+ }
1215
+
1216
+ .flashcard.flipped .card-inner {
1217
+ transform: rotateY(180deg);
1218
+ }
1219
+
1220
+ .card-front, .card-back {
1221
+ position: absolute;
1222
+ width: 100%;
1223
+ height: 100%;
1224
+ backface-visibility: hidden;
1225
+ background: rgba(0, 0, 0, 0.4);
1226
+ border: 1px solid rgba(50, 184, 198, 0.3);
1227
+ border-radius: var(--radius-lg);
1228
+ display: flex;
1229
+ flex-direction: column;
1230
+ align-items: center;
1231
+ justify-content: center;
1232
+ gap: var(--space-16);
1233
+ padding: var(--space-24);
1234
+ }
1235
+
1236
+ .card-back {
1237
+ transform: rotateY(180deg);
1238
+ }
1239
+
1240
+ .card-front h3, .card-back h3 {
1241
+ color: #32b8c6;
1242
+ font-size: var(--font-size-3xl);
1243
+ margin: 0;
1244
+ }
1245
+
1246
+ .card-controls {
1247
+ display: flex;
1248
+ gap: var(--space-16);
1249
+ }
1250
+
1251
+ /* Pronunciation Layout */
1252
+ .pronunciation-layout {
1253
+ display: flex;
1254
+ flex-direction: column;
1255
+ align-items: center;
1256
+ gap: var(--space-32);
1257
+ max-width: 600px;
1258
+ margin: 0 auto;
1259
+ }
1260
+
1261
+ .practice-word {
1262
+ text-align: center;
1263
+ background: rgba(0, 0, 0, 0.3);
1264
+ padding: var(--space-32);
1265
+ border-radius: var(--radius-lg);
1266
+ border: 1px solid rgba(50, 184, 198, 0.3);
1267
+ }
1268
+
1269
+ .practice-word h3 {
1270
+ color: #32b8c6;
1271
+ font-size: var(--font-size-4xl);
1272
+ margin-bottom: var(--space-16);
1273
+ }
1274
+
1275
+ .recording-area {
1276
+ text-align: center;
1277
+ }
1278
+
1279
+ .record-visualizer {
1280
+ width: 200px;
1281
+ height: 100px;
1282
+ background: rgba(0, 0, 0, 0.3);
1283
+ border: 1px solid rgba(50, 184, 198, 0.3);
1284
+ border-radius: var(--radius-lg);
1285
+ margin-bottom: var(--space-16);
1286
+ position: relative;
1287
+ overflow: hidden;
1288
+ }
1289
+
1290
+ .record-btn {
1291
+ font-size: var(--font-size-lg);
1292
+ padding: var(--space-16) var(--space-32);
1293
+ }
1294
+
1295
+ .record-btn.recording {
1296
+ background: #ff6b9d !important;
1297
+ animation: pulse 1s infinite;
1298
+ }
1299
+
1300
+ .pronunciation-feedback {
1301
+ width: 100%;
1302
+ padding: var(--space-16);
1303
+ background: rgba(0, 0, 0, 0.3);
1304
+ border-radius: var(--radius-lg);
1305
+ border: 1px solid rgba(50, 184, 198, 0.3);
1306
+ text-align: center;
1307
+ }
1308
+
1309
+ @keyframes pulse {
1310
+ 0%, 100% { transform: scale(1); }
1311
+ 50% { transform: scale(1.05); }
1312
+ }
1313
+
1314
+ /* Grammar Layout */
1315
+ .grammar-layout {
1316
+ max-width: 800px;
1317
+ margin: 0 auto;
1318
+ }
1319
+
1320
+ .question-card {
1321
+ background: rgba(0, 0, 0, 0.3);
1322
+ border: 1px solid rgba(50, 184, 198, 0.3);
1323
+ border-radius: var(--radius-lg);
1324
+ padding: var(--space-32);
1325
+ margin-bottom: var(--space-32);
1326
+ }
1327
+
1328
+ .question-card h3 {
1329
+ color: var(--color-text);
1330
+ font-size: var(--font-size-xl);
1331
+ margin-bottom: var(--space-24);
1332
+ text-align: center;
1333
+ }
1334
+
1335
+ .options-grid {
1336
+ display: grid;
1337
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1338
+ gap: var(--space-16);
1339
+ }
1340
+
1341
+ .option-btn {
1342
+ padding: var(--space-16);
1343
+ background: rgba(0, 0, 0, 0.3);
1344
+ border: 1px solid rgba(50, 184, 198, 0.3);
1345
+ color: var(--color-text);
1346
+ border-radius: var(--radius-base);
1347
+ cursor: pointer;
1348
+ transition: all var(--duration-fast) var(--ease-standard);
1349
+ }
1350
+
1351
+ .option-btn:hover {
1352
+ background: rgba(50, 184, 198, 0.1);
1353
+ border-color: rgba(50, 184, 198, 0.6);
1354
+ }
1355
+
1356
+ .option-btn.correct {
1357
+ background: rgba(46, 160, 67, 0.2);
1358
+ border-color: #2ea043;
1359
+ color: #2ea043;
1360
+ }
1361
+
1362
+ .option-btn.incorrect {
1363
+ background: rgba(218, 54, 51, 0.2);
1364
+ border-color: #da3633;
1365
+ color: #da3633;
1366
+ }
1367
+
1368
+ .grammar-feedback {
1369
+ background: rgba(0, 0, 0, 0.3);
1370
+ border: 1px solid rgba(50, 184, 198, 0.3);
1371
+ border-radius: var(--radius-lg);
1372
+ padding: var(--space-24);
1373
+ text-align: center;
1374
+ }
1375
+
1376
+ /* Quick Actions */
1377
+ .quick-actions {
1378
+ position: fixed;
1379
+ bottom: var(--space-24);
1380
+ right: var(--space-24);
1381
+ display: flex;
1382
+ flex-direction: column;
1383
+ gap: var(--space-12);
1384
+ z-index: 1000;
1385
+ }
1386
+
1387
+ .quick-btn {
1388
+ width: 60px;
1389
+ height: 60px;
1390
+ border-radius: 50%;
1391
+ background: rgba(0, 0, 0, 0.6);
1392
+ border: 1px solid rgba(50, 184, 198, 0.3);
1393
+ color: var(--color-text);
1394
+ font-size: var(--font-size-xl);
1395
+ cursor: pointer;
1396
+ backdrop-filter: blur(10px);
1397
+ transition: all var(--duration-fast) var(--ease-standard);
1398
+ }
1399
+
1400
+ .quick-btn:hover {
1401
+ background: rgba(50, 184, 198, 0.2);
1402
+ border-color: rgba(50, 184, 198, 0.6);
1403
+ transform: scale(1.1);
1404
+ }
1405
+
1406
+ /* Responsive Design */
1407
+ @media (max-width: 768px) {
1408
+ .home-layout {
1409
+ grid-template-columns: 1fr;
1410
+ gap: var(--space-24);
1411
+ text-align: center;
1412
+ }
1413
+
1414
+ .echo-character {
1415
+ width: 250px;
1416
+ height: 250px;
1417
+ }
1418
+
1419
+ .modes-grid {
1420
+ grid-template-columns: 1fr;
1421
+ }
1422
+
1423
+ .conversation-layout {
1424
+ grid-template-columns: 1fr;
1425
+ gap: var(--space-16);
1426
+ }
1427
+
1428
+ .character-side {
1429
+ order: -1;
1430
+ padding-top: 0;
1431
+ }
1432
+
1433
+ .flashcard {
1434
+ width: 100%;
1435
+ max-width: 350px;
1436
+ }
1437
+
1438
+ .user-stats {
1439
+ gap: var(--space-16);
1440
+ }
1441
+
1442
+ .quick-actions {
1443
+ bottom: var(--space-16);
1444
+ right: var(--space-16);
1445
+ }
1446
+
1447
+ .quick-btn {
1448
+ width: 50px;
1449
+ height: 50px;
1450
+ font-size: var(--font-size-lg);
1451
+ }
1452
+ }
1453
+
1454
+ /* Additional Styles for Scenarios */
1455
+ .scenarios-layout {
1456
+ max-width: 800px;
1457
+ margin: 0 auto;
1458
+ }
1459
+
1460
+ .scenario-selector {
1461
+ margin-bottom: var(--space-32);
1462
+ }
1463
+
1464
+ .scenario-buttons {
1465
+ display: flex;
1466
+ gap: var(--space-16);
1467
+ flex-wrap: wrap;
1468
+ justify-content: center;
1469
+ }
1470
+
1471
+ .scenario-btn {
1472
+ padding: var(--space-12) var(--space-24);
1473
+ background: rgba(0, 0, 0, 0.3);
1474
+ border: 1px solid rgba(50, 184, 198, 0.3);
1475
+ color: var(--color-text);
1476
+ border-radius: var(--radius-lg);
1477
+ cursor: pointer;
1478
+ transition: all var(--duration-fast) var(--ease-standard);
1479
+ }
1480
+
1481
+ .scenario-btn:hover, .scenario-btn.active {
1482
+ background: rgba(50, 184, 198, 0.2);
1483
+ border-color: rgba(50, 184, 198, 0.6);
1484
+ }
1485
+
1486
+ .scenario-dialogue {
1487
+ background: rgba(0, 0, 0, 0.3);
1488
+ border: 1px solid rgba(50, 184, 198, 0.3);
1489
+ border-radius: var(--radius-lg);
1490
+ padding: var(--space-24);
1491
+ }
1492
+
1493
+ .dialogue-line {
1494
+ margin-bottom: var(--space-16);
1495
+ padding: var(--space-12);
1496
+ border-radius: var(--radius-base);
1497
+ }
1498
+
1499
+ .dialogue-line.waiter, .dialogue-line.interviewer {
1500
+ background: rgba(50, 184, 198, 0.1);
1501
+ border-left: 3px solid #32b8c6;
1502
+ }
1503
+
1504
+ .dialogue-line.customer, .dialogue-line.candidate {
1505
+ background: rgba(138, 43, 226, 0.1);
1506
+ border-left: 3px solid #8a2be2;
1507
+ margin-left: var(--space-24);
1508
+ }
1509
+
1510
+ /* Daily Challenge Styles */
1511
+ .daily-layout {
1512
+ max-width: 600px;
1513
+ margin: 0 auto;
1514
+ }
1515
+
1516
+ .challenge-header {
1517
+ text-align: center;
1518
+ margin-bottom: var(--space-32);
1519
+ }
1520
+
1521
+ .challenge-timer {
1522
+ font-size: var(--font-size-3xl);
1523
+ color: #ff6b9d;
1524
+ font-weight: var(--font-weight-bold);
1525
+ text-shadow: 0 0 10px rgba(255, 107, 157, 0.4);
1526
+ }
1527
+
1528
+ .challenge-content {
1529
+ background: rgba(0, 0, 0, 0.3);
1530
+ border: 1px solid rgba(50, 184, 198, 0.3);
1531
+ border-radius: var(--radius-lg);
1532
+ padding: var(--space-32);
1533
+ margin-bottom: var(--space-32);
1534
+ text-align: center;
1535
+ }
1536
+
1537
+ .challenge-sentence {
1538
+ font-size: var(--font-size-xl);
1539
+ color: #32b8c6;
1540
+ margin: var(--space-24) 0;
1541
+ }
1542
+
1543
+ .word-bank {
1544
+ display: flex;
1545
+ gap: var(--space-12);
1546
+ justify-content: center;
1547
+ flex-wrap: wrap;
1548
+ }
1549
+
1550
+ .word-option {
1551
+ padding: var(--space-8) var(--space-16);
1552
+ background: rgba(138, 43, 226, 0.2);
1553
+ border: 1px solid #8a2be2;
1554
+ color: #8a2be2;
1555
+ border-radius: var(--radius-base);
1556
+ cursor: pointer;
1557
+ transition: all var(--duration-fast) var(--ease-standard);
1558
+ }
1559
+
1560
+ .word-option:hover {
1561
+ background: rgba(138, 43, 226, 0.3);
1562
+ transform: translateY(-2px);
1563
+ }
1564
+
1565
+ .word-option.selected {
1566
+ background: #8a2be2;
1567
+ color: white;
1568
+ }
1569
+
1570
+ /* Progress Screen Styles */
1571
+ .progress-layout {
1572
+ max-width: 800px;
1573
+ margin: 0 auto;
1574
+ }
1575
+
1576
+ .stats-grid {
1577
+ display: grid;
1578
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
1579
+ gap: var(--space-24);
1580
+ margin-bottom: var(--space-32);
1581
+ }
1582
+
1583
+ .stat-card {
1584
+ background: rgba(0, 0, 0, 0.3);
1585
+ border: 1px solid rgba(50, 184, 198, 0.3);
1586
+ border-radius: var(--radius-lg);
1587
+ padding: var(--space-24);
1588
+ text-align: center;
1589
+ }
1590
+
1591
+ .stat-card h3 {
1592
+ color: var(--color-text);
1593
+ margin-bottom: var(--space-16);
1594
+ font-size: var(--font-size-lg);
1595
+ }
1596
+
1597
+ .stat-number {
1598
+ font-size: var(--font-size-4xl);
1599
+ font-weight: var(--font-weight-bold);
1600
+ color: #32b8c6;
1601
+ text-shadow: 0 0 10px rgba(50, 184, 198, 0.4);
1602
+ }
1603
+
1604
+ .achievements-section h3 {
1605
+ color: #32b8c6;
1606
+ margin-bottom: var(--space-24);
1607
+ text-align: center;
1608
+ }
1609
+
1610
+ .achievements-grid {
1611
+ display: grid;
1612
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
1613
+ gap: var(--space-16);
1614
+ }
1615
+
1616
+ .achievement-card {
1617
+ background: rgba(0, 0, 0, 0.3);
1618
+ border: 1px solid rgba(50, 184, 198, 0.3);
1619
+ border-radius: var(--radius-lg);
1620
+ padding: var(--space-16);
1621
+ text-align: center;
1622
+ opacity: 0.5;
1623
+ transition: all var(--duration-fast) var(--ease-standard);
1624
+ }
1625
+
1626
+ .achievement-card.unlocked {
1627
+ opacity: 1;
1628
+ border-color: rgba(255, 215, 0, 0.6);
1629
+ box-shadow: 0 0 20px rgba(255, 215, 0, 0.2);
1630
+ }
1631
+
1632
+ .achievement-icon {
1633
+ font-size: var(--font-size-3xl);
1634
+ margin-bottom: var(--space-8);
1635
+ display: block;
1636
+ }
1637
+
1638
+ .achievement-name {
1639
+ color: #32b8c6;
1640
+ font-weight: var(--font-weight-bold);
1641
+ margin-bottom: var(--space-4);
1642
+ }
1643
+
1644
+ .achievement-description {
1645
+ color: var(--color-text-secondary);
1646
+ font-size: var(--font-size-sm);
1647
+ }
test_echo_light_app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import unittest
2
+ from selenium import webdriver
3
+ from selenium.webdriver.common.by import By
4
+ from selenium.webdriver.common.keys import Keys
5
+ from selenium.webdriver.support.ui import WebDriverWait
6
+ from selenium.webdriver.support import expected_conditions as EC
7
+ import time
8
+
9
+ class EchoLightAppTest(unittest.TestCase):
10
+ @classmethod
11
+ def setUpClass(cls):
12
+ # Adjust the path to your chromedriver if needed
13
+ cls.driver = webdriver.Chrome()
14
+ cls.driver.get('http://localhost:5500/index.html') # Use your local server/port
15
+ cls.driver.maximize_window()
16
+ cls.wait = WebDriverWait(cls.driver, 10)
17
+
18
+ @classmethod
19
+ def tearDownClass(cls):
20
+ cls.driver.quit()
21
+
22
+ def test_1_text_chat(self):
23
+ driver = self.driver
24
+ wait = self.wait
25
+ # Find input and send a message
26
+ input_box = wait.until(EC.presence_of_element_located((By.ID, 'messageInput')))
27
+ input_box.clear()
28
+ input_box.send_keys('Hello, how are you?')
29
+ input_box.send_keys(Keys.ENTER)
30
+ # Wait for echo reply
31
+ wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, '.message.echo')) > 0)
32
+ messages = driver.find_elements(By.CSS_SELECTOR, '.message.echo')
33
+ self.assertTrue(any('how are you' in m.text.lower() or m.text for m in messages))
34
+
35
+ def test_2_empty_message(self):
36
+ driver = self.driver
37
+ wait = self.wait
38
+ input_box = wait.until(EC.presence_of_element_located((By.ID, 'messageInput')))
39
+ input_box.clear()
40
+ input_box.send_keys(' ')
41
+ input_box.send_keys(Keys.ENTER)
42
+ # No new user message should be added
43
+ time.sleep(1)
44
+ user_msgs = driver.find_elements(By.CSS_SELECTOR, '.message.user')
45
+ self.assertFalse(user_msgs[-1].text.strip() == '')
46
+
47
+ def test_3_modes_navigation(self):
48
+ driver = self.driver
49
+ wait = self.wait
50
+ # Modes grid
51
+ modes = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.mode-card')))
52
+ for mode in modes:
53
+ mode.click()
54
+ time.sleep(0.5)
55
+ # Check that the screen is active
56
+ active = driver.find_elements(By.CSS_SELECTOR, '.screen.active')
57
+ self.assertTrue(len(active) > 0)
58
+
59
+ def test_4_vocabulary_card(self):
60
+ driver = self.driver
61
+ wait = self.wait
62
+ # Go to vocabulary mode
63
+ vocab_mode = wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR, '.mode-card')))[1]
64
+ vocab_mode.click()
65
+ time.sleep(0.5)
66
+ # Check card content
67
+ word = driver.find_element(By.ID, 'cardWord').text
68
+ self.assertTrue(len(word) > 0)
69
+ # Next card
70
+ next_btn = driver.find_element(By.ID, 'nextCardBtn') if driver.find_elements(By.ID, 'nextCardBtn') else None
71
+ if next_btn:
72
+ next_btn.click()
73
+ time.sleep(0.5)
74
+ new_word = driver.find_element(By.ID, 'cardWord').text
75
+ self.assertNotEqual(word, new_word)
76
+
77
+ def test_5_achievements(self):
78
+ driver = self.driver
79
+ wait = self.wait
80
+ # Go to achievements/progress screen
81
+ progress_btn = driver.find_element(By.ID, 'progressBtn') if driver.find_elements(By.ID, 'progressBtn') else None
82
+ if progress_btn:
83
+ progress_btn.click()
84
+ time.sleep(0.5)
85
+ achievements = driver.find_elements(By.CSS_SELECTOR, '.achievement-card')
86
+ self.assertTrue(len(achievements) > 0)
87
+
88
+ if __name__ == '__main__':
89
+ unittest.main()