zeroed-some/cob / 6237a51

Browse files

fixes, cleanup

Authored by espadonne
SHA
6237a51a44013f993f3d0de5b1ea02df81a80afe
Parents
827e77f
Tree
d2b83b9

4 changed files

StatusFile+-
M deploy.sh 0 1
M index.html 1 1
M js/entities.js 331 173
M js/game.js 179 129
deploy.shmodified
@@ -54,7 +54,6 @@ sudo chown -R root:root "$RELEASES/$STAMP"
54
 sudo find "$RELEASES/$STAMP" -type d -exec chmod 0755 {} +
54
 sudo find "$RELEASES/$STAMP" -type d -exec chmod 0755 {} +
55
 sudo find "$RELEASES/$STAMP" -type f -exec chmod 0644 {} +
55
 sudo find "$RELEASES/$STAMP" -type f -exec chmod 0644 {} +
56
 
56
 
57
-# If the nginx user is different on your system, adjust here
58
 NGINX_USER="nginx"
57
 NGINX_USER="nginx"
59
 if id "$NGINX_USER" >/dev/null 2>&1; then
58
 if id "$NGINX_USER" >/dev/null 2>&1; then
60
   sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; }
59
   sudo -u "$NGINX_USER" test -r "$RELEASES/$STAMP/index.html" || { echo "✗ nginx user cannot read index.html"; exit 1; }
index.htmlmodified
@@ -13,7 +13,7 @@
13
             Click to jump • Space to spin web • Shift to munch!<br>
13
             Click to jump • Space to spin web • Shift to munch!<br>
14
             Web Strands: <span id="strand-count">0</span><br>
14
             Web Strands: <span id="strand-count">0</span><br>
15
             Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>
15
             Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>
16
-            Total Score: <span id="total-score">0</span>
16
+            Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span>
17
         </div>
17
         </div>
18
         <div id="phase-indicator">
18
         <div id="phase-indicator">
19
             <span id="phase">DUSK</span><br>
19
             <span id="phase">DUSK</span><br>
js/entities.jsmodified
@@ -22,16 +22,21 @@ class Spider {
22
 
22
 
23
     // DAWN PHASE: Check and consume stamina
23
     // DAWN PHASE: Check and consume stamina
24
     if (gamePhase === 'DAWN') {
24
     if (gamePhase === 'DAWN') {
25
+      // FIX: Only check if we ACTUALLY don't have enough stamina
25
       if (jumpStamina < jumpCost) {
26
       if (jumpStamina < jumpCost) {
26
         isExhausted = true
27
         isExhausted = true
28
+        // Only show notification if we're truly out
29
+        if (jumpStamina < 5 && notifications.length < 3) {
30
+          notifications.push(
31
+            new Notification('NO STAMINA!', color(255, 50, 50))
32
+          )
33
+        }
27
         return
34
         return
28
       }
35
       }
29
       jumpStamina -= jumpCost
36
       jumpStamina -= jumpCost
30
       stats.totalJumps++
37
       stats.totalJumps++
31
       // Delay stamina regen after each jump during DAWN
38
       // Delay stamina regen after each jump during DAWN
32
-      if (gamePhase === 'DAWN') {
39
+      staminaRegenCooldown = 60 // 1s at 60fps
33
-        staminaRegenCooldown = 60 // 1s at 60fps
34
-      }
35
     }
40
     }
36
 
41
 
37
     // PHASE 4B: Track wind jumps
42
     // PHASE 4B: Track wind jumps
@@ -119,15 +124,57 @@ class Spider {
119
     if (this.munchCooldown > 0) return
124
     if (this.munchCooldown > 0) return
120
 
125
 
121
     isMunching = true
126
     isMunching = true
122
-    this.munchCooldown = 30
127
+    this.munchCooldown = this.munchCooldownMax || 30 // Use upgrade value if available
123
 
128
 
124
     for (let i = flies.length - 1; i >= 0; i--) {
129
     for (let i = flies.length - 1; i >= 0; i--) {
125
       let fly = flies[i]
130
       let fly = flies[i]
126
       let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y)
131
       let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y)
127
       if (d < this.munchRadius) {
132
       if (d < this.munchRadius) {
128
         fliesMunched++
133
         fliesMunched++
134
+        stats.fliesMunchedInCurrentNight++ // Track for achievements
135
+
136
+        // POINTS: Award points for munching
137
+        let munchPoints = 4 // Base points for munch
138
+        if (fly.type === 'golden') munchPoints = 8
139
+        if (fly.type === 'queen') munchPoints = 10
140
+        if (fly.type === 'moth') munchPoints = 6
141
+        playerPoints += munchPoints
142
+        totalFliesCaught++ // Add to lifetime counter
143
+
129
         webSilk = min(webSilk + 15, maxWebSilk)
144
         webSilk = min(webSilk + 15, maxWebSilk)
130
 
145
 
146
+        // PHASE 3: Metabolize upgrade effect
147
+        if (upgrades.metabolize && upgrades.metabolize.level > 0) {
148
+          // Heal nearby broken strands
149
+          for (let strand of webStrands) {
150
+            if (strand.broken) {
151
+              let distToStrand = Infinity
152
+              if (strand.path && strand.path.length > 0) {
153
+                for (let point of strand.path) {
154
+                  let d = dist(this.pos.x, this.pos.y, point.x, point.y)
155
+                  if (d < distToStrand) distToStrand = d
156
+                }
157
+              }
158
+
159
+              if (distToStrand < 100) {
160
+                strand.broken = false
161
+                strand.strength = 0.5 // Heal to half strength
162
+
163
+                // Green healing particles
164
+                for (let j = 0; j < 5; j++) {
165
+                  let p = new Particle(this.pos.x, this.pos.y)
166
+                  p.color = color(100, 255, 100)
167
+                  p.vel = createVector(random(-2, 2), random(-2, 2))
168
+                  p.size = 3
169
+                  particles.push(p)
170
+                }
171
+
172
+                break // Only heal one strand per munch
173
+              }
174
+            }
175
+          }
176
+        }
177
+
131
         for (let j = 0; j < 12; j++) {
178
         for (let j = 0; j < 12; j++) {
132
           let p = new Particle(fly.pos.x, fly.pos.y)
179
           let p = new Particle(fly.pos.x, fly.pos.y)
133
           p.color = color(255, random(100, 255), 0)
180
           p.color = color(255, random(100, 255), 0)
@@ -202,34 +249,62 @@ class Spider {
202
       let branchStart = Math.min(branch.startX, branch.endX)
249
       let branchStart = Math.min(branch.startX, branch.endX)
203
       let branchEnd = Math.max(branch.startX, branch.endX)
250
       let branchEnd = Math.max(branch.startX, branch.endX)
204
 
251
 
205
-      // Since the branch angle is very small (0.05 radians ≈ 3 degrees),
252
+      // FIX: Extend collision detection beyond visual tip
206
-      // we can use a simpler approximation
253
+      // The visual branch ends at branchEnd, but we need collision slightly beyond
207
-      if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
254
+      let collisionPadding = 15 // Extra collision at the tip
255
+
256
+      if (
257
+        this.pos.x >= branchStart - 10 &&
258
+        this.pos.x <= branchEnd + collisionPadding
259
+      ) {
208
         // Calculate position along branch (0 to 1)
260
         // Calculate position along branch (0 to 1)
261
+        // FIX: Clamp t to ensure we handle tip properly
209
         let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
262
         let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
210
-        t = constrain(t, 0, 1)
263
+
264
+        // Allow t to go slightly beyond 1.0 for tip collision
265
+        if (this.pos.x > branchEnd) {
266
+          t = 1.0 // At the tip, use tip thickness
267
+        } else {
268
+          t = constrain(t, 0, 1)
269
+        }
211
 
270
 
212
         // Branch visual thickness tapers from full at start to 35% at end
271
         // Branch visual thickness tapers from full at start to 35% at end
213
-        // This matches exactly how it's drawn in the bezier curves
272
+        let branchTopThickness
214
-        let branchTopThickness = lerp(
273
+        if (t >= 1.0) {
215
-          branch.thickness * 0.9,
274
+          // At the very tip, maintain minimum collision thickness
216
-          branch.thickness * 0.35,
275
+          branchTopThickness = branch.thickness * 0.35
217
-          t
276
+        } else {
218
-        )
277
+          branchTopThickness = lerp(
278
+            branch.thickness * 0.9,
279
+            branch.thickness * 0.35,
280
+            t
281
+          )
282
+        }
219
 
283
 
220
         // The branch is drawn centered at branch.y
284
         // The branch is drawn centered at branch.y
221
-        // With small angle approximation: the top of the branch is at
222
         let branchSurfaceY = branch.y - branchTopThickness
285
         let branchSurfaceY = branch.y - branchTopThickness
223
 
286
 
224
-        // Add slight angle correction (for small angles, tan ≈ sin ≈ angle in radians)
287
+        // FIX: Correct angle calculation based on branch side
225
-        let angleCorrection = (this.pos.x - branchStart) * branch.angle
288
+        let angleCorrection
289
+        if (branch.side === 'right') {
290
+          // Right branch slopes down to the left (negative angle)
291
+          // Use distance from the end point (which is on the left for right branches)
292
+          angleCorrection = -(this.pos.x - branchEnd) * abs(branch.angle)
293
+        } else {
294
+          // Left branch slopes down to the right (positive angle)
295
+          // Use distance from the start point
296
+          angleCorrection = (this.pos.x - branchStart) * abs(branch.angle)
297
+        }
226
         branchSurfaceY += angleCorrection
298
         branchSurfaceY += angleCorrection
227
 
299
 
228
         // Check if spider is crossing the branch from above
300
         // Check if spider is crossing the branch from above
229
         let prevY = this.pos.y - this.vel.y
301
         let prevY = this.pos.y - this.vel.y
230
 
302
 
303
+        // FIX: More generous collision detection at the tip
304
+        let collisionBuffer = t >= 0.9 ? 5 : 0 // Extra buffer near tip
305
+
231
         if (
306
         if (
232
-          prevY <= branchSurfaceY && // Was above
307
+          prevY <= branchSurfaceY + collisionBuffer && // Was above (with buffer)
233
           this.pos.y + this.radius >= branchSurfaceY && // Now at or below
308
           this.pos.y + this.radius >= branchSurfaceY && // Now at or below
234
           this.pos.y < branch.y + branch.thickness
309
           this.pos.y < branch.y + branch.thickness
235
         ) {
310
         ) {
@@ -350,132 +425,148 @@ class Spider {
350
     this.land()
425
     this.land()
351
   }
426
   }
352
 
427
 
353
-land() {
428
+  land () {
354
-  this.vel.mult(0);
429
+    this.vel.mult(0)
355
-  this.isAirborne = false;
430
+    this.isAirborne = false
356
-  this.canJump = true;
431
+    this.canJump = true
357
-
358
-  // FIX: Check if we're actually landing on something valid
359
-  let landedOnSomething = false;
360
-  let landingPoint = null; // Store where we're landing for anchor
361
-  let landingObstacle = null; // NEW: Track which obstacle we're landing on
362
-
363
-  // Check if on ground
364
-  if (this.pos.y >= height - this.radius - 5) {
365
-    landedOnSomething = true;
366
-    landingPoint = createVector(this.pos.x, height);
367
-  }
368
 
432
 
369
-  // Check if on an obstacle
433
+    // FIX: Check if we're actually landing on something valid
370
-  if (!landedOnSomething) {
434
+    let landedOnSomething = false
371
-    for (let obstacle of obstacles) {
435
+    let landingPoint = null // Store where we're landing for anchor
372
-      if (this.checkObstacleCollision(obstacle)) {
436
+    let landingObstacle = null // NEW: Track which obstacle we're landing on
373
-        landedOnSomething = true;
437
+
374
-        landingObstacle = obstacle; // NEW: Store the obstacle
438
+    // Check if on ground
375
-        // Calculate edge point for anchor
439
+    if (this.pos.y >= height - this.radius - 5) {
376
-        let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x);
440
+      landedOnSomething = true
377
-        landingPoint = createVector(
441
+      landingPoint = createVector(this.pos.x, height)
378
-          obstacle.x + cos(angle) * obstacle.radius,
442
+    }
379
-          obstacle.y + sin(angle) * obstacle.radius
443
+
380
-        );
444
+    // Check if on an obstacle
381
-        break;
445
+    if (!landedOnSomething) {
446
+      for (let obstacle of obstacles) {
447
+        if (this.checkObstacleCollision(obstacle)) {
448
+          landedOnSomething = true
449
+          landingObstacle = obstacle // NEW: Store the obstacle
450
+          // Calculate edge point for anchor
451
+          let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x)
452
+          landingPoint = createVector(
453
+            obstacle.x + cos(angle) * obstacle.radius,
454
+            obstacle.y + sin(angle) * obstacle.radius
455
+          )
456
+          break
457
+        }
382
       }
458
       }
383
     }
459
     }
384
-  }
385
 
460
 
386
-  // Check if on a web strand
461
+    // Check if on a web strand
387
-  if (!landedOnSomething) {
462
+    if (!landedOnSomething) {
388
-    for (let strand of webStrands) {
463
+      for (let strand of webStrands) {
389
-      if (strand !== currentStrand && !strand.broken && this.checkStrandCollision(strand)) {
464
+        if (
390
-        landedOnSomething = true;
465
+          strand !== currentStrand &&
391
-        // For web strands, use spider position as anchor
466
+          !strand.broken &&
392
-        landingPoint = this.pos.copy();
467
+          this.checkStrandCollision(strand)
393
-        break;
468
+        ) {
469
+          landedOnSomething = true
470
+          // For web strands, use spider position as anchor
471
+          landingPoint = this.pos.copy()
472
+          break
473
+        }
394
       }
474
       }
395
     }
475
     }
396
-  }
397
 
476
 
398
-  // Check if on home branch
477
+    // Check if on home branch
399
-  if (!landedOnSomething && window.homeBranch) {
478
+    if (!landedOnSomething && window.homeBranch) {
400
-    let branch = window.homeBranch;
479
+      let branch = window.homeBranch
401
-    let branchStart = Math.min(branch.startX, branch.endX);
480
+      let branchStart = Math.min(branch.startX, branch.endX)
402
-    let branchEnd = Math.max(branch.startX, branch.endX);
481
+      let branchEnd = Math.max(branch.startX, branch.endX)
403
-
482
+
404
-    if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
483
+      if (this.pos.x >= branchStart - 10 && this.pos.x <= branchEnd + 10) {
405
-      let t = (this.pos.x - branchStart) / (branchEnd - branchStart);
484
+        let t = (this.pos.x - branchStart) / (branchEnd - branchStart)
406
-      t = constrain(t, 0, 1);
485
+        t = constrain(t, 0, 1)
407
-      let branchTopThickness = lerp(branch.thickness * 0.9, branch.thickness * 0.35, t);
486
+        let branchTopThickness = lerp(
408
-      let branchSurfaceY = branch.y - branchTopThickness;
487
+          branch.thickness * 0.9,
409
-      let angleCorrection = (this.pos.x - branchStart) * branch.angle;
488
+          branch.thickness * 0.35,
410
-      branchSurfaceY += angleCorrection;
489
+          t
411
-
490
+        )
412
-      if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) {
491
+        let branchSurfaceY = branch.y - branchTopThickness
413
-        landedOnSomething = true;
492
+        let angleCorrection = (this.pos.x - branchStart) * branch.angle
414
-        landingPoint = createVector(this.pos.x, branchSurfaceY);
493
+        branchSurfaceY += angleCorrection
494
+
495
+        if (abs(this.pos.y - branchSurfaceY) < this.radius + 10) {
496
+          landedOnSomething = true
497
+          landingPoint = createVector(this.pos.x, branchSurfaceY)
498
+        }
415
       }
499
       }
416
     }
500
     }
417
-  }
418
 
501
 
419
-  // FIX: If we're deploying web but didn't land on anything valid, destroy the web
502
+    // FIX: If we're deploying web but didn't land on anything valid, destroy the web
420
-  if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) {
503
+    if (currentStrand && isDeployingWeb && (spacePressed || touchHolding)) {
421
-    if (landedOnSomething && landingPoint) {
504
+      if (landedOnSomething && landingPoint) {
422
-      // Valid landing - finalize the web at the landing point
505
+        // Valid landing - finalize the web at the landing point
423
-      currentStrand.end = landingPoint.copy(); // Use edge point, not spider center
506
+        currentStrand.end = landingPoint.copy() // Use edge point, not spider center
424
-      
425
-      // NEW: Track obstacle attachment for the end point
426
-      if (landingObstacle) {
427
-        currentStrand.endObstacle = landingObstacle;
428
-        currentStrand.endAngle = atan2(
429
-          landingPoint.y - landingObstacle.y,
430
-          landingPoint.x - landingObstacle.x
431
-        );
432
-      }
433
-      
434
-      if (!currentStrand.path || currentStrand.path.length === 0) {
435
-        currentStrand.path = [landingPoint.copy()];
436
-      } else {
437
-        currentStrand.path.push(landingPoint.copy());
438
-      }
439
-      
440
-      let newNode = new WebNode(landingPoint.x, landingPoint.y);
441
-      // NEW: Track node attachment
442
-      if (landingObstacle) {
443
-        newNode.attachedObstacle = landingObstacle;
444
-        newNode.attachmentAngle = currentStrand.endAngle;
445
-      }
446
-      webNodes.push(newNode);
447
 
507
 
448
-      // Update last anchor for next web
508
+        // POINTS: Award 1 point for successful web strand
449
-      this.lastAnchorPoint = landingPoint.copy();
509
+        playerPoints += 1
450
-    } else {
510
+        stats.strandsCreated++ // Track for stats
451
-      // Invalid landing in mid-air - destroy the web!
452
-      if (webStrands.length > 0 && webStrands[webStrands.length - 1] === currentStrand) {
453
-        webStrands.pop(); // Remove the invalid strand
454
 
511
 
455
-        // Create poof particles
512
+        // NEW: Track obstacle attachment for the end point
456
-        for (let i = 0; i < 8; i++) {
513
+        if (landingObstacle) {
457
-          let p = new Particle(this.pos.x, this.pos.y);
514
+          currentStrand.endObstacle = landingObstacle
458
-          p.color = color(255, 255, 255, 150);
515
+          currentStrand.endAngle = atan2(
459
-          p.vel = createVector(random(-3, 3), random(-3, 3));
516
+            landingPoint.y - landingObstacle.y,
460
-          p.size = 4;
517
+            landingPoint.x - landingObstacle.x
461
-          particles.push(p);
518
+          )
462
         }
519
         }
463
 
520
 
464
-        // Notification
521
+        if (!currentStrand.path || currentStrand.path.length === 0) {
465
-        if (notifications.length < 3) {
522
+          currentStrand.path = [landingPoint.copy()]
466
-          notifications.push(new Notification("Web needs anchor point!", color(255, 150, 150)));
523
+        } else {
524
+          currentStrand.path.push(landingPoint.copy())
525
+        }
526
+
527
+        let newNode = new WebNode(landingPoint.x, landingPoint.y)
528
+        // NEW: Track node attachment
529
+        if (landingObstacle) {
530
+          newNode.attachedObstacle = landingObstacle
531
+          newNode.attachmentAngle = currentStrand.endAngle
532
+        }
533
+        webNodes.push(newNode)
534
+
535
+        // Update last anchor for next web
536
+        this.lastAnchorPoint = landingPoint.copy()
537
+      } else {
538
+        // Invalid landing in mid-air - destroy the web!
539
+        if (
540
+          webStrands.length > 0 &&
541
+          webStrands[webStrands.length - 1] === currentStrand
542
+        ) {
543
+          webStrands.pop() // Remove the invalid strand
544
+
545
+          // Create poof particles
546
+          for (let i = 0; i < 8; i++) {
547
+            let p = new Particle(this.pos.x, this.pos.y)
548
+            p.color = color(255, 255, 255, 150)
549
+            p.vel = createVector(random(-3, 3), random(-3, 3))
550
+            p.size = 4
551
+            particles.push(p)
552
+          }
553
+
554
+          // Notification
555
+          if (notifications.length < 3) {
556
+            notifications.push(
557
+              new Notification('Web needs anchor point!', color(255, 150, 150))
558
+            )
559
+          }
467
         }
560
         }
468
       }
561
       }
562
+    } else if (landedOnSomething && landingPoint) {
563
+      // Update last anchor point even when not deploying web
564
+      this.lastAnchorPoint = landingPoint.copy()
469
     }
565
     }
470
-  } else if (landedOnSomething && landingPoint) {
471
-    // Update last anchor point even when not deploying web
472
-    this.lastAnchorPoint = landingPoint.copy();
473
-  }
474
-
475
-  currentStrand = null;
476
-  isDeployingWeb = false;
477
-}
478
 
566
 
567
+    currentStrand = null
568
+    isDeployingWeb = false
569
+  }
479
 
570
 
480
   display () {
571
   display () {
481
     push()
572
     push()
@@ -552,7 +643,6 @@ class Fly {
552
 
643
 
553
   update () {
644
   update () {
554
     if (this.stuck) {
645
     if (this.stuck) {
555
-      // If stuck, check if we need to move with a drifting web
556
       this.updatePositionOnWeb()
646
       this.updatePositionOnWeb()
557
       return
647
       return
558
     }
648
     }
@@ -562,9 +652,17 @@ class Fly {
562
       if (this.vel.mag() < 0.1) {
652
       if (this.vel.mag() < 0.1) {
563
         this.stuck = true
653
         this.stuck = true
564
         fliesCaught++
654
         fliesCaught++
655
+        totalFliesCaught++ // Add to lifetime counter
656
+
657
+        // POINTS: Award points for catching fly
658
+        let catchPoints = 2 // Base points for catch
659
+        if (this.type === 'golden') catchPoints = 4
660
+        if (this.type === 'queen') catchPoints = 5
661
+        if (this.type === 'moth') catchPoints = 3
662
+        playerPoints += catchPoints
663
+
565
         webSilk = min(webSilk + 5, maxWebSilk)
664
         webSilk = min(webSilk + 5, maxWebSilk)
566
       }
665
       }
567
-      // While caught but not yet stuck, also follow the web
568
       this.updatePositionOnWeb()
666
       this.updatePositionOnWeb()
569
       return
667
       return
570
     }
668
     }
@@ -759,7 +857,29 @@ class Fly {
759
       ellipse(0, 0, 20)
857
       ellipse(0, 0, 20)
760
     }
858
     }
761
 
859
 
762
-    if (gamePhase === 'NIGHT') {
860
+    // ENHANCED: Special golden fly glow
861
+    if (this.type === 'golden') {
862
+      // Multiple layers of golden glow for visibility
863
+      noStroke()
864
+      // Outermost glow - very faint but wide
865
+      fill(255, 215, 0, 15)
866
+      ellipse(0, 0, 80)
867
+      // Mid glow
868
+      fill(255, 200, 0, 25)
869
+      ellipse(0, 0, 60)
870
+      // Inner glow
871
+      fill(255, 185, 0, 40)
872
+      ellipse(0, 0, 40)
873
+      // Core glow
874
+      fill(255, 170, 0, 60)
875
+      ellipse(0, 0, 25)
876
+
877
+      // Pulsing effect
878
+      let pulse = sin(frameCount * 0.1) * 0.3 + 0.7
879
+      fill(255, 215, 0, 80 * pulse)
880
+      ellipse(0, 0, 20)
881
+    } else if (gamePhase === 'NIGHT') {
882
+      // Regular firefly glow
763
       noStroke()
883
       noStroke()
764
       fill(255, 255, 150, this.glowIntensity * 0.3)
884
       fill(255, 255, 150, this.glowIntensity * 0.3)
765
       ellipse(0, 0, 30)
885
       ellipse(0, 0, 30)
@@ -767,8 +887,21 @@ class Fly {
767
       ellipse(0, 0, 20)
887
       ellipse(0, 0, 20)
768
     }
888
     }
769
 
889
 
770
-    fill(30)
890
+    // Body color based on type
771
-    stroke(0)
891
+    if (this.type === 'golden') {
892
+      fill(255, 215, 0) // Gold body
893
+      stroke(200, 150, 0)
894
+    } else if (this.type === 'moth') {
895
+      fill(60, 40, 30) // Brown moth
896
+      stroke(40, 20, 10)
897
+    } else if (this.type === 'queen') {
898
+      fill(150, 50, 200) // Purple queen
899
+      stroke(100, 0, 150)
900
+    } else {
901
+      fill(30) // Regular black
902
+      stroke(0)
903
+    }
904
+
772
     strokeWeight(0.5)
905
     strokeWeight(0.5)
773
     ellipse(0, 0, this.radius * 2)
906
     ellipse(0, 0, this.radius * 2)
774
 
907
 
@@ -778,13 +911,37 @@ class Fly {
778
       this.wingPhase += wingSpeed
911
       this.wingPhase += wingSpeed
779
       let wingSpread = sin(this.wingPhase) * 5
912
       let wingSpread = sin(this.wingPhase) * 5
780
 
913
 
781
-      fill(255, 255, 255, 150)
914
+      // Wing color based on type
915
+      if (this.type === 'golden') {
916
+        fill(255, 240, 150, 200) // Golden translucent wings
917
+      } else if (this.type === 'moth') {
918
+        fill(120, 100, 80, 150) // Brown moth wings
919
+      } else if (this.type === 'queen') {
920
+        fill(200, 150, 255, 180) // Purple queen wings
921
+      } else {
922
+        fill(255, 255, 255, 150) // Regular white wings
923
+      }
924
+
782
       noStroke()
925
       noStroke()
783
       ellipse(-wingSpread, 0, 6, 4)
926
       ellipse(-wingSpread, 0, 6, 4)
784
       ellipse(wingSpread, 0, 6, 4)
927
       ellipse(wingSpread, 0, 6, 4)
785
     }
928
     }
786
 
929
 
787
-    if (gamePhase === 'NIGHT') {
930
+    // Special markings for special types
931
+    if (this.type === 'golden') {
932
+      // Golden sparkle on body
933
+      fill(255, 255, 200)
934
+      noStroke()
935
+      ellipse(0, 0, 2)
936
+    } else if (this.type === 'queen') {
937
+      // Crown marking
938
+      stroke(255, 215, 0)
939
+      strokeWeight(1)
940
+      line(-2, -3, -1, -5)
941
+      line(0, -3, 0, -5)
942
+      line(2, -3, 1, -5)
943
+    } else if (gamePhase === 'NIGHT') {
944
+      // Regular glow abdomen
788
       fill(255, 255, 100, this.glowIntensity)
945
       fill(255, 255, 100, this.glowIntensity)
789
       noStroke()
946
       noStroke()
790
       ellipse(0, 2, 3)
947
       ellipse(0, 2, 3)
@@ -911,50 +1068,49 @@ class Obstacle {
911
     }
1068
     }
912
   }
1069
   }
913
 
1070
 
914
-updateAttachedStrands() {
1071
+  updateAttachedStrands () {
915
-  // Update web strands that are connected to this obstacle
1072
+    // Update web strands that are connected to this obstacle
916
-  for (let strand of webStrands) {
1073
+    for (let strand of webStrands) {
917
-    if (!strand || strand.broken) continue;
1074
+      if (!strand || strand.broken) continue
918
-    
1075
+
919
-    // Check if strand starts at this obstacle
1076
+      // Check if strand starts at this obstacle
920
-    if (strand.startObstacle === this) {
1077
+      if (strand.startObstacle === this) {
921
-      // Update the start position to maintain the attachment
1078
+        // Update the start position to maintain the attachment
922
-      let angle = strand.startAngle; // Use stored angle
1079
+        let angle = strand.startAngle // Use stored angle
923
-      strand.start.x = this.x + cos(angle) * this.radius;
1080
+        strand.start.x = this.x + cos(angle) * this.radius
924
-      strand.start.y = this.y + sin(angle) * this.radius;
1081
+        strand.start.y = this.y + sin(angle) * this.radius
925
-      
1082
+
926
-      // Update path if it exists
1083
+        // Update path if it exists
927
-      if (strand.path && strand.path.length > 0) {
1084
+        if (strand.path && strand.path.length > 0) {
928
-        strand.path[0].x = strand.start.x;
1085
+          strand.path[0].x = strand.start.x
929
-        strand.path[0].y = strand.start.y;
1086
+          strand.path[0].y = strand.start.y
1087
+        }
930
       }
1088
       }
931
-    }
1089
+
932
-    
1090
+      // Check if strand ends at this obstacle
933
-    // Check if strand ends at this obstacle
1091
+      if (strand.endObstacle === this) {
934
-    if (strand.endObstacle === this) {
1092
+        // Update the end position to maintain the attachment
935
-      // Update the end position to maintain the attachment
1093
+        let angle = strand.endAngle // Use stored angle
936
-      let angle = strand.endAngle; // Use stored angle
1094
+        strand.end.x = this.x + cos(angle) * this.radius
937
-      strand.end.x = this.x + cos(angle) * this.radius;
1095
+        strand.end.y = this.y + sin(angle) * this.radius
938
-      strand.end.y = this.y + sin(angle) * this.radius;
1096
+
939
-      
1097
+        // Update path if it exists
940
-      // Update path if it exists
1098
+        if (strand.path && strand.path.length > 0) {
941
-      if (strand.path && strand.path.length > 0) {
1099
+          strand.path[strand.path.length - 1].x = strand.end.x
942
-        strand.path[strand.path.length - 1].x = strand.end.x;
1100
+          strand.path[strand.path.length - 1].y = strand.end.y
943
-        strand.path[strand.path.length - 1].y = strand.end.y;
1101
+        }
944
       }
1102
       }
945
     }
1103
     }
946
-  }
1104
+
947
-  
1105
+    // Also update web nodes attached to this obstacle
948
-  // Also update web nodes attached to this obstacle
1106
+    for (let node of webNodes) {
949
-  for (let node of webNodes) {
1107
+      if (node.attachedObstacle === this) {
950
-    if (node.attachedObstacle === this) {
1108
+        let angle = node.attachmentAngle
951
-      let angle = node.attachmentAngle;
1109
+        node.x = this.x + cos(angle) * this.radius
952
-      node.x = this.x + cos(angle) * this.radius;
1110
+        node.y = this.y + sin(angle) * this.radius
953
-      node.y = this.y + sin(angle) * this.radius;
1111
+      }
954
     }
1112
     }
955
   }
1113
   }
956
-}
957
-
958
 
1114
 
959
   breakAttachedStrands () {
1115
   breakAttachedStrands () {
960
     // Check for strands attached to this obstacle's edge
1116
     // Check for strands attached to this obstacle's edge
@@ -2072,7 +2228,6 @@ class Bird {
2072
 
2228
 
2073
         // Warning notifications - but limited to prevent spam
2229
         // Warning notifications - but limited to prevent spam
2074
         if (notifications.length < 3) {
2230
         if (notifications.length < 3) {
2075
-          // Limit notifications
2076
           if (jumpStamina <= 20) {
2231
           if (jumpStamina <= 20) {
2077
             notifications.push(
2232
             notifications.push(
2078
               new Notification('CRITICAL STAMINA!', color(255, 50, 50))
2233
               new Notification('CRITICAL STAMINA!', color(255, 50, 50))
@@ -2083,6 +2238,9 @@ class Bird {
2083
             )
2238
             )
2084
           }
2239
           }
2085
         }
2240
         }
2241
+
2242
+        // POINTS: Award 2 points for surviving a bird attack
2243
+        playerPoints += 2
2086
       }
2244
       }
2087
 
2245
 
2088
       // Bird bounces off
2246
       // Bird bounces off
js/game.jsmodified
@@ -19,6 +19,7 @@ let gameOverTimer = 0
19
 let deathReason = ''
19
 let deathReason = ''
20
 let finalScore = 0
20
 let finalScore = 0
21
 let screenShake = 0
21
 let screenShake = 0
22
+let fliesSpawnedThisNight = 0
22
 
23
 
23
 // Resources
24
 // Resources
24
 let webSilk = 100
25
 let webSilk = 100
@@ -68,6 +69,7 @@ let isExhausted = false
68
 let fliesMunchedLastNight = 0
69
 let fliesMunchedLastNight = 0
69
 let birds = []
70
 let birds = []
70
 let staminaRegenCooldown = 0
71
 let staminaRegenCooldown = 0
72
+let staminaBonus = 0;
71
 
73
 
72
 // PHASE 4B: Wind System
74
 // PHASE 4B: Wind System
73
 let windActive = false
75
 let windActive = false
@@ -733,33 +735,40 @@ function draw () {
733
     gamePhase = 'DAWN'
735
     gamePhase = 'DAWN'
734
     phaseTimer = 0
736
     phaseTimer = 0
735
 
737
 
736
-    // Calculate dawn stamina with CLEAR BONUS DISPLAY
738
+    // NEW STAMINA CALCULATION:
737
-    let baseStamina = 30
739
+    // Fixed 100 max stamina, but starting amount depends on performance
738
-    let flyBonus = fliesMunchedLastNight * 10
740
+    maxJumpStamina = 100 // Always 100 max
739
-    staminaBonus = flyBonus
740
 
741
 
741
-    maxJumpStamina = baseStamina + flyBonus
742
+    // Calculate percentage of flies munched
742
-    maxJumpStamina = min(maxJumpStamina, 200)
743
+    let totalFliesInNight = fliesSpawnedThisNight + flies.length // Spawned + any remaining
743
-    jumpStamina = maxJumpStamina
744
+    let munchPercentage = fliesMunchedLastNight / totalFliesInNight
744
 
745
 
745
-    // IMPROVED: Combine notifications to reduce overlap
746
+    // Base stamina: 20 minimum, up to 100 for 50% or more flies munched
746
-    let staminaMessage = `Dawn: ${maxJumpStamina} stamina (${baseStamina} + ${flyBonus} from ${fliesMunchedLastNight} flies)`
747
+    if (munchPercentage >= 0.5) {
747
-    let warningMessage = ''
748
+      jumpStamina = 100 // Full stamina for eating 50%+ of flies
749
+    } else {
750
+      // Scale from 20 to 100 based on 0% to 50% munched
751
+      jumpStamina = Math.floor(20 + munchPercentage * 2 * 80)
752
+    }
753
+
754
+    // Create informative notification
755
+    let percentEaten = Math.floor(munchPercentage * 100)
756
+    let staminaMessage = `Dawn: ${jumpStamina}/100 stamina (${percentEaten}% of ${totalFliesInNight} flies eaten)`
748
 
757
 
749
-    if (maxJumpStamina <= 50) {
758
+    if (jumpStamina <= 30) {
750
-      warningMessage = ' ⚠️ DANGER!'
751
       notifications.push(
759
       notifications.push(
752
-        new Notification(staminaMessage + warningMessage, color(255, 50, 50))
760
+        new Notification(staminaMessage + ' ⚠️ DANGER!', color(255, 50, 50))
753
       )
761
       )
754
-    } else if (maxJumpStamina <= 80) {
762
+    } else if (jumpStamina <= 60) {
755
-      warningMessage = ' - Low stamina!'
756
       notifications.push(
763
       notifications.push(
757
-        new Notification(staminaMessage + warningMessage, color(255, 150, 50))
764
+        new Notification(
765
+          staminaMessage + ' - Low stamina!',
766
+          color(255, 150, 50)
767
+        )
758
       )
768
       )
759
-    } else if (maxJumpStamina >= 150) {
769
+    } else if (jumpStamina >= 90) {
760
-      warningMessage = ' - Well fed!'
761
       notifications.push(
770
       notifications.push(
762
-        new Notification(staminaMessage + warningMessage, color(100, 255, 100))
771
+        new Notification(staminaMessage + ' - Well fed!', color(100, 255, 100))
763
       )
772
       )
764
     } else {
773
     } else {
765
       notifications.push(new Notification(staminaMessage, color(255, 200, 100)))
774
       notifications.push(new Notification(staminaMessage, color(255, 200, 100)))
@@ -1116,6 +1125,8 @@ function draw () {
1116
 
1125
 
1117
     jumpStamina += regen
1126
     jumpStamina += regen
1118
     jumpStamina = min(jumpStamina, maxJumpStamina)
1127
     jumpStamina = min(jumpStamina, maxJumpStamina)
1128
+
1129
+    // FIX: Only set exhausted when truly out of stamina
1119
     isExhausted = jumpStamina < jumpCost
1130
     isExhausted = jumpStamina < jumpCost
1120
 
1131
 
1121
     // Update and display birds
1132
     // Update and display birds
@@ -1234,6 +1245,7 @@ function draw () {
1234
       if (flyType === 'queen') fly.baseSpeed *= 0.5
1245
       if (flyType === 'queen') fly.baseSpeed *= 0.5
1235
       fly.currentSpeed = fly.baseSpeed
1246
       fly.currentSpeed = fly.baseSpeed
1236
       flies.push(fly)
1247
       flies.push(fly)
1248
+      fliesSpawnedThisNight++ // Track dynamic spawn
1237
     }
1249
     }
1238
     if (phaseTimer % 300 === 0 && foodBoxes.length < 6) {
1250
     if (phaseTimer % 300 === 0 && foodBoxes.length < 6) {
1239
       spawnFoodBox()
1251
       spawnFoodBox()
@@ -1927,6 +1939,9 @@ function applyUpgradeEffects () {
1927
 }
1939
 }
1928
 
1940
 
1929
 function spawnNightFlies () {
1941
 function spawnNightFlies () {
1942
+  // Reset counter for new night
1943
+  fliesSpawnedThisNight = 0
1944
+
1930
   // Base flies + more per night
1945
   // Base flies + more per night
1931
   let numFlies = 5 + currentNight
1946
   let numFlies = 5 + currentNight
1932
 
1947
 
@@ -1956,6 +1971,7 @@ function spawnNightFlies () {
1956
     if (flyType === 'queen') fly.baseSpeed *= 0.5 // Queens are much slower
1971
     if (flyType === 'queen') fly.baseSpeed *= 0.5 // Queens are much slower
1957
     fly.currentSpeed = fly.baseSpeed
1972
     fly.currentSpeed = fly.baseSpeed
1958
     flies.push(fly)
1973
     flies.push(fly)
1974
+    fliesSpawnedThisNight++ // Track spawn
1959
   }
1975
   }
1960
 
1976
 
1961
   // PHASE 2: Guarantee at least 1 golden fly per night
1977
   // PHASE 2: Guarantee at least 1 golden fly per night
@@ -1964,6 +1980,7 @@ function spawnNightFlies () {
1964
     goldenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 1.3
1980
     goldenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 1.3
1965
     goldenFly.currentSpeed = goldenFly.baseSpeed
1981
     goldenFly.currentSpeed = goldenFly.baseSpeed
1966
     flies.push(goldenFly)
1982
     flies.push(goldenFly)
1983
+    fliesSpawnedThisNight++ // Track spawn
1967
     // Add notification
1984
     // Add notification
1968
     notifications.push(
1985
     notifications.push(
1969
       new Notification('Golden Firefly Appeared! ✨', color(255, 215, 0))
1986
       new Notification('Golden Firefly Appeared! ✨', color(255, 215, 0))
@@ -1979,6 +1996,7 @@ function spawnNightFlies () {
1979
     queenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 0.5
1996
     queenFly.baseSpeed = baseFlySpeed * flySpeedMultiplier * 0.5
1980
     queenFly.currentSpeed = queenFly.baseSpeed
1997
     queenFly.currentSpeed = queenFly.baseSpeed
1981
     flies.push(queenFly)
1998
     flies.push(queenFly)
1999
+    fliesSpawnedThisNight++ // Track spawn
1982
     // Add notification
2000
     // Add notification
1983
     notifications.push(
2001
     notifications.push(
1984
       new Notification('Queen Firefly Arrived! 👑', color(200, 100, 255))
2002
       new Notification('Queen Firefly Arrived! 👑', color(200, 100, 255))
@@ -2509,50 +2527,57 @@ function updateResources () {
2509
   }
2527
   }
2510
 }
2528
 }
2511
 
2529
 
2512
-function handleWebDeployment() {
2530
+function handleWebDeployment () {
2513
   // Handle keyboard-based web deployment
2531
   // Handle keyboard-based web deployment
2514
   if (spacePressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) {
2532
   if (spacePressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) {
2515
-    isDeployingWeb = true;
2533
+    isDeployingWeb = true
2516
-    currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
2534
+    currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null)
2517
-    currentStrand.path = [spider.lastAnchorPoint.copy()];
2535
+    currentStrand.path = [spider.lastAnchorPoint.copy()]
2518
-    
2536
+
2519
     // NEW: Check if starting from an obstacle
2537
     // NEW: Check if starting from an obstacle
2520
     for (let obstacle of obstacles) {
2538
     for (let obstacle of obstacles) {
2521
-      let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y);
2539
+      let d = dist(
2540
+        spider.lastAnchorPoint.x,
2541
+        spider.lastAnchorPoint.y,
2542
+        obstacle.x,
2543
+        obstacle.y
2544
+      )
2522
       // Check if anchor is on obstacle edge (within tolerance)
2545
       // Check if anchor is on obstacle edge (within tolerance)
2523
       if (abs(d - obstacle.radius) < 10) {
2546
       if (abs(d - obstacle.radius) < 10) {
2524
-        currentStrand.startObstacle = obstacle;
2547
+        currentStrand.startObstacle = obstacle
2525
         currentStrand.startAngle = atan2(
2548
         currentStrand.startAngle = atan2(
2526
           spider.lastAnchorPoint.y - obstacle.y,
2549
           spider.lastAnchorPoint.y - obstacle.y,
2527
           spider.lastAnchorPoint.x - obstacle.x
2550
           spider.lastAnchorPoint.x - obstacle.x
2528
-        );
2551
+        )
2529
-        break;
2552
+        break
2530
       }
2553
       }
2531
     }
2554
     }
2532
-    
2555
+
2533
-    webStrands.push(currentStrand);
2556
+    webStrands.push(currentStrand)
2534
-    
2557
+
2535
-    let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y);
2558
+    let newNode = new WebNode(
2559
+      spider.lastAnchorPoint.x,
2560
+      spider.lastAnchorPoint.y
2561
+    )
2536
     // NEW: Track node attachment if on obstacle
2562
     // NEW: Track node attachment if on obstacle
2537
     if (currentStrand.startObstacle) {
2563
     if (currentStrand.startObstacle) {
2538
-      newNode.attachedObstacle = currentStrand.startObstacle;
2564
+      newNode.attachedObstacle = currentStrand.startObstacle
2539
-      newNode.attachmentAngle = currentStrand.startAngle;
2565
+      newNode.attachmentAngle = currentStrand.startAngle
2540
     }
2566
     }
2541
-    webNodes.push(newNode);
2567
+    webNodes.push(newNode)
2542
   }
2568
   }
2543
 
2569
 
2544
   // Update web for keyboard controls
2570
   // Update web for keyboard controls
2545
   if (currentStrand && isDeployingWeb && spider.isAirborne && spacePressed) {
2571
   if (currentStrand && isDeployingWeb && spider.isAirborne && spacePressed) {
2546
-    currentStrand.end = spider.pos.copy();
2572
+    currentStrand.end = spider.pos.copy()
2547
     if (frameCount % 2 === 0) {
2573
     if (frameCount % 2 === 0) {
2548
-      currentStrand.path.push(spider.pos.copy());
2574
+      currentStrand.path.push(spider.pos.copy())
2549
     }
2575
     }
2550
   }
2576
   }
2551
 
2577
 
2552
   // Touch-based web deployment is handled in touchMoved()
2578
   // Touch-based web deployment is handled in touchMoved()
2553
 }
2579
 }
2554
 
2580
 
2555
-
2556
 function updateUI () {
2581
 function updateUI () {
2557
   // Update control instructions based on device
2582
   // Update control instructions based on device
2558
   let isMobile =
2583
   let isMobile =
@@ -2583,14 +2608,15 @@ function updateUI () {
2583
     '<br>' +
2608
     '<br>' +
2584
     'Web Strands: <span id="strand-count">0</span><br>' +
2609
     'Web Strands: <span id="strand-count">0</span><br>' +
2585
     'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>' +
2610
     'Flies Caught: <span id="flies-caught">0</span> | Munched: <span id="flies-munched">0</span><br>' +
2586
-    'Total Score: <span id="total-score">0</span>'
2611
+    'Points: <span id="player-points">0</span> | Total Score: <span id="total-score">0</span>'
2587
 
2612
 
2588
-  // PHASE 1 UPDATES
2613
+  // Update all the displays
2589
   document.getElementById('strand-count').textContent = webStrands.filter(
2614
   document.getElementById('strand-count').textContent = webStrands.filter(
2590
     s => !s.broken
2615
     s => !s.broken
2591
   ).length
2616
   ).length
2592
   document.getElementById('flies-caught').textContent = fliesCaught
2617
   document.getElementById('flies-caught').textContent = fliesCaught
2593
   document.getElementById('flies-munched').textContent = fliesMunched
2618
   document.getElementById('flies-munched').textContent = fliesMunched
2619
+  document.getElementById('player-points').textContent = playerPoints // NEW
2594
   document.getElementById('total-score').textContent = totalFliesCaught
2620
   document.getElementById('total-score').textContent = totalFliesCaught
2595
 
2621
 
2596
   // Update phase display
2622
   // Update phase display
@@ -2622,27 +2648,40 @@ function updateUI () {
2622
     timerText +
2648
     timerText +
2623
     `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>`
2649
     `<br><small ${staminaColor}>Dawn Stamina: ${potentialStamina}</small>`
2624
 
2650
 
2625
-  if (gamePhase === 'DUSK') {
2651
+  if (gamePhase === 'NIGHT') {
2626
-    let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60)
2652
+  let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60);
2627
-    timerText = `${timeLeft}s to prepare!`
2653
+  
2628
-  } else if (gamePhase === 'NIGHT') {
2654
+  // Calculate current munch percentage
2629
-    let timeLeft = Math.ceil((NIGHT_DURATION - phaseTimer) / 60)
2655
+  let totalFliesInNight = fliesSpawnedThisNight + flies.length;
2630
-    // PHASE 2: Count different fly types
2656
+  let currentMunchPercent = totalFliesInNight > 0 ? 
2631
-    let regularCount = flies.filter(f => f.type === 'regular').length
2657
+    Math.floor((fliesMunched / totalFliesInNight) * 100) : 0;
2632
-    let goldenCount = flies.filter(f => f.type === 'golden').length
2658
+  
2633
-    let mothCount = flies.filter(f => f.type === 'moth').length
2659
+  // Calculate predicted dawn stamina
2634
-    let queenCount = flies.filter(f => f.type === 'queen').length
2660
+  let predictedStamina;
2635
-
2661
+  if (currentMunchPercent >= 50) {
2636
-    timerText = `${timeLeft}s • ${flies.length} flies`
2662
+    predictedStamina = 100;
2637
-
2663
+  } else {
2638
-    // Show special fly counts if any
2664
+    predictedStamina = Math.floor(20 + (currentMunchPercent * 2) * 0.8);
2639
-    if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2665
+  }
2640
-      let specialCounts = []
2666
+  
2641
-      if (queenCount > 0) specialCounts.push(`${queenCount}👑`)
2667
+  timerText = `${timeLeft}s • ${flies.length} flies`;
2642
-      if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`)
2668
+  
2643
-      if (mothCount > 0) specialCounts.push(`${mothCount}🦋`)
2669
+  // Show special fly counts if any
2644
-      timerText += ` (${specialCounts.join(' ')})`
2670
+  let goldenCount = flies.filter(f => f.type === 'golden').length;
2645
-    }
2671
+  let mothCount = flies.filter(f => f.type === 'moth').length;
2672
+  let queenCount = flies.filter(f => f.type === 'queen').length;
2673
+  
2674
+  if (goldenCount > 0 || mothCount > 0 || queenCount > 0) {
2675
+    let specialCounts = [];
2676
+    if (queenCount > 0) specialCounts.push(`${queenCount}👑`);
2677
+    if (goldenCount > 0) specialCounts.push(`${goldenCount}✨`);
2678
+    if (mothCount > 0) specialCounts.push(`${mothCount}🦋`);
2679
+    timerText += ` (${specialCounts.join(' ')})`;
2680
+  }
2681
+   // Show munch progress
2682
+  document.getElementById('timer').innerHTML = timerText + 
2683
+    `<br><small style="color: ${predictedStamina < 40 ? '#ff4444' : predictedStamina < 70 ? '#ffaa44' : '#44ff44'}">` +
2684
+    `Munched: ${currentMunchPercent}% → ${predictedStamina} dawn stamina</small>`;
2646
   } else if (gamePhase === 'DAWN') {
2685
   } else if (gamePhase === 'DAWN') {
2647
     let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60)
2686
     let timeLeft = Math.ceil((DAWN_DURATION - phaseTimer) / 60)
2648
     // PHASE 4: Show birds and exhaustion status
2687
     // PHASE 4: Show birds and exhaustion status
@@ -2687,48 +2726,49 @@ function updateUI () {
2687
   }
2726
   }
2688
 
2727
 
2689
   // PHASE 4: Update meter based on phase
2728
   // PHASE 4: Update meter based on phase
2690
-  if (gamePhase === 'DAWN') {
2729
+if (gamePhase === 'DAWN') {
2691
-    // Show stamina instead of silk during dawn
2730
+  // Show stamina instead of silk during dawn
2692
-    document.getElementById('web-meter-label').textContent = 'STAMINA'
2731
+  document.getElementById('web-meter-label').textContent = 'STAMINA';
2693
-    let staminaPercent = (jumpStamina / maxJumpStamina) * 100
2732
+  
2694
-    document.getElementById('web-meter-fill').style.width = staminaPercent + '%'
2733
+  // FIX: Always show percentage out of 100, not out of variable max
2695
-
2734
+  let staminaPercent = (jumpStamina / 100) * 100; // Always out of 100
2696
-    // Color based on stamina level
2735
+  document.getElementById('web-meter-fill').style.width = staminaPercent + '%';
2697
-    if (jumpStamina < jumpCost) {
2736
+
2698
-      // Exhausted - red flash
2737
+  // Color based on stamina level
2699
-      let flash = sin(frameCount * 0.3) * 0.5 + 0.5
2738
+  if (jumpStamina < 20) {
2700
-      document.getElementById(
2739
+    // Exhausted - red flash
2701
-        'web-meter-fill'
2740
+    let flash = sin(frameCount * 0.3) * 0.5 + 0.5;
2702
-      ).style.background = `linear-gradient(90deg, rgb(255, ${
2741
+    document.getElementById('web-meter-fill').style.background = 
2703
-        50 + flash * 50
2742
+      `linear-gradient(90deg, rgb(255, ${50 + flash * 50}, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`;
2704
-      }, ${50 + flash * 50}), rgb(200, ${30 + flash * 30}, ${30 + flash * 30}))`
2743
+  } else if (jumpStamina < 40) {
2705
-    } else if (jumpStamina < maxJumpStamina * 0.3) {
2744
+    // Very tired - orange-red
2706
-      // Very tired - orange-red
2745
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FF6B35, #FF4444)';
2707
-      document.getElementById('web-meter-fill').style.background =
2746
+  } else if (jumpStamina < 60) {
2708
-        'linear-gradient(90deg, #FF6B35, #FF4444)'
2747
+    // Tired - orange
2709
-    } else if (jumpStamina < maxJumpStamina * 0.5) {
2748
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFA500, #FF8C00)';
2710
-      // Tired - orange
2749
+  } else if (jumpStamina < 80) {
2711
-      document.getElementById('web-meter-fill').style.background =
2750
+    // OK - yellow-orange
2712
-        'linear-gradient(90deg, #FFA500, #FF8C00)'
2751
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #FFD700, #FFA500)';
2713
-    } else {
2714
-      // Good stamina - yellow-orange
2715
-      document.getElementById('web-meter-fill').style.background =
2716
-        'linear-gradient(90deg, #FFD700, #FFA500)'
2717
-    }
2718
-    if (jumpStamina <= 0 && !gameOver) {
2719
-      push()
2720
-      fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50)
2721
-      rect(0, 0, width, height)
2722
-
2723
-      textAlign(CENTER)
2724
-      textSize(32)
2725
-      fill(255, 50, 50)
2726
-      stroke(0)
2727
-      strokeWeight(3)
2728
-      text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2)
2729
-      pop()
2730
-    }
2731
   } else {
2752
   } else {
2753
+    // Good stamina - green-yellow
2754
+    document.getElementById('web-meter-fill').style.background = 'linear-gradient(90deg, #90EE90, #FFD700)';
2755
+  }
2756
+  
2757
+  // Show critical warning overlay
2758
+  if (jumpStamina <= 0 && !gameOver) {
2759
+    push();
2760
+    fill(255, 0, 0, 50 + sin(frameCount * 0.3) * 50);
2761
+    rect(0, 0, width, height);
2762
+
2763
+    textAlign(CENTER);
2764
+    textSize(32);
2765
+    fill(255, 50, 50);
2766
+    stroke(0);
2767
+    strokeWeight(3);
2768
+    text('NO STAMINA - AVOID BIRDS!', width / 2, height / 2);
2769
+    pop();
2770
+  }
2771
+} else {
2732
     // Normal silk meter
2772
     // Normal silk meter
2733
     document.getElementById('web-meter-label').textContent = 'SILK'
2773
     document.getElementById('web-meter-label').textContent = 'SILK'
2734
     let meterPercent = (webSilk / maxWebSilk) * 100
2774
     let meterPercent = (webSilk / maxWebSilk) * 100
@@ -2840,6 +2880,7 @@ window.restartGame = function () {
2840
   currentNight = 1
2880
   currentNight = 1
2841
   fliesCaught = 0
2881
   fliesCaught = 0
2842
   fliesMunched = 0
2882
   fliesMunched = 0
2883
+  playerPoints = 0
2843
   totalFliesCaught = 0
2884
   totalFliesCaught = 0
2844
   jumpStamina = 100
2885
   jumpStamina = 100
2845
   maxJumpStamina = 100
2886
   maxJumpStamina = 100
@@ -3023,58 +3064,67 @@ function recycleNearbyWeb () {
3023
   }
3064
   }
3024
 }
3065
 }
3025
 
3066
 
3026
-function touchStarted() {
3067
+function touchStarted () {
3027
   if (touches.length > 0) {
3068
   if (touches.length > 0) {
3028
-    touchStartTime = millis();
3069
+    touchStartTime = millis()
3029
-    touchStartX = touches[0].x;
3070
+    touchStartX = touches[0].x
3030
-    touchStartY = touches[0].y;
3071
+    touchStartY = touches[0].y
3031
 
3072
 
3032
     // Check for double tap on spider to munch
3073
     // Check for double tap on spider to munch
3033
-    let touchOnSpider = dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30;
3074
+    let touchOnSpider =
3075
+      dist(touches[0].x, touches[0].y, spider.pos.x, spider.pos.y) < 30
3034
 
3076
 
3035
     if (touchOnSpider && millis() - lastTapTime < 300) {
3077
     if (touchOnSpider && millis() - lastTapTime < 300) {
3036
       // Double tap detected on spider - MUNCH!
3078
       // Double tap detected on spider - MUNCH!
3037
-      spider.munch();
3079
+      spider.munch()
3038
-      lastTapTime = 0; // Reset to prevent triple tap
3080
+      lastTapTime = 0 // Reset to prevent triple tap
3039
     } else if (!spider.isAirborne) {
3081
     } else if (!spider.isAirborne) {
3040
       // Single tap while on ground - jump
3082
       // Single tap while on ground - jump
3041
-      spider.jump(touches[0].x, touches[0].y);
3083
+      spider.jump(touches[0].x, touches[0].y)
3042
-      lastTapTime = millis();
3084
+      lastTapTime = millis()
3043
     } else if (spider.isAirborne && webSilk > 10 && !isDeployingWeb) {
3085
     } else if (spider.isAirborne && webSilk > 10 && !isDeployingWeb) {
3044
       // Start web deployment if airborne (only if not already deploying)
3086
       // Start web deployment if airborne (only if not already deploying)
3045
-      touchHolding = true;
3087
+      touchHolding = true
3046
-      isDeployingWeb = true;
3088
+      isDeployingWeb = true
3047
-      currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
3089
+      currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null)
3048
-      currentStrand.path = [spider.lastAnchorPoint.copy()];
3090
+      currentStrand.path = [spider.lastAnchorPoint.copy()]
3049
-      
3091
+
3050
       for (let obstacle of obstacles) {
3092
       for (let obstacle of obstacles) {
3051
-        let d = dist(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y, obstacle.x, obstacle.y);
3093
+        let d = dist(
3094
+          spider.lastAnchorPoint.x,
3095
+          spider.lastAnchorPoint.y,
3096
+          obstacle.x,
3097
+          obstacle.y
3098
+        )
3052
         // Check if anchor is on obstacle edge (within tolerance)
3099
         // Check if anchor is on obstacle edge (within tolerance)
3053
         if (abs(d - obstacle.radius) < 10) {
3100
         if (abs(d - obstacle.radius) < 10) {
3054
-          currentStrand.startObstacle = obstacle;
3101
+          currentStrand.startObstacle = obstacle
3055
           currentStrand.startAngle = atan2(
3102
           currentStrand.startAngle = atan2(
3056
             spider.lastAnchorPoint.y - obstacle.y,
3103
             spider.lastAnchorPoint.y - obstacle.y,
3057
             spider.lastAnchorPoint.x - obstacle.x
3104
             spider.lastAnchorPoint.x - obstacle.x
3058
-          );
3105
+          )
3059
-          break;
3106
+          break
3060
         }
3107
         }
3061
       }
3108
       }
3062
-      
3109
+
3063
-      webStrands.push(currentStrand);
3110
+      webStrands.push(currentStrand)
3064
-      
3111
+
3065
-      let newNode = new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y);
3112
+      let newNode = new WebNode(
3113
+        spider.lastAnchorPoint.x,
3114
+        spider.lastAnchorPoint.y
3115
+      )
3066
       // NEW: Track node attachment if on obstacle
3116
       // NEW: Track node attachment if on obstacle
3067
       if (currentStrand.startObstacle) {
3117
       if (currentStrand.startObstacle) {
3068
-        newNode.attachedObstacle = currentStrand.startObstacle;
3118
+        newNode.attachedObstacle = currentStrand.startObstacle
3069
-        newNode.attachmentAngle = currentStrand.startAngle;
3119
+        newNode.attachmentAngle = currentStrand.startAngle
3070
       }
3120
       }
3071
-      webNodes.push(newNode);
3121
+      webNodes.push(newNode)
3072
     } else if (spider.isAirborne && isDeployingWeb) {
3122
     } else if (spider.isAirborne && isDeployingWeb) {
3073
       // If already deploying and user taps again, just continue (don't create new strand)
3123
       // If already deploying and user taps again, just continue (don't create new strand)
3074
-      touchHolding = true;
3124
+      touchHolding = true
3075
     }
3125
     }
3076
   }
3126
   }
3077
-  return false; // Prevent default
3127
+  return false // Prevent default
3078
 }
3128
 }
3079
 
3129
 
3080
 function touchMoved () {
3130
 function touchMoved () {