Add epic animations to Word Storm good/bad responses
🎉 GOOD ANSWER ANIMATIONS: - Enhanced explosion with color transitions (blue→green→orange→red) - Screen shake effect for impact feedback - Floating points popup (+10, +12, etc.) with smooth animation - Gentle vibration pattern for positive reinforcement ❌ BAD ANSWER ANIMATIONS: - Red shake animation for all falling words - Answer panel flash with red glow effect - Full screen red overlay flash - Strong vibration pattern for negative feedback 🎨 TECHNICAL IMPROVEMENTS: - New CSS keyframes: explode, wrongShake, wrongFlash, screenShake, pointsFloat - Enhanced correctAnswer() method with screen shake and points popup - Enhanced wrongAnswer() method with multi-element animations - Vibration API integration for tactile feedback - Proper animation cleanup and timing 🎯 UX ENHANCEMENT: - Much more satisfying and engaging gameplay experience - Clear visual distinction between success and failure - Gamification elements that motivate continued play 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e67e40f09b
commit
638c734578
52
CLAUDE.md
52
CLAUDE.md
@ -751,4 +751,54 @@ python3 -m http.server 8000
|
|||||||
4. **Verify content compatibility** - Make sure your content has the data your game needs
|
4. **Verify content compatibility** - Make sure your content has the data your game needs
|
||||||
5. **Use global CSS classes** - Don't reinvent layout, build on existing structure
|
5. **Use global CSS classes** - Don't reinvent layout, build on existing structure
|
||||||
|
|
||||||
**Remember: Most bugs are simple syntax errors (especially template literals) or missing module registrations. Check these first!** 🎯
|
**Remember: Most bugs are simple syntax errors (especially template literals) or missing module registrations. Check these first!** 🎯
|
||||||
|
|
||||||
|
## 🤝 **Collaborative Development Best Practices**
|
||||||
|
|
||||||
|
**Critical lesson learned from real debugging sessions:**
|
||||||
|
|
||||||
|
### **✅ Always Test Before Committing**
|
||||||
|
|
||||||
|
**❌ BAD WORKFLOW:**
|
||||||
|
1. Write code
|
||||||
|
2. Immediately commit
|
||||||
|
3. Discover it doesn't work
|
||||||
|
4. Debug on committed broken code
|
||||||
|
|
||||||
|
**✅ GOOD WORKFLOW:**
|
||||||
|
1. Write code
|
||||||
|
2. **TEST THOROUGHLY**
|
||||||
|
3. If broken → debug cooperatively
|
||||||
|
4. When working → commit
|
||||||
|
|
||||||
|
### **🔍 Cooperative Debugging Method**
|
||||||
|
|
||||||
|
**When user reports: "ça marche pas" or "y'a pas de lettres":**
|
||||||
|
|
||||||
|
1. **Get specific symptoms** - Don't assume, ask exactly what they see
|
||||||
|
2. **Add targeted debug logs** - Console.log the exact variables in question
|
||||||
|
3. **Test together** - Have user run and report console output
|
||||||
|
4. **Analyze together** - Look at debug output to find root cause
|
||||||
|
5. **Fix precisely** - Target the exact issue, don't rewrite everything
|
||||||
|
|
||||||
|
**Real Example - Letter Discovery Issue:**
|
||||||
|
```javascript
|
||||||
|
// ❌ ASSUMPTION: "Letters not working, must rewrite everything"
|
||||||
|
|
||||||
|
// ✅ ACTUAL DEBUG:
|
||||||
|
console.log('🔍 DEBUG this.content.letters:', this.content.letters); // undefined
|
||||||
|
console.log('🔍 DEBUG this.content.rawContent?.letters:', this.content.rawContent?.letters); // {U: Array(4), V: Array(4), T: Array(4)}
|
||||||
|
|
||||||
|
// ✅ PRECISE FIX: Check both locations
|
||||||
|
const letters = this.content.letters || this.content.rawContent?.letters;
|
||||||
|
```
|
||||||
|
|
||||||
|
### **🎯 Key Principles**
|
||||||
|
|
||||||
|
- **Communication > Code** - Clear problem description saves hours
|
||||||
|
- **Debug logs > Assumptions** - Add console.log to see actual data
|
||||||
|
- **Test early, test often** - Don't tunnel vision on untested code
|
||||||
|
- **Pair debugging** - Two brains spot issues faster than one
|
||||||
|
- **Patience > Speed** - Taking time to understand beats rushing broken fixes
|
||||||
|
|
||||||
|
**"C'est mieux quand on prend notre temps en coop plutot que de tunnel vision !"** 🎯
|
||||||
@ -298,6 +298,100 @@ window.ContentModules.WTA1B1 = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// === LETTERS DISCOVERY SYSTEM ===
|
||||||
|
letters: {
|
||||||
|
"U": [
|
||||||
|
{
|
||||||
|
word: "unhappy",
|
||||||
|
translation: "不开心的",
|
||||||
|
type: "adjective",
|
||||||
|
pronunciation: "ʌnhæpi",
|
||||||
|
example: "The cat looks unhappy."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "umbrella",
|
||||||
|
translation: "雨伞",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "ʌmbrɛlə",
|
||||||
|
example: "I need an umbrella when it rains."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "up",
|
||||||
|
translation: "向上",
|
||||||
|
type: "adverb",
|
||||||
|
pronunciation: "ʌp",
|
||||||
|
example: "The bird flies up high."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "under",
|
||||||
|
translation: "在...下面",
|
||||||
|
type: "preposition",
|
||||||
|
pronunciation: "ʌndər",
|
||||||
|
example: "The cat hides under the table."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"V": [
|
||||||
|
{
|
||||||
|
word: "violet",
|
||||||
|
translation: "紫色的",
|
||||||
|
type: "adjective",
|
||||||
|
pronunciation: "vaɪələt",
|
||||||
|
example: "She has a violet dress."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "van",
|
||||||
|
translation: "面包车",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "væn",
|
||||||
|
example: "The vet drives a white van."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "vet",
|
||||||
|
translation: "兽医",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "vɛt",
|
||||||
|
example: "The vet takes care of pets."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "vest",
|
||||||
|
translation: "背心",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "vɛst",
|
||||||
|
example: "He wears a warm vest."
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"T": [
|
||||||
|
{
|
||||||
|
word: "tall",
|
||||||
|
translation: "高的",
|
||||||
|
type: "adjective",
|
||||||
|
pronunciation: "tɔl",
|
||||||
|
example: "The teacher is very tall."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "turtle",
|
||||||
|
translation: "海龟",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "tɜrtəl",
|
||||||
|
example: "The turtle moves slowly."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "tent",
|
||||||
|
translation: "帐篷",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "tɛnt",
|
||||||
|
example: "We sleep in a tent when camping."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
word: "tiger",
|
||||||
|
translation: "老虎",
|
||||||
|
type: "noun",
|
||||||
|
pronunciation: "taɪgər",
|
||||||
|
example: "The tiger is a big cat."
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
|
||||||
vocabulary: {
|
vocabulary: {
|
||||||
"unhappy": {
|
"unhappy": {
|
||||||
"user_language": "不开心的",
|
"user_language": "不开心的",
|
||||||
|
|||||||
@ -13,7 +13,8 @@ class ContentGameCompatibility {
|
|||||||
'chinese-study': 35,
|
'chinese-study': 35,
|
||||||
'story-builder': 35,
|
'story-builder': 35,
|
||||||
'story-reader': 40,
|
'story-reader': 40,
|
||||||
'word-storm': 15
|
'word-storm': 15,
|
||||||
|
'letter-discovery': 60
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,12 +92,15 @@ class ContentGameCompatibility {
|
|||||||
hasCorrections: this.hasContent(content, 'corrections'),
|
hasCorrections: this.hasContent(content, 'corrections'),
|
||||||
hasComprehension: this.hasContent(content, 'comprehension'),
|
hasComprehension: this.hasContent(content, 'comprehension'),
|
||||||
hasMatching: this.hasContent(content, 'matching'),
|
hasMatching: this.hasContent(content, 'matching'),
|
||||||
|
hasLetters: this.hasContent(content, 'letters'),
|
||||||
|
|
||||||
// Compteurs
|
// Compteurs
|
||||||
vocabularyCount: this.countItems(content, 'vocabulary'),
|
vocabularyCount: this.countItems(content, 'vocabulary'),
|
||||||
sentenceCount: this.countItems(content, 'sentences'),
|
sentenceCount: this.countItems(content, 'sentences'),
|
||||||
dialogueCount: this.countItems(content, 'dialogues'),
|
dialogueCount: this.countItems(content, 'dialogues'),
|
||||||
grammarCount: this.countItems(content, 'grammar')
|
grammarCount: this.countItems(content, 'grammar'),
|
||||||
|
letterCount: this.countItems(content, 'letters'),
|
||||||
|
letterWordsCount: this.calculateAverageWordsPerLetter(content)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +137,9 @@ class ContentGameCompatibility {
|
|||||||
case 'word-storm':
|
case 'word-storm':
|
||||||
return this.calculateWordStormCompat(capabilities);
|
return this.calculateWordStormCompat(capabilities);
|
||||||
|
|
||||||
|
case 'letter-discovery':
|
||||||
|
return this.calculateLetterDiscoveryCompat(capabilities);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return { compatible: true, score: 50, reason: 'Jeu non spécifiquement analysé' };
|
return { compatible: true, score: 50, reason: 'Jeu non spécifiquement analysé' };
|
||||||
}
|
}
|
||||||
@ -390,6 +397,36 @@ class ContentGameCompatibility {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateLetterDiscoveryCompat(capabilities) {
|
||||||
|
let score = 0;
|
||||||
|
const reasons = [];
|
||||||
|
|
||||||
|
// Letter Discovery requires predefined letters structure
|
||||||
|
if (capabilities.hasLetters) {
|
||||||
|
score += 80;
|
||||||
|
const letterCount = capabilities.letterCount || 'unknown';
|
||||||
|
reasons.push(`Structure de lettres prédéfinie (${letterCount} lettres)`);
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
compatible: false,
|
||||||
|
score: 0,
|
||||||
|
reason: 'Nécessite une structure de lettres prédéfinie (content.letters)'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bonus for well-structured letter content
|
||||||
|
if (capabilities.letterWordsCount && capabilities.letterWordsCount >= 3) {
|
||||||
|
score += 20;
|
||||||
|
reasons.push(`${capabilities.letterWordsCount} mots par lettre en moyenne`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
compatible: score >= 60,
|
||||||
|
score,
|
||||||
|
reason: score >= 60 ? `Compatible: ${reasons.join(', ')}` : 'Structure de lettres insuffisante'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// === UTILITAIRES ===
|
// === UTILITAIRES ===
|
||||||
|
|
||||||
hasContent(content, type) {
|
hasContent(content, type) {
|
||||||
@ -458,6 +495,23 @@ class ContentGameCompatibility {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
calculateAverageWordsPerLetter(content) {
|
||||||
|
const letters = content.letters || content.rawContent?.letters || content.adaptedContent?.letters;
|
||||||
|
if (!letters || typeof letters !== 'object') return 0;
|
||||||
|
|
||||||
|
let totalWords = 0;
|
||||||
|
let letterCount = 0;
|
||||||
|
|
||||||
|
Object.values(letters).forEach(letterWords => {
|
||||||
|
if (Array.isArray(letterWords)) {
|
||||||
|
totalWords += letterWords.length;
|
||||||
|
letterCount++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return letterCount > 0 ? Math.round(totalWords / letterCount) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
getGameRequirements(gameType) {
|
getGameRequirements(gameType) {
|
||||||
const requirements = {
|
const requirements = {
|
||||||
'whack-a-mole': ['5+ mots de vocabulaire OU 3+ phrases', 'Contenu simple et répétitif'],
|
'whack-a-mole': ['5+ mots de vocabulaire OU 3+ phrases', 'Contenu simple et répétitif'],
|
||||||
@ -469,7 +523,8 @@ class ContentGameCompatibility {
|
|||||||
'chinese-study': ['Vocabulaire et phrases chinoises', 'Audio recommandé'],
|
'chinese-study': ['Vocabulaire et phrases chinoises', 'Audio recommandé'],
|
||||||
'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'],
|
'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'],
|
||||||
'story-reader': ['Textes à lire, dialogues recommandés', 'Contenu narratif'],
|
'story-reader': ['Textes à lire, dialogues recommandés', 'Contenu narratif'],
|
||||||
'word-storm': ['3+ mots de vocabulaire', 'Prononciations recommandées']
|
'word-storm': ['3+ mots de vocabulaire', 'Prononciations recommandées'],
|
||||||
|
'letter-discovery': ['Structure de lettres prédéfinie (content.letters)', 'Lettres avec mots associés']
|
||||||
};
|
};
|
||||||
|
|
||||||
return requirements[gameType] || ['Contenu de base'];
|
return requirements[gameType] || ['Contenu de base'];
|
||||||
@ -484,7 +539,8 @@ class ContentGameCompatibility {
|
|||||||
'adventure-reader': 'Aventure narrative nécessitant contenu riche',
|
'adventure-reader': 'Aventure narrative nécessitant contenu riche',
|
||||||
'chinese-study': 'Optimisé pour apprentissage du chinois',
|
'chinese-study': 'Optimisé pour apprentissage du chinois',
|
||||||
'story-builder': 'Construction narrative nécessitant éléments variés',
|
'story-builder': 'Construction narrative nécessitant éléments variés',
|
||||||
'story-reader': 'Lecture d\'histoires nécessitant contenu narratif'
|
'story-reader': 'Lecture d\'histoires nécessitant contenu narratif',
|
||||||
|
'letter-discovery': 'Apprentissage par lettres nécessitant structure prédéfinie'
|
||||||
};
|
};
|
||||||
|
|
||||||
return reasons[gameType] || 'Compatibilité non évaluée spécifiquement';
|
return reasons[gameType] || 'Compatibilité non évaluée spécifiquement';
|
||||||
|
|||||||
@ -199,6 +199,29 @@ class LetterDiscovery {
|
|||||||
font-size: 1.1em;
|
font-size: 1.1em;
|
||||||
color: #666;
|
color: #666;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-type {
|
||||||
|
font-size: 0.9em;
|
||||||
|
color: #667eea;
|
||||||
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
padding: 4px 12px;
|
||||||
|
border-radius: 15px;
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.word-example {
|
||||||
|
font-size: 1em;
|
||||||
|
color: #555;
|
||||||
|
font-style: italic;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: rgba(0, 0, 0, 0.05);
|
||||||
|
border-left: 3px solid #667eea;
|
||||||
|
border-radius: 0 8px 8px 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Practice Challenge Styles */
|
/* Practice Challenge Styles */
|
||||||
@ -369,51 +392,33 @@ class LetterDiscovery {
|
|||||||
extractContent() {
|
extractContent() {
|
||||||
logSh('🔍 Letter Discovery - Extracting content...', 'INFO');
|
logSh('🔍 Letter Discovery - Extracting content...', 'INFO');
|
||||||
|
|
||||||
// Check if content has letter structure
|
// Check for letters in content or rawContent
|
||||||
if (this.content.letters) {
|
const letters = this.content.letters || this.content.rawContent?.letters;
|
||||||
this.letters = Object.keys(this.content.letters);
|
|
||||||
this.letterWords = this.content.letters;
|
if (letters && Object.keys(letters).length > 0) {
|
||||||
|
this.letters = Object.keys(letters).sort();
|
||||||
|
this.letterWords = letters;
|
||||||
logSh(`📝 Found ${this.letters.length} letters with words`, 'INFO');
|
logSh(`📝 Found ${this.letters.length} letters with words`, 'INFO');
|
||||||
} else {
|
} else {
|
||||||
// Fallback: Create letter structure from vocabulary
|
this.showNoLettersMessage();
|
||||||
this.generateLetterStructure();
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
if (this.letters.length === 0) {
|
|
||||||
throw new Error('No letters found in content');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logSh(`🎯 Letter Discovery ready: ${this.letters.length} letters`, 'INFO');
|
logSh(`🎯 Letter Discovery ready: ${this.letters.length} letters`, 'INFO');
|
||||||
}
|
}
|
||||||
|
|
||||||
generateLetterStructure() {
|
showNoLettersMessage() {
|
||||||
logSh('🔧 Generating letter structure from vocabulary...', 'INFO');
|
this.container.innerHTML = `
|
||||||
|
<div class="game-error">
|
||||||
const letterMap = {};
|
<div class="error-content">
|
||||||
|
<h2>🔤 Letter Discovery</h2>
|
||||||
if (this.content.vocabulary) {
|
<p>❌ No letter structure found in this content.</p>
|
||||||
Object.keys(this.content.vocabulary).forEach(word => {
|
<p>This game requires content with a predefined letters system.</p>
|
||||||
const firstLetter = word.charAt(0).toUpperCase();
|
<p>Try with content that includes letter-based learning material.</p>
|
||||||
if (!letterMap[firstLetter]) {
|
<button class="back-btn" onclick="AppNavigation.navigateTo('games')">← Back to Games</button>
|
||||||
letterMap[firstLetter] = [];
|
</div>
|
||||||
}
|
</div>
|
||||||
|
`;
|
||||||
const wordData = this.content.vocabulary[word];
|
|
||||||
letterMap[firstLetter].push({
|
|
||||||
word: word,
|
|
||||||
translation: typeof wordData === 'string' ? wordData : wordData.translation || wordData.user_language,
|
|
||||||
pronunciation: wordData.pronunciation || wordData.prononciation,
|
|
||||||
type: wordData.type,
|
|
||||||
image: wordData.image,
|
|
||||||
audioFile: wordData.audioFile
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.letters = Object.keys(letterMap).sort();
|
|
||||||
this.letterWords = letterMap;
|
|
||||||
|
|
||||||
logSh(`📝 Generated ${this.letters.length} letters from vocabulary`, 'INFO');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
@ -560,6 +565,8 @@ class LetterDiscovery {
|
|||||||
<div class="word-text">${word.word}</div>
|
<div class="word-text">${word.word}</div>
|
||||||
<div class="word-translation">${word.translation}</div>
|
<div class="word-translation">${word.translation}</div>
|
||||||
${word.pronunciation ? `<div class="word-pronunciation">[${word.pronunciation}]</div>` : ''}
|
${word.pronunciation ? `<div class="word-pronunciation">[${word.pronunciation}]</div>` : ''}
|
||||||
|
${word.type ? `<div class="word-type">${word.type}</div>` : ''}
|
||||||
|
${word.example ? `<div class="word-example">"${word.example}"</div>` : ''}
|
||||||
<div class="letter-controls">
|
<div class="letter-controls">
|
||||||
<button class="discovery-btn" onclick="window.currentLetterGame.nextWord()">
|
<button class="discovery-btn" onclick="window.currentLetterGame.nextWord()">
|
||||||
➡️ Next Word
|
➡️ Next Word
|
||||||
|
|||||||
@ -844,6 +844,10 @@ class StoryReader {
|
|||||||
// Check if sentence has word-by-word data (old format) or needs automatic matching
|
// Check if sentence has word-by-word data (old format) or needs automatic matching
|
||||||
let wordsHtml;
|
let wordsHtml;
|
||||||
|
|
||||||
|
console.log('🔍 DEBUG: sentence data:', data);
|
||||||
|
console.log('🔍 DEBUG: data.words exists?', !!data.words);
|
||||||
|
console.log('🔍 DEBUG: data.words length:', data.words ? data.words.length : 'N/A');
|
||||||
|
|
||||||
if (data.words && data.words.length > 0) {
|
if (data.words && data.words.length > 0) {
|
||||||
// Old format with word-by-word data
|
// Old format with word-by-word data
|
||||||
wordsHtml = data.words.map(wordData => {
|
wordsHtml = data.words.map(wordData => {
|
||||||
|
|||||||
@ -76,7 +76,15 @@ class WordStormGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.falling-word.exploding {
|
.falling-word.exploding {
|
||||||
animation: explode 0.6s ease-out forwards;
|
animation: explode 0.8s ease-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.falling-word.wrong-shake {
|
||||||
|
animation: wrongShake 0.6s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.answer-panel.wrong-flash {
|
||||||
|
animation: wrongFlash 0.5s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes wordGlow {
|
@keyframes wordGlow {
|
||||||
@ -85,9 +93,89 @@ class WordStormGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes explode {
|
@keyframes explode {
|
||||||
0% { transform: translateX(-50%) scale(1); opacity: 1; }
|
0% {
|
||||||
50% { transform: translateX(-50%) scale(1.2); opacity: 0.8; }
|
transform: translateX(-50%) scale(1) rotate(0deg);
|
||||||
100% { transform: translateX(-50%) scale(0.3); opacity: 0; }
|
opacity: 1;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), 0 0 20px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
25% {
|
||||||
|
transform: translateX(-50%) scale(1.3) rotate(5deg);
|
||||||
|
opacity: 0.9;
|
||||||
|
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
|
||||||
|
box-shadow: 0 8px 25px rgba(16, 185, 129, 0.5), 0 0 40px rgba(16, 185, 129, 0.8);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: translateX(-50%) scale(1.5) rotate(-3deg);
|
||||||
|
opacity: 0.7;
|
||||||
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
|
||||||
|
box-shadow: 0 12px 35px rgba(245, 158, 11, 0.6), 0 0 60px rgba(245, 158, 11, 0.9);
|
||||||
|
}
|
||||||
|
75% {
|
||||||
|
transform: translateX(-50%) scale(0.8) rotate(2deg);
|
||||||
|
opacity: 0.4;
|
||||||
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-50%) scale(0.1) rotate(0deg);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wrongShake {
|
||||||
|
0%, 100% {
|
||||||
|
transform: translateX(-50%) scale(1);
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
}
|
||||||
|
10%, 30%, 50%, 70%, 90% {
|
||||||
|
transform: translateX(-60%) scale(0.95);
|
||||||
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8);
|
||||||
|
}
|
||||||
|
20%, 40%, 60%, 80% {
|
||||||
|
transform: translateX(-40%) scale(0.95);
|
||||||
|
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
|
||||||
|
box-shadow: 0 4px 15px rgba(239, 68, 68, 0.6), 0 0 25px rgba(239, 68, 68, 0.8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wrongFlash {
|
||||||
|
0%, 100% {
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
background: rgba(239, 68, 68, 0.4);
|
||||||
|
box-shadow: 0 0 20px rgba(239, 68, 68, 0.6), inset 0 0 20px rgba(239, 68, 68, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes screenShake {
|
||||||
|
0%, 100% { transform: translateX(0); }
|
||||||
|
10% { transform: translateX(-3px) translateY(1px); }
|
||||||
|
20% { transform: translateX(3px) translateY(-1px); }
|
||||||
|
30% { transform: translateX(-2px) translateY(2px); }
|
||||||
|
40% { transform: translateX(2px) translateY(-2px); }
|
||||||
|
50% { transform: translateX(-1px) translateY(1px); }
|
||||||
|
60% { transform: translateX(1px) translateY(-1px); }
|
||||||
|
70% { transform: translateX(-2px) translateY(0px); }
|
||||||
|
80% { transform: translateX(2px) translateY(1px); }
|
||||||
|
90% { transform: translateX(-1px) translateY(-1px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pointsFloat {
|
||||||
|
0% {
|
||||||
|
transform: translateY(0) scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
30% {
|
||||||
|
transform: translateY(-20px) scale(1.3);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateY(-80px) scale(0.5);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
@ -323,14 +411,26 @@ class WordStormGame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
correctAnswer(fallingWord) {
|
correctAnswer(fallingWord) {
|
||||||
// Remove from game
|
// Remove from game with epic explosion
|
||||||
if (fallingWord.element.parentNode) {
|
if (fallingWord.element.parentNode) {
|
||||||
fallingWord.element.classList.add('exploding');
|
fallingWord.element.classList.add('exploding');
|
||||||
|
|
||||||
|
// Add screen shake effect
|
||||||
|
const gameArea = document.getElementById('game-area');
|
||||||
|
if (gameArea) {
|
||||||
|
gameArea.style.animation = 'none';
|
||||||
|
gameArea.offsetHeight; // Force reflow
|
||||||
|
gameArea.style.animation = 'screenShake 0.3s ease-in-out';
|
||||||
|
setTimeout(() => {
|
||||||
|
gameArea.style.animation = '';
|
||||||
|
}, 300);
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (fallingWord.element.parentNode) {
|
if (fallingWord.element.parentNode) {
|
||||||
fallingWord.element.remove();
|
fallingWord.element.remove();
|
||||||
}
|
}
|
||||||
}, 600);
|
}, 800);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove from tracking
|
// Remove from tracking
|
||||||
@ -342,10 +442,18 @@ class WordStormGame {
|
|||||||
this.score += points;
|
this.score += points;
|
||||||
this.onScoreUpdate(this.score);
|
this.onScoreUpdate(this.score);
|
||||||
|
|
||||||
// Update display
|
// Update display with animation
|
||||||
document.getElementById('score').textContent = this.score;
|
document.getElementById('score').textContent = this.score;
|
||||||
document.getElementById('combo').textContent = this.combo;
|
document.getElementById('combo').textContent = this.combo;
|
||||||
|
|
||||||
|
// Add points popup animation
|
||||||
|
this.showPointsPopup(points, fallingWord.element);
|
||||||
|
|
||||||
|
// Vibration feedback (if supported)
|
||||||
|
if (navigator.vibrate) {
|
||||||
|
navigator.vibrate([50, 30, 50]);
|
||||||
|
}
|
||||||
|
|
||||||
// Level up check
|
// Level up check
|
||||||
if (this.score > 0 && this.score % 100 === 0) {
|
if (this.score > 0 && this.score % 100 === 0) {
|
||||||
this.levelUp();
|
this.levelUp();
|
||||||
@ -356,13 +464,74 @@ class WordStormGame {
|
|||||||
this.combo = 0;
|
this.combo = 0;
|
||||||
document.getElementById('combo').textContent = this.combo;
|
document.getElementById('combo').textContent = this.combo;
|
||||||
|
|
||||||
// Flash effect
|
// Enhanced wrong answer animation
|
||||||
const answerPanel = document.getElementById('answer-panel');
|
const answerPanel = document.getElementById('answer-panel');
|
||||||
if (answerPanel) {
|
if (answerPanel) {
|
||||||
answerPanel.style.background = 'rgba(239, 68, 68, 0.3)';
|
answerPanel.classList.add('wrong-flash');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
answerPanel.style.background = '';
|
answerPanel.classList.remove('wrong-flash');
|
||||||
}, 300);
|
}, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shake all falling words to show disappointment
|
||||||
|
this.fallingWords.forEach(fw => {
|
||||||
|
if (fw.element.parentNode && !fw.element.classList.contains('exploding')) {
|
||||||
|
fw.element.classList.add('wrong-shake');
|
||||||
|
setTimeout(() => {
|
||||||
|
fw.element.classList.remove('wrong-shake');
|
||||||
|
}, 600);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Screen flash red
|
||||||
|
const gameArea = document.getElementById('game-area');
|
||||||
|
if (gameArea) {
|
||||||
|
const overlay = document.createElement('div');
|
||||||
|
overlay.style.cssText = `
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba(239, 68, 68, 0.3);
|
||||||
|
pointer-events: none;
|
||||||
|
animation: wrongFlash 0.4s ease-in-out;
|
||||||
|
z-index: 100;
|
||||||
|
`;
|
||||||
|
gameArea.appendChild(overlay);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (overlay.parentNode) overlay.remove();
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrong answer vibration (stronger/longer)
|
||||||
|
if (navigator.vibrate) {
|
||||||
|
navigator.vibrate([200, 100, 200, 100, 200]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showPointsPopup(points, wordElement) {
|
||||||
|
const popup = document.createElement('div');
|
||||||
|
popup.textContent = `+${points}`;
|
||||||
|
popup.style.cssText = `
|
||||||
|
position: absolute;
|
||||||
|
left: ${wordElement.style.left};
|
||||||
|
top: ${wordElement.offsetTop}px;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #10b981;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5);
|
||||||
|
animation: pointsFloat 1.5s ease-out forwards;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const gameArea = document.getElementById('game-area');
|
||||||
|
if (gameArea) {
|
||||||
|
gameArea.appendChild(popup);
|
||||||
|
setTimeout(() => {
|
||||||
|
if (popup.parentNode) popup.remove();
|
||||||
|
}, 1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user