dopaminejs 2.0.1 → 2.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.
package/dist/dopamine.js CHANGED
@@ -1,4 +1,4 @@
1
- class M {
1
+ class C {
2
2
  constructor() {
3
3
  this.listeners = /* @__PURE__ */ new Map();
4
4
  }
@@ -8,28 +8,28 @@ class M {
8
8
  * @param {Function} callback - Callback function
9
9
  * @returns {Function} Unsubscribe function
10
10
  */
11
- on(t, e) {
12
- return this.listeners.has(t) || this.listeners.set(t, /* @__PURE__ */ new Set()), this.listeners.get(t).add(e), () => this.off(t, e);
11
+ on(e, t) {
12
+ return this.listeners.has(e) || this.listeners.set(e, /* @__PURE__ */ new Set()), this.listeners.get(e).add(t), () => this.off(e, t);
13
13
  }
14
14
  /**
15
15
  * Unsubscribe from an event
16
16
  * @param {string} event - Event name
17
17
  * @param {Function} callback - Callback function
18
18
  */
19
- off(t, e) {
20
- this.listeners.has(t) && this.listeners.get(t).delete(e);
19
+ off(e, t) {
20
+ this.listeners.has(e) && this.listeners.get(e).delete(t);
21
21
  }
22
22
  /**
23
23
  * Emit an event
24
24
  * @param {string} event - Event name
25
25
  * @param {*} data - Event data
26
26
  */
27
- emit(t, e) {
28
- this.listeners.has(t) && this.listeners.get(t).forEach((s) => {
27
+ emit(e, t) {
28
+ this.listeners.has(e) && this.listeners.get(e).forEach((s) => {
29
29
  try {
30
- s(e);
30
+ s(t);
31
31
  } catch (i) {
32
- console.error(`Error in listener for event '${t}':`, i);
32
+ console.error(`Error in listener for event '${e}':`, i);
33
33
  }
34
34
  });
35
35
  }
@@ -40,16 +40,17 @@ class M {
40
40
  this.listeners.clear();
41
41
  }
42
42
  }
43
- class E extends M {
44
- constructor(t, e = {}) {
45
- super(), this.dataService = t, this.player = null, this.achievements = { ...this._initAchievements(), ...e.achievements || {} };
43
+ class O extends C {
44
+ constructor(e, t = {}) {
45
+ super(), this.dataService = e, this.player = null, this.achievements = { ...this._initAchievements(), ...t.achievements || {} };
46
46
  }
47
47
  /**
48
48
  * Initialize the reward system for current player
49
49
  * @returns {Promise<Object>} Player data
50
50
  */
51
51
  async init() {
52
- return this.player = await this.dataService.load("player", this._getDefaultPlayer()), this._updateDailyStreak(), await this.save(), this.player;
52
+ const e = await this.dataService.load("player", null);
53
+ return this.player = this._migrate(e), this._updateDailyStreak(), await this.save(), this.player;
53
54
  }
54
55
  /**
55
56
  * Get default player data structure
@@ -74,6 +75,27 @@ class E extends M {
74
75
  }
75
76
  };
76
77
  }
78
+ /**
79
+ * Merge a persisted player record onto the current default shape.
80
+ *
81
+ * Saves written by older versions are missing whatever fields were added
82
+ * since. Without this the first property access on a new field throws
83
+ * before the game has drawn a frame.
84
+ *
85
+ * @param {Object|null} saved - Previously persisted player, if any
86
+ * @returns {Object} Player object guaranteed to have every field
87
+ * @private
88
+ */
89
+ _migrate(e) {
90
+ const t = this._getDefaultPlayer();
91
+ return !e || typeof e != "object" ? t : {
92
+ ...t,
93
+ ...e,
94
+ streak: { ...t.streak, ...e.streak || {} },
95
+ achievements: e.achievements || {},
96
+ stats: e.stats || {}
97
+ };
98
+ }
77
99
  /**
78
100
  * Save player data
79
101
  */
@@ -86,28 +108,30 @@ class E extends M {
86
108
  * @param {string} reason - Why XP was awarded (for notifications)
87
109
  * @returns {Object} { leveledUp: boolean, newLevel: number, xpGained: number }
88
110
  */
89
- async addXP(t, e = "") {
111
+ async addXP(e, t = "") {
112
+ if (!Number.isFinite(e))
113
+ throw new TypeError(`[DopamineJS] addXP expects a finite number, received ${e}`);
90
114
  const s = this.player.level;
91
- this.player.xp += t;
115
+ this.player.xp += e;
92
116
  const i = this._calculateLevel(this.player.xp), r = i > s;
93
- return r && (this.player.level = i), await this.save(), this.emit("xp_gained", { amount: t, reason: e, leveledUp: r, newLevel: i }), r && this.emit("level_up", { oldLevel: s, newLevel: i }), { leveledUp: r, newLevel: i, xpGained: t };
117
+ return r && (this.player.level = i), await this.save(), this.emit("xp_gained", { amount: e, reason: t, leveledUp: r, newLevel: i }), r && this.emit("level_up", { oldLevel: s, newLevel: i }), { leveledUp: r, newLevel: i, xpGained: e };
94
118
  }
95
119
  /**
96
120
  * Calculate level from XP (exponential curve)
97
121
  */
98
- _calculateLevel(t) {
99
- const e = Math.floor((1 + Math.sqrt(1 + 8 * t / 50)) / 2);
100
- return Math.max(1, e);
122
+ _calculateLevel(e) {
123
+ const t = Math.floor((1 + Math.sqrt(1 + 4 * e / 50)) / 2);
124
+ return Math.max(1, t);
101
125
  }
102
126
  /**
103
127
  * Get XP needed for next level
104
128
  */
105
129
  getXPForNextLevel() {
106
- const t = this.player.level + 1, e = 50 * t * (t - 1), s = 50 * this.player.level * (this.player.level - 1);
130
+ const e = this.player.level + 1, t = 50 * e * (e - 1), s = 50 * this.player.level * (this.player.level - 1), i = t - s, r = i > 0 ? Math.max(0, Math.min(1, (this.player.xp - s) / i)) : 0;
107
131
  return {
108
- total: e,
109
- needed: e - this.player.xp,
110
- progress: (this.player.xp - s) / (e - s)
132
+ total: t,
133
+ needed: Math.max(0, t - this.player.xp),
134
+ progress: r
111
135
  };
112
136
  }
113
137
  /**
@@ -115,87 +139,114 @@ class E extends M {
115
139
  * @param {string} gameName - Unique ID for the game
116
140
  * @param {Object} result - Game-specific result data
117
141
  */
118
- async recordGame(t, e) {
119
- this.player.stats[t] || (this.player.stats[t] = { totalPlays: 0, highScore: 0 });
120
- const s = this.player.stats[t];
121
- s.totalPlays++, this.player.totalGamesPlayed++, e.score !== void 0 && e.score > s.highScore && (s.highScore = e.score, this.emit("new_high_score", { gameName: t, score: e.score })), Object.keys(e).forEach((r) => {
122
- r !== "score" && (s[r] = (s[r] || 0) + (typeof e[r] == "number" ? e[r] : 0));
142
+ async recordGame(e, t) {
143
+ this.player.stats[e] || (this.player.stats[e] = { totalPlays: 0, highScore: 0 });
144
+ const s = this.player.stats[e];
145
+ s.totalPlays++, this.player.totalGamesPlayed++, t.score !== void 0 && t.score > s.highScore && (s.highScore = t.score, this.emit("new_high_score", { gameName: e, score: t.score })), Object.keys(t).forEach((r) => {
146
+ r !== "score" && (s[r] = (s[r] || 0) + (typeof t[r] == "number" ? t[r] : 0));
123
147
  });
124
148
  let i = 10;
125
- e.score && (i += Math.floor(e.score / 5)), i = Math.floor(i * this.getStreakMultiplier()), await this.addXP(i, `Played ${t}`), await this.checkAchievements(t, e), await this.save();
149
+ t.score && (i += Math.floor(t.score / 5)), i = Math.floor(i * this.getStreakMultiplier()), await this.addXP(i, `Played ${e}`), await this.checkAchievements(e, t), await this.save();
126
150
  }
127
151
  /**
128
152
  * Check and unlock achievements
129
153
  */
130
- async checkAchievements(t, e) {
154
+ async checkAchievements(e, t) {
131
155
  const s = [];
132
- for (const [i, r] of Object.entries(this.achievements))
133
- this.player.achievements[i] || r.check(this.player, t, e) && (await this.unlockAchievement(i), s.push(r));
156
+ for (const [i, r] of Object.entries(this.achievements)) {
157
+ if (this.player.achievements[i]) continue;
158
+ let n = !1;
159
+ try {
160
+ n = r.check(this.player, e, t);
161
+ } catch (o) {
162
+ console.error(`[DopamineJS] Achievement "${i}" check threw:`, o);
163
+ continue;
164
+ }
165
+ n && (await this.unlockAchievement(i), s.push(r));
166
+ }
134
167
  return s;
135
168
  }
136
169
  /**
137
170
  * Unlock an achievement
138
171
  */
139
- async unlockAchievement(t) {
140
- const e = this.achievements[t];
141
- return e ? (this.player.achievements[t] = {
172
+ async unlockAchievement(e) {
173
+ const t = this.achievements[e];
174
+ return t ? (this.player.achievements[e] = {
142
175
  unlockedAt: Date.now(),
143
176
  seen: !1
144
- }, await this.addXP(e.xp, `Achievement: ${e.name}`), this.emit("achievement_unlocked", e), await this.save(), !0) : !1;
177
+ }, await this.addXP(
178
+ Number.isFinite(t.xp) ? t.xp : 0,
179
+ `Achievement: ${t.name}`
180
+ ), this.emit("achievement_unlocked", t), await this.save(), !0) : !1;
145
181
  }
146
182
  /**
147
183
  * Get all unlocked achievements
148
184
  */
149
185
  getUnlockedAchievements() {
150
- return Object.entries(this.player.achievements).map(([t, e]) => ({
151
- ...this.achievements[t],
152
- id: t,
153
- ...e
186
+ return Object.entries(this.player.achievements).map(([e, t]) => ({
187
+ ...this.achievements[e],
188
+ id: e,
189
+ ...t
154
190
  }));
155
191
  }
156
192
  /**
157
193
  * Get achievements user hasn't seen the notification for yet
158
194
  */
159
195
  getUnseenAchievements() {
160
- return this.getUnlockedAchievements().filter((t) => !t.seen);
196
+ return this.getUnlockedAchievements().filter((e) => !e.seen);
161
197
  }
162
198
  /**
163
199
  * Mark achievements as seen
164
200
  */
165
- async markAchievementsSeen(t) {
166
- for (const e of t)
167
- this.player.achievements[e] && (this.player.achievements[e].seen = !0);
201
+ async markAchievementsSeen(e) {
202
+ for (const t of e)
203
+ this.player.achievements[t] && (this.player.achievements[t].seen = !0);
168
204
  await this.save();
169
205
  }
170
206
  /**
171
207
  * Update daily streak
172
208
  */
173
209
  _updateDailyStreak() {
174
- const t = this._getTodayDateString(), e = this.player.streak.lastPlayDate;
175
- if (e === t)
210
+ const e = this._getTodayDateString(), t = this.player.streak.lastPlayDate;
211
+ if (t === e)
176
212
  return;
177
213
  const s = this._getYesterdayDateString();
178
- e === s ? (this.player.streak.current++, this.player.streak.current > this.player.streak.longest && (this.player.streak.longest = this.player.streak.current)) : this.player.streak.current = 1, this.player.streak.lastPlayDate = t;
214
+ t === s ? (this.player.streak.current++, this.player.streak.current > this.player.streak.longest && (this.player.streak.longest = this.player.streak.current)) : this.player.streak.current = 1, this.player.streak.lastPlayDate = e;
179
215
  }
180
216
  /**
181
217
  * Get streak multiplier for XP
182
218
  */
183
219
  getStreakMultiplier() {
184
- const t = this.player.streak.current;
185
- return t >= 7 ? 2 : t >= 5 ? 1.5 : t >= 3 ? 1.25 : 1;
220
+ const e = this.player.streak.current;
221
+ return e >= 7 ? 2 : e >= 5 ? 1.5 : e >= 3 ? 1.25 : 1;
222
+ }
223
+ /**
224
+ * Format a Date as a YYYY-MM-DD string in the player's local timezone.
225
+ *
226
+ * Deliberately not toISOString(), which formats in UTC. For anyone east or
227
+ * west of Greenwich that shifts the calendar day for part of every day and
228
+ * makes streaks reset or double-count.
229
+ *
230
+ * @param {Date} date
231
+ * @returns {string}
232
+ * @private
233
+ */
234
+ _toLocalDateString(e) {
235
+ const t = e.getFullYear(), s = String(e.getMonth() + 1).padStart(2, "0"), i = String(e.getDate()).padStart(2, "0");
236
+ return `${t}-${s}-${i}`;
186
237
  }
187
238
  /**
188
- * Get today's date as YYYY-MM-DD string
239
+ * Get today's local date as YYYY-MM-DD string
189
240
  */
190
241
  _getTodayDateString() {
191
- return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
242
+ return this._toLocalDateString(/* @__PURE__ */ new Date());
192
243
  }
193
244
  /**
194
- * Get yesterday's date as YYYY-MM-DD string
245
+ * Get yesterday's local date as YYYY-MM-DD string
195
246
  */
196
247
  _getYesterdayDateString() {
197
- const t = /* @__PURE__ */ new Date();
198
- return t.setDate(t.getDate() - 1), t.toISOString().split("T")[0];
248
+ const e = /* @__PURE__ */ new Date();
249
+ return e.setDate(e.getDate() - 1), this._toLocalDateString(e);
199
250
  }
200
251
  /**
201
252
  * Initialize achievement definitions
@@ -209,52 +260,77 @@ class E extends M {
209
260
  description: "Play your first game",
210
261
  icon: "🎮",
211
262
  xp: 50,
212
- check: (t) => t.totalGamesPlayed >= 1
263
+ check: (e) => e.totalGamesPlayed >= 1
213
264
  },
214
265
  level_5: {
215
266
  name: "Rising Star",
216
267
  description: "Reach level 5",
217
268
  icon: "⭐",
218
269
  xp: 100,
219
- check: (t) => t.level >= 5
270
+ check: (e) => e.level >= 5
220
271
  },
221
272
  level_10: {
222
273
  name: "Expert Player",
223
274
  description: "Reach level 10",
224
275
  icon: "🏆",
225
276
  xp: 250,
226
- check: (t) => t.level >= 10
277
+ check: (e) => e.level >= 10
227
278
  },
228
279
  streak_3: {
229
280
  name: "Dedicated",
230
281
  description: "Play 3 days in a row",
231
282
  icon: "🔥",
232
283
  xp: 100,
233
- check: (t) => t.streak.current >= 3
284
+ check: (e) => e.streak.current >= 3
234
285
  },
235
286
  streak_7: {
236
287
  name: "Unstoppable",
237
288
  description: "Play 7 days in a row",
238
289
  icon: "💥",
239
290
  xp: 300,
240
- check: (t) => t.streak.current >= 7
291
+ check: (e) => e.streak.current >= 7
241
292
  }
242
293
  };
243
294
  }
244
295
  }
245
- class P {
246
- constructor(t = {}) {
247
- this.storage = t.storage || window.localStorage, this.prefix = t.prefix || "dopamine_";
296
+ function w() {
297
+ const a = /* @__PURE__ */ new Map();
298
+ return {
299
+ getItem: (e) => a.has(e) ? a.get(e) : null,
300
+ setItem: (e, t) => {
301
+ a.set(e, String(t));
302
+ },
303
+ removeItem: (e) => {
304
+ a.delete(e);
305
+ },
306
+ clear: () => {
307
+ a.clear();
308
+ }
309
+ };
310
+ }
311
+ function S() {
312
+ try {
313
+ if (typeof localStorage > "u")
314
+ return w();
315
+ const a = "__dopamine_probe__";
316
+ return localStorage.setItem(a, "1"), localStorage.removeItem(a), localStorage;
317
+ } catch {
318
+ return w();
319
+ }
320
+ }
321
+ class A {
322
+ constructor(e = {}) {
323
+ this.storage = e.storage || S(), this.prefix = e.prefix || "dopamine_";
248
324
  }
249
325
  /**
250
326
  * Save data to local storage
251
327
  * @param {string} key
252
328
  * @param {any} data
253
329
  */
254
- async save(t, e) {
330
+ async save(e, t) {
255
331
  try {
256
- const s = JSON.stringify(e);
257
- return this.storage.setItem(this.prefix + t, s), !0;
332
+ const s = JSON.stringify(t);
333
+ return this.storage.setItem(this.prefix + e, s), !0;
258
334
  } catch (s) {
259
335
  return console.error("Error saving data:", s), !1;
260
336
  }
@@ -264,240 +340,305 @@ class P {
264
340
  * @param {string} key
265
341
  * @param {any} defaultValue
266
342
  */
267
- async load(t, e = null) {
343
+ async load(e, t = null) {
268
344
  try {
269
- const s = this.storage.getItem(this.prefix + t);
270
- return s ? JSON.parse(s) : e;
345
+ const s = this.storage.getItem(this.prefix + e);
346
+ return s ? JSON.parse(s) : t;
271
347
  } catch (s) {
272
- return console.error("Error loading data:", s), e;
348
+ return console.error("Error loading data:", s), t;
273
349
  }
274
350
  }
275
351
  /**
276
352
  * Clear specific data
277
353
  * @param {string} key
278
354
  */
279
- async clear(t) {
280
- this.storage.removeItem(this.prefix + t);
355
+ async clear(e) {
356
+ this.storage.removeItem(this.prefix + e);
281
357
  }
282
358
  }
283
- class T {
284
- constructor(t) {
285
- this.particleSystem = t, this.container = null, this.xpBar = null, this.levelDisplay = null, this.streakDisplay = null, this.init();
359
+ const k = "game-ui-overlay";
360
+ class D {
361
+ constructor(e) {
362
+ this.particleSystem = e, this.container = null, this.xpBar = null, this.levelDisplay = null, this.streakDisplay = null, this._timers = /* @__PURE__ */ new Set(), this._detached = [], this.init();
286
363
  }
287
364
  /**
288
365
  * Initialize UI overlay
289
366
  */
290
367
  init() {
291
- this.container = document.createElement("div"), this.container.id = "game-ui-overlay", this.container.innerHTML = `
368
+ this.container = document.createElement("div"), this.container.className = "game-ui-overlay", document.getElementById(k) || (this.container.id = k), this.container.innerHTML = `
292
369
  <div class="game-ui-top-bar">
293
- <div class="level-badge" id="level-badge">
370
+ <div class="level-badge">
294
371
  <span class="level-label">LVL</span>
295
- <span class="level-number" id="level-number">1</span>
372
+ <span class="level-number">1</span>
296
373
  </div>
297
-
374
+
298
375
  <div class="xp-container">
299
376
  <div class="xp-bar-bg">
300
- <div class="xp-bar-fill" id="xp-bar-fill" style="width: 0%"></div>
377
+ <div class="xp-bar-fill" style="width: 0%"></div>
301
378
  </div>
302
- <div class="xp-text" id="xp-text">0 /100 XP</div>
379
+ <div class="xp-text">0 / 100 XP</div>
303
380
  </div>
304
-
305
- <div class="streak-badge" id="streak-badge">
381
+
382
+ <div class="streak-badge">
306
383
  <span class="streak-icon">🔥</span>
307
- <span class="streak-number" id="streak-number">1</span>
384
+ <span class="streak-number">1</span>
308
385
  </div>
309
386
  </div>
310
- `, document.body.appendChild(this.container), this.xpBar = document.getElementById("xp-bar-fill"), this.xpText = document.getElementById("xp-text"), this.levelDisplay = document.getElementById("level-number"), this.streakDisplay = document.getElementById("streak-number");
387
+ `, document.body.appendChild(this.container), this.levelBadge = this.container.querySelector(".level-badge"), this.streakBadge = this.container.querySelector(".streak-badge"), this.xpBar = this.container.querySelector(".xp-bar-fill"), this.xpText = this.container.querySelector(".xp-text"), this.levelDisplay = this.container.querySelector(".level-number"), this.streakDisplay = this.container.querySelector(".streak-number");
388
+ }
389
+ /**
390
+ * setTimeout that destroy() can cancel.
391
+ * @private
392
+ */
393
+ _defer(e, t) {
394
+ const s = setTimeout(() => {
395
+ this._timers.delete(s), e();
396
+ }, t);
397
+ return this._timers.add(s), s;
398
+ }
399
+ /**
400
+ * Append a transient element and remember it for cleanup.
401
+ * @private
402
+ */
403
+ _mount(e) {
404
+ return document.body.appendChild(e), this._detached.push(e), e;
405
+ }
406
+ /**
407
+ * Replay a CSS animation by clearing it for one tick.
408
+ * @private
409
+ */
410
+ _replayAnimation(e, t) {
411
+ e && (e.style.animation = "none", this._defer(() => {
412
+ e.style.animation = t;
413
+ }, 10));
311
414
  }
312
415
  /**
313
416
  * Update XP bar
417
+ * @param {number} current - XP held right now
418
+ * @param {number} needed - XP still required for the next level
419
+ * @param {number} total - XP total that marks the next level
314
420
  */
315
- updateXP(t, e, s) {
316
- const i = t / s * 100;
317
- this.xpBar.style.width = `${Math.min(100, i)}%`, this.xpText.textContent = `${t} / ${s} XP`, this.xpBar.style.animation = "none", setTimeout(() => {
318
- this.xpBar.style.animation = "xp-pulse 0.3s ease-out";
319
- }, 10);
421
+ updateXP(e, t, s) {
422
+ const i = s > 0 ? e / s * 100 : 0;
423
+ this.xpBar.style.width = `${Math.max(0, Math.min(100, i))}%`, this.xpText.textContent = `${e} / ${s} XP`, this._replayAnimation(this.xpBar, "xp-pulse 0.3s ease-out");
320
424
  }
321
425
  /**
322
426
  * Update level display
323
427
  */
324
- updateLevel(t) {
325
- this.levelDisplay.textContent = t;
326
- const e = document.getElementById("level-badge");
327
- e.style.animation = "none", setTimeout(() => {
328
- e.style.animation = "level-pulse 0.5s ease-out";
329
- }, 10);
428
+ updateLevel(e) {
429
+ this.levelDisplay.textContent = e, this._replayAnimation(this.levelBadge, "level-pulse 0.5s ease-out");
330
430
  }
331
431
  /**
332
432
  * Update streak display
333
433
  */
334
- updateStreak(t) {
335
- this.streakDisplay.textContent = t;
336
- const e = document.getElementById("streak-badge");
337
- t >= 7 ? e.style.background = "linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)" : t >= 3 && (e.style.background = "linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)");
434
+ updateStreak(e) {
435
+ this.streakDisplay.textContent = e, e >= 7 ? this.streakBadge.style.background = "linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)" : e >= 3 && (this.streakBadge.style.background = "linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)");
338
436
  }
339
437
  /**
340
438
  * Show achievement unlocked popup
341
- */
342
- showAchievement(t) {
343
- const e = document.createElement("div");
344
- e.className = "achievement-popup", e.innerHTML = `
439
+ *
440
+ * `icon` is rendered as HTML by design (documented in ARCHITECTURE.md, so
441
+ * games can pass an <img> or inline SVG). Every other field is game state
442
+ * and could carry a player-supplied or server-supplied string, so it goes
443
+ * in as text.
444
+ */
445
+ showAchievement(e) {
446
+ const t = document.createElement("div");
447
+ t.className = "achievement-popup", t.innerHTML = `
345
448
  <div class="achievement-shine"></div>
346
449
  <div class="achievement-content">
347
- <div class="achievement-icon">${t.icon}</div>
450
+ <div class="achievement-icon"></div>
348
451
  <div class="achievement-info">
349
452
  <div class="achievement-title">Achievement Unlocked!</div>
350
- <div class="achievement-name">${t.name}</div>
351
- <div class="achievement-desc">${t.description}</div>
352
- <div class="achievement-xp">+${t.xp} XP</div>
453
+ <div class="achievement-name"></div>
454
+ <div class="achievement-desc"></div>
455
+ <div class="achievement-xp"></div>
353
456
  </div>
354
457
  </div>
355
- `, document.body.appendChild(e), setTimeout(() => {
356
- const s = e.getBoundingClientRect();
458
+ `, t.querySelector(".achievement-icon").innerHTML = e.icon ?? "", t.querySelector(".achievement-name").textContent = e.name ?? "", t.querySelector(".achievement-desc").textContent = e.description ?? "", t.querySelector(".achievement-xp").textContent = `+${e.xp ?? 0} XP`, this._mount(t), this._defer(() => {
459
+ const s = t.getBoundingClientRect();
357
460
  this.particleSystem.confetti(s.left + s.width / 2, s.top + s.height / 2, 40);
358
- }, 300), setTimeout(() => {
359
- e.style.animation = "slideOut 0.3s ease-in forwards", setTimeout(() => e.remove(), 300);
461
+ }, 300), this._defer(() => {
462
+ t.style.animation = "slideOut 0.3s ease-in forwards", this._defer(() => this._remove(t), 300);
360
463
  }, 4e3);
361
464
  }
362
465
  /**
363
466
  * Show level up screen
364
467
  */
365
- showLevelUp(t, e) {
468
+ showLevelUp(e, t) {
366
469
  const s = document.createElement("div");
367
470
  s.className = "level-up-overlay", s.innerHTML = `
368
471
  <div class="level-up-content">
369
472
  <div class="level-up-title">LEVEL UP!</div>
370
- <div class="level-up-number">${e}</div>
473
+ <div class="level-up-number"></div>
371
474
  <div class="level-up-subtitle">Amazing progress!</div>
372
475
  </div>
373
- `, document.body.appendChild(s), setTimeout(() => {
476
+ `, s.querySelector(".level-up-number").textContent = t, this._mount(s), this._defer(() => {
374
477
  for (let i = 0; i < 5; i++)
375
- setTimeout(() => {
478
+ this._defer(() => {
376
479
  const r = Math.random() * window.innerWidth, n = Math.random() * window.innerHeight * 0.6;
377
480
  this.particleSystem.confetti(r, n, 30);
378
481
  }, i * 200);
379
- }, 300), setTimeout(() => {
380
- s.style.opacity = "0", setTimeout(() => s.remove(), 500);
482
+ }, 300), this._defer(() => {
483
+ s.style.opacity = "0", this._defer(() => this._remove(s), 500);
381
484
  }, 2500);
382
485
  }
383
486
  /**
384
487
  * Show floating text (e.g., "+50 XP", "COMBO!")
385
488
  */
386
- showFloatingText(t, e, s, i = "#ffd700", r = "24px") {
489
+ showFloatingText(e, t, s, i = "#ffd700", r = "24px") {
387
490
  const n = document.createElement("div");
388
- n.className = "floating-text", n.textContent = t, n.style.left = e + "px", n.style.top = s + "px", n.style.color = i, n.style.fontSize = r, document.body.appendChild(n), setTimeout(() => n.remove(), 2e3);
491
+ n.className = "floating-text", n.textContent = e, n.style.left = t + "px", n.style.top = s + "px", n.style.color = i, n.style.fontSize = r, this._mount(n), this._defer(() => this._remove(n), 2e3);
389
492
  }
390
493
  /**
391
494
  * Show combo multiplier
392
495
  */
393
- showCombo(t, e, s) {
394
- this.showFloatingText(`${t}× COMBO!`, e, s, "#ff6b6b", "32px"), this.particleSystem.fire(e, s, 20);
496
+ showCombo(e, t, s) {
497
+ this.showFloatingText(`${e}× COMBO!`, t, s, "#ff6b6b", "32px"), this.particleSystem.fire(t, s, 20);
395
498
  }
396
499
  /**
397
- * Show "near miss"indicator
500
+ * Show "near miss" indicator
398
501
  */
399
- showNearMiss(t, e) {
400
- this.showFloatingText("CLOSE CALL!", t, e, "#45b7d1", "20px"), this.particleSystem.sparkle(t, e, 15, "#45b7d1");
502
+ showNearMiss(e, t) {
503
+ this.showFloatingText("CLOSE CALL!", e, t, "#45b7d1", "20px"), this.particleSystem.sparkle(e, t, 15, "#45b7d1");
401
504
  }
402
505
  /**
403
506
  * Show lucky moment (random multiplier)
404
507
  */
405
- showLuckyMoment(t, e, s) {
406
- this.showFloatingText(`LUCKY ${t}×!`, e, s, "#ffd700", "36px"), this.particleSystem.starBurst(e, s, 12), this.particleSystem.confetti(e, s, 25);
508
+ showLuckyMoment(e, t, s) {
509
+ this.showFloatingText(`LUCKY ${e}×!`, t, s, "#ffd700", "36px"), this.particleSystem.starBurst(t, s, 12), this.particleSystem.confetti(t, s, 25);
407
510
  }
408
511
  /**
409
512
  * Show generic notification
410
513
  */
411
- showNotification(t, e = "normal") {
514
+ showNotification(e, t = "normal") {
412
515
  const s = window.innerWidth / 2, i = window.innerHeight / 3;
413
- e === "legendary" ? this.showLuckyMoment(t, s, i) : e === "rare" ? this.showCombo(t, s, i) : this.showFloatingText(t, s, i, "#fff", "24px");
516
+ t === "legendary" ? this.showLuckyMoment(e, s, i) : t === "rare" ? this.showCombo(e, s, i) : this.showFloatingText(e, s, i, "#fff", "24px");
414
517
  }
415
518
  /**
416
519
  * Show Game Over Summary
520
+ *
521
+ * @param {Object} data
522
+ * @param {number|string} data.score - Final score
523
+ * @param {Object} [data.metrics] - Label/value pairs listed under the score
524
+ * @param {Function} [data.onReplay] - Replay button handler (default: reload)
525
+ * @param {Function} [data.onExit] - Exit button handler (button hidden if omitted)
526
+ */
527
+ showSummary(e) {
528
+ const t = document.createElement("div");
529
+ t.className = "message-box", t.style.display = "flex", t.style.position = "absolute", t.style.top = "50%", t.style.left = "50%", t.style.transform = "translate(-50%, -50%)", t.style.zIndex = "1000";
530
+ const s = document.createElement("h1");
531
+ s.textContent = "Game Over";
532
+ const i = document.createElement("div");
533
+ i.className = "summary-score", i.style.cssText = "font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;", i.textContent = e.score;
534
+ const r = document.createElement("div");
535
+ r.className = "summary-metrics", r.style.cssText = "width: 100%; margin-bottom: 20px;";
536
+ for (const [o, c] of Object.entries(e.metrics || {})) {
537
+ const h = document.createElement("div");
538
+ h.style.cssText = "display:flex;justify-content:space-between;width:100%";
539
+ const l = document.createElement("span");
540
+ l.textContent = `${o}:`;
541
+ const u = document.createElement("strong");
542
+ u.textContent = c, h.append(l, u), r.appendChild(h);
543
+ }
544
+ t.append(s, i, r);
545
+ const n = document.createElement("button");
546
+ if (n.className = "btn", n.textContent = "Play Again", n.addEventListener("click", e.onReplay || (() => window.location.reload())), t.appendChild(n), e.onExit) {
547
+ const o = document.createElement("button");
548
+ o.className = "btn", o.textContent = "Exit", o.style.cssText = "margin-top: 10px; background: #543847;", o.addEventListener("click", e.onExit), t.appendChild(o);
549
+ }
550
+ return this._mount(t), t;
551
+ }
552
+ /**
553
+ * @private
417
554
  */
418
- showSummary(t) {
419
- const e = document.createElement("div");
420
- e.className = "message-box", e.style.display = "flex", e.style.position = "absolute", e.style.top = "50%", e.style.left = "50%", e.style.transform = "translate(-50%, -50%)", e.style.zIndex = "1000";
421
- let s = "";
422
- t.metrics && (s = Object.entries(t.metrics).map(([i, r]) => `<div style="display:flex;justify-content:space-between;width:100%"><span>${i}:</span> <strong>${r}</strong></div>`).join("")), e.innerHTML = `
423
- <h1>Game Over</h1>
424
- <div style="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;">${t.score}</div>
425
- <div style="width: 100%; margin-bottom: 20px;">
426
- ${s}
427
- </div>
428
- <button class="btn" onclick="location.reload()">Play Again</button>
429
- <button class="btn" onclick="window.location.href='index.html'" style="margin-top: 10px; background: #543847;">Exit</button>
430
- `, document.body.appendChild(e);
555
+ _remove(e) {
556
+ e.remove();
557
+ const t = this._detached.indexOf(e);
558
+ t > -1 && this._detached.splice(t, 1);
559
+ }
560
+ /**
561
+ * Tear down the overlay, any transient popups, and all pending timers.
562
+ */
563
+ destroy() {
564
+ var e;
565
+ for (const t of this._timers)
566
+ clearTimeout(t);
567
+ this._timers.clear();
568
+ for (const t of this._detached)
569
+ t.remove();
570
+ this._detached = [], (e = this.container) == null || e.remove(), this.container = null, this.xpBar = null, this.xpText = null, this.levelBadge = null, this.streakBadge = null, this.levelDisplay = null, this.streakDisplay = null;
431
571
  }
432
572
  }
433
- class C {
434
- constructor(t = {}) {
435
- this.container = t.container ? typeof t.container == "string" ? document.querySelector(t.container) : t.container : document.body, this.canvasId = t.canvasId || "dopamine-particle-canvas", this.canvas = document.getElementById(this.canvasId), this.canvas || (this.canvas = document.createElement("canvas"), this.canvas.id = this.canvasId, this.canvas.style.pointerEvents = "none", this.canvas.style.zIndex = t.zIndex || "9999", this.container === document.body ? (this.canvas.style.position = "fixed", this.canvas.style.top = "0", this.canvas.style.left = "0") : (this.canvas.style.position = "absolute", this.canvas.style.top = "0", this.canvas.style.left = "0", window.getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative")), this.container.appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d"), this.particles = [], this.pool = [], this.sprites = /* @__PURE__ */ new Map(), this.customEffects = /* @__PURE__ */ new Map(), this.isAnimating = !1, this._resize(), window.ResizeObserver && this.container !== document.body ? new ResizeObserver(() => this._resize()).observe(this.container) : window.addEventListener("resize", () => this._resize());
573
+ class T {
574
+ constructor(e = {}) {
575
+ this.container = e.container ? typeof e.container == "string" ? document.querySelector(e.container) : e.container : document.body, this.canvasId = e.canvasId || "dopamine-particle-canvas", this.canvas = document.createElement("canvas"), this.canvas.classList.add("dopamine-particle-canvas"), document.getElementById(this.canvasId) || (this.canvas.id = this.canvasId), this.canvas.style.pointerEvents = "none", this.canvas.style.zIndex = e.zIndex || "9999", this.container === document.body ? (this.canvas.style.position = "fixed", this.canvas.style.top = "0", this.canvas.style.left = "0") : (this.canvas.style.position = "absolute", this.canvas.style.top = "0", this.canvas.style.left = "0", window.getComputedStyle(this.container).position === "static" && (this.container.style.position = "relative")), this.container.appendChild(this.canvas), this.ctx = this.canvas.getContext("2d"), this.particles = [], this.pool = [], this.sprites = /* @__PURE__ */ new Map(), this.customEffects = /* @__PURE__ */ new Map(), this.isAnimating = !1, this._onResize = () => this._resize(), this._resizeObserver = null, this._frameHandle = null, this._resize(), window.ResizeObserver && this.container !== document.body ? (this._resizeObserver = new ResizeObserver(this._onResize), this._resizeObserver.observe(this.container)) : window.addEventListener("resize", this._onResize);
436
576
  }
437
577
  _resize() {
438
- this.container === document.body ? (this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight) : (this.canvas.width = this.container.clientWidth, this.canvas.height = this.container.clientHeight);
578
+ const e = this.container === document.body ? window.innerWidth : this.container.clientWidth, t = this.container === document.body ? window.innerHeight : this.container.clientHeight, s = window.devicePixelRatio || 1;
579
+ this.width = e, this.height = t, this.canvas.width = Math.round(e * s), this.canvas.height = Math.round(t * s), this.canvas.style.width = `${e}px`, this.canvas.style.height = `${t}px`, this.ctx.setTransform(s, 0, 0, s, 0, 0);
439
580
  }
440
581
  /**
441
582
  * Register a custom sprite image
442
583
  * @param {string} key
443
584
  * @param {string} url
444
585
  */
445
- registerSprite(t, e) {
586
+ registerSprite(e, t) {
446
587
  const s = new Image();
447
- s.src = e, this.sprites.set(t, s);
588
+ s.src = t, this.sprites.set(e, s);
448
589
  }
449
590
  /**
450
591
  * Register a custom effect
451
592
  * @param {string} name
452
593
  * @param {Function} callback - (x, y, ...args) => void
453
594
  */
454
- registerEffect(t, e) {
455
- this.customEffects.set(t, e);
595
+ registerEffect(e, t) {
596
+ this.customEffects.set(e, t);
456
597
  }
457
598
  /**
458
599
  * Play a registered effect
459
600
  */
460
- play(t, e, s, ...i) {
461
- const r = this.customEffects.get(t);
462
- r ? r(e, s, ...i) : console.warn(`[DopamineJS] Effect '${t}' not found.`);
601
+ play(e, t, s, ...i) {
602
+ const r = this.customEffects.get(e);
603
+ r ? r(t, s, ...i) : console.warn(`[DopamineJS] Effect '${e}' not found.`);
463
604
  }
464
605
  /**
465
606
  * Emit particles with custom config
466
607
  * @param {Object} config
467
608
  */
468
- emit(t) {
609
+ emit(e) {
469
610
  const {
470
- x: e,
611
+ x: t,
471
612
  y: s,
472
613
  count: i = 10,
473
614
  speed: r = 5,
474
615
  gravity: n = 0.1,
475
616
  decay: o = 0.02,
476
- life: h = 1,
477
- color: c = "#ffd700",
478
- sprite: d = null,
617
+ life: c = 1,
618
+ color: h = "#ffd700",
619
+ sprite: l = null,
479
620
  spread: u = Math.PI * 2,
480
621
  angle: m = 0,
481
622
  size: f = 5
482
- } = t;
623
+ } = e;
483
624
  for (let v = 0; v < i; v++) {
484
- const l = this._getParticle(), x = m + (Math.random() - 0.5) * u, w = Math.random() * r;
485
- l.x = e, l.y = s, l.vx = Math.cos(x) * w, l.vy = Math.sin(x) * w, l.life = h, l.decay = o * (0.8 + Math.random() * 0.4), l.gravity = n, l.color = Array.isArray(c) ? c[Math.floor(Math.random() * c.length)] : c, l.size = f * (0.8 + Math.random() * 0.4), l.sprite = d, l.rotation = Math.random() * Math.PI * 2, l.rotationSpeed = (Math.random() - 0.5) * 0.2, this.particles.push(l);
625
+ const d = this._getParticle(), _ = m + (Math.random() - 0.5) * u, x = Math.random() * r;
626
+ d.x = t, d.y = s, d.vx = Math.cos(_) * x, d.vy = Math.sin(_) * x, d.life = c, d.decay = o * (0.8 + Math.random() * 0.4), d.gravity = n, d.color = Array.isArray(h) ? h[Math.floor(Math.random() * h.length)] : h, d.size = f * (0.8 + Math.random() * 0.4), d.sprite = l, d.rotation = Math.random() * Math.PI * 2, d.rotationSpeed = (Math.random() - 0.5) * 0.2, this.particles.push(d);
486
627
  }
487
628
  this._startAnimation();
488
629
  }
489
630
  _getParticle() {
490
631
  return this.pool.length > 0 ? this.pool.pop() : {};
491
632
  }
492
- _recycleParticle(t) {
493
- this.pool.push(t);
633
+ _recycleParticle(e) {
634
+ this.pool.push(e);
494
635
  }
495
636
  // --- Built-in Effects ---
496
- confetti(t, e, s = 50) {
637
+ confetti(e, t, s = 50) {
497
638
  const i = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#f7dc6f", "#c06c84", "#6c5ce7"];
498
639
  this.emit({
499
- x: t,
500
- y: e,
640
+ x: e,
641
+ y: t,
501
642
  count: s,
502
643
  color: i,
503
644
  speed: 10,
@@ -507,10 +648,10 @@ class C {
507
648
  spread: Math.PI * 2
508
649
  });
509
650
  }
510
- coinShower(t, e, s = 10) {
651
+ coinShower(e, t, s = 10) {
511
652
  this.emit({
512
- x: t,
513
- y: e,
653
+ x: e,
654
+ y: t,
514
655
  count: s,
515
656
  color: "#ffd700",
516
657
  speed: 4,
@@ -522,10 +663,10 @@ class C {
522
663
  spread: Math.PI / 2
523
664
  });
524
665
  }
525
- sparkle(t, e, s = 20, i = "#ffd700") {
666
+ sparkle(e, t, s = 20, i = "#ffd700") {
526
667
  this.emit({
527
- x: t,
528
- y: e,
668
+ x: e,
669
+ y: t,
529
670
  count: s,
530
671
  color: i,
531
672
  speed: 3,
@@ -535,11 +676,11 @@ class C {
535
676
  size: 3
536
677
  });
537
678
  }
538
- fire(t, e, s = 15) {
679
+ fire(e, t, s = 15) {
539
680
  const i = ["#ff6b00", "#ff8800", "#ffaa00", "#ff4400"];
540
681
  this.emit({
541
- x: t,
542
- y: e,
682
+ x: e,
683
+ y: t,
543
684
  count: s,
544
685
  color: i,
545
686
  speed: 3,
@@ -550,10 +691,10 @@ class C {
550
691
  spread: Math.PI / 4
551
692
  });
552
693
  }
553
- starBurst(t, e, s = 8) {
694
+ starBurst(e, t, s = 8) {
554
695
  for (let i = 0; i < s; i++) {
555
696
  const r = this._getParticle(), n = Math.PI * 2 / s * i, o = 4;
556
- r.x = t, r.y = e, r.vx = Math.cos(n) * o, r.vy = Math.sin(n) * o, r.life = 1, r.decay = 0.03, r.gravity = 0, r.color = "#ffffff", r.size = 15, r.sprite = null, r.type = "star", r.rotation = n, r.rotationSpeed = 0, this.particles.push(r);
697
+ r.x = e, r.y = t, r.vx = Math.cos(n) * o, r.vy = Math.sin(n) * o, r.life = 1, r.decay = 0.03, r.gravity = 0, r.color = "#ffffff", r.size = 15, r.sprite = null, r.type = "star", r.rotation = n, r.rotationSpeed = 0, this.particles.push(r);
557
698
  }
558
699
  this._startAnimation();
559
700
  }
@@ -561,98 +702,129 @@ class C {
561
702
  this.isAnimating || (this.isAnimating = !0, this._animate());
562
703
  }
563
704
  _animate() {
564
- this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
565
- for (let t = this.particles.length - 1; t >= 0; t--) {
566
- const e = this.particles[t];
567
- if (e.vy += e.gravity, e.x += e.vx, e.y += e.vy, e.life -= e.decay, e.rotationSpeed && (e.rotation += e.rotationSpeed), e.life <= 0) {
568
- this._recycleParticle(e), this.particles.splice(t, 1);
705
+ this._frameHandle = null, this.ctx.clearRect(0, 0, this.width, this.height);
706
+ for (let e = this.particles.length - 1; e >= 0; e--) {
707
+ const t = this.particles[e];
708
+ if (t.vy += t.gravity, t.x += t.vx, t.y += t.vy, t.life -= t.decay, t.rotationSpeed && (t.rotation += t.rotationSpeed), t.life <= 0) {
709
+ this._recycleParticle(t), this.particles.splice(e, 1);
569
710
  continue;
570
711
  }
571
- if (this.ctx.save(), this.ctx.globalAlpha = e.life, this.ctx.translate(e.x, e.y), this.ctx.rotate(e.rotation), e.sprite && this.sprites.has(e.sprite)) {
572
- const s = this.sprites.get(e.sprite);
573
- s.complete && this.ctx.drawImage(s, -e.size / 2, -e.size / 2, e.size, e.size);
574
- } else e.type === "star" ? (this.ctx.fillStyle = e.color, this._drawStarPath(0, 0, 5, e.size, e.size / 2), this.ctx.fill()) : (this.ctx.fillStyle = e.color, this.ctx.beginPath(), this.ctx.arc(0, 0, e.size, 0, Math.PI * 2), this.ctx.fill());
712
+ if (this.ctx.save(), this.ctx.globalAlpha = t.life, this.ctx.translate(t.x, t.y), this.ctx.rotate(t.rotation), t.sprite && this.sprites.has(t.sprite)) {
713
+ const s = this.sprites.get(t.sprite);
714
+ s.complete && this.ctx.drawImage(s, -t.size / 2, -t.size / 2, t.size, t.size);
715
+ } else t.type === "star" ? (this.ctx.fillStyle = t.color, this._drawStarPath(0, 0, 5, t.size, t.size / 2), this.ctx.fill()) : (this.ctx.fillStyle = t.color, this.ctx.beginPath(), this.ctx.arc(0, 0, t.size, 0, Math.PI * 2), this.ctx.fill());
575
716
  this.ctx.restore();
576
717
  }
577
- this.particles.length > 0 ? requestAnimationFrame(() => this._animate()) : this.isAnimating = !1;
718
+ this.particles.length > 0 ? this._frameHandle = requestAnimationFrame(() => this._animate()) : this.isAnimating = !1;
578
719
  }
579
- _drawStarPath(t, e, s, i, r) {
580
- let n = Math.PI / 2 * 3, o = t, h = e;
581
- const c = Math.PI / s;
582
- this.ctx.beginPath(), this.ctx.moveTo(t, e - i);
583
- for (let d = 0; d < s; d++)
584
- o = t + Math.cos(n) * i, h = e + Math.sin(n) * i, this.ctx.lineTo(o, h), n += c, o = t + Math.cos(n) * r, h = e + Math.sin(n) * r, this.ctx.lineTo(o, h), n += c;
585
- this.ctx.lineTo(t, e - i), this.ctx.closePath();
720
+ _drawStarPath(e, t, s, i, r) {
721
+ let n = Math.PI / 2 * 3, o = e, c = t;
722
+ const h = Math.PI / s;
723
+ this.ctx.beginPath(), this.ctx.moveTo(e, t - i);
724
+ for (let l = 0; l < s; l++)
725
+ o = e + Math.cos(n) * i, c = t + Math.sin(n) * i, this.ctx.lineTo(o, c), n += h, o = e + Math.cos(n) * r, c = t + Math.sin(n) * r, this.ctx.lineTo(o, c), n += h;
726
+ this.ctx.lineTo(e, t - i), this.ctx.closePath();
586
727
  }
587
728
  clear() {
588
- this.particles.forEach((t) => this._recycleParticle(t)), this.particles = [], this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
729
+ this.particles.forEach((e) => this._recycleParticle(e)), this.particles = [], this.ctx.clearRect(0, 0, this.width, this.height);
730
+ }
731
+ /**
732
+ * Stop animating, detach resize handling, and remove the canvas.
733
+ *
734
+ * Without this the resize listener and canvas outlive the instance, which
735
+ * matters in any single-page app that mounts and unmounts a game view.
736
+ */
737
+ destroy() {
738
+ this.clear(), this.isAnimating = !1, this._frameHandle !== null && (cancelAnimationFrame(this._frameHandle), this._frameHandle = null), this._resizeObserver && (this._resizeObserver.disconnect(), this._resizeObserver = null), window.removeEventListener("resize", this._onResize), this.canvas.remove(), this.sprites.clear(), this.customEffects.clear(), this.pool = [];
589
739
  }
590
740
  }
591
741
  class I {
592
- constructor(t = {}) {
593
- this.audioContext = null, this.storageKey = t.storageKey || "dopamineSoundsMuted", this.muted = localStorage.getItem(this.storageKey) === "true", this.assets = /* @__PURE__ */ new Map(), this.customSounds = t.customSounds || {}, Object.keys(this.customSounds).length > 0 && this.preloadSounds(this.customSounds);
742
+ constructor(e = {}) {
743
+ this.audioContext = null, this.storageKey = e.storageKey || "dopamineSoundsMuted", this.storage = e.storage || S(), this.muted = this.storage.getItem(this.storageKey) === "true", this.assets = /* @__PURE__ */ new Map(), this.customSounds = e.customSounds || {}, this.masterGain = null, this._volume = e.volume ?? 1, Object.keys(this.customSounds).length > 0 && this.preloadSounds(this.customSounds);
594
744
  }
595
745
  /**
596
746
  * Initialize Audio Context (must be called after user interaction)
597
747
  */
598
748
  initAudio() {
599
749
  if (!this.audioContext) {
600
- const t = window.AudioContext || window.webkitAudioContext;
601
- this.audioContext = new t();
750
+ const e = typeof window < "u" ? window.AudioContext || window.webkitAudioContext : void 0;
751
+ if (!e) return !1;
752
+ this.audioContext = new e(), this.masterGain = this.audioContext.createGain(), this.masterGain.gain.value = this._volume, this.masterGain.connect(this.audioContext.destination);
602
753
  }
603
- this.audioContext.state === "suspended" && this.audioContext.resume();
754
+ return this.audioContext.state === "suspended" && this.audioContext.resume(), !0;
755
+ }
756
+ /**
757
+ * Set master output volume.
758
+ * @param {number} value - 0..1
759
+ */
760
+ setVolume(e) {
761
+ return this._volume = Math.max(0, Math.min(1, e)), this.masterGain && (this.masterGain.gain.value = this._volume), this._volume;
762
+ }
763
+ /**
764
+ * Current master output volume.
765
+ * @returns {number}
766
+ */
767
+ getVolume() {
768
+ return this._volume;
769
+ }
770
+ /**
771
+ * Node every sound routes through, so volume and mute apply uniformly.
772
+ * @private
773
+ */
774
+ _output() {
775
+ return this.masterGain || this.audioContext.destination;
604
776
  }
605
777
  /**
606
778
  * Toggle mute state
607
779
  * @returns {boolean} New mute state
608
780
  */
609
781
  toggleMute() {
610
- return this.muted = !this.muted, localStorage.setItem(this.storageKey, this.muted), this.muted;
782
+ return this.muted = !this.muted, this.storage.setItem(this.storageKey, this.muted), this.muted;
611
783
  }
612
784
  /**
613
785
  * Register a custom sound URL
614
786
  * @param {string} key - Unique identifier (e.g., 'jump', 'win')
615
787
  * @param {string} url - Path to audio file
616
788
  */
617
- registerSound(t, e) {
618
- this.customSounds[t] = e, this.audioContext && this.loadSound(t, e);
789
+ registerSound(e, t) {
790
+ this.customSounds[e] = t, this.audioContext && this.loadSound(e, t);
619
791
  }
620
792
  /**
621
793
  * Preload multiple sounds
622
794
  * @param {Object} soundMap - { key: url }
623
795
  */
624
- async preloadSounds(t) {
625
- const e = Object.entries(t).map(([s, i]) => this.loadSound(s, i));
626
- await Promise.allSettled(e);
796
+ async preloadSounds(e) {
797
+ const t = Object.entries(e).map(([s, i]) => this.loadSound(s, i));
798
+ await Promise.allSettled(t);
627
799
  }
628
800
  /**
629
801
  * Load and decode a sound file
630
802
  */
631
- async loadSound(t, e) {
803
+ async loadSound(e, t) {
632
804
  try {
633
805
  if (!this.audioContext)
634
806
  return;
635
- const i = await (await fetch(e)).arrayBuffer(), r = await this.audioContext.decodeAudioData(i);
636
- this.assets.set(t, r);
807
+ const i = await (await fetch(t)).arrayBuffer(), r = await this.audioContext.decodeAudioData(i);
808
+ this.assets.set(e, r);
637
809
  } catch (s) {
638
- console.warn(`[DopamineJS] Failed to load sound '${t}':`, s);
810
+ console.warn(`[DopamineJS] Failed to load sound '${e}':`, s);
639
811
  }
640
812
  }
641
813
  /**
642
814
  * Play a sound by key (custom) or fallback to synth
643
815
  * @param {string} key - 'jump', 'score', 'success', etc.
644
816
  */
645
- async play(t) {
646
- if (this.muted) return;
647
- if (this.initAudio(), this.assets.has(t)) {
648
- this._playBuffer(this.assets.get(t));
817
+ async play(e) {
818
+ if (this.muted || !this.initAudio()) return;
819
+ if (this.assets.has(e)) {
820
+ this._playBuffer(this.assets.get(e));
649
821
  return;
650
822
  }
651
- if (this.customSounds[t] && !this.assets.has(t) && (await this.loadSound(t, this.customSounds[t]), this.assets.has(t))) {
652
- this._playBuffer(this.assets.get(t));
823
+ if (this.customSounds[e] && !this.assets.has(e) && (await this.loadSound(e, this.customSounds[e]), this.assets.has(e))) {
824
+ this._playBuffer(this.assets.get(e));
653
825
  return;
654
826
  }
655
- const e = {
827
+ const t = {
656
828
  jump: () => this.playJump(!0),
657
829
  score: () => this.playScore(!0),
658
830
  success: () => this.playSuccess(!0),
@@ -660,53 +832,54 @@ class I {
660
832
  error: () => this.playError(!0),
661
833
  gameover: () => this.playGameOver(!0)
662
834
  };
663
- e[t] && e[t]();
835
+ t[e] && t[e]();
664
836
  }
665
- _playBuffer(t) {
666
- const e = this.audioContext.createBufferSource();
667
- e.buffer = t, e.connect(this.audioContext.destination), e.start(0);
837
+ _playBuffer(e) {
838
+ const t = this.audioContext.createBufferSource();
839
+ t.buffer = e, t.connect(this._output()), t.start(0);
668
840
  }
669
841
  /**
670
842
  * Play a synthesized tone
671
843
  */
672
- playTone(t, e, s = "sine", i = 0.3) {
673
- if (this.muted) return;
674
- this.initAudio();
844
+ playTone(e, t, s = "sine", i = 0.3) {
845
+ if (this.muted || !this.initAudio()) return;
675
846
  const r = this.audioContext.createOscillator(), n = this.audioContext.createGain();
676
- r.connect(n), n.connect(this.audioContext.destination), r.frequency.value = t, r.type = s, n.gain.setValueAtTime(i, this.audioContext.currentTime), n.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + e), r.start(this.audioContext.currentTime), r.stop(this.audioContext.currentTime + e);
847
+ r.connect(n), n.connect(this._output()), r.frequency.value = e, r.type = s;
848
+ const o = Math.max(1e-4, i);
849
+ n.gain.setValueAtTime(o, this.audioContext.currentTime), n.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + t), r.start(this.audioContext.currentTime), r.stop(this.audioContext.currentTime + t);
677
850
  }
678
851
  // --- Presets (Synth) ---
679
- playJump(t = !1) {
680
- if (!t && this.assets.has("jump")) return this.play("jump");
852
+ playJump(e = !1) {
853
+ if (!e && this.assets.has("jump")) return this.play("jump");
681
854
  this.playTone(400, 0.1, "square", 0.2);
682
855
  }
683
- playScore(t = !1) {
684
- if (!t && this.assets.has("score")) return this.play("score");
856
+ playScore(e = !1) {
857
+ if (!e && this.assets.has("score")) return this.play("score");
685
858
  this.playTone(800, 0.15, "sine", 0.25), setTimeout(() => this.playTone(1e3, 0.15, "sine", 0.25), 50);
686
859
  }
687
- playGameOver(t = !1) {
688
- if (!t && this.assets.has("gameover")) return this.play("gameover");
860
+ playGameOver(e = !1) {
861
+ if (!e && this.assets.has("gameover")) return this.play("gameover");
689
862
  this.playTone(300, 0.2, "sawtooth", 0.3), setTimeout(() => this.playTone(200, 0.2, "sawtooth", 0.3), 100), setTimeout(() => this.playTone(150, 0.3, "sawtooth", 0.3), 200);
690
863
  }
691
- playClick(t = !1) {
692
- if (!t && this.assets.has("click")) return this.play("click");
864
+ playClick(e = !1) {
865
+ if (!e && this.assets.has("click")) return this.play("click");
693
866
  this.playTone(600, 0.05, "sine", 0.1);
694
867
  }
695
- playSuccess(t = !1) {
696
- if (!t && this.assets.has("success")) return this.play("success");
868
+ playSuccess(e = !1) {
869
+ if (!e && this.assets.has("success")) return this.play("success");
697
870
  this.playTone(600, 0.1, "sine", 0.2), setTimeout(() => this.playTone(800, 0.2, "sine", 0.2), 100);
698
871
  }
699
- playError(t = !1) {
700
- if (!t && this.assets.has("error")) return this.play("error");
872
+ playError(e = !1) {
873
+ if (!e && this.assets.has("error")) return this.play("error");
701
874
  this.playTone(200, 0.2, "sawtooth", 0.3);
702
875
  }
703
876
  }
704
- class $ {
705
- constructor(t = {}) {
706
- this.config = t, this.dataService = new P(t.data || {}), this.rewardSystem = new E(this.dataService, t.rewards || {}), this.soundManager = new I(t.sound || {}), this.particleSystem = new C(t.particles || {}), this.gameUI = new T(this.particleSystem), this._bindEvents();
877
+ class N {
878
+ constructor(e = {}) {
879
+ this.config = e, this.dataService = new A(e.data || {}), this.rewardSystem = new O(this.dataService, e.rewards || {}), this.soundManager = new I(e.sound || {}), this.particleSystem = new T(e.particles || {}), this.gameUI = new D(this.particleSystem), this._bindEvents();
707
880
  }
708
881
  async init() {
709
- return await this.rewardSystem.init(), console.log("DopamineJS Initialized 🚀"), {
882
+ return await this.rewardSystem.init(), {
710
883
  rewardSystem: this.rewardSystem,
711
884
  gameUI: this.gameUI,
712
885
  particleSystem: this.particleSystem,
@@ -714,15 +887,15 @@ class $ {
714
887
  };
715
888
  }
716
889
  _bindEvents() {
717
- this.rewardSystem.on("xp_gained", (t) => {
718
- const { total: e, needed: s } = this.rewardSystem.getXPForNextLevel();
719
- this.gameUI.updateXP(this.rewardSystem.player.xp, s, e), t.xpGained > 0;
720
- }), this.rewardSystem.on("level_up", (t) => {
721
- this.gameUI.updateLevel(t.newLevel), this.gameUI.showLevelUp(t.oldLevel, t.newLevel);
722
- }), this.rewardSystem.on("achievement_unlocked", (t) => {
723
- this.gameUI.showAchievement(t);
724
- }), this.rewardSystem.on("new_high_score", (t) => {
725
- this.gameUI.showNotification(`New High Score: ${t.score}!`, "legendary"), this.soundManager.playSuccess();
890
+ this.rewardSystem.on("xp_gained", (e) => {
891
+ const { total: t, needed: s } = this.rewardSystem.getXPForNextLevel();
892
+ this.gameUI.updateXP(this.rewardSystem.player.xp, s, t), e.xpGained > 0;
893
+ }), this.rewardSystem.on("level_up", (e) => {
894
+ this.gameUI.updateLevel(e.newLevel), this.gameUI.showLevelUp(e.oldLevel, e.newLevel);
895
+ }), this.rewardSystem.on("achievement_unlocked", (e) => {
896
+ this.gameUI.showAchievement(e);
897
+ }), this.rewardSystem.on("new_high_score", (e) => {
898
+ this.gameUI.showNotification(`New High Score: ${e.score}!`, "legendary"), this.soundManager.playSuccess();
726
899
  });
727
900
  }
728
901
  }
@@ -737,8 +910,8 @@ class p {
737
910
  * @param {number} priority - Higher priority = called first (default: 0)
738
911
  * @returns {EventBus} - For chaining
739
912
  */
740
- on(t, e, s = 0) {
741
- return this._listeners.has(t) || this._listeners.set(t, /* @__PURE__ */ new Set()), this._listeners.get(t).add({ callback: e, priority: s }), this._sortedCache.delete(t), this;
913
+ on(e, t, s = 0) {
914
+ return this._listeners.has(e) || this._listeners.set(e, /* @__PURE__ */ new Set()), this._listeners.get(e).add({ callback: t, priority: s }), this._sortedCache.delete(e), this;
742
915
  }
743
916
  /**
744
917
  * Register a one-time event listener
@@ -746,8 +919,8 @@ class p {
746
919
  * @param {Function} callback - Callback function
747
920
  * @returns {EventBus} - For chaining
748
921
  */
749
- once(t, e) {
750
- return this._onceListeners.has(t) || this._onceListeners.set(t, /* @__PURE__ */ new Set()), this._onceListeners.get(t).add(e), this;
922
+ once(e, t) {
923
+ return this._onceListeners.has(e) || this._onceListeners.set(e, /* @__PURE__ */ new Set()), this._onceListeners.get(e).add(t), this;
751
924
  }
752
925
  /**
753
926
  * Remove an event listener
@@ -755,62 +928,74 @@ class p {
755
928
  * @param {Function} callback - Callback function to remove
756
929
  * @returns {EventBus} - For chaining
757
930
  */
758
- off(t, e) {
759
- const s = this._listeners.get(t);
760
- if (s) {
761
- for (const r of s)
762
- if (r.callback === e) {
763
- s.delete(r), this._sortedCache.delete(t);
764
- break;
765
- }
766
- }
767
- const i = this._onceListeners.get(t);
768
- return i && i.delete(e), this;
931
+ off(e, t) {
932
+ const s = this._listeners.get(e);
933
+ if (s)
934
+ for (const r of [...s])
935
+ r.callback === t && (s.delete(r), this._sortedCache.delete(e));
936
+ const i = this._onceListeners.get(e);
937
+ return i && i.delete(t), this;
769
938
  }
770
939
  /**
771
940
  * Emit an event to all registered listeners
772
941
  * @param {string} event - Event name
773
942
  * @param {*} data - Event data
774
943
  */
775
- emit(t, e) {
776
- const s = this._getSortedListeners(t);
944
+ emit(e, t) {
945
+ const s = this._getSortedListeners(e);
777
946
  for (let r = 0; r < s.length; r++)
778
- s[r].callback(e);
779
- const i = this._onceListeners.get(t);
947
+ this._invoke(s[r].callback, t, e);
948
+ const i = this._onceListeners.get(e);
780
949
  if (i && i.size > 0) {
781
- for (const r of i)
782
- r(e);
950
+ const r = [...i];
783
951
  i.clear();
952
+ for (const n of r)
953
+ this._invoke(n, t, e);
954
+ }
955
+ }
956
+ /**
957
+ * Call one listener without letting it abort the dispatch.
958
+ *
959
+ * A throwing listener used to take out every lower-priority listener for
960
+ * that event, which on 'tick' means the game silently stops updating.
961
+ *
962
+ * @private
963
+ */
964
+ _invoke(e, t, s) {
965
+ try {
966
+ e(t);
967
+ } catch (i) {
968
+ console.error(`[DopamineJS] Listener for "${s}" threw:`, i);
784
969
  }
785
970
  }
786
971
  /**
787
972
  * Get sorted listeners for an event (with caching)
788
973
  * @private
789
974
  */
790
- _getSortedListeners(t) {
791
- if (this._sortedCache.has(t))
792
- return this._sortedCache.get(t);
793
- const e = this._listeners.get(t);
794
- if (!e || e.size === 0)
975
+ _getSortedListeners(e) {
976
+ if (this._sortedCache.has(e))
977
+ return this._sortedCache.get(e);
978
+ const t = this._listeners.get(e);
979
+ if (!t || t.size === 0)
795
980
  return [];
796
- const s = Array.from(e).sort((i, r) => r.priority - i.priority);
797
- return this._sortedCache.set(t, s), s;
981
+ const s = Array.from(t).sort((i, r) => r.priority - i.priority);
982
+ return this._sortedCache.set(e, s), s;
798
983
  }
799
984
  /**
800
985
  * Remove all listeners for an event, or all events if no event specified
801
986
  * @param {string} [event] - Event name (optional)
802
987
  */
803
- clear(t) {
804
- t ? (this._listeners.delete(t), this._onceListeners.delete(t), this._sortedCache.delete(t)) : (this._listeners.clear(), this._onceListeners.clear(), this._sortedCache.clear());
988
+ clear(e) {
989
+ e ? (this._listeners.delete(e), this._onceListeners.delete(e), this._sortedCache.delete(e)) : (this._listeners.clear(), this._onceListeners.clear(), this._sortedCache.clear());
805
990
  }
806
991
  /**
807
992
  * Check if an event has listeners
808
993
  * @param {string} event - Event name
809
994
  * @returns {boolean}
810
995
  */
811
- hasListeners(t) {
812
- const e = this._listeners.get(t), s = this._onceListeners.get(t);
813
- return e && e.size > 0 || s && s.size > 0;
996
+ hasListeners(e) {
997
+ const t = this._listeners.get(e), s = this._onceListeners.get(e);
998
+ return !!(t != null && t.size) || !!(s != null && s.size);
814
999
  }
815
1000
  }
816
1001
  p.Events = {
@@ -833,9 +1018,9 @@ p.Events = {
833
1018
  PLUGIN_LOADED: "plugin_loaded",
834
1019
  PLUGIN_UNLOADED: "plugin_unloaded"
835
1020
  };
836
- class O {
837
- constructor(t) {
838
- this.kernel = t, this._systems = /* @__PURE__ */ new Map(), this._updateOrder = [], this._fixedUpdateOrder = [], this._needsRecompute = !1;
1021
+ class L {
1022
+ constructor(e) {
1023
+ this.kernel = e, this._systems = /* @__PURE__ */ new Map(), this._updateOrder = [], this._fixedUpdateOrder = [], this._needsRecompute = !1;
839
1024
  }
840
1025
  /**
841
1026
  * Register a system
@@ -846,62 +1031,62 @@ class O {
846
1031
  * @param {string[]} options.dependencies - System names this depends on
847
1032
  * @returns {SystemRegistry} - For chaining
848
1033
  */
849
- register(t, e, { priority: s = 0, dependencies: i = [] } = {}) {
850
- this._systems.has(t) && (console.warn(`[SystemRegistry] System "${t}" already registered. Replacing.`), this.unregister(t));
1034
+ register(e, t, { priority: s = 0, dependencies: i = [] } = {}) {
1035
+ this._systems.has(e) && (console.warn(`[SystemRegistry] System "${e}" already registered. Replacing.`), this.unregister(e));
851
1036
  for (const r of i)
852
- this._systems.has(r) || console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${t}"`);
853
- return this._systems.set(t, {
854
- system: e,
1037
+ this._systems.has(r) || console.warn(`[SystemRegistry] Dependency "${r}" not found for system "${e}"`);
1038
+ return this._systems.set(e, {
1039
+ system: t,
855
1040
  priority: s,
856
1041
  dependencies: i
857
- }), e.init && e.init(this.kernel), this._needsRecompute = !0, this.kernel.events.emit("system_registered", { name: t, system: e }), this;
1042
+ }), t.init && t.init(this.kernel), this._needsRecompute = !0, this.kernel.events.emit("system_registered", { name: e, system: t }), this;
858
1043
  }
859
1044
  /**
860
1045
  * Get a system by name
861
1046
  * @param {string} name - System name
862
1047
  * @returns {Object|undefined} - System instance
863
1048
  */
864
- get(t) {
865
- const e = this._systems.get(t);
866
- return e ? e.system : void 0;
1049
+ get(e) {
1050
+ const t = this._systems.get(e);
1051
+ return t ? t.system : void 0;
867
1052
  }
868
1053
  /**
869
1054
  * Check if a system is registered
870
1055
  * @param {string} name - System name
871
1056
  * @returns {boolean}
872
1057
  */
873
- has(t) {
874
- return this._systems.has(t);
1058
+ has(e) {
1059
+ return this._systems.has(e);
875
1060
  }
876
1061
  /**
877
1062
  * Unregister a system
878
1063
  * @param {string} name - System name
879
1064
  * @returns {boolean} - True if system was found and removed
880
1065
  */
881
- unregister(t) {
882
- const e = this._systems.get(t);
883
- return e ? (e.system.destroy && e.system.destroy(), this._systems.delete(t), this._needsRecompute = !0, this.kernel.events.emit("system_unregistered", { name: t }), !0) : !1;
1066
+ unregister(e) {
1067
+ const t = this._systems.get(e);
1068
+ return t ? (t.system.destroy && t.system.destroy(), this._systems.delete(e), this._needsRecompute = !0, this.kernel.events.emit("system_unregistered", { name: e }), !0) : !1;
884
1069
  }
885
1070
  /**
886
1071
  * Update all systems (called each frame)
887
1072
  * @param {number} dt - Delta time in seconds
888
1073
  */
889
- update(t) {
1074
+ update(e) {
890
1075
  this._needsRecompute && this._recomputeUpdateOrder();
891
- for (let e = 0; e < this._updateOrder.length; e++) {
892
- const s = this._updateOrder[e];
893
- s.update && s.update(t);
1076
+ for (let t = 0; t < this._updateOrder.length; t++) {
1077
+ const s = this._updateOrder[t];
1078
+ s.update && s.update(e);
894
1079
  }
895
1080
  }
896
1081
  /**
897
1082
  * Fixed update for physics (called at fixed timestep)
898
1083
  * @param {number} dt - Fixed delta time
899
1084
  */
900
- fixedUpdate(t) {
1085
+ fixedUpdate(e) {
901
1086
  this._needsRecompute && this._recomputeUpdateOrder();
902
- for (let e = 0; e < this._fixedUpdateOrder.length; e++) {
903
- const s = this._fixedUpdateOrder[e];
904
- s.fixedUpdate && s.fixedUpdate(t);
1087
+ for (let t = 0; t < this._fixedUpdateOrder.length; t++) {
1088
+ const s = this._fixedUpdateOrder[t];
1089
+ s.fixedUpdate && s.fixedUpdate(e);
905
1090
  }
906
1091
  }
907
1092
  /**
@@ -910,29 +1095,29 @@ class O {
910
1095
  * @private
911
1096
  */
912
1097
  _recomputeUpdateOrder() {
913
- const t = Array.from(this._systems.entries()), e = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
914
- for (const [n, o] of t)
915
- e.set(n, o.dependencies), s.set(n, 0);
1098
+ const e = Array.from(this._systems.entries()), t = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
916
1099
  for (const [n, o] of e)
917
- for (const h of o)
918
- s.has(h) && s.set(n, s.get(n) + 1);
1100
+ t.set(n, o.dependencies), s.set(n, 0);
1101
+ for (const [n, o] of t)
1102
+ for (const c of o)
1103
+ s.has(c) && s.set(n, s.get(n) + 1);
919
1104
  const i = [], r = [];
920
1105
  for (const [n, o] of s)
921
1106
  o === 0 && i.push(n);
922
1107
  for (; i.length > 0; ) {
923
- i.sort((o, h) => {
924
- const c = this._systems.get(o).priority;
925
- return this._systems.get(h).priority - c;
1108
+ i.sort((o, c) => {
1109
+ const h = this._systems.get(o).priority;
1110
+ return this._systems.get(c).priority - h;
926
1111
  });
927
1112
  const n = i.shift();
928
1113
  r.push(n);
929
- for (const [o, h] of e)
930
- if (h.includes(n)) {
931
- const c = s.get(o) - 1;
932
- s.set(o, c), c === 0 && i.push(o);
1114
+ for (const [o, c] of t)
1115
+ if (c.includes(n)) {
1116
+ const h = s.get(o) - 1;
1117
+ s.set(o, h), h === 0 && i.push(o);
933
1118
  }
934
1119
  }
935
- r.length !== t.length && (console.error("[SystemRegistry] Circular dependency detected in systems!"), r.length = 0, r.push(...t.map(([n]) => n).sort((n, o) => this._systems.get(o).priority - this._systems.get(n).priority))), this._updateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.update), this._fixedUpdateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.fixedUpdate), this._needsRecompute = !1;
1120
+ r.length !== e.length && (console.error("[SystemRegistry] Circular dependency detected in systems!"), r.length = 0, r.push(...e.map(([n]) => n).sort((n, o) => this._systems.get(o).priority - this._systems.get(n).priority))), this._updateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.update), this._fixedUpdateOrder = r.map((n) => this._systems.get(n).system).filter((n) => n.fixedUpdate), this._needsRecompute = !1;
936
1121
  }
937
1122
  /**
938
1123
  * Get all registered system names
@@ -945,13 +1130,13 @@ class O {
945
1130
  * Clear all systems
946
1131
  */
947
1132
  clear() {
948
- for (const t of this._systems.keys())
949
- this.unregister(t);
1133
+ for (const e of this._systems.keys())
1134
+ this.unregister(e);
950
1135
  }
951
1136
  }
952
- class A {
953
- constructor(t) {
954
- this.kernel = t, this._plugins = /* @__PURE__ */ new Map(), this._loadOrder = [];
1137
+ class U {
1138
+ constructor(e) {
1139
+ this.kernel = e, this._plugins = /* @__PURE__ */ new Map(), this._loadOrder = [];
955
1140
  }
956
1141
  /**
957
1142
  * Register and initialize a plugin
@@ -967,21 +1152,21 @@ class A {
967
1152
  * @param {Object} plugin - Plugin instance
968
1153
  * @returns {PluginRegistry} - For chaining
969
1154
  */
970
- use(t) {
971
- if (!t.name)
1155
+ use(e) {
1156
+ if (!e.name)
972
1157
  throw new Error("[PluginRegistry] Plugin must have a name");
973
- if (this._plugins.has(t.name))
974
- return console.warn(`[PluginRegistry] Plugin "${t.name}" already registered. Skipping.`), this;
975
- if (!t.init || typeof t.init != "function")
976
- throw new Error(`[PluginRegistry] Plugin "${t.name}" must have an init() method`);
977
- this._plugins.set(t.name, t), this._loadOrder.push(t.name);
1158
+ if (this._plugins.has(e.name))
1159
+ return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
1160
+ if (!e.init || typeof e.init != "function")
1161
+ throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
1162
+ this._plugins.set(e.name, e), this._loadOrder.push(e.name);
978
1163
  try {
979
- t.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
980
- name: t.name,
981
- version: t.version
982
- }), console.log(`[PluginRegistry] Loaded plugin: ${t.name}${t.version ? ` v${t.version}` : ""}`);
983
- } catch (e) {
984
- throw console.error(`[PluginRegistry] Failed to initialize plugin "${t.name}":`, e), this._plugins.delete(t.name), this._loadOrder.pop(), e;
1164
+ e.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
1165
+ name: e.name,
1166
+ version: e.version
1167
+ });
1168
+ } catch (t) {
1169
+ throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, t), this._plugins.delete(e.name), this._loadOrder.pop(), t;
985
1170
  }
986
1171
  return this;
987
1172
  }
@@ -992,21 +1177,21 @@ class A {
992
1177
  * @param {Object} plugin - Plugin instance with async init
993
1178
  * @returns {Promise<PluginRegistry>} - For chaining
994
1179
  */
995
- async useAsync(t) {
996
- if (!t.name)
1180
+ async useAsync(e) {
1181
+ if (!e.name)
997
1182
  throw new Error("[PluginRegistry] Plugin must have a name");
998
- if (this._plugins.has(t.name))
999
- return console.warn(`[PluginRegistry] Plugin "${t.name}" already registered. Skipping.`), this;
1000
- if (!t.init || typeof t.init != "function")
1001
- throw new Error(`[PluginRegistry] Plugin "${t.name}" must have an init() method`);
1002
- this._plugins.set(t.name, t), this._loadOrder.push(t.name);
1183
+ if (this._plugins.has(e.name))
1184
+ return console.warn(`[PluginRegistry] Plugin "${e.name}" already registered. Skipping.`), this;
1185
+ if (!e.init || typeof e.init != "function")
1186
+ throw new Error(`[PluginRegistry] Plugin "${e.name}" must have an init() method`);
1187
+ this._plugins.set(e.name, e), this._loadOrder.push(e.name);
1003
1188
  try {
1004
- await t.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
1005
- name: t.name,
1006
- version: t.version
1007
- }), console.log(`[PluginRegistry] Loaded plugin: ${t.name}${t.version ? ` v${t.version}` : ""}`);
1008
- } catch (e) {
1009
- throw console.error(`[PluginRegistry] Failed to initialize plugin "${t.name}":`, e), this._plugins.delete(t.name), this._loadOrder.pop(), e;
1189
+ await e.init(this.kernel), this.kernel.events.emit("plugin_loaded", {
1190
+ name: e.name,
1191
+ version: e.version
1192
+ });
1193
+ } catch (t) {
1194
+ throw console.error(`[PluginRegistry] Failed to initialize plugin "${e.name}":`, t), this._plugins.delete(e.name), this._loadOrder.pop(), t;
1010
1195
  }
1011
1196
  return this;
1012
1197
  }
@@ -1015,35 +1200,35 @@ class A {
1015
1200
  * @param {string} name - Plugin name
1016
1201
  * @returns {boolean} - True if plugin was found and removed
1017
1202
  */
1018
- remove(t) {
1019
- const e = this._plugins.get(t);
1020
- if (!e)
1203
+ remove(e) {
1204
+ const t = this._plugins.get(e);
1205
+ if (!t)
1021
1206
  return !1;
1022
- if (e.destroy)
1207
+ if (t.destroy)
1023
1208
  try {
1024
- e.destroy();
1209
+ t.destroy();
1025
1210
  } catch (i) {
1026
- console.error(`[PluginRegistry] Error destroying plugin "${t}":`, i);
1211
+ console.error(`[PluginRegistry] Error destroying plugin "${e}":`, i);
1027
1212
  }
1028
- this._plugins.delete(t);
1029
- const s = this._loadOrder.indexOf(t);
1030
- return s > -1 && this._loadOrder.splice(s, 1), this.kernel.events.emit("plugin_unloaded", { name: t }), !0;
1213
+ this._plugins.delete(e);
1214
+ const s = this._loadOrder.indexOf(e);
1215
+ return s > -1 && this._loadOrder.splice(s, 1), this.kernel.events.emit("plugin_unloaded", { name: e }), !0;
1031
1216
  }
1032
1217
  /**
1033
1218
  * Get a plugin by name
1034
1219
  * @param {string} name - Plugin name
1035
1220
  * @returns {Object|undefined} - Plugin instance
1036
1221
  */
1037
- get(t) {
1038
- return this._plugins.get(t);
1222
+ get(e) {
1223
+ return this._plugins.get(e);
1039
1224
  }
1040
1225
  /**
1041
1226
  * Check if a plugin is registered
1042
1227
  * @param {string} name - Plugin name
1043
1228
  * @returns {boolean}
1044
1229
  */
1045
- has(t) {
1046
- return this._plugins.has(t);
1230
+ has(e) {
1231
+ return this._plugins.has(e);
1047
1232
  }
1048
1233
  /**
1049
1234
  * Get all registered plugin names
@@ -1063,13 +1248,13 @@ class A {
1063
1248
  * Clear all plugins (in reverse load order)
1064
1249
  */
1065
1250
  clear() {
1066
- for (let t = this._loadOrder.length - 1; t >= 0; t--)
1067
- this.remove(this._loadOrder[t]);
1251
+ for (let e = this._loadOrder.length - 1; e >= 0; e--)
1252
+ this.remove(this._loadOrder[e]);
1068
1253
  }
1069
1254
  }
1070
- class D {
1071
- constructor(t = {}) {
1072
- this.width = t.width || 800, this.height = t.height || 600, this.backgroundColor = t.backgroundColor || "#000000", this.canvas = document.createElement("canvas"), this.canvas.width = this.width, this.canvas.height = this.height, this.ctx = this.canvas.getContext("2d"), (t.parent ? typeof t.parent == "string" ? document.querySelector(t.parent) : t.parent : document.body).appendChild(this.canvas);
1255
+ class R {
1256
+ constructor(e = {}) {
1257
+ this.width = e.width || 800, this.height = e.height || 600, this.backgroundColor = e.backgroundColor || "#000000", e.canvas ? (this.canvas = e.canvas, this.canvas.width = this.width, this.canvas.height = this.height) : (this.canvas = document.createElement("canvas"), this.canvas.width = this.width, this.canvas.height = this.height, (e.parent ? typeof e.parent == "string" ? document.querySelector(e.parent) : e.parent : document.body).appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d");
1073
1258
  }
1074
1259
  /**
1075
1260
  * Clear the canvas.
@@ -1077,48 +1262,48 @@ class D {
1077
1262
  clear() {
1078
1263
  this.ctx.fillStyle = this.backgroundColor, this.ctx.fillRect(0, 0, this.width, this.height);
1079
1264
  }
1080
- resize(t, e) {
1081
- this.width = t, this.height = e, this.canvas.width = t, this.canvas.height = e;
1265
+ resize(e, t) {
1266
+ this.width = e, this.height = t, this.canvas.width = e, this.canvas.height = t;
1082
1267
  }
1083
1268
  }
1084
- class L {
1269
+ class z {
1085
1270
  constructor() {
1086
- this.running = !1, this.lastTime = 0, this.callbacks = /* @__PURE__ */ new Set(), this._tick = this._tick.bind(this);
1271
+ this.running = !1, this.lastTime = 0, this.callbacks = /* @__PURE__ */ new Set(), this._frameHandle = null, this._tick = this._tick.bind(this);
1087
1272
  }
1088
1273
  /**
1089
1274
  * Add a callback to the loop.
1090
1275
  * @param {Function} callback - Function to call each frame (receives dt).
1091
1276
  */
1092
- add(t) {
1093
- this.callbacks.add(t);
1277
+ add(e) {
1278
+ this.callbacks.add(e);
1094
1279
  }
1095
1280
  /**
1096
1281
  * Remove a callback from the loop.
1097
1282
  * @param {Function} callback
1098
1283
  */
1099
- remove(t) {
1100
- this.callbacks.delete(t);
1284
+ remove(e) {
1285
+ this.callbacks.delete(e);
1101
1286
  }
1102
1287
  /**
1103
1288
  * Start the loop.
1104
1289
  */
1105
1290
  start() {
1106
- this.running || (this.running = !0, this.lastTime = performance.now(), requestAnimationFrame(this._tick));
1291
+ this.running || (this.running = !0, this.lastTime = performance.now(), this._frameHandle !== null && cancelAnimationFrame(this._frameHandle), this._frameHandle = requestAnimationFrame(this._tick));
1107
1292
  }
1108
1293
  /**
1109
1294
  * Stop the loop.
1110
1295
  */
1111
1296
  stop() {
1112
- this.running = !1;
1113
- }
1114
- _tick(t) {
1115
- if (!this.running) return;
1116
- const e = (t - this.lastTime) / 1e3;
1117
- this.lastTime = t;
1118
- const s = Math.min(e, 0.1);
1119
- for (const i of this.callbacks)
1297
+ this.running = !1, this._frameHandle !== null && (cancelAnimationFrame(this._frameHandle), this._frameHandle = null);
1298
+ }
1299
+ _tick(e) {
1300
+ if (this._frameHandle = null, !this.running) return;
1301
+ const t = (e - this.lastTime) / 1e3;
1302
+ this.lastTime = e;
1303
+ const s = Math.min(t, 0.1);
1304
+ for (const i of [...this.callbacks])
1120
1305
  i(s);
1121
- requestAnimationFrame(this._tick);
1306
+ this.running && (this._frameHandle = requestAnimationFrame(this._tick));
1122
1307
  }
1123
1308
  }
1124
1309
  class y {
@@ -1129,33 +1314,33 @@ class y {
1129
1314
  * Access systems through kernel (no more globals!)
1130
1315
  */
1131
1316
  get physics() {
1132
- var t;
1133
- return (t = this.kernel) == null ? void 0 : t.systems.get("physics");
1317
+ var e;
1318
+ return (e = this.kernel) == null ? void 0 : e.systems.get("physics");
1134
1319
  }
1135
1320
  get input() {
1136
- var t;
1137
- return (t = this.kernel) == null ? void 0 : t.systems.get("input");
1321
+ var e;
1322
+ return (e = this.kernel) == null ? void 0 : e.systems.get("input");
1138
1323
  }
1139
1324
  get loader() {
1140
- var t;
1141
- return (t = this.kernel) == null ? void 0 : t.systems.get("loader");
1325
+ var e;
1326
+ return (e = this.kernel) == null ? void 0 : e.systems.get("loader");
1142
1327
  }
1143
1328
  get events() {
1144
- var t;
1145
- return (t = this.kernel) == null ? void 0 : t.events;
1329
+ var e;
1330
+ return (e = this.kernel) == null ? void 0 : e.events;
1146
1331
  }
1147
1332
  onAttach() {
1148
1333
  }
1149
1334
  onDetach() {
1150
1335
  }
1151
- update(t) {
1336
+ update(e) {
1152
1337
  }
1153
- render(t) {
1338
+ render(e) {
1154
1339
  }
1155
1340
  }
1156
- class B extends y {
1157
- constructor(t = "box", e = 50, s = 50, i = 25) {
1158
- super(), this.type = t, this.width = e, this.height = s, this.radius = i, this.tag = null;
1341
+ class H extends y {
1342
+ constructor(e = "box", t = 50, s = 50, i = 25) {
1343
+ super(), this.type = e, this.width = t, this.height = s, this.radius = i, this.tag = null;
1159
1344
  }
1160
1345
  onAttach() {
1161
1346
  super.onAttach(), this.physics && this.physics.add(this);
@@ -1164,36 +1349,36 @@ class B extends y {
1164
1349
  super.onDetach(), this.physics && this.physics.remove(this);
1165
1350
  }
1166
1351
  getBounds() {
1167
- const t = this.gameObject;
1352
+ const e = this.gameObject;
1168
1353
  if (this.type === "box")
1169
1354
  return {
1170
- left: t.x - this.width / 2,
1171
- right: t.x + this.width / 2,
1172
- top: t.y - this.height / 2,
1173
- bottom: t.y + this.height / 2
1355
+ left: e.x - this.width / 2,
1356
+ right: e.x + this.width / 2,
1357
+ top: e.y - this.height / 2,
1358
+ bottom: e.y + this.height / 2
1174
1359
  };
1175
1360
  if (this.type === "circle")
1176
1361
  return {
1177
- x: t.x,
1178
- y: t.y,
1362
+ x: e.x,
1363
+ y: e.y,
1179
1364
  radius: this.radius
1180
1365
  };
1181
1366
  }
1182
1367
  }
1183
- class _ {
1368
+ class b {
1184
1369
  constructor() {
1185
1370
  this.colliders = [], this.kernel = null;
1186
1371
  }
1187
1372
  /**
1188
1373
  * ISystem interface - called when registered
1189
1374
  */
1190
- init(t) {
1191
- this.kernel = t;
1375
+ init(e) {
1376
+ this.kernel = e;
1192
1377
  }
1193
1378
  /**
1194
1379
  * ISystem interface - called at fixed timestep
1195
1380
  */
1196
- fixedUpdate(t) {
1381
+ fixedUpdate(e) {
1197
1382
  this.step();
1198
1383
  }
1199
1384
  /**
@@ -1202,27 +1387,27 @@ class _ {
1202
1387
  destroy() {
1203
1388
  this.colliders = [];
1204
1389
  }
1205
- add(t) {
1206
- this.colliders.push(t);
1390
+ add(e) {
1391
+ this.colliders.push(e);
1207
1392
  }
1208
- remove(t) {
1209
- const e = this.colliders.indexOf(t);
1210
- e > -1 && this.colliders.splice(e, 1);
1393
+ remove(e) {
1394
+ const t = this.colliders.indexOf(e);
1395
+ t > -1 && this.colliders.splice(t, 1);
1211
1396
  }
1212
1397
  /**
1213
1398
  * Main Physics Step.
1214
1399
  * Checks all pairs and triggers callbacks.
1215
1400
  */
1216
1401
  step() {
1217
- for (let t = 0; t < this.colliders.length; t++)
1218
- for (let e = t + 1; e < this.colliders.length; e++) {
1219
- const s = this.colliders[t], i = this.colliders[e];
1402
+ for (let e = 0; e < this.colliders.length; e++)
1403
+ for (let t = e + 1; t < this.colliders.length; t++) {
1404
+ const s = this.colliders[e], i = this.colliders[t];
1220
1405
  this._intersects(s, s.getBounds(), i, i.getBounds()) && (this._notifyCollision(s.gameObject, i.gameObject), this._notifyCollision(i.gameObject, s.gameObject));
1221
1406
  }
1222
1407
  }
1223
- _notifyCollision(t, e) {
1224
- !t || !t.components || t.components.forEach((s) => {
1225
- s.onCollisionEnter && s.onCollisionEnter(e);
1408
+ _notifyCollision(e, t) {
1409
+ !e || !e.components || e.components.forEach((s) => {
1410
+ s.onCollisionEnter && s.onCollisionEnter(t);
1226
1411
  });
1227
1412
  }
1228
1413
  /**
@@ -1230,51 +1415,51 @@ class _ {
1230
1415
  * @param {Collider} source
1231
1416
  * @param {string} targetTag
1232
1417
  */
1233
- checkOverlap(t, e) {
1234
- const s = [], i = t.getBounds();
1418
+ checkOverlap(e, t) {
1419
+ const s = [], i = e.getBounds();
1235
1420
  for (const r of this.colliders) {
1236
- if (r === t || e && r.tag !== e) continue;
1421
+ if (r === e || t && r.tag !== t) continue;
1237
1422
  const n = r.getBounds();
1238
- this._intersects(t, i, r, n) && s.push(r);
1423
+ this._intersects(e, i, r, n) && s.push(r);
1239
1424
  }
1240
1425
  return s;
1241
1426
  }
1242
- _intersects(t, e, s, i) {
1243
- if (t.type === "box" && s.type === "box")
1244
- return e.left < i.right && e.right > i.left && e.top < i.bottom && e.bottom > i.top;
1245
- if (t.type === "circle" && s.type === "circle") {
1246
- const r = e.x - i.x, n = e.y - i.y;
1247
- return Math.sqrt(r * r + n * n) < e.radius + i.radius;
1427
+ _intersects(e, t, s, i) {
1428
+ if (e.type === "box" && s.type === "box")
1429
+ return t.left < i.right && t.right > i.left && t.top < i.bottom && t.bottom > i.top;
1430
+ if (e.type === "circle" && s.type === "circle") {
1431
+ const r = t.x - i.x, n = t.y - i.y;
1432
+ return Math.sqrt(r * r + n * n) < t.radius + i.radius;
1248
1433
  }
1249
1434
  return !1;
1250
1435
  }
1251
1436
  // IPhysicsSystem interface methods (for plugin compatibility)
1252
- addBody(t) {
1253
- this.add(t);
1437
+ addBody(e) {
1438
+ this.add(e);
1254
1439
  }
1255
- removeBody(t) {
1256
- this.remove(t);
1440
+ removeBody(e) {
1441
+ this.remove(e);
1257
1442
  }
1258
- checkCollision(t, e) {
1259
- return this._intersects(t, t.getBounds(), e, e.getBounds());
1443
+ checkCollision(e, t) {
1444
+ return this._intersects(e, e.getBounds(), t, t.getBounds());
1260
1445
  }
1261
- raycast(t, e, s) {
1446
+ raycast(e, t, s) {
1262
1447
  return null;
1263
1448
  }
1264
- setGravity(t, e) {
1449
+ setGravity(e, t) {
1265
1450
  }
1266
1451
  }
1267
- const G = new _();
1452
+ const $ = new b();
1268
1453
  console.warn("[DopamineJS] GlobalPhysics is deprecated. Use kernel.physics instead.");
1269
- class k {
1454
+ class M {
1270
1455
  constructor() {
1271
1456
  this.keys = /* @__PURE__ */ new Map(), this.mouse = { x: 0, y: 0, buttons: /* @__PURE__ */ new Map() }, this.kernel = null, this._onKeyDown = this._onKeyDown.bind(this), this._onKeyUp = this._onKeyUp.bind(this), this._onMouseMove = this._onMouseMove.bind(this), this._onMouseDown = this._onMouseDown.bind(this), this._onMouseUp = this._onMouseUp.bind(this);
1272
1457
  }
1273
1458
  /**
1274
1459
  * ISystem interface - called when registered
1275
1460
  */
1276
- init(t) {
1277
- this.kernel = t, window.addEventListener("keydown", this._onKeyDown), window.addEventListener("keyup", this._onKeyUp), window.addEventListener("mousemove", this._onMouseMove), window.addEventListener("mousedown", this._onMouseDown), window.addEventListener("mouseup", this._onMouseUp);
1461
+ init(e) {
1462
+ this.kernel = e, window.addEventListener("keydown", this._onKeyDown), window.addEventListener("keyup", this._onKeyUp), window.addEventListener("mousemove", this._onMouseMove), window.addEventListener("mousedown", this._onMouseDown), window.addEventListener("mouseup", this._onMouseUp);
1278
1463
  }
1279
1464
  /**
1280
1465
  * ISystem interface - cleanup
@@ -1282,39 +1467,39 @@ class k {
1282
1467
  destroy() {
1283
1468
  window.removeEventListener("keydown", this._onKeyDown), window.removeEventListener("keyup", this._onKeyUp), window.removeEventListener("mousemove", this._onMouseMove), window.removeEventListener("mousedown", this._onMouseDown), window.removeEventListener("mouseup", this._onMouseUp);
1284
1469
  }
1285
- _onKeyDown(t) {
1286
- this.keys.set(t.key, !0);
1470
+ _onKeyDown(e) {
1471
+ this.keys.set(e.key, !0);
1287
1472
  }
1288
- _onKeyUp(t) {
1289
- this.keys.set(t.key, !1);
1473
+ _onKeyUp(e) {
1474
+ this.keys.set(e.key, !1);
1290
1475
  }
1291
- _onMouseMove(t) {
1292
- this.mouse.x = t.clientX, this.mouse.y = t.clientY;
1476
+ _onMouseMove(e) {
1477
+ this.mouse.x = e.clientX, this.mouse.y = e.clientY;
1293
1478
  }
1294
- _onMouseDown(t) {
1295
- this.mouse.buttons.set(t.button, !0);
1479
+ _onMouseDown(e) {
1480
+ this.mouse.buttons.set(e.button, !0);
1296
1481
  }
1297
- _onMouseUp(t) {
1298
- this.mouse.buttons.set(t.button, !1);
1482
+ _onMouseUp(e) {
1483
+ this.mouse.buttons.set(e.button, !1);
1299
1484
  }
1300
- isKeyDown(t) {
1301
- return this.keys.get(t) || !1;
1485
+ isKeyDown(e) {
1486
+ return this.keys.get(e) || !1;
1302
1487
  }
1303
- isMouseButtonDown(t = 0) {
1304
- return this.mouse.buttons.get(t) || !1;
1488
+ isMouseButtonDown(e = 0) {
1489
+ return this.mouse.buttons.get(e) || !1;
1305
1490
  }
1306
1491
  }
1307
- const F = new k();
1492
+ const q = new M();
1308
1493
  console.warn("[DopamineJS] GlobalInput is deprecated. Use kernel.input instead.");
1309
- class S {
1494
+ class E {
1310
1495
  constructor() {
1311
1496
  this.assets = /* @__PURE__ */ new Map(), this.kernel = null;
1312
1497
  }
1313
1498
  /**
1314
1499
  * ISystem interface - called when registered
1315
1500
  */
1316
- init(t) {
1317
- this.kernel = t;
1501
+ init(e) {
1502
+ this.kernel = e;
1318
1503
  }
1319
1504
  /**
1320
1505
  * ISystem interface - cleanup
@@ -1322,39 +1507,47 @@ class S {
1322
1507
  destroy() {
1323
1508
  this.assets.clear();
1324
1509
  }
1325
- async loadImage(t, e) {
1510
+ async loadImage(e, t) {
1326
1511
  return new Promise((s, i) => {
1327
1512
  const r = new Image();
1328
1513
  r.onload = () => {
1329
- this.assets.set(t, r), s(r);
1330
- }, r.onerror = i, r.src = e;
1514
+ this.assets.set(e, r), s(r);
1515
+ }, r.onerror = i, r.src = t;
1331
1516
  });
1332
1517
  }
1333
- get(t) {
1334
- return this.assets.get(t);
1518
+ get(e) {
1519
+ return this.assets.get(e);
1335
1520
  }
1336
1521
  }
1337
- const N = new S();
1522
+ const K = new E();
1338
1523
  console.warn("[DopamineJS] GlobalLoader is deprecated. Use kernel.loader instead.");
1339
- class U {
1340
- constructor(t = {}) {
1341
- this.config = t, this.events = new p(), this.systems = new O(this), this.plugins = new A(this), this._registerCoreSystems(t), this.fixedTimestep = t.fixedTimestep || 1 / 60, this._accumulator = 0, this._maxAccumulator = this.fixedTimestep * 5;
1524
+ class B {
1525
+ constructor(e = {}) {
1526
+ this.config = e, this._boundUpdate = this._update.bind(this), this.events = new p(), this.systems = new L(this), this.plugins = new U(this), this._registerCoreSystems(e), this.fixedTimestep = e.fixedTimestep || 1 / 60, this._accumulator = 0, this._maxAccumulator = this.fixedTimestep * 5;
1342
1527
  }
1343
1528
  /**
1344
1529
  * Register core engine systems
1345
1530
  * These can be replaced by plugins
1346
1531
  * @private
1347
1532
  */
1348
- _registerCoreSystems(t) {
1349
- this.systems.register("ticker", new L(), {
1533
+ _registerCoreSystems(e) {
1534
+ this.systems.register("ticker", new z(), {
1350
1535
  priority: 100
1351
- }), this.systems.register("renderer", new D(t.renderer || {}), {
1536
+ });
1537
+ const t = {
1538
+ width: e.width,
1539
+ height: e.height,
1540
+ backgroundColor: e.backgroundColor,
1541
+ canvas: e.canvas,
1542
+ ...e.renderer
1543
+ };
1544
+ this.systems.register("renderer", new R(t), {
1352
1545
  priority: 90
1353
- }), this.systems.register("input", new k(), {
1546
+ }), this.systems.register("input", new M(), {
1354
1547
  priority: 80
1355
- }), this.systems.register("loader", new S(), {
1548
+ }), this.systems.register("loader", new E(), {
1356
1549
  priority: 70
1357
- }), this.systems.register("physics", new _(), {
1550
+ }), this.systems.register("physics", new b(), {
1358
1551
  priority: 60
1359
1552
  });
1360
1553
  }
@@ -1362,24 +1555,24 @@ class U {
1362
1555
  * Start the kernel (begins game loop)
1363
1556
  */
1364
1557
  start() {
1365
- const t = this.systems.get("ticker");
1366
- t && (t.add(this._update.bind(this)), t.start()), this.events.emit("kernel_started");
1558
+ const e = this.systems.get("ticker");
1559
+ e && (e.add(this._boundUpdate), e.start()), this.events.emit("kernel_started");
1367
1560
  }
1368
1561
  /**
1369
1562
  * Stop the kernel
1370
1563
  */
1371
1564
  stop() {
1372
- const t = this.systems.get("ticker");
1373
- t && t.stop(), this.events.emit("kernel_stopped");
1565
+ const e = this.systems.get("ticker");
1566
+ e && e.stop(), this.events.emit("kernel_stopped");
1374
1567
  }
1375
1568
  /**
1376
1569
  * Main update loop
1377
1570
  * @private
1378
1571
  */
1379
- _update(t) {
1380
- for (this.events.emit(p.Events.TICK, { dt: t }), this._accumulator += t, this._accumulator > this._maxAccumulator && (this._accumulator = this._maxAccumulator); this._accumulator >= this.fixedTimestep; )
1572
+ _update(e) {
1573
+ for (this.events.emit(p.Events.TICK, { dt: e }), this._accumulator += e, this._accumulator > this._maxAccumulator && (this._accumulator = this._maxAccumulator); this._accumulator >= this.fixedTimestep; )
1381
1574
  this.events.emit(p.Events.FIXED_UPDATE, { dt: this.fixedTimestep }), this.systems.fixedUpdate(this.fixedTimestep), this._accumulator -= this.fixedTimestep;
1382
- this.systems.update(t), this.events.emit(p.Events.RENDER, { dt: t });
1575
+ this.systems.update(e), this.events.emit(p.Events.RENDER, { dt: e });
1383
1576
  }
1384
1577
  /**
1385
1578
  * Shorthand accessors for common systems (backward compatibility)
@@ -1403,7 +1596,9 @@ class U {
1403
1596
  * Destroy the kernel and cleanup all systems
1404
1597
  */
1405
1598
  destroy() {
1406
- this.stop(), this.plugins.clear(), this.systems.clear(), this.events.clear();
1599
+ this.stop();
1600
+ const e = this.systems.get("ticker");
1601
+ e && e.remove(this._boundUpdate), this.plugins.clear(), this.systems.clear(), this.events.clear();
1407
1602
  }
1408
1603
  }
1409
1604
  const g = {
@@ -1431,21 +1626,21 @@ const g = {
1431
1626
  destroy() {
1432
1627
  }
1433
1628
  };
1434
- class K {
1629
+ class X {
1435
1630
  constructor() {
1436
1631
  this.kernel = null;
1437
1632
  }
1438
- init(t) {
1439
- this.kernel = t;
1633
+ init(e) {
1634
+ this.kernel = e;
1440
1635
  }
1441
- update(t) {
1636
+ update(e) {
1442
1637
  }
1443
- fixedUpdate(t) {
1638
+ fixedUpdate(e) {
1444
1639
  }
1445
1640
  destroy() {
1446
1641
  }
1447
1642
  }
1448
- const X = {
1643
+ const J = {
1449
1644
  ...g,
1450
1645
  /**
1451
1646
  * Add a physics body to the simulation
@@ -1465,7 +1660,7 @@ const X = {
1465
1660
  * @param {Object} b - Second body
1466
1661
  * @returns {boolean} - True if colliding
1467
1662
  */
1468
- checkCollision(a, t) {
1663
+ checkCollision(a, e) {
1469
1664
  },
1470
1665
  /**
1471
1666
  * Perform a raycast
@@ -1474,14 +1669,14 @@ const X = {
1474
1669
  * @param {number} distance - Maximum ray distance
1475
1670
  * @returns {Object|null} - Hit result or null
1476
1671
  */
1477
- raycast(a, t, e) {
1672
+ raycast(a, e, t) {
1478
1673
  },
1479
1674
  /**
1480
1675
  * Set gravity for the physics world
1481
1676
  * @param {number} x - Gravity X component
1482
1677
  * @param {number} y - Gravity Y component
1483
1678
  */
1484
- setGravity(a, t) {
1679
+ setGravity(a, e) {
1485
1680
  },
1486
1681
  /**
1487
1682
  * Step the physics simulation
@@ -1489,14 +1684,14 @@ const X = {
1489
1684
  */
1490
1685
  step(a) {
1491
1686
  }
1492
- }, H = {
1687
+ }, W = {
1493
1688
  ...g,
1494
1689
  /**
1495
1690
  * Register a sound with a key
1496
1691
  * @param {string} key - Sound identifier
1497
1692
  * @param {string} url - Path to audio file
1498
1693
  */
1499
- registerSound(a, t) {
1694
+ registerSound(a, e) {
1500
1695
  },
1501
1696
  /**
1502
1697
  * Preload multiple sounds
@@ -1510,7 +1705,7 @@ const X = {
1510
1705
  * @param {string} key - Sound identifier
1511
1706
  * @param {Object} options - Playback options (volume, loop, etc.)
1512
1707
  */
1513
- play(a, t) {
1708
+ play(a, e) {
1514
1709
  },
1515
1710
  /**
1516
1711
  * Play a synthesized tone
@@ -1519,7 +1714,7 @@ const X = {
1519
1714
  * @param {string} type - Waveform type ('sine', 'square', etc.)
1520
1715
  * @param {number} volume - Volume (0-1)
1521
1716
  */
1522
- playTone(a, t, e, s) {
1717
+ playTone(a, e, t, s) {
1523
1718
  },
1524
1719
  /**
1525
1720
  * Stop a playing sound
@@ -1539,21 +1734,21 @@ const X = {
1539
1734
  */
1540
1735
  setVolume(a) {
1541
1736
  }
1542
- }, q = {
1737
+ }, Y = {
1543
1738
  ...g,
1544
1739
  /**
1545
1740
  * Register a sprite for particles
1546
1741
  * @param {string} key - Sprite identifier
1547
1742
  * @param {string} url - Path to image
1548
1743
  */
1549
- registerSprite(a, t) {
1744
+ registerSprite(a, e) {
1550
1745
  },
1551
1746
  /**
1552
1747
  * Register a custom effect
1553
1748
  * @param {string} name - Effect name
1554
1749
  * @param {Function} callback - Effect function (x, y, ...args) => void
1555
1750
  */
1556
- registerEffect(a, t) {
1751
+ registerEffect(a, e) {
1557
1752
  },
1558
1753
  /**
1559
1754
  * Play a registered effect
@@ -1562,7 +1757,7 @@ const X = {
1562
1757
  * @param {number} y - Y position
1563
1758
  * @param {...any} args - Additional arguments
1564
1759
  */
1565
- play(a, t, e, ...s) {
1760
+ play(a, e, t, ...s) {
1566
1761
  },
1567
1762
  /**
1568
1763
  * Emit particles with custom configuration
@@ -1576,80 +1771,78 @@ const X = {
1576
1771
  clear() {
1577
1772
  },
1578
1773
  // Built-in effects (optional, but recommended)
1579
- confetti(a, t, e) {
1774
+ confetti(a, e, t) {
1580
1775
  },
1581
- coinShower(a, t, e) {
1776
+ coinShower(a, e, t) {
1582
1777
  },
1583
- sparkle(a, t, e, s) {
1778
+ sparkle(a, e, t, s) {
1584
1779
  },
1585
- fire(a, t, e) {
1780
+ fire(a, e, t) {
1586
1781
  },
1587
- starBurst(a, t, e) {
1782
+ starBurst(a, e, t) {
1588
1783
  }
1589
1784
  };
1590
- class J {
1785
+ class V {
1591
1786
  constructor() {
1592
- this.children = [], this.game = null;
1787
+ this.gameObjects = [], this.kernel = null;
1593
1788
  }
1594
1789
  /**
1595
1790
  * Called when the scene is added to the Game.
1596
1791
  */
1597
1792
  onEnter() {
1598
1793
  }
1599
- onExit() {
1600
- }
1601
1794
  /**
1602
- * Update loop for the scene.
1603
- * @param {number} dt
1795
+ * Called when the scene is removed from the Game.
1604
1796
  */
1605
- update(t) {
1606
- for (const e of this.children)
1607
- e.update && e.update(t);
1797
+ onExit() {
1608
1798
  }
1609
1799
  /**
1610
- this.gameObjects = [];
1611
- this.kernel = null; // Injected by Game
1612
- }
1613
-
1614
- /**
1615
- * Add a GameObject to the scene.
1616
- * @param {GameObject} gameObject
1617
- */
1618
- add(t) {
1619
- return t.kernel = this.kernel, this.gameObjects.push(t), t;
1800
+ * Add a GameObject to the scene.
1801
+ * @param {GameObject} gameObject
1802
+ */
1803
+ add(e) {
1804
+ return this.kernel && (e.kernel = this.kernel), this.gameObjects.push(e), e;
1620
1805
  }
1621
1806
  /**
1622
1807
  * Remove a GameObject from the scene.
1623
1808
  * @param {GameObject} gameObject
1624
1809
  */
1625
- remove(t) {
1626
- const e = this.gameObjects.indexOf(t);
1627
- e > -1 && this.gameObjects.splice(e, 1);
1810
+ remove(e) {
1811
+ const t = this.gameObjects.indexOf(e);
1812
+ t > -1 && this.gameObjects.splice(t, 1);
1628
1813
  }
1629
- update(t) {
1630
- for (const e of this.gameObjects)
1631
- e.update(t);
1814
+ /**
1815
+ * Update loop for the scene.
1816
+ * @param {number} dt
1817
+ */
1818
+ update(e) {
1819
+ for (const t of this.gameObjects)
1820
+ t.update(e);
1632
1821
  }
1633
- render(t) {
1634
- for (const e of this.gameObjects)
1635
- e.render(t);
1822
+ /**
1823
+ * Render loop for the scene.
1824
+ * @param {CanvasRenderingContext2D} ctx
1825
+ */
1826
+ render(e) {
1827
+ for (const t of this.gameObjects)
1828
+ t.render(e);
1636
1829
  }
1637
1830
  }
1638
- class R {
1639
- constructor(t) {
1640
- this.game = t, this.currentScene = null;
1831
+ class G {
1832
+ constructor(e) {
1833
+ this.game = e, this.currentScene = null;
1641
1834
  }
1642
1835
  /**
1643
1836
  * Switch to a new scene.
1644
1837
  * @param {Scene} scene
1645
1838
  */
1646
- run(t) {
1647
- this.currentScene && this.currentScene.onExit(), this.currentScene = t, this.currentScene.game = this.game, this.game.scene = t, t.onEnter();
1839
+ run(e) {
1840
+ this.currentScene && this.currentScene.onExit(), this.currentScene = e, this.currentScene.game = this.game, this.game.scene = e, e.onEnter();
1648
1841
  }
1649
1842
  }
1650
- class W {
1651
- constructor(t = {}) {
1652
- this.config = t, this.kernel = new U(t), this.director = new R(this), this.kernel.events.on(p.Events.TICK, this._update.bind(this), 10), this.kernel.events.on(p.Events.RENDER, this._render.bind(this), 10);
1843
+ class Q {
1844
+ constructor(e = {}) {
1845
+ this.config = e, this.kernel = new B(e), this.director = new G(this), this.kernel.systems.register("director", this.director), this.kernel.events.on(p.Events.TICK, this._update.bind(this), 10), this.kernel.events.on(p.Events.RENDER, this._render.bind(this), 10);
1653
1846
  }
1654
1847
  /**
1655
1848
  * Backward compatibility accessors
@@ -1682,13 +1875,13 @@ class W {
1682
1875
  * Switch to a new scene.
1683
1876
  * @param {Scene} newScene
1684
1877
  */
1685
- setScene(t) {
1686
- t.kernel = this.kernel, this.director.run(t);
1878
+ setScene(e) {
1879
+ e.kernel = this.kernel, this.director.run(e);
1687
1880
  }
1688
- _update({ dt: t }) {
1689
- this.director.currentScene && this.director.currentScene.update(t);
1881
+ _update({ dt: e }) {
1882
+ this.director.currentScene && this.director.currentScene.update(e);
1690
1883
  }
1691
- _render({ dt: t }) {
1884
+ _render({ dt: e }) {
1692
1885
  this.renderer.clear(), this.director.currentScene && this.director.currentScene.render(this.renderer.ctx);
1693
1886
  }
1694
1887
  /**
@@ -1698,178 +1891,188 @@ class W {
1698
1891
  this.kernel.destroy();
1699
1892
  }
1700
1893
  }
1701
- class Y {
1702
- constructor(t = 0, e = 0) {
1703
- this.x = t, this.y = e, this.rotation = 0, this.scale = { x: 1, y: 1 }, this.components = [], this.children = [], this.parent = null, this.kernel = null;
1894
+ class Z {
1895
+ constructor(e = 0, t = 0) {
1896
+ this.x = e, this.y = t, this.rotation = 0, this.scale = { x: 1, y: 1 }, this.components = [], this.children = [], this.parent = null, this._kernel = null;
1897
+ }
1898
+ get kernel() {
1899
+ return this._kernel;
1900
+ }
1901
+ set kernel(e) {
1902
+ this._kernel = e;
1903
+ for (const t of this.components)
1904
+ t.kernel = e;
1905
+ for (const t of this.children)
1906
+ t.kernel = e;
1704
1907
  }
1705
1908
  /**
1706
1909
  * Add a child object.
1707
1910
  * @param {GameObject} child
1708
1911
  */
1709
- addChild(t) {
1710
- return t.parent = this, t.kernel = this.kernel, this.children.push(t), t;
1912
+ addChild(e) {
1913
+ return e.parent = this, e.kernel = this.kernel, this.children.push(e), e;
1711
1914
  }
1712
1915
  /**
1713
1916
  * Add a component.
1714
1917
  * @param {Component} component
1715
1918
  */
1716
- addComponent(t) {
1717
- return t.gameObject = this, t.kernel = this.kernel, this.components.push(t), t.onAttach(), t;
1919
+ addComponent(e) {
1920
+ return e.gameObject = this, e.kernel = this.kernel, this.components.push(e), e.onAttach(), e;
1718
1921
  }
1719
1922
  /**
1720
1923
  * Get a component by class type.
1721
1924
  * @param {class} type
1722
1925
  */
1723
- getComponent(t) {
1724
- return this.components.find((e) => e instanceof t);
1926
+ getComponent(e) {
1927
+ return this.components.find((t) => t instanceof e);
1725
1928
  }
1726
- update(t) {
1727
- for (const e of this.components)
1728
- e.update && e.update(t);
1729
- for (const e of this.children)
1730
- e.update(t);
1929
+ update(e) {
1930
+ for (const t of this.components)
1931
+ t.update && t.update(e);
1932
+ for (const t of this.children)
1933
+ t.update(e);
1731
1934
  }
1732
- render(t) {
1733
- t.save(), t.translate(this.x, this.y), t.rotate(this.rotation), t.scale(this.scale.x, this.scale.y);
1734
- for (const e of this.components)
1735
- e.render && e.render(t);
1736
- for (const e of this.children)
1737
- e.render(t);
1738
- t.restore();
1935
+ render(e) {
1936
+ e.save(), e.translate(this.x, this.y), e.rotate(this.rotation), e.scale(this.scale.x, this.scale.y);
1937
+ for (const t of this.components)
1938
+ t.render && t.render(e);
1939
+ for (const t of this.children)
1940
+ t.render(e);
1941
+ e.restore();
1739
1942
  }
1740
1943
  }
1741
- class b {
1742
- constructor(t = 0, e = 0) {
1743
- this.x = t, this.y = e;
1944
+ class P {
1945
+ constructor(e = 0, t = 0) {
1946
+ this.x = e, this.y = t;
1744
1947
  }
1745
- add(t) {
1746
- return this.x += t.x, this.y += t.y, this;
1948
+ add(e) {
1949
+ return this.x += e.x, this.y += e.y, this;
1747
1950
  }
1748
- sub(t) {
1749
- return this.x -= t.x, this.y -= t.y, this;
1951
+ sub(e) {
1952
+ return this.x -= e.x, this.y -= e.y, this;
1750
1953
  }
1751
- scale(t) {
1752
- return this.x *= t, this.y *= t, this;
1954
+ scale(e) {
1955
+ return this.x *= e, this.y *= e, this;
1753
1956
  }
1754
- distance(t) {
1755
- const e = this.x - t.x, s = this.y - t.y;
1756
- return Math.sqrt(e * e + s * s);
1957
+ distance(e) {
1958
+ const t = this.x - e.x, s = this.y - e.y;
1959
+ return Math.sqrt(t * t + s * s);
1757
1960
  }
1758
1961
  normalize() {
1759
- const t = Math.sqrt(this.x * this.x + this.y * this.y);
1760
- return t > 0 && (this.x /= t, this.y /= t), this;
1962
+ const e = Math.sqrt(this.x * this.x + this.y * this.y);
1963
+ return e > 0 && (this.x /= e, this.y /= e), this;
1761
1964
  }
1762
1965
  clone() {
1763
- return new b(this.x, this.y);
1966
+ return new P(this.x, this.y);
1764
1967
  }
1765
- static distance(t, e) {
1766
- return Math.sqrt(Math.pow(e.x - t.x, 2) + Math.pow(e.y - t.y, 2));
1968
+ static distance(e, t) {
1969
+ return Math.sqrt(Math.pow(t.x - e.x, 2) + Math.pow(t.y - e.y, 2));
1767
1970
  }
1768
1971
  }
1769
- class z extends y {
1770
- constructor(t) {
1771
- super(), this.image = t, this.anchor = { x: 0.5, y: 0.5 }, this.width = t ? t.width : 0, this.height = t ? t.height : 0, this.frame = { x: 0, y: 0, w: this.width, h: this.height };
1972
+ class j extends y {
1973
+ constructor(e) {
1974
+ super(), this.image = e, this.anchor = { x: 0.5, y: 0.5 }, this.width = e ? e.width : 0, this.height = e ? e.height : 0, this.frame = { x: 0, y: 0, w: this.width, h: this.height };
1772
1975
  }
1773
- setFrame(t, e, s, i) {
1774
- this.frame = { x: t, y: e, w: s, h: i }, this.width = s, this.height = i;
1976
+ setFrame(e, t, s, i) {
1977
+ this.frame = { x: e, y: t, w: s, h: i }, this.width = s, this.height = i;
1775
1978
  }
1776
- setTexture(t) {
1777
- this.image = t, t && this.setFrame(0, 0, t.width, t.height);
1979
+ setTexture(e) {
1980
+ this.image = e, e && this.setFrame(0, 0, e.width, e.height);
1778
1981
  }
1779
- render(t) {
1982
+ render(e) {
1780
1983
  if (!this.image) return;
1781
- const e = this.frame.w, s = this.frame.h, i = -e * this.anchor.x, r = -s * this.anchor.y;
1782
- t.drawImage(
1984
+ const t = this.frame.w, s = this.frame.h, i = -t * this.anchor.x, r = -s * this.anchor.y;
1985
+ e.drawImage(
1783
1986
  this.image,
1784
1987
  this.frame.x,
1785
1988
  this.frame.y,
1786
- e,
1989
+ t,
1787
1990
  s,
1788
1991
  // Source
1789
1992
  i,
1790
1993
  r,
1791
- e,
1994
+ t,
1792
1995
  s
1793
1996
  // Destination
1794
1997
  );
1795
1998
  }
1796
1999
  }
1797
- class V extends y {
2000
+ class ee extends y {
1798
2001
  constructor() {
1799
2002
  super(), this.animations = {}, this.currentAnim = null, this.timer = 0, this.currentFrameIndex = 0, this.sprite = null;
1800
2003
  }
1801
2004
  onAttach() {
1802
- this.sprite = this.gameObject.getComponent(z), this.sprite || console.warn("Animator attached to GameObject without a Sprite component.");
2005
+ this.sprite = this.gameObject.getComponent(j), this.sprite || console.warn("Animator attached to GameObject without a Sprite component.");
1803
2006
  }
1804
- addAnimation(t, e, s = 10, i = !0) {
1805
- this.animations[t] = { frames: e, fps: s, loop: i };
2007
+ addAnimation(e, t, s = 10, i = !0) {
2008
+ this.animations[e] = { frames: t, fps: s, loop: i };
1806
2009
  }
1807
- play(t) {
1808
- this.currentAnim && this.currentAnim.name === t || this.animations[t] && (this.currentAnim = { name: t, ...this.animations[t] }, this.currentFrameIndex = 0, this.timer = 0, this._updateFrame());
2010
+ play(e) {
2011
+ this.currentAnim && this.currentAnim.name === e || this.animations[e] && (this.currentAnim = { name: e, ...this.animations[e] }, this.currentFrameIndex = 0, this.timer = 0, this._updateFrame());
1809
2012
  }
1810
- update(t) {
2013
+ update(e) {
1811
2014
  if (!this.currentAnim || !this.sprite) return;
1812
- this.timer += t;
1813
- const e = 1 / this.currentAnim.fps;
1814
- this.timer >= e && (this.timer -= e, this.currentFrameIndex++, this.currentFrameIndex >= this.currentAnim.frames.length && (this.currentAnim.loop ? this.currentFrameIndex = 0 : this.currentFrameIndex = this.currentAnim.frames.length - 1), this._updateFrame());
2015
+ this.timer += e;
2016
+ const t = 1 / this.currentAnim.fps;
2017
+ this.timer >= t && (this.timer -= t, this.currentFrameIndex++, this.currentFrameIndex >= this.currentAnim.frames.length && (this.currentAnim.loop ? this.currentFrameIndex = 0 : this.currentFrameIndex = this.currentAnim.frames.length - 1), this._updateFrame());
1815
2018
  }
1816
2019
  _updateFrame() {
1817
- const t = this.currentAnim.frames[this.currentFrameIndex];
1818
- this.sprite.setFrame(t.x, t.y, t.w, t.h);
2020
+ const e = this.currentAnim.frames[this.currentFrameIndex];
2021
+ this.sprite.setFrame(e.x, e.y, e.w, e.h);
1819
2022
  }
1820
2023
  }
1821
- class Q {
1822
- static createGround(t, e, s = "#333") {
2024
+ class te {
2025
+ static createGround(e, t, s = "#333") {
1823
2026
  const i = document.createElement("canvas");
1824
- i.width = t, i.height = e;
2027
+ i.width = e, i.height = t;
1825
2028
  const r = i.getContext("2d");
1826
- r.fillStyle = s, r.fillRect(0, 0, t, e);
2029
+ r.fillStyle = s, r.fillRect(0, 0, e, t);
1827
2030
  for (let n = 0; n < 1e3; n++) {
1828
2031
  r.fillStyle = Math.random() > 0.5 ? "#444" : "#222";
1829
2032
  const o = Math.random() * 4 + 2;
1830
- r.fillRect(Math.random() * t, Math.random() * e, o, o);
2033
+ r.fillRect(Math.random() * e, Math.random() * t, o, o);
1831
2034
  }
1832
2035
  r.strokeStyle = "rgba(255,255,255,0.05)", r.lineWidth = 2, r.beginPath();
1833
- for (let n = 0; n <= t; n += 64)
1834
- r.moveTo(n, 0), r.lineTo(n, e);
1835
2036
  for (let n = 0; n <= e; n += 64)
1836
- r.moveTo(0, n), r.lineTo(t, n);
2037
+ r.moveTo(n, 0), r.lineTo(n, t);
2038
+ for (let n = 0; n <= t; n += 64)
2039
+ r.moveTo(0, n), r.lineTo(e, n);
1837
2040
  return r.stroke(), i;
1838
2041
  }
1839
- static createCharacterSheet(t, e) {
2042
+ static createCharacterSheet(e, t) {
1840
2043
  const r = document.createElement("canvas");
1841
2044
  r.width = 64 * 4, r.height = 64;
1842
2045
  const n = r.getContext("2d");
1843
2046
  for (let o = 0; o < 4; o++) {
1844
- const h = o * 64, c = 0, d = h + 64 / 2, u = c + 64 / 2, m = o % 2 === 0 ? 0 : -2, f = o === 1 ? 5 : o === 3 ? -5 : 0;
1845
- n.fillStyle = t, n.beginPath(), n.arc(d, u + m, 20, 0, Math.PI * 2), n.fill(), n.fillStyle = e, n.beginPath(), n.arc(d - 15, u + m + f, 8, 0, Math.PI * 2), n.arc(d + 15, u + m - f, 8, 0, Math.PI * 2), n.fill(), n.fillStyle = j(t), n.beginPath(), n.arc(d, u + m, 15, 0, Math.PI * 2), n.fill(), e === "#333" && (n.fillStyle = "#000", n.fillRect(d + 10, u + m - 5, 25, 6));
2047
+ const c = o * 64, h = 0, l = c + 64 / 2, u = h + 64 / 2, m = o % 2 === 0 ? 0 : -2, f = o === 1 ? 5 : o === 3 ? -5 : 0;
2048
+ n.fillStyle = e, n.beginPath(), n.arc(l, u + m, 20, 0, Math.PI * 2), n.fill(), n.fillStyle = t, n.beginPath(), n.arc(l - 15, u + m + f, 8, 0, Math.PI * 2), n.arc(l + 15, u + m - f, 8, 0, Math.PI * 2), n.fill(), n.fillStyle = F(e), n.beginPath(), n.arc(l, u + m, 15, 0, Math.PI * 2), n.fill(), t === "#333" && (n.fillStyle = "#000", n.fillRect(l + 10, u + m - 5, 25, 6));
1846
2049
  }
1847
2050
  return r;
1848
2051
  }
1849
2052
  }
1850
- function j(a, t) {
2053
+ function F(a, e) {
1851
2054
  return a;
1852
2055
  }
1853
- class Z extends y {
1854
- constructor(t) {
1855
- super(), this.system = t;
2056
+ class se extends y {
2057
+ constructor(e) {
2058
+ super(), this.system = e;
1856
2059
  }
1857
2060
  /**
1858
2061
  * Trigger an effect at the GameObject's current position.
1859
2062
  * @param {string} effectName - 'confetti', 'fire', 'sparkle', etc.
1860
2063
  * @param {object} options - override options
1861
2064
  */
1862
- play(t, e = {}) {
2065
+ play(e, t = {}) {
1863
2066
  const s = this.gameObject.x, i = this.gameObject.y;
1864
- switch (t) {
2067
+ switch (e) {
1865
2068
  case "confetti":
1866
- this.system.confetti(s, i, e.count);
2069
+ this.system.confetti(s, i, t.count);
1867
2070
  break;
1868
2071
  case "fire":
1869
- this.system.fire(s, i, e.count);
2072
+ this.system.fire(s, i, t.count);
1870
2073
  break;
1871
2074
  case "sparkle":
1872
- this.system.sparkle(s, i, e.count, e.color);
2075
+ this.system.sparkle(s, i, t.count, t.color);
1873
2076
  break;
1874
2077
  case "blood":
1875
2078
  this.system.emit({
@@ -1886,62 +2089,62 @@ class Z extends y {
1886
2089
  this.system.emit({
1887
2090
  x: s,
1888
2091
  y: i,
1889
- ...e
2092
+ ...t
1890
2093
  });
1891
2094
  }
1892
2095
  }
1893
2096
  }
1894
- class tt extends y {
2097
+ class ie extends y {
1895
2098
  constructor() {
1896
2099
  super(), this.intensity = 0, this.duration = 0, this.timer = 0, this.originalPos = { x: 0, y: 0 }, this.isShaking = !1;
1897
2100
  }
1898
- shake(t = 5, e = 0.2) {
1899
- this.intensity = t, this.duration = e, this.timer = 0, this.isShaking = !0, this.timer === 0 && (this.originalPos.x = this.gameObject.x, this.originalPos.y = this.gameObject.y);
2101
+ shake(e = 5, t = 0.2) {
2102
+ this.intensity = e, this.duration = t, this.timer = 0, this.isShaking = !0, this.timer === 0 && (this.originalPos.x = this.gameObject.x, this.originalPos.y = this.gameObject.y);
1900
2103
  }
1901
- update(t) {
2104
+ update(e) {
1902
2105
  if (!this.isShaking) return;
1903
- if (this.timer += t, this.timer >= this.duration) {
2106
+ if (this.timer += e, this.timer >= this.duration) {
1904
2107
  this.isShaking = !1, this.gameObject.x = this.originalPos.x, this.gameObject.y = this.originalPos.y;
1905
2108
  return;
1906
2109
  }
1907
- const e = (Math.random() - 0.5) * 2 * this.intensity, s = (Math.random() - 0.5) * 2 * this.intensity;
1908
- this.gameObject.x = this.originalPos.x + e, this.gameObject.y = this.originalPos.y + s;
2110
+ const t = (Math.random() - 0.5) * 2 * this.intensity, s = (Math.random() - 0.5) * 2 * this.intensity;
2111
+ this.gameObject.x = this.originalPos.x + t, this.gameObject.y = this.originalPos.y + s;
1909
2112
  }
1910
2113
  }
1911
2114
  export {
1912
- V as Animator,
1913
- B as Collider,
2115
+ ee as Animator,
2116
+ H as Collider,
1914
2117
  y as Component,
1915
- P as DataService,
1916
- R as Director,
1917
- U as DopamineKernel,
2118
+ A as DataService,
2119
+ G as Director,
2120
+ B as DopamineKernel,
1918
2121
  p as EventBus,
1919
- W as Game,
1920
- Y as GameObject,
1921
- T as GameUI,
1922
- F as GlobalInput,
1923
- N as GlobalLoader,
1924
- G as GlobalPhysics,
1925
- H as IAudioSystem,
1926
- q as IParticleSystem,
1927
- X as IPhysicsSystem,
2122
+ Q as Game,
2123
+ Z as GameObject,
2124
+ D as GameUI,
2125
+ q as GlobalInput,
2126
+ K as GlobalLoader,
2127
+ $ as GlobalPhysics,
2128
+ W as IAudioSystem,
2129
+ Y as IParticleSystem,
2130
+ J as IPhysicsSystem,
1928
2131
  g as ISystem,
1929
- k as Input,
1930
- S as Loader,
1931
- Z as ParticleEmitter,
1932
- C as ParticleSystem,
1933
- _ as Physics,
1934
- A as PluginRegistry,
1935
- D as Renderer,
1936
- E as RewardSystem,
1937
- J as Scene,
1938
- tt as ScreenShake,
2132
+ M as Input,
2133
+ E as Loader,
2134
+ se as ParticleEmitter,
2135
+ T as ParticleSystem,
2136
+ b as Physics,
2137
+ U as PluginRegistry,
2138
+ R as Renderer,
2139
+ O as RewardSystem,
2140
+ V as Scene,
2141
+ ie as ScreenShake,
1939
2142
  I as SoundManager,
1940
- z as Sprite,
1941
- K as System,
1942
- O as SystemRegistry,
1943
- Q as TextureGenerator,
1944
- L as Ticker,
1945
- b as Vector2,
1946
- $ as default
2143
+ j as Sprite,
2144
+ X as System,
2145
+ L as SystemRegistry,
2146
+ te as TextureGenerator,
2147
+ z as Ticker,
2148
+ P as Vector2,
2149
+ N as default
1947
2150
  };