404game 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,571 @@
1
+ const j = /* @__PURE__ */ new Set([" ", "Spacebar", "Enter"]), J = /* @__PURE__ */ new Set(["ArrowUp", "w", "W"]), Q = /* @__PURE__ */ new Set(["ArrowDown", "s", "S"]), Z = /* @__PURE__ */ new Set(["ArrowLeft", "a", "A"]), ee = /* @__PURE__ */ new Set(["ArrowRight", "d", "D"]), F = 30;
2
+ class te {
3
+ constructor(e) {
4
+ this.state = {
5
+ up: !1,
6
+ down: !1,
7
+ left: !1,
8
+ right: !1,
9
+ action: !1,
10
+ actionPressed: !1,
11
+ swipe: null,
12
+ tap: !1
13
+ }, this.actionWasPressed = !1, this.nextSwipe = null, this.nextTap = !1, this.touchStartX = 0, this.touchStartY = 0, this.touchStartTime = 0, this.boundHandlers = [], this.target = e, this.attach();
14
+ }
15
+ attach() {
16
+ const e = (f) => {
17
+ this.handleKey(f.key, !0) && f.preventDefault();
18
+ }, t = (f) => {
19
+ this.handleKey(f.key, !1);
20
+ }, n = (f) => {
21
+ const g = f.touches[0];
22
+ g && (this.touchStartX = g.clientX, this.touchStartY = g.clientY, this.touchStartTime = performance.now(), f.preventDefault());
23
+ }, o = (f) => {
24
+ const g = f.changedTouches[0];
25
+ if (!g) return;
26
+ const l = g.clientX - this.touchStartX, c = g.clientY - this.touchStartY, a = performance.now() - this.touchStartTime, i = Math.abs(l), d = Math.abs(c);
27
+ i < F && d < F && a < 400 ? (this.nextTap = !0, this.nextSwipe = null, this.actionWasPressed = !0) : i > d ? this.nextSwipe = l > 0 ? "right" : "left" : this.nextSwipe = c > 0 ? "down" : "up", f.preventDefault();
28
+ }, u = (f) => {
29
+ this.nextTap = !0, this.actionWasPressed = !0, f.preventDefault();
30
+ };
31
+ this.addListener(window, "keydown", e), this.addListener(window, "keyup", t), this.addListener(this.target, "touchstart", n, { passive: !1 }), this.addListener(this.target, "touchend", o, { passive: !1 }), this.addListener(this.target, "mousedown", u);
32
+ }
33
+ addListener(e, t, n, o) {
34
+ e.addEventListener(t, n, o), this.boundHandlers.push({ type: t, fn: n, target: e });
35
+ }
36
+ handleKey(e, t) {
37
+ let n = !1;
38
+ if (J.has(e))
39
+ this.state.up = t, n = !0, t && (this.nextSwipe = "up");
40
+ else if (Q.has(e))
41
+ this.state.down = t, n = !0, t && (this.nextSwipe = "down");
42
+ else if (Z.has(e))
43
+ this.state.left = t, n = !0, t && (this.nextSwipe = "left");
44
+ else if (ee.has(e))
45
+ this.state.right = t, n = !0, t && (this.nextSwipe = "right");
46
+ else if (j.has(e)) {
47
+ const o = this.state.action;
48
+ this.state.action = t, t && !o && (this.actionWasPressed = !0), n = !0;
49
+ }
50
+ return n;
51
+ }
52
+ /** Called by Engine once per frame BEFORE update. */
53
+ beginFrame() {
54
+ this.state.actionPressed = this.actionWasPressed, this.state.swipe = this.nextSwipe, this.state.tap = this.nextTap;
55
+ }
56
+ /** Called by Engine once per frame AFTER update. */
57
+ endFrame() {
58
+ this.actionWasPressed = !1, this.nextSwipe = null, this.nextTap = !1;
59
+ }
60
+ destroy() {
61
+ for (const { target: e, type: t, fn: n } of this.boundHandlers)
62
+ e.removeEventListener(t, n);
63
+ this.boundHandlers = [];
64
+ }
65
+ }
66
+ const p = {
67
+ clear(s, e, t, n) {
68
+ s.fillStyle = n.bg, s.fillRect(0, 0, e, t);
69
+ },
70
+ rect(s, e, t, n, o, u) {
71
+ s.fillStyle = u, s.fillRect(e, t, n, o);
72
+ },
73
+ circle(s, e, t, n, o) {
74
+ s.fillStyle = o, s.beginPath(), s.arc(e, t, n, 0, Math.PI * 2), s.fill();
75
+ },
76
+ text(s, e, t, n, o = {}) {
77
+ const u = o.size ?? 16, f = o.bold ? "bold " : "";
78
+ s.fillStyle = o.color ?? "#fff", s.font = `${f}${u}px ${o.font ?? "monospace"}`, s.textAlign = o.align ?? "left", s.textBaseline = o.baseline ?? "alphabetic", s.fillText(e, t, n);
79
+ },
80
+ glow(s, e, t, n) {
81
+ s.save(), s.shadowColor = e, s.shadowBlur = t, n(), s.restore();
82
+ }
83
+ };
84
+ class se {
85
+ constructor(e) {
86
+ this.rafId = null, this.state = "idle", this.lastTime = 0, this.startTime = 0, this.score = 0, this.loop = () => {
87
+ if (this.state !== "running") return;
88
+ const o = performance.now(), u = Math.min(0.05, (o - this.lastTime) / 1e3);
89
+ this.lastTime = o, this.gameCtx.dt = u, this.gameCtx.time = (o - this.startTime) / 1e3, this.input.beginFrame(), p.clear(this.ctx2d, this.opts.width, this.opts.height, this.opts.theme), this.opts.game.update(this.gameCtx), this.state === "running" && this.opts.game.draw(this.gameCtx), this.input.endFrame(), this.rafId = requestAnimationFrame(this.loop);
90
+ }, this.opts = e;
91
+ const t = e.canvas.getContext("2d");
92
+ if (!t) throw new Error("404game: 2D canvas context unavailable");
93
+ this.ctx2d = t;
94
+ const n = window.devicePixelRatio || 1;
95
+ e.canvas.width = e.width * n, e.canvas.height = e.height * n, e.canvas.style.width = `${e.width}px`, e.canvas.style.height = `${e.height}px`, this.ctx2d.setTransform(n, 0, 0, n, 0, 0), this.input = new te(e.canvas), this.gameCtx = {
96
+ ctx: this.ctx2d,
97
+ width: e.width,
98
+ height: e.height,
99
+ theme: e.theme,
100
+ input: this.input.state,
101
+ dt: 0,
102
+ time: 0,
103
+ setScore: (o) => {
104
+ this.score = o, this.opts.onScore(o);
105
+ },
106
+ gameOver: () => this.handleGameOver(),
107
+ win: () => this.handleWin()
108
+ };
109
+ }
110
+ start() {
111
+ this.state !== "running" && (this.score = 0, this.opts.onScore(0), this.startTime = performance.now(), this.lastTime = this.startTime, this.state = "running", this.opts.game.init(this.gameCtx), this.loop());
112
+ }
113
+ stop() {
114
+ this.rafId != null && (cancelAnimationFrame(this.rafId), this.rafId = null), this.state = "idle";
115
+ }
116
+ isRunning() {
117
+ return this.state === "running";
118
+ }
119
+ handleGameOver() {
120
+ this.state === "running" && (this.state = "gameover", this.rafId != null && (cancelAnimationFrame(this.rafId), this.rafId = null), this.opts.onGameOver(this.score));
121
+ }
122
+ handleWin() {
123
+ this.state === "running" && (this.state = "won", this.rafId != null && (cancelAnimationFrame(this.rafId), this.rafId = null), this.opts.onWin(this.score));
124
+ }
125
+ getInputState() {
126
+ return this.input.state;
127
+ }
128
+ destroy() {
129
+ var e, t;
130
+ this.stop(), this.input.destroy(), (t = (e = this.opts.game).destroy) == null || t.call(e);
131
+ }
132
+ }
133
+ class ne {
134
+ constructor(e, t) {
135
+ this.opts = t, this.root = e, this.banner = document.createElement("div"), this.banner.className = "g404-banner", this.banner.innerHTML = `
136
+ <div class="g404-banner-title">${B(t.message)}</div>
137
+ <div class="g404-banner-sub">${B(t.subMessage)}</div>
138
+ `, this.hud = document.createElement("div"), this.hud.className = "g404-hud", this.scoreEl = document.createElement("span"), this.scoreEl.className = "g404-score", this.scoreEl.textContent = "Score: 0", this.highEl = document.createElement("span"), this.highEl.className = "g404-high", this.highEl.textContent = "Best: 0", this.hud.appendChild(this.scoreEl), t.showHighscore && this.hud.appendChild(this.highEl), this.startScreen = document.createElement("div"), this.startScreen.className = "g404-screen g404-start", this.startScreen.innerHTML = `
139
+ <div class="g404-screen-title">READY?</div>
140
+ <div class="g404-screen-sub">Press SPACE or TAP to start</div>
141
+ <button class="g404-btn" type="button">▶ START</button>
142
+ `, this.startScreen.querySelector("button").addEventListener("click", (n) => {
143
+ n.stopPropagation(), t.onStart();
144
+ }), this.gameOverScreen = document.createElement("div"), this.gameOverScreen.className = "g404-screen g404-gameover", this.gameOverScreen.style.display = "none", this.gameOverScreen.innerHTML = `
145
+ <div class="g404-screen-title">GAME OVER</div>
146
+ <div class="g404-final-score">Score: <span class="g404-final">0</span></div>
147
+ <button class="g404-btn" type="button">↻ PLAY AGAIN</button>
148
+ `, this.finalScoreEl = this.gameOverScreen.querySelector(".g404-final"), this.gameOverScreen.querySelector("button").addEventListener("click", (n) => {
149
+ n.stopPropagation(), t.onRestart();
150
+ }), this.injectStyles(t.theme), this.root.appendChild(this.banner), this.root.appendChild(this.hud), this.root.appendChild(this.startScreen), this.root.appendChild(this.gameOverScreen);
151
+ }
152
+ injectStyles(e) {
153
+ if (document.getElementById("g404-styles")) return;
154
+ const t = document.createElement("style");
155
+ t.id = "g404-styles", t.textContent = `
156
+ .g404-root { position: relative; display: inline-block; font-family: ${e.font}; }
157
+ .g404-banner { text-align: center; padding: 12px 8px 8px; color: ${e.fg}; font-family: ${e.font}; }
158
+ .g404-banner-title { font-size: 22px; font-weight: bold; letter-spacing: 2px; color: ${e.accent}; text-shadow: 0 0 12px ${e.accent}55; }
159
+ .g404-banner-sub { font-size: 13px; color: ${e.muted}; margin-top: 4px; }
160
+ .g404-hud { display: flex; justify-content: space-between; padding: 6px 12px; color: ${e.fg}; font-family: ${e.font}; font-size: 14px; }
161
+ .g404-score { color: ${e.fg}; }
162
+ .g404-high { color: ${e.muted}; }
163
+ .g404-canvas-wrap { position: relative; line-height: 0; }
164
+ .g404-canvas { display: block; border-radius: 6px; touch-action: none; cursor: pointer; }
165
+ .g404-screen { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px; background: ${e.bg}cc; backdrop-filter: blur(4px); color: ${e.fg}; font-family: ${e.font}; border-radius: 6px; }
166
+ .g404-screen-title { font-size: 32px; font-weight: bold; letter-spacing: 3px; color: ${e.accent}; text-shadow: 0 0 16px ${e.accent}88; }
167
+ .g404-screen-sub { font-size: 14px; color: ${e.muted}; }
168
+ .g404-final-score { font-size: 18px; color: ${e.fg}; }
169
+ .g404-final { color: ${e.accent}; font-weight: bold; }
170
+ .g404-btn { background: ${e.accent}; color: ${e.bg}; border: none; padding: 12px 28px; font-size: 16px; font-weight: bold; letter-spacing: 1px; border-radius: 6px; cursor: pointer; font-family: ${e.font}; transition: transform 0.1s, box-shadow 0.2s; }
171
+ .g404-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 16px ${e.accent}66; }
172
+ .g404-btn:active { transform: translateY(0); }
173
+ `, document.head.appendChild(t);
174
+ }
175
+ setScore(e) {
176
+ this.scoreEl.textContent = `Score: ${e}`;
177
+ }
178
+ setHighscore(e) {
179
+ this.highEl.textContent = `Best: ${e}`;
180
+ }
181
+ showStart() {
182
+ this.startScreen.style.display = "flex", this.gameOverScreen.style.display = "none";
183
+ }
184
+ hideStart() {
185
+ this.startScreen.style.display = "none";
186
+ }
187
+ showGameOver(e) {
188
+ this.finalScoreEl.textContent = String(e), this.gameOverScreen.style.display = "flex", this.startScreen.style.display = "none";
189
+ }
190
+ hideGameOver() {
191
+ this.gameOverScreen.style.display = "none";
192
+ }
193
+ destroy() {
194
+ this.banner.remove(), this.hud.remove(), this.startScreen.remove(), this.gameOverScreen.remove();
195
+ }
196
+ }
197
+ function B(s) {
198
+ const e = document.createElement("div");
199
+ return e.textContent = s, e.innerHTML;
200
+ }
201
+ const K = "404game.hs.", P = {
202
+ getHighscore(s) {
203
+ try {
204
+ const e = localStorage.getItem(K + s);
205
+ return e && parseInt(e, 10) || 0;
206
+ } catch {
207
+ return 0;
208
+ }
209
+ },
210
+ setHighscore(s, e) {
211
+ try {
212
+ const t = P.getHighscore(s);
213
+ if (e > t)
214
+ return localStorage.setItem(K + s, String(e)), !0;
215
+ } catch {
216
+ }
217
+ return !1;
218
+ }
219
+ }, V = {
220
+ neon: {
221
+ bg: "#0a0a18",
222
+ fg: "#f0f0ff",
223
+ accent: "#00ffd5",
224
+ danger: "#ff3b6b",
225
+ muted: "#5a5a8a",
226
+ font: '"Courier New", monospace'
227
+ },
228
+ retro: {
229
+ bg: "#2b1d0e",
230
+ fg: "#ffe1a8",
231
+ accent: "#ffaa00",
232
+ danger: "#d62828",
233
+ muted: "#8a6b3b",
234
+ font: '"Courier New", monospace'
235
+ },
236
+ minimal: {
237
+ bg: "#fafafa",
238
+ fg: "#111111",
239
+ accent: "#0066ff",
240
+ danger: "#e63946",
241
+ muted: "#999999",
242
+ font: "system-ui, -apple-system, sans-serif"
243
+ },
244
+ dark: {
245
+ bg: "#1a1a1a",
246
+ fg: "#e8e8e8",
247
+ accent: "#7c5cff",
248
+ danger: "#ff5252",
249
+ muted: "#666666",
250
+ font: "system-ui, -apple-system, sans-serif"
251
+ }
252
+ };
253
+ function ie(s) {
254
+ return V[s ?? "neon"] ?? V.neon;
255
+ }
256
+ const y = 16, X = 0.12;
257
+ function ae() {
258
+ let s = 0, e = 0, t = [], n = "right", o = "right", u = { x: 0, y: 0 }, f = 0, g = 0;
259
+ function l() {
260
+ for (; ; ) {
261
+ const a = { x: Math.floor(Math.random() * s), y: Math.floor(Math.random() * e) };
262
+ if (!t.some((i) => i.x === a.x && i.y === a.y)) {
263
+ u = a;
264
+ return;
265
+ }
266
+ }
267
+ }
268
+ function c(a) {
269
+ ({ up: "down", down: "up", left: "right", right: "left" })[a] !== n && (o = a);
270
+ }
271
+ return {
272
+ name: "snake",
273
+ init(a) {
274
+ s = Math.floor(a.width / y), e = Math.floor(a.height / y);
275
+ const i = Math.floor(s / 2), d = Math.floor(e / 2);
276
+ t = [{ x: i, y: d }, { x: i - 1, y: d }, { x: i - 2, y: d }], n = "right", o = "right", f = 0, g = 0, l(), a.setScore(0);
277
+ },
278
+ update(a) {
279
+ const { input: i } = a;
280
+ for (i.swipe === "up" || i.up ? c("up") : i.swipe === "down" || i.down ? c("down") : i.swipe === "left" || i.left ? c("left") : (i.swipe === "right" || i.right) && c("right"), f += a.dt; f >= X; ) {
281
+ f -= X, n = o;
282
+ const d = t[0], r = { x: d.x, y: d.y };
283
+ if (n === "up" ? r.y -= 1 : n === "down" ? r.y += 1 : n === "left" ? r.x -= 1 : n === "right" && (r.x += 1), r.x < 0 || r.x >= s || r.y < 0 || r.y >= e) {
284
+ a.gameOver();
285
+ return;
286
+ }
287
+ if (t.some((h) => h.x === r.x && h.y === r.y)) {
288
+ a.gameOver();
289
+ return;
290
+ }
291
+ t.unshift(r), r.x === u.x && r.y === u.y ? (g += 10, a.setScore(g), l()) : t.pop();
292
+ }
293
+ },
294
+ draw(a) {
295
+ const { ctx: i, theme: d } = a;
296
+ i.strokeStyle = d.muted + "22", i.lineWidth = 1;
297
+ for (let r = 0; r <= s; r++)
298
+ i.beginPath(), i.moveTo(r * y, 0), i.lineTo(r * y, e * y), i.stroke();
299
+ for (let r = 0; r <= e; r++)
300
+ i.beginPath(), i.moveTo(0, r * y), i.lineTo(s * y, r * y), i.stroke();
301
+ p.glow(i, d.danger, 12, () => {
302
+ p.circle(i, u.x * y + y / 2, u.y * y + y / 2, y / 2 - 2, d.danger);
303
+ }), p.glow(i, d.accent, 8, () => {
304
+ for (let r = 0; r < t.length; r++) {
305
+ const h = t[r];
306
+ p.rect(i, h.x * y + 1, h.y * y + 1, y - 2, y - 2, r === 0 ? d.accent : d.accent + "cc");
307
+ }
308
+ });
309
+ }
310
+ };
311
+ }
312
+ const re = 1400, oe = -380, O = 50, k = 130, L = 200, q = 160, E = 12;
313
+ function ce() {
314
+ let s = 0, e = 0, t = 0, n = [], o = 0, u = 0;
315
+ return {
316
+ name: "flappy",
317
+ init(f) {
318
+ e = f.width * 0.25, s = f.height / 2, t = 0, n = [], o = 0, u = L, f.setScore(0);
319
+ },
320
+ update(f) {
321
+ const { input: g, dt: l, width: c, height: a } = f;
322
+ if ((g.actionPressed || g.tap || g.swipe === "up") && (t = oe), t += re * l, s += t * l, s > a - E || s < E) {
323
+ f.gameOver();
324
+ return;
325
+ }
326
+ if (u += q * l, u >= L) {
327
+ u -= L;
328
+ const i = 60 + Math.random() * (a - 120 - k);
329
+ n.push({ x: c + O, gapY: i, passed: !1 });
330
+ }
331
+ for (const i of n)
332
+ if (i.x -= q * l, !i.passed && i.x + O < e && (i.passed = !0, o += 1, f.setScore(o)), e + E > i.x && e - E < i.x + O && (s - E < i.gapY || s + E > i.gapY + k)) {
333
+ f.gameOver();
334
+ return;
335
+ }
336
+ n = n.filter((i) => i.x + O > -10);
337
+ },
338
+ draw(f) {
339
+ const { ctx: g, theme: l, height: c } = f;
340
+ p.glow(g, l.accent, 8, () => {
341
+ for (const a of n)
342
+ p.rect(g, a.x, 0, O, a.gapY, l.accent), p.rect(g, a.x, a.gapY + k, O, c - (a.gapY + k), l.accent);
343
+ }), p.glow(g, l.danger, 12, () => {
344
+ p.circle(g, e, s, E, l.danger);
345
+ }), p.circle(g, e + 4, s - 3, 2, l.bg);
346
+ }
347
+ };
348
+ }
349
+ const T = 80, M = 10, H = 30, z = 420, S = 6, C = 280, he = 5, Y = 8;
350
+ function le() {
351
+ let s = 0, e = 0, t = 0, n = 0, o = 0, u = [], f = 0, g = !1;
352
+ return {
353
+ name: "breakout",
354
+ init(l) {
355
+ s = l.width / 2 - T / 2, e = l.width / 2, t = l.height - H - M - S - 1, n = 0, o = 0, g = !1, f = 0, l.setScore(0), u = [];
356
+ const c = 6, a = 40, i = (l.width - c * (Y + 1)) / Y, d = 16, r = [l.theme.danger, l.theme.accent, l.theme.fg, l.theme.accent, l.theme.danger];
357
+ for (let h = 0; h < he; h++)
358
+ for (let m = 0; m < Y; m++)
359
+ u.push({
360
+ x: c + m * (i + c),
361
+ y: a + h * (d + c),
362
+ w: i,
363
+ h: d,
364
+ alive: !0,
365
+ color: r[h] ?? l.theme.accent
366
+ });
367
+ },
368
+ update(l) {
369
+ const { input: c, dt: a, width: i, height: d } = l;
370
+ c.left && (s -= z * a), c.right && (s += z * a), c.swipe === "left" && (s -= 40), c.swipe === "right" && (s += 40), s = Math.max(0, Math.min(i - T, s));
371
+ const r = d - H - M;
372
+ if (!g) {
373
+ if (e = s + T / 2, t = r - S - 1, c.actionPressed || c.tap) {
374
+ g = !0;
375
+ const h = -Math.PI / 2 + (Math.random() - 0.5) * 0.6;
376
+ n = Math.cos(h) * C, o = Math.sin(h) * C;
377
+ }
378
+ return;
379
+ }
380
+ if (e += n * a, t += o * a, e < S && (e = S, n = -n), e > i - S && (e = i - S, n = -n), t < S && (t = S, o = -o), t + S >= r && t - S <= r + M && e >= s && e <= s + T && o > 0) {
381
+ o = -Math.abs(o), n = (e - (s + T / 2)) / (T / 2) * C * 0.9;
382
+ const m = Math.sqrt(n * n + o * o), b = C;
383
+ n = n / m * b, o = o / m * b;
384
+ }
385
+ for (const h of u)
386
+ if (h.alive && e + S > h.x && e - S < h.x + h.w && t + S > h.y && t - S < h.y + h.h) {
387
+ h.alive = !1, f += 10, l.setScore(f);
388
+ const m = Math.min(e + S - h.x, h.x + h.w - (e - S)), b = Math.min(t + S - h.y, h.y + h.h - (t - S));
389
+ m < b ? n = -n : o = -o;
390
+ break;
391
+ }
392
+ if (u.every((h) => !h.alive)) {
393
+ l.win();
394
+ return;
395
+ }
396
+ if (t > d + 20) {
397
+ l.gameOver();
398
+ return;
399
+ }
400
+ },
401
+ draw(l) {
402
+ const { ctx: c, theme: a, height: i } = l;
403
+ for (const r of u)
404
+ r.alive && p.glow(c, r.color, 6, () => {
405
+ p.rect(c, r.x, r.y, r.w, r.h, r.color);
406
+ });
407
+ const d = i - H - M;
408
+ p.glow(c, a.accent, 8, () => {
409
+ p.rect(c, s, d, T, M, a.accent);
410
+ }), p.glow(c, a.fg, 10, () => {
411
+ p.circle(c, e, t, S, a.fg);
412
+ }), g || p.text(c, "Press SPACE / TAP to launch", l.width / 2, l.height - 8, {
413
+ color: a.muted,
414
+ size: 11,
415
+ font: a.font,
416
+ align: "center"
417
+ });
418
+ }
419
+ };
420
+ }
421
+ const de = 2200, fe = -780, $ = 24, I = 28, ge = 30, R = 240;
422
+ function ue() {
423
+ let s = 0, e = 0, t = 0, n = 0, o = [], u = 0, f = 0, g = 0, l = R;
424
+ return {
425
+ name: "dino",
426
+ init(c) {
427
+ s = 60, n = c.height - ge, e = n - I, t = 0, o = [], u = 0.8, f = 0, g = 0, l = R, c.setScore(0);
428
+ },
429
+ update(c) {
430
+ const { input: a, dt: i, width: d } = c, r = e >= n - I;
431
+ if ((a.actionPressed || a.tap || a.swipe === "up") && r && (t = fe), t += de * i, e += t * i, e > n - I && (e = n - I, t = 0), l += i * 8, g += i * 10, g >= 1) {
432
+ const h = Math.floor(g);
433
+ g -= h, f += h, c.setScore(f);
434
+ }
435
+ if (u -= i, u <= 0) {
436
+ const h = 22 + Math.random() * 28, m = 12 + Math.random() * 16;
437
+ o.push({ x: d + 20, w: m, h }), u = 0.7 + Math.random() * 0.9 - Math.min(0.4, (l - R) / 1e3), u < 0.4 && (u = 0.4);
438
+ }
439
+ for (const h of o) h.x -= l * i;
440
+ o = o.filter((h) => h.x + h.w > -10);
441
+ for (const h of o) {
442
+ const m = h.x, b = n - h.h;
443
+ if (s + $ > m && s < m + h.w && e + I > b && e < b + h.h) {
444
+ c.gameOver();
445
+ return;
446
+ }
447
+ }
448
+ },
449
+ draw(c) {
450
+ const { ctx: a, theme: i, width: d } = c;
451
+ a.strokeStyle = i.muted, a.lineWidth = 1, a.beginPath(), a.moveTo(0, n + 0.5), a.lineTo(d, n + 0.5), a.stroke();
452
+ for (let r = 0; r < 20; r++) {
453
+ const h = r * 40 - c.time * 80 % 40;
454
+ p.rect(a, h, n + 4, 16, 2, i.muted);
455
+ }
456
+ p.glow(a, i.danger, 6, () => {
457
+ for (const r of o)
458
+ p.rect(a, r.x, n - r.h, r.w, r.h, i.danger);
459
+ }), p.glow(a, i.accent, 10, () => {
460
+ p.rect(a, s, e, $, I, i.accent);
461
+ }), p.rect(a, s + $ - 7, e + 6, 3, 3, i.bg);
462
+ }
463
+ };
464
+ }
465
+ const me = "0.1.0", _ = {
466
+ snake: ae,
467
+ flappy: ce,
468
+ breakout: le,
469
+ dino: ue
470
+ }, A = /* @__PURE__ */ new WeakMap();
471
+ function U(s) {
472
+ const e = typeof s == "string" ? document.querySelector(s) : s;
473
+ if (!e) throw new Error(`404game: target not found: ${String(s)}`);
474
+ return e;
475
+ }
476
+ function G(s) {
477
+ if (!s || s === "random") {
478
+ const t = Object.keys(_), n = t[Math.floor(Math.random() * t.length)];
479
+ return { name: n, module: _[n]() };
480
+ }
481
+ const e = _[s];
482
+ if (!e) throw new Error(`404game: unknown game "${s}"`);
483
+ return { name: s, module: e() };
484
+ }
485
+ function pe(s, e = {}) {
486
+ const t = U(s), n = A.get(t);
487
+ n && n.destroy();
488
+ const o = ie(e.theme), u = e.width ?? (Math.min(t.clientWidth || 480, 720) || 480), f = e.height ?? 360;
489
+ t.classList.add("g404-root"), t.innerHTML = "";
490
+ const g = document.createElement("div");
491
+ g.className = "g404-canvas-wrap";
492
+ const l = document.createElement("canvas");
493
+ l.className = "g404-canvas", l.style.background = o.bg, g.appendChild(l), t.appendChild(g);
494
+ let c = "", a = 0, i = G(e.game);
495
+ c = i.name;
496
+ const d = new ne(t, {
497
+ message: e.message ?? "404 — Page Not Found",
498
+ subMessage: e.subMessage ?? "But hey, want to play instead?",
499
+ theme: o,
500
+ showHighscore: e.showHighscore !== !1,
501
+ onStart: () => {
502
+ var w;
503
+ d.hideStart(), d.hideGameOver(), m.start(), (w = e.onStart) == null || w.call(e);
504
+ },
505
+ onRestart: () => {
506
+ var w;
507
+ d.hideGameOver(), i = G(e.game), c = i.name, m.destroy(), b(), m.start(), (w = e.onStart) == null || w.call(e);
508
+ }
509
+ }), r = t.querySelector(".g404-start"), h = t.querySelector(".g404-gameover");
510
+ r && g.appendChild(r), h && g.appendChild(h), g.style.position = "relative", d.setHighscore(P.getHighscore(c));
511
+ let m;
512
+ function b() {
513
+ m = new se({
514
+ canvas: l,
515
+ theme: o,
516
+ width: u,
517
+ height: f,
518
+ game: i.module,
519
+ onScore: (w) => {
520
+ var v;
521
+ a = w, d.setScore(w), (v = e.onScore) == null || v.call(e, w);
522
+ },
523
+ onGameOver: (w) => {
524
+ var v;
525
+ P.setHighscore(c, w), d.setHighscore(P.getHighscore(c)), d.showGameOver(w), (v = e.onGameOver) == null || v.call(e, w);
526
+ },
527
+ onWin: (w) => {
528
+ var v;
529
+ P.setHighscore(c, w), d.setHighscore(P.getHighscore(c)), d.showGameOver(w), (v = e.onWin) == null || v.call(e, w);
530
+ }
531
+ });
532
+ }
533
+ if (b(), e.autoStart)
534
+ d.hideStart(), m.start();
535
+ else {
536
+ d.showStart();
537
+ const w = (v) => {
538
+ var x, D, N;
539
+ v.preventDefault(), !m.isRunning() && ((D = (x = t.querySelector(".g404-start")) == null ? void 0 : x.checkVisibility) == null ? void 0 : D.call(x)) !== !1 && (r == null ? void 0 : r.style.display) !== "none" && (d.hideStart(), m.start(), (N = e.onStart) == null || N.call(e));
540
+ };
541
+ l.addEventListener("click", w), window.addEventListener("keydown", (v) => {
542
+ var x;
543
+ (v.key === " " || v.key === "Enter") && !m.isRunning() && (r == null ? void 0 : r.style.display) !== "none" && (v.preventDefault(), d.hideStart(), m.start(), (x = e.onStart) == null || x.call(e));
544
+ });
545
+ }
546
+ const W = {
547
+ destroy() {
548
+ m.destroy(), d.destroy(), t.innerHTML = "", t.classList.remove("g404-root"), A.delete(t);
549
+ },
550
+ restart() {
551
+ m.destroy(), i = G(e.game), c = i.name, b(), d.setHighscore(P.getHighscore(c)), d.hideGameOver(), d.hideStart(), m.start();
552
+ },
553
+ getScore() {
554
+ return a;
555
+ }
556
+ };
557
+ return A.set(t, W), W;
558
+ }
559
+ function we(s) {
560
+ const e = U(s), t = A.get(e);
561
+ t && t.destroy();
562
+ }
563
+ const Se = ["snake", "flappy", "breakout", "dino"], ye = me, be = { mount: pe, unmount: we, games: Se, version: ye };
564
+ export {
565
+ be as default,
566
+ Se as games,
567
+ pe as mount,
568
+ we as unmount,
569
+ ye as version
570
+ };
571
+ //# sourceMappingURL=404game.js.map