zeroed-some/cob / 2fe52d1

Browse files

refactor

Authored by espadonne
SHA
2fe52d12e08039407c5c04c6bdaabbe1343f9c89
Parents
5724b53
Tree
18c1160

5 changed files

StatusFile+-
A css/styles.css 86 0
M index.html 5 1105
A js/entities.js 525 0
A js/game.js 355 0
A js/physics.js 147 0
css/styles.cssadded
@@ -0,0 +1,86 @@
1
+body {
2
+  margin: 0;
3
+  padding: 0;
4
+  overflow: hidden;
5
+  background: #000;
6
+  width: 100vw;
7
+  height: 100vh;
8
+  font-family: Arial, sans-serif;
9
+}
10
+
11
+#game-container {
12
+  position: fixed;
13
+  top: 0;
14
+  left: 0;
15
+  width: 100vw;
16
+  height: 100vh;
17
+}
18
+
19
+canvas {
20
+  display: block;
21
+  width: 100% !important;
22
+  height: 100% !important;
23
+}
24
+
25
+#info {
26
+  position: absolute;
27
+  top: 20px;
28
+  left: 20px;
29
+  color: white;
30
+  font-size: 16px;
31
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
32
+  pointer-events: none;
33
+  z-index: 10;
34
+  background: rgba(0, 0, 0, 0.3);
35
+  padding: 10px 15px;
36
+  border-radius: 8px;
37
+}
38
+
39
+#phase-indicator {
40
+  position: absolute;
41
+  top: 20px;
42
+  right: 20px;
43
+  color: white;
44
+  font-size: 20px;
45
+  font-weight: bold;
46
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
47
+  pointer-events: none;
48
+  z-index: 10;
49
+  text-align: right;
50
+  background: rgba(0, 0, 0, 0.3);
51
+  padding: 10px 15px;
52
+  border-radius: 8px;
53
+}
54
+
55
+#web-meter {
56
+  position: absolute;
57
+  bottom: 30px;
58
+  left: 50%;
59
+  transform: translateX(-50%);
60
+  width: 300px;
61
+  height: 40px;
62
+  background: rgba(0, 0, 0, 0.4);
63
+  border: 3px solid rgba(255, 255, 255, 0.6);
64
+  border-radius: 20px;
65
+  overflow: hidden;
66
+  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.5);
67
+}
68
+
69
+#web-meter-fill {
70
+  height: 100%;
71
+  background: linear-gradient(90deg, #87ceeb, #e0f6ff);
72
+  transition: width 0.3s ease;
73
+  box-shadow: inset 0 0 15px rgba(255, 255, 255, 0.5);
74
+}
75
+
76
+#web-meter-label {
77
+  position: absolute;
78
+  bottom: 75px;
79
+  left: 50%;
80
+  transform: translateX(-50%);
81
+  color: white;
82
+  font-size: 14px;
83
+  font-weight: bold;
84
+  text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
85
+}
86
+
index.htmlmodified
1125 lines changed — click to load
@@ -6,84 +6,7 @@
66
     <title>Cob :: Monch Fireflies</title>
77
     <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script>
88
     <style>
9
-        body {
10
-            margin: 0;
11
-            padding: 0;
12
-            overflow: hidden;
13
-            background: #000;
14
-            width: 100vw;
15
-            height: 100vh;
16
-            font-family: Arial, sans-serif;
17
-        }
18
-        #game-container {
19
-            position: fixed;
20
-            top: 0;
21
-            left: 0;
22
-            width: 100vw;
23
-            height: 100vh;
24
-        }
25
-        canvas {
26
-            display: block;
27
-            width: 100% !important;
28
-            height: 100% !important;
29
-        }
30
-        #info {
31
-            position: absolute;
32
-            top: 20px;
33
-            left: 20px;
34
-            color: white;
35
-            font-size: 16px;
36
-            text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
37
-            pointer-events: none;
38
-            z-index: 10;
39
-            background: rgba(0,0,0,0.3);
40
-            padding: 10px 15px;
41
-            border-radius: 8px;
42
-        }
43
-        #phase-indicator {
44
-            position: absolute;
45
-            top: 20px;
46
-            right: 20px;
47
-            color: white;
48
-            font-size: 20px;
49
-            font-weight: bold;
50
-            text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
51
-            pointer-events: none;
52
-            z-index: 10;
53
-            text-align: right;
54
-            background: rgba(0,0,0,0.3);
55
-            padding: 10px 15px;
56
-            border-radius: 8px;
57
-        }
58
-        #web-meter {
59
-            position: absolute;
60
-            bottom: 30px;
61
-            left: 50%;
62
-            transform: translateX(-50%);
63
-            width: 300px;
64
-            height: 40px;
65
-            background: rgba(0,0,0,0.4);
66
-            border: 3px solid rgba(255,255,255,0.6);
67
-            border-radius: 20px;
68
-            overflow: hidden;
69
-            box-shadow: 0 4px 15px rgba(0,0,0,0.5);
70
-        }
71
-        #web-meter-fill {
72
-            height: 100%;
73
-            background: linear-gradient(90deg, #87CEEB, #E0F6FF);
74
-            transition: width 0.3s ease;
75
-            box-shadow: inset 0 0 15px rgba(255,255,255,0.5);
76
-        }
77
-        #web-meter-label {
78
-            position: absolute;
79
-            bottom: 75px;
80
-            left: 50%;
81
-            transform: translateX(-50%);
82
-            color: white;
83
-            font-size: 14px;
84
-            font-weight: bold;
85
-            text-shadow: 2px 2px 4px rgba(0,0,0,0.8);
86
-        }
9
+        
8710
     </style>
8811
 </head>
8912
 <body>
@@ -97,1036 +20,13 @@
9720
             <span id="phase">DUSK</span><br>
9821
             <span id="timer"></span>
9922
         </div>
100
-        <div id="web-meter-label">WEB SILK</div>
23
+        <div id="web-meter-label">SILK</div>
10124
         <div id="web-meter">
10225
             <div id="web-meter-fill"></div>
10326
         </div>
10427
     </div>
105
-    <script>
106
-        let spider;
107
-        let obstacles = [];
108
-        let webStrands = [];
109
-        let flies = [];
110
-        let foodBoxes = [];
111
-        let particles = [];
112
-        let isDeployingWeb = false;
113
-        let currentStrand = null;
114
-        let spacePressed = false; // Track spacebar state
115
-        let isMunching = false;
116
-        
117
-        // Web resource management
118
-        let webSilk = 100;
119
-        let maxWebSilk = 100;
120
-        let silkRechargeRate = 0.05;
121
-        let silkDrainRate = 2;
122
-        
123
-        // Game phases
124
-        let gamePhase = 'DUSK';
125
-        let phaseTimer = 0;
126
-        let DUSK_DURATION = 1500; // 25 seconds at 60fps
127
-        let TRANSITION_DURATION = 180; // 3 seconds
128
-        let skyColor1, skyColor2, currentSkyColor1, currentSkyColor2;
129
-        let moonY = 100;
130
-        let moonOpacity = 0;
131
-        let fliesCaught = 0;
132
-        let fliesMunched = 0;
133
-
134
-        // Web physics
135
-        let webNodes = [];
136
-
137
-        class Spider {
138
-            constructor(x, y) {
139
-                this.pos = createVector(x, y);
140
-                this.vel = createVector(0, 0);
141
-                this.acc = createVector(0, 0);
142
-                this.radius = 8;
143
-                this.isAirborne = false;
144
-                this.canJump = true;
145
-                this.lastAnchorPoint = null;
146
-                this.gravity = createVector(0, 0.3);
147
-                this.jumpPower = 12;
148
-                this.maxSpeed = 15;
149
-                this.munchRadius = 20; // Range for munching
150
-                this.munchCooldown = 0;
151
-            }
152
-
153
-            jump(targetX, targetY) {
154
-                if (!this.canJump) return;
155
-                
156
-                let direction = createVector(targetX - this.pos.x, targetY - this.pos.y);
157
-                direction.normalize();
158
-                direction.mult(this.jumpPower);
159
-                
160
-                this.vel = direction;
161
-                this.isAirborne = true;
162
-                this.canJump = false;
163
-                this.lastAnchorPoint = this.pos.copy();
164
-            }
165
-            
166
-            munch() {
167
-                if (this.munchCooldown > 0) return;
168
-                
169
-                isMunching = true;
170
-                this.munchCooldown = 30;
171
-                
172
-                // Check for flies in munch range
173
-                for (let i = flies.length - 1; i >= 0; i--) {
174
-                    let fly = flies[i];
175
-                    let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y);
176
-                    if (d < this.munchRadius) {
177
-                        // Successful munch!
178
-                        fliesMunched++;
179
-                        webSilk = min(webSilk + 15, maxWebSilk); // Munching gives good silk
180
-                        
181
-                        // Create munch particles
182
-                        for (let j = 0; j < 12; j++) {
183
-                            let p = new Particle(fly.pos.x, fly.pos.y);
184
-                            p.color = color(255, random(100, 255), 0);
185
-                            particles.push(p);
186
-                        }
187
-                        
188
-                        flies.splice(i, 1);
189
-                        break; // Only munch one fly at a time
190
-                    }
191
-                }
192
-            }
193
-
194
-            update() {
195
-                if (this.isAirborne) {
196
-                    this.acc.add(this.gravity);
197
-                }
198
-                
199
-                this.vel.add(this.acc);
200
-                this.vel.limit(this.maxSpeed);
201
-                this.pos.add(this.vel);
202
-                this.acc.mult(0);
203
-                
204
-                // Update munch cooldown
205
-                if (this.munchCooldown > 0) {
206
-                    this.munchCooldown--;
207
-                    if (this.munchCooldown === 0) {
208
-                        isMunching = false;
209
-                    }
210
-                }
211
-
212
-                // Check ground collision
213
-                if (this.pos.y >= height - this.radius) {
214
-                    this.pos.y = height - this.radius;
215
-                    this.land();
216
-                }
217
-
218
-                // Check wall collisions
219
-                if (this.pos.x <= this.radius || this.pos.x >= width - this.radius) {
220
-                    this.pos.x = constrain(this.pos.x, this.radius, width - this.radius);
221
-                    this.vel.x *= -0.5;
222
-                }
223
-
224
-                // Check ceiling
225
-                if (this.pos.y <= this.radius) {
226
-                    this.pos.y = this.radius;
227
-                    this.vel.y *= -0.5;
228
-                }
229
-
230
-                // Check obstacle collisions
231
-                for (let obstacle of obstacles) {
232
-                    if (this.checkObstacleCollision(obstacle)) {
233
-                        this.landOnObstacle(obstacle);
234
-                    }
235
-                }
236
-
237
-                // Check web strand collisions
238
-                for (let strand of webStrands) {
239
-                    if (this.isAirborne && this.checkStrandCollision(strand)) {
240
-                        this.landOnStrand(strand);
241
-                    }
242
-                }
243
-                
244
-                // Check food box collisions
245
-                for (let i = foodBoxes.length - 1; i >= 0; i--) {
246
-                    let box = foodBoxes[i];
247
-                    if (dist(this.pos.x, this.pos.y, box.pos.x, box.pos.y) < this.radius + box.radius) {
248
-                        box.collect();
249
-                        foodBoxes.splice(i, 1);
250
-                    }
251
-                }
252
-            }
253
-
254
-            checkObstacleCollision(obstacle) {
255
-                let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y);
256
-                return d < this.radius + obstacle.radius;
257
-            }
258
-
259
-            checkStrandCollision(strand) {
260
-                let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
261
-                return d < this.radius + 2;
262
-            }
263
-
264
-            pointToLineDistance(point, lineStart, lineEnd) {
265
-                let line = p5.Vector.sub(lineEnd, lineStart);
266
-                let lineLength = line.mag();
267
-                line.normalize();
268
-                
269
-                let pointToStart = p5.Vector.sub(point, lineStart);
270
-                let projLength = constrain(pointToStart.dot(line), 0, lineLength);
271
-                
272
-                let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
273
-                return p5.Vector.dist(point, closestPoint);
274
-            }
275
-
276
-            landOnObstacle(obstacle) {
277
-                let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x);
278
-                this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius);
279
-                this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius);
280
-                this.land();
281
-            }
282
-
283
-            landOnStrand(strand) {
284
-                let line = p5.Vector.sub(strand.end, strand.start);
285
-                let lineLength = line.mag();
286
-                line.normalize();
287
-                
288
-                let pointToStart = p5.Vector.sub(this.pos, strand.start);
289
-                let projLength = constrain(pointToStart.dot(line), 0, lineLength);
290
-                
291
-                let closestPoint = p5.Vector.add(strand.start, p5.Vector.mult(line, projLength));
292
-                this.pos = closestPoint;
293
-                this.land();
294
-            }
295
-
296
-            land() {
297
-                this.vel.mult(0);
298
-                this.isAirborne = false;
299
-                this.canJump = true;
300
-                
301
-                if (currentStrand && isDeployingWeb) {
302
-                    currentStrand.end = this.pos.copy();
303
-                    // Make sure to capture the final position in the path
304
-                    currentStrand.path.push(this.pos.copy());
305
-                    webNodes.push(new WebNode(this.pos.x, this.pos.y));
306
-                    currentStrand = null;
307
-                }
308
-                isDeployingWeb = false;
309
-            }
310
-
311
-            display() {
312
-                push();
313
-                translate(this.pos.x, this.pos.y);
314
-                
315
-                // Munch animation
316
-                if (isMunching && this.munchCooldown > 15) {
317
-                    // Chomping mouth effect
318
-                    push();
319
-                    fill(255, 100, 100, 150);
320
-                    noStroke();
321
-                    let munchSize = 15 + sin(frameCount * 0.5) * 5;
322
-                    arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE);
323
-                    pop();
324
-                }
325
-                
326
-                // Spider body
327
-                fill(20);
328
-                stroke(0);
329
-                strokeWeight(1);
330
-                ellipse(0, 0, this.radius * 2);
331
-                
332
-                // Spider details
333
-                fill(40);
334
-                noStroke();
335
-                ellipse(0, -2, this.radius * 1.2, this.radius * 1.5);
336
-                
337
-                // Eyes (glow in night)
338
-                if (gamePhase === 'NIGHT') {
339
-                    fill(255, 100, 100);
340
-                } else {
341
-                    fill(255, 0, 0);
342
-                }
343
-                ellipse(-3, -3, 3);
344
-                ellipse(3, -3, 3);
345
-                
346
-                // Legs
347
-                stroke(0);
348
-                strokeWeight(1.5);
349
-                for (let i = 0; i < 4; i++) {
350
-                    let angle = PI/6 + (i * PI/8);
351
-                    line(0, 0, cos(angle) * 12, sin(angle) * 8);
352
-                    line(0, 0, -cos(angle) * 12, sin(angle) * 8);
353
-                }
354
-                
355
-                // Low silk warning indicator
356
-                if (webSilk < 20) {
357
-                    fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50);
358
-                    noStroke();
359
-                    ellipse(0, -15, 8);
360
-                }
361
-                
362
-                pop();
363
-            }
364
-        }
365
-
366
-        class FoodBox {
367
-            constructor(x, y) {
368
-                this.pos = createVector(x, y);
369
-                this.radius = 10;
370
-                this.collected = false;
371
-                this.floatOffset = random(TWO_PI);
372
-                this.silkValue = random(20, 35);
373
-                this.glowPhase = random(TWO_PI);
374
-            }
375
-            
376
-            collect() {
377
-                webSilk = min(webSilk + this.silkValue, maxWebSilk);
378
-                
379
-                // Create particle effect
380
-                for (let i = 0; i < 8; i++) {
381
-                    particles.push(new Particle(this.pos.x, this.pos.y));
382
-                }
383
-            }
384
-            
385
-            display() {
386
-                push();
387
-                let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3;
388
-                translate(this.pos.x, this.pos.y + floatY);
389
-                
390
-                // Glow effect
391
-                let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50;
392
-                noStroke();
393
-                fill(255, 200, 100, glowIntensity * 0.3);
394
-                ellipse(0, 0, 40);
395
-                fill(255, 220, 150, glowIntensity * 0.5);
396
-                ellipse(0, 0, 25);
397
-                
398
-                // Bento box shape
399
-                rectMode(CENTER);
400
-                
401
-                // Shadow
402
-                fill(0, 0, 0, 50);
403
-                rect(2, 2, this.radius * 2, this.radius * 1.8, 3);
404
-                
405
-                // Main box
406
-                fill(139, 69, 19);
407
-                stroke(100, 50, 0);
408
-                strokeWeight(1);
409
-                rect(0, 0, this.radius * 2, this.radius * 1.8, 3);
410
-                
411
-                // Box dividers
412
-                stroke(100, 50, 0);
413
-                strokeWeight(1);
414
-                line(-this.radius, 0, this.radius, 0);
415
-                line(0, -this.radius * 0.9, 0, this.radius * 0.9);
416
-                
417
-                // Food dots
418
-                noStroke();
419
-                fill(255, 200, 100);
420
-                ellipse(-5, -4, 4);
421
-                ellipse(5, -4, 3);
422
-                ellipse(-4, 5, 3);
423
-                ellipse(4, 4, 4);
424
-                
425
-                pop();
426
-            }
427
-        }
428
-
429
-        class Particle {
430
-            constructor(x, y) {
431
-                this.pos = createVector(x, y);
432
-                this.vel = createVector(random(-3, 3), random(-5, -2));
433
-                this.lifetime = 255;
434
-                this.color = color(255, random(200, 255), random(100, 200));
435
-            }
436
-            
437
-            update() {
438
-                this.vel.y += 0.2;
439
-                this.pos.add(this.vel);
440
-                this.lifetime -= 8;
441
-            }
442
-            
443
-            display() {
444
-                push();
445
-                noStroke();
446
-                fill(red(this.color), green(this.color), blue(this.color), this.lifetime);
447
-                ellipse(this.pos.x, this.pos.y, 6);
448
-                pop();
449
-            }
450
-            
451
-            isDead() {
452
-                return this.lifetime <= 0;
453
-            }
454
-        }
455
-
456
-        class Obstacle {
457
-            constructor(x, y, radius, type) {
458
-                this.x = x;
459
-                this.y = y;
460
-                this.radius = radius;
461
-                this.type = type || (random() < 0.5 ? 'branch' : 'leaf');
462
-                this.rotation = random(TWO_PI);
463
-                this.leafPoints = [];
464
-                
465
-                if (this.type === 'leaf') {
466
-                    // Generate random leaf shape
467
-                    let numPoints = 8;
468
-                    for (let i = 0; i < numPoints; i++) {
469
-                        let angle = (TWO_PI / numPoints) * i;
470
-                        let r = radius * random(0.7, 1.2);
471
-                        if (i === 0 || i === numPoints/2) r = radius * 1.3; // Make leaf pointed
472
-                        this.leafPoints.push({angle: angle, radius: r});
473
-                    }
474
-                }
475
-            }
476
-
477
-            display() {
478
-                push();
479
-                translate(this.x, this.y);
480
-                rotate(this.rotation);
481
-                
482
-                if (this.type === 'branch') {
483
-                    // Draw branch/twig
484
-                    if (gamePhase === 'NIGHT') {
485
-                        stroke(40, 20, 0);
486
-                        fill(50, 25, 5);
487
-                    } else {
488
-                        stroke(101, 67, 33);
489
-                        fill(139, 90, 43);
490
-                    }
491
-                    strokeWeight(3);
492
-                    
493
-                    // Main branch
494
-                    push();
495
-                    strokeWeight(this.radius / 3);
496
-                    line(-this.radius, 0, this.radius, 0);
497
-                    
498
-                    // Small twigs
499
-                    strokeWeight(2);
500
-                    line(-this.radius/2, 0, -this.radius/2 - 10, -10);
501
-                    line(this.radius/3, 0, this.radius/3 + 8, -8);
502
-                    line(0, 0, 5, -15);
503
-                    
504
-                    // Texture lines
505
-                    stroke(80, 50, 20, 100);
506
-                    strokeWeight(1);
507
-                    for (let i = -this.radius; i < this.radius; i += 5) {
508
-                        line(i, -2, i + 2, 2);
509
-                    }
510
-                    pop();
511
-                    
512
-                    // Anchor point indicator (subtle)
513
-                    noStroke();
514
-                    fill(255, 255, 255, 30);
515
-                    ellipse(0, 0, this.radius * 2);
516
-                    
517
-                } else if (this.type === 'leaf') {
518
-                    // Draw organic leaf shape
519
-                    if (gamePhase === 'NIGHT') {
520
-                        fill(20, 40, 20);
521
-                        stroke(10, 20, 10);
522
-                    } else {
523
-                        fill(34, 139, 34);
524
-                        stroke(25, 100, 25);
525
-                    }
526
-                    strokeWeight(2);
527
-                    
528
-                    // Leaf shape
529
-                    beginShape();
530
-                    for (let point of this.leafPoints) {
531
-                        let x = cos(point.angle) * point.radius;
532
-                        let y = sin(point.angle) * point.radius;
533
-                        curveVertex(x, y);
534
-                    }
535
-                    // Close the shape
536
-                    let firstPoint = this.leafPoints[0];
537
-                    curveVertex(cos(firstPoint.angle) * firstPoint.radius, 
538
-                               sin(firstPoint.angle) * firstPoint.radius);
539
-                    let secondPoint = this.leafPoints[1];
540
-                    curveVertex(cos(secondPoint.angle) * secondPoint.radius, 
541
-                               sin(secondPoint.angle) * secondPoint.radius);
542
-                    endShape();
543
-                    
544
-                    // Leaf veins
545
-                    stroke(25, 100, 25, 100);
546
-                    strokeWeight(1);
547
-                    line(0, -this.radius, 0, this.radius);
548
-                    line(0, 0, -this.radius/2, -this.radius/2);
549
-                    line(0, 0, this.radius/2, -this.radius/2);
550
-                    line(0, 0, -this.radius/2, this.radius/2);
551
-                    line(0, 0, this.radius/2, this.radius/2);
552
-                }
553
-                
554
-                pop();
555
-            }
556
-        }
557
-
558
-        class WebStrand {
559
-            constructor(start, end) {
560
-                this.start = start;
561
-                this.end = end;
562
-                this.strength = 1;
563
-                this.vibration = 0;
564
-                this.path = []; // Store the path the spider traveled
565
-            }
566
-
567
-            update() {
568
-                this.vibration *= 0.95;
569
-                
570
-                for (let node of webNodes) {
571
-                    if (dist(node.x, node.y, this.start.x, this.start.y) < 5 ||
572
-                        dist(node.x, node.y, this.end.x, this.end.y) < 5) {
573
-                        node.applyForce(0, 0.1);
574
-                    }
575
-                }
576
-            }
577
-
578
-            display() {
579
-                push();
580
-                
581
-                if (gamePhase === 'NIGHT') {
582
-                    stroke(255, 255, 255, 250);
583
-                    strokeWeight(2);
584
-                } else {
585
-                    stroke(255, 255, 255, 200);
586
-                    strokeWeight(1.5);
587
-                }
588
-                
589
-                noFill();
590
-                
591
-                // If we have a path, draw the curved strand following the spider's arc
592
-                if (this.path && this.path.length > 2) {
593
-                    beginShape();
594
-                    // Start with first point twice for curve vertex
595
-                    curveVertex(this.path[0].x, this.path[0].y + this.vibration * sin(frameCount * 0.3));
596
-                    
597
-                    // Draw all path points with vibration applied
598
-                    for (let i = 0; i < this.path.length; i++) {
599
-                        let point = this.path[i];
600
-                        let vibOffset = this.vibration * sin(frameCount * 0.3 + i * 0.1) * (i / this.path.length);
601
-                        curveVertex(point.x, point.y + vibOffset);
602
-                    }
603
-                    
604
-                    // End with last point twice
605
-                    let lastPoint = this.path[this.path.length - 1];
606
-                    curveVertex(lastPoint.x, lastPoint.y + this.vibration * sin(frameCount * 0.3));
607
-                    endShape();
608
-                    
609
-                    // Glow effect
610
-                    stroke(255, 255, 255, 50);
611
-                    strokeWeight(4);
612
-                    beginShape();
613
-                    curveVertex(this.path[0].x, this.path[0].y);
614
-                    for (let point of this.path) {
615
-                        curveVertex(point.x, point.y);
616
-                    }
617
-                    curveVertex(lastPoint.x, lastPoint.y);
618
-                    endShape();
619
-                } else {
620
-                    // Fallback to simple line if no path
621
-                    let midX = (this.start.x + this.end.x) / 2;
622
-                    let midY = (this.start.y + this.end.y) / 2 + this.vibration * sin(frameCount * 0.3);
623
-                    
624
-                    beginShape();
625
-                    curveVertex(this.start.x, this.start.y);
626
-                    curveVertex(this.start.x, this.start.y);
627
-                    curveVertex(midX, midY);
628
-                    curveVertex(this.end.x, this.end.y);
629
-                    curveVertex(this.end.x, this.end.y);
630
-                    endShape();
631
-                    
632
-                    stroke(255, 255, 255, 50);
633
-                    strokeWeight(4);
634
-                    beginShape();
635
-                    curveVertex(this.start.x, this.start.y);
636
-                    curveVertex(this.start.x, this.start.y);
637
-                    curveVertex(midX, midY);
638
-                    curveVertex(this.end.x, this.end.y);
639
-                    curveVertex(this.end.x, this.end.y);
640
-                    endShape();
641
-                }
642
-                
643
-                pop();
644
-            }
645
-
646
-            vibrate(amount) {
647
-                this.vibration = min(this.vibration + amount, 10);
648
-            }
649
-        }
650
-
651
-        class WebNode {
652
-            constructor(x, y) {
653
-                this.x = x;
654
-                this.y = y;
655
-                this.vx = 0;
656
-                this.vy = 0;
657
-                this.pinned = false;
658
-            }
659
-
660
-            applyForce(fx, fy) {
661
-                if (!this.pinned) {
662
-                    this.vx += fx;
663
-                    this.vy += fy;
664
-                }
665
-            }
666
-
667
-            update() {
668
-                if (!this.pinned) {
669
-                    this.x += this.vx;
670
-                    this.y += this.vy;
671
-                    this.vx *= 0.98;
672
-                    this.vy *= 0.98;
673
-                }
674
-            }
675
-        }
676
-
677
-        class Fly {
678
-            constructor() {
679
-                if (random() < 0.5) {
680
-                    this.pos = createVector(random() < 0.5 ? -20 : width + 20, random(50, height - 100));
681
-                } else {
682
-                    this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20);
683
-                }
684
-                
685
-                this.vel = createVector(random(-2, 2), random(-1, 1));
686
-                this.acc = createVector(0, 0);
687
-                this.radius = 4;
688
-                this.caught = false;
689
-                this.stuck = false;
690
-                this.wingPhase = random(TWO_PI);
691
-                this.wanderAngle = random(TWO_PI);
692
-                this.glowIntensity = random(150, 255);
693
-                this.webTouchCount = 0; // Track how many strands touched
694
-                this.requiredStrands = 3; // Need to touch 3+ strands to get caught
695
-                this.touchedStrands = new Set(); // Track unique strands touched
696
-            }
697
-
698
-            update() {
699
-                if (this.stuck) return;
700
-                
701
-                if (this.caught) {
702
-                    this.vel.mult(0.95);
703
-                    if (this.vel.mag() < 0.1) {
704
-                        this.stuck = true;
705
-                        fliesCaught++;
706
-                        webSilk = min(webSilk + 5, maxWebSilk);
707
-                    }
708
-                    return;
709
-                }
710
-                
711
-                // Wander behavior
712
-                this.wanderAngle += random(-0.3, 0.3);
713
-                let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle));
714
-                wanderForce.mult(0.1);
715
-                this.acc.add(wanderForce);
716
-                
717
-                this.vel.add(this.acc);
718
-                this.vel.limit(3);
719
-                this.pos.add(this.vel);
720
-                this.acc.mult(0);
721
-                
722
-                // Wrap around screen
723
-                if (this.pos.x < -30) this.pos.x = width + 30;
724
-                if (this.pos.x > width + 30) this.pos.x = -30;
725
-                if (this.pos.y < -30) this.pos.y = height + 30;
726
-                if (this.pos.y > height + 30) this.pos.y = -30;
727
-                
728
-                // Check web collision - need multiple strands to catch
729
-                this.touchedStrands.clear();
730
-                for (let strand of webStrands) {
731
-                    let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
732
-                    if (d < this.radius + 3) {
733
-                        this.touchedStrands.add(strand);
734
-                    }
735
-                }
736
-                
737
-                // Only get caught if touching enough strands (a proper web)
738
-                if (this.touchedStrands.size >= this.requiredStrands) {
739
-                    this.caught = true;
740
-                    // Vibrate all touched strands
741
-                    for (let strand of this.touchedStrands) {
742
-                        strand.vibrate(5);
743
-                    }
744
-                    // Propagate vibrations
745
-                    for (let strand of webStrands) {
746
-                        if (!this.touchedStrands.has(strand)) {
747
-                            for (let touched of this.touchedStrands) {
748
-                                let d1 = dist(strand.start.x, strand.start.y, touched.start.x, touched.start.y);
749
-                                let d2 = dist(strand.start.x, strand.start.y, touched.end.x, touched.end.y);
750
-                                let d3 = dist(strand.end.x, strand.end.y, touched.start.x, touched.start.y);
751
-                                let d4 = dist(strand.end.x, strand.end.y, touched.end.x, touched.end.y);
752
-                                if (min(d1, d2, d3, d4) < 50) {
753
-                                    strand.vibrate(2);
754
-                                    break;
755
-                                }
756
-                            }
757
-                        }
758
-                    }
759
-                }
760
-            }
761
-
762
-            pointToLineDistance(point, lineStart, lineEnd) {
763
-                let line = p5.Vector.sub(lineEnd, lineStart);
764
-                let lineLength = line.mag();
765
-                line.normalize();
766
-                
767
-                let pointToStart = p5.Vector.sub(point, lineStart);
768
-                let projLength = constrain(pointToStart.dot(line), 0, lineLength);
769
-                
770
-                let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
771
-                return p5.Vector.dist(point, closestPoint);
772
-            }
773
-
774
-            display() {
775
-                push();
776
-                translate(this.pos.x, this.pos.y);
777
-                
778
-                // Show if fly is near a web but not caught yet
779
-                if (this.touchedStrands.size > 0 && !this.caught) {
780
-                    // Warning indicator
781
-                    stroke(255, 255, 0, 100);
782
-                    strokeWeight(1);
783
-                    noFill();
784
-                    ellipse(0, 0, 20);
785
-                }
786
-                
787
-                // Glow effect for firefly
788
-                if (gamePhase === 'NIGHT') {
789
-                    noStroke();
790
-                    fill(255, 255, 150, this.glowIntensity * 0.3);
791
-                    ellipse(0, 0, 30);
792
-                    fill(255, 255, 100, this.glowIntensity * 0.5);
793
-                    ellipse(0, 0, 20);
794
-                }
795
-                
796
-                // Body
797
-                fill(30);
798
-                stroke(0);
799
-                strokeWeight(0.5);
800
-                ellipse(0, 0, this.radius * 2);
801
-                
802
-                // Wings (animated)
803
-                if (!this.stuck) {
804
-                    this.wingPhase += 0.5;
805
-                    let wingSpread = sin(this.wingPhase) * 5;
806
-                    
807
-                    fill(255, 255, 255, 150);
808
-                    noStroke();
809
-                    ellipse(-wingSpread, 0, 6, 4);
810
-                    ellipse(wingSpread, 0, 6, 4);
811
-                }
812
-                
813
-                // Glow abdomen at night
814
-                if (gamePhase === 'NIGHT') {
815
-                    fill(255, 255, 100, this.glowIntensity);
816
-                    noStroke();
817
-                    ellipse(0, 2, 3);
818
-                }
819
-                
820
-                pop();
821
-            }
822
-        }
823
-
824
-        function spawnFoodBox() {
825
-            let x, y;
826
-            let attempts = 0;
827
-            let valid = false;
828
-            
829
-            while (!valid && attempts < 50) {
830
-                x = random(50, width - 50);
831
-                y = random(50, height - 100);
832
-                valid = true;
833
-                
834
-                for (let obstacle of obstacles) {
835
-                    if (dist(x, y, obstacle.x, obstacle.y) < obstacle.radius + 30) {
836
-                        valid = false;
837
-                        break;
838
-                    }
839
-                }
840
-                
841
-                for (let box of foodBoxes) {
842
-                    if (dist(x, y, box.pos.x, box.pos.y) < 50) {
843
-                        valid = false;
844
-                        break;
845
-                    }
846
-                }
847
-                
848
-                attempts++;
849
-            }
850
-            
851
-            if (valid) {
852
-                foodBoxes.push(new FoodBox(x, y));
853
-            }
854
-        }
855
-
856
-        function setup() {
857
-            // Use full viewport dimensions
858
-            let canvas = createCanvas(window.innerWidth, window.innerHeight);
859
-            canvas.parent('game-container');
860
-            
861
-            skyColor1 = color(135, 206, 235);
862
-            skyColor2 = color(255, 183, 77);
863
-            currentSkyColor1 = skyColor1;
864
-            currentSkyColor2 = skyColor2;
865
-            
866
-            spider = new Spider(width / 2, height - 50);
867
-            
868
-            // Create organic obstacles with variety - scale with viewport
869
-            let numObstacles = Math.floor((width * height) / 60000); // Scale based on area
870
-            numObstacles = constrain(numObstacles, 8, 20);
871
-            
872
-            for (let i = 0; i < numObstacles; i++) {
873
-                let x = random(100, width - 100);
874
-                let y = random(100, height - 150);
875
-                let radius = random(25, 45);
876
-                let type = random() < 0.6 ? 'branch' : 'leaf';
877
-                
878
-                let valid = true;
879
-                for (let obstacle of obstacles) {
880
-                    if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 30) {
881
-                        valid = false;
882
-                        break;
883
-                    }
884
-                }
885
-                
886
-                if (valid) {
887
-                    obstacles.push(new Obstacle(x, y, radius, type));
888
-                }
889
-            }
890
-            
891
-            // Add guaranteed anchor points with specific types - distributed across viewport
892
-            obstacles.push(new Obstacle(50, height/2, 35, 'branch'));
893
-            obstacles.push(new Obstacle(width - 50, height/2, 35, 'branch'));
894
-            obstacles.push(new Obstacle(width/2, 50, 40, 'leaf'));
895
-            obstacles.push(new Obstacle(width/4, height - 200, 30, 'leaf'));
896
-            obstacles.push(new Obstacle(3*width/4, height - 200, 30, 'branch'));
897
-            
898
-            // Add more anchor points for larger screens
899
-            if (width > 1200) {
900
-                obstacles.push(new Obstacle(width/3, height/3, 35, 'leaf'));
901
-                obstacles.push(new Obstacle(2*width/3, height/3, 35, 'branch'));
902
-            }
903
-            
904
-            // Spawn initial food boxes
905
-            let numBoxes = Math.max(3, Math.floor(width / 400));
906
-            for (let i = 0; i < numBoxes; i++) {
907
-                spawnFoodBox();
908
-            }
909
-        }
910
-        
911
-        function windowResized() {
912
-            resizeCanvas(window.innerWidth, window.innerHeight);
913
-        }
914
-
915
-        function draw() {
916
-            phaseTimer++;
917
-            
918
-            // Phase transitions
919
-            if (gamePhase === 'DUSK' && phaseTimer >= DUSK_DURATION) {
920
-                gamePhase = 'TRANSITION';
921
-                phaseTimer = 0;
922
-            } else if (gamePhase === 'TRANSITION' && phaseTimer >= TRANSITION_DURATION) {
923
-                gamePhase = 'NIGHT';
924
-                phaseTimer = 0;
925
-                for (let i = 0; i < 5; i++) {
926
-                    flies.push(new Fly());
927
-                }
928
-                for (let i = 0; i < 3; i++) {
929
-                    spawnFoodBox();
930
-                }
931
-            }
932
-            
933
-            // Update sky colors
934
-            if (gamePhase === 'DUSK') {
935
-                currentSkyColor1 = lerpColor(color(135, 206, 235), color(255, 140, 90), phaseTimer / DUSK_DURATION);
936
-                currentSkyColor2 = lerpColor(color(255, 183, 77), color(120, 60, 120), phaseTimer / DUSK_DURATION);
937
-            } else if (gamePhase === 'TRANSITION') {
938
-                let t = phaseTimer / TRANSITION_DURATION;
939
-                currentSkyColor1 = lerpColor(color(255, 140, 90), color(25, 25, 112), t);
940
-                currentSkyColor2 = lerpColor(color(120, 60, 120), color(0, 0, 40), t);
941
-                moonOpacity = t * 255;
942
-                moonY = lerp(100, 60, t);
943
-            } else if (gamePhase === 'NIGHT') {
944
-                currentSkyColor1 = color(25, 25, 112);
945
-                currentSkyColor2 = color(0, 0, 40);
946
-                moonOpacity = 255;
947
-                
948
-                if (phaseTimer % 120 === 0 && flies.length < 15) {
949
-                    flies.push(new Fly());
950
-                }
951
-                
952
-                if (phaseTimer % 300 === 0 && foodBoxes.length < 6) {
953
-                    spawnFoodBox();
954
-                }
955
-            }
956
-            
957
-            // Draw sky gradient
958
-            for(let i = 0; i <= height; i++) {
959
-                let inter = map(i, 0, height, 0, 1);
960
-                let c = lerpColor(currentSkyColor1, currentSkyColor2, inter);
961
-                stroke(c);
962
-                line(0, i, width, i);
963
-            }
964
-            
965
-            // Draw moon during night
966
-            if (moonOpacity > 0) {
967
-                push();
968
-                noStroke();
969
-                fill(255, 255, 230, moonOpacity);
970
-                ellipse(width - 100, moonY, 50);
971
-                fill(255, 255, 200, moonOpacity * 0.3);
972
-                ellipse(width - 100, moonY, 70);
973
-                
974
-                fill(230, 230, 200, moonOpacity * 0.5);
975
-                ellipse(width - 105, moonY - 5, 8);
976
-                ellipse(width - 95, moonY + 8, 12);
977
-                ellipse(width - 110, moonY + 10, 6);
978
-                pop();
979
-                
980
-                if (gamePhase === 'NIGHT') {
981
-                    randomSeed(42);
982
-                    for (let i = 0; i < 50; i++) {
983
-                        let x = random(width);
984
-                        let y = random(height * 0.6);
985
-                        let brightness = random(100, 255);
986
-                        stroke(255, 255, 255, brightness);
987
-                        strokeWeight(random(1, 2));
988
-                        point(x, y);
989
-                    }
990
-                    randomSeed(millis());
991
-                }
992
-            }
993
-            
994
-            // Display everything
995
-            for (let obstacle of obstacles) {
996
-                obstacle.display();
997
-            }
998
-            
999
-            for (let box of foodBoxes) {
1000
-                box.display();
1001
-            }
1002
-            
1003
-            for (let i = particles.length - 1; i >= 0; i--) {
1004
-                particles[i].update();
1005
-                particles[i].display();
1006
-                if (particles[i].isDead()) {
1007
-                    particles.splice(i, 1);
1008
-                }
1009
-            }
1010
-            
1011
-            for (let strand of webStrands) {
1012
-                strand.update();
1013
-                strand.display();
1014
-            }
1015
-            
1016
-            for (let node of webNodes) {
1017
-                node.update();
1018
-            }
1019
-            
1020
-            if (currentStrand && isDeployingWeb && spider.isAirborne) {
1021
-                let opacity = map(webSilk, 0, 20, 50, 150);
1022
-                stroke(255, 255, 255, opacity);
1023
-                strokeWeight(1.5);
1024
-                line(currentStrand.start.x, currentStrand.start.y, spider.pos.x, spider.pos.y);
1025
-            }
1026
-            
1027
-            for (let i = flies.length - 1; i >= 0; i--) {
1028
-                flies[i].update();
1029
-                flies[i].display();
1030
-            }
1031
-            
1032
-            spider.update();
1033
-            spider.display();
1034
-            
1035
-            // Update resources
1036
-            webSilk = min(webSilk + silkRechargeRate, maxWebSilk);
1037
-            
1038
-            if (isDeployingWeb && spider.isAirborne && webSilk > 0) {
1039
-                webSilk = max(0, webSilk - silkDrainRate);
1040
-                if (webSilk <= 0) {
1041
-                    isDeployingWeb = false;
1042
-                    if (currentStrand) {
1043
-                        webStrands.pop();
1044
-                        currentStrand = null;
1045
-                    }
1046
-                }
1047
-            }
1048
-            
1049
-            // Update UI
1050
-            document.getElementById('strand-count').textContent = webStrands.length;
1051
-            document.getElementById('flies-caught').textContent = fliesCaught;
1052
-            document.getElementById('flies-munched').textContent = fliesMunched;
1053
-            document.getElementById('phase').textContent = gamePhase === 'TRANSITION' ? 'NIGHTFALL' : gamePhase;
1054
-            
1055
-            if (gamePhase === 'DUSK') {
1056
-                let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60);
1057
-                document.getElementById('timer').textContent = `${timeLeft}s to prepare!`;
1058
-            } else if (gamePhase === 'TRANSITION') {
1059
-                document.getElementById('timer').textContent = 'Night approaches...';
1060
-            } else {
1061
-                document.getElementById('timer').textContent = `${flies.length} flies active`;
1062
-            }
1063
-            
1064
-            let meterPercent = (webSilk / maxWebSilk) * 100;
1065
-            document.getElementById('web-meter-fill').style.width = meterPercent + '%';
1066
-            
1067
-            if (webSilk < 20) {
1068
-                let flash = sin(frameCount * 0.2) * 0.5 + 0.5;
1069
-                document.getElementById('web-meter-fill').style.background = 
1070
-                    `linear-gradient(90deg, rgb(255, ${100 + flash * 100}, ${100 + flash * 100}), rgb(255, ${150 + flash * 50}, ${150 + flash * 50}))`;
1071
-            } else {
1072
-                document.getElementById('web-meter-fill').style.background = 
1073
-                    'linear-gradient(90deg, #87CEEB, #E0F6FF)';
1074
-            }
1075
-            
1076
-            if (mouseIsPressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) {
1077
-                isDeployingWeb = true;
1078
-                currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
1079
-                webStrands.push(currentStrand);
1080
-                webNodes.push(new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y));
1081
-            }
1082
-            
1083
-            if (currentStrand && isDeployingWeb && spider.isAirborne) {
1084
-                currentStrand.end = spider.pos.copy();
1085
-            }
1086
-        }
1087
-
1088
-        function keyPressed() {
1089
-            if (key === ' ') {
1090
-                spacePressed = true;
1091
-                return false; // Prevent page scroll
1092
-            }
1093
-            if (keyCode === SHIFT) {
1094
-                spider.munch();
1095
-                return false;
1096
-            }
1097
-        }
1098
-        
1099
-        function keyReleased() {
1100
-            if (key === ' ') {
1101
-                spacePressed = false;
1102
-                isDeployingWeb = false;
1103
-                return false;
1104
-            }
1105
-        }
1106
-
1107
-        function mousePressed() {
1108
-            // Only jump on mouse press, no web deployment
1109
-            if (!spider.isAirborne) {
1110
-                spider.jump(mouseX, mouseY);
1111
-            }
1112
-        }
1113
-
1114
-        function mouseReleased() {
1115
-            // No longer needed for web deployment
1116
-        }
1117
-
1118
-        function touchStarted() {
1119
-            // Only jump on touch, no web deployment
1120
-            if (!spider.isAirborne) {
1121
-                spider.jump(touches[0].x, touches[0].y);
1122
-            }
1123
-            return false;
1124
-        }
1125
-
1126
-        function touchEnded() {
1127
-            // No longer needed for web deployment
1128
-            return false;
1129
-        }
1130
-    </script>
28
+    <script src="physics.js"></script>
29
+    <script src="entities.js"></script>
30
+    <script src="game.js"></script>
113131
 </body>
113232
 </html>
js/entities.jsadded
@@ -0,0 +1,525 @@
1
+// entities.js - All game entity classes
2
+
3
+class Spider {
4
+    constructor(x, y) {
5
+        this.pos = createVector(x, y);
6
+        this.vel = createVector(0, 0);
7
+        this.acc = createVector(0, 0);
8
+        this.radius = 8;
9
+        this.isAirborne = false;
10
+        this.canJump = true;
11
+        this.lastAnchorPoint = null;
12
+        this.gravity = createVector(0, 0.3);
13
+        this.jumpPower = 12;
14
+        this.maxSpeed = 15;
15
+        this.munchRadius = 20;
16
+        this.munchCooldown = 0;
17
+    }
18
+
19
+    jump(targetX, targetY) {
20
+        if (!this.canJump) return;
21
+        
22
+        let direction = createVector(targetX - this.pos.x, targetY - this.pos.y);
23
+        direction.normalize();
24
+        direction.mult(this.jumpPower);
25
+        
26
+        this.vel = direction;
27
+        this.isAirborne = true;
28
+        this.canJump = false;
29
+        this.lastAnchorPoint = this.pos.copy();
30
+    }
31
+    
32
+    munch() {
33
+        if (this.munchCooldown > 0) return;
34
+        
35
+        isMunching = true;
36
+        this.munchCooldown = 30;
37
+        
38
+        for (let i = flies.length - 1; i >= 0; i--) {
39
+            let fly = flies[i];
40
+            let d = dist(this.pos.x, this.pos.y, fly.pos.x, fly.pos.y);
41
+            if (d < this.munchRadius) {
42
+                fliesMunched++;
43
+                webSilk = min(webSilk + 15, maxWebSilk);
44
+                
45
+                for (let j = 0; j < 12; j++) {
46
+                    let p = new Particle(fly.pos.x, fly.pos.y);
47
+                    p.color = color(255, random(100, 255), 0);
48
+                    particles.push(p);
49
+                }
50
+                
51
+                flies.splice(i, 1);
52
+                break;
53
+            }
54
+        }
55
+    }
56
+
57
+    update() {
58
+        if (this.isAirborne) {
59
+            this.acc.add(this.gravity);
60
+        }
61
+        
62
+        this.vel.add(this.acc);
63
+        this.vel.limit(this.maxSpeed);
64
+        this.pos.add(this.vel);
65
+        this.acc.mult(0);
66
+        
67
+        if (this.munchCooldown > 0) {
68
+            this.munchCooldown--;
69
+            if (this.munchCooldown === 0) {
70
+                isMunching = false;
71
+            }
72
+        }
73
+
74
+        if (this.pos.y >= height - this.radius) {
75
+            this.pos.y = height - this.radius;
76
+            this.land();
77
+        }
78
+
79
+        if (this.pos.x <= this.radius || this.pos.x >= width - this.radius) {
80
+            this.pos.x = constrain(this.pos.x, this.radius, width - this.radius);
81
+            this.vel.x *= -0.5;
82
+        }
83
+
84
+        if (this.pos.y <= this.radius) {
85
+            this.pos.y = this.radius;
86
+            this.vel.y *= -0.5;
87
+        }
88
+
89
+        for (let obstacle of obstacles) {
90
+            if (this.checkObstacleCollision(obstacle)) {
91
+                this.landOnObstacle(obstacle);
92
+            }
93
+        }
94
+
95
+        for (let strand of webStrands) {
96
+            if (strand === currentStrand) continue;
97
+            
98
+            if (this.isAirborne && this.checkStrandCollision(strand)) {
99
+                this.landOnStrand(strand);
100
+            }
101
+        }
102
+        
103
+        for (let i = foodBoxes.length - 1; i >= 0; i--) {
104
+            let box = foodBoxes[i];
105
+            if (dist(this.pos.x, this.pos.y, box.pos.x, box.pos.y) < this.radius + box.radius) {
106
+                box.collect();
107
+                foodBoxes.splice(i, 1);
108
+            }
109
+        }
110
+    }
111
+
112
+    checkObstacleCollision(obstacle) {
113
+        let d = dist(this.pos.x, this.pos.y, obstacle.x, obstacle.y);
114
+        return d < this.radius + obstacle.radius;
115
+    }
116
+
117
+    checkStrandCollision(strand) {
118
+        let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
119
+        return d < this.radius + 2;
120
+    }
121
+
122
+    pointToLineDistance(point, lineStart, lineEnd) {
123
+        let line = p5.Vector.sub(lineEnd, lineStart);
124
+        let lineLength = line.mag();
125
+        line.normalize();
126
+        
127
+        let pointToStart = p5.Vector.sub(point, lineStart);
128
+        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
129
+        
130
+        let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
131
+        return p5.Vector.dist(point, closestPoint);
132
+    }
133
+
134
+    landOnObstacle(obstacle) {
135
+        let angle = atan2(this.pos.y - obstacle.y, this.pos.x - obstacle.x);
136
+        this.pos.x = obstacle.x + cos(angle) * (obstacle.radius + this.radius);
137
+        this.pos.y = obstacle.y + sin(angle) * (obstacle.radius + this.radius);
138
+        this.land();
139
+    }
140
+
141
+    landOnStrand(strand) {
142
+        let line = p5.Vector.sub(strand.end, strand.start);
143
+        let lineLength = line.mag();
144
+        line.normalize();
145
+        
146
+        let pointToStart = p5.Vector.sub(this.pos, strand.start);
147
+        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
148
+        
149
+        let closestPoint = p5.Vector.add(strand.start, p5.Vector.mult(line, projLength));
150
+        this.pos = closestPoint;
151
+        this.land();
152
+    }
153
+
154
+    land() {
155
+        this.vel.mult(0);
156
+        this.isAirborne = false;
157
+        this.canJump = true;
158
+        
159
+        if (currentStrand && isDeployingWeb && spacePressed) {
160
+            currentStrand.end = this.pos.copy();
161
+            currentStrand.path.push(this.pos.copy());
162
+            webNodes.push(new WebNode(this.pos.x, this.pos.y));
163
+        }
164
+        
165
+        currentStrand = null;
166
+        isDeployingWeb = false;
167
+    }
168
+
169
+    display() {
170
+        push();
171
+        translate(this.pos.x, this.pos.y);
172
+        
173
+        if (isMunching && this.munchCooldown > 15) {
174
+            push();
175
+            fill(255, 100, 100, 150);
176
+            noStroke();
177
+            let munchSize = 15 + sin(frameCount * 0.5) * 5;
178
+            arc(0, 0, munchSize, munchSize, 0, PI + HALF_PI, PIE);
179
+            pop();
180
+        }
181
+        
182
+        fill(20);
183
+        stroke(0);
184
+        strokeWeight(1);
185
+        ellipse(0, 0, this.radius * 2);
186
+        
187
+        fill(40);
188
+        noStroke();
189
+        ellipse(0, -2, this.radius * 1.2, this.radius * 1.5);
190
+        
191
+        if (gamePhase === 'NIGHT') {
192
+            fill(255, 100, 100);
193
+        } else {
194
+            fill(255, 0, 0);
195
+        }
196
+        ellipse(-3, -3, 3);
197
+        ellipse(3, -3, 3);
198
+        
199
+        stroke(0);
200
+        strokeWeight(1.5);
201
+        for (let i = 0; i < 4; i++) {
202
+            let angle = PI/6 + (i * PI/8);
203
+            line(0, 0, cos(angle) * 12, sin(angle) * 8);
204
+            line(0, 0, -cos(angle) * 12, sin(angle) * 8);
205
+        }
206
+        
207
+        if (webSilk < 20) {
208
+            fill(255, 100, 100, 150 + sin(frameCount * 0.2) * 50);
209
+            noStroke();
210
+            ellipse(0, -15, 8);
211
+        }
212
+        
213
+        pop();
214
+    }
215
+}
216
+
217
+class Fly {
218
+    constructor() {
219
+        if (random() < 0.5) {
220
+            this.pos = createVector(random() < 0.5 ? -20 : width + 20, random(50, height - 100));
221
+        } else {
222
+            this.pos = createVector(random(width), random() < 0.5 ? -20 : height + 20);
223
+        }
224
+        
225
+        this.vel = createVector(random(-2, 2), random(-1, 1));
226
+        this.acc = createVector(0, 0);
227
+        this.radius = 4;
228
+        this.caught = false;
229
+        this.stuck = false;
230
+        this.wingPhase = random(TWO_PI);
231
+        this.wanderAngle = random(TWO_PI);
232
+        this.glowIntensity = random(150, 255);
233
+        this.webTouchCount = 0;
234
+        this.requiredStrands = 3;
235
+        this.touchedStrands = new Set();
236
+    }
237
+
238
+    update() {
239
+        if (this.stuck) return;
240
+        
241
+        if (this.caught) {
242
+            this.vel.mult(0.95);
243
+            if (this.vel.mag() < 0.1) {
244
+                this.stuck = true;
245
+                fliesCaught++;
246
+                webSilk = min(webSilk + 5, maxWebSilk);
247
+            }
248
+            return;
249
+        }
250
+        
251
+        this.wanderAngle += random(-0.3, 0.3);
252
+        let wanderForce = createVector(cos(this.wanderAngle), sin(this.wanderAngle));
253
+        wanderForce.mult(0.1);
254
+        this.acc.add(wanderForce);
255
+        
256
+        this.vel.add(this.acc);
257
+        this.vel.limit(3);
258
+        this.pos.add(this.vel);
259
+        this.acc.mult(0);
260
+        
261
+        if (this.pos.x < -30) this.pos.x = width + 30;
262
+        if (this.pos.x > width + 30) this.pos.x = -30;
263
+        if (this.pos.y < -30) this.pos.y = height + 30;
264
+        if (this.pos.y > height + 30) this.pos.y = -30;
265
+        
266
+        this.touchedStrands.clear();
267
+        for (let strand of webStrands) {
268
+            let d = this.pointToLineDistance(this.pos, strand.start, strand.end);
269
+            if (d < this.radius + 3) {
270
+                this.touchedStrands.add(strand);
271
+            }
272
+        }
273
+        
274
+        if (this.touchedStrands.size >= this.requiredStrands) {
275
+            this.caught = true;
276
+            for (let strand of this.touchedStrands) {
277
+                strand.vibrate(5);
278
+            }
279
+            for (let strand of webStrands) {
280
+                if (!this.touchedStrands.has(strand)) {
281
+                    for (let touched of this.touchedStrands) {
282
+                        let d1 = dist(strand.start.x, strand.start.y, touched.start.x, touched.start.y);
283
+                        let d2 = dist(strand.start.x, strand.start.y, touched.end.x, touched.end.y);
284
+                        let d3 = dist(strand.end.x, strand.end.y, touched.start.x, touched.start.y);
285
+                        let d4 = dist(strand.end.x, strand.end.y, touched.end.x, touched.end.y);
286
+                        if (min(d1, d2, d3, d4) < 50) {
287
+                            strand.vibrate(2);
288
+                            break;
289
+                        }
290
+                    }
291
+                }
292
+            }
293
+        }
294
+    }
295
+
296
+    pointToLineDistance(point, lineStart, lineEnd) {
297
+        let line = p5.Vector.sub(lineEnd, lineStart);
298
+        let lineLength = line.mag();
299
+        line.normalize();
300
+        
301
+        let pointToStart = p5.Vector.sub(point, lineStart);
302
+        let projLength = constrain(pointToStart.dot(line), 0, lineLength);
303
+        
304
+        let closestPoint = p5.Vector.add(lineStart, p5.Vector.mult(line, projLength));
305
+        return p5.Vector.dist(point, closestPoint);
306
+    }
307
+
308
+    display() {
309
+        push();
310
+        translate(this.pos.x, this.pos.y);
311
+        
312
+        if (this.touchedStrands.size > 0 && !this.caught) {
313
+            stroke(255, 255, 0, 100);
314
+            strokeWeight(1);
315
+            noFill();
316
+            ellipse(0, 0, 20);
317
+        }
318
+        
319
+        if (gamePhase === 'NIGHT') {
320
+            noStroke();
321
+            fill(255, 255, 150, this.glowIntensity * 0.3);
322
+            ellipse(0, 0, 30);
323
+            fill(255, 255, 100, this.glowIntensity * 0.5);
324
+            ellipse(0, 0, 20);
325
+        }
326
+        
327
+        fill(30);
328
+        stroke(0);
329
+        strokeWeight(0.5);
330
+        ellipse(0, 0, this.radius * 2);
331
+        
332
+        if (!this.stuck) {
333
+            this.wingPhase += 0.5;
334
+            let wingSpread = sin(this.wingPhase) * 5;
335
+            
336
+            fill(255, 255, 255, 150);
337
+            noStroke();
338
+            ellipse(-wingSpread, 0, 6, 4);
339
+            ellipse(wingSpread, 0, 6, 4);
340
+        }
341
+        
342
+        if (gamePhase === 'NIGHT') {
343
+            fill(255, 255, 100, this.glowIntensity);
344
+            noStroke();
345
+            ellipse(0, 2, 3);
346
+        }
347
+        
348
+        pop();
349
+    }
350
+}
351
+
352
+class Obstacle {
353
+    constructor(x, y, radius, type) {
354
+        this.x = x;
355
+        this.y = y;
356
+        this.radius = radius;
357
+        this.type = type || (random() < 0.5 ? 'branch' : 'leaf');
358
+        this.rotation = random(TWO_PI);
359
+        this.leafPoints = [];
360
+        
361
+        if (this.type === 'leaf') {
362
+            let numPoints = 8;
363
+            for (let i = 0; i < numPoints; i++) {
364
+                let angle = (TWO_PI / numPoints) * i;
365
+                let r = radius * random(0.7, 1.2);
366
+                if (i === 0 || i === numPoints/2) r = radius * 1.3;
367
+                this.leafPoints.push({angle: angle, radius: r});
368
+            }
369
+        }
370
+    }
371
+
372
+    display() {
373
+        push();
374
+        translate(this.x, this.y);
375
+        rotate(this.rotation);
376
+        
377
+        if (this.type === 'branch') {
378
+            if (gamePhase === 'NIGHT') {
379
+                stroke(40, 20, 0);
380
+                fill(50, 25, 5);
381
+            } else {
382
+                stroke(101, 67, 33);
383
+                fill(139, 90, 43);
384
+            }
385
+            strokeWeight(3);
386
+            
387
+            push();
388
+            strokeWeight(this.radius / 3);
389
+            line(-this.radius, 0, this.radius, 0);
390
+            
391
+            strokeWeight(2);
392
+            line(-this.radius/2, 0, -this.radius/2 - 10, -10);
393
+            line(this.radius/3, 0, this.radius/3 + 8, -8);
394
+            line(0, 0, 5, -15);
395
+            
396
+            stroke(80, 50, 20, 100);
397
+            strokeWeight(1);
398
+            for (let i = -this.radius; i < this.radius; i += 5) {
399
+                line(i, -2, i + 2, 2);
400
+            }
401
+            pop();
402
+            
403
+            noStroke();
404
+            fill(255, 255, 255, 30);
405
+            ellipse(0, 0, this.radius * 2);
406
+            
407
+        } else if (this.type === 'leaf') {
408
+            if (gamePhase === 'NIGHT') {
409
+                fill(20, 40, 20);
410
+                stroke(10, 20, 10);
411
+            } else {
412
+                fill(34, 139, 34);
413
+                stroke(25, 100, 25);
414
+            }
415
+            strokeWeight(2);
416
+            
417
+            beginShape();
418
+            for (let point of this.leafPoints) {
419
+                let x = cos(point.angle) * point.radius;
420
+                let y = sin(point.angle) * point.radius;
421
+                curveVertex(x, y);
422
+            }
423
+            let firstPoint = this.leafPoints[0];
424
+            curveVertex(cos(firstPoint.angle) * firstPoint.radius, 
425
+                       sin(firstPoint.angle) * firstPoint.radius);
426
+            let secondPoint = this.leafPoints[1];
427
+            curveVertex(cos(secondPoint.angle) * secondPoint.radius, 
428
+                       sin(secondPoint.angle) * secondPoint.radius);
429
+            endShape();
430
+            
431
+            stroke(25, 100, 25, 100);
432
+            strokeWeight(1);
433
+            line(0, -this.radius, 0, this.radius);
434
+            line(0, 0, -this.radius/2, -this.radius/2);
435
+            line(0, 0, this.radius/2, -this.radius/2);
436
+            line(0, 0, -this.radius/2, this.radius/2);
437
+            line(0, 0, this.radius/2, this.radius/2);
438
+        }
439
+        
440
+        pop();
441
+    }
442
+}
443
+
444
+class FoodBox {
445
+    constructor(x, y) {
446
+        this.pos = createVector(x, y);
447
+        this.radius = 10;
448
+        this.collected = false;
449
+        this.floatOffset = random(TWO_PI);
450
+        this.silkValue = random(20, 35);
451
+        this.glowPhase = random(TWO_PI);
452
+    }
453
+    
454
+    collect() {
455
+        webSilk = min(webSilk + this.silkValue, maxWebSilk);
456
+        
457
+        for (let i = 0; i < 8; i++) {
458
+            particles.push(new Particle(this.pos.x, this.pos.y));
459
+        }
460
+    }
461
+    
462
+    display() {
463
+        push();
464
+        let floatY = sin(frameCount * 0.05 + this.floatOffset) * 3;
465
+        translate(this.pos.x, this.pos.y + floatY);
466
+        
467
+        let glowIntensity = 100 + sin(frameCount * 0.1 + this.glowPhase) * 50;
468
+        noStroke();
469
+        fill(255, 200, 100, glowIntensity * 0.3);
470
+        ellipse(0, 0, 40);
471
+        fill(255, 220, 150, glowIntensity * 0.5);
472
+        ellipse(0, 0, 25);
473
+        
474
+        rectMode(CENTER);
475
+        
476
+        fill(0, 0, 0, 50);
477
+        rect(2, 2, this.radius * 2, this.radius * 1.8, 3);
478
+        
479
+        fill(139, 69, 19);
480
+        stroke(100, 50, 0);
481
+        strokeWeight(1);
482
+        rect(0, 0, this.radius * 2, this.radius * 1.8, 3);
483
+        
484
+        stroke(100, 50, 0);
485
+        strokeWeight(1);
486
+        line(-this.radius, 0, this.radius, 0);
487
+        line(0, -this.radius * 0.9, 0, this.radius * 0.9);
488
+        
489
+        noStroke();
490
+        fill(255, 200, 100);
491
+        ellipse(-5, -4, 4);
492
+        ellipse(5, -4, 3);
493
+        ellipse(-4, 5, 3);
494
+        ellipse(4, 4, 4);
495
+        
496
+        pop();
497
+    }
498
+}
499
+
500
+class Particle {
501
+    constructor(x, y) {
502
+        this.pos = createVector(x, y);
503
+        this.vel = createVector(random(-3, 3), random(-5, -2));
504
+        this.lifetime = 255;
505
+        this.color = color(255, random(200, 255), random(100, 200));
506
+    }
507
+    
508
+    update() {
509
+        this.vel.y += 0.2;
510
+        this.pos.add(this.vel);
511
+        this.lifetime -= 8;
512
+    }
513
+    
514
+    display() {
515
+        push();
516
+        noStroke();
517
+        fill(red(this.color), green(this.color), blue(this.color), this.lifetime);
518
+        ellipse(this.pos.x, this.pos.y, 6);
519
+        pop();
520
+    }
521
+    
522
+    isDead() {
523
+        return this.lifetime <= 0;
524
+    }
525
+}
js/game.jsadded
@@ -0,0 +1,355 @@
1
+// game.js - Main game loop and state management
2
+
3
+// Game objects
4
+let spider;
5
+let obstacles = [];
6
+let webStrands = [];
7
+let flies = [];
8
+let foodBoxes = [];
9
+let particles = [];
10
+let webNodes = [];
11
+
12
+// Game state
13
+let isDeployingWeb = false;
14
+let currentStrand = null;
15
+let spacePressed = false;
16
+let isMunching = false;
17
+
18
+// Resources
19
+let webSilk = 100;
20
+let maxWebSilk = 100;
21
+let silkRechargeRate = 0.05;
22
+let silkDrainRate = 2;
23
+
24
+// Game phases
25
+let gamePhase = 'DUSK';
26
+let phaseTimer = 0;
27
+let DUSK_DURATION = 1500; // 25 seconds
28
+let TRANSITION_DURATION = 180; // 3 seconds
29
+let skyColor1, skyColor2, currentSkyColor1, currentSkyColor2;
30
+let moonY = 100;
31
+let moonOpacity = 0;
32
+let fliesCaught = 0;
33
+let fliesMunched = 0;
34
+
35
+function setup() {
36
+    let canvas = createCanvas(window.innerWidth, window.innerHeight);
37
+    canvas.parent('game-container');
38
+    
39
+    skyColor1 = color(135, 206, 235);
40
+    skyColor2 = color(255, 183, 77);
41
+    currentSkyColor1 = skyColor1;
42
+    currentSkyColor2 = skyColor2;
43
+    
44
+    spider = new Spider(width / 2, height - 50);
45
+    
46
+    // Create obstacles
47
+    let numObstacles = Math.floor((width * height) / 60000);
48
+    numObstacles = constrain(numObstacles, 8, 20);
49
+    
50
+    for (let i = 0; i < numObstacles; i++) {
51
+        let x = random(100, width - 100);
52
+        let y = random(100, height - 150);
53
+        let radius = random(25, 45);
54
+        let type = random() < 0.6 ? 'branch' : 'leaf';
55
+        
56
+        let valid = true;
57
+        for (let obstacle of obstacles) {
58
+            if (dist(x, y, obstacle.x, obstacle.y) < radius + obstacle.radius + 30) {
59
+                valid = false;
60
+                break;
61
+            }
62
+        }
63
+        
64
+        if (valid) {
65
+            obstacles.push(new Obstacle(x, y, radius, type));
66
+        }
67
+    }
68
+    
69
+    // Add guaranteed anchor points
70
+    obstacles.push(new Obstacle(50, height/2, 35, 'branch'));
71
+    obstacles.push(new Obstacle(width - 50, height/2, 35, 'branch'));
72
+    obstacles.push(new Obstacle(width/2, 50, 40, 'leaf'));
73
+    obstacles.push(new Obstacle(width/4, height - 200, 30, 'leaf'));
74
+    obstacles.push(new Obstacle(3*width/4, height - 200, 30, 'branch'));
75
+    
76
+    if (width > 1200) {
77
+        obstacles.push(new Obstacle(width/3, height/3, 35, 'leaf'));
78
+        obstacles.push(new Obstacle(2*width/3, height/3, 35, 'branch'));
79
+    }
80
+    
81
+    // Spawn initial food boxes
82
+    let numBoxes = Math.max(3, Math.floor(width / 400));
83
+    for (let i = 0; i < numBoxes; i++) {
84
+        spawnFoodBox();
85
+    }
86
+}
87
+
88
+function draw() {
89
+    // Update phase timer
90
+    phaseTimer++;
91
+    
92
+    // Phase transitions
93
+    if (gamePhase === 'DUSK' && phaseTimer >= DUSK_DURATION) {
94
+        gamePhase = 'TRANSITION';
95
+        phaseTimer = 0;
96
+    } else if (gamePhase === 'TRANSITION' && phaseTimer >= TRANSITION_DURATION) {
97
+        gamePhase = 'NIGHT';
98
+        phaseTimer = 0;
99
+        for (let i = 0; i < 5; i++) {
100
+            flies.push(new Fly());
101
+        }
102
+        for (let i = 0; i < 3; i++) {
103
+            spawnFoodBox();
104
+        }
105
+    }
106
+    
107
+    // Update sky colors
108
+    updateSkyColors();
109
+    
110
+    // Draw sky gradient
111
+    drawSkyGradient();
112
+    
113
+    // Draw moon and stars
114
+    if (moonOpacity > 0) {
115
+        drawMoon();
116
+    }
117
+    
118
+    // Display game objects
119
+    for (let obstacle of obstacles) {
120
+        obstacle.display();
121
+    }
122
+    
123
+    for (let box of foodBoxes) {
124
+        box.display();
125
+    }
126
+    
127
+    for (let i = particles.length - 1; i >= 0; i--) {
128
+        particles[i].update();
129
+        particles[i].display();
130
+        if (particles[i].isDead()) {
131
+            particles.splice(i, 1);
132
+        }
133
+    }
134
+    
135
+    for (let strand of webStrands) {
136
+        strand.update();
137
+        strand.display();
138
+    }
139
+    
140
+    for (let node of webNodes) {
141
+        node.update();
142
+    }
143
+    
144
+    // Display current strand being created
145
+    if (currentStrand && isDeployingWeb && spider.isAirborne) {
146
+        let opacity = map(webSilk, 0, 20, 50, 150);
147
+        stroke(255, 255, 255, opacity);
148
+        strokeWeight(1.5);
149
+        
150
+        if (currentStrand.path && currentStrand.path.length > 0) {
151
+            noFill();
152
+            beginShape();
153
+            curveVertex(currentStrand.path[0].x, currentStrand.path[0].y);
154
+            for (let point of currentStrand.path) {
155
+                curveVertex(point.x, point.y);
156
+            }
157
+            curveVertex(spider.pos.x, spider.pos.y);
158
+            curveVertex(spider.pos.x, spider.pos.y);
159
+            endShape();
160
+        } else {
161
+            line(currentStrand.start.x, currentStrand.start.y, spider.pos.x, spider.pos.y);
162
+        }
163
+    }
164
+    
165
+    for (let i = flies.length - 1; i >= 0; i--) {
166
+        flies[i].update();
167
+        flies[i].display();
168
+    }
169
+    
170
+    spider.update();
171
+    spider.display();
172
+    
173
+    // Update resources
174
+    updateResources();
175
+    
176
+    // Handle web deployment
177
+    handleWebDeployment();
178
+    
179
+    // Update UI
180
+    updateUI();
181
+    
182
+    // Spawn entities during night
183
+    if (gamePhase === 'NIGHT') {
184
+        if (phaseTimer % 120 === 0 && flies.length < 15) {
185
+            flies.push(new Fly());
186
+        }
187
+        if (phaseTimer % 300 === 0 && foodBoxes.length < 6) {
188
+            spawnFoodBox();
189
+        }
190
+    }
191
+}
192
+
193
+function updateSkyColors() {
194
+    if (gamePhase === 'DUSK') {
195
+        currentSkyColor1 = lerpColor(color(135, 206, 235), color(255, 140, 90), phaseTimer / DUSK_DURATION);
196
+        currentSkyColor2 = lerpColor(color(255, 183, 77), color(120, 60, 120), phaseTimer / DUSK_DURATION);
197
+    } else if (gamePhase === 'TRANSITION') {
198
+        let t = phaseTimer / TRANSITION_DURATION;
199
+        currentSkyColor1 = lerpColor(color(255, 140, 90), color(25, 25, 112), t);
200
+        currentSkyColor2 = lerpColor(color(120, 60, 120), color(0, 0, 40), t);
201
+        moonOpacity = t * 255;
202
+        moonY = lerp(100, 60, t);
203
+    } else if (gamePhase === 'NIGHT') {
204
+        currentSkyColor1 = color(25, 25, 112);
205
+        currentSkyColor2 = color(0, 0, 40);
206
+        moonOpacity = 255;
207
+    }
208
+}
209
+
210
+function drawSkyGradient() {
211
+    for(let i = 0; i <= height; i++) {
212
+        let inter = map(i, 0, height, 0, 1);
213
+        let c = lerpColor(currentSkyColor1, currentSkyColor2, inter);
214
+        stroke(c);
215
+        line(0, i, width, i);
216
+    }
217
+}
218
+
219
+function drawMoon() {
220
+    push();
221
+    noStroke();
222
+    fill(255, 255, 230, moonOpacity);
223
+    ellipse(width - 100, moonY, 50);
224
+    fill(255, 255, 200, moonOpacity * 0.3);
225
+    ellipse(width - 100, moonY, 70);
226
+    
227
+    fill(230, 230, 200, moonOpacity * 0.5);
228
+    ellipse(width - 105, moonY - 5, 8);
229
+    ellipse(width - 95, moonY + 8, 12);
230
+    ellipse(width - 110, moonY + 10, 6);
231
+    pop();
232
+    
233
+    if (gamePhase === 'NIGHT') {
234
+        randomSeed(42);
235
+        for (let i = 0; i < 50; i++) {
236
+            let x = random(width);
237
+            let y = random(height * 0.6);
238
+            let brightness = random(100, 255);
239
+            stroke(255, 255, 255, brightness);
240
+            strokeWeight(random(1, 2));
241
+            point(x, y);
242
+        }
243
+        randomSeed(millis());
244
+    }
245
+}
246
+
247
+function updateResources() {
248
+    webSilk = min(webSilk + silkRechargeRate, maxWebSilk);
249
+    
250
+    if (isDeployingWeb && spider.isAirborne && spacePressed && webSilk > 0) {
251
+        webSilk = max(0, webSilk - silkDrainRate);
252
+        if (webSilk <= 0) {
253
+            isDeployingWeb = false;
254
+            spacePressed = false;
255
+            if (currentStrand) {
256
+                webStrands.pop();
257
+                currentStrand = null;
258
+            }
259
+        }
260
+    }
261
+    
262
+    if (!spacePressed && isDeployingWeb) {
263
+        isDeployingWeb = false;
264
+    }
265
+}
266
+
267
+function handleWebDeployment() {
268
+    if (spacePressed && spider.isAirborne && !isDeployingWeb && webSilk > 10) {
269
+        isDeployingWeb = true;
270
+        currentStrand = new WebStrand(spider.lastAnchorPoint.copy(), null);
271
+        currentStrand.path = [spider.lastAnchorPoint.copy()];
272
+        webStrands.push(currentStrand);
273
+        webNodes.push(new WebNode(spider.lastAnchorPoint.x, spider.lastAnchorPoint.y));
274
+    }
275
+    
276
+    if (currentStrand && isDeployingWeb && spider.isAirborne) {
277
+        currentStrand.end = spider.pos.copy();
278
+        if (frameCount % 2 === 0) {
279
+            currentStrand.path.push(spider.pos.copy());
280
+        }
281
+    }
282
+}
283
+
284
+function updateUI() {
285
+    document.getElementById('strand-count').textContent = webStrands.length;
286
+    document.getElementById('flies-caught').textContent = fliesCaught;
287
+    document.getElementById('flies-munched').textContent = fliesMunched;
288
+    document.getElementById('phase').textContent = gamePhase === 'TRANSITION' ? 'NIGHTFALL' : gamePhase;
289
+    
290
+    if (gamePhase === 'DUSK') {
291
+        let timeLeft = Math.ceil((DUSK_DURATION - phaseTimer) / 60);
292
+        document.getElementById('timer').textContent = `${timeLeft}s to prepare!`;
293
+    } else if (gamePhase === 'TRANSITION') {
294
+        document.getElementById('timer').textContent = 'Night approaches...';
295
+    } else {
296
+        document.getElementById('timer').textContent = `${flies.length} flies active`;
297
+    }
298
+    
299
+    let meterPercent = (webSilk / maxWebSilk) * 100;
300
+    document.getElementById('web-meter-fill').style.width = meterPercent + '%';
301
+    
302
+    if (webSilk < 20) {
303
+        let flash = sin(frameCount * 0.2) * 0.5 + 0.5;
304
+        document.getElementById('web-meter-fill').style.background = 
305
+            `linear-gradient(90deg, rgb(255, ${100 + flash * 100}, ${100 + flash * 100}), rgb(255, ${150 + flash * 50}, ${150 + flash * 50}))`;
306
+    } else {
307
+        document.getElementById('web-meter-fill').style.background = 
308
+            'linear-gradient(90deg, #87CEEB, #E0F6FF)';
309
+    }
310
+}
311
+
312
+// Input handlers
313
+function keyPressed() {
314
+    if (key === ' ') {
315
+        spacePressed = true;
316
+        return false;
317
+    }
318
+    if (keyCode === SHIFT) {
319
+        spider.munch();
320
+        return false;
321
+    }
322
+}
323
+
324
+function keyReleased() {
325
+    if (key === ' ') {
326
+        spacePressed = false;
327
+        isDeployingWeb = false;
328
+        return false;
329
+    }
330
+}
331
+
332
+function mousePressed() {
333
+    if (!spider.isAirborne) {
334
+        spider.jump(mouseX, mouseY);
335
+    }
336
+}
337
+
338
+function mouseReleased() {
339
+    // No longer needed for web deployment
340
+}
341
+
342
+function touchStarted() {
343
+    if (!spider.isAirborne) {
344
+        spider.jump(touches[0].x, touches[0].y);
345
+    }
346
+    return false;
347
+}
348
+
349
+function touchEnded() {
350
+    return false;
351
+}
352
+
353
+function windowResized() {
354
+    resizeCanvas(window.innerWidth, window.innerHeight);
355
+}
js/physics.jsadded
@@ -0,0 +1,147 @@
1
+// physics.js - Web physics and strand management
2
+
3
+class WebStrand {
4
+    constructor(start, end) {
5
+        this.start = start;
6
+        this.end = end;
7
+        this.strength = 1;
8
+        this.vibration = 0;
9
+        this.path = [];
10
+    }
11
+
12
+    update() {
13
+        this.vibration *= 0.95;
14
+        
15
+        for (let node of webNodes) {
16
+            if (dist(node.x, node.y, this.start.x, this.start.y) < 5 ||
17
+                dist(node.x, node.y, this.end.x, this.end.y) < 5) {
18
+                node.applyForce(0, 0.1);
19
+            }
20
+        }
21
+    }
22
+
23
+    display() {
24
+        push();
25
+        
26
+        if (gamePhase === 'NIGHT') {
27
+            stroke(255, 255, 255, 250);
28
+            strokeWeight(2);
29
+        } else {
30
+            stroke(255, 255, 255, 200);
31
+            strokeWeight(1.5);
32
+        }
33
+        
34
+        noFill();
35
+        
36
+        if (this.path && this.path.length > 2) {
37
+            beginShape();
38
+            curveVertex(this.path[0].x, this.path[0].y + this.vibration * sin(frameCount * 0.3));
39
+            
40
+            for (let i = 0; i < this.path.length; i++) {
41
+                let point = this.path[i];
42
+                let vibOffset = this.vibration * sin(frameCount * 0.3 + i * 0.1) * (i / this.path.length);
43
+                curveVertex(point.x, point.y + vibOffset);
44
+            }
45
+            
46
+            let lastPoint = this.path[this.path.length - 1];
47
+            curveVertex(lastPoint.x, lastPoint.y + this.vibration * sin(frameCount * 0.3));
48
+            endShape();
49
+            
50
+            stroke(255, 255, 255, 50);
51
+            strokeWeight(4);
52
+            beginShape();
53
+            curveVertex(this.path[0].x, this.path[0].y);
54
+            for (let point of this.path) {
55
+                curveVertex(point.x, point.y);
56
+            }
57
+            curveVertex(lastPoint.x, lastPoint.y);
58
+            endShape();
59
+        } else {
60
+            let midX = (this.start.x + this.end.x) / 2;
61
+            let midY = (this.start.y + this.end.y) / 2 + this.vibration * sin(frameCount * 0.3);
62
+            
63
+            beginShape();
64
+            curveVertex(this.start.x, this.start.y);
65
+            curveVertex(this.start.x, this.start.y);
66
+            curveVertex(midX, midY);
67
+            curveVertex(this.end.x, this.end.y);
68
+            curveVertex(this.end.x, this.end.y);
69
+            endShape();
70
+            
71
+            stroke(255, 255, 255, 50);
72
+            strokeWeight(4);
73
+            beginShape();
74
+            curveVertex(this.start.x, this.start.y);
75
+            curveVertex(this.start.x, this.start.y);
76
+            curveVertex(midX, midY);
77
+            curveVertex(this.end.x, this.end.y);
78
+            curveVertex(this.end.x, this.end.y);
79
+            endShape();
80
+        }
81
+        
82
+        pop();
83
+    }
84
+
85
+    vibrate(amount) {
86
+        this.vibration = min(this.vibration + amount, 10);
87
+    }
88
+}
89
+
90
+class WebNode {
91
+    constructor(x, y) {
92
+        this.x = x;
93
+        this.y = y;
94
+        this.vx = 0;
95
+        this.vy = 0;
96
+        this.pinned = false;
97
+    }
98
+
99
+    applyForce(fx, fy) {
100
+        if (!this.pinned) {
101
+            this.vx += fx;
102
+            this.vy += fy;
103
+        }
104
+    }
105
+
106
+    update() {
107
+        if (!this.pinned) {
108
+            this.x += this.vx;
109
+            this.y += this.vy;
110
+            this.vx *= 0.98;
111
+            this.vy *= 0.98;
112
+        }
113
+    }
114
+}
115
+
116
+// Helper function to spawn food boxes
117
+function spawnFoodBox() {
118
+    let x, y;
119
+    let attempts = 0;
120
+    let valid = false;
121
+    
122
+    while (!valid && attempts < 50) {
123
+        x = random(50, width - 50);
124
+        y = random(50, height - 100);
125
+        valid = true;
126
+        
127
+        for (let obstacle of obstacles) {
128
+            if (dist(x, y, obstacle.x, obstacle.y) < obstacle.radius + 30) {
129
+                valid = false;
130
+                break;
131
+            }
132
+        }
133
+        
134
+        for (let box of foodBoxes) {
135
+            if (dist(x, y, box.pos.x, box.pos.y) < 50) {
136
+                valid = false;
137
+                break;
138
+            }
139
+        }
140
+        
141
+        attempts++;
142
+    }
143
+    
144
+    if (valid) {
145
+        foodBoxes.push(new FoodBox(x, y));
146
+    }
147
+}