zeroed-some/cob / 054467b

Browse files

touch support for store modals

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
054467b8b1902307aa951669087b489bbcca7a03
Parents
16abfe2
Tree
438b375

2 changed files

StatusFile+-
M css/styles.css 19 0
M js/game.js 252 174
css/styles.cssmodified
@@ -84,3 +84,22 @@ canvas {
84
   text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
84
   text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
85
 }
85
 }
86
 
86
 
87
+  /* Make buttons touch-friendly */
88
+  #upgrade-shop button,
89
+  #stats-panel button,
90
+  #game-over-screen button {
91
+    min-height: 44px;
92
+    min-width: 44px;
93
+    touch-action: manipulation;
94
+    -webkit-tap-highlight-color: transparent;
95
+    user-select: none;
96
+    cursor: pointer;
97
+  }
98
+  
99
+  /* Prevent scroll/zoom issues on mobile */
100
+  #upgrade-shop,
101
+  #stats-panel {
102
+    touch-action: none;
103
+    -webkit-user-select: none;
104
+    user-select: none;
105
+  }
js/game.jsmodified
@@ -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