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
@@ -477,99 +477,99 @@ function setup () {
477
   let numObstacles = Math.floor((width * height) / 60000) // More obstacles
477
   let numObstacles = Math.floor((width * height) / 60000) // More obstacles
478
   numObstacles = constrain(numObstacles, 15, 25)
478
   numObstacles = constrain(numObstacles, 15, 25)
479
 
479
 
480
-// Create ant balloons
480
+  // Create ant balloons
481
-let numBalloons = Math.floor(random(15, 21))
481
+  let numBalloons = Math.floor(random(15, 21))
482
-for (let i = 0; i < numBalloons; i++) {
482
+  for (let i = 0; i < numBalloons; i++) {
483
-  let attempts = 0
483
+    let attempts = 0
484
-  let placed = false
484
+    let placed = false
485
-
485
+
486
-  while (!placed && attempts < 30) {
486
+    while (!placed && attempts < 30) {
487
-    // FIX: True random distribution with better spread
487
+      // FIX: True random distribution with better spread
488
-    let x, y
488
+      let x, y
489
-    
489
+
490
-    // Use different strategies for better distribution
490
+      // Use different strategies for better distribution
491
-    let strategy = random()
491
+      let strategy = random()
492
-    
492
+
493
-    if (strategy < 0.3) {
493
+      if (strategy < 0.3) {
494
-      // 30% - Truly random across upper area
494
+        // 30% - Truly random across upper area
495
-      x = random(80, width - 80)
495
+        x = random(80, width - 80)
496
-      y = random(60, height * 0.5)
497
-    } else if (strategy < 0.6) {
498
-      // 30% - Radial distribution from center
499
-      let angle = random(TWO_PI)
500
-      let radius = random(100, min(width, height) * 0.35)
501
-      x = width / 2 + cos(angle) * radius
502
-      y = height * 0.35 + sin(angle) * radius * 0.7  // Elliptical, flatter
503
-      x = constrain(x, 80, width - 80)
504
-      y = constrain(y, 60, height * 0.6)
505
-    } else if (strategy < 0.8) {
506
-      // 20% - Edge preference for variety
507
-      if (random() < 0.5) {
508
-        x = random() < 0.5 ? random(80, 150) : random(width - 150, width - 80)
509
         y = random(60, height * 0.5)
496
         y = random(60, height * 0.5)
497
+      } else if (strategy < 0.6) {
498
+        // 30% - Radial distribution from center
499
+        let angle = random(TWO_PI)
500
+        let radius = random(100, min(width, height) * 0.35)
501
+        x = width / 2 + cos(angle) * radius
502
+        y = height * 0.35 + sin(angle) * radius * 0.7 // Elliptical, flatter
503
+        x = constrain(x, 80, width - 80)
504
+        y = constrain(y, 60, height * 0.6)
505
+      } else if (strategy < 0.8) {
506
+        // 20% - Edge preference for variety
507
+        if (random() < 0.5) {
508
+          x = random() < 0.5 ? random(80, 150) : random(width - 150, width - 80)
509
+          y = random(60, height * 0.5)
510
+        } else {
511
+          x = random(80, width - 80)
512
+          y = random(60, 120)
513
+        }
510
       } else {
514
       } else {
511
-        x = random(80, width - 80)
515
+        // 20% - Poisson disk sampling attempt (avoid clusters)
512
-        y = random(60, 120)
516
+        let bestX = random(80, width - 80)
513
-      }
517
+        let bestY = random(60, height * 0.6)
514
-    } else {
518
+        let bestMinDist = 0
515
-      // 20% - Poisson disk sampling attempt (avoid clusters)
519
+
516
-      let bestX = random(80, width - 80)
520
+        // Try a few positions and pick the one furthest from existing balloons
517
-      let bestY = random(60, height * 0.6)
521
+        for (let j = 0; j < 5; j++) {
518
-      let bestMinDist = 0
522
+          let testX = random(80, width - 80)
519
-      
523
+          let testY = random(60, height * 0.6)
520
-      // Try a few positions and pick the one furthest from existing balloons
524
+          let minDist = Infinity
521
-      for (let j = 0; j < 5; j++) {
525
+
522
-        let testX = random(80, width - 80)
526
+          for (let obstacle of obstacles) {
523
-        let testY = random(60, height * 0.6)
527
+            if (obstacle.type === 'balloon') {
524
-        let minDist = Infinity
528
+              let d = dist(testX, testY, obstacle.x, obstacle.y)
525
-        
529
+              minDist = min(minDist, d)
526
-        for (let obstacle of obstacles) {
530
+            }
527
-          if (obstacle.type === 'balloon') {
531
+          }
528
-            let d = dist(testX, testY, obstacle.x, obstacle.y)
532
+
529
-            minDist = min(minDist, d)
533
+          if (minDist > bestMinDist) {
534
+            bestMinDist = minDist
535
+            bestX = testX
536
+            bestY = testY
530
           }
537
           }
531
         }
538
         }
532
-        
539
+
533
-        if (minDist > bestMinDist) {
540
+        x = bestX
534
-          bestMinDist = minDist
541
+        y = bestY
535
-          bestX = testX
536
-          bestY = testY
537
-        }
538
       }
542
       }
539
-      
540
-      x = bestX
541
-      y = bestY
542
-    }
543
 
543
 
544
-    let radius = random(35, 50) // Varied sizes for visual interest
544
+      let radius = random(35, 50) // Varied sizes for visual interest
545
 
545
 
546
-    let valid = true
546
+      let valid = true
547
-    // Check distance from other obstacles
547
+      // Check distance from other obstacles
548
-    for (let obstacle of obstacles) {
548
+      for (let obstacle of obstacles) {
549
-      if (
549
+        if (
550
-        dist(x, y, obstacle.x, obstacle.y) <
550
+          dist(x, y, obstacle.x, obstacle.y) <
551
-        radius + obstacle.radius + 40
551
+          radius + obstacle.radius + 40
552
-      ) {
552
+        ) {
553
-        valid = false
553
+          valid = false
554
-        break
554
+          break
555
+        }
555
       }
556
       }
556
-    }
557
 
557
 
558
-    // Check distance from home branch
558
+      // Check distance from home branch
559
-    if (valid && window.homeBranch) {
559
+      if (valid && window.homeBranch) {
560
-      let branchY = window.homeBranch.y
560
+        let branchY = window.homeBranch.y
561
-      if (Math.abs(y - branchY) < radius + 40) {
561
+        if (Math.abs(y - branchY) < radius + 40) {
562
-        valid = false
562
+          valid = false
563
+        }
563
       }
564
       }
564
-    }
565
 
565
 
566
-    if (valid) {
566
+      if (valid) {
567
-      obstacles.push(new Obstacle(x, y, radius, 'balloon'))
567
+        obstacles.push(new Obstacle(x, y, radius, 'balloon'))
568
-      placed = true
568
+        placed = true
569
+      }
570
+      attempts++
569
     }
571
     }
570
-    attempts++
571
   }
572
   }
572
-}
573
 
573
 
574
   // Create beetles
574
   // Create beetles
575
   let numBeetles = Math.floor(random(9, 15))
575
   let numBeetles = Math.floor(random(9, 15))
@@ -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
+
1417
+  // Remove any existing listeners
1418
+  closeBtn.replaceWith(closeBtn.cloneNode(true))
1419
+  closeBtn = document.getElementById('close-stats-btn')
1420
+
1421
+  closeBtn.addEventListener('click', function () {
1416
     document.getElementById('stats-panel').style.display = 'none'
1422
     document.getElementById('stats-panel').style.display = 'none'
1423
+    if (gamePhase === 'DAY') {
1424
+      gamePhase = 'DAY_TO_DUSK'
1425
+      phaseTimer = 0
1426
+    }
1427
+  })
1417
 
1428
 
1418
-    // IMMEDIATELY transition to dusk after closing stats
1429
+  closeBtn.addEventListener('touchend', function (e) {
1430
+    e.preventDefault()
1431
+    document.getElementById('stats-panel').style.display = 'none'
1419
     if (gamePhase === 'DAY') {
1432
     if (gamePhase === 'DAY') {
1420
       gamePhase = 'DAY_TO_DUSK'
1433
       gamePhase = 'DAY_TO_DUSK'
1421
       phaseTimer = 0
1434
       phaseTimer = 0
1422
     }
1435
     }
1423
-  }
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
 
@@ -1949,16 +1983,17 @@ window.buyUpgrade = function (upgradeKey) {
1949
   }
1983
   }
1950
 
1984
 
1951
   // Check if can afford and not maxed
1985
   // Check if can afford and not maxed
1952
-  let availablePoints = playerPoints - spentPoints  // Calculate available points
1986
+  let availablePoints = playerPoints - spentPoints // Calculate available points
1953
   if (availablePoints >= upgrade.cost && upgrade.level < upgrade.maxLevel) {
1987
   if (availablePoints >= upgrade.cost && upgrade.level < upgrade.maxLevel) {
1954
-    spentPoints += upgrade.cost  // Track spent points
1988
+    spentPoints += upgrade.cost // Track spent points
1955
     upgrade.level++
1989
     upgrade.level++
1956
 
1990
 
1957
     // Apply upgrade effects immediately
1991
     // Apply upgrade effects immediately
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
2718
-  
2753
+        ? Math.floor((fliesMunched / totalFliesInNight) * 100)
2719
-  // Calculate predicted dawn stamina
2754
+        : 0
2720
-  let predictedStamina;
2755
+
2721
-  if (currentMunchPercent >= 50) {
2756
+    // Calculate predicted dawn stamina
2722
-    predictedStamina = 100;
2757
+    let predictedStamina
2723
-  } else {
2758
+    if (currentMunchPercent >= 50) {
2724
-    predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8);
2759
+      predictedStamina = 100
2725
-  }
2760
+    } else {
2726
-  
2761
+      predictedStamina = Math.floor(20 + currentMunchPercent * 2 * 0.8)
2727
-  timerText = `${timeLeft}s • ${flies.length} flies`;
2762
+    }
2728
-  
2763
+
2729
-  // Show special fly counts if any
2764
+    timerText = `${timeLeft}s • ${flies.length} flies`
2730
-  let goldenCount = flies.filter(f => f.type === 'golden').length;
2765
+
2731
-  let mothCount = flies.filter(f => f.type === 'moth').length;
2766
+    // Show special fly counts if any
2732
-  let queenCount = flies.filter(f => f.type === 'queen').length;
2767
+    let goldenCount = flies.filter(f => f.type === 'golden').length
2733
-  
2768
+    let mothCount = flies.filter(f => f.type === 'moth').length
2734
-  if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2769
+    let queenCount = flies.filter(f => f.type === 'queen').length
2735
-    let specialCounts = [];
2770
+
2736
-    if (queenCount > 0) specialCounts.push(`${queenCount}👑`);
2771
+    if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2737
-    if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`);
2772
+      let specialCounts = []
2738
-    if (mothCount > 0) specialCounts.push(`${mothCount}🦋`);
2773
+      if (queenCount > 0) specialCounts.push(`${queenCount}👑`)
2739
-    timerText += ` (${specialCounts.join(' ')})`;
2774
+      if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`)
2740
-  }
2775
+      if (mothCount > 0) specialCounts.push(`${mothCount}🦋`)
2741
-   // Show munch progress
2776
+      timerText += ` (${specialCounts.join(' ')})`
2742
-  document.getElementById('timer').innerHTML = timerText + 
2777
+    }
2743
-    `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` +
2778
+    // Show munch progress
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
   } 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
@@ -2786,49 +2830,56 @@ function updateUI () {
2786
   }
2830
   }
2787
 
2831
 
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'
2803
-  } else if (jumpStamina < 40) {
2847
+      ).style.background = `linear-gradient(90deg, rgb(255, ${
2804
-    // Very tired - orange-red
2848
+        50 + flash * 50
2805
-    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FF6B35, #FF4444)';
2849
+      }, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`
2806
-  } else if (jumpStamina < 60) {
2850
+    } else if (jumpStamina < 40) {
2807
-    // Tired - orange
2851
+      // Very tired - orange-red
2808
-    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFA500, #FF8C00)';
2852
+      document.getElementById('web-meter-fill').style.background =
2809
-  } else if (jumpStamina < 80) {
2853
+        'linear-gradient(90deg, #FF6B35, #FF4444)'
2810
-    // OK - yellow-orange
2854
+    } else if (jumpStamina < 60) {
2811
-    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFD700, #FFA500)';
2855
+      // Tired - orange
2856
+      document.getElementById('web-meter-fill').style.background =
2857
+        'linear-gradient(90deg, #FFA500, #FF8C00)'
2858
+    } else if (jumpStamina < 80) {
2859
+      // OK - yellow-orange
2860
+      document.getElementById('web-meter-fill').style.background =
2861
+        'linear-gradient(90deg, #FFD700, #FFA500)'
2862
+    } else {
2863
+      // Good stamina - green-yellow
2864
+      document.getElementById('web-meter-fill').style.background =
2865
+        'linear-gradient(90deg, #90EE90, #FFD700)'
2866
+    }
2867
+
2868
+    // Show critical warning overlay
2869
+    if (jumpStamina <= 0 && !gameOver) {
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()
2881
+    }
2812
   } else {
2882
   } else {
2813
-    // Good stamina - green-yellow
2814
-    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #90EE90, #FFD700)';
2815
-  }
2816
-  
2817
-  // Show critical warning overlay
2818
-  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();
2830
-  }
2831
-} else {
2832
     // Normal silk meter
2883
     // Normal silk meter
2833
     document.getElementById('web-meter-label').textContent = 'SILK'
2884
     document.getElementById('web-meter-label').textContent = 'SILK'
2834
     let meterPercent = (webSilk / maxWebSilk) * 100
2885
     let meterPercent = (webSilk / maxWebSilk) * 100
@@ -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