C50BARZ commited on
Commit
ff3dd11
·
verified ·
1 Parent(s): c119b5c

please make a full screen 16-bit style mario style game, can shoot fireballs and jump on to kill enemies witch cool illustrated background

Browse files
Files changed (4) hide show
  1. README.md +7 -4
  2. index.html +78 -19
  3. script.js +671 -0
  4. style.css +20 -20
README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
  title: Mario Style Pixel Platformer
3
- emoji: 📈
4
- colorFrom: blue
5
- colorTo: purple
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
  title: Mario Style Pixel Platformer
3
+ colorFrom: pink
4
+ colorTo: green
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
index.html CHANGED
@@ -1,19 +1,78 @@
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>Mario Style Pixel Platformer</title>
7
+ <link rel="icon" type="image/x-icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='64' height='64'%3E%3Crect width='64' height='64' fill='%231e40af'/%3E%3Ctext x='50%25' y='55%25' font-size='36' text-anchor='middle' fill='white' font-family='monospace'%3EM%3C/text%3E%3C/svg%3E">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons"></script>
11
+ </head>
12
+ <body class="overflow-hidden">
13
+ <!-- Background Canvas -->
14
+ <canvas id="bg" class="fixed inset-0 w-full h-full -z-10"></canvas>
15
+ <!-- Game Canvas -->
16
+ <canvas id="game" class="fixed inset-0 w-full h-full"></canvas>
17
+
18
+ <!-- UI Overlay -->
19
+ <div id="hud" class="fixed inset-0 pointer-events-none">
20
+ <div class="flex justify-between items-start p-4 text-white font-mono select-none">
21
+ <div class="bg-black/40 rounded px-3 py-2 backdrop-blur">
22
+ <div class="text-sm opacity-80">SCORE</div>
23
+ <div id="score" class="text-2xl font-bold">000000</div>
24
+ </div>
25
+ <div class="flex gap-4">
26
+ <div class="bg-black/40 rounded px-3 py-2 backdrop-blur text-center">
27
+ <div class="text-sm opacity-80">COINS</div>
28
+ <div id="coins" class="text-2xl font-bold">0</div>
29
+ </div>
30
+ <div class="bg-black/40 rounded px-3 py-2 backdrop-blur text-center">
31
+ <div class="text-sm opacity-80">LIVES</div>
32
+ <div id="lives" class="text-2xl font-bold">3</div>
33
+ </div>
34
+ </div>
35
+ </div>
36
+
37
+ <!-- Center Messages -->
38
+ <div id="message" class="absolute inset-0 flex items-center justify-center pointer-events-none">
39
+ <div id="messageBox" class="hidden bg-black/70 text-white px-8 py-6 rounded-xl text-center backdrop-blur">
40
+ <div id="messageTitle" class="text-4xl font-extrabold mb-2">PAUSED</div>
41
+ <div id="messageText" class="text-lg opacity-90">Press P to Resume</div>
42
+ </div>
43
+ </div>
44
+
45
+ <!-- Help -->
46
+ <div class="absolute bottom-3 right-3 text-white/80 text-sm bg-black/40 rounded px-3 py-2 backdrop-blur">
47
+ <div class="flex items-center gap-2">
48
+ <i data-feather="info"></i>
49
+ Controls: Left/Right or A/D to move • Jump: W/Up/Space • Fire: F • Pause: P • Mute: M • Reset: R
50
+ </div>
51
+ </div>
52
+ </div>
53
+
54
+ <!-- Touch Controls -->
55
+ <div id="touchControls" class="fixed inset-0 pointer-events-none">
56
+ <div class="absolute left-4 bottom-6 flex gap-3 pointer-events-auto">
57
+ <button id="btnLeft" class="bg-blue-600/80 active:bg-blue-700/80 text-white rounded-full p-4 shadow-lg">
58
+ <i data-feather="arrow-left"></i>
59
+ </button>
60
+ <button id="btnRight" class="bg-blue-600/80 active:bg-blue-700/80 text-white rounded-full p-4 shadow-lg">
61
+ <i data-feather="arrow-right"></i>
62
+ </button>
63
+ </div>
64
+ <div class="absolute right-4 bottom-6 flex gap-3 pointer-events-auto">
65
+ <button id="btnJump" class="bg-emerald-600/80 active:bg-emerald-700/80 text-white rounded-full p-4 shadow-lg">
66
+ <i data-feather="arrow-up"></i>
67
+ </button>
68
+ <button id="btnFire" class="bg-orange-600/80 active:bg-orange-700/80 text-white rounded-full p-4 shadow-lg">
69
+ <i data-feather="zap"></i>
70
+ </button>
71
+ </div>
72
+ </div>
73
+
74
+ <script src="script.js"></script>
75
+ <script>feather.replace();</script>
76
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
77
+ </body>
78
+ </html>
script.js ADDED
@@ -0,0 +1,671 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (() => {
2
+ // Game Config
3
+ const WORLD = { width: 4200, height: 180 };
4
+ const GRAVITY = 0.35;
5
+ const FRICTION = 0.80;
6
+ const MAX_FALL = 8.5;
7
+ const PLAYER = { w: 14, h: 16, speed: 1.25, jump: 6.5, fireCooldown: 280 };
8
+ const CAMERA = { lerp: 0.12, shake: 0 };
9
+ const TILE = 16;
10
+
11
+ // Colors (choose pleasing 16-bit palette)
12
+ const COLORS = {
13
+ sky: "#14213d",
14
+ sky2: "#0f1a35",
15
+ cloud: "#e9f0ff",
16
+ cloud2: "#d6e4ff",
17
+ hill: "#274c77",
18
+ hill2: "#1d3a5b",
19
+ grass: "#41a55d",
20
+ dirt: "#6b4f3a",
21
+ dirt2: "#5a4130",
22
+ pipe: "#2f9e44",
23
+ pipeDark: "#237a34",
24
+ enemy: "#8b5e34",
25
+ enemyDark: "#704a2a",
26
+ playerHat: "#e63946",
27
+ playerBody: "#1d3557",
28
+ playerFace: "#ffe0bd",
29
+ fire1: "#ff9f1c",
30
+ fire2: "#ffd166",
31
+ coin1: "#ffd166",
32
+ coin2: "#fca311",
33
+ uiText: "#ffffff",
34
+ overlay: "rgba(0,0,0,0.55)"
35
+ };
36
+
37
+ // DOM
38
+ const bgCanvas = document.getElementById("bg");
39
+ const gameCanvas = document.getElementById("game");
40
+ const bgCtx = bgCanvas.getContext("2d");
41
+ const ctx = gameCanvas.getContext("2d");
42
+ ctx.imageSmoothingEnabled = false;
43
+ bgCtx.imageSmoothingEnabled = false;
44
+
45
+ const scoreEl = document.getElementById("score");
46
+ const coinsEl = document.getElementById("coins");
47
+ const livesEl = document.getElementById("lives");
48
+ const messageBox = document.getElementById("messageBox");
49
+ const messageTitle = document.getElementById("messageTitle");
50
+ const messageText = document.getElementById("messageText");
51
+
52
+ // Input
53
+ const keys = {
54
+ left: false, right: false, up: false, fire: false,
55
+ pause: false, mute: false
56
+ };
57
+
58
+ // Touch controls
59
+ const btnLeft = document.getElementById("btnLeft");
60
+ const btnRight = document.getElementById("btnRight");
61
+ const btnJump = document.getElementById("btnJump");
62
+ const btnFire = document.getElementById("btnFire");
63
+
64
+ // Camera
65
+ const camera = { x: 0, y: 0, w: 320, h: 180 };
66
+
67
+ // Game State
68
+ let state = "playing"; // playing, paused, gameover, win
69
+ let score = 0;
70
+ let coins = 0;
71
+ let lives = 3;
72
+ let time = 0;
73
+ let muted = false;
74
+
75
+ // Entities
76
+ const player = {
77
+ x: 32, y: 0, w: PLAYER.w, h: PLAYER.h,
78
+ vx: 0, vy: 0, dir: 1, onGround: false,
79
+ canFireAt: 0, spawnX: 32, spawnY: 0
80
+ };
81
+
82
+ /** Platforms and Pipes (simple AABB rectangles) */
83
+ const solids = [
84
+ // Ground
85
+ { x: 0, y: 150, w: WORLD.width, h: 60 },
86
+ // Small hills / platforms
87
+ { x: 240, y: 136, w: 90, h: 8 },
88
+ { x: 420, y: 122, w: 90, h: 8 },
89
+ { x: 650, y: 142, w: 80, h: 8 },
90
+ { x: 980, y: 120, w: 120, h: 8 },
91
+ { x: 1270, y: 128, w: 100, h: 8 },
92
+ { x: 1550, y: 110, w: 80, h: 8 },
93
+ { x: 1800, y: 140, w: 140, h: 8 },
94
+ { x: 2150, y: 118, w: 100, h: 8 },
95
+ { x: 2450, y: 130, w: 90, h: 8 },
96
+ { x: 2780, y: 115, w: 120, h: 8 },
97
+ { x: 3120, y: 140, w: 120, h: 8 },
98
+ { x: 3450, y: 120, w: 100, h: 8 },
99
+ { x: 3720, y: 135, w: 90, h: 8 },
100
+ ];
101
+
102
+ // Pipes
103
+ const pipes = [
104
+ { x: 600, y: 150 - 24, w: 24, h: 24 },
105
+ { x: 1320, y: 150 - 32, w: 24, h: 32 },
106
+ { x: 2000, y: 150 - 40, w: 24, h: 40 },
107
+ { x: 2680, y: 150 - 28, w: 24, h: 28 },
108
+ { x: 3300, y: 150 - 36, w: 24, h: 36 },
109
+ ];
110
+ solids.push(...pipes);
111
+
112
+ /** Enemies */
113
+ const enemies = [];
114
+ const enemySpawn = [300, 480, 700, 1060, 1300, 1700, 2100, 2450, 2890, 3230, 3600, 3920];
115
+ enemySpawn.forEach((x, i) => {
116
+ enemies.push({
117
+ x, y: 0, w: 14, h: 12,
118
+ vx: (i % 2 === 0 ? -0.6 : -0.8),
119
+ vy: 0,
120
+ dir: -1, onGround: false, dead: false, squash: 0
121
+ });
122
+ });
123
+
124
+ /** Fireballs */
125
+ const fireballs = [];
126
+
127
+ /** Coins */
128
+ const coinsArr = [];
129
+ const coinSpawn = [
130
+ [260, 120], [440, 108], [680, 128], [1000, 100], [1280, 110],
131
+ [1560, 95], [1810, 120], [2160, 100], [2460, 110], [2790, 95],
132
+ [3140, 120], [3460, 100], [3740, 116]
133
+ ];
134
+ coinSpawn.forEach(([x, y]) => {
135
+ coinsArr.push({ x, y, w: 6, h: 8, t: Math.random() * Math.PI * 2, collected: false });
136
+ });
137
+
138
+ // Resize handling
139
+ function resize() {
140
+ bgCanvas.width = window.innerWidth;
141
+ bgCanvas.height = window.innerHeight;
142
+ gameCanvas.width = window.innerWidth;
143
+ gameCanvas.height = window.innerHeight;
144
+
145
+ camera.w = gameCanvas.width;
146
+ camera.h = gameCanvas.height;
147
+ }
148
+ window.addEventListener("resize", resize);
149
+ resize();
150
+
151
+ // Input: Keyboard
152
+ const onKey = (e, down) => {
153
+ const k = e.key.toLowerCase();
154
+ if (["arrowleft","arrowright","arrowup"," "].includes(e.key.toLowerCase())) e.preventDefault();
155
+ if (k === "arrowleft" || k === "a") keys.left = down;
156
+ if (k === "arrowright" || k === "d") keys.right = down;
157
+ if (k === "arrowup" || k === "w" || e.key === " ") keys.up = down;
158
+ if (k === "f") keys.fire = down;
159
+ if (k === "p" && down) togglePause();
160
+ if (k === "m" && down) muted = !muted;
161
+ if (k === "r" && down) resetGame();
162
+ };
163
+ window.addEventListener("keydown", (e)=>onKey(e, true));
164
+ window.addEventListener("keyup", (e)=>onKey(e, false));
165
+
166
+ // Touch helpers
167
+ const press = (setter) => (down) => { setter(down); };
168
+ btnLeft.addEventListener("pointerdown", press(v => keys.left = v));
169
+ btnLeft.addEventListener("pointerup", press(v => keys.left = v));
170
+ btnLeft.addEventListener("pointerleave", press(v => keys.left = v));
171
+ btnRight.addEventListener("pointerdown", press(v => keys.right = v));
172
+ btnRight.addEventListener("pointerup", press(v => keys.right = v));
173
+ btnRight.addEventListener("pointerleave", press(v => keys.right = v));
174
+ btnJump.addEventListener("pointerdown", press(v => keys.up = v));
175
+ btnJump.addEventListener("pointerup", press(v => keys.up = v));
176
+ btnJump.addEventListener("pointerleave", press(v => keys.up = v));
177
+ btnFire.addEventListener("pointerdown", press(v => keys.fire = v));
178
+ btnFire.addEventListener("pointerup", press(v => keys.fire = v));
179
+ btnFire.addEventListener("pointerleave", press(v => keys.fire = v));
180
+
181
+ // Audio (simple blips)
182
+ const AudioCtx = window.AudioContext || window.webkitAudioContext;
183
+ const actx = new AudioCtx();
184
+ function beep(freq = 440, dur = 0.06, type = "square", vol = 0.05) {
185
+ if (muted) return;
186
+ const o = actx.createOscillator();
187
+ const g = actx.createGain();
188
+ o.type = type;
189
+ o.frequency.value = freq;
190
+ g.gain.value = vol;
191
+ o.connect(g).connect(actx.destination);
192
+ o.start();
193
+ o.stop(actx.currentTime + dur);
194
+ }
195
+
196
+ // Utils
197
+ const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
198
+ function rectsOverlap(a, b) {
199
+ return a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
200
+ }
201
+ function collideAxis(entity, dx, dy) {
202
+ // Move X
203
+ if (dx !== 0) {
204
+ entity.x += dx;
205
+ for (const s of solids) {
206
+ if (rectsOverlap(entity, s)) {
207
+ if (dx > 0) entity.x = s.x - entity.w;
208
+ else entity.x = s.x + s.w;
209
+ entity.vx = 0;
210
+ }
211
+ }
212
+ }
213
+ // Move Y
214
+ let onGround = false;
215
+ if (dy !== 0) {
216
+ entity.y += dy;
217
+ for (const s of solids) {
218
+ if (rectsOverlap(entity, s)) {
219
+ if (dy > 0) { // falling
220
+ entity.y = s.y - entity.h;
221
+ entity.vy = 0;
222
+ onGround = true;
223
+ } else { // jumping
224
+ entity.y = s.y + s.h;
225
+ entity.vy = 0;
226
+ }
227
+ }
228
+ }
229
+ }
230
+ return onGround;
231
+ }
232
+
233
+ // Update player
234
+ function updatePlayer(dt) {
235
+ // Horizontal
236
+ if (keys.left) player.vx -= PLAYER.speed * dt;
237
+ if (keys.right) player.vx += PLAYER.speed * dt;
238
+ if (!(keys.left || keys.right)) player.vx *= FRICTION;
239
+ player.vx = clamp(player.vx, -2.4, 2.4);
240
+ if (Math.abs(player.vx) > 0.05) player.dir = player.vx > 0 ? 1 : -1;
241
+
242
+ // Jump
243
+ if (keys.up && player.onGround) {
244
+ player.vy = -PLAYER.jump;
245
+ player.onGround = false;
246
+ beep(600, 0.07, "square", 0.05);
247
+ CAMERA.shake = Math.max(CAMERA.shake, 2);
248
+ }
249
+
250
+ // Gravity
251
+ player.vy += GRAVITY * dt;
252
+ player.vy = clamp(player.vy, -99, MAX_FALL);
253
+
254
+ // Move and collide
255
+ const onGroundNow = collideAxis(player, player.vx * dt, 0) === true;
256
+ const prevVy = player.vy;
257
+ const landed = collideAxis(player, 0, player.vy * dt) === true;
258
+ if (landed) {
259
+ player.onGround = true;
260
+ if (prevVy > 4.5) {
261
+ CAMERA.shake = Math.max(CAMERA.shake, prevVy * 0.3);
262
+ beep(120, 0.05, "sawtooth", 0.03);
263
+ }
264
+ } else {
265
+ player.onGround = onGroundNow;
266
+ }
267
+
268
+ // Fire
269
+ if (keys.fire && performance.now() > player.canFireAt) {
270
+ shootFireball();
271
+ player.canFireAt = performance.now() + PLAYER.fireCooldown;
272
+ }
273
+
274
+ // Clamp world
275
+ player.x = clamp(player.x, 0, WORLD.width - player.w);
276
+ if (player.y > WORLD.height + 100) {
277
+ // fell off world
278
+ loseLife(true);
279
+ }
280
+ }
281
+
282
+ function shootFireball() {
283
+ const fb = {
284
+ x: player.x + player.w / 2 + player.dir * 8,
285
+ y: player.y + 6,
286
+ w: 5, h: 5,
287
+ vx: 3.2 * player.dir,
288
+ vy: -0.2,
289
+ life: 1400
290
+ };
291
+ fireballs.push(fb);
292
+ beep(900, 0.05, "square", 0.04);
293
+ }
294
+
295
+ function updateFireballs(dt) {
296
+ for (let i = fireballs.length - 1; i >= 0; i--) {
297
+ const f = fireballs[i];
298
+ f.vy += 0.1 * dt;
299
+ f.x += f.vx * dt;
300
+ f.y += f.vy * dt;
301
+ f.life -= dt;
302
+ let remove = false;
303
+
304
+ // Collide with solids
305
+ for (const s of solids) {
306
+ if (rectsOverlap(f, s)) { remove = true; break; }
307
+ }
308
+ // Hit enemies
309
+ if (!remove) {
310
+ for (const e of enemies) {
311
+ if (!e.dead && rectsOverlap(f, e)) {
312
+ e.dead = true;
313
+ e.squash = 0.3;
314
+ addScore(100);
315
+ beep(300, 0.06, "triangle", 0.05);
316
+ remove = true;
317
+ break;
318
+ }
319
+ }
320
+ }
321
+ if (remove || f.life <= 0) fireballs.splice(i, 1);
322
+ }
323
+ }
324
+
325
+ function updateEnemies(dt) {
326
+ for (const e of enemies) {
327
+ if (e.dead) {
328
+ e.squash -= 0.02 * dt;
329
+ if (e.squash <= -0.2) e.y += 0.2 * dt;
330
+ continue;
331
+ }
332
+ // Basic AI: walk, turn on edge or hit wall
333
+ const ahead = { x: e.x + (e.vx > 0 ? e.w + 1 : -1), y: e.y + e.h + 1, w: 1, h: 1 };
334
+ // Edge detection
335
+ let onGround = false;
336
+ for (const s of solids) {
337
+ if (rectsOverlap(ahead, s)) { onGround = true; break; }
338
+ }
339
+ if (!onGround) e.vx = -e.vx;
340
+
341
+ // Gravity
342
+ e.vy += GRAVITY * dt;
343
+ e.vy = clamp(e.vy, -99, MAX_FALL);
344
+
345
+ // Move with collision
346
+ collideAxis(e, e.vx * dt, 0);
347
+ const landed = collideAxis(e, 0, e.vy * dt);
348
+ if (landed) e.onGround = true;
349
+
350
+ // Reverse on solid hit
351
+ for (const s of solids) {
352
+ if (rectsOverlap(e, s)) {
353
+ if (e.x + e.w > s.x && e.x < s.x) e.vx = Math.abs(e.vx);
354
+ else if (e.x < s.x + s.w && e.x + e.w > s.x + s.w) e.vx = -Math.abs(e.vx);
355
+ }
356
+ }
357
+
358
+ // Player interaction
359
+ if (rectsOverlap(player, e) && !e.dead) {
360
+ // Stomp if falling and above enemy
361
+ const playerBottom = player.y + player.h;
362
+ const enemyTop = e.y;
363
+ if (player.vy > 0.2 && playerBottom - enemyTop < 10) {
364
+ e.dead = true;
365
+ e.squash = 0.3;
366
+ player.vy = -4.8; // bounce
367
+ addScore(100);
368
+ beep(340, 0.07, "triangle", 0.05);
369
+ } else {
370
+ // Player hit
371
+ loseLife(false);
372
+ }
373
+ }
374
+ }
375
+ }
376
+
377
+ function updateCoins(dt) {
378
+ for (const c of coinsArr) {
379
+ c.t += 0.05 * dt;
380
+ if (!c.collected && rectsOverlap(player, c)) {
381
+ c.collected = true;
382
+ coins += 1;
383
+ addScore(200);
384
+ beep(880, 0.05, "sine", 0.05);
385
+ }
386
+ }
387
+ }
388
+
389
+ function addScore(n) {
390
+ score += n;
391
+ scoreEl.textContent = score.toString().padStart(6, "0");
392
+ }
393
+
394
+ function loseLife(fell) {
395
+ if (state !== "playing") return;
396
+ lives -= 1;
397
+ livesEl.textContent = lives.toString();
398
+ beep(160, 0.25, "sawtooth", 0.08);
399
+ CAMERA.shake = 10;
400
+ if (lives < 0) {
401
+ state = "gameover";
402
+ showMessage("GAME OVER", "Press R to Restart");
403
+ } else {
404
+ // Respawn
405
+ player.x = player.spawnX;
406
+ player.y = player.spawnY;
407
+ player.vx = 0;
408
+ player.vy = 0;
409
+ }
410
+ }
411
+
412
+ function checkWin() {
413
+ if (player.x > WORLD.width - 220 && state === "playing") {
414
+ state = "win";
415
+ showMessage("YOU WIN!", "Press R to Play Again");
416
+ addScore(1000);
417
+ }
418
+ }
419
+
420
+ function showMessage(title, text) {
421
+ messageTitle.textContent = title;
422
+ messageText.textContent = text;
423
+ messageBox.classList.remove("hidden");
424
+ }
425
+ function hideMessage() {
426
+ messageBox.classList.add("hidden");
427
+ }
428
+ function togglePause() {
429
+ if (state === "playing") {
430
+ state = "paused";
431
+ showMessage("PAUSED", "Press P to Resume");
432
+ } else if (state === "paused") {
433
+ state = "playing";
434
+ hideMessage();
435
+ }
436
+ }
437
+ function resetGame() {
438
+ // Reset state
439
+ score = 0; coins = 0; lives = 3; time = 0;
440
+ player.x = 32; player.y = 0; player.vx = 0; player.vy = 0;
441
+ enemies.forEach((e, i) => {
442
+ e.dead = false; e.squash = 0;
443
+ e.x = enemySpawn[i]; e.y = 0; e.vx = (i % 2 === 0 ? -0.6 : -0.8); e.vy = 0;
444
+ });
445
+ coinsArr.forEach(c => c.collected = false);
446
+ fireballs.length = 0;
447
+ scoreEl.textContent = "000000";
448
+ coinsEl.textContent = "0";
449
+ livesEl.textContent = "3";
450
+ state = "playing";
451
+ hideMessage();
452
+ }
453
+
454
+ // Camera follows player with easing
455
+ function updateCamera(dt) {
456
+ const targetX = clamp(player.x + player.w / 2 - camera.w / 2, 0, WORLD.width - camera.w);
457
+ camera.x += (targetX - camera.x) * CAMERA.lerp * dt;
458
+ // Parallax background offset
459
+ }
460
+
461
+ // Background drawing (parallax)
462
+ function drawBackground() {
463
+ // Sky gradient is via CSS; draw clouds/hills on bg canvas
464
+ const w = bgCanvas.width, h = bgCanvas.height;
465
+ bgCtx.clearRect(0, 0, w, h);
466
+
467
+ // Parallax offsets
468
+ const camX = camera.x * 0.3;
469
+ const hillX = camera.x * 0.5;
470
+
471
+ // Clouds
472
+ function cloud(x, y, s, c1, c2) {
473
+ bgCtx.fillStyle = c1;
474
+ bgCtx.beginPath();
475
+ bgCtx.ellipse(x, y, 18*s, 12*s, 0, 0, Math.PI*2);
476
+ bgCtx.ellipse(x+16*s, y+2*s, 16*s, 10*s, 0, 0, Math.PI*2);
477
+ bgCtx.ellipse(x-16*s, y+3*s, 14*s, 9*s, 0, 0, Math.PI*2);
478
+ bgCtx.fill();
479
+ bgCtx.fillStyle = c2;
480
+ bgCtx.beginPath();
481
+ bgCtx.ellipse(x+2, y+5, 14*s, 8*s, 0, 0, Math.PI*2);
482
+ bgCtx.fill();
483
+ }
484
+ for (let i=0;i<6;i++){
485
+ const cx = ((i*280 - camX*0.8) % (w+300)) - 150;
486
+ cloud(cx, 80 + (i%2)*12, 1 + (i%3)*0.2, COLORS.cloud, COLORS.cloud2);
487
+ }
488
+
489
+ // Hills
490
+ function hill(x, baseY, r, color) {
491
+ bgCtx.fillStyle = color;
492
+ bgCtx.beginPath();
493
+ bgCtx.arc(x, baseY, r, Math.PI, 0);
494
+ bgCtx.lineTo(x+r, baseY+200);
495
+ bgCtx.lineTo(x-r, baseY+200);
496
+ bgCtx.closePath();
497
+ bgCtx.fill();
498
+ }
499
+ for (let i=0;i<8;i++) {
500
+ const hx = ((i*280 - hillX) % (w+400)) - 200;
501
+ hill(hx, h-40, 140 + (i%3)*20, COLORS.hill);
502
+ hill(hx+140, h-36, 120 + ((i+1)%3)*16, COLORS.hill2);
503
+ }
504
+ }
505
+
506
+ // Draw entities and tiles
507
+ function draw() {
508
+ // Camera shake
509
+ if (CAMERA.shake > 0) CAMERA.shake *= 0.9;
510
+
511
+ const shakeX = (Math.random() - 0.5) * CAMERA.shake;
512
+ const shakeY = (Math.random() - 0.5) * CAMERA.shake;
513
+
514
+ // Clear
515
+ ctx.clearRect(0, 0, gameCanvas.width, gameCanvas.height);
516
+
517
+ ctx.save();
518
+ ctx.translate(-Math.floor(camera.x) + shakeX, -Math.floor(camera.y) + shakeY);
519
+
520
+ // Ground tiles
521
+ for (let x = 0; x < WORLD.width; x += TILE) {
522
+ const baseY = 150;
523
+ ctx.fillStyle = COLORS.dirt;
524
+ ctx.fillRect(x, baseY, TILE, WORLD.height - baseY);
525
+ // top grass
526
+ ctx.fillStyle = COLORS.grass;
527
+ ctx.fillRect(x, baseY - 2, TILE, 2);
528
+ // speckles
529
+ ctx.fillStyle = COLORS.dirt2;
530
+ for (let i = 0; i < 3; i++) {
531
+ const sx = x + (i * 5 % 12) + 2;
532
+ const sy = baseY + 6 + (i * 7 % 10);
533
+ ctx.fillRect(sx, sy, 2, 2);
534
+ }
535
+ }
536
+
537
+ // Platforms
538
+ for (const s of solids) {
539
+ if (s.y < 150) {
540
+ ctx.fillStyle = COLORS.dirt;
541
+ ctx.fillRect(s.x, s.y, s.w, s.h);
542
+ ctx.fillStyle = COLORS.grass;
543
+ ctx.fillRect(s.x, s.y - 2, s.w, 2);
544
+ }
545
+ }
546
+
547
+ // Pipes
548
+ for (const p of pipes) {
549
+ ctx.fillStyle = COLORS.pipe;
550
+ ctx.fillRect(p.x, p.y, p.w, p.h);
551
+ ctx.fillStyle = COLORS.pipeDark;
552
+ ctx.fillRect(p.x + 2, p.y + 2, p.w - 4, p.h - 4);
553
+ // lip
554
+ ctx.fillStyle = COLORS.pipe;
555
+ ctx.fillRect(p.x - 4, p.y - 4, p.w + 8, 4);
556
+ }
557
+
558
+ // Coins
559
+ for (const c of coinsArr) {
560
+ if (c.collected) continue;
561
+ const bob = Math.sin(c.t) * 2;
562
+ ctx.fillStyle = COLORS.coin2;
563
+ ctx.fillRect(c.x, c.y + bob, c.w, c.h);
564
+ ctx.fillStyle = COLORS.coin1;
565
+ ctx.fillRect(c.x + 1, c.y + 1 + bob, c.w - 2, c.h - 2);
566
+ // highlight
567
+ ctx.fillStyle = "rgba(255,255,255,0.5)";
568
+ ctx.fillRect(c.x + 1, c.y + 2 + bob, 1, c.h - 4);
569
+ }
570
+
571
+ // Enemies
572
+ for (const e of enemies) {
573
+ if (e.dead) {
574
+ // Squash effect
575
+ ctx.fillStyle = COLORS.enemyDark;
576
+ ctx.fillRect(e.x, e.y + e.h - 4, e.w, 4);
577
+ continue;
578
+ }
579
+ ctx.fillStyle = COLORS.enemy;
580
+ ctx.fillRect(e.x, e.y, e.w, e.h);
581
+ ctx.fillStyle = COLORS.enemyDark;
582
+ ctx.fillRect(e.x + 2, e.y + e.h - 3, e.w - 4, 3);
583
+ // Eyes
584
+ ctx.fillStyle = "#111";
585
+ ctx.fillRect(e.x + 3, e.y + 3, 2, 2);
586
+ ctx.fillRect(e.x + e.w - 5, e.y + 3, 2, 2);
587
+ }
588
+
589
+ // Fireballs
590
+ for (const f of fireballs) {
591
+ ctx.fillStyle = COLORS.fire1;
592
+ ctx.fillRect(f.x - 2, f.y - 2, 4, 4);
593
+ ctx.fillStyle = COLORS.fire2;
594
+ ctx.fillRect(f.x - 1, f.y - 1, 2, 2);
595
+ }
596
+
597
+ // Player
598
+ drawPlayer();
599
+
600
+ // Goal flag near end
601
+ const flagX = WORLD.width - 160, flagY = 110;
602
+ ctx.fillStyle = "#444";
603
+ ctx.fillRect(flagX, flagY - 40, 3, 60);
604
+ ctx.fillStyle = "#e63946";
605
+ ctx.fillRect(flagX + 3, flagY - 38, 18, 12);
606
+ ctx.fillStyle = "#fff";
607
+ ctx.fillRect(flagX + 9, flagY - 35, 6, 3);
608
+
609
+ ctx.restore();
610
+
611
+ // HUD update
612
+ coinsEl.textContent = coins.toString();
613
+ }
614
+
615
+ function drawPlayer() {
616
+ const p = player;
617
+ // Shadow
618
+ ctx.fillStyle = "rgba(0,0,0,0.25)";
619
+ ctx.fillRect(p.x - 2, p.y + p.h - 2, p.w + 4, 3);
620
+
621
+ // Body
622
+ ctx.fillStyle = COLORS.playerBody;
623
+ ctx.fillRect(p.x, p.y + 4, p.w, p.h - 4);
624
+ // Hat
625
+ ctx.fillStyle = COLORS.playerHat;
626
+ ctx.fillRect(p.x, p.y, p.w, 4);
627
+ // Face
628
+ ctx.fillStyle = COLORS.playerFace;
629
+ ctx.fillRect(p.x + 3, p.y + 5, 8, 6);
630
+ // Eye
631
+ ctx.fillStyle = "#111";
632
+ if (p.dir >= 0) ctx.fillRect(p.x + 8, p.y + 6, 2, 2);
633
+ else ctx.fillRect(p.x + 4, p.y + 6, 2, 2);
634
+ // Shoes
635
+ ctx.fillStyle = "#3d3d3d";
636
+ ctx.fillRect(p.x + 2, p.y + p.h - 2, 4, 2);
637
+ ctx.fillRect(p.x + p.w - 6, p.y + p.h - 2, 4, 2);
638
+ }
639
+
640
+ // Main loop
641
+ let last = performance.now();
642
+ function loop(now) {
643
+ const dtMs = now - last;
644
+ last = now;
645
+ const dt = clamp(dtMs / (1000 / 60), 0.2, 3); // normalize to 60fps units
646
+
647
+ time += dtMs;
648
+ if (state === "playing") {
649
+ updatePlayer(dt);
650
+ updateFireballs(dt);
651
+ updateEnemies(dt);
652
+ updateCoins(dt);
653
+ checkWin();
654
+ updateCamera(dt);
655
+ }
656
+
657
+ // Redraw bg and fg
658
+ drawBackground();
659
+ draw();
660
+
661
+ requestAnimationFrame(loop);
662
+ }
663
+ requestAnimationFrame(loop);
664
+
665
+ // Initialize
666
+ resetGame();
667
+
668
+ // Prevent context menu on touch
669
+ window.addEventListener("contextmenu", e => e.preventDefault(), { passive: false });
670
+
671
+ })();
style.css CHANGED
@@ -1,28 +1,28 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }
 
 
 
1
+ /* Pixel crisp rendering */
2
+ canvas {
3
+ image-rendering: pixelated;
4
+ image-rendering: -moz-crisp-edges;
5
+ image-rendering: crisp-edges;
6
  }
7
 
8
+ /* Ensure full height */
9
+ html, body {
10
+ height: 100%;
11
+ background: #0b1021;
12
  }
13
 
14
+ /* Hide touch controls on non-touch devices */
15
+ @media (hover: hover) and (pointer: fine) {
16
+ #touchControls { display: none; }
 
 
17
  }
18
 
19
+ /* Animated background shimmer (subtle) */
20
+ #bg {
21
+ background: linear-gradient(180deg, #0b1021 0%, #0f1530 60%, #121a3a 100%);
 
 
 
22
  }
23
 
24
+ /* Small responsive UI tweaks */
25
+ @media (max-width: 640px) {
26
+ #hud .text-2xl { font-size: 1.25rem; }
27
+ #hud .text-4xl { font-size: 2rem; }
28
+ }