awacke1 commited on
Commit
2776b1f
·
verified ·
1 Parent(s): dfcb18c

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +646 -18
index.html CHANGED
@@ -1,19 +1,647 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  </html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Mappy 3D - Synthwave Edition</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ padding: 0;
11
+ display: flex;
12
+ justify-content: center;
13
+ align-items: center;
14
+ min-height: 100vh;
15
+ background-color: #1a0a2a; /* Dark synth purple */
16
+ font-family: 'Press Start 2P', cursive;
17
+ color: #fff;
18
+ overflow: hidden;
19
+ }
20
+ #game-container {
21
+ text-align: center;
22
+ }
23
+ canvas {
24
+ background-color: #1a1a2e;
25
+ display: block;
26
+ border: 4px solid #fff;
27
+ border-radius: 10px;
28
+ /* Synthwave glow */
29
+ box-shadow: 0 0 20px #fff, 0 0 30px #f0f, 0 0 40px #0ff;
30
+ }
31
+ #info-panel {
32
+ display: flex;
33
+ justify-content: space-between;
34
+ align-items: center;
35
+ width: 800px;
36
+ padding: 10px 0;
37
+ font-size: 24px;
38
+ position: absolute;
39
+ top: 10px;
40
+ left: 50%;
41
+ transform: translateX(-50%);
42
+ z-index: 10;
43
+ text-shadow: 0 0 5px #f0f;
44
+ }
45
+ #reset-button {
46
+ font-family: 'Press Start 2P', cursive;
47
+ background-color: #ff00ff;
48
+ color: #fff;
49
+ border: 2px solid #fff;
50
+ border-radius: 5px;
51
+ padding: 10px 15px;
52
+ cursor: pointer;
53
+ font-size: 16px;
54
+ box-shadow: 0 0 10px #f0f;
55
+ transition: all 0.2s ease;
56
+ }
57
+ #reset-button:hover {
58
+ background-color: #fff;
59
+ color: #ff00ff;
60
+ box-shadow: 0 0 20px #f0f, 0 0 30px #f0f;
61
+ }
62
+ #controls-info {
63
+ margin-top: 15px;
64
+ font-size: 16px;
65
+ color: #aaa;
66
+ position: absolute;
67
+ bottom: 10px;
68
+ width: 100%;
69
+ text-align: center;
70
+ }
71
+ #message-overlay {
72
+ position: absolute;
73
+ top: 0;
74
+ left: 0;
75
+ width: 100%;
76
+ height: 100%;
77
+ background-color: rgba(0, 0, 0, 0.7);
78
+ display: none; /* Hidden by default */
79
+ justify-content: center;
80
+ align-items: center;
81
+ text-align: center;
82
+ z-index: 20;
83
+ }
84
+ #message-overlay div {
85
+ font-size: 48px;
86
+ text-shadow: 0 0 10px #f0f, 0 0 20px #f0f;
87
+ }
88
+ #message-overlay span {
89
+ font-size: 24px;
90
+ color: #0ff; /* Cyan for secondary message */
91
+ margin-top: 20px;
92
+ display: block;
93
+ }
94
+ @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
95
+ </style>
96
+ </head>
97
+ <body>
98
+ <div id="info-panel">
99
+ <div>
100
+ <span id="score">SCORE: 0</span>
101
+ <span id="lives" style="margin-left: 20px;">LIVES: 3</span>
102
+ </div>
103
+ <button id="reset-button">RESET</button>
104
+ </div>
105
+ <div id="game-container">
106
+ <canvas id="gameCanvas"></canvas>
107
+ </div>
108
+ <div id="message-overlay">
109
+ <div>
110
+ <div id="primary-message">MAPPY 3D</div>
111
+ <span id="secondary-message">Press Enter to Start</span>
112
+ </div>
113
+ </div>
114
+ <div id="controls-info">
115
+ 🎹 Left/Right Arrow Keys to Move 🎷
116
+ </div>
117
+
118
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
119
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>
120
+
121
+ <script>
122
+ // --- DOM Elements ---
123
+ const canvas = document.getElementById('gameCanvas');
124
+ const scoreElement = document.getElementById('score');
125
+ const livesElement = document.getElementById('lives');
126
+ const resetButton = document.getElementById('reset-button');
127
+ const messageOverlay = document.getElementById('message-overlay');
128
+ const primaryMessage = document.getElementById('primary-message');
129
+ const secondaryMessage = document.getElementById('secondary-message');
130
+
131
+ // --- Game Configuration ---
132
+ const NUM_FLOORS = 5;
133
+ const FLOOR_Y_POSITIONS = [-8, -4, 0, 4, 8];
134
+ const FLOOR_WIDTH = 40;
135
+ const FLOOR_DEPTH = 4;
136
+ const FLOOR_HEIGHT = 0.2;
137
+ const TRAMPOLINE_POS_X = 18;
138
+ const TRAMPOLINE_ALLEY_WIDTH = 6;
139
+ const PLAYER_MOVE_SPEED = 10;
140
+ const DEATH_Y_LEVEL = -15; // Y-coordinate for falling off world
141
+
142
+ // --- Game State ---
143
+ let score = 0;
144
+ let lives = 3;
145
+ let gameState = 'start';
146
+ let keys = {};
147
+ let gameObjects = [];
148
+
149
+ // --- Scene Setup ---
150
+ const scene = new THREE.Scene();
151
+ scene.background = new THREE.Color(0x1a0a2a);
152
+ const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
153
+ camera.position.set(0, 2, 24);
154
+ const renderer = new THREE.WebGLRenderer({ canvas: canvas, antialias: true });
155
+ renderer.setSize(window.innerWidth, window.innerHeight);
156
+ renderer.shadowMap.enabled = true;
157
+
158
+ // --- Lighting ---
159
+ const ambientLight = new THREE.AmbientLight(0x400080, 0.7);
160
+ scene.add(ambientLight);
161
+ const mainShadowLight = new THREE.PointLight(0xff00ff, 0.6, 50);
162
+ mainShadowLight.castShadow = true;
163
+ mainShadowLight.position.set(0, 5, 10);
164
+ scene.add(mainShadowLight);
165
+
166
+ // --- Physics Setup ---
167
+ const world = new CANNON.World();
168
+ world.gravity.set(0, -35, 0);
169
+ world.broadphase = new CANNON.NaiveBroadphase();
170
+ world.solver.iterations = 10;
171
+
172
+ // --- Materials ---
173
+ const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x222222, metalness: 0.8, roughness: 0.2 });
174
+ const roofMaterial = new THREE.MeshStandardMaterial({ color: 0x111111, metalness: 0.9, roughness: 0.1 });
175
+ const trampolineMaterial = new THREE.MeshStandardMaterial({ color: 0xff00ff, emissive: 0xff00ff, emissiveIntensity: 0.8 });
176
+ const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x00ffff, emissive: 0x00ffff, emissiveIntensity: 0.6, transparent: true, opacity: 0.7 });
177
+ const windowFrameMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc, metalness: 1.0 });
178
+
179
+ // Physics Materials
180
+ const groundPhysMaterial = new CANNON.Material("groundMaterial");
181
+ const playerPhysMaterial = new CANNON.Material("playerMaterial");
182
+ const trampolinePhysMaterial = new CANNON.Material("trampolineMaterial");
183
+
184
+ world.addContactMaterial(new CANNON.ContactMaterial(groundPhysMaterial, playerPhysMaterial, { friction: 0.0, restitution: 0.1 }));
185
+ world.addContactMaterial(new CANNON.ContactMaterial(trampolinePhysMaterial, playerPhysMaterial, { friction: 0.3, restitution: 1.8 }));
186
+
187
+ // --- Helper function to add objects ---
188
+ function addObject(mesh, body, type, name = '') {
189
+ if(mesh) scene.add(mesh);
190
+ if(body) world.addBody(body);
191
+ gameObjects.push({ mesh, body, type, name, active: true });
192
+ }
193
+
194
+ // --- Character Creation Functions ---
195
+ function createMappy() {
196
+ const mappyGroup = new THREE.Group();
197
+ const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0x4d96ff, emissive: 0x4d96ff, emissiveIntensity: 0.2 });
198
+ const detailMaterial = new THREE.MeshStandardMaterial({ color: 0x000000 });
199
+
200
+ // Body & Head
201
+ const body = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 16), bodyMaterial);
202
+ body.position.y = 0.5; body.castShadow = true; mappyGroup.add(body);
203
+ const head = new THREE.Mesh(new THREE.SphereGeometry(0.35, 16, 16), bodyMaterial);
204
+ head.position.y = 1.2; mappyGroup.add(head);
205
+
206
+ // Ears
207
+ const ear1 = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.2, 0.1, 12), detailMaterial);
208
+ ear1.position.set(-0.3, 1.5, 0); mappyGroup.add(ear1);
209
+ const ear2 = ear1.clone(); ear2.position.x = 0.3; mappyGroup.add(ear2);
210
+
211
+ // Details
212
+ const nose = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8), detailMaterial);
213
+ nose.position.set(0, 1.2, 0.35); mappyGroup.add(nose);
214
+ const eyeMaterial = new THREE.MeshBasicMaterial({color: 0xffffff});
215
+ const eye1 = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 8), eyeMaterial);
216
+ eye1.position.set(-0.15, 1.3, 0.3); mappyGroup.add(eye1);
217
+ const eye2 = eye1.clone(); eye2.position.x = 0.15; mappyGroup.add(eye2);
218
+
219
+ // Hat
220
+ const hatTop = new THREE.Mesh(new THREE.CylinderGeometry(0.2, 0.25, 0.3, 12), bodyMaterial);
221
+ hatTop.position.y = 1.6; mappyGroup.add(hatTop);
222
+ const hatBrim = new THREE.Mesh(new THREE.CylinderGeometry(0.3, 0.3, 0.05, 12), detailMaterial);
223
+ hatBrim.position.y = 1.45; mappyGroup.add(hatBrim);
224
+
225
+ const shape = new CANNON.Sphere(0.7);
226
+ const physicsBody = new CANNON.Body({ mass: 5, shape, material: playerPhysMaterial, fixedRotation: true, linearDamping: 0.1 });
227
+ physicsBody.position.set(0, 0, 0);
228
+ addObject(mappyGroup, physicsBody, 'player', 'mappy');
229
+ }
230
+
231
+ function createCat(x, y, z) {
232
+ const catGroup = new THREE.Group();
233
+ const bodyMaterial = new THREE.MeshStandardMaterial({ color: 0xff66a3, emissive: 0xff66a3, emissiveIntensity: 0.3 });
234
+ const detailMaterial = new THREE.MeshStandardMaterial({ color: 0x222222 });
235
+
236
+ const body = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.8, 1.2), bodyMaterial);
237
+ body.position.y = 0.4; body.castShadow = true; catGroup.add(body);
238
+ const head = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), bodyMaterial);
239
+ head.position.y = 1.0; head.position.z = 0.4; catGroup.add(head);
240
+ const earShape = new THREE.ConeGeometry(0.15, 0.3, 8);
241
+ const ear1 = new THREE.Mesh(earShape, detailMaterial);
242
+ ear1.position.set(-0.3, 1.4, 0.4); catGroup.add(ear1);
243
+ const ear2 = ear1.clone(); ear2.position.x = 0.3; catGroup.add(ear2);
244
+
245
+ // Details
246
+ const nose = new THREE.Mesh(new THREE.SphereGeometry(0.08, 8, 8), detailMaterial);
247
+ nose.position.set(0, 1.0, 0.8); catGroup.add(nose);
248
+ const eye1 = new THREE.Mesh(new THREE.SphereGeometry(0.1, 8, 8), detailMaterial);
249
+ eye1.scale.y = 1.5; // Oval eyes
250
+ eye1.position.set(-0.15, 1.1, 0.75); catGroup.add(eye1);
251
+ const eye2 = eye1.clone(); eye2.position.x = 0.15; catGroup.add(eye2);
252
+
253
+ // Tail
254
+ const tailSegment = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 0.2, 6), bodyMaterial);
255
+ let currentSegment = tailSegment;
256
+ currentSegment.position.set(0, 0.3, -0.7);
257
+ catGroup.add(currentSegment);
258
+ for(let i = 0; i < 4; i++) {
259
+ const nextSegment = tailSegment.clone();
260
+ nextSegment.position.y = -0.1;
261
+ nextSegment.rotation.x = Math.PI / 8;
262
+ currentSegment.add(nextSegment);
263
+ currentSegment = nextSegment;
264
+ }
265
+
266
+
267
+ const shape = new CANNON.Box(new CANNON.Vec3(0.4, 0.5, 0.6));
268
+ const physicsBody = new CANNON.Body({ mass: 3, material: groundPhysMaterial });
269
+ physicsBody.addShape(shape);
270
+ physicsBody.position.set(x, y, z);
271
+ physicsBody.fixedRotation = true; physicsBody.linearDamping = 0.5;
272
+ physicsBody.direction = Math.random() < 0.5 ? 1 : -1;
273
+ physicsBody.speed = 5 + Math.random() * 2;
274
+ addObject(catGroup, physicsBody, 'cat');
275
+ }
276
+
277
+ // --- Unique Artwork Creation Functions ---
278
+ function createArtwork(x, y, z, type) {
279
+ const artGroup = new THREE.Group();
280
+ const material1 = new THREE.MeshStandardMaterial({ color: 0x00ffff, metalness: 0.8, roughness: 0.1, emissive: 0x00ffff, emissiveIntensity: 0.5 });
281
+ const material2 = new THREE.MeshStandardMaterial({ color: 0xffffff, metalness: 0.8, roughness: 0.1 });
282
+
283
+ switch(type) {
284
+ case 0: // Twisted Tower
285
+ for(let i = 0; i < 5; i++) {
286
+ const box = new THREE.Mesh(new THREE.BoxGeometry(0.8 - i*0.1, 0.3, 0.8 - i*0.1), material1);
287
+ box.position.y = i * 0.3;
288
+ box.rotation.y = i * Math.PI / 4;
289
+ artGroup.add(box);
290
+ }
291
+ break;
292
+ case 1: // Geometric Stack
293
+ const base = new THREE.Mesh(new THREE.BoxGeometry(1, 0.2, 1), material2);
294
+ artGroup.add(base);
295
+ const sphere = new THREE.Mesh(new THREE.SphereGeometry(0.4, 16, 16), material1);
296
+ sphere.position.y = 0.5;
297
+ artGroup.add(sphere);
298
+ const cone = new THREE.Mesh(new THREE.ConeGeometry(0.3, 0.8, 12), material2);
299
+ cone.position.y = 1.1;
300
+ artGroup.add(cone);
301
+ break;
302
+ case 2: // Celestial Orb
303
+ const ring = new THREE.Mesh(new THREE.TorusGeometry(0.6, 0.05, 8, 32), material2);
304
+ ring.rotation.x = Math.PI / 2;
305
+ artGroup.add(ring);
306
+ const orb = new THREE.Mesh(new THREE.IcosahedronGeometry(0.4, 0), material1);
307
+ artGroup.add(orb);
308
+ break;
309
+ case 3: // Extruded Star
310
+ const starShape = new THREE.Shape();
311
+ const sides = 5;
312
+ const innerRadius = 0.2;
313
+ const outerRadius = 0.4;
314
+ starShape.moveTo(0, outerRadius);
315
+ for (let i = 0; i < sides; i++) {
316
+ let angle = (i / sides) * 2 * Math.PI;
317
+ starShape.lineTo(Math.sin(angle) * outerRadius, Math.cos(angle) * outerRadius);
318
+ angle += (1 / (sides * 2)) * 2 * Math.PI;
319
+ starShape.lineTo(Math.sin(angle) * innerRadius, Math.cos(angle) * innerRadius);
320
+ }
321
+ const extrudeSettings = { depth: 0.2, bevelEnabled: false };
322
+ const star = new THREE.Mesh(new THREE.ExtrudeGeometry(starShape, extrudeSettings), material1);
323
+ star.rotation.x = Math.PI/2;
324
+ artGroup.add(star);
325
+ break;
326
+ case 4: // Spiky Ball
327
+ const center = new THREE.Mesh(new THREE.SphereGeometry(0.3, 16, 16), material2);
328
+ artGroup.add(center);
329
+ for(let i=0; i<10; i++) {
330
+ const spike = new THREE.Mesh(new THREE.ConeGeometry(0.05, 0.5, 6), material1);
331
+ const randomDirection = new THREE.Vector3(
332
+ Math.random() * 2 - 1,
333
+ Math.random() * 2 - 1,
334
+ Math.random() * 2 - 1
335
+ ).normalize();
336
+ spike.position.copy(randomDirection).multiplyScalar(0.4);
337
+ spike.lookAt(0,0,0);
338
+ spike.rotation.x += Math.PI/2;
339
+ artGroup.add(spike);
340
+ }
341
+ break;
342
+ }
343
+
344
+ artGroup.position.set(x, y, z);
345
+ artGroup.castShadow = true;
346
+ scene.add(artGroup);
347
+ const itemData = { mesh: artGroup, body: null, type: 'item', active: true, isBonus: false, bonusTimer: 0 };
348
+ gameObjects.push(itemData);
349
+ return itemData;
350
+ }
351
+
352
+
353
+ function createDoor(x, y, z) {
354
+ const doorGroup = new THREE.Group();
355
+ const doorMesh = new THREE.Mesh(new THREE.BoxGeometry(2, 3.5, 0.2), doorMaterial.clone());
356
+ doorMesh.castShadow = true;
357
+ doorGroup.add(doorMesh);
358
+ doorGroup.position.set(x, y, z);
359
+ const doorData = { mesh: doorGroup, body: null, type: 'door', active: true, opening: false, openAngle: 0 };
360
+ addObject(doorGroup, null, 'door');
361
+ return doorData;
362
+ }
363
+
364
+ // --- World Creation ---
365
+ function createWorld() {
366
+ const centralFloorWidth = FLOOR_WIDTH - TRAMPOLINE_ALLEY_WIDTH * 2;
367
+ const centralFloorShape = new CANNON.Box(new CANNON.Vec3(centralFloorWidth / 2, FLOOR_HEIGHT / 2, FLOOR_DEPTH / 2));
368
+ const gridMaterial = new THREE.MeshBasicMaterial({ color: 0xff00ff, wireframe: true });
369
+
370
+ for(let i = 0; i < NUM_FLOORS; i++) {
371
+ const y = FLOOR_Y_POSITIONS[i];
372
+ const floorBody = new CANNON.Body({ mass: 0, shape: centralFloorShape, material: groundPhysMaterial });
373
+ floorBody.position.set(0, y - FLOOR_HEIGHT / 2, 0);
374
+ const floorMesh = new THREE.Mesh(new THREE.BoxGeometry(centralFloorWidth, FLOOR_HEIGHT, FLOOR_DEPTH), gridMaterial);
375
+ addObject(floorMesh, floorBody, 'floor');
376
+
377
+ const light = new THREE.PointLight(0x00ffff, 0.2, 15);
378
+ light.position.set(0, y + 2, 5);
379
+ scene.add(light);
380
+
381
+ const wallHeight = (i === NUM_FLOORS - 1) ? 6 : 4;
382
+ const wallZ = -FLOOR_DEPTH / 2;
383
+
384
+ const wallShape = new CANNON.Box(new CANNON.Vec3(FLOOR_WIDTH / 2, wallHeight / 2, 0.1));
385
+ const wallBody = new CANNON.Body({ mass: 0, material: groundPhysMaterial });
386
+ wallBody.addShape(wallShape);
387
+ wallBody.position.set(0, y + wallHeight / 2, wallZ);
388
+
389
+ const backWallGroup = new THREE.Group();
390
+ const wallMesh = new THREE.Mesh(new THREE.BoxGeometry(FLOOR_WIDTH, wallHeight, 0.2), wallMaterial);
391
+ backWallGroup.add(wallMesh);
392
+
393
+ if (i === NUM_FLOORS - 1) {
394
+ [-10, 0, 10].forEach(wx => {
395
+ const arch = new THREE.Shape();
396
+ arch.moveTo(-1.5, 0);
397
+ arch.lineTo(-1.5, 2.5);
398
+ arch.absarc(0, 2.5, 1.5, Math.PI, 0, true);
399
+ arch.lineTo(1.5, 0);
400
+ arch.lineTo(-1.5, 0);
401
+
402
+ const frame = new THREE.Mesh(new THREE.ShapeGeometry(arch), windowFrameMaterial);
403
+ frame.position.set(wx, 0.1, 0.11);
404
+ backWallGroup.add(frame);
405
+ });
406
+ } else {
407
+ [-10, 10].forEach(wx => {
408
+ const frameGeo = new THREE.BoxGeometry(2.2, 2.2, 0.1);
409
+ const frame = new THREE.Mesh(frameGeo, windowFrameMaterial);
410
+ frame.position.set(wx, -0.5, 0.11);
411
+ backWallGroup.add(frame);
412
+ });
413
+ }
414
+
415
+ addObject(backWallGroup, wallBody, 'wall');
416
+ }
417
+
418
+ const topY = FLOOR_Y_POSITIONS[NUM_FLOORS - 1] + 6;
419
+ const roofShape = new CANNON.Box(new CANNON.Vec3(FLOOR_WIDTH / 2, 0.1, FLOOR_DEPTH / 2));
420
+ const roofBody = new CANNON.Body({ mass: 0, material: groundPhysMaterial });
421
+ roofBody.addShape(roofShape);
422
+ roofBody.position.set(0, topY, 0);
423
+ const roofMesh = new THREE.Mesh(new THREE.BoxGeometry(FLOOR_WIDTH, 0.2, FLOOR_DEPTH), roofMaterial);
424
+ addObject(roofMesh, roofBody, 'roof');
425
+
426
+ [-TRAMPOLINE_POS_X, TRAMPOLINE_POS_X].forEach(x => {
427
+ const trampBody = new CANNON.Body({ mass: 0, shape: new CANNON.Box(new CANNON.Vec3(TRAMPOLINE_ALLEY_WIDTH/2, 0.2, FLOOR_DEPTH)), material: trampolinePhysMaterial });
428
+ trampBody.position.set(x, FLOOR_Y_POSITIONS[0] - 1, 0);
429
+ const trampMesh = new THREE.Mesh(new THREE.BoxGeometry(TRAMPOLINE_ALLEY_WIDTH, 0.4, FLOOR_DEPTH * 2), trampolineMaterial);
430
+ trampMesh.receiveShadow = true;
431
+ addObject(trampMesh, trampBody, 'trampoline');
432
+ });
433
+
434
+ const sideWallHeight = topY - FLOOR_Y_POSITIONS[0] + 2;
435
+ const sideWallShape = new CANNON.Box(new CANNON.Vec3(0.5, sideWallHeight/2, FLOOR_DEPTH/2));
436
+ [-FLOOR_WIDTH/2 - 0.5, FLOOR_WIDTH/2 + 0.5].forEach(x => {
437
+ const wallBody = new CANNON.Body({ mass: 0, material: groundPhysMaterial});
438
+ wallBody.addShape(sideWallShape); wallBody.position.set(x, sideWallHeight/2 + FLOOR_Y_POSITIONS[0] - 1, 0);
439
+ addObject(new THREE.Mesh(new THREE.BoxGeometry(1, sideWallHeight, FLOOR_DEPTH), wallMaterial), wallBody, 'wall');
440
+ });
441
+ }
442
+
443
+ // --- Game Logic ---
444
+ function resetGame() {
445
+ score = 0;
446
+ lives = 3;
447
+ updateUI();
448
+ initLevel();
449
+ }
450
+
451
+ function initLevel() {
452
+ gameObjects.forEach(obj => {
453
+ if (obj.mesh) scene.remove(obj.mesh);
454
+ if (obj.body) world.remove(obj.body);
455
+ });
456
+ gameObjects = [];
457
+
458
+ createWorld();
459
+ createMappy();
460
+
461
+ FLOOR_Y_POSITIONS.forEach((y, i) => {
462
+ const catCount = (i === NUM_FLOORS - 1) ? 2 : 1;
463
+ for (let c = 0; c < catCount; c++) {
464
+ createCat((Math.random() - 0.5) * (FLOOR_WIDTH - TRAMPOLINE_ALLEY_WIDTH * 2 - 4), y + 1, 0);
465
+ }
466
+ if (i < NUM_FLOORS - 1) {
467
+ createDoor((Math.random() > 0.5 ? 1 : -1) * 8, y + 1.75, 0);
468
+ }
469
+ });
470
+
471
+ const items = [];
472
+ const artTypes = 5; // Number of createArtwork types
473
+ for (let i = 0; i < NUM_FLOORS; i++) {
474
+ const itemCount = (i === NUM_FLOORS - 1) ? 4 : 2;
475
+ for(let j=0; j<itemCount; j++){
476
+ const itemX = (Math.random() - 0.5) * (FLOOR_WIDTH - TRAMPOLINE_ALLEY_WIDTH * 2 - 4);
477
+ const artType = Math.floor(Math.random() * artTypes);
478
+ items.push(createArtwork(itemX, FLOOR_Y_POSITIONS[i] + 1.5, 0, artType));
479
+ }
480
+ }
481
+ const bonusItem = items[Math.floor(Math.random() * items.length)];
482
+ bonusItem.isBonus = true;
483
+ bonusItem.bonusTimer = 15;
484
+ bonusItem.mesh.children.forEach(child => {
485
+ child.material = new THREE.MeshStandardMaterial({ color: 0xff00ff, metalness: 1.0, roughness: 0.1, emissive: 0xff00ff, emissiveIntensity: 1.0 });
486
+ });
487
+
488
+
489
+ gameState = 'playing';
490
+ messageOverlay.style.display = 'none';
491
+ }
492
+
493
+ function resetPlayer() {
494
+ lives--;
495
+ updateUI();
496
+ if (lives <= 0) {
497
+ gameState = 'game-over';
498
+ showMessage('GAME OVER 😭', 'Press Enter to Restart');
499
+ } else {
500
+ const playerObj = gameObjects.find(obj => obj.type === 'player');
501
+ if (playerObj) {
502
+ playerObj.body.position.set(0, FLOOR_Y_POSITIONS[2] + 5, 0);
503
+ playerObj.body.velocity.set(0, 0, 0);
504
+ playerObj.body.angularVelocity.set(0, 0, 0);
505
+ }
506
+ }
507
+ }
508
+
509
+ function updateUI() {
510
+ scoreElement.textContent = `SCORE: ${score}`;
511
+ livesElement.textContent = `LIVES: ${lives}`;
512
+ }
513
+
514
+ function showMessage(primary, secondary) {
515
+ primaryMessage.textContent = primary;
516
+ secondaryMessage.textContent = secondary;
517
+ messageOverlay.style.display = 'flex';
518
+ }
519
+
520
+ // --- Main Game Loop ---
521
+ const clock = new THREE.Clock();
522
+ let cameraTarget = new THREE.Vector3();
523
+
524
+ function animate() {
525
+ requestAnimationFrame(animate);
526
+ const dt = clock.getDelta();
527
+
528
+ if (gameState === 'playing') {
529
+ world.step(1 / 60, dt);
530
+
531
+ const playerObj = gameObjects.find(obj => obj.type === 'player');
532
+ if (!playerObj) return;
533
+
534
+ // Check if player fell off the world
535
+ if (playerObj.body.position.y < DEATH_Y_LEVEL) {
536
+ resetPlayer();
537
+ }
538
+
539
+ const currentXVelocity = playerObj.body.velocity.x;
540
+ let targetXVelocity = 0;
541
+ if (keys['ArrowLeft']) {
542
+ targetXVelocity = -PLAYER_MOVE_SPEED;
543
+ } else if (keys['ArrowRight']) {
544
+ targetXVelocity = PLAYER_MOVE_SPEED;
545
+ }
546
+ playerObj.body.velocity.x = currentXVelocity + (targetXVelocity - currentXVelocity) * 0.3;
547
+
548
+
549
+ gameObjects.forEach(obj => {
550
+ if (!obj.active) return;
551
+ if (obj.mesh && obj.body) {
552
+ obj.mesh.position.copy(obj.body.position);
553
+ obj.mesh.quaternion.copy(obj.body.quaternion);
554
+ }
555
+
556
+ if (obj.type === 'cat') {
557
+ const centralFloorWidth = FLOOR_WIDTH - TRAMPOLINE_ALLEY_WIDTH * 2;
558
+ if (Math.abs(obj.body.position.x) > centralFloorWidth / 2 - 2) obj.body.direction *= -1;
559
+ obj.body.velocity.x = obj.body.direction * obj.body.speed;
560
+ if (playerObj.body.position.distanceTo(obj.body.position) < 1.2) resetPlayer();
561
+ }
562
+
563
+ if (obj.type === 'item') {
564
+ obj.mesh.rotation.y += 0.02;
565
+ if (obj.isBonus) {
566
+ obj.bonusTimer -= dt;
567
+ const intensity = Math.abs(Math.sin(obj.bonusTimer * 20)) * 1.5 + 0.5;
568
+ obj.mesh.children.forEach(child => {
569
+ if(child.material.emissive) child.material.emissiveIntensity = intensity
570
+ });
571
+ if (obj.bonusTimer <= 0) {
572
+ obj.isBonus = false;
573
+ // This part is tricky as materials are different. A full implementation would store original materials.
574
+ // For now, we just stop the flashing.
575
+ obj.mesh.children.forEach(child => {
576
+ if(child.material.emissive) child.material.emissiveIntensity = 0.5
577
+ });
578
+ }
579
+ }
580
+ if (playerObj.mesh.position.distanceTo(obj.mesh.position) < 1.5) {
581
+ obj.active = false; scene.remove(obj.mesh);
582
+ score += obj.isBonus ? 1000 : 100;
583
+ updateUI();
584
+ }
585
+ }
586
+
587
+ if (obj.type === 'door' && !obj.opening) {
588
+ if (playerObj.mesh.position.distanceTo(obj.mesh.position) < 1.5) {
589
+ obj.opening = true;
590
+ gameObjects.filter(o => o.type === 'cat' && o.active).forEach(cat => {
591
+ if (cat.mesh.position.distanceTo(obj.mesh.position) < 2.5) {
592
+ cat.active = false;
593
+ scene.remove(cat.mesh);
594
+ world.remove(cat.body);
595
+ score += 500;
596
+ updateUI();
597
+ }
598
+ });
599
+ }
600
+ }
601
+ if (obj.opening && obj.openAngle < Math.PI / 2) {
602
+ obj.openAngle += dt * 5;
603
+ obj.mesh.children[0].rotation.y = obj.openAngle;
604
+ }
605
+ });
606
+
607
+ if (gameObjects.filter(o => o.type === 'item' && o.active).length === 0) {
608
+ gameState = 'level-clear';
609
+ score += 1000;
610
+ updateUI();
611
+ showMessage('LEVEL CLEAR! ✨', 'Press Enter for Next Level');
612
+ }
613
+
614
+ cameraTarget.set(playerObj.mesh.position.x * 0.5, playerObj.mesh.position.y + 2, 24);
615
+ camera.position.lerp(cameraTarget, 0.05);
616
+ }
617
+
618
+ renderer.render(scene, camera);
619
+ }
620
+
621
+ // --- Event Listeners ---
622
+ resetButton.addEventListener('click', resetGame);
623
+
624
+ window.addEventListener('keydown', (e) => {
625
+ keys[e.key] = true;
626
+ if (e.key === 'Enter') {
627
+ if (gameState === 'start' || gameState === 'game-over') {
628
+ resetGame();
629
+ } else if (gameState === 'level-clear') {
630
+ initLevel();
631
+ }
632
+ }
633
+ });
634
+ window.addEventListener('keyup', (e) => { keys[e.key] = false; });
635
+ window.addEventListener('resize', () => {
636
+ camera.aspect = window.innerWidth / window.innerHeight;
637
+ camera.updateProjectionMatrix();
638
+ renderer.setSize(window.innerWidth, window.innerHeight);
639
+ }, false);
640
+
641
+ // --- Start Game ---
642
+ updateUI();
643
+ showMessage('MAPPY 3D', 'Press Enter to Start');
644
+ animate();
645
+ </script>
646
+ </body>
647
  </html>