@@ -70,7 +70,7 @@ let isExhausted = false |
| 70 | 70 | let fliesMunchedLastNight = 0 |
| 71 | 71 | let birds = [] |
| 72 | 72 | let staminaRegenCooldown = 0 |
| 73 | | -let staminaBonus = 0; |
| 73 | +let staminaBonus = 0 |
| 74 | 74 | |
| 75 | 75 | // PHASE 4B: Wind System |
| 76 | 76 | let windActive = false |
@@ -1411,20 +1411,39 @@ function openStatsPanel () { |
| 1411 | 1411 | // Show panel |
| 1412 | 1412 | document.getElementById('stats-panel').style.display = 'block' |
| 1413 | 1413 | |
| 1414 | | - // Add close button listener |
| 1415 | | - document.getElementById('close-stats-btn').onclick = () => { |
| 1416 | | - document.getElementById('stats-panel').style.display = 'none' |
| 1414 | + // FIX: Add both click AND touch listeners |
| 1415 | + let closeBtn = document.getElementById('close-stats-btn') |
| 1416 | + |
| 1417 | + // Remove any existing listeners |
| 1418 | + closeBtn.replaceWith(closeBtn.cloneNode(true)) |
| 1419 | + closeBtn = document.getElementById('close-stats-btn') |
| 1417 | 1420 | |
| 1418 | | - // IMMEDIATELY transition to dusk after closing stats |
| 1421 | + closeBtn.addEventListener('click', function () { |
| 1422 | + document.getElementById('stats-panel').style.display = 'none' |
| 1419 | 1423 | if (gamePhase === 'DAY') { |
| 1420 | 1424 | gamePhase = 'DAY_TO_DUSK' |
| 1421 | 1425 | phaseTimer = 0 |
| 1422 | 1426 | } |
| 1427 | + }) |
| 1428 | + |
| 1429 | + closeBtn.addEventListener('touchend', function (e) { |
| 1430 | + e.preventDefault() |
| 1431 | + document.getElementById('stats-panel').style.display = 'none' |
| 1432 | + if (gamePhase === 'DAY') { |
| 1433 | + gamePhase = 'DAY_TO_DUSK' |
| 1434 | + phaseTimer = 0 |
| 1423 | 1435 | } |
| 1436 | + }) |
| 1424 | 1437 | } |
| 1425 | 1438 | |
| 1426 | 1439 | // Make selectSkin global |
| 1427 | | -window.selectSkin = function (skinId) { |
| 1440 | +window.selectSkin = function (skinId, event) { |
| 1441 | + // Prevent touch issues |
| 1442 | + if (event) { |
| 1443 | + event.preventDefault() |
| 1444 | + event.stopPropagation() |
| 1445 | + } |
| 1446 | + |
| 1428 | 1447 | if (unlockedSkins[skinId]) { |
| 1429 | 1448 | currentSkin = skinId |
| 1430 | 1449 | saveGame() |
@@ -1653,7 +1672,7 @@ function saveGame () { |
| 1653 | 1672 | nightsSurvived: nightsSurvived, |
| 1654 | 1673 | currentNight: currentNight, |
| 1655 | 1674 | playerPoints: playerPoints, |
| 1656 | | - spentPoints: spentPoints, |
| 1675 | + spentPoints: spentPoints |
| 1657 | 1676 | } |
| 1658 | 1677 | |
| 1659 | 1678 | localStorage.setItem('cobGameSave', JSON.stringify(saveData)) |
@@ -1792,23 +1811,32 @@ function spawnDawnBirds () { |
| 1792 | 1811 | // ============================================ |
| 1793 | 1812 | |
| 1794 | 1813 | function openUpgradeShop () { |
| 1795 | | - if (currentNight <= 1) return // No shop on first night |
| 1814 | + if (currentNight <= 1) return |
| 1796 | 1815 | |
| 1797 | 1816 | shopOpen = true |
| 1798 | 1817 | noLoop() // Pause the game |
| 1799 | 1818 | |
| 1800 | | - // Calculate points from flies caught this session |
| 1801 | | - // playerPoints = totalFliesCaught |
| 1802 | | - |
| 1803 | 1819 | // Update shop UI |
| 1804 | 1820 | document.getElementById('upgrade-shop').style.display = 'block' |
| 1805 | | - document.getElementById('available-points').textContent = playerPoints - spentPoints |
| 1821 | + document.getElementById('available-points').textContent = |
| 1822 | + playerPoints - spentPoints |
| 1806 | 1823 | |
| 1807 | 1824 | // Populate upgrade lists |
| 1808 | 1825 | updateShopDisplay() |
| 1809 | 1826 | |
| 1810 | | - // Add continue button listener |
| 1811 | | - document.getElementById('continue-btn').onclick = closeUpgradeShop |
| 1827 | + // FIX: Add both click AND touch listeners for mobile |
| 1828 | + let continueBtn = document.getElementById('continue-btn') |
| 1829 | + |
| 1830 | + // Remove any existing listeners to prevent duplicates |
| 1831 | + continueBtn.replaceWith(continueBtn.cloneNode(true)) |
| 1832 | + continueBtn = document.getElementById('continue-btn') |
| 1833 | + |
| 1834 | + // Add both click and touch support |
| 1835 | + continueBtn.addEventListener('click', closeUpgradeShop) |
| 1836 | + continueBtn.addEventListener('touchend', function (e) { |
| 1837 | + e.preventDefault() // Prevent ghost clicks |
| 1838 | + closeUpgradeShop() |
| 1839 | + }) |
| 1812 | 1840 | } |
| 1813 | 1841 | |
| 1814 | 1842 | function closeUpgradeShop () { |
@@ -1862,7 +1890,7 @@ function updateShopDisplay () { |
| 1862 | 1890 | }/${upgrade.maxLevel}) |
| 1863 | 1891 | <br><small>${upgrade.description}</small> |
| 1864 | 1892 | </div> |
| 1865 | | - <button onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1893 | + <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1866 | 1894 | style="padding: 5px 15px; background: ${ |
| 1867 | 1895 | canAfford && !maxed ? '#4CAF50' : '#666' |
| 1868 | 1896 | }; |
@@ -1907,7 +1935,7 @@ function updateShopDisplay () { |
| 1907 | 1935 | }/${upgrade.maxLevel}) |
| 1908 | 1936 | <br><small>${upgrade.description}</small> |
| 1909 | 1937 | </div> |
| 1910 | | - <button onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1938 | + <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1911 | 1939 | style="padding: 5px 15px; background: ${ |
| 1912 | 1940 | canAfford && !maxed ? '#FF69B4' : '#666' |
| 1913 | 1941 | }; |
@@ -1934,6 +1962,12 @@ function updateShopDisplay () { |
| 1934 | 1962 | |
| 1935 | 1963 | // Make buyUpgrade global so onclick can access it |
| 1936 | 1964 | window.buyUpgrade = function (upgradeKey) { |
| 1965 | + // Prevent any touch/click propagation issues |
| 1966 | + if (event) { |
| 1967 | + event.preventDefault() |
| 1968 | + event.stopPropagation() |
| 1969 | + } |
| 1970 | + |
| 1937 | 1971 | let upgrade = upgrades[upgradeKey] |
| 1938 | 1972 | if (!upgrade) return |
| 1939 | 1973 | |
@@ -1958,7 +1992,8 @@ window.buyUpgrade = function (upgradeKey) { |
| 1958 | 1992 | applyUpgradeEffects() |
| 1959 | 1993 | |
| 1960 | 1994 | // Update display with available points |
| 1961 | | - document.getElementById('available-points').textContent = playerPoints - spentPoints |
| 1995 | + document.getElementById('available-points').textContent = |
| 1996 | + playerPoints - spentPoints |
| 1962 | 1997 | updateShopDisplay() |
| 1963 | 1998 | |
| 1964 | 1999 | // Show notification |
@@ -2709,39 +2744,48 @@ function updateUI () { |
| 2709 | 2744 | `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>` |
| 2710 | 2745 | |
| 2711 | 2746 | if (gamePhase === 'NIGHT') { |
| 2712 | | - let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60); |
| 2747 | + let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60) |
| 2713 | 2748 | |
| 2714 | 2749 | // Calculate current munch percentage |
| 2715 | | - let totalFliesInNight = fliesSpawnedThisNight + flies.length; |
| 2716 | | - let currentMunchPercent = totalFliesInNight > 0 ? |
| 2717 | | - Math.floor((fliesMunched / totalFliesInNight) * 100) : 0; |
| 2750 | + let totalFliesInNight = fliesSpawnedThisNight + flies.length |
| 2751 | + let currentMunchPercent = |
| 2752 | + totalFliesInNight > 0 |
| 2753 | + ? Math.floor((fliesMunched / totalFliesInNight) * 100) |
| 2754 | + : 0 |
| 2718 | 2755 | |
| 2719 | 2756 | // Calculate predicted dawn stamina |
| 2720 | | - let predictedStamina; |
| 2757 | + let predictedStamina |
| 2721 | 2758 | if (currentMunchPercent >= 50) { |
| 2722 | | - predictedStamina = 100; |
| 2759 | + predictedStamina = 100 |
| 2723 | 2760 | } else { |
| 2724 | | - predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8); |
| 2761 | + predictedStamina = Math.floor(20 + currentMunchPercent * 2 * 0.8) |
| 2725 | 2762 | } |
| 2726 | 2763 | |
| 2727 | | - timerText = `${timeLeft}s • ${flies.length} flies`; |
| 2764 | + timerText = `${timeLeft}s • ${flies.length} flies` |
| 2728 | 2765 | |
| 2729 | 2766 | // Show special fly counts if any |
| 2730 | | - let goldenCount = flies.filter(f => f.type === 'golden').length; |
| 2731 | | - let mothCount = flies.filter(f => f.type === 'moth').length; |
| 2732 | | - let queenCount = flies.filter(f => f.type === 'queen').length; |
| 2767 | + let goldenCount = flies.filter(f => f.type === 'golden').length |
| 2768 | + let mothCount = flies.filter(f => f.type === 'moth').length |
| 2769 | + let queenCount = flies.filter(f => f.type === 'queen').length |
| 2733 | 2770 | |
| 2734 | 2771 | if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { |
| 2735 | | - let specialCounts = []; |
| 2736 | | - if (queenCount > 0) specialCounts.push(`${queenCount}👑`); |
| 2737 | | - if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`); |
| 2738 | | - if (mothCount > 0) specialCounts.push(`${mothCount}🦋`); |
| 2739 | | - timerText += ` (${specialCounts.join(' ')})`; |
| 2772 | + let specialCounts = [] |
| 2773 | + if (queenCount > 0) specialCounts.push(`${queenCount}👑`) |
| 2774 | + if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`) |
| 2775 | + if (mothCount > 0) specialCounts.push(`${mothCount}🦋`) |
| 2776 | + timerText += ` (${specialCounts.join(' ')})` |
| 2740 | 2777 | } |
| 2741 | 2778 | // Show munch progress |
| 2742 | | - document.getElementById('timer').innerHTML = timerText + |
| 2743 | | - `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` + |
| 2744 | | - `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>`; |
| 2779 | + document.getElementById('timer').innerHTML = |
| 2780 | + timerText + |
| 2781 | + `<br><small style="color: ${ |
| 2782 | + predictedStamina < 40 |
| 2783 | + ? '#ff4444' |
| 2784 | + : predictedStamina < 70 |
| 2785 | + ? '#ffaa44' |
| 2786 | + : '#44ff44' |
| 2787 | + }">` + |
| 2788 | + `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>` |
| 2745 | 2789 | } else if (gamePhase === 'DAWN') { |
| 2746 | 2790 | let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60) |
| 2747 | 2791 | // PHASE 4: Show birds and exhaustion status |
@@ -2788,45 +2832,52 @@ function updateUI () { |
| 2788 | 2832 | // PHASE 4: Update meter based on phase |
| 2789 | 2833 | if (gamePhase === 'DAWN') { |
| 2790 | 2834 | // Show stamina instead of silk during dawn |
| 2791 | | - document.getElementById('web-meter-label').textContent = 'STAMINA'; |
| 2835 | + document.getElementById('web-meter-label').textContent = 'STAMINA' |
| 2792 | 2836 | |
| 2793 | 2837 | // FIX: Always show percentage out of 100, not out of variable max |
| 2794 | | - let staminaPercent = (jumpStamina / 100) * 100; // Always out of 100 |
| 2795 | | - document.getElementById('web-meter-fill').style.width = staminaPercent + '%'; |
| 2838 | + let staminaPercent = (jumpStamina / 100) * 100 // Always out of 100 |
| 2839 | + document.getElementById('web-meter-fill').style.width = staminaPercent + '%' |
| 2796 | 2840 | |
| 2797 | 2841 | // Color based on stamina level |
| 2798 | 2842 | if (jumpStamina < 20) { |
| 2799 | 2843 | // Exhausted - red flash |
| 2800 | | - let flash = sin(frameCount * 0.3) * 0.5 + 0.5; |
| 2801 | | - document.getElementById('web-meter-fill').style.background = |
| 2802 | | - `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`; |
| 2844 | + let flash = sin(frameCount * 0.3) * 0.5 + 0.5 |
| 2845 | + document.getElementById( |
| 2846 | + 'web-meter-fill' |
| 2847 | + ).style.background = `linear-gradient(90deg, rgb(255, ${ |
| 2848 | + 50 + flash * 50 |
| 2849 | + }, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))` |
| 2803 | 2850 | } else if (jumpStamina < 40) { |
| 2804 | 2851 | // Very tired - orange-red |
| 2805 | | - document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FF6B35, #FF4444)'; |
| 2852 | + document.getElementById('web-meter-fill').style.background = |
| 2853 | + 'linear-gradient(90deg, #FF6B35, #FF4444)' |
| 2806 | 2854 | } else if (jumpStamina < 60) { |
| 2807 | 2855 | // Tired - orange |
| 2808 | | - document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFA500, #FF8C00)'; |
| 2856 | + document.getElementById('web-meter-fill').style.background = |
| 2857 | + 'linear-gradient(90deg, #FFA500, #FF8C00)' |
| 2809 | 2858 | } else if (jumpStamina < 80) { |
| 2810 | 2859 | // OK - yellow-orange |
| 2811 | | - document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFD700, #FFA500)'; |
| 2860 | + document.getElementById('web-meter-fill').style.background = |
| 2861 | + 'linear-gradient(90deg, #FFD700, #FFA500)' |
| 2812 | 2862 | } else { |
| 2813 | 2863 | // Good stamina - green-yellow |
| 2814 | | - document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #90EE90, #FFD700)'; |
| 2864 | + document.getElementById('web-meter-fill').style.background = |
| 2865 | + 'linear-gradient(90deg, #90EE90, #FFD700)' |
| 2815 | 2866 | } |
| 2816 | 2867 | |
| 2817 | 2868 | // Show critical warning overlay |
| 2818 | 2869 | if (jumpStamina <= 0 && !gameOver) { |
| 2819 | | - push(); |
| 2820 | | - fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50); |
| 2821 | | - rect(0, 0, width, height); |
| 2822 | | - |
| 2823 | | - textAlign(CENTER); |
| 2824 | | - textSize(32); |
| 2825 | | - fill(255, 50, 50); |
| 2826 | | - stroke(0); |
| 2827 | | - strokeWeight(3); |
| 2828 | | - text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2); |
| 2829 | | - pop(); |
| 2870 | + push() |
| 2871 | + fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50) |
| 2872 | + rect(0, 0, width, height) |
| 2873 | + |
| 2874 | + textAlign(CENTER) |
| 2875 | + textSize(32) |
| 2876 | + fill(255, 50, 50) |
| 2877 | + stroke(0) |
| 2878 | + strokeWeight(3) |
| 2879 | + text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2) |
| 2880 | + pop() |
| 2830 | 2881 | } |
| 2831 | 2882 | } else { |
| 2832 | 2883 | // Normal silk meter |
@@ -2918,6 +2969,13 @@ function showGameOverScreen () { |
| 2918 | 2969 | ` |
| 2919 | 2970 | |
| 2920 | 2971 | document.body.insertAdjacentHTML('beforeend', gameOverHTML) |
| 2972 | + // FIX: Add touch support to restart button |
| 2973 | + let restartBtn = document.getElementById('restart-btn') |
| 2974 | + restartBtn.addEventListener('click', restartGame) |
| 2975 | + restartBtn.addEventListener('touchend', function (e) { |
| 2976 | + e.preventDefault() |
| 2977 | + restartGame() |
| 2978 | + }) |
| 2921 | 2979 | } |
| 2922 | 2980 | |
| 2923 | 2981 | // Add restart game function: |
@@ -3125,6 +3183,14 @@ function recycleNearbyWeb () { |
| 3125 | 3183 | } |
| 3126 | 3184 | |
| 3127 | 3185 | function touchStarted () { |
| 3186 | + // FIX: Don't process game touches when modals are open |
| 3187 | + if ( |
| 3188 | + shopOpen || |
| 3189 | + document.getElementById('stats-panel').style.display === 'block' |
| 3190 | + ) { |
| 3191 | + return false |
| 3192 | + } |
| 3193 | + |
| 3128 | 3194 | if (touches.length > 0) { |
| 3129 | 3195 | touchStartTime = millis() |
| 3130 | 3196 | touchStartX = touches[0].x |
@@ -3188,6 +3254,12 @@ function touchStarted () { |
| 3188 | 3254 | } |
| 3189 | 3255 | |
| 3190 | 3256 | function touchMoved () { |
| 3257 | + if ( |
| 3258 | + shopOpen || |
| 3259 | + document.getElementById('stats-panel').style.display === 'block' |
| 3260 | + ) { |
| 3261 | + return false |
| 3262 | + } |
| 3191 | 3263 | // Update web deployment target while holding |
| 3192 | 3264 | if ( |
| 3193 | 3265 | touchHolding && |
@@ -3206,6 +3278,12 @@ function touchMoved () { |
| 3206 | 3278 | } |
| 3207 | 3279 | |
| 3208 | 3280 | function touchEnded () { |
| 3281 | + if ( |
| 3282 | + shopOpen || |
| 3283 | + document.getElementById('stats-panel').style.display === 'block' |
| 3284 | + ) { |
| 3285 | + return false |
| 3286 | + } |
| 3209 | 3287 | touchHolding = false |
| 3210 | 3288 | touchProcessing = false |
| 3211 | 3289 | |