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