dopaminejs 1.0.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/README.md ADDED
@@ -0,0 +1,84 @@
1
+ # DopamineJS 🧠✨
2
+
3
+ A lightweight, modular JavaScript library for adding "juice", rewards, and satisfying feedback to your HTML5 games.
4
+
5
+ ## Features
6
+
7
+ - **Reward System**: XP, Levels, Achievements, and Daily Streaks.
8
+ - **Game UI**: Beautiful, animated overlays for level-ups, notifications, and HUDs.
9
+ - **Particle Effects**: Confetti, sparkles, fire, and coin showers.
10
+ - **Sound Manager**: Synthesized retro sound effects (no external assets needed).
11
+ - **Data Persistence**: Automatic local storage management.
12
+
13
+ ## Installation
14
+
15
+ ### Via npm (Coming Soon)
16
+ ```bash
17
+ npm install dopaminejs
18
+ ```
19
+
20
+ ### Direct Include
21
+ Include the built script and CSS in your HTML:
22
+ ```html
23
+ <link rel="stylesheet" href="dist/dopamine.css">
24
+ <script src="dist/dopamine.js"></script>
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```javascript
30
+ import Dopamine from 'dopaminejs';
31
+
32
+ // Initialize
33
+ const game = new Dopamine();
34
+ await game.init();
35
+
36
+ // Access modules
37
+ const { rewardSystem, particleSystem, soundManager, gameUI } = game;
38
+
39
+ // 1. Award XP
40
+ await rewardSystem.addXP(100, 'Enemy Defeated');
41
+
42
+ // 2. Show Visual Feedback
43
+ particleSystem.confetti(window.innerWidth / 2, window.innerHeight / 2);
44
+
45
+ // 3. Play Sound
46
+ soundManager.playSuccess();
47
+
48
+ // 4. Show Notification
49
+ gameUI.showNotification('Level Up!', 'legendary');
50
+ ```
51
+
52
+ ## Modules
53
+
54
+ ### RewardSystem
55
+ Manages player progression.
56
+ - `addXP(amount, reason)`
57
+ - `recordGame(gameId, result)`
58
+ - `unlockAchievement(id)`
59
+
60
+ ### ParticleSystem
61
+ Visual effects on a dedicated canvas layer.
62
+ - `confetti(x, y)`
63
+ - `sparkle(x, y)`
64
+ - `fire(x, y)`
65
+ - `coinShower(x, y)`
66
+
67
+ ### SoundManager
68
+ Synthesized audio effects.
69
+ - `playJump()`
70
+ - `playScore()`
71
+ - `playGameOver()`
72
+ - `toggleMute()`
73
+
74
+ ### GameUI
75
+ Overlay system for feedback.
76
+ - `showNotification(msg, type)`
77
+ - `showLevelUp(oldLvl, newLvl)`
78
+ - `showSummary(data)`
79
+
80
+ ## License
81
+ MIT
82
+
83
+ ## Support
84
+ [![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/T6T01CQT4R)
@@ -0,0 +1,627 @@
1
+ class v {
2
+ constructor() {
3
+ this.storage = window.localStorage;
4
+ }
5
+ /**
6
+ * Save data to local storage
7
+ * @param {string} key
8
+ * @param {any} data
9
+ */
10
+ async save(e, t) {
11
+ try {
12
+ const i = JSON.stringify(t);
13
+ return this.storage.setItem(e, i), !0;
14
+ } catch (i) {
15
+ return console.error("Error saving data:", i), !1;
16
+ }
17
+ }
18
+ /**
19
+ * Load data from local storage
20
+ * @param {string} key
21
+ * @param {any} defaultValue
22
+ */
23
+ async load(e, t = null) {
24
+ try {
25
+ const i = this.storage.getItem(e);
26
+ return i ? JSON.parse(i) : t;
27
+ } catch (i) {
28
+ return console.error("Error loading data:", i), t;
29
+ }
30
+ }
31
+ /**
32
+ * Clear specific data
33
+ * @param {string} key
34
+ */
35
+ async clear(e) {
36
+ this.storage.removeItem(e);
37
+ }
38
+ }
39
+ const m = new v();
40
+ class u {
41
+ constructor() {
42
+ this.player = null, this.achievements = this._initAchievements(), this.listeners = [];
43
+ }
44
+ /**
45
+ * Initialize the reward system for current player
46
+ * @returns {Promise<Object>} Player data
47
+ */
48
+ async init() {
49
+ return this.player = await m.load("player", this._getDefaultPlayer()), this._updateDailyStreak(), await this.save(), this.player;
50
+ }
51
+ /**
52
+ * Get default player data structure
53
+ */
54
+ _getDefaultPlayer() {
55
+ return {
56
+ name: "Player",
57
+ xp: 0,
58
+ level: 1,
59
+ totalGamesPlayed: 0,
60
+ createdAt: Date.now(),
61
+ lastPlayedAt: Date.now(),
62
+ streak: {
63
+ current: 1,
64
+ longest: 1,
65
+ lastPlayDate: this._getTodayDateString()
66
+ },
67
+ achievements: {},
68
+ // achievement_id: { unlockedAt: timestamp, seen: boolean }
69
+ stats: {
70
+ // Dynamic stats object, games will add their own keys
71
+ }
72
+ };
73
+ }
74
+ /**
75
+ * Save player data
76
+ */
77
+ async save() {
78
+ this.player && (this.player.lastPlayedAt = Date.now(), await m.save("player", this.player));
79
+ }
80
+ /**
81
+ * Add XP and handle level ups
82
+ * @param {number} amount - XP to add
83
+ * @param {string} reason - Why XP was awarded (for notifications)
84
+ * @returns {Object} { leveledUp: boolean, newLevel: number, xpGained: number }
85
+ */
86
+ async addXP(e, t = "") {
87
+ const i = this.player.level;
88
+ this.player.xp += e;
89
+ const s = this._calculateLevel(this.player.xp), a = s > i;
90
+ return a && (this.player.level = s), await this.save(), this._emit("xp_gained", { amount: e, reason: t, leveledUp: a, newLevel: s }), a && this._emit("level_up", { oldLevel: i, newLevel: s }), { leveledUp: a, newLevel: s, xpGained: e };
91
+ }
92
+ /**
93
+ * Calculate level from XP (exponential curve)
94
+ */
95
+ _calculateLevel(e) {
96
+ const t = Math.floor((1 + Math.sqrt(1 + 8 * e / 50)) / 2);
97
+ return Math.max(1, t);
98
+ }
99
+ /**
100
+ * Get XP needed for next level
101
+ */
102
+ getXPForNextLevel() {
103
+ const e = this.player.level + 1, t = 50 * e * (e - 1), i = 50 * this.player.level * (this.player.level - 1);
104
+ return {
105
+ total: t,
106
+ needed: t - this.player.xp,
107
+ progress: (this.player.xp - i) / (t - i)
108
+ };
109
+ }
110
+ /**
111
+ * Record game result and award XP
112
+ * @param {string} gameName - Unique ID for the game
113
+ * @param {Object} result - Game-specific result data
114
+ */
115
+ async recordGame(e, t) {
116
+ this.player.stats[e] || (this.player.stats[e] = { totalPlays: 0, highScore: 0 });
117
+ const i = this.player.stats[e];
118
+ i.totalPlays++, this.player.totalGamesPlayed++, t.score !== void 0 && t.score > i.highScore && (i.highScore = t.score, this._emit("new_high_score", { gameName: e, score: t.score })), Object.keys(t).forEach((a) => {
119
+ a !== "score" && (i[a] = (i[a] || 0) + (typeof t[a] == "number" ? t[a] : 0));
120
+ });
121
+ let s = 10;
122
+ t.score && (s += Math.floor(t.score / 5)), s = Math.floor(s * this.getStreakMultiplier()), await this.addXP(s, `Played ${e}`), await this.checkAchievements(e, t), await this.save();
123
+ }
124
+ /**
125
+ * Check and unlock achievements
126
+ */
127
+ async checkAchievements(e, t) {
128
+ const i = [];
129
+ for (const [s, a] of Object.entries(this.achievements))
130
+ this.player.achievements[s] || a.check(this.player, e, t) && (await this.unlockAchievement(s), i.push(a));
131
+ return i;
132
+ }
133
+ /**
134
+ * Unlock an achievement
135
+ */
136
+ async unlockAchievement(e) {
137
+ const t = this.achievements[e];
138
+ return t ? (this.player.achievements[e] = {
139
+ unlockedAt: Date.now(),
140
+ seen: !1
141
+ }, await this.addXP(t.xp, `Achievement: ${t.name}`), this._emit("achievement_unlocked", t), await this.save(), !0) : !1;
142
+ }
143
+ /**
144
+ * Get all unlocked achievements
145
+ */
146
+ getUnlockedAchievements() {
147
+ return Object.entries(this.player.achievements).map(([e, t]) => ({
148
+ ...this.achievements[e],
149
+ id: e,
150
+ ...t
151
+ }));
152
+ }
153
+ /**
154
+ * Get achievements user hasn't seen the notification for yet
155
+ */
156
+ getUnseenAchievements() {
157
+ return this.getUnlockedAchievements().filter((e) => !e.seen);
158
+ }
159
+ /**
160
+ * Mark achievements as seen
161
+ */
162
+ async markAchievementsSeen(e) {
163
+ for (const t of e)
164
+ this.player.achievements[t] && (this.player.achievements[t].seen = !0);
165
+ await this.save();
166
+ }
167
+ /**
168
+ * Update daily streak
169
+ */
170
+ _updateDailyStreak() {
171
+ const e = this._getTodayDateString(), t = this.player.streak.lastPlayDate;
172
+ if (t === e)
173
+ return;
174
+ const i = this._getYesterdayDateString();
175
+ t === i ? (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;
176
+ }
177
+ /**
178
+ * Get streak multiplier for XP
179
+ */
180
+ getStreakMultiplier() {
181
+ const e = this.player.streak.current;
182
+ return e >= 7 ? 2 : e >= 5 ? 1.5 : e >= 3 ? 1.25 : 1;
183
+ }
184
+ /**
185
+ * Get today's date as YYYY-MM-DD string
186
+ */
187
+ _getTodayDateString() {
188
+ return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
189
+ }
190
+ /**
191
+ * Get yesterday's date as YYYY-MM-DD string
192
+ */
193
+ _getYesterdayDateString() {
194
+ const e = /* @__PURE__ */ new Date();
195
+ return e.setDate(e.getDate() - 1), e.toISOString().split("T")[0];
196
+ }
197
+ /**
198
+ * Event system for UI updates
199
+ */
200
+ on(e, t) {
201
+ this.listeners.push({ event: e, callback: t });
202
+ }
203
+ _emit(e, t) {
204
+ this.listeners.filter((i) => i.event === e).forEach((i) => i.callback(t));
205
+ }
206
+ /**
207
+ * Initialize achievement definitions
208
+ * Can be extended by games
209
+ */
210
+ _initAchievements() {
211
+ return {
212
+ // Universal achievements
213
+ first_game: {
214
+ name: "First Steps",
215
+ description: "Play your first game",
216
+ icon: "🎮",
217
+ xp: 50,
218
+ check: (e) => e.totalGamesPlayed >= 1
219
+ },
220
+ level_5: {
221
+ name: "Rising Star",
222
+ description: "Reach level 5",
223
+ icon: "⭐",
224
+ xp: 100,
225
+ check: (e) => e.level >= 5
226
+ },
227
+ level_10: {
228
+ name: "Expert Player",
229
+ description: "Reach level 10",
230
+ icon: "🏆",
231
+ xp: 250,
232
+ check: (e) => e.level >= 10
233
+ },
234
+ streak_3: {
235
+ name: "Dedicated",
236
+ description: "Play 3 days in a row",
237
+ icon: "🔥",
238
+ xp: 100,
239
+ check: (e) => e.streak.current >= 3
240
+ },
241
+ streak_7: {
242
+ name: "Unstoppable",
243
+ description: "Play 7 days in a row",
244
+ icon: "💥",
245
+ xp: 300,
246
+ check: (e) => e.streak.current >= 7
247
+ }
248
+ };
249
+ }
250
+ /**
251
+ * Register custom achievements
252
+ * @param {Object} achievements
253
+ */
254
+ registerAchievements(e) {
255
+ this.achievements = { ...this.achievements, ...e };
256
+ }
257
+ }
258
+ const y = new u();
259
+ class f {
260
+ constructor(e = "dopamine-particle-canvas") {
261
+ this.canvasId = e, this.canvas = document.getElementById(e), this.canvas || (this.canvas = document.createElement("canvas"), this.canvas.id = e, this.canvas.style.position = "fixed", this.canvas.style.top = "0", this.canvas.style.left = "0", this.canvas.style.width = "100%", this.canvas.style.height = "100%", this.canvas.style.pointerEvents = "none", this.canvas.style.zIndex = "9999", document.body.appendChild(this.canvas)), this.ctx = this.canvas.getContext("2d"), this.particles = [], this.isAnimating = !1, this._resize(), window.addEventListener("resize", () => this._resize());
262
+ }
263
+ _resize() {
264
+ this.canvas.width = window.innerWidth, this.canvas.height = window.innerHeight;
265
+ }
266
+ /**
267
+ * Confetti explosion (for achievements, level ups)
268
+ */
269
+ confetti(e, t, i = 50) {
270
+ const s = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#f7dc6f", "#c06c84", "#6c5ce7"];
271
+ for (let a = 0; a < i; a++)
272
+ this.particles.push({
273
+ x: e,
274
+ y: t,
275
+ vx: (Math.random() - 0.5) * 10,
276
+ vy: (Math.random() - 0.5) * 10 - 5,
277
+ rotation: Math.random() * Math.PI * 2,
278
+ rotationSpeed: (Math.random() - 0.5) * 0.2,
279
+ color: s[Math.floor(Math.random() * s.length)],
280
+ size: Math.random() * 8 + 4,
281
+ life: 1,
282
+ decay: 0.015 + Math.random() * 0.01,
283
+ gravity: 0.3,
284
+ type: "confetti"
285
+ });
286
+ this._startAnimation();
287
+ }
288
+ /**
289
+ * Sparkle effect (for score milestones, combos)
290
+ */
291
+ sparkle(e, t, i = 20, s = "#ffd700") {
292
+ for (let a = 0; a < i; a++) {
293
+ const n = Math.PI * 2 / i * a, o = 2 + Math.random() * 3;
294
+ this.particles.push({
295
+ x: e,
296
+ y: t,
297
+ vx: Math.cos(n) * o,
298
+ vy: Math.sin(n) * o,
299
+ color: s,
300
+ size: Math.random() * 4 + 2,
301
+ life: 1,
302
+ decay: 0.02,
303
+ gravity: -0.1,
304
+ // Slight upward drift
305
+ type: "spark"
306
+ });
307
+ }
308
+ this._startAnimation();
309
+ }
310
+ /**
311
+ * Coin shower (for collecting coins)
312
+ */
313
+ coinShower(e, t, i = 10) {
314
+ for (let s = 0; s < i; s++)
315
+ this.particles.push({
316
+ x: e + (Math.random() - 0.5) * 100,
317
+ y: t - Math.random() * 50,
318
+ vx: (Math.random() - 0.5) * 3,
319
+ vy: Math.random() * -2 - 3,
320
+ color: "#ffd700",
321
+ size: 12,
322
+ life: 1,
323
+ decay: 0.012,
324
+ gravity: 0.4,
325
+ type: "coin"
326
+ });
327
+ this._startAnimation();
328
+ }
329
+ /**
330
+ * Fire particles (for streaks, combos)
331
+ */
332
+ fire(e, t, i = 15) {
333
+ const s = ["#ff6b00", "#ff8800", "#ffaa00", "#ff4400"];
334
+ for (let a = 0; a < i; a++)
335
+ this.particles.push({
336
+ x: e + (Math.random() - 0.5) * 20,
337
+ y: t,
338
+ vx: (Math.random() - 0.5) * 2,
339
+ vy: -Math.random() * 3 - 2,
340
+ color: s[Math.floor(Math.random() * s.length)],
341
+ size: Math.random() * 6 + 3,
342
+ life: 1,
343
+ decay: 0.025,
344
+ gravity: -0.2,
345
+ // Upward
346
+ type: "fire"
347
+ });
348
+ this._startAnimation();
349
+ }
350
+ /**
351
+ * Star burst (for "perfect" moments)
352
+ */
353
+ starBurst(e, t, i = 8) {
354
+ for (let s = 0; s < i; s++) {
355
+ const a = Math.PI * 2 / i * s, n = 4;
356
+ this.particles.push({
357
+ x: e,
358
+ y: t,
359
+ vx: Math.cos(a) * n,
360
+ vy: Math.sin(a) * n,
361
+ rotation: a,
362
+ color: "#ffffff",
363
+ size: 15,
364
+ life: 1,
365
+ decay: 0.03,
366
+ gravity: 0,
367
+ type: "star"
368
+ });
369
+ }
370
+ this._startAnimation();
371
+ }
372
+ _startAnimation() {
373
+ this.isAnimating || (this.isAnimating = !0, this._animate());
374
+ }
375
+ _animate() {
376
+ this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
377
+ for (let e = this.particles.length - 1; e >= 0; e--) {
378
+ const t = this.particles[e];
379
+ 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) {
380
+ this.particles.splice(e, 1);
381
+ continue;
382
+ }
383
+ this.ctx.save(), this.ctx.globalAlpha = t.life, this.ctx.fillStyle = t.color, t.type === "confetti" ? (this.ctx.translate(t.x, t.y), this.ctx.rotate(t.rotation), this.ctx.fillRect(-t.size / 2, -t.size / 2, t.size, t.size / 2)) : t.type === "coin" ? (this.ctx.beginPath(), this.ctx.arc(t.x, t.y, t.size, 0, Math.PI * 2), this.ctx.fill(), this.ctx.fillStyle = "#ffee88", this.ctx.beginPath(), this.ctx.arc(t.x - t.size / 3, t.y - t.size / 3, t.size / 3, 0, Math.PI * 2), this.ctx.fill()) : t.type === "star" ? this._drawStar(t.x, t.y, 5, t.size, t.size / 2, t.rotation) : (this.ctx.beginPath(), this.ctx.arc(t.x, t.y, t.size, 0, Math.PI * 2), this.ctx.fill()), this.ctx.restore();
384
+ }
385
+ this.particles.length > 0 ? requestAnimationFrame(() => this._animate()) : this.isAnimating = !1;
386
+ }
387
+ _drawStar(e, t, i, s, a, n) {
388
+ let o = Math.PI / 2 * 3 + n;
389
+ const d = Math.PI / i;
390
+ this.ctx.beginPath(), this.ctx.moveTo(e, t - s);
391
+ for (let p = 0; p < i; p++) {
392
+ let c = e + Math.cos(o) * s, h = t + Math.sin(o) * s;
393
+ this.ctx.lineTo(c, h), o += d, c = e + Math.cos(o) * a, h = t + Math.sin(o) * a, this.ctx.lineTo(c, h), o += d;
394
+ }
395
+ this.ctx.lineTo(e, t - s), this.ctx.closePath(), this.ctx.fill();
396
+ }
397
+ /**
398
+ * Clear all particles
399
+ */
400
+ clear() {
401
+ this.particles = [], this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
402
+ }
403
+ }
404
+ const l = new f();
405
+ class g {
406
+ constructor() {
407
+ this.container = null, this.xpBar = null, this.levelDisplay = null, this.streakDisplay = null, this.init();
408
+ }
409
+ /**
410
+ * Initialize UI overlay
411
+ */
412
+ init() {
413
+ this.container = document.createElement("div"), this.container.id = "game-ui-overlay", this.container.innerHTML = `
414
+ <div class="game-ui-top-bar">
415
+ <div class="level-badge" id="level-badge">
416
+ <span class="level-label">LVL</span>
417
+ <span class="level-number" id="level-number">1</span>
418
+ </div>
419
+
420
+ <div class="xp-container">
421
+ <div class="xp-bar-bg">
422
+ <div class="xp-bar-fill" id="xp-bar-fill" style="width: 0%"></div>
423
+ </div>
424
+ <div class="xp-text" id="xp-text">0 /100 XP</div>
425
+ </div>
426
+
427
+ <div class="streak-badge" id="streak-badge">
428
+ <span class="streak-icon">🔥</span>
429
+ <span class="streak-number" id="streak-number">1</span>
430
+ </div>
431
+ </div>
432
+ `, 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");
433
+ }
434
+ /**
435
+ * Update XP bar
436
+ */
437
+ updateXP(e, t, i) {
438
+ const s = e / i * 100;
439
+ this.xpBar.style.width = `${Math.min(100, s)}%`, this.xpText.textContent = `${e} / ${i} XP`, this.xpBar.style.animation = "none", setTimeout(() => {
440
+ this.xpBar.style.animation = "xp-pulse 0.3s ease-out";
441
+ }, 10);
442
+ }
443
+ /**
444
+ * Update level display
445
+ */
446
+ updateLevel(e) {
447
+ this.levelDisplay.textContent = e;
448
+ const t = document.getElementById("level-badge");
449
+ t.style.animation = "none", setTimeout(() => {
450
+ t.style.animation = "level-pulse 0.5s ease-out";
451
+ }, 10);
452
+ }
453
+ /**
454
+ * Update streak display
455
+ */
456
+ updateStreak(e) {
457
+ this.streakDisplay.textContent = e;
458
+ const t = document.getElementById("streak-badge");
459
+ e >= 7 ? t.style.background = "linear-gradient(135deg, #ff6b00 0%, #ff4400 100%)" : e >= 3 && (t.style.background = "linear-gradient(135deg, #ff8800 0%, #ff6b00 100%)");
460
+ }
461
+ /**
462
+ * Show achievement unlocked popup
463
+ */
464
+ showAchievement(e) {
465
+ const t = document.createElement("div");
466
+ t.className = "achievement-popup", t.innerHTML = `
467
+ <div class="achievement-shine"></div>
468
+ <div class="achievement-content">
469
+ <div class="achievement-icon">${e.icon}</div>
470
+ <div class="achievement-info">
471
+ <div class="achievement-title">Achievement Unlocked!</div>
472
+ <div class="achievement-name">${e.name}</div>
473
+ <div class="achievement-desc">${e.description}</div>
474
+ <div class="achievement-xp">+${e.xp} XP</div>
475
+ </div>
476
+ </div>
477
+ `, document.body.appendChild(t), setTimeout(() => {
478
+ const i = t.getBoundingClientRect();
479
+ l.confetti(i.left + i.width / 2, i.top + i.height / 2, 40);
480
+ }, 300), setTimeout(() => {
481
+ t.style.animation = "slideOut 0.3s ease-in forwards", setTimeout(() => t.remove(), 300);
482
+ }, 4e3);
483
+ }
484
+ /**
485
+ * Show level up screen
486
+ */
487
+ showLevelUp(e, t) {
488
+ const i = document.createElement("div");
489
+ i.className = "level-up-overlay", i.innerHTML = `
490
+ <div class="level-up-content">
491
+ <div class="level-up-title">LEVEL UP!</div>
492
+ <div class="level-up-number">${t}</div>
493
+ <div class="level-up-subtitle">Amazing progress!</div>
494
+ </div>
495
+ `, document.body.appendChild(i), setTimeout(() => {
496
+ for (let s = 0; s < 5; s++)
497
+ setTimeout(() => {
498
+ const a = Math.random() * window.innerWidth, n = Math.random() * window.innerHeight * 0.6;
499
+ l.confetti(a, n, 30);
500
+ }, s * 200);
501
+ }, 300), setTimeout(() => {
502
+ i.style.opacity = "0", setTimeout(() => i.remove(), 500);
503
+ }, 2500);
504
+ }
505
+ /**
506
+ * Show floating text (e.g., "+50 XP", "COMBO!")
507
+ */
508
+ showFloatingText(e, t, i, s = "#ffd700", a = "24px") {
509
+ const n = document.createElement("div");
510
+ n.className = "floating-text", n.textContent = e, n.style.left = t + "px", n.style.top = i + "px", n.style.color = s, n.style.fontSize = a, document.body.appendChild(n), setTimeout(() => n.remove(), 2e3);
511
+ }
512
+ /**
513
+ * Show combo multiplier
514
+ */
515
+ showCombo(e, t, i) {
516
+ this.showFloatingText(`${e}× COMBO!`, t, i, "#ff6b6b", "32px"), l.fire(t, i, 20);
517
+ }
518
+ /**
519
+ * Show "near miss"indicator
520
+ */
521
+ showNearMiss(e, t) {
522
+ this.showFloatingText("CLOSE CALL!", e, t, "#45b7d1", "20px"), l.sparkle(e, t, 15, "#45b7d1");
523
+ }
524
+ /**
525
+ * Show lucky moment (random multiplier)
526
+ */
527
+ showLuckyMoment(e, t, i) {
528
+ this.showFloatingText(`LUCKY ${e}×!`, t, i, "#ffd700", "36px"), l.starBurst(t, i, 12), l.confetti(t, i, 25);
529
+ }
530
+ /**
531
+ * Show generic notification
532
+ */
533
+ showNotification(e, t = "normal") {
534
+ const i = window.innerWidth / 2, s = window.innerHeight / 3;
535
+ t === "legendary" ? this.showLuckyMoment(e, i, s) : t === "rare" ? this.showCombo(e, i, s) : this.showFloatingText(e, i, s, "#fff", "24px");
536
+ }
537
+ /**
538
+ * Show Game Over Summary
539
+ */
540
+ showSummary(e) {
541
+ const t = document.createElement("div");
542
+ 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";
543
+ let i = "";
544
+ e.metrics && (i = Object.entries(e.metrics).map(([s, a]) => `<div style="display:flex;justify-content:space-between;width:100%"><span>${s}:</span> <strong>${a}</strong></div>`).join("")), t.innerHTML = `
545
+ <h1>Game Over</h1>
546
+ <div style="font-size: 3rem; font-weight: bold; color: #e06020; margin: 10px 0;">${e.score}</div>
547
+ <div style="width: 100%; margin-bottom: 20px;">
548
+ ${i}
549
+ </div>
550
+ <button class="btn" onclick="location.reload()">Play Again</button>
551
+ <button class="btn" onclick="window.location.href='index.html'" style="margin-top: 10px; background: #543847;">Exit</button>
552
+ `, document.body.appendChild(t);
553
+ }
554
+ }
555
+ const x = new g();
556
+ class w {
557
+ constructor() {
558
+ this.audioContext = null, this.muted = localStorage.getItem("dopamineSoundsMuted") === "true";
559
+ }
560
+ /**
561
+ * Initialize Audio Context (must be called after user interaction)
562
+ */
563
+ initAudio() {
564
+ this.audioContext || (this.audioContext = new (window.AudioContext || window.webkitAudioContext)());
565
+ }
566
+ /**
567
+ * Toggle mute state
568
+ * @returns {boolean} New mute state
569
+ */
570
+ toggleMute() {
571
+ return this.muted = !this.muted, localStorage.setItem("dopamineSoundsMuted", this.muted), this.muted;
572
+ }
573
+ /**
574
+ * Play a synthesized tone
575
+ * @param {number} frequency
576
+ * @param {number} duration
577
+ * @param {string} type - 'sine', 'square', 'sawtooth', 'triangle'
578
+ * @param {number} volume
579
+ */
580
+ playTone(e, t, i = "sine", s = 0.3) {
581
+ if (this.muted) return;
582
+ this.initAudio();
583
+ const a = this.audioContext.createOscillator(), n = this.audioContext.createGain();
584
+ a.connect(n), n.connect(this.audioContext.destination), a.frequency.value = e, a.type = i, n.gain.setValueAtTime(s, this.audioContext.currentTime), n.gain.exponentialRampToValueAtTime(0.01, this.audioContext.currentTime + t), a.start(this.audioContext.currentTime), a.stop(this.audioContext.currentTime + t);
585
+ }
586
+ // --- Presets ---
587
+ playJump() {
588
+ this.playTone(400, 0.1, "square", 0.2);
589
+ }
590
+ playScore() {
591
+ this.playTone(800, 0.15, "sine", 0.25), setTimeout(() => this.playTone(1e3, 0.15, "sine", 0.25), 50);
592
+ }
593
+ playGameOver() {
594
+ 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);
595
+ }
596
+ playClick() {
597
+ this.playTone(600, 0.05, "sine", 0.1);
598
+ }
599
+ playSuccess() {
600
+ this.playTone(600, 0.1, "sine", 0.2), setTimeout(() => this.playTone(800, 0.2, "sine", 0.2), 100);
601
+ }
602
+ playError() {
603
+ this.playTone(200, 0.2, "sawtooth", 0.3);
604
+ }
605
+ }
606
+ const b = new w();
607
+ class M {
608
+ constructor(e = {}) {
609
+ this.config = e;
610
+ }
611
+ async init() {
612
+ return await y.init(), console.log("DopamineJS Initialized 🚀"), {
613
+ rewardSystem: y,
614
+ gameUI: x,
615
+ particleSystem: l,
616
+ soundManager: b
617
+ };
618
+ }
619
+ }
620
+ export {
621
+ m as dataService,
622
+ M as default,
623
+ x as gameUI,
624
+ l as particleSystem,
625
+ y as rewardSystem,
626
+ b as soundManager
627
+ };