diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..7f0c4fc --- /dev/null +++ b/TODO.md @@ -0,0 +1,13 @@ +# TODO List - Class Generator 2.0 + +## Game Improvements + +- [ ] **Whack-a-Mole**: Adjust size to be more flexible +- [ ] **Whack-a-Mole**: Hard mode is too difficult +- [ ] **Whack-a-Mole**: Add speed meter +- [ ] **Adventure Reader**: Add restart button at the top +- [ ] **Riverrun**: Players don't understand the game mechanics +- [ ] **Mario**: Handle touchscreen controls +- [ ] **Sentence Invaders**: Make harder with time +- [ ] **Wizard**: Crashes on WeChat +- [ ] **Wizard**: Crashes with WTE (Word Translation Exercise) diff --git a/src/games/MarioEducational.js b/src/games/MarioEducational.js index dd06104..afcb57d 100644 --- a/src/games/MarioEducational.js +++ b/src/games/MarioEducational.js @@ -116,6 +116,9 @@ class MarioEducational extends Module { this._handleMouseDown = null; this._handleMouseUp = null; this._handleMouseMove = null; + this._handleTouchStart = null; + this._handleTouchEnd = null; + this._handleTouchMove = null; // Mouse control state this._mousePressed = false; @@ -274,6 +277,9 @@ class MarioEducational extends Module { this._canvas.removeEventListener('mouseup', this._handleMouseUp); this._canvas.removeEventListener('mousemove', this._handleMouseMove); this._canvas.removeEventListener('mouseleave', this._handleMouseUp); + this._canvas.removeEventListener('touchstart', this._handleTouchStart); + this._canvas.removeEventListener('touchend', this._handleTouchEnd); + this._canvas.removeEventListener('touchmove', this._handleTouchMove); } // Clear canvas @@ -470,6 +476,46 @@ class MarioEducational extends Module { this._mouseWorldPos.y = canvasY; }; + // Touch event handlers for touchscreen support + this._handleTouchStart = (e) => { + if (this._isGameOver || this._isPaused || this._isQuestionActive) return; + + // Only use the first touch point (ignore multi-touch) + const touch = e.touches[0]; + const rect = this._canvas.getBoundingClientRect(); + const canvasX = touch.clientX - rect.left; + const canvasY = touch.clientY - rect.top; + + // Convert to world coordinates (account for camera) + this._mouseWorldPos.x = canvasX + this._camera.x; + this._mouseWorldPos.y = canvasY; + this._mousePressed = true; + + console.log(`👆 Touch down at world: (${this._mouseWorldPos.x.toFixed(0)}, ${this._mouseWorldPos.y.toFixed(0)})`); + }; + + this._handleTouchEnd = (e) => { + this._mousePressed = false; + this._mouseTarget.x = null; + this._mouseTarget.y = null; + console.log(`👆 Touch released`); + }; + + this._handleTouchMove = (e) => { + if (!this._mousePressed) return; + if (this._isGameOver || this._isPaused || this._isQuestionActive) return; + + // Only use the first touch point (ignore multi-touch) + const touch = e.touches[0]; + const rect = this._canvas.getBoundingClientRect(); + const canvasX = touch.clientX - rect.left; + const canvasY = touch.clientY - rect.top; + + // Convert to world coordinates + this._mouseWorldPos.x = canvasX + this._camera.x; + this._mouseWorldPos.y = canvasY; + }; + document.addEventListener('keydown', this._handleKeyDown); document.addEventListener('keyup', this._handleKeyUp); @@ -479,7 +525,14 @@ class MarioEducational extends Module { this._canvas.addEventListener('mousemove', this._handleMouseMove); // Also handle mouse leaving canvas this._canvas.addEventListener('mouseleave', this._handleMouseUp); + + // Add touch event listeners for touchscreen support + this._canvas.addEventListener('touchstart', this._handleTouchStart); + this._canvas.addEventListener('touchend', this._handleTouchEnd); + this._canvas.addEventListener('touchmove', this._handleTouchMove); + console.log('🖱️ Mouse event listeners attached to canvas'); + console.log('👆 Touch event listeners attached to canvas'); } else { console.error('❌ Canvas not found when setting up mouse handlers!'); } diff --git a/src/games/RiverRun.js b/src/games/RiverRun.js index 7510cff..645b13e 100644 --- a/src/games/RiverRun.js +++ b/src/games/RiverRun.js @@ -334,19 +334,19 @@ class RiverRun extends Module { return; } + // Check if clicked on a word (canvas element) + if (e.target.classList.contains('floating-word')) { + e.stopPropagation(); + this._handleWordClick(e.target); + return; + } + const rect = riverGame.getBoundingClientRect(); const clickX = ((e.clientX - rect.left) / rect.width) * 100; const clickY = ((e.clientY - rect.top) / rect.height) * 100; this._movePlayer(clickX, clickY); }); - - riverGame.addEventListener('click', (e) => { - if (e.target.classList.contains('floating-word')) { - e.stopPropagation(); - this._handleWordClick(e.target); - } - }); } _start() { @@ -432,27 +432,31 @@ class RiverRun extends Module { this._wordsSpawnedSinceTarget++; } - const wordElement = document.createElement('div'); - wordElement.className = 'floating-word'; + // Calculate size based on word length (longer word = bigger trunk) + const wordLength = word.french.length; + const baseSize = 40; // Base trunk size + const sizeMultiplier = 1 + (wordLength * 0.15); // Each character adds 15% size + const trunkWidth = Math.min(baseSize * sizeMultiplier, 120); // Max trunk width + const trunkHeight = baseSize * 0.8; // Trunk is slightly flatter than wide - const spacePadding = ' '.repeat(this._level * 2); - wordElement.textContent = spacePadding + word.french + spacePadding; + const wordElement = document.createElement('canvas'); + wordElement.className = 'floating-word'; + wordElement.width = trunkWidth; + wordElement.height = trunkHeight + 30; // Extra space for text + wordElement.style.position = 'absolute'; + wordElement.style.cursor = 'pointer'; // More random positioning with different strategies let xPosition; const strategy = Math.random(); if (strategy < 0.4) { - // Random across full width (with margins) xPosition = Math.random() * 80 + 10; } else if (strategy < 0.6) { - // Prefer left side xPosition = Math.random() * 40 + 10; } else if (strategy < 0.8) { - // Prefer right side xPosition = Math.random() * 40 + 50; } else { - // Prefer center xPosition = Math.random() * 30 + 35; } @@ -461,15 +465,21 @@ class RiverRun extends Module { wordElement.style.left = `${xPosition}%`; wordElement.style.top = `${yStart}px`; + wordElement.style.transform = `translateX(-${trunkWidth / 2}px)`; // Center the canvas wordElement.wordData = word; + // Draw the trunk on the canvas + this._drawTrunk(wordElement, word.french, trunkWidth, trunkHeight); + riverCanvas.appendChild(wordElement); this._floatingWords.push({ element: wordElement, y: yStart, x: xPosition, - wordData: word + wordData: word, + trunkWidth: trunkWidth, + trunkHeight: trunkHeight }); if (Math.random() < 0.1) { @@ -477,6 +487,74 @@ class RiverRun extends Module { } } + _drawTrunk(canvas, text, width, height) { + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + // Draw trunk (wood texture) + const trunkY = 0; + + // Wood color (brown) + const woodColor = '#8B4513'; + const darkWoodColor = '#654321'; + const lightWoodColor = '#A0522D'; + + // Draw main trunk body + ctx.fillStyle = woodColor; + ctx.beginPath(); + ctx.ellipse(width / 2, trunkY + height / 2, width / 2, height / 2, 0, 0, Math.PI * 2); + ctx.fill(); + + // Add wood grain texture + ctx.strokeStyle = darkWoodColor; + ctx.lineWidth = 2; + ctx.globalAlpha = 0.4; + for (let i = 0; i < 5; i++) { + const y = trunkY + (i * height / 4); + ctx.beginPath(); + ctx.ellipse(width / 2, y, width / 2 - 2, 3, 0, 0, Math.PI * 2); + ctx.stroke(); + } + ctx.globalAlpha = 1; + + // Add shine/highlight + ctx.fillStyle = lightWoodColor; + ctx.globalAlpha = 0.3; + ctx.beginPath(); + ctx.ellipse(width / 3, trunkY + height / 3, width / 4, height / 4, 0, 0, Math.PI * 2); + ctx.fill(); + ctx.globalAlpha = 1; + + // Draw rings (tree rings effect) + ctx.strokeStyle = darkWoodColor; + ctx.lineWidth = 1; + ctx.globalAlpha = 0.5; + const ringCount = 3; + for (let i = 1; i <= ringCount; i++) { + const ringRatio = i / (ringCount + 1); + ctx.beginPath(); + ctx.ellipse(width / 2, trunkY + height / 2, (width / 2) * ringRatio, (height / 2) * ringRatio, 0, 0, Math.PI * 2); + ctx.stroke(); + } + ctx.globalAlpha = 1; + + // Draw text on the trunk + ctx.fillStyle = '#ffffff'; + ctx.font = `bold ${Math.max(10, width / 4)}px Arial`; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + ctx.shadowColor = 'rgba(0,0,0,0.5)'; + ctx.shadowBlur = 3; + ctx.shadowOffsetX = 1; + ctx.shadowOffsetY = 1; + + ctx.fillText(text, width / 2, trunkY + height / 2); + + // Add text below trunk for clarity + ctx.font = `bold ${Math.max(8, width / 5)}px Arial`; + ctx.fillText(text, width / 2, trunkY + height + 15); + } + _getRandomWord() { return this._availableWords[Math.floor(Math.random() * this._availableWords.length)]; } @@ -676,9 +754,14 @@ class RiverRun extends Module { } _handleWordClick(wordElement) { + // Skip if already collected or missed + if (wordElement.dataset.collected === 'true' || wordElement.classList.contains('collected') || wordElement.classList.contains('missed')) { + return; + } + const wordData = wordElement.wordData; - if (wordData.french === this._currentTarget.french) { + if (wordData && this._currentTarget && wordData.french === this._currentTarget.french) { this._collectWord(wordElement, true); } else { this._missWord(wordElement); @@ -1252,32 +1335,21 @@ class RiverRun extends Module { .floating-word { position: absolute; - background: rgba(255,255,255,0.95); - border: 3px solid #4682B4; - border-radius: 15px; - padding: 8px 15px; - font-size: 1.1em; - font-weight: bold; - color: #333; cursor: pointer; transition: all 0.2s ease; z-index: 40; - box-shadow: - 0 4px 15px rgba(0,0,0,0.2), - 0 0 0 0 rgba(70,130,180,0.4); + filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3)); animation: wordFloat 3s ease-in-out infinite alternate; } @keyframes wordFloat { - from { transform: translateY(0px) rotate(-1deg); } - to { transform: translateY(-5px) rotate(1deg); } + from { transform: translateX(-50%) translateY(0px) rotate(-1deg); } + to { transform: translateX(-50%) translateY(-5px) rotate(1deg); } } .floating-word:hover { - transform: scale(1.1) translateY(-3px); - box-shadow: - 0 6px 20px rgba(0,0,0,0.3), - 0 0 20px rgba(70,130,180,0.6); + filter: drop-shadow(0 6px 15px rgba(0,0,0,0.4)); + transform: translateX(-50%) scale(1.08) !important; } .floating-word.collected { @@ -1286,15 +1358,15 @@ class RiverRun extends Module { @keyframes wordCollected { 0% { - transform: scale(1); + transform: translateX(-50%) scale(1); opacity: 1; } 50% { - transform: scale(1.3); + transform: translateX(-50%) scale(1.3); opacity: 0.8; } 100% { - transform: scale(0) translateY(-50px); + transform: translateX(-50%) scale(0) translateY(-50px); opacity: 0; } } @@ -1305,26 +1377,26 @@ class RiverRun extends Module { @keyframes wordMissed { 0% { - transform: scale(1); + transform: translateX(-50%) scale(1); opacity: 1; - background: rgba(255,255,255,0.95); + filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3)); } 100% { - transform: scale(0.8); + transform: translateX(-50%) scale(0.8); opacity: 0; - background: rgba(220,20,60,0.8); + filter: drop-shadow(0 0 10px rgba(220,20,60,0.8)); } } @keyframes wordShake { 0%, 100% { - transform: translateX(0) scale(1); + transform: translateX(-50%) translateX(0) scale(1); } 10%, 30%, 50%, 70%, 90% { - transform: translateX(-10px) scale(1.05); + transform: translateX(-50%) translateX(-10px) scale(1.05); } 20%, 40%, 60%, 80% { - transform: translateX(10px) scale(1.05); + transform: translateX(-50%) translateX(10px) scale(1.05); } } diff --git a/src/games/SentenceInvaders.js b/src/games/SentenceInvaders.js index 56335e5..74b78a0 100644 --- a/src/games/SentenceInvaders.js +++ b/src/games/SentenceInvaders.js @@ -20,10 +20,15 @@ class SentenceInvaders extends Module { this._config = { container: null, maxSentences: 40, - fallSpeedVhPerSecond: 10, // Slower than WordStorm (sentences take longer to read) - spawnRate: 5000, // ms between spawns (slower for TTS) + baseFallSpeed: 10, // Base speed (vh/s) + baseSpawnRate: 5000, // Base spawn rate (ms) + fallSpeedVhPerSecond: 10, // Current speed + spawnRate: 5000, // Current spawn rate sentenceLifetime: 12000, // ms before sentence disappears startingLives: 3, + difficultyIncreaseInterval: 10000, // Increase difficulty every 10s + maxFallSpeed: 50, // Maximum fall speed (vh/s) + minSpawnRate: 1500, // Minimum spawn rate (ms) ...config }; @@ -41,6 +46,7 @@ class SentenceInvaders extends Module { this._fallingAliens = []; this._currentSentenceIndex = 0; this._spawnInterval = null; + this._difficultyInterval = null; // Progressive difficulty timer this._activeTTS = null; // Track active TTS this._aliensKilled = 0; this._aliensMissed = 0; @@ -160,6 +166,7 @@ class SentenceInvaders extends Module { // Start the game this._gameStartTime = Date.now(); this._startSpawning(); + this._startProgressiveDifficulty(); // Emit game ready event this._eventBus.emit('game:ready', { @@ -185,6 +192,11 @@ class SentenceInvaders extends Module { this._spawnInterval = null; } + if (this._difficultyInterval) { + clearInterval(this._difficultyInterval); + this._difficultyInterval = null; + } + // Stop any active TTS ttsService.cancel(); @@ -771,6 +783,11 @@ class SentenceInvaders extends Module { } _startSpawning() { + // Clear existing interval if any + if (this._spawnInterval) { + clearInterval(this._spawnInterval); + } + this._spawnInterval = setInterval(() => { if (!this._isGamePaused && !this._isGameOver) { this._spawnFallingAlien(); @@ -778,6 +795,51 @@ class SentenceInvaders extends Module { }, this._config.spawnRate); } + _startProgressiveDifficulty() { + // Clear existing interval if any + if (this._difficultyInterval) { + clearInterval(this._difficultyInterval); + } + + this._difficultyInterval = setInterval(() => { + if (!this._isGamePaused && !this._isGameOver) { + this._increaseDifficulty(); + } + }, this._config.difficultyIncreaseInterval); + } + + _increaseDifficulty() { + // Increase fall speed by 8% every interval (more aggressive than before) + const newFallSpeed = Math.min( + this._config.maxFallSpeed, + this._config.fallSpeedVhPerSecond * 1.08 + ); + + // Decrease spawn rate by 6% every interval (spawn faster) + const newSpawnRate = Math.max( + this._config.minSpawnRate, + this._config.spawnRate * 0.94 + ); + + // Only update if values changed + if (newFallSpeed !== this._config.fallSpeedVhPerSecond || + newSpawnRate !== this._config.spawnRate) { + + this._config.fallSpeedVhPerSecond = newFallSpeed; + this._config.spawnRate = newSpawnRate; + + // Restart spawning with new rate + this._startSpawning(); + + // Update existing aliens to use new speed + this._fallingAliens.forEach(alien => { + if (alien.element && alien.element.parentNode) { + this._animateFalling(alien.element); + } + }); + } + } + _spawnFallingAlien() { if (this._sentences.length === 0) return; @@ -962,9 +1024,18 @@ class SentenceInvaders extends Module { const points = 15 + (this._combo * 3); // Higher base points for sentences this._score += points; - // Increase speed based on combo - const speedMultiplier = Math.min(1 + (this._combo * 0.03), 2); - this._config.fallSpeedVhPerSecond = 10 * speedMultiplier; + // Combo provides temporary speed boost (doesn't reset base progression) + // This adds excitement without disrupting the progressive difficulty + const comboBoost = Math.min(this._combo * 0.02, 0.3); // Max 30% boost from combo + const currentBaseSpeed = this._config.fallSpeedVhPerSecond; + const boostedSpeed = Math.min(this._config.maxFallSpeed, currentBaseSpeed * (1 + comboBoost)); + + // Apply boost to existing aliens + this._fallingAliens.forEach(alien => { + if (alien.element && alien.element.parentNode) { + this._animateFalling(alien.element); + } + }); this._updateHUD(); this._showPointsPopup(points, alien.element); @@ -994,7 +1065,7 @@ class SentenceInvaders extends Module { soundSystem.play('enemy_defeat'); this._combo = 0; - this._config.fallSpeedVhPerSecond = 10; // Reset speed + // Don't reset speed anymore - let progressive difficulty continue // Flash answer panel const answerPanel = document.getElementById('answer-panel'); @@ -1114,15 +1185,18 @@ class SentenceInvaders extends Module { _levelUp() { this._level++; - // Increase difficulty - this._config.fallSpeedVhPerSecond = Math.min(40, this._config.fallSpeedVhPerSecond * 1.05); - this._config.spawnRate = Math.max(1500, this._config.spawnRate / 1.05); + // Level up gives a significant boost on top of progressive difficulty + this._config.fallSpeedVhPerSecond = Math.min( + this._config.maxFallSpeed, + this._config.fallSpeedVhPerSecond * 1.15 + ); + this._config.spawnRate = Math.max( + this._config.minSpawnRate, + this._config.spawnRate * 0.85 + ); - // Restart intervals - if (this._spawnInterval) { - clearInterval(this._spawnInterval); - this._startSpawning(); - } + // Restart intervals with new rates + this._startSpawning(); this._updateHUD(); @@ -1182,6 +1256,11 @@ class SentenceInvaders extends Module { this._spawnInterval = null; } + if (this._difficultyInterval) { + clearInterval(this._difficultyInterval); + this._difficultyInterval = null; + } + // Stop TTS ttsService.cancel(); @@ -1250,14 +1329,17 @@ class SentenceInvaders extends Module { this._aliensKilled = 0; this._aliensMissed = 0; - // Reset config - this._config.fallSpeedVhPerSecond = 10; - this._config.spawnRate = 5000; + // Reset config to base values + this._config.fallSpeedVhPerSecond = this._config.baseFallSpeed; + this._config.spawnRate = this._config.baseSpawnRate; // Clear intervals if (this._spawnInterval) { clearInterval(this._spawnInterval); } + if (this._difficultyInterval) { + clearInterval(this._difficultyInterval); + } // Clear aliens this._fallingAliens.forEach(fa => { @@ -1273,6 +1355,7 @@ class SentenceInvaders extends Module { // Update and restart this._updateHUD(); this._startSpawning(); + this._startProgressiveDifficulty(); } _updateHUD() { diff --git a/src/games/WhackAMole.js b/src/games/WhackAMole.js index 7eb7981..c7a9858 100644 --- a/src/games/WhackAMole.js +++ b/src/games/WhackAMole.js @@ -212,8 +212,9 @@ class WhackAMole extends Module { style.id = cssId; style.textContent = ` .whack-game-wrapper { - padding: 10px; - max-width: 650px; + padding: 8px; + max-width: 95vw; + max-width: min(650px, 95vw); margin: 0 auto; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); @@ -222,14 +223,18 @@ class WhackAMole extends Module { min-height: auto; max-height: 90vh; overflow: hidden; + display: flex; + flex-direction: column; } .whack-game-header { display: flex; - justify-content: space-between; + justify-content: center; align-items: center; - margin-bottom: 10px; - padding: 10px; + flex-wrap: wrap; + gap: 6px; + margin-bottom: 8px; + padding: 8px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; backdrop-filter: blur(10px); @@ -237,67 +242,78 @@ class WhackAMole extends Module { .game-stats { display: flex; - gap: 10px; + gap: 6px; align-items: center; + flex-wrap: wrap; + justify-content: center; } .stat-item { text-align: center; background: rgba(255, 255, 255, 0.1); - padding: 6px 12px; + padding: 4px 8px; border-radius: 6px; - min-width: 60px; + min-width: 50px; + flex: 0 1 auto; } .stat-value { display: block; - font-size: 1.2rem; + font-size: 0.95rem; font-weight: bold; margin-bottom: 2px; } .stat-label { - font-size: 0.7rem; + font-size: 0.65rem; opacity: 0.9; } .target-display { background: rgba(255, 255, 255, 0.2); - padding: 8px 15px; + padding: 6px 10px; border-radius: 8px; text-align: center; border: 2px solid rgba(255, 255, 255, 0.3); + min-width: 100px; + max-width: 150px; + flex-shrink: 1; } .target-label { - font-size: 0.7rem; + font-size: 0.65rem; opacity: 0.9; margin-bottom: 2px; } .target-word { - font-size: 1rem; + font-size: 0.9rem; font-weight: bold; + word-break: break-word; } .game-controls { display: flex; - gap: 8px; + gap: 6px; align-items: center; flex-wrap: wrap; + justify-content: center; } .control-btn { - padding: 6px 12px; + padding: 5px 10px; border: 2px solid rgba(255, 255, 255, 0.3); border-radius: 6px; background: rgba(255, 255, 255, 0.1); color: white; - font-size: 0.75rem; + font-size: 0.7rem; font-weight: 500; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(5px); + white-space: nowrap; + min-width: fit-content; + flex-shrink: 1; } .control-btn:hover { @@ -319,12 +335,13 @@ class WhackAMole extends Module { .whack-game-board { display: grid; grid-template-columns: repeat(3, 1fr); - gap: 10px; - margin: 10px 0; - padding: 10px; + gap: 8px; + margin: 8px 0; + padding: 8px; background: rgba(0, 0, 0, 0.2); border-radius: 10px; min-height: auto; + flex: 1; } .whack-hole { @@ -332,10 +349,11 @@ class WhackAMole extends Module { aspect-ratio: 1; background: radial-gradient(circle at center, #8b5cf6 0%, #7c3aed 100%); border-radius: 50%; - border: 4px solid rgba(255, 255, 255, 0.3); + border: 3px solid rgba(255, 255, 255, 0.3); overflow: hidden; cursor: pointer; transition: all 0.3s ease; + min-height: 60px; } .whack-hole:hover { @@ -350,15 +368,15 @@ class WhackAMole extends Module { transform: translate(-50%, -50%) scale(0); background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); border-radius: 8px; - padding: 8px; + padding: 6px; color: white; text-align: center; font-weight: 600; - font-size: 0.85rem; + font-size: 0.75rem; transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); cursor: pointer; - max-width: 80%; + max-width: 85%; word-wrap: break-word; } @@ -570,38 +588,96 @@ class WhackAMole extends Module { } } - @media (max-width: 768px) { + /* Ensure Exit button uses control-btn styles */ + #exit-whack { + padding: 5px 10px !important; + border: 2px solid rgba(255, 255, 255, 0.3) !important; + border-radius: 6px; + background: rgba(255, 255, 255, 0.1) !important; + color: white !important; + font-size: 0.7rem !important; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + backdrop-filter: blur(5px); + white-space: nowrap; + min-width: fit-content; + flex-shrink: 1; + } + + #exit-whack:hover { + background: rgba(255, 255, 255, 0.2) !important; + transform: translateY(-2px); + } + + #exit-whack .btn-icon, + #exit-whack .btn-text { + font-size: 0.7rem; + } + + @media (max-width: 480px) { .whack-game-wrapper { - padding: 8px; - } - - .whack-game-header { - flex-direction: column; - gap: 8px; - padding: 8px; - } - - .game-stats { - gap: 8px; - } - - .whack-game-board { - gap: 8px; - padding: 8px; - } - - .whack-mole { - font-size: 0.75rem; padding: 6px; } - .game-controls { - justify-content: center; + .whack-game-header { + gap: 4px; + padding: 6px; + margin-bottom: 6px; } - .control-btn { - padding: 5px 10px; - font-size: 0.7rem; + .stat-item { + padding: 3px 6px; + min-width: 45px; + } + + .stat-value { + font-size: 0.85rem; + } + + .stat-label { + font-size: 0.6rem; + } + + .target-display { + padding: 4px 8px; + min-width: 90px; + max-width: 120px; + } + + .target-word { + font-size: 0.8rem; + } + + .game-controls { + gap: 4px; + } + + .control-btn, + #exit-whack { + padding: 4px 8px !important; + font-size: 0.65rem !important; + } + + #exit-whack .btn-icon, + #exit-whack .btn-text { + font-size: 0.65rem; + } + + .whack-game-board { + gap: 6px; + padding: 6px; + margin: 6px 0; + } + + .whack-hole { + min-height: 50px; + border-width: 2px; + } + + .whack-mole { + font-size: 0.65rem; + padding: 4px; } } `; diff --git a/src/games/WhackAMoleHard.js b/src/games/WhackAMoleHard.js index 8728ee4..1ba3d22 100644 --- a/src/games/WhackAMoleHard.js +++ b/src/games/WhackAMoleHard.js @@ -223,12 +223,14 @@ class WhackAMoleHard extends Module { .game-info { display: flex; - justify-content: space-between; + flex-direction: column; + justify-content: center; align-items: center; + gap: clamp(12px, 3vw, 20px); width: 100%; - max-width: 800px; - margin-bottom: 30px; - padding: 20px; + max-width: 100%; + margin-bottom: clamp(15px, 4vw, 30px); + padding: clamp(12px, 3vw, 20px); background: #f8fafc; border-radius: 12px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); @@ -236,23 +238,27 @@ class WhackAMoleHard extends Module { .game-stats { display: flex; - gap: 30px; + gap: clamp(15px, 3vw, 30px); + flex-wrap: wrap; + justify-content: center; + width: 100%; } .stat-item { text-align: center; + min-width: clamp(60px, 20vw, 100px); } .stat-value { display: block; - font-size: 24px; + font-size: clamp(18px, 4vw, 24px); font-weight: bold; color: #1f2937; margin-bottom: 5px; } .stat-label { - font-size: 12px; + font-size: clamp(10px, 2vw, 12px); color: #6b7280; text-transform: uppercase; letter-spacing: 0.5px; diff --git a/src/games/WizardSpellCaster.js b/src/games/WizardSpellCaster.js index c136f3d..1c8b136 100644 --- a/src/games/WizardSpellCaster.js +++ b/src/games/WizardSpellCaster.js @@ -1520,17 +1520,22 @@ class WizardSpellCaster extends Module { const isCorrect = playerSentence === expectedSentence; if (isCorrect) { + // Capture spell data before it gets reset by _generateNewSpells() + const spellType = this._selectedSpell.type; + const spellDamage = this._selectedSpell.damage; + const spellData = { ...this._selectedSpell }; + // Successful cast! - this._showCastingEffect(this._selectedSpell.type); + this._showCastingEffect(spellType); setTimeout(() => { - this._showSpellEffect(this._selectedSpell.type); + this._showSpellEffect(spellType); }, 500); // Deal damage - this._enemyHP = Math.max(0, this._enemyHP - this._selectedSpell.damage); + this._enemyHP = Math.max(0, this._enemyHP - spellDamage); this._updateEnemyHealth(); - this._showDamageNumber(this._selectedSpell.damage); + this._showDamageNumber(spellDamage); // Update score with bonuses const wordCount = this._selectedWords.length; @@ -1550,15 +1555,15 @@ class WizardSpellCaster extends Module { if (spellTime < 3) speedBonus += 500; } - this._score += (this._selectedSpell.damage * scoreMultiplier) + speedBonus; + this._score += (spellDamage * scoreMultiplier) + speedBonus; document.getElementById('current-score').textContent = this._score; // Emit spell cast event this._eventBus.emit('wizard-spell-caster:spell-cast', { gameId: 'wizard-spell-caster', instanceId: this.name, - spell: this._selectedSpell, - damage: this._selectedSpell.damage, + spell: spellData, + damage: spellDamage, score: this._score, speedBonus }, this.name);