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
50
CLAUDE.md
50
CLAUDE.md
@ -752,3 +752,53 @@ python3 -m http.server 8000
|
||||
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!** 🎯
|
||||
|
||||
## 🤝 **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: {
|
||||
"unhappy": {
|
||||
"user_language": "不开心的",
|
||||
|
||||
@ -13,7 +13,8 @@ class ContentGameCompatibility {
|
||||
'chinese-study': 35,
|
||||
'story-builder': 35,
|
||||
'story-reader': 40,
|
||||
'word-storm': 15
|
||||
'word-storm': 15,
|
||||
'letter-discovery': 60
|
||||
};
|
||||
}
|
||||
|
||||
@ -91,12 +92,15 @@ class ContentGameCompatibility {
|
||||
hasCorrections: this.hasContent(content, 'corrections'),
|
||||
hasComprehension: this.hasContent(content, 'comprehension'),
|
||||
hasMatching: this.hasContent(content, 'matching'),
|
||||
hasLetters: this.hasContent(content, 'letters'),
|
||||
|
||||
// Compteurs
|
||||
vocabularyCount: this.countItems(content, 'vocabulary'),
|
||||
sentenceCount: this.countItems(content, 'sentences'),
|
||||
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':
|
||||
return this.calculateWordStormCompat(capabilities);
|
||||
|
||||
case 'letter-discovery':
|
||||
return this.calculateLetterDiscoveryCompat(capabilities);
|
||||
|
||||
default:
|
||||
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 ===
|
||||
|
||||
hasContent(content, type) {
|
||||
@ -458,6 +495,23 @@ class ContentGameCompatibility {
|
||||
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) {
|
||||
const requirements = {
|
||||
'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é'],
|
||||
'story-builder': ['Dialogues OU 5+ phrases', 'Vocabulaire varié'],
|
||||
'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'];
|
||||
@ -484,7 +539,8 @@ class ContentGameCompatibility {
|
||||
'adventure-reader': 'Aventure narrative nécessitant contenu riche',
|
||||
'chinese-study': 'Optimisé pour apprentissage du chinois',
|
||||
'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';
|
||||
|
||||
@ -199,6 +199,29 @@ class LetterDiscovery {
|
||||
font-size: 1.1em;
|
||||
color: #666;
|
||||
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 */
|
||||
@ -369,51 +392,33 @@ class LetterDiscovery {
|
||||
extractContent() {
|
||||
logSh('🔍 Letter Discovery - Extracting content...', 'INFO');
|
||||
|
||||
// Check if content has letter structure
|
||||
if (this.content.letters) {
|
||||
this.letters = Object.keys(this.content.letters);
|
||||
this.letterWords = this.content.letters;
|
||||
// Check for letters in content or rawContent
|
||||
const letters = this.content.letters || this.content.rawContent?.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');
|
||||
} else {
|
||||
// Fallback: Create letter structure from vocabulary
|
||||
this.generateLetterStructure();
|
||||
}
|
||||
|
||||
if (this.letters.length === 0) {
|
||||
throw new Error('No letters found in content');
|
||||
this.showNoLettersMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
logSh(`🎯 Letter Discovery ready: ${this.letters.length} letters`, 'INFO');
|
||||
}
|
||||
|
||||
generateLetterStructure() {
|
||||
logSh('🔧 Generating letter structure from vocabulary...', 'INFO');
|
||||
|
||||
const letterMap = {};
|
||||
|
||||
if (this.content.vocabulary) {
|
||||
Object.keys(this.content.vocabulary).forEach(word => {
|
||||
const firstLetter = word.charAt(0).toUpperCase();
|
||||
if (!letterMap[firstLetter]) {
|
||||
letterMap[firstLetter] = [];
|
||||
}
|
||||
|
||||
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');
|
||||
showNoLettersMessage() {
|
||||
this.container.innerHTML = `
|
||||
<div class="game-error">
|
||||
<div class="error-content">
|
||||
<h2>🔤 Letter Discovery</h2>
|
||||
<p>❌ No letter structure found in this content.</p>
|
||||
<p>This game requires content with a predefined letters system.</p>
|
||||
<p>Try with content that includes letter-based learning material.</p>
|
||||
<button class="back-btn" onclick="AppNavigation.navigateTo('games')">← Back to Games</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
init() {
|
||||
@ -560,6 +565,8 @@ class LetterDiscovery {
|
||||
<div class="word-text">${word.word}</div>
|
||||
<div class="word-translation">${word.translation}</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">
|
||||
<button class="discovery-btn" onclick="window.currentLetterGame.nextWord()">
|
||||
➡️ Next Word
|
||||
|
||||
@ -844,6 +844,10 @@ class StoryReader {
|
||||
// Check if sentence has word-by-word data (old format) or needs automatic matching
|
||||
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) {
|
||||
// Old format with word-by-word data
|
||||
wordsHtml = data.words.map(wordData => {
|
||||
|
||||
@ -76,7 +76,15 @@ class WordStormGame {
|
||||
}
|
||||
|
||||
.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 {
|
||||
@ -85,9 +93,89 @@ class WordStormGame {
|
||||
}
|
||||
|
||||
@keyframes explode {
|
||||
0% { transform: translateX(-50%) scale(1); opacity: 1; }
|
||||
50% { transform: translateX(-50%) scale(1.2); opacity: 0.8; }
|
||||
100% { transform: translateX(-50%) scale(0.3); opacity: 0; }
|
||||
0% {
|
||||
transform: translateX(-50%) scale(1) rotate(0deg);
|
||||
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) {
|
||||
@ -323,14 +411,26 @@ class WordStormGame {
|
||||
}
|
||||
|
||||
correctAnswer(fallingWord) {
|
||||
// Remove from game
|
||||
// Remove from game with epic explosion
|
||||
if (fallingWord.element.parentNode) {
|
||||
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(() => {
|
||||
if (fallingWord.element.parentNode) {
|
||||
fallingWord.element.remove();
|
||||
}
|
||||
}, 600);
|
||||
}, 800);
|
||||
}
|
||||
|
||||
// Remove from tracking
|
||||
@ -342,10 +442,18 @@ class WordStormGame {
|
||||
this.score += points;
|
||||
this.onScoreUpdate(this.score);
|
||||
|
||||
// Update display
|
||||
// Update display with animation
|
||||
document.getElementById('score').textContent = this.score;
|
||||
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
|
||||
if (this.score > 0 && this.score % 100 === 0) {
|
||||
this.levelUp();
|
||||
@ -356,13 +464,74 @@ class WordStormGame {
|
||||
this.combo = 0;
|
||||
document.getElementById('combo').textContent = this.combo;
|
||||
|
||||
// Flash effect
|
||||
// Enhanced wrong answer animation
|
||||
const answerPanel = document.getElementById('answer-panel');
|
||||
if (answerPanel) {
|
||||
answerPanel.style.background = 'rgba(239, 68, 68, 0.3)';
|
||||
answerPanel.classList.add('wrong-flash');
|
||||
setTimeout(() => {
|
||||
answerPanel.style.background = '';
|
||||
}, 300);
|
||||
answerPanel.classList.remove('wrong-flash');
|
||||
}, 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