@@ -70,7 +70,7 @@ let isExhausted = false |
| 70 | let fliesMunchedLastNight = 0 | 70 | let fliesMunchedLastNight = 0 |
| 71 | let birds = [] | 71 | let birds = [] |
| 72 | let staminaRegenCooldown = 0 | 72 | let staminaRegenCooldown = 0 |
| 73 | -let staminaBonus = 0; | 73 | +let staminaBonus = 0 |
| 74 | | 74 | |
| 75 | // PHASE 4B: Wind System | 75 | // PHASE 4B: Wind System |
| 76 | let windActive = false | 76 | let windActive = false |
@@ -1411,20 +1411,39 @@ function openStatsPanel () { |
| 1411 | // Show panel | 1411 | // Show panel |
| 1412 | document.getElementById('stats-panel').style.display = 'block' | 1412 | document.getElementById('stats-panel').style.display = 'block' |
| 1413 | | 1413 | |
| 1414 | - // Add close button listener | 1414 | + // FIX: Add both click AND touch listeners |
| 1415 | - document.getElementById('close-stats-btn').onclick = () => { | 1415 | + let closeBtn = document.getElementById('close-stats-btn') |
| 1416 | - document.getElementById('stats-panel').style.display = 'none' | 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 | if (gamePhase === 'DAY') { | 1423 | if (gamePhase === 'DAY') { |
| 1420 | gamePhase = 'DAY_TO_DUSK' | 1424 | gamePhase = 'DAY_TO_DUSK' |
| 1421 | phaseTimer = 0 | 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 | // Make selectSkin global | 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 | if (unlockedSkins[skinId]) { | 1447 | if (unlockedSkins[skinId]) { |
| 1429 | currentSkin = skinId | 1448 | currentSkin = skinId |
| 1430 | saveGame() | 1449 | saveGame() |
@@ -1653,7 +1672,7 @@ function saveGame () { |
| 1653 | nightsSurvived: nightsSurvived, | 1672 | nightsSurvived: nightsSurvived, |
| 1654 | currentNight: currentNight, | 1673 | currentNight: currentNight, |
| 1655 | playerPoints: playerPoints, | 1674 | playerPoints: playerPoints, |
| 1656 | - spentPoints: spentPoints, | 1675 | + spentPoints: spentPoints |
| 1657 | } | 1676 | } |
| 1658 | | 1677 | |
| 1659 | localStorage.setItem('cobGameSave', JSON.stringify(saveData)) | 1678 | localStorage.setItem('cobGameSave', JSON.stringify(saveData)) |
@@ -1792,23 +1811,32 @@ function spawnDawnBirds () { |
| 1792 | // ============================================ | 1811 | // ============================================ |
| 1793 | | 1812 | |
| 1794 | function openUpgradeShop () { | 1813 | function openUpgradeShop () { |
| 1795 | - if (currentNight <= 1) return // No shop on first night | 1814 | + if (currentNight <= 1) return |
| 1796 | | 1815 | |
| 1797 | shopOpen = true | 1816 | shopOpen = true |
| 1798 | noLoop() // Pause the game | 1817 | noLoop() // Pause the game |
| 1799 | | 1818 | |
| 1800 | - // Calculate points from flies caught this session | | |
| 1801 | - // playerPoints = totalFliesCaught | | |
| 1802 | - | | |
| 1803 | // Update shop UI | 1819 | // Update shop UI |
| 1804 | document.getElementById('upgrade-shop').style.display = 'block' | 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 | // Populate upgrade lists | 1824 | // Populate upgrade lists |
| 1808 | updateShopDisplay() | 1825 | updateShopDisplay() |
| 1809 | | 1826 | |
| 1810 | - // Add continue button listener | 1827 | + // FIX: Add both click AND touch listeners for mobile |
| 1811 | - document.getElementById('continue-btn').onclick = closeUpgradeShop | 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 | function closeUpgradeShop () { | 1842 | function closeUpgradeShop () { |
@@ -1862,7 +1890,7 @@ function updateShopDisplay () { |
| 1862 | }/${upgrade.maxLevel}) | 1890 | }/${upgrade.maxLevel}) |
| 1863 | <br><small>${upgrade.description}</small> | 1891 | <br><small>${upgrade.description}</small> |
| 1864 | </div> | 1892 | </div> |
| 1865 | - <button onclick="buyUpgrade('${key}')" ${buttonDisabled} | 1893 | + <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1866 | style="padding: 5px 15px; background: ${ | 1894 | style="padding: 5px 15px; background: ${ |
| 1867 | canAfford && !maxed ? '#4CAF50' : '#666' | 1895 | canAfford && !maxed ? '#4CAF50' : '#666' |
| 1868 | }; | 1896 | }; |
@@ -1907,7 +1935,7 @@ function updateShopDisplay () { |
| 1907 | }/${upgrade.maxLevel}) | 1935 | }/${upgrade.maxLevel}) |
| 1908 | <br><small>${upgrade.description}</small> | 1936 | <br><small>${upgrade.description}</small> |
| 1909 | </div> | 1937 | </div> |
| 1910 | - <button onclick="buyUpgrade('${key}')" ${buttonDisabled} | 1938 | + <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled} |
| 1911 | style="padding: 5px 15px; background: ${ | 1939 | style="padding: 5px 15px; background: ${ |
| 1912 | canAfford && !maxed ? '#FF69B4' : '#666' | 1940 | canAfford && !maxed ? '#FF69B4' : '#666' |
| 1913 | }; | 1941 | }; |
@@ -1934,6 +1962,12 @@ function updateShopDisplay () { |
| 1934 | | 1962 | |
| 1935 | // Make buyUpgrade global so onclick can access it | 1963 | // Make buyUpgrade global so onclick can access it |
| 1936 | window.buyUpgrade = function (upgradeKey) { | 1964 | window.buyUpgrade = function (upgradeKey) { |
| | 1965 | + // Prevent any touch/click propagation issues |
| | 1966 | + if (event) { |
| | 1967 | + event.preventDefault() |
| | 1968 | + event.stopPropagation() |
| | 1969 | + } |
| | 1970 | + |
| 1937 | let upgrade = upgrades[upgradeKey] | 1971 | let upgrade = upgrades[upgradeKey] |
| 1938 | if (!upgrade) return | 1972 | if (!upgrade) return |
| 1939 | | 1973 | |
@@ -1958,7 +1992,8 @@ window.buyUpgrade = function (upgradeKey) { |
| 1958 | applyUpgradeEffects() | 1992 | applyUpgradeEffects() |
| 1959 | | 1993 | |
| 1960 | // Update display with available points | 1994 | // Update display with available points |
| 1961 | - document.getElementById('available-points').textContent = playerPoints - spentPoints | 1995 | + document.getElementById('available-points').textContent = |
| | 1996 | + playerPoints - spentPoints |
| 1962 | updateShopDisplay() | 1997 | updateShopDisplay() |
| 1963 | | 1998 | |
| 1964 | // Show notification | 1999 | // Show notification |
@@ -2709,39 +2744,48 @@ function updateUI () { |
| 2709 | `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>` | 2744 | `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>` |
| 2710 | | 2745 | |
| 2711 | if (gamePhase === 'NIGHT') { | 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 | // Calculate current munch percentage | 2749 | // Calculate current munch percentage |
| 2715 | - let totalFliesInNight = fliesSpawnedThisNight + flies.length; | 2750 | + let totalFliesInNight = fliesSpawnedThisNight + flies.length |
| 2716 | - let currentMunchPercent = totalFliesInNight > 0 ? | 2751 | + let currentMunchPercent = |
| 2717 | - Math.floor((fliesMunched / totalFliesInNight) * 100) : 0; | 2752 | + totalFliesInNight > 0 |
| | 2753 | + ? Math.floor((fliesMunched / totalFliesInNight) * 100) |
| | 2754 | + : 0 |
| 2718 | | 2755 | |
| 2719 | // Calculate predicted dawn stamina | 2756 | // Calculate predicted dawn stamina |
| 2720 | - let predictedStamina; | 2757 | + let predictedStamina |
| 2721 | if (currentMunchPercent >= 50) { | 2758 | if (currentMunchPercent >= 50) { |
| 2722 | - predictedStamina = 100; | 2759 | + predictedStamina = 100 |
| 2723 | } else { | 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 | // Show special fly counts if any | 2766 | // Show special fly counts if any |
| 2730 | - let goldenCount = flies.filter(f => f.type === 'golden').length; | 2767 | + let goldenCount = flies.filter(f => f.type === 'golden').length |
| 2731 | - let mothCount = flies.filter(f => f.type === 'moth').length; | 2768 | + let mothCount = flies.filter(f => f.type === 'moth').length |
| 2732 | - let queenCount = flies.filter(f => f.type === 'queen').length; | 2769 | + let queenCount = flies.filter(f => f.type === 'queen').length |
| 2733 | | 2770 | |
| 2734 | if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { | 2771 | if (goldenCount > 0 || mothCount > 0 || queenCount > 0) { |
| 2735 | - let specialCounts = []; | 2772 | + let specialCounts = [] |
| 2736 | - if (queenCount > 0) specialCounts.push(`${queenCount}👑`); | 2773 | + if (queenCount > 0) specialCounts.push(`${queenCount}👑`) |
| 2737 | - if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`); | 2774 | + if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`) |
| 2738 | - if (mothCount > 0) specialCounts.push(`${mothCount}🦋`); | 2775 | + if (mothCount > 0) specialCounts.push(`${mothCount}🦋`) |
| 2739 | - timerText += ` (${specialCounts.join(' ')})`; | 2776 | + timerText += ` (${specialCounts.join(' ')})` |
| 2740 | } | 2777 | } |
| 2741 | // Show munch progress | 2778 | // Show munch progress |
| 2742 | - document.getElementById('timer').innerHTML = timerText + | 2779 | + document.getElementById('timer').innerHTML = |
| 2743 | - `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` + | 2780 | + timerText + |
| 2744 | - `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>`; | 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 | } else if (gamePhase === 'DAWN') { | 2789 | } else if (gamePhase === 'DAWN') { |
| 2746 | let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60) | 2790 | let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60) |
| 2747 | // PHASE 4: Show birds and exhaustion status | 2791 | // PHASE 4: Show birds and exhaustion status |
@@ -2788,45 +2832,52 @@ function updateUI () { |
| 2788 | // PHASE 4: Update meter based on phase | 2832 | // PHASE 4: Update meter based on phase |
| 2789 | if (gamePhase === 'DAWN') { | 2833 | if (gamePhase === 'DAWN') { |
| 2790 | // Show stamina instead of silk during dawn | 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 | // FIX: Always show percentage out of 100, not out of variable max | 2837 | // FIX: Always show percentage out of 100, not out of variable max |
| 2794 | - let staminaPercent = (jumpStamina / 100) * 100; // Always out of 100 | 2838 | + let staminaPercent = (jumpStamina / 100) * 100 // Always out of 100 |
| 2795 | - document.getElementById('web-meter-fill').style.width = staminaPercent + '%'; | 2839 | + document.getElementById('web-meter-fill').style.width = staminaPercent + '%' |
| 2796 | | 2840 | |
| 2797 | // Color based on stamina level | 2841 | // Color based on stamina level |
| 2798 | if (jumpStamina < 20) { | 2842 | if (jumpStamina < 20) { |
| 2799 | // Exhausted - red flash | 2843 | // Exhausted - red flash |
| 2800 | - let flash = sin(frameCount * 0.3) * 0.5 + 0.5; | 2844 | + let flash = sin(frameCount * 0.3) * 0.5 + 0.5 |
| 2801 | - document.getElementById('web-meter-fill').style.background = | 2845 | + document.getElementById( |
| 2802 | - `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`; | 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 | } else if (jumpStamina < 40) { | 2850 | } else if (jumpStamina < 40) { |
| 2804 | // Very tired - orange-red | 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 | } else if (jumpStamina < 60) { | 2854 | } else if (jumpStamina < 60) { |
| 2807 | // Tired - orange | 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 | } else if (jumpStamina < 80) { | 2858 | } else if (jumpStamina < 80) { |
| 2810 | // OK - yellow-orange | 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 | } else { | 2862 | } else { |
| 2813 | // Good stamina - green-yellow | 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 | // Show critical warning overlay | 2868 | // Show critical warning overlay |
| 2818 | if (jumpStamina <= 0 && !gameOver) { | 2869 | if (jumpStamina <= 0 && !gameOver) { |
| 2819 | - push(); | 2870 | + push() |
| 2820 | - fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50); | 2871 | + fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50) |
| 2821 | - rect(0, 0, width, height); | 2872 | + rect(0, 0, width, height) |
| 2822 | - | 2873 | + |
| 2823 | - textAlign(CENTER); | 2874 | + textAlign(CENTER) |
| 2824 | - textSize(32); | 2875 | + textSize(32) |
| 2825 | - fill(255, 50, 50); | 2876 | + fill(255, 50, 50) |
| 2826 | - stroke(0); | 2877 | + stroke(0) |
| 2827 | - strokeWeight(3); | 2878 | + strokeWeight(3) |
| 2828 | - text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2); | 2879 | + text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2) |
| 2829 | - pop(); | 2880 | + pop() |
| 2830 | } | 2881 | } |
| 2831 | } else { | 2882 | } else { |
| 2832 | // Normal silk meter | 2883 | // Normal silk meter |
@@ -2918,6 +2969,13 @@ function showGameOverScreen () { |
| 2918 | ` | 2969 | ` |
| 2919 | | 2970 | |
| 2920 | document.body.insertAdjacentHTML('beforeend', gameOverHTML) | 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 | // Add restart game function: | 2981 | // Add restart game function: |
@@ -3125,6 +3183,14 @@ function recycleNearbyWeb () { |
| 3125 | } | 3183 | } |
| 3126 | | 3184 | |
| 3127 | function touchStarted () { | 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 | if (touches.length > 0) { | 3194 | if (touches.length > 0) { |
| 3129 | touchStartTime = millis() | 3195 | touchStartTime = millis() |
| 3130 | touchStartX = touches[0].x | 3196 | touchStartX = touches[0].x |
@@ -3188,6 +3254,12 @@ function touchStarted () { |
| 3188 | } | 3254 | } |
| 3189 | | 3255 | |
| 3190 | function touchMoved () { | 3256 | function touchMoved () { |
| | 3257 | + if ( |
| | 3258 | + shopOpen || |
| | 3259 | + document.getElementById('stats-panel').style.display === 'block' |
| | 3260 | + ) { |
| | 3261 | + return false |
| | 3262 | + } |
| 3191 | // Update web deployment target while holding | 3263 | // Update web deployment target while holding |
| 3192 | if ( | 3264 | if ( |
| 3193 | touchHolding && | 3265 | touchHolding && |
@@ -3206,6 +3278,12 @@ function touchMoved () { |
| 3206 | } | 3278 | } |
| 3207 | | 3279 | |
| 3208 | function touchEnded () { | 3280 | function touchEnded () { |
| | 3281 | + if ( |
| | 3282 | + shopOpen || |
| | 3283 | + document.getElementById('stats-panel').style.display === 'block' |
| | 3284 | + ) { |
| | 3285 | + return false |
| | 3286 | + } |
| 3209 | touchHolding = false | 3287 | touchHolding = false |
| 3210 | touchProcessing = false | 3288 | touchProcessing = false |
| 3211 | | 3289 | |