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 {
8484
   text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
8585
 }
8686
 
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
7070
 let fliesMunchedLastNight = 0
7171
 let birds = []
7272
 let staminaRegenCooldown = 0
73
-let staminaBonus = 0;
73
+let staminaBonus = 0
7474
 
7575
 // PHASE 4B: Wind System
7676
 let windActive = false
@@ -1411,20 +1411,39 @@ function openStatsPanel () {
14111411
   // Show panel
14121412
   document.getElementById('stats-panel').style.display = 'block'
14131413
 
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')
14171420
 
1418
-    // IMMEDIATELY transition to dusk after closing stats
1421
+  closeBtn.addEventListener('click', function () {
1422
+    document.getElementById('stats-panel').style.display = 'none'
14191423
     if (gamePhase === 'DAY') {
14201424
       gamePhase = 'DAY_TO_DUSK'
14211425
       phaseTimer = 0
14221426
     }
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
14231435
     }
1436
+  })
14241437
 }
14251438
 
14261439
 // 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
+
14281447
   if (unlockedSkins[skinId]) {
14291448
     currentSkin = skinId
14301449
     saveGame()
@@ -1653,7 +1672,7 @@ function saveGame () {
16531672
     nightsSurvived: nightsSurvived,
16541673
     currentNight: currentNight,
16551674
     playerPoints: playerPoints,
1656
-    spentPoints: spentPoints,
1675
+    spentPoints: spentPoints
16571676
   }
16581677
 
16591678
   localStorage.setItem('cobGameSave', JSON.stringify(saveData))
@@ -1792,23 +1811,32 @@ function spawnDawnBirds () {
17921811
 // ============================================
17931812
 
17941813
 function openUpgradeShop () {
1795
-  if (currentNight <= 1) return // No shop on first night
1814
+  if (currentNight <= 1) return
17961815
 
17971816
   shopOpen = true
17981817
   noLoop() // Pause the game
17991818
 
1800
-  // Calculate points from flies caught this session
1801
-  // playerPoints = totalFliesCaught
1802
-
18031819
   // Update shop UI
18041820
   document.getElementById('upgrade-shop').style.display = 'block'
1805
-  document.getElementById('available-points').textContent = playerPoints - spentPoints
1821
+  document.getElementById('available-points').textContent =
1822
+    playerPoints - spentPoints
18061823
 
18071824
   // Populate upgrade lists
18081825
   updateShopDisplay()
18091826
 
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
+  })
18121840
 }
18131841
 
18141842
 function closeUpgradeShop () {
@@ -1862,7 +1890,7 @@ function updateShopDisplay () {
18621890
       }/${upgrade.maxLevel})
18631891
                             <br><small>${upgrade.description}</small>
18641892
                         </div>
1865
-                        <button onclick="buyUpgrade('${key}')" ${buttonDisabled}
1893
+                        <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled}
18661894
                                 style="padding: 5px 15px; background: ${
18671895
                                   canAfford && !maxed ? '#4CAF50' : '#666'
18681896
                                 }; 
@@ -1907,7 +1935,7 @@ function updateShopDisplay () {
19071935
       }/${upgrade.maxLevel})
19081936
                             <br><small>${upgrade.description}</small>
19091937
                         </div>
1910
-                        <button onclick="buyUpgrade('${key}')" ${buttonDisabled}
1938
+                        <button ontouchend="buyUpgrade('${key}')" onclick="buyUpgrade('${key}')" ${buttonDisabled}
19111939
                                 style="padding: 5px 15px; background: ${
19121940
                                   canAfford && !maxed ? '#FF69B4' : '#666'
19131941
                                 }; 
@@ -1934,6 +1962,12 @@ function updateShopDisplay () {
19341962
 
19351963
 // Make buyUpgrade global so onclick can access it
19361964
 window.buyUpgrade = function (upgradeKey) {
1965
+  // Prevent any touch/click propagation issues
1966
+  if (event) {
1967
+    event.preventDefault()
1968
+    event.stopPropagation()
1969
+  }
1970
+
19371971
   let upgrade = upgrades[upgradeKey]
19381972
   if (!upgrade) return
19391973
 
@@ -1958,7 +1992,8 @@ window.buyUpgrade = function (upgradeKey) {
19581992
     applyUpgradeEffects()
19591993
 
19601994
     // Update display with available points
1961
-    document.getElementById('available-points').textContent = playerPoints - spentPoints
1995
+    document.getElementById('available-points').textContent =
1996
+      playerPoints - spentPoints
19621997
     updateShopDisplay()
19631998
 
19641999
     // Show notification
@@ -2709,39 +2744,48 @@ function updateUI () {
27092744
     `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>`
27102745
 
27112746
   if (gamePhase === 'NIGHT') {
2712
-  let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60);
2747
+    let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60)
27132748
 
27142749
     // 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
27182755
 
27192756
     // Calculate predicted dawn stamina
2720
-  let predictedStamina;
2757
+    let predictedStamina
27212758
     if (currentMunchPercent >= 50) {
2722
-    predictedStamina = 100;
2759
+      predictedStamina = 100
27232760
     } else {
2724
-    predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8);
2761
+      predictedStamina = Math.floor(20 + currentMunchPercent * 2 * 0.8)
27252762
     }
27262763
 
2727
-  timerText = `${timeLeft}s • ${flies.length} flies`;
2764
+    timerText = `${timeLeft}s • ${flies.length} flies`
27282765
 
27292766
     // 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
27332770
 
27342771
     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(' ')})`
27402777
     }
27412778
     // 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>`
27452789
   } else if (gamePhase === 'DAWN') {
27462790
     let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60)
27472791
     // PHASE 4: Show birds and exhaustion status
@@ -2788,45 +2832,52 @@ function updateUI () {
27882832
   // PHASE 4: Update meter based on phase
27892833
   if (gamePhase === 'DAWN') {
27902834
     // Show stamina instead of silk during dawn
2791
-  document.getElementById('web-meter-label').textContent = 'STAMINA';
2835
+    document.getElementById('web-meter-label').textContent = 'STAMINA'
27922836
 
27932837
     // 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 + '%'
27962840
 
27972841
     // Color based on stamina level
27982842
     if (jumpStamina < 20) {
27992843
       // 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}))`
28032850
     } else if (jumpStamina < 40) {
28042851
       // 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)'
28062854
     } else if (jumpStamina < 60) {
28072855
       // 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)'
28092858
     } else if (jumpStamina < 80) {
28102859
       // 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)'
28122862
     } else {
28132863
       // 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)'
28152866
     }
28162867
 
28172868
     // Show critical warning overlay
28182869
     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()
28302881
     }
28312882
   } else {
28322883
     // Normal silk meter
@@ -2918,6 +2969,13 @@ function showGameOverScreen () {
29182969
     `
29192970
 
29202971
   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
+  })
29212979
 }
29222980
 
29232981
 // Add restart game function:
@@ -3125,6 +3183,14 @@ function recycleNearbyWeb () {
31253183
 }
31263184
 
31273185
 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
+
31283194
   if (touches.length > 0) {
31293195
     touchStartTime = millis()
31303196
     touchStartX = touches[0].x
@@ -3188,6 +3254,12 @@ function touchStarted () {
31883254
 }
31893255
 
31903256
 function touchMoved () {
3257
+  if (
3258
+    shopOpen ||
3259
+    document.getElementById('stats-panel').style.display === 'block'
3260
+  ) {
3261
+    return false
3262
+  }
31913263
   // Update web deployment target while holding
31923264
   if (
31933265
     touchHolding &&
@@ -3206,6 +3278,12 @@ function touchMoved () {
32063278
 }
32073279
 
32083280
 function touchEnded () {
3281
+  if (
3282
+    shopOpen ||
3283
+    document.getElementById('stats-panel').style.display === 'block'
3284
+  ) {
3285
+    return false
3286
+  }
32093287
   touchHolding = false
32103288
   touchProcessing = false
32113289