bonkjs 0.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.
Files changed (88) hide show
  1. package/README.md +94 -0
  2. package/dist/audio/AudioManager.d.ts +105 -0
  3. package/dist/audio/AudioManager.d.ts.map +1 -0
  4. package/dist/audio/AudioSource.d.ts +69 -0
  5. package/dist/audio/AudioSource.d.ts.map +1 -0
  6. package/dist/audio/index.d.ts +3 -0
  7. package/dist/audio/index.d.ts.map +1 -0
  8. package/dist/bonkjs.js +4307 -0
  9. package/dist/bonkjs.js.map +1 -0
  10. package/dist/devtools/Tweaker.d.ts +61 -0
  11. package/dist/devtools/Tweaker.d.ts.map +1 -0
  12. package/dist/devtools/TweakerOverlay.d.ts +66 -0
  13. package/dist/devtools/TweakerOverlay.d.ts.map +1 -0
  14. package/dist/devtools/index.d.ts +3 -0
  15. package/dist/devtools/index.d.ts.map +1 -0
  16. package/dist/devtools/tweaker-styles.d.ts +5 -0
  17. package/dist/devtools/tweaker-styles.d.ts.map +1 -0
  18. package/dist/devtools/types.d.ts +37 -0
  19. package/dist/devtools/types.d.ts.map +1 -0
  20. package/dist/index.d.ts +13 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/input/Input.d.ts +114 -0
  23. package/dist/input/Input.d.ts.map +1 -0
  24. package/dist/input/Keys.d.ts +63 -0
  25. package/dist/input/Keys.d.ts.map +1 -0
  26. package/dist/input/index.d.ts +3 -0
  27. package/dist/input/index.d.ts.map +1 -0
  28. package/dist/math/index.d.ts +2 -0
  29. package/dist/math/index.d.ts.map +1 -0
  30. package/dist/math/vec2.d.ts +27 -0
  31. package/dist/math/vec2.d.ts.map +1 -0
  32. package/dist/physics/CollisionLayers.d.ts +25 -0
  33. package/dist/physics/CollisionLayers.d.ts.map +1 -0
  34. package/dist/physics/MatterPhysicsWorld.d.ts +24 -0
  35. package/dist/physics/MatterPhysicsWorld.d.ts.map +1 -0
  36. package/dist/physics/PhysicsWorld.d.ts +101 -0
  37. package/dist/physics/PhysicsWorld.d.ts.map +1 -0
  38. package/dist/physics/RigidBody.d.ts +72 -0
  39. package/dist/physics/RigidBody.d.ts.map +1 -0
  40. package/dist/physics/index.d.ts +6 -0
  41. package/dist/physics/index.d.ts.map +1 -0
  42. package/dist/render/AnimatedSprite.d.ts +87 -0
  43. package/dist/render/AnimatedSprite.d.ts.map +1 -0
  44. package/dist/render/Camera.d.ts +76 -0
  45. package/dist/render/Camera.d.ts.map +1 -0
  46. package/dist/render/PixiRenderer.d.ts +80 -0
  47. package/dist/render/PixiRenderer.d.ts.map +1 -0
  48. package/dist/render/Renderer.d.ts +151 -0
  49. package/dist/render/Renderer.d.ts.map +1 -0
  50. package/dist/render/Sprite.d.ts +49 -0
  51. package/dist/render/Sprite.d.ts.map +1 -0
  52. package/dist/render/index.d.ts +6 -0
  53. package/dist/render/index.d.ts.map +1 -0
  54. package/dist/runtime/EventSystem.d.ts +38 -0
  55. package/dist/runtime/EventSystem.d.ts.map +1 -0
  56. package/dist/runtime/Game.d.ts +59 -0
  57. package/dist/runtime/Game.d.ts.map +1 -0
  58. package/dist/runtime/Scheduler.d.ts +50 -0
  59. package/dist/runtime/Scheduler.d.ts.map +1 -0
  60. package/dist/runtime/Time.d.ts +30 -0
  61. package/dist/runtime/Time.d.ts.map +1 -0
  62. package/dist/runtime/Transform.d.ts +47 -0
  63. package/dist/runtime/Transform.d.ts.map +1 -0
  64. package/dist/runtime/index.d.ts +6 -0
  65. package/dist/runtime/index.d.ts.map +1 -0
  66. package/dist/types.d.ts +54 -0
  67. package/dist/types.d.ts.map +1 -0
  68. package/dist/ui/UIElement.d.ts +211 -0
  69. package/dist/ui/UIElement.d.ts.map +1 -0
  70. package/dist/ui/UIManager.d.ts +102 -0
  71. package/dist/ui/UIManager.d.ts.map +1 -0
  72. package/dist/ui/index.d.ts +11 -0
  73. package/dist/ui/index.d.ts.map +1 -0
  74. package/dist/ui/layout/UIHBox.d.ts +42 -0
  75. package/dist/ui/layout/UIHBox.d.ts.map +1 -0
  76. package/dist/ui/layout/UIVBox.d.ts +52 -0
  77. package/dist/ui/layout/UIVBox.d.ts.map +1 -0
  78. package/dist/ui/primitives/UIButton.d.ts +77 -0
  79. package/dist/ui/primitives/UIButton.d.ts.map +1 -0
  80. package/dist/ui/primitives/UIImage.d.ts +54 -0
  81. package/dist/ui/primitives/UIImage.d.ts.map +1 -0
  82. package/dist/ui/primitives/UIPanel.d.ts +71 -0
  83. package/dist/ui/primitives/UIPanel.d.ts.map +1 -0
  84. package/dist/ui/primitives/UIText.d.ts +57 -0
  85. package/dist/ui/primitives/UIText.d.ts.map +1 -0
  86. package/dist/ui/types.d.ts +138 -0
  87. package/dist/ui/types.d.ts.map +1 -0
  88. package/package.json +54 -0
package/dist/bonkjs.js ADDED
@@ -0,0 +1,4307 @@
1
+ import { Howler as D, Howl as X } from "howler";
2
+ import { Application as Y, Container as A, Graphics as I, Texture as E, Sprite as S, Rectangle as H, Assets as K, Text as q, TextStyle as Z } from "pixi.js";
3
+ import b from "matter-js";
4
+ class R {
5
+ displayObject;
6
+ container;
7
+ config;
8
+ /**
9
+ * The base texture for sprite sheet operations.
10
+ *
11
+ * WHY store this separately from displayObject.texture?
12
+ * When we call setTextureRegion(), we create NEW Texture objects that
13
+ * reference different rectangular regions of this base texture. The
14
+ * displayObject.texture changes each frame, but baseTexture stays constant.
15
+ *
16
+ * Without storing baseTexture, we'd lose the original full-sheet texture
17
+ * after the first setTextureRegion() call, making subsequent region
18
+ * changes impossible.
19
+ */
20
+ baseTexture = null;
21
+ constructor(t, e, i) {
22
+ this.displayObject = t, this.container = e, this.config = i, e.addChild(t);
23
+ }
24
+ /** Replace the display object (used when texture loads) */
25
+ replaceDisplayObject(t) {
26
+ t.position.copyFrom(this.displayObject.position), t.rotation = this.displayObject.rotation, t.scale.copyFrom(this.displayObject.scale), t.alpha = this.displayObject.alpha, t.visible = this.displayObject.visible, t.zIndex = this.displayObject.zIndex;
27
+ const e = this.container.getChildIndex(this.displayObject);
28
+ this.container.removeChildAt(e), this.displayObject.destroy(), this.container.addChildAt(t, e), this.displayObject = t;
29
+ }
30
+ setPosition(t, e) {
31
+ this.displayObject.position.set(t, e);
32
+ }
33
+ setRotation(t) {
34
+ this.displayObject.rotation = t * Math.PI / 180;
35
+ }
36
+ setScale(t, e) {
37
+ this.displayObject.scale.set(t, e);
38
+ }
39
+ setAlpha(t) {
40
+ this.displayObject.alpha = t;
41
+ }
42
+ setVisible(t) {
43
+ this.displayObject.visible = t;
44
+ }
45
+ get zIndex() {
46
+ return this.displayObject.zIndex;
47
+ }
48
+ set zIndex(t) {
49
+ this.displayObject.zIndex = t;
50
+ }
51
+ destroy() {
52
+ this.container.removeChild(this.displayObject), this.displayObject.destroy();
53
+ }
54
+ /**
55
+ * Store the base texture for sprite sheet animation.
56
+ * Called once when the full sprite sheet texture is loaded.
57
+ */
58
+ setBaseTexture(t) {
59
+ this.baseTexture = t;
60
+ }
61
+ /**
62
+ * ═══════════════════════════════════════════════════════════════════════════
63
+ * TEXTURE REGION SELECTION (Sprite Sheet Animation)
64
+ * ═══════════════════════════════════════════════════════════════════════════
65
+ *
66
+ * This method creates a "view" into a portion of the sprite sheet texture.
67
+ * Each animation frame is a rectangular region of the larger texture.
68
+ *
69
+ * How PixiJS Texture Frames Work:
70
+ * ┌─────────────────────────────────────────────────────────────┐
71
+ * │ baseTexture (full sprite sheet, e.g. 128x64 pixels) │
72
+ * │ ┌───────┬───────┬───────┬───────┐ │
73
+ * │ │ frame │ frame │ frame │ frame │ │
74
+ * │ │ 0 │ 1 │ 2 │ 3 │ ← Row 0 │
75
+ * │ ├───────┼───────┼───────┼───────┤ │
76
+ * │ │ frame │ frame │ frame │ frame │ │
77
+ * │ │ 4 │ 5 │ 6 │ 7 │ ← Row 1 │
78
+ * │ └───────┴───────┴───────┴───────┘ │
79
+ * │ ↑ │
80
+ * │ new Texture({ source: baseTexture.source, │
81
+ * │ frame: Rectangle(32, 0, 32, 32) }) │
82
+ * │ Creates a texture showing ONLY frame 1 │
83
+ * └─────────────────────────────────────────────────────────────┘
84
+ *
85
+ * WHY create new Texture objects instead of modifying the existing one?
86
+ * PixiJS Texture objects are immutable after creation. The frame property
87
+ * is set at construction time. To show a different region, we must create
88
+ * a new Texture with a different frame Rectangle.
89
+ *
90
+ * @param x - Left edge of the frame in pixels
91
+ * @param y - Top edge of the frame in pixels
92
+ * @param width - Frame width in pixels
93
+ * @param height - Frame height in pixels
94
+ */
95
+ setTextureRegion(t, e, i, s) {
96
+ if (!this.baseTexture || !(this.displayObject instanceof S)) return;
97
+ const o = new H(t, e, i, s), a = new E({
98
+ source: this.baseTexture.source,
99
+ frame: o
100
+ });
101
+ this.displayObject.texture = a;
102
+ }
103
+ }
104
+ class J {
105
+ app = null;
106
+ worldContainer = null;
107
+ /**
108
+ * UI container for screen-space UI elements.
109
+ *
110
+ * ┌─────────────────────────────────────────────────────────────────┐
111
+ * │ PIXI.JS APPLICATION │
112
+ * ├─────────────────────────────────────────────────────────────────┤
113
+ * │ app.stage │
114
+ * │ ├── worldContainer (camera-affected) │
115
+ * │ │ └── Game sprites, tilemaps, etc. │
116
+ * │ │ │
117
+ * │ └── uiContainer (screen-space, fixed at 0,0) │
118
+ * │ └── UI elements (panels, text, buttons) │
119
+ * └─────────────────────────────────────────────────────────────────┘
120
+ *
121
+ * WHY separate containers?
122
+ * - worldContainer is transformed by camera (position, zoom)
123
+ * - uiContainer stays fixed at screen origin, ignores camera
124
+ * - UI always renders on top of game world
125
+ */
126
+ uiContainer = null;
127
+ textureCache = /* @__PURE__ */ new Map();
128
+ viewportWidth = 800;
129
+ viewportHeight = 600;
130
+ cameraX = 0;
131
+ cameraY = 0;
132
+ cameraZoom = 1;
133
+ async init(t) {
134
+ return this.app = new Y(), this.viewportWidth = t.width, this.viewportHeight = t.height, await this.app.init({
135
+ width: t.width,
136
+ height: t.height,
137
+ backgroundColor: t.backgroundColor ?? 1710638,
138
+ antialias: t.antialias ?? !0,
139
+ resolution: t.resolution ?? window.devicePixelRatio,
140
+ autoDensity: !0
141
+ }), this.worldContainer = new A(), this.worldContainer.sortableChildren = !0, this.app.stage.addChild(this.worldContainer), this.uiContainer = new A(), this.uiContainer.sortableChildren = !0, this.app.stage.addChild(this.uiContainer), this.app.canvas;
142
+ }
143
+ createSprite(t) {
144
+ if (!this.worldContainer)
145
+ throw new Error("Renderer not initialized. Call init() first.");
146
+ const e = t.width ?? 32, i = t.height ?? 32, s = t.color ?? 16711935, o = new I();
147
+ o.rect(-e / 2, -i / 2, e, i), o.fill(s), t.alpha !== void 0 && (o.alpha = t.alpha), t.zIndex !== void 0 && (o.zIndex = t.zIndex);
148
+ const a = new R(o, this.worldContainer, t);
149
+ return t.src && this.loadTexture(t.src).then((n) => {
150
+ if (n && n !== E.WHITE) {
151
+ const l = new S(n);
152
+ l.anchor.set(t.anchor?.[0] ?? 0.5, t.anchor?.[1] ?? 0.5), (t.width || t.height) && (l.width = e, l.height = i), a.replaceDisplayObject(l);
153
+ }
154
+ }), a;
155
+ }
156
+ /**
157
+ * ═══════════════════════════════════════════════════════════════════════════
158
+ * CREATE ANIMATED SPRITE
159
+ * ═══════════════════════════════════════════════════════════════════════════
160
+ *
161
+ * Creates a RenderObject optimized for sprite sheet animation:
162
+ * - Stores the base texture for later setTextureRegion() calls
163
+ * - Uses frame dimensions instead of scaling the texture
164
+ * - Notifies via callback when texture is ready for animation
165
+ *
166
+ * Flow:
167
+ * 1. Create placeholder Graphics immediately (so we have something to show)
168
+ * 2. Load texture asynchronously
169
+ * 3. When loaded, store as baseTexture and swap to Sprite
170
+ * 4. Call onTextureReady so AnimatedSpriteComponent can start animating
171
+ */
172
+ createAnimatedSprite(t) {
173
+ if (!this.worldContainer)
174
+ throw new Error("Renderer not initialized. Call init() first.");
175
+ const { frameWidth: e, frameHeight: i } = t, s = t.color ?? 16711935, o = new I();
176
+ o.rect(-e / 2, -i / 2, e, i), o.fill(s), t.alpha !== void 0 && (o.alpha = t.alpha), t.zIndex !== void 0 && (o.zIndex = t.zIndex);
177
+ const a = new R(o, this.worldContainer, t);
178
+ return t.src && this.loadTexture(t.src).then((n) => {
179
+ if (n && n !== E.WHITE) {
180
+ const l = new H(0, 0, e, i), h = new E({
181
+ source: n.source,
182
+ frame: l
183
+ }), u = new S(h);
184
+ u.anchor.set(t.anchor?.[0] ?? 0.5, t.anchor?.[1] ?? 0.5), a.setBaseTexture(n), a.replaceDisplayObject(u), t.onTextureReady?.();
185
+ }
186
+ }), a;
187
+ }
188
+ removeObject(t) {
189
+ t.destroy();
190
+ }
191
+ render() {
192
+ }
193
+ resize(t, e) {
194
+ this.app && (this.app.renderer.resize(t, e), this.viewportWidth = t, this.viewportHeight = e, this.updateWorldContainer());
195
+ }
196
+ getCanvas() {
197
+ return this.app?.canvas;
198
+ }
199
+ destroy() {
200
+ this.uiContainer && (this.uiContainer.destroy({ children: !0 }), this.uiContainer = null), this.worldContainer && (this.worldContainer.destroy({ children: !0 }), this.worldContainer = null), this.app && (this.app.destroy(!0), this.app = null), this.textureCache.clear();
201
+ }
202
+ setCameraPosition(t, e) {
203
+ this.cameraX = t, this.cameraY = e, this.updateWorldContainer();
204
+ }
205
+ setCameraZoom(t) {
206
+ this.cameraZoom = t, this.updateWorldContainer();
207
+ }
208
+ getViewportSize() {
209
+ return { width: this.viewportWidth, height: this.viewportHeight };
210
+ }
211
+ getCameraPosition() {
212
+ return { x: this.cameraX, y: this.cameraY };
213
+ }
214
+ getCameraZoom() {
215
+ return this.cameraZoom;
216
+ }
217
+ // ==================== PixiJS Internals ====================
218
+ getStage() {
219
+ if (!this.app) throw new Error("Renderer not initialized. Call init() first.");
220
+ return this.app.stage;
221
+ }
222
+ getWorldContainer() {
223
+ if (!this.worldContainer) throw new Error("Renderer not initialized. Call init() first.");
224
+ return this.worldContainer;
225
+ }
226
+ getApp() {
227
+ if (!this.app) throw new Error("Renderer not initialized. Call init() first.");
228
+ return this.app;
229
+ }
230
+ // ==================== Background ====================
231
+ setBackgroundColor(t) {
232
+ if (!this.app) throw new Error("Renderer not initialized. Call init() first.");
233
+ this.app.renderer.background.color = t;
234
+ }
235
+ // ==================== UI Support ====================
236
+ getUIContainer() {
237
+ if (!this.uiContainer) throw new Error("Renderer not initialized. Call init() first.");
238
+ return this.uiContainer;
239
+ }
240
+ addToUI(t) {
241
+ if (!this.uiContainer)
242
+ throw new Error("Renderer not initialized. Call init() first.");
243
+ this.uiContainer.addChild(t);
244
+ }
245
+ removeFromUI(t) {
246
+ this.uiContainer && this.uiContainer.removeChild(t);
247
+ }
248
+ /** Update world container transform based on camera position and zoom */
249
+ updateWorldContainer() {
250
+ this.worldContainer && (this.worldContainer.scale.set(this.cameraZoom, this.cameraZoom), this.worldContainer.position.set(
251
+ this.viewportWidth / 2 - this.cameraX * this.cameraZoom,
252
+ this.viewportHeight / 2 - this.cameraY * this.cameraZoom
253
+ ));
254
+ }
255
+ /** Load a texture with caching */
256
+ async loadTexture(t) {
257
+ if (this.textureCache.has(t))
258
+ return this.textureCache.get(t);
259
+ try {
260
+ const e = await K.load(t);
261
+ return this.textureCache.set(t, e), e;
262
+ } catch {
263
+ return null;
264
+ }
265
+ }
266
+ }
267
+ const _ = /* @__PURE__ */ new Map();
268
+ function Q(r, t) {
269
+ _.set(r, t);
270
+ }
271
+ function tt(r, t) {
272
+ const e = _.get(r);
273
+ if (!e)
274
+ throw new Error(
275
+ `Physics backend "${r}" not found. Available: ${Array.from(_.keys()).join(", ")}`
276
+ );
277
+ return e(t);
278
+ }
279
+ function yt() {
280
+ return Array.from(_.keys());
281
+ }
282
+ class et {
283
+ nameToIndex = /* @__PURE__ */ new Map();
284
+ nextIndex = 0;
285
+ constructor() {
286
+ this.register("default");
287
+ }
288
+ /** Register a named layer. Returns the bit index. No-op if already registered. */
289
+ register(t) {
290
+ const e = this.nameToIndex.get(t);
291
+ if (e !== void 0) return e;
292
+ if (this.nextIndex >= 32)
293
+ return console.warn(
294
+ `[CollisionLayers] Maximum 32 layers reached. Cannot register "${t}".`
295
+ ), 0;
296
+ const i = this.nextIndex++;
297
+ return this.nameToIndex.set(t, i), i;
298
+ }
299
+ /** Get bitmask category for a layer name. Auto-registers if unknown. */
300
+ category(t) {
301
+ return 1 << this.register(t);
302
+ }
303
+ /** Get combined bitmask from an array of layer names. Empty = collide with all. */
304
+ mask(t) {
305
+ if (t.length === 0) return 4294967295;
306
+ let e = 0;
307
+ for (const i of t)
308
+ e |= this.category(i);
309
+ return e;
310
+ }
311
+ /** Get all registered layer names (in registration order). */
312
+ getLayerNames() {
313
+ return Array.from(this.nameToIndex.keys());
314
+ }
315
+ /** Clear the registry. Useful for testing or scene transitions. */
316
+ reset() {
317
+ this.nameToIndex.clear(), this.nextIndex = 0, this.register("default");
318
+ }
319
+ }
320
+ const j = new et();
321
+ class it {
322
+ constructor(t, e) {
323
+ this.matterBody = t, this.type = e;
324
+ }
325
+ get id() {
326
+ return String(this.matterBody.id);
327
+ }
328
+ get position() {
329
+ return [this.matterBody.position.x, this.matterBody.position.y];
330
+ }
331
+ set position(t) {
332
+ b.Body.setPosition(this.matterBody, { x: t[0], y: t[1] });
333
+ }
334
+ get rotation() {
335
+ return this.matterBody.angle * 180 / Math.PI;
336
+ }
337
+ set rotation(t) {
338
+ b.Body.setAngle(this.matterBody, t * Math.PI / 180);
339
+ }
340
+ get velocity() {
341
+ return [this.matterBody.velocity.x, this.matterBody.velocity.y];
342
+ }
343
+ set velocity(t) {
344
+ b.Body.setVelocity(this.matterBody, { x: t[0], y: t[1] });
345
+ }
346
+ get angularVelocity() {
347
+ return this.matterBody.angularVelocity * 180 / Math.PI;
348
+ }
349
+ set angularVelocity(t) {
350
+ b.Body.setAngularVelocity(this.matterBody, t * Math.PI / 180);
351
+ }
352
+ applyForce(t) {
353
+ b.Body.applyForce(this.matterBody, this.matterBody.position, {
354
+ x: t[0],
355
+ y: t[1]
356
+ });
357
+ }
358
+ applyImpulse(t) {
359
+ const e = this.matterBody.mass;
360
+ b.Body.setVelocity(this.matterBody, {
361
+ x: this.matterBody.velocity.x + t[0] / e,
362
+ y: this.matterBody.velocity.y + t[1] / e
363
+ });
364
+ }
365
+ setVelocity(t) {
366
+ b.Body.setVelocity(this.matterBody, { x: t[0], y: t[1] });
367
+ }
368
+ setPosition(t) {
369
+ b.Body.setPosition(this.matterBody, { x: t[0], y: t[1] });
370
+ }
371
+ setRotation(t) {
372
+ b.Body.setAngle(this.matterBody, t * Math.PI / 180);
373
+ }
374
+ }
375
+ class st {
376
+ engine;
377
+ bodies = /* @__PURE__ */ new Map();
378
+ collisionStartCallbacks = [];
379
+ collisionEndCallbacks = [];
380
+ constructor(t) {
381
+ this.engine = b.Engine.create(), t?.gravity && this.setGravity(t.gravity), b.Events.on(this.engine, "collisionStart", (e) => {
382
+ for (const i of e.pairs) {
383
+ const s = this.bodies.get(i.bodyA.id), o = this.bodies.get(i.bodyB.id);
384
+ if (s && o) {
385
+ const a = {
386
+ bodyA: s,
387
+ bodyB: o,
388
+ contacts: i.collision.supports.map((n) => ({
389
+ point: [n.x, n.y],
390
+ normal: [
391
+ i.collision.normal.x,
392
+ i.collision.normal.y
393
+ ]
394
+ })),
395
+ isSensor: i.isSensor
396
+ };
397
+ for (const n of this.collisionStartCallbacks)
398
+ n(a);
399
+ }
400
+ }
401
+ }), b.Events.on(this.engine, "collisionEnd", (e) => {
402
+ for (const i of e.pairs) {
403
+ const s = this.bodies.get(i.bodyA.id), o = this.bodies.get(i.bodyB.id);
404
+ if (s && o) {
405
+ const a = {
406
+ bodyA: s,
407
+ bodyB: o,
408
+ contacts: [],
409
+ isSensor: i.isSensor
410
+ };
411
+ for (const n of this.collisionEndCallbacks)
412
+ n(a);
413
+ }
414
+ }
415
+ });
416
+ }
417
+ createBody(t) {
418
+ const e = t.type === "static", i = b.Bodies.rectangle(
419
+ t.position[0],
420
+ t.position[1],
421
+ 1,
422
+ 1,
423
+ {
424
+ isStatic: e,
425
+ angle: t.rotation * Math.PI / 180,
426
+ friction: t.friction ?? 0.1,
427
+ restitution: t.restitution ?? 0,
428
+ frictionAir: t.linearDamping ?? 0.01,
429
+ mass: t.mass,
430
+ inertia: t.fixedRotation ? 1 / 0 : void 0
431
+ }
432
+ );
433
+ t.gravityScale !== void 0 && t.gravityScale !== 1 && (i.gravityScale = t.gravityScale), b.Composite.add(this.engine.world, i);
434
+ const s = new it(i, t.type);
435
+ return this.bodies.set(i.id, s), s;
436
+ }
437
+ removeBody(t) {
438
+ const e = t;
439
+ b.Composite.remove(this.engine.world, e.matterBody), this.bodies.delete(e.matterBody.id);
440
+ }
441
+ addCollider(t, e) {
442
+ const i = t, s = i.matterBody, o = e.offset ?? [0, 0], a = {};
443
+ e.layer && (a.category = j.category(e.layer)), e.mask && e.mask.length > 0 && (a.mask = j.mask(e.mask));
444
+ const n = {
445
+ isStatic: s.isStatic,
446
+ isSensor: e.isTrigger,
447
+ friction: s.friction,
448
+ restitution: s.restitution,
449
+ frictionAir: s.frictionAir,
450
+ angle: s.angle,
451
+ inertia: s.inertia,
452
+ collisionFilter: Object.keys(a).length > 0 ? a : void 0
453
+ }, l = s;
454
+ l.gravityScale !== void 0 && (n.gravityScale = l.gravityScale);
455
+ let h = null;
456
+ const u = s.position.x + o[0], p = s.position.y + o[1];
457
+ switch (e.type) {
458
+ case "box":
459
+ h = b.Bodies.rectangle(
460
+ u,
461
+ p,
462
+ e.width ?? 32,
463
+ e.height ?? 32,
464
+ n
465
+ );
466
+ break;
467
+ case "circle":
468
+ h = b.Bodies.circle(u, p, e.radius ?? 16, n);
469
+ break;
470
+ case "polygon":
471
+ if (e.vertices && e.vertices.length >= 3) {
472
+ const g = e.vertices.map((k) => ({ x: k[0], y: k[1] }));
473
+ h = b.Bodies.fromVertices(u, p, [g], n);
474
+ }
475
+ break;
476
+ }
477
+ h && (b.Composite.remove(this.engine.world, s), this.bodies.delete(s.id), b.Composite.add(this.engine.world, h), this.bodies.set(h.id, i), i.matterBody = h);
478
+ }
479
+ step(t) {
480
+ const e = this.engine.gravity;
481
+ for (const i of this.bodies.values()) {
482
+ const s = i.matterBody;
483
+ if (s.gravityScale !== void 0 && s.gravityScale !== 1 && !s.isStatic) {
484
+ const o = s.gravityScale - 1;
485
+ b.Body.applyForce(s, s.position, {
486
+ x: e.x * s.mass * o * e.scale,
487
+ y: e.y * s.mass * o * e.scale
488
+ });
489
+ }
490
+ }
491
+ b.Engine.update(this.engine, t * 1e3);
492
+ }
493
+ setGravity(t) {
494
+ this.engine.gravity.x = t[0] / 1e3, this.engine.gravity.y = t[1] / 1e3, this.engine.gravity.scale = 1e-3;
495
+ }
496
+ getGravity() {
497
+ return [
498
+ this.engine.gravity.x * 1e3,
499
+ this.engine.gravity.y * 1e3
500
+ ];
501
+ }
502
+ raycast(t, e, i) {
503
+ const s = Math.sqrt(e[0] ** 2 + e[1] ** 2), o = e[0] / s, a = e[1] / s, n = {
504
+ x: t[0] + o * i,
505
+ y: t[1] + a * i
506
+ }, l = b.Query.ray(
507
+ b.Composite.allBodies(this.engine.world),
508
+ { x: t[0], y: t[1] },
509
+ n
510
+ );
511
+ if (l.length === 0) return null;
512
+ let h = l[0], u = 1 / 0, p = null;
513
+ for (const f of l) {
514
+ const x = f.supports?.[0], c = f.bodyB ?? f.bodyA, w = x ? x.x : c.position.x, O = x ? x.y : c.position.y, M = w - t[0], F = O - t[1], L = Math.sqrt(M * M + F * F);
515
+ L < u && (u = L, h = f, p = { x: w, y: O });
516
+ }
517
+ const g = h.bodyB ?? h.bodyA, k = this.bodies.get(g.id);
518
+ if (!k) return null;
519
+ const m = p ?? { x: g.position.x, y: g.position.y };
520
+ return {
521
+ body: k,
522
+ point: [m.x, m.y],
523
+ normal: [h.normal.x, h.normal.y],
524
+ distance: u
525
+ };
526
+ }
527
+ queryAABB(t, e) {
528
+ const i = {
529
+ min: { x: t[0], y: t[1] },
530
+ max: { x: e[0], y: e[1] }
531
+ }, s = b.Composite.allBodies(this.engine.world), o = [];
532
+ for (const a of s)
533
+ if (b.Bounds.overlaps(a.bounds, i)) {
534
+ const n = this.bodies.get(a.id);
535
+ n && o.push(n);
536
+ }
537
+ return o;
538
+ }
539
+ onCollisionStart(t) {
540
+ return this.collisionStartCallbacks.push(t), () => {
541
+ const e = this.collisionStartCallbacks.indexOf(t);
542
+ e !== -1 && this.collisionStartCallbacks.splice(e, 1);
543
+ };
544
+ }
545
+ onCollisionEnd(t) {
546
+ return this.collisionEndCallbacks.push(t), () => {
547
+ const e = this.collisionEndCallbacks.indexOf(t);
548
+ e !== -1 && this.collisionEndCallbacks.splice(e, 1);
549
+ };
550
+ }
551
+ clear() {
552
+ b.Composite.clear(this.engine.world, !1), this.bodies.clear();
553
+ }
554
+ destroy() {
555
+ this.clear(), b.Engine.clear(this.engine), this.collisionStartCallbacks = [], this.collisionEndCallbacks = [];
556
+ }
557
+ }
558
+ Q("matter", (r) => new st(r));
559
+ class C {
560
+ /** Time since last frame in seconds */
561
+ static deltaTime = 0;
562
+ /** Unscaled time since last frame */
563
+ static unscaledDeltaTime = 0;
564
+ /** Total elapsed time since game start */
565
+ static time = 0;
566
+ /** Unscaled total elapsed time */
567
+ static unscaledTime = 0;
568
+ /** Fixed timestep for physics (1/60 second) */
569
+ static fixedDeltaTime = 1 / 60;
570
+ /** Time scale for slow-mo or pause effects */
571
+ static timeScale = 1;
572
+ /** Frame count since game start */
573
+ static frameCount = 0;
574
+ /** Current frames per second */
575
+ static fps = 60;
576
+ static fpsAccumulator = 0;
577
+ static fpsFrameCount = 0;
578
+ static lastFpsUpdate = 0;
579
+ /** Update time values (called by WorldManager) */
580
+ static update(t) {
581
+ this.unscaledDeltaTime = t, this.deltaTime = t * this.timeScale, this.unscaledTime += t, this.time += this.deltaTime, this.frameCount++, this.fpsAccumulator += t, this.fpsFrameCount++, this.fpsAccumulator >= 1 && (this.fps = Math.round(this.fpsFrameCount / this.fpsAccumulator), this.fpsAccumulator = 0, this.fpsFrameCount = 0);
582
+ }
583
+ /** Reset all time values */
584
+ static reset() {
585
+ this.deltaTime = 0, this.unscaledDeltaTime = 0, this.time = 0, this.unscaledTime = 0, this.timeScale = 1, this.frameCount = 0, this.fps = 60, this.fpsAccumulator = 0, this.fpsFrameCount = 0;
586
+ }
587
+ }
588
+ function kt(r) {
589
+ return { type: "frames", count: r };
590
+ }
591
+ function xt(r) {
592
+ return { type: "seconds", duration: r };
593
+ }
594
+ function wt(r) {
595
+ return { type: "until", predicate: r };
596
+ }
597
+ function Ct(r) {
598
+ return { type: "coroutine", generator: r };
599
+ }
600
+ let W = 0;
601
+ class ot {
602
+ coroutines = [];
603
+ /** Start a new coroutine */
604
+ start(t) {
605
+ const e = W++, i = {
606
+ id: e,
607
+ generator: t,
608
+ waitingFor: null,
609
+ remainingFrames: 0,
610
+ remainingTime: 0,
611
+ nestedCoroutine: null,
612
+ cancelled: !1
613
+ };
614
+ return this.coroutines.push(i), this.advanceCoroutine(i), {
615
+ id: e,
616
+ cancel: () => this.cancel(e),
617
+ get isRunning() {
618
+ return !i.cancelled;
619
+ }
620
+ };
621
+ }
622
+ /** Cancel a running coroutine */
623
+ cancel(t) {
624
+ const e = this.coroutines.find((i) => i.id === t);
625
+ e && (e.cancelled = !0);
626
+ }
627
+ /** Cancel all coroutines */
628
+ cancelAll() {
629
+ for (const t of this.coroutines)
630
+ t.cancelled = !0;
631
+ this.coroutines = [];
632
+ }
633
+ /** Update all coroutines (called each frame) */
634
+ update() {
635
+ const t = C.deltaTime;
636
+ this.coroutines = this.coroutines.filter((e) => {
637
+ if (e.cancelled) return !1;
638
+ if (e.waitingFor)
639
+ switch (e.waitingFor.type) {
640
+ case "frames":
641
+ if (e.remainingFrames--, e.remainingFrames > 0) return !0;
642
+ break;
643
+ case "seconds":
644
+ if (e.remainingTime -= t, e.remainingTime > 0) return !0;
645
+ break;
646
+ case "until":
647
+ if (!e.waitingFor.predicate()) return !0;
648
+ break;
649
+ case "coroutine":
650
+ if (e.nestedCoroutine && !e.nestedCoroutine.cancelled)
651
+ return !0;
652
+ break;
653
+ }
654
+ return this.advanceCoroutine(e);
655
+ });
656
+ }
657
+ /** Advance a coroutine to its next yield point */
658
+ advanceCoroutine(t) {
659
+ try {
660
+ const e = t.generator.next();
661
+ if (e.done)
662
+ return !1;
663
+ const i = e.value;
664
+ switch (t.waitingFor = i, i.type) {
665
+ case "frames":
666
+ t.remainingFrames = i.count;
667
+ break;
668
+ case "seconds":
669
+ t.remainingTime = i.duration;
670
+ break;
671
+ case "until":
672
+ break;
673
+ case "coroutine":
674
+ const s = {
675
+ id: W++,
676
+ generator: i.generator,
677
+ waitingFor: null,
678
+ remainingFrames: 0,
679
+ remainingTime: 0,
680
+ nestedCoroutine: null,
681
+ cancelled: !1
682
+ };
683
+ t.nestedCoroutine = s, this.coroutines.push(s), this.advanceCoroutine(s);
684
+ break;
685
+ }
686
+ return !0;
687
+ } catch (e) {
688
+ return console.error("Coroutine error:", e), !1;
689
+ }
690
+ }
691
+ /** Get number of running coroutines */
692
+ get count() {
693
+ return this.coroutines.length;
694
+ }
695
+ }
696
+ const rt = new ot(), nt = {
697
+ axes: {
698
+ horizontal: {
699
+ negative: ["KeyA", "ArrowLeft"],
700
+ positive: ["KeyD", "ArrowRight"],
701
+ smoothing: 10
702
+ },
703
+ vertical: {
704
+ negative: ["KeyW", "ArrowUp"],
705
+ positive: ["KeyS", "ArrowDown"],
706
+ smoothing: 10
707
+ }
708
+ },
709
+ buttons: {
710
+ jump: { keys: ["Space"] },
711
+ fire: { keys: ["KeyX", "Mouse0"] }
712
+ }
713
+ };
714
+ class y {
715
+ // ==================== Key State ====================
716
+ /** Keys currently held down */
717
+ static keysHeld = /* @__PURE__ */ new Set();
718
+ /** Keys pressed this frame */
719
+ static keysDown = /* @__PURE__ */ new Set();
720
+ /** Keys released this frame */
721
+ static keysUp = /* @__PURE__ */ new Set();
722
+ // ==================== Mouse State ====================
723
+ /** Mouse buttons currently held (0=left, 1=middle, 2=right) */
724
+ static mouseButtonsHeld = /* @__PURE__ */ new Set();
725
+ /** Mouse buttons pressed this frame */
726
+ static mouseButtonsDown = /* @__PURE__ */ new Set();
727
+ /** Mouse buttons released this frame */
728
+ static mouseButtonsUp = /* @__PURE__ */ new Set();
729
+ /** Current mouse position relative to canvas */
730
+ static _mousePosition = [0, 0];
731
+ // ==================== Axis State ====================
732
+ /** Smoothed axis values */
733
+ static axisValues = /* @__PURE__ */ new Map();
734
+ // ==================== Configuration ====================
735
+ /** Current input configuration */
736
+ static config = nt;
737
+ /** Canvas element for mouse position calculation */
738
+ static canvas = null;
739
+ /** Whether input is initialized */
740
+ static initialized = !1;
741
+ // ==================== Event Handlers ====================
742
+ static onKeyDown = (t) => {
743
+ y.keysHeld.has(t.code) || y.keysDown.add(t.code), y.keysHeld.add(t.code);
744
+ };
745
+ static onKeyUp = (t) => {
746
+ y.keysHeld.delete(t.code), y.keysUp.add(t.code);
747
+ };
748
+ static onMouseDown = (t) => {
749
+ y.mouseButtonsHeld.has(t.button) || y.mouseButtonsDown.add(t.button), y.mouseButtonsHeld.add(t.button);
750
+ };
751
+ static onMouseUp = (t) => {
752
+ y.mouseButtonsHeld.delete(t.button), y.mouseButtonsUp.add(t.button);
753
+ };
754
+ static onMouseMove = (t) => {
755
+ if (y.canvas) {
756
+ const e = y.canvas.getBoundingClientRect();
757
+ y._mousePosition = [
758
+ t.clientX - e.left,
759
+ t.clientY - e.top
760
+ ];
761
+ } else
762
+ y._mousePosition = [t.clientX, t.clientY];
763
+ };
764
+ static onContextMenu = (t) => {
765
+ y.canvas && t.target === y.canvas && t.preventDefault();
766
+ };
767
+ // ==================== Lifecycle ====================
768
+ /**
769
+ * Initialize the input system.
770
+ * Call once at startup.
771
+ */
772
+ static initialize(t) {
773
+ if (this.initialized) {
774
+ console.warn("Input system already initialized");
775
+ return;
776
+ }
777
+ this.canvas = t ?? null, window.addEventListener("keydown", this.onKeyDown), window.addEventListener("keyup", this.onKeyUp), window.addEventListener("mousedown", this.onMouseDown), window.addEventListener("mouseup", this.onMouseUp), window.addEventListener("mousemove", this.onMouseMove), window.addEventListener("contextmenu", this.onContextMenu);
778
+ for (const e of Object.keys(this.config.axes))
779
+ this.axisValues.set(e, 0);
780
+ this.initialized = !0, console.log("Input system initialized");
781
+ }
782
+ /**
783
+ * Update input state.
784
+ * Call at the start of each frame, after Time.update().
785
+ */
786
+ static update() {
787
+ this.keysDown.clear(), this.keysUp.clear(), this.mouseButtonsDown.clear(), this.mouseButtonsUp.clear(), this.updateAxes();
788
+ }
789
+ /**
790
+ * Destroy the input system and remove all listeners.
791
+ */
792
+ static destroy() {
793
+ this.initialized && (window.removeEventListener("keydown", this.onKeyDown), window.removeEventListener("keyup", this.onKeyUp), window.removeEventListener("mousedown", this.onMouseDown), window.removeEventListener("mouseup", this.onMouseUp), window.removeEventListener("mousemove", this.onMouseMove), window.removeEventListener("contextmenu", this.onContextMenu), this.keysHeld.clear(), this.keysDown.clear(), this.keysUp.clear(), this.mouseButtonsHeld.clear(), this.mouseButtonsDown.clear(), this.mouseButtonsUp.clear(), this.axisValues.clear(), this.canvas = null, this.initialized = !1, console.log("Input system destroyed"));
794
+ }
795
+ // ==================== Axis Updates ====================
796
+ static updateAxes() {
797
+ const t = C.deltaTime || 0.016666666666666666;
798
+ for (const [e, i] of Object.entries(this.config.axes)) {
799
+ const s = this.getAxisRaw(e), o = this.axisValues.get(e) ?? 0, a = i.smoothing ?? 10;
800
+ if (a === 0)
801
+ this.axisValues.set(e, s);
802
+ else {
803
+ const n = Math.min(1, a * t), l = o + (s - o) * n;
804
+ this.axisValues.set(e, Math.abs(l) < 1e-3 ? 0 : l);
805
+ }
806
+ }
807
+ }
808
+ // ==================== Axis API ====================
809
+ /**
810
+ * Get a smoothed axis value between -1 and 1.
811
+ */
812
+ static getAxis(t) {
813
+ return this.axisValues.get(t) ?? 0;
814
+ }
815
+ /**
816
+ * Get raw axis value: -1, 0, or 1 (no smoothing).
817
+ */
818
+ static getAxisRaw(t) {
819
+ const e = this.config.axes[t];
820
+ if (!e) return 0;
821
+ let i = 0;
822
+ for (const s of e.negative)
823
+ if (this.isKeyOrMouseHeld(s)) {
824
+ i -= 1;
825
+ break;
826
+ }
827
+ for (const s of e.positive)
828
+ if (this.isKeyOrMouseHeld(s)) {
829
+ i += 1;
830
+ break;
831
+ }
832
+ return i;
833
+ }
834
+ // ==================== Button API ====================
835
+ /**
836
+ * Check if a button is currently held.
837
+ */
838
+ static getButton(t) {
839
+ const e = this.config.buttons[t];
840
+ if (!e) return !1;
841
+ for (const i of e.keys)
842
+ if (this.isKeyOrMouseHeld(i)) return !0;
843
+ return !1;
844
+ }
845
+ /**
846
+ * Check if a button was pressed this frame.
847
+ */
848
+ static getButtonDown(t) {
849
+ const e = this.config.buttons[t];
850
+ if (!e) return !1;
851
+ for (const i of e.keys)
852
+ if (this.isKeyOrMouseDown(i)) return !0;
853
+ return !1;
854
+ }
855
+ /**
856
+ * Check if a button was released this frame.
857
+ */
858
+ static getButtonUp(t) {
859
+ const e = this.config.buttons[t];
860
+ if (!e) return !1;
861
+ for (const i of e.keys)
862
+ if (this.isKeyOrMouseUp(i)) return !0;
863
+ return !1;
864
+ }
865
+ // ==================== Raw Key API ====================
866
+ /**
867
+ * Check if a key is currently held.
868
+ * @param code KeyboardEvent.code (e.g., 'KeyW', 'Space', 'ArrowUp')
869
+ */
870
+ static getKey(t) {
871
+ return this.keysHeld.has(t);
872
+ }
873
+ /**
874
+ * Check if a key was pressed this frame.
875
+ * @param code KeyboardEvent.code (e.g., 'KeyW', 'Space', 'ArrowUp')
876
+ */
877
+ static getKeyDown(t) {
878
+ return this.keysDown.has(t);
879
+ }
880
+ /**
881
+ * Check if a key was released this frame.
882
+ * @param code KeyboardEvent.code (e.g., 'KeyW', 'Space', 'ArrowUp')
883
+ */
884
+ static getKeyUp(t) {
885
+ return this.keysUp.has(t);
886
+ }
887
+ // ==================== Mouse API ====================
888
+ /**
889
+ * Current mouse position relative to canvas (or window if no canvas).
890
+ */
891
+ static get mousePosition() {
892
+ return [...this._mousePosition];
893
+ }
894
+ /**
895
+ * Check if a mouse button is currently held.
896
+ * @param button 0=left, 1=middle, 2=right
897
+ */
898
+ static getMouseButton(t) {
899
+ return this.mouseButtonsHeld.has(t);
900
+ }
901
+ /**
902
+ * Check if a mouse button was pressed this frame.
903
+ */
904
+ static getMouseButtonDown(t) {
905
+ return this.mouseButtonsDown.has(t);
906
+ }
907
+ /**
908
+ * Check if a mouse button was released this frame.
909
+ */
910
+ static getMouseButtonUp(t) {
911
+ return this.mouseButtonsUp.has(t);
912
+ }
913
+ // ==================== Configuration ====================
914
+ /**
915
+ * Configure an axis.
916
+ */
917
+ static setAxis(t, e) {
918
+ this.config.axes[t] = e, this.axisValues.has(t) || this.axisValues.set(t, 0);
919
+ }
920
+ /**
921
+ * Configure a button.
922
+ */
923
+ static setButton(t, e) {
924
+ this.config.buttons[t] = e;
925
+ }
926
+ /**
927
+ * Set the entire input configuration.
928
+ */
929
+ static setConfig(t) {
930
+ this.config = t, this.axisValues.clear();
931
+ for (const e of Object.keys(t.axes))
932
+ this.axisValues.set(e, 0);
933
+ }
934
+ // ==================== Helpers ====================
935
+ static isKeyOrMouseHeld(t) {
936
+ if (t.startsWith("Mouse")) {
937
+ const e = parseInt(t.slice(5), 10);
938
+ return this.mouseButtonsHeld.has(e);
939
+ }
940
+ return this.keysHeld.has(t);
941
+ }
942
+ static isKeyOrMouseDown(t) {
943
+ if (t.startsWith("Mouse")) {
944
+ const e = parseInt(t.slice(5), 10);
945
+ return this.mouseButtonsDown.has(e);
946
+ }
947
+ return this.keysDown.has(t);
948
+ }
949
+ static isKeyOrMouseUp(t) {
950
+ if (t.startsWith("Mouse")) {
951
+ const e = parseInt(t.slice(5), 10);
952
+ return this.mouseButtonsUp.has(e);
953
+ }
954
+ return this.keysUp.has(t);
955
+ }
956
+ }
957
+ class at {
958
+ listeners = /* @__PURE__ */ new Map();
959
+ /** Subscribe to an event */
960
+ on(t, e) {
961
+ this.listeners.has(t) || this.listeners.set(t, []);
962
+ const i = {
963
+ callback: e,
964
+ once: !1
965
+ };
966
+ return this.listeners.get(t).push(i), () => this.off(t, e);
967
+ }
968
+ /** Subscribe to an event (fires once then unsubscribes) */
969
+ once(t, e) {
970
+ this.listeners.has(t) || this.listeners.set(t, []);
971
+ const i = {
972
+ callback: e,
973
+ once: !0
974
+ };
975
+ return this.listeners.get(t).push(i), () => this.off(t, e);
976
+ }
977
+ /** Unsubscribe from an event */
978
+ off(t, e) {
979
+ const i = this.listeners.get(t);
980
+ if (!i) return;
981
+ const s = i.findIndex((o) => o.callback === e);
982
+ s !== -1 && i.splice(s, 1);
983
+ }
984
+ /** Emit an event */
985
+ emit(t, e) {
986
+ const i = this.listeners.get(t);
987
+ if (!i) return;
988
+ const s = [...i];
989
+ for (const o of s)
990
+ o.callback(e), o.once && this.off(t, o.callback);
991
+ }
992
+ /** Remove all listeners for an event (or all events) */
993
+ removeAllListeners(t) {
994
+ t ? this.listeners.delete(t) : this.listeners.clear();
995
+ }
996
+ /** Check if event has listeners */
997
+ hasListeners(t) {
998
+ const e = this.listeners.get(t);
999
+ return e !== void 0 && e.length > 0;
1000
+ }
1001
+ }
1002
+ const B = new at(), T = {
1003
+ /** Fired when the game is paused */
1004
+ PAUSE: "engine:pause",
1005
+ /** Fired when the game is resumed */
1006
+ RESUME: "engine:resume",
1007
+ /** Fired on collision enter */
1008
+ COLLISION_ENTER: "engine:collision:enter",
1009
+ /** Fired on collision exit */
1010
+ COLLISION_EXIT: "engine:collision:exit",
1011
+ /** Fired on trigger enter */
1012
+ TRIGGER_ENTER: "engine:trigger:enter",
1013
+ /** Fired on trigger exit */
1014
+ TRIGGER_EXIT: "engine:trigger:exit"
1015
+ };
1016
+ class lt {
1017
+ world;
1018
+ transform;
1019
+ bodyType;
1020
+ /** The underlying physics body */
1021
+ _body;
1022
+ collisionEnterCallbacks = [];
1023
+ collisionExitCallbacks = [];
1024
+ triggerEnterCallbacks = [];
1025
+ triggerExitCallbacks = [];
1026
+ constructor(t, e, i) {
1027
+ this.world = t, this.transform = e, this.bodyType = i.type;
1028
+ const s = e.worldPosition, o = e.worldRotation;
1029
+ this._body = t.createBody({
1030
+ type: i.type,
1031
+ position: s,
1032
+ rotation: o,
1033
+ mass: i.mass,
1034
+ friction: i.friction,
1035
+ restitution: i.restitution,
1036
+ linearDamping: i.linearDamping,
1037
+ angularDamping: i.angularDamping,
1038
+ fixedRotation: i.fixedRotation,
1039
+ bullet: i.bullet,
1040
+ gravityScale: i.gravityScale
1041
+ });
1042
+ }
1043
+ /** Get the underlying physics body. */
1044
+ get body() {
1045
+ return this._body;
1046
+ }
1047
+ /** Get/set velocity. */
1048
+ get velocity() {
1049
+ return this._body.velocity;
1050
+ }
1051
+ set velocity(t) {
1052
+ this._body.setVelocity(t);
1053
+ }
1054
+ /** Apply a continuous force. */
1055
+ applyForce(t) {
1056
+ this._body.applyForce(t);
1057
+ }
1058
+ /** Apply an instant impulse. */
1059
+ applyImpulse(t) {
1060
+ this._body.applyImpulse(t);
1061
+ }
1062
+ /** Add a collider shape to this body. */
1063
+ addCollider(t) {
1064
+ this.world.addCollider(this._body, t);
1065
+ }
1066
+ /** Sync transform FROM physics (call for dynamic bodies after physics step). */
1067
+ syncFromPhysics() {
1068
+ if (this.bodyType !== "dynamic") return;
1069
+ const t = this._body.position, e = this._body.rotation;
1070
+ this.transform.position = [t[0], t[1]], this.transform.rotation = e;
1071
+ }
1072
+ /** Sync transform TO physics (call for kinematic bodies before physics step). */
1073
+ syncToPhysics() {
1074
+ if (this.bodyType !== "kinematic") return;
1075
+ const t = this.transform.worldPosition, e = this.transform.worldRotation;
1076
+ this._body.setPosition(t), this._body.setRotation(e);
1077
+ }
1078
+ /** Register collision enter callback. */
1079
+ onCollisionEnter(t) {
1080
+ return this.collisionEnterCallbacks.push(t), () => {
1081
+ const e = this.collisionEnterCallbacks.indexOf(t);
1082
+ e !== -1 && this.collisionEnterCallbacks.splice(e, 1);
1083
+ };
1084
+ }
1085
+ /** Register collision exit callback. */
1086
+ onCollisionExit(t) {
1087
+ return this.collisionExitCallbacks.push(t), () => {
1088
+ const e = this.collisionExitCallbacks.indexOf(t);
1089
+ e !== -1 && this.collisionExitCallbacks.splice(e, 1);
1090
+ };
1091
+ }
1092
+ /** Register trigger enter callback. */
1093
+ onTriggerEnter(t) {
1094
+ return this.triggerEnterCallbacks.push(t), () => {
1095
+ const e = this.triggerEnterCallbacks.indexOf(t);
1096
+ e !== -1 && this.triggerEnterCallbacks.splice(e, 1);
1097
+ };
1098
+ }
1099
+ /** Register trigger exit callback. */
1100
+ onTriggerExit(t) {
1101
+ return this.triggerExitCallbacks.push(t), () => {
1102
+ const e = this.triggerExitCallbacks.indexOf(t);
1103
+ e !== -1 && this.triggerExitCallbacks.splice(e, 1);
1104
+ };
1105
+ }
1106
+ /** Destroy and remove from physics world. */
1107
+ destroy() {
1108
+ this.world.removeBody(this._body), this.collisionEnterCallbacks = [], this.collisionExitCallbacks = [], this.triggerEnterCallbacks = [], this.triggerExitCallbacks = [];
1109
+ }
1110
+ // Internal methods called by Game collision routing
1111
+ /** @internal */
1112
+ _fireCollisionEnter(t, e) {
1113
+ for (const i of this.collisionEnterCallbacks) i(t, e);
1114
+ }
1115
+ /** @internal */
1116
+ _fireCollisionExit(t) {
1117
+ for (const e of this.collisionExitCallbacks) e(t);
1118
+ }
1119
+ /** @internal */
1120
+ _fireTriggerEnter(t) {
1121
+ for (const e of this.triggerEnterCallbacks) e(t);
1122
+ }
1123
+ /** @internal */
1124
+ _fireTriggerExit(t) {
1125
+ for (const e of this.triggerExitCallbacks) e(t);
1126
+ }
1127
+ }
1128
+ class vt {
1129
+ renderer;
1130
+ physics;
1131
+ fixedUpdateCallbacks = [];
1132
+ updateCallbacks = [];
1133
+ lateUpdateCallbacks = [];
1134
+ running = !1;
1135
+ paused = !1;
1136
+ animFrameId = null;
1137
+ lastTime = 0;
1138
+ fixedAccumulator = 0;
1139
+ maxDeltaTime = 0.25;
1140
+ /** Body ID → RigidBody for collision routing */
1141
+ registeredBodies = /* @__PURE__ */ new Map();
1142
+ physicsCleanup = [];
1143
+ constructor(t) {
1144
+ this.renderer = new J(), t?.physics?.enabled !== !1 ? (this.physics = tt("matter", {
1145
+ gravity: t?.physics?.gravity ?? [0, 980]
1146
+ }), this.setupCollisionRouting()) : this.physics = null;
1147
+ }
1148
+ /** Initialize the renderer and input system. Returns the canvas. */
1149
+ async init(t) {
1150
+ const e = await this.renderer.init({
1151
+ width: t?.width ?? 800,
1152
+ height: t?.height ?? 600,
1153
+ backgroundColor: t?.backgroundColor,
1154
+ antialias: t?.antialias ?? !0,
1155
+ resolution: t?.resolution
1156
+ });
1157
+ return y.initialize(e), e;
1158
+ }
1159
+ /** Register a callback for fixed-timestep updates (physics). */
1160
+ onFixedUpdate(t) {
1161
+ return this.fixedUpdateCallbacks.push(t), () => {
1162
+ const e = this.fixedUpdateCallbacks.indexOf(t);
1163
+ e !== -1 && this.fixedUpdateCallbacks.splice(e, 1);
1164
+ };
1165
+ }
1166
+ /** Register a callback for per-frame updates. */
1167
+ onUpdate(t) {
1168
+ return this.updateCallbacks.push(t), () => {
1169
+ const e = this.updateCallbacks.indexOf(t);
1170
+ e !== -1 && this.updateCallbacks.splice(e, 1);
1171
+ };
1172
+ }
1173
+ /** Register a callback for late updates (after main update). */
1174
+ onLateUpdate(t) {
1175
+ return this.lateUpdateCallbacks.push(t), () => {
1176
+ const e = this.lateUpdateCallbacks.indexOf(t);
1177
+ e !== -1 && this.lateUpdateCallbacks.splice(e, 1);
1178
+ };
1179
+ }
1180
+ /** Register a RigidBody for collision routing. */
1181
+ registerBody(t) {
1182
+ t.body && this.registeredBodies.set(t.body.id, t);
1183
+ }
1184
+ /** Unregister a RigidBody from collision routing. */
1185
+ unregisterBody(t) {
1186
+ t.body && this.registeredBodies.delete(t.body.id);
1187
+ }
1188
+ /** Convenience: create a RigidBody and auto-register it. */
1189
+ createBody(t, e) {
1190
+ if (!this.physics)
1191
+ throw new Error("Physics is not enabled for this Game instance");
1192
+ const i = new lt(this.physics, t, e);
1193
+ return this.registerBody(i), i;
1194
+ }
1195
+ /** Start the game loop. */
1196
+ start() {
1197
+ this.running || (this.running = !0, this.paused = !1, this.lastTime = performance.now(), this.fixedAccumulator = 0, this.loop());
1198
+ }
1199
+ /** Stop the game loop. */
1200
+ stop() {
1201
+ this.running = !1, this.animFrameId !== null && (cancelAnimationFrame(this.animFrameId), this.animFrameId = null);
1202
+ }
1203
+ /** Pause the game (loop continues but updates are skipped). */
1204
+ pause() {
1205
+ this.paused || (this.paused = !0, B.emit(T.PAUSE));
1206
+ }
1207
+ /** Resume a paused game. */
1208
+ resume() {
1209
+ this.paused && (this.paused = !1, this.lastTime = performance.now(), B.emit(T.RESUME));
1210
+ }
1211
+ /** Clean up all resources. */
1212
+ destroy() {
1213
+ this.stop();
1214
+ for (const t of this.physicsCleanup) t();
1215
+ this.physicsCleanup = [], this.registeredBodies.clear(), this.physics?.destroy(), y.destroy(), this.renderer.destroy();
1216
+ }
1217
+ loop = () => {
1218
+ if (!this.running) return;
1219
+ const t = performance.now();
1220
+ let e = (t - this.lastTime) / 1e3;
1221
+ if (this.lastTime = t, e > this.maxDeltaTime && (e = this.maxDeltaTime), !this.paused) {
1222
+ for (C.update(e), this.fixedAccumulator += e; this.fixedAccumulator >= C.fixedDeltaTime; ) {
1223
+ this.fixedAccumulator -= C.fixedDeltaTime, this.physics && this.physics.step(C.fixedDeltaTime);
1224
+ for (const i of this.fixedUpdateCallbacks) i();
1225
+ }
1226
+ for (const i of this.updateCallbacks) i();
1227
+ for (const i of this.lateUpdateCallbacks) i();
1228
+ rt.update(), y.update();
1229
+ }
1230
+ this.renderer.render(), this.animFrameId = requestAnimationFrame(this.loop);
1231
+ };
1232
+ setupCollisionRouting() {
1233
+ this.physics && (this.physicsCleanup.push(
1234
+ this.physics.onCollisionStart((t) => {
1235
+ this.routeCollision(t, "enter");
1236
+ })
1237
+ ), this.physicsCleanup.push(
1238
+ this.physics.onCollisionEnd((t) => {
1239
+ this.routeCollision(t, "exit");
1240
+ })
1241
+ ));
1242
+ }
1243
+ routeCollision(t, e) {
1244
+ const i = this.registeredBodies.get(t.bodyA.id), s = this.registeredBodies.get(t.bodyB.id);
1245
+ if (!(!i || !s))
1246
+ if (t.isSensor)
1247
+ e === "enter" ? (i._fireTriggerEnter(s), s._fireTriggerEnter(i)) : (i._fireTriggerExit(s), s._fireTriggerExit(i));
1248
+ else {
1249
+ const o = t.contacts[0] ?? { point: [0, 0], normal: [0, 0] };
1250
+ e === "enter" ? (i._fireCollisionEnter(s, o), s._fireCollisionEnter(i, {
1251
+ point: o.point,
1252
+ normal: [-o.normal[0], -o.normal[1]]
1253
+ })) : (i._fireCollisionExit(s), s._fireCollisionExit(i));
1254
+ }
1255
+ }
1256
+ }
1257
+ class $ {
1258
+ /** Position in world space */
1259
+ position;
1260
+ /** Rotation in degrees */
1261
+ rotation;
1262
+ /** Scale factor */
1263
+ scale;
1264
+ /** Z-index for render ordering (higher = on top) */
1265
+ zIndex;
1266
+ /** Parent transform for hierarchical positioning */
1267
+ parent = null;
1268
+ /** Child transforms */
1269
+ children = [];
1270
+ constructor(t) {
1271
+ this.position = t?.position ? [...t.position] : [0, 0], this.rotation = t?.rotation ?? 0, this.scale = t?.scale ? [...t.scale] : [1, 1], this.zIndex = t?.zIndex ?? 0;
1272
+ }
1273
+ /** Get world position (accounting for parent transforms) */
1274
+ get worldPosition() {
1275
+ if (!this.parent) return this.position;
1276
+ const t = this.parent.worldPosition, e = this.parent.worldScale, i = this.parent.worldRotation * Math.PI / 180, s = Math.cos(i), o = Math.sin(i), a = this.position[0] * e[0], n = this.position[1] * e[1];
1277
+ return [
1278
+ t[0] + s * a - o * n,
1279
+ t[1] + o * a + s * n
1280
+ ];
1281
+ }
1282
+ /** Set world position */
1283
+ set worldPosition(t) {
1284
+ if (!this.parent) {
1285
+ this.position = [...t];
1286
+ return;
1287
+ }
1288
+ const e = this.parent.worldPosition, i = this.parent.worldScale, s = -this.parent.worldRotation * Math.PI / 180, o = t[0] - e[0], a = t[1] - e[1], n = Math.cos(s), l = Math.sin(s);
1289
+ this.position = [
1290
+ (n * o - l * a) / i[0],
1291
+ (l * o + n * a) / i[1]
1292
+ ];
1293
+ }
1294
+ /** Get world rotation */
1295
+ get worldRotation() {
1296
+ return this.parent ? this.parent.worldRotation + this.rotation : this.rotation;
1297
+ }
1298
+ /** Set world rotation */
1299
+ set worldRotation(t) {
1300
+ if (!this.parent) {
1301
+ this.rotation = t;
1302
+ return;
1303
+ }
1304
+ this.rotation = t - this.parent.worldRotation;
1305
+ }
1306
+ /** Get world scale */
1307
+ get worldScale() {
1308
+ if (!this.parent) return this.scale;
1309
+ const t = this.parent.worldScale;
1310
+ return [this.scale[0] * t[0], this.scale[1] * t[1]];
1311
+ }
1312
+ /** Add a child transform */
1313
+ addChild(t) {
1314
+ t.parent && t.parent.removeChild(t), t.parent = this, this.children.push(t);
1315
+ }
1316
+ /** Remove a child transform */
1317
+ removeChild(t) {
1318
+ const e = this.children.indexOf(t);
1319
+ e !== -1 && (this.children.splice(e, 1), t.parent = null);
1320
+ }
1321
+ /** Get all children */
1322
+ getChildren() {
1323
+ return this.children;
1324
+ }
1325
+ /** Get forward direction vector */
1326
+ get forward() {
1327
+ const t = this.worldRotation * Math.PI / 180;
1328
+ return [Math.cos(t), Math.sin(t)];
1329
+ }
1330
+ /** Get right direction vector */
1331
+ get right() {
1332
+ const t = (this.worldRotation + 90) * Math.PI / 180;
1333
+ return [Math.cos(t), Math.sin(t)];
1334
+ }
1335
+ /** Translate by offset */
1336
+ translate(t, e) {
1337
+ this.position[0] += t, this.position[1] += e;
1338
+ }
1339
+ /** Rotate by degrees */
1340
+ rotate(t) {
1341
+ this.rotation += t;
1342
+ }
1343
+ /** Look at a target position */
1344
+ lookAt(t) {
1345
+ const e = t[0] - this.worldPosition[0], i = t[1] - this.worldPosition[1];
1346
+ this.worldRotation = Math.atan2(i, e) * 180 / Math.PI;
1347
+ }
1348
+ /** Convert to JSON */
1349
+ toJSON() {
1350
+ return {
1351
+ position: [...this.position],
1352
+ rotation: this.rotation,
1353
+ scale: [...this.scale],
1354
+ zIndex: this.zIndex
1355
+ };
1356
+ }
1357
+ /** Create from JSON */
1358
+ static fromJSON(t) {
1359
+ return new $(t);
1360
+ }
1361
+ }
1362
+ class St {
1363
+ /** Optional transform for auto-sync via sync() */
1364
+ transform;
1365
+ /** Flip sprite horizontally */
1366
+ flipX = !1;
1367
+ /** Flip sprite vertically */
1368
+ flipY = !1;
1369
+ /** Alpha transparency */
1370
+ alpha;
1371
+ renderObject;
1372
+ constructor(t, e = {}) {
1373
+ this.transform = e.transform ?? null, this.alpha = e.alpha ?? 1;
1374
+ const i = {
1375
+ src: e.src,
1376
+ width: e.width ?? 32,
1377
+ height: e.height ?? 32,
1378
+ color: e.color ?? 16711935,
1379
+ anchor: e.anchor ?? [0.5, 0.5],
1380
+ alpha: this.alpha,
1381
+ zIndex: e.zIndex ?? 0
1382
+ };
1383
+ this.renderObject = t.createSprite(i);
1384
+ }
1385
+ /** Sync visual position/rotation/scale from the attached transform. */
1386
+ sync() {
1387
+ if (!this.transform) return;
1388
+ const t = this.transform.worldPosition, e = this.transform.worldRotation, i = this.transform.worldScale;
1389
+ this.renderObject.setPosition(t[0], t[1]), this.renderObject.setRotation(e);
1390
+ const s = this.flipX ? -i[0] : i[0], o = this.flipY ? -i[1] : i[1];
1391
+ this.renderObject.setScale(s, o), this.renderObject.setAlpha(this.alpha), this.renderObject.zIndex = this.transform.zIndex;
1392
+ }
1393
+ /** Manually set position. */
1394
+ setPosition(t, e) {
1395
+ this.renderObject.setPosition(t, e);
1396
+ }
1397
+ /** Manually set rotation (degrees). */
1398
+ setRotation(t) {
1399
+ this.renderObject.setRotation(t);
1400
+ }
1401
+ /** Manually set scale. */
1402
+ setScale(t, e) {
1403
+ this.renderObject.setScale(t, e);
1404
+ }
1405
+ /** Set visibility. */
1406
+ setVisible(t) {
1407
+ this.renderObject.setVisible(t);
1408
+ }
1409
+ /** Set z-index. */
1410
+ set zIndex(t) {
1411
+ this.renderObject.zIndex = t;
1412
+ }
1413
+ get zIndex() {
1414
+ return this.renderObject.zIndex;
1415
+ }
1416
+ /** Destroy the sprite and remove from renderer. */
1417
+ destroy() {
1418
+ this.renderObject.destroy();
1419
+ }
1420
+ }
1421
+ class Et {
1422
+ /** Optional transform for auto-sync via sync() */
1423
+ transform;
1424
+ /** Flip sprite horizontally */
1425
+ flipX = !1;
1426
+ /** Flip sprite vertically */
1427
+ flipY = !1;
1428
+ /** Alpha transparency */
1429
+ alpha;
1430
+ /** Called when a non-looping animation reaches its last frame */
1431
+ onAnimationComplete = null;
1432
+ /** Called every time the displayed frame changes */
1433
+ onFrameChange = null;
1434
+ frameWidth;
1435
+ frameHeight;
1436
+ renderObject;
1437
+ animations;
1438
+ currentAnimation = null;
1439
+ currentFrameIndex = 0;
1440
+ frameAccumulator = 0;
1441
+ isPlaying = !1;
1442
+ sheetColumns = 1;
1443
+ textureReady = !1;
1444
+ constructor(t, e) {
1445
+ if (this.transform = e.transform ?? null, this.alpha = e.alpha ?? 1, this.frameWidth = e.frameWidth, this.frameHeight = e.frameHeight, this.animations = /* @__PURE__ */ new Map(), e.animations)
1446
+ for (const [s, o] of Object.entries(e.animations))
1447
+ this.animations.set(s, {
1448
+ frames: o.frames,
1449
+ frameRate: o.frameRate ?? 12,
1450
+ loop: o.loop ?? !0
1451
+ });
1452
+ const i = {
1453
+ src: e.src,
1454
+ frameWidth: e.frameWidth,
1455
+ frameHeight: e.frameHeight,
1456
+ color: e.color ?? 16711935,
1457
+ anchor: e.anchor ?? [0.5, 0.5],
1458
+ alpha: this.alpha,
1459
+ zIndex: e.zIndex ?? 0,
1460
+ onTextureReady: () => this.onTextureLoaded(e.defaultAnimation)
1461
+ };
1462
+ this.renderObject = t.createAnimatedSprite(i);
1463
+ }
1464
+ /** Must be called each frame to advance animation and sync transform. */
1465
+ update() {
1466
+ if (this.textureReady && this.isPlaying && this.currentAnimation) {
1467
+ const t = this.animations.get(this.currentAnimation);
1468
+ if (t) {
1469
+ const e = 1 / t.frameRate;
1470
+ for (this.frameAccumulator += C.deltaTime; this.frameAccumulator >= e; )
1471
+ this.frameAccumulator -= e, this.advanceFrame(t);
1472
+ }
1473
+ }
1474
+ this.sync();
1475
+ }
1476
+ /** Sync visual from attached transform. */
1477
+ sync() {
1478
+ if (!this.transform) return;
1479
+ const t = this.transform.worldPosition, e = this.transform.worldRotation, i = this.transform.worldScale;
1480
+ this.renderObject.setPosition(t[0], t[1]), this.renderObject.setRotation(e);
1481
+ const s = this.flipX ? -i[0] : i[0], o = this.flipY ? -i[1] : i[1];
1482
+ this.renderObject.setScale(s, o), this.renderObject.setAlpha(this.alpha), this.transform.zIndex !== void 0 && (this.renderObject.zIndex = this.transform.zIndex);
1483
+ }
1484
+ /** Play a named animation. */
1485
+ playAnimation(t, e = !1) {
1486
+ if (!this.animations.has(t)) {
1487
+ console.warn(`Animation "${t}" not found`);
1488
+ return;
1489
+ }
1490
+ this.currentAnimation === t && this.isPlaying && !e || (this.currentAnimation = t, this.currentFrameIndex = 0, this.frameAccumulator = 0, this.isPlaying = !0, this.textureReady && this.applyFrame());
1491
+ }
1492
+ /** Stop animation on current frame. */
1493
+ stop() {
1494
+ this.isPlaying = !1;
1495
+ }
1496
+ /** Resume playing. */
1497
+ play() {
1498
+ this.currentAnimation && (this.isPlaying = !0);
1499
+ }
1500
+ /** Jump to a specific frame. */
1501
+ gotoFrame(t, e = !1) {
1502
+ if (!this.currentAnimation) return;
1503
+ const i = this.animations.get(this.currentAnimation);
1504
+ i && (this.currentFrameIndex = Math.max(0, Math.min(t, i.frames.length - 1)), this.frameAccumulator = 0, this.isPlaying = e, this.textureReady && this.applyFrame());
1505
+ }
1506
+ getCurrentAnimation() {
1507
+ return this.currentAnimation;
1508
+ }
1509
+ isAnimationPlaying() {
1510
+ return this.isPlaying;
1511
+ }
1512
+ hasAnimation(t) {
1513
+ return this.animations.has(t);
1514
+ }
1515
+ getAnimationNames() {
1516
+ return Array.from(this.animations.keys());
1517
+ }
1518
+ setSheetColumns(t) {
1519
+ this.sheetColumns = t;
1520
+ }
1521
+ /** Manually set position. */
1522
+ setPosition(t, e) {
1523
+ this.renderObject.setPosition(t, e);
1524
+ }
1525
+ /** Set visibility. */
1526
+ setVisible(t) {
1527
+ this.renderObject.setVisible(t);
1528
+ }
1529
+ /** Destroy and remove from renderer. */
1530
+ destroy() {
1531
+ this.renderObject.destroy();
1532
+ }
1533
+ onTextureLoaded(t) {
1534
+ this.textureReady = !0;
1535
+ let e = 0;
1536
+ for (const i of this.animations.values())
1537
+ for (const s of i.frames)
1538
+ e = Math.max(e, s);
1539
+ this.sheetColumns = Math.max(4, Math.ceil(Math.sqrt(e + 1))), t && this.animations.has(t) && this.playAnimation(t);
1540
+ }
1541
+ advanceFrame(t) {
1542
+ const e = this.currentFrameIndex + 1;
1543
+ if (e >= t.frames.length)
1544
+ if (t.loop)
1545
+ this.currentFrameIndex = 0;
1546
+ else {
1547
+ this.isPlaying = !1, this.onAnimationComplete?.(this.currentAnimation);
1548
+ return;
1549
+ }
1550
+ else
1551
+ this.currentFrameIndex = e;
1552
+ this.applyFrame(), this.onFrameChange?.(this.currentFrameIndex, this.currentAnimation);
1553
+ }
1554
+ applyFrame() {
1555
+ if (!this.renderObject?.setTextureRegion || !this.currentAnimation) return;
1556
+ const t = this.animations.get(this.currentAnimation);
1557
+ if (!t) return;
1558
+ const e = t.frames[this.currentFrameIndex], i = e % this.sheetColumns, s = Math.floor(e / this.sheetColumns), o = i * this.frameWidth, a = s * this.frameHeight;
1559
+ this.renderObject.setTextureRegion(o, a, this.frameWidth, this.frameHeight);
1560
+ }
1561
+ }
1562
+ class It {
1563
+ /** Zoom level */
1564
+ zoom;
1565
+ /** Follow speed */
1566
+ followSmoothing;
1567
+ /** Offset from target */
1568
+ offset;
1569
+ /** World bounds */
1570
+ bounds;
1571
+ /** Deadzone */
1572
+ deadzone;
1573
+ renderer;
1574
+ targetFn = null;
1575
+ currentPosition = [0, 0];
1576
+ // Screen shake state
1577
+ shakeIntensity = 0;
1578
+ shakeDuration = 0;
1579
+ shakeElapsed = 0;
1580
+ shakeDecay = 0.9;
1581
+ constructor(t, e) {
1582
+ this.renderer = t, this.zoom = e?.zoom ?? 1, this.followSmoothing = e?.followSmoothing ?? 5, this.offset = e?.offset ? [...e.offset] : [0, 0], e?.bounds && (this.bounds = { ...e.bounds }), e?.deadzone && (this.deadzone = { ...e.deadzone });
1583
+ }
1584
+ /**
1585
+ * Set a follow target. Takes a function that returns a position,
1586
+ * avoiding coupling to any specific entity type.
1587
+ */
1588
+ follow(t) {
1589
+ this.targetFn = t;
1590
+ const e = t();
1591
+ this.currentPosition = [e[0] + this.offset[0], e[1] + this.offset[1]];
1592
+ }
1593
+ /** Stop following. */
1594
+ unfollow() {
1595
+ this.targetFn = null;
1596
+ }
1597
+ /** Apply screen shake with intensity that decays over time. */
1598
+ shake(t, e) {
1599
+ this.shakeIntensity = t, this.shakeDuration = e?.duration ?? 0, this.shakeElapsed = 0, this.shakeDecay = e?.decay ?? 0.9;
1600
+ }
1601
+ /** Stop any active screen shake immediately. */
1602
+ stopShake() {
1603
+ this.shakeIntensity = 0;
1604
+ }
1605
+ /** Update camera (call in lateUpdate). */
1606
+ update() {
1607
+ let t = this.getTargetPosition();
1608
+ this.deadzone && (t = this.applyDeadzone(t)), this.currentPosition = this.smoothFollow(t), this.bounds && (this.currentPosition = this.clampToBounds(this.currentPosition));
1609
+ let e = this.currentPosition[0], i = this.currentPosition[1];
1610
+ this.shakeIntensity > 0.5 && (e += (Math.random() * 2 - 1) * this.shakeIntensity, i += (Math.random() * 2 - 1) * this.shakeIntensity, this.shakeIntensity *= Math.pow(this.shakeDecay, C.deltaTime * 60), this.shakeDuration > 0 && (this.shakeElapsed += C.deltaTime, this.shakeElapsed >= this.shakeDuration && (this.shakeIntensity = 0))), this.renderer.setCameraPosition(e, i), this.renderer.setCameraZoom(this.zoom);
1611
+ }
1612
+ /** Instantly move camera to position (no smoothing). */
1613
+ snapTo(t, e) {
1614
+ this.currentPosition = [t, e], this.renderer.setCameraPosition(t, e), this.renderer.setCameraZoom(this.zoom);
1615
+ }
1616
+ /** Get current camera position. */
1617
+ getPosition() {
1618
+ return [...this.currentPosition];
1619
+ }
1620
+ getTargetPosition() {
1621
+ if (this.targetFn) {
1622
+ const t = this.targetFn();
1623
+ return [t[0] + this.offset[0], t[1] + this.offset[1]];
1624
+ }
1625
+ return [...this.currentPosition];
1626
+ }
1627
+ smoothFollow(t) {
1628
+ const e = Math.min(1, this.followSmoothing * C.deltaTime);
1629
+ return [
1630
+ this.currentPosition[0] + (t[0] - this.currentPosition[0]) * e,
1631
+ this.currentPosition[1] + (t[1] - this.currentPosition[1]) * e
1632
+ ];
1633
+ }
1634
+ applyDeadzone(t) {
1635
+ const e = this.deadzone, i = e.width / 2, s = e.height / 2;
1636
+ let [o, a] = t;
1637
+ const [n, l] = this.currentPosition;
1638
+ return o < n - i ? o = o + i : o > n + i ? o = o - i : o = n, a < l - s ? a = a + s : a > l + s ? a = a - s : a = l, [o, a];
1639
+ }
1640
+ clampToBounds(t) {
1641
+ const e = this.bounds, i = this.renderer.getViewportSize(), s = i.width / 2 / this.zoom, o = i.height / 2 / this.zoom;
1642
+ return [
1643
+ Math.max(e.minX + s, Math.min(e.maxX - s, t[0])),
1644
+ Math.max(e.minY + o, Math.min(e.maxY - o, t[1]))
1645
+ ];
1646
+ }
1647
+ }
1648
+ const Pt = {
1649
+ // Letters
1650
+ A: "KeyA",
1651
+ B: "KeyB",
1652
+ C: "KeyC",
1653
+ D: "KeyD",
1654
+ E: "KeyE",
1655
+ F: "KeyF",
1656
+ G: "KeyG",
1657
+ H: "KeyH",
1658
+ I: "KeyI",
1659
+ J: "KeyJ",
1660
+ K: "KeyK",
1661
+ L: "KeyL",
1662
+ M: "KeyM",
1663
+ N: "KeyN",
1664
+ O: "KeyO",
1665
+ P: "KeyP",
1666
+ Q: "KeyQ",
1667
+ R: "KeyR",
1668
+ S: "KeyS",
1669
+ T: "KeyT",
1670
+ U: "KeyU",
1671
+ V: "KeyV",
1672
+ W: "KeyW",
1673
+ X: "KeyX",
1674
+ Y: "KeyY",
1675
+ Z: "KeyZ",
1676
+ // Arrows
1677
+ ArrowUp: "ArrowUp",
1678
+ ArrowDown: "ArrowDown",
1679
+ ArrowLeft: "ArrowLeft",
1680
+ ArrowRight: "ArrowRight",
1681
+ // Common
1682
+ Space: "Space",
1683
+ Enter: "Enter",
1684
+ Escape: "Escape",
1685
+ ShiftLeft: "ShiftLeft",
1686
+ ShiftRight: "ShiftRight",
1687
+ ControlLeft: "ControlLeft",
1688
+ ControlRight: "ControlRight",
1689
+ Tab: "Tab",
1690
+ Backspace: "Backspace",
1691
+ // Numbers
1692
+ Digit0: "Digit0",
1693
+ Digit1: "Digit1",
1694
+ Digit2: "Digit2",
1695
+ Digit3: "Digit3",
1696
+ Digit4: "Digit4",
1697
+ Digit5: "Digit5",
1698
+ Digit6: "Digit6",
1699
+ Digit7: "Digit7",
1700
+ Digit8: "Digit8",
1701
+ Digit9: "Digit9"
1702
+ }, N = {
1703
+ /** Fired when audio context is unlocked by user interaction */
1704
+ UNLOCKED: "audio:unlocked",
1705
+ /** Fired when a volume category changes */
1706
+ VOLUME_CHANGED: "audio:volume:changed"
1707
+ };
1708
+ class ht {
1709
+ /** Cache of loaded sounds by path */
1710
+ cache = /* @__PURE__ */ new Map();
1711
+ /** Volume levels for each category */
1712
+ volumes = {
1713
+ master: 1,
1714
+ music: 1,
1715
+ sfx: 1
1716
+ };
1717
+ /** Whether audio context has been unlocked */
1718
+ unlocked = !1;
1719
+ /** Callbacks waiting for unlock */
1720
+ unlockCallbacks = [];
1721
+ /** Whether init has been called */
1722
+ initialized = !1;
1723
+ /**
1724
+ * Initialize the audio manager.
1725
+ * Sets up browser autoplay unlock listeners.
1726
+ * Safe to call multiple times.
1727
+ */
1728
+ init() {
1729
+ if (this.initialized) return;
1730
+ if (this.initialized = !0, D.ctx?.state === "running") {
1731
+ this.handleUnlock();
1732
+ return;
1733
+ }
1734
+ const t = ["click", "touchstart", "keydown"], e = () => {
1735
+ this.unlocked || (D.ctx?.state === "suspended" ? D.ctx.resume().then(() => {
1736
+ this.handleUnlock();
1737
+ }) : this.handleUnlock(), t.forEach((i) => {
1738
+ document.removeEventListener(i, e, !0);
1739
+ }));
1740
+ };
1741
+ t.forEach((i) => {
1742
+ document.addEventListener(i, e, !0);
1743
+ });
1744
+ }
1745
+ /** Handle successful audio unlock */
1746
+ handleUnlock() {
1747
+ this.unlocked = !0;
1748
+ for (const t of this.unlockCallbacks)
1749
+ try {
1750
+ t();
1751
+ } catch (e) {
1752
+ console.error("Error in audio unlock callback:", e);
1753
+ }
1754
+ this.unlockCallbacks = [], B.emit(N.UNLOCKED);
1755
+ }
1756
+ /**
1757
+ * Set volume for a category.
1758
+ * @param category - The volume category
1759
+ * @param value - Volume level (0-1)
1760
+ */
1761
+ setVolume(t, e) {
1762
+ this.volumes[t] = Math.max(0, Math.min(1, e)), t === "master" && D.volume(this.volumes.master), B.emit(N.VOLUME_CHANGED, { category: t, value: this.volumes[t] });
1763
+ }
1764
+ /**
1765
+ * Get volume for a category.
1766
+ * @param category - The volume category
1767
+ * @returns Volume level (0-1)
1768
+ */
1769
+ getVolume(t) {
1770
+ return this.volumes[t];
1771
+ }
1772
+ /**
1773
+ * Get effective volume for a category (master * category).
1774
+ * @param category - The volume category
1775
+ * @returns Effective volume level (0-1)
1776
+ */
1777
+ getEffectiveVolume(t) {
1778
+ return t === "master" ? this.volumes.master : this.volumes.master * this.volumes[t];
1779
+ }
1780
+ /**
1781
+ * Preload audio files.
1782
+ * @param paths - Array of audio file paths to preload
1783
+ * @returns Promise that resolves when all files are loaded
1784
+ */
1785
+ async preload(t) {
1786
+ const e = t.map((i) => this.getSound(i));
1787
+ await Promise.all(e);
1788
+ }
1789
+ /**
1790
+ * Get or load a sound (async).
1791
+ * @param path - Path to the audio file
1792
+ * @returns Promise that resolves to the Howl instance
1793
+ */
1794
+ async getSound(t) {
1795
+ const e = this.cache.get(t);
1796
+ return e || new Promise((i, s) => {
1797
+ const o = new X({
1798
+ src: [t],
1799
+ preload: !0,
1800
+ onload: () => {
1801
+ this.cache.set(t, o), i(o);
1802
+ },
1803
+ onloaderror: (a, n) => {
1804
+ console.error(`Failed to load audio: ${t}`, n), s(new Error(`Failed to load audio: ${t}`));
1805
+ }
1806
+ });
1807
+ });
1808
+ }
1809
+ /**
1810
+ * Get cached sound (sync).
1811
+ * Returns null if not loaded.
1812
+ * @param path - Path to the audio file
1813
+ * @returns Howl instance or null
1814
+ */
1815
+ getSoundSync(t) {
1816
+ return this.cache.get(t) ?? null;
1817
+ }
1818
+ /**
1819
+ * Check if audio context is unlocked.
1820
+ * @returns True if audio can play without user interaction
1821
+ */
1822
+ isUnlocked() {
1823
+ return this.unlocked;
1824
+ }
1825
+ /**
1826
+ * Register a callback for when audio is unlocked.
1827
+ * Calls immediately if already unlocked.
1828
+ * @param callback - Function to call when unlocked
1829
+ */
1830
+ onUnlock(t) {
1831
+ this.unlocked ? t() : this.unlockCallbacks.push(t);
1832
+ }
1833
+ /**
1834
+ * Unload a specific sound from cache.
1835
+ * @param path - Path to the audio file
1836
+ */
1837
+ unload(t) {
1838
+ const e = this.cache.get(t);
1839
+ e && (e.unload(), this.cache.delete(t));
1840
+ }
1841
+ /**
1842
+ * Destroy and cleanup all sounds.
1843
+ */
1844
+ destroy() {
1845
+ for (const t of this.cache.values())
1846
+ t.unload();
1847
+ this.cache.clear(), this.unlockCallbacks = [];
1848
+ }
1849
+ /**
1850
+ * Pause all sounds.
1851
+ */
1852
+ pauseAll() {
1853
+ for (const t of this.cache.values())
1854
+ t.pause();
1855
+ }
1856
+ /**
1857
+ * Resume all sounds that were playing.
1858
+ */
1859
+ resumeAll() {
1860
+ for (const t of this.cache.values()) {
1861
+ if (t.playing()) continue;
1862
+ const e = t.seek();
1863
+ typeof e == "number" && e > 0 && t.play();
1864
+ }
1865
+ }
1866
+ /**
1867
+ * Stop all sounds.
1868
+ */
1869
+ stopAll() {
1870
+ for (const t of this.cache.values())
1871
+ t.stop();
1872
+ }
1873
+ }
1874
+ const G = new ht();
1875
+ function v() {
1876
+ return G;
1877
+ }
1878
+ class Dt {
1879
+ /** Audio file source path */
1880
+ src;
1881
+ /** Base volume (0-1) */
1882
+ volume;
1883
+ /** Whether to loop */
1884
+ loop;
1885
+ /** Volume category */
1886
+ category;
1887
+ /** Spatial audio enabled */
1888
+ spatial;
1889
+ /** Minimum distance for full volume */
1890
+ minDistance;
1891
+ /** Maximum distance before silent */
1892
+ maxDistance;
1893
+ sound = null;
1894
+ soundId = null;
1895
+ effectiveVolume = 1;
1896
+ _transform = null;
1897
+ constructor(t) {
1898
+ this.src = t?.src ?? "", this.volume = t?.volume ?? 1, this.loop = t?.loop ?? !1, this.category = t?.category ?? "sfx", this.spatial = t?.spatial ?? !1, this.minDistance = t?.minDistance ?? 100, this.maxDistance = t?.maxDistance ?? 500;
1899
+ }
1900
+ /** Set the transform for spatial audio positioning. */
1901
+ setTransform(t) {
1902
+ this._transform = t;
1903
+ }
1904
+ /** Load the audio file. Call before play(). */
1905
+ async load() {
1906
+ if (G.init(), this.src)
1907
+ try {
1908
+ this.sound = await v().getSound(this.src);
1909
+ } catch (t) {
1910
+ console.error(`AudioSource: Failed to load ${this.src}`, t);
1911
+ }
1912
+ }
1913
+ /** Play the sound. */
1914
+ play() {
1915
+ if (!this.sound) {
1916
+ console.warn("AudioSource: No sound loaded");
1917
+ return;
1918
+ }
1919
+ this.soundId !== null && this.sound.stop(this.soundId), this.effectiveVolume = this.volume * v().getEffectiveVolume(this.category), this.sound.loop(this.loop), this.soundId = this.sound.play(), this.sound.volume(this.effectiveVolume, this.soundId), this.spatial && this.updateSpatialAudio(null);
1920
+ }
1921
+ /** Pause playback. */
1922
+ pause() {
1923
+ this.sound && this.soundId !== null && this.sound.pause(this.soundId);
1924
+ }
1925
+ /** Stop playback and reset. */
1926
+ stop() {
1927
+ this.sound && this.soundId !== null && (this.sound.stop(this.soundId), this.soundId = null);
1928
+ }
1929
+ /** Resume paused playback. */
1930
+ resume() {
1931
+ this.sound && this.soundId !== null && this.sound.play(this.soundId);
1932
+ }
1933
+ /** Seek to a specific time in seconds. */
1934
+ seek(t) {
1935
+ this.sound && this.soundId !== null && this.sound.seek(t, this.soundId);
1936
+ }
1937
+ /** Fade volume over time. */
1938
+ fade(t, e, i) {
1939
+ if (this.sound && this.soundId !== null) {
1940
+ const s = t * v().getEffectiveVolume(this.category), o = e * v().getEffectiveVolume(this.category);
1941
+ this.sound.fade(s, o, i, this.soundId);
1942
+ }
1943
+ }
1944
+ /** Check if currently playing. */
1945
+ get playing() {
1946
+ return !this.sound || this.soundId === null ? !1 : this.sound.playing(this.soundId);
1947
+ }
1948
+ /** Get current playback time in seconds. */
1949
+ getTime() {
1950
+ if (!this.sound || this.soundId === null) return 0;
1951
+ const t = this.sound.seek(this.soundId);
1952
+ return typeof t == "number" ? t : 0;
1953
+ }
1954
+ /** Get total duration in seconds. */
1955
+ getDuration() {
1956
+ return this.sound ? this.sound.duration() : 0;
1957
+ }
1958
+ /** Play a one-shot sound. */
1959
+ async playOneShot(t) {
1960
+ const e = t ?? this.src;
1961
+ if (e)
1962
+ try {
1963
+ const i = await v().getSound(e), s = this.volume * v().getEffectiveVolume(this.category), o = i.play();
1964
+ i.volume(s, o);
1965
+ } catch (i) {
1966
+ console.error(`AudioSource: Failed to play one-shot ${e}`, i);
1967
+ }
1968
+ }
1969
+ /** Update spatial audio (call each frame if spatial is enabled). */
1970
+ updateSpatialAudio(t) {
1971
+ if (!this.sound || this.soundId === null || !t || !this._transform) return;
1972
+ const e = t.getViewportSize(), i = t.getCameraPosition(), s = this._transform.worldPosition, o = s[0] - i.x, a = s[1] - i.y, n = Math.sqrt(o * o + a * a), l = e.width / 2, h = Math.max(-1, Math.min(1, o / l));
1973
+ let u = 1;
1974
+ if (n > this.minDistance)
1975
+ if (n >= this.maxDistance)
1976
+ u = 0;
1977
+ else {
1978
+ const p = this.maxDistance - this.minDistance;
1979
+ u = 1 - (n - this.minDistance) / p;
1980
+ }
1981
+ this.sound.stereo(h, this.soundId), this.sound.volume(this.effectiveVolume * u, this.soundId);
1982
+ }
1983
+ /** Destroy and clean up. */
1984
+ destroy() {
1985
+ this.stop(), this.sound = null;
1986
+ }
1987
+ }
1988
+ const dt = Math.PI / 180, V = 180 / Math.PI, zt = {
1989
+ // Creation
1990
+ zero: () => [0, 0],
1991
+ one: () => [1, 1],
1992
+ from: (r, t) => [r, t],
1993
+ // Arithmetic
1994
+ add: (r, t) => [r[0] + t[0], r[1] + t[1]],
1995
+ sub: (r, t) => [r[0] - t[0], r[1] - t[1]],
1996
+ scale: (r, t) => [r[0] * t, r[1] * t],
1997
+ mul: (r, t) => [r[0] * t[0], r[1] * t[1]],
1998
+ negate: (r) => [-r[0], -r[1]],
1999
+ // Geometry
2000
+ length: (r) => Math.sqrt(r[0] * r[0] + r[1] * r[1]),
2001
+ lengthSq: (r) => r[0] * r[0] + r[1] * r[1],
2002
+ distance: (r, t) => {
2003
+ const e = t[0] - r[0], i = t[1] - r[1];
2004
+ return Math.sqrt(e * e + i * i);
2005
+ },
2006
+ distanceSq: (r, t) => {
2007
+ const e = t[0] - r[0], i = t[1] - r[1];
2008
+ return e * e + i * i;
2009
+ },
2010
+ normalize: (r) => {
2011
+ const t = Math.sqrt(r[0] * r[0] + r[1] * r[1]);
2012
+ return t === 0 ? [0, 0] : [r[0] / t, r[1] / t];
2013
+ },
2014
+ dot: (r, t) => r[0] * t[0] + r[1] * t[1],
2015
+ cross: (r, t) => r[0] * t[1] - r[1] * t[0],
2016
+ // Interpolation
2017
+ lerp: (r, t, e) => [
2018
+ r[0] + (t[0] - r[0]) * e,
2019
+ r[1] + (t[1] - r[1]) * e
2020
+ ],
2021
+ // Transformation
2022
+ rotate: (r, t) => {
2023
+ const e = t * dt, i = Math.cos(e), s = Math.sin(e);
2024
+ return [r[0] * i - r[1] * s, r[0] * s + r[1] * i];
2025
+ },
2026
+ reflect: (r, t) => {
2027
+ const e = 2 * (r[0] * t[0] + r[1] * t[1]);
2028
+ return [r[0] - e * t[0], r[1] - e * t[1]];
2029
+ },
2030
+ perpendicular: (r) => [-r[1], r[0]],
2031
+ // Comparison
2032
+ equals: (r, t, e = 1e-6) => Math.abs(r[0] - t[0]) <= e && Math.abs(r[1] - t[1]) <= e,
2033
+ // Utility
2034
+ angle: (r) => Math.atan2(r[1], r[0]) * V,
2035
+ angleBetween: (r, t) => {
2036
+ const e = Math.sqrt(r[0] * r[0] + r[1] * r[1]), i = Math.sqrt(t[0] * t[0] + t[1] * t[1]);
2037
+ if (e === 0 || i === 0) return 0;
2038
+ const s = r[0] * t[0] + r[1] * t[1], o = Math.max(-1, Math.min(1, s / (e * i)));
2039
+ return Math.acos(o) * V;
2040
+ },
2041
+ clampLength: (r, t) => {
2042
+ const e = r[0] * r[0] + r[1] * r[1];
2043
+ if (e <= t * t) return [r[0], r[1]];
2044
+ const i = Math.sqrt(e);
2045
+ return [r[0] / i * t, r[1] / i * t];
2046
+ }
2047
+ };
2048
+ let ct = 1;
2049
+ class P {
2050
+ // ==================== Identity ====================
2051
+ /** Unique identifier for this element */
2052
+ id;
2053
+ /** Human-readable name for debugging */
2054
+ name;
2055
+ // ==================== Positioning ====================
2056
+ /**
2057
+ * Anchor point determines where the element attaches to its parent/screen.
2058
+ *
2059
+ * The anchor affects both:
2060
+ * 1. Which point on the parent/screen we measure from
2061
+ * 2. Which point on this element is placed at that location
2062
+ *
2063
+ * Example: anchor='bottom-right' means:
2064
+ * - Reference the parent's bottom-right corner
2065
+ * - Place this element's bottom-right corner there
2066
+ * - Offset moves the element up and left (into the parent)
2067
+ */
2068
+ anchor = "top-left";
2069
+ /**
2070
+ * Pixel offset from the anchor point.
2071
+ * Positive values move right/down from the anchor.
2072
+ */
2073
+ offset = [0, 0];
2074
+ /**
2075
+ * Requested size in pixels [width, height].
2076
+ * 0 = auto-size to content (uses measureContent())
2077
+ */
2078
+ size = [0, 0];
2079
+ // ==================== Computed Layout ====================
2080
+ /**
2081
+ * Computed position after layout pass (screen coordinates).
2082
+ * Set by parent or UIManager during layout().
2083
+ */
2084
+ computedPosition = [0, 0];
2085
+ /**
2086
+ * Computed size after layout pass.
2087
+ * Either explicit size or measureContent() result.
2088
+ */
2089
+ computedSize = [0, 0];
2090
+ // ==================== Visual Properties ====================
2091
+ /** Whether this element is visible */
2092
+ visible = !0;
2093
+ /** Opacity (0-1) */
2094
+ alpha = 1;
2095
+ /** Render order within parent (higher = on top) */
2096
+ zIndex = 0;
2097
+ // ==================== Hierarchy ====================
2098
+ /** Parent element (null for root elements) */
2099
+ parent = null;
2100
+ /** Child elements */
2101
+ children = [];
2102
+ // ==================== PixiJS ====================
2103
+ /**
2104
+ * The PixiJS container for this element.
2105
+ * Subclasses add their visual content (Graphics, Text, Sprite) to this.
2106
+ */
2107
+ displayObject;
2108
+ // ==================== Layout State ====================
2109
+ /**
2110
+ * Whether layout needs to be recalculated.
2111
+ * Set to true when properties change that affect layout.
2112
+ */
2113
+ layoutDirty = !0;
2114
+ // ==================== Constructor ====================
2115
+ constructor(t = {}) {
2116
+ this.id = t.id ?? `ui_${ct++}`, this.name = t.name ?? this.id, t.anchor !== void 0 && (this.anchor = t.anchor), t.offset !== void 0 && (this.offset = [...t.offset]), t.size !== void 0 && (this.size = [...t.size]), t.visible !== void 0 && (this.visible = t.visible), t.alpha !== void 0 && (this.alpha = t.alpha), t.zIndex !== void 0 && (this.zIndex = t.zIndex), this.displayObject = new A(), this.displayObject.sortableChildren = !0;
2117
+ }
2118
+ // ==================== Lifecycle ====================
2119
+ /**
2120
+ * Called when the element is first added to the UI hierarchy.
2121
+ * Override to perform one-time initialization.
2122
+ */
2123
+ awake() {
2124
+ }
2125
+ /**
2126
+ * Called every frame.
2127
+ * Override for per-frame logic (animations, state updates).
2128
+ */
2129
+ update() {
2130
+ }
2131
+ /**
2132
+ * Called when the element is removed from the UI hierarchy.
2133
+ * Override to clean up resources.
2134
+ */
2135
+ onDestroy() {
2136
+ }
2137
+ // ==================== Hierarchy Management ====================
2138
+ /**
2139
+ * Add a child element.
2140
+ */
2141
+ addChild(t) {
2142
+ t.parent && t.parent.removeChild(t), t.parent = this, this.children.push(t), this.displayObject.addChild(t.displayObject), t.awake(), this.markLayoutDirty();
2143
+ }
2144
+ /**
2145
+ * Remove a child element.
2146
+ */
2147
+ removeChild(t) {
2148
+ const e = this.children.indexOf(t);
2149
+ e !== -1 && (t.onDestroy(), t.parent = null, this.children.splice(e, 1), this.displayObject.removeChild(t.displayObject), this.markLayoutDirty());
2150
+ }
2151
+ /**
2152
+ * Remove all children.
2153
+ */
2154
+ removeAllChildren() {
2155
+ for (const t of [...this.children])
2156
+ this.removeChild(t);
2157
+ }
2158
+ /**
2159
+ * Get all children.
2160
+ */
2161
+ getChildren() {
2162
+ return this.children;
2163
+ }
2164
+ /**
2165
+ * Get the PixiJS display object.
2166
+ * Used by UIManager to add to render tree.
2167
+ */
2168
+ getDisplayObject() {
2169
+ return this.displayObject;
2170
+ }
2171
+ // ==================== Layout ====================
2172
+ /**
2173
+ * Mark this element and its ancestors as needing layout recalculation.
2174
+ */
2175
+ markLayoutDirty() {
2176
+ this.layoutDirty = !0, this.parent && this.parent.markLayoutDirty();
2177
+ }
2178
+ /**
2179
+ * Measure the intrinsic content size.
2180
+ * Override in subclasses to return the natural size of the content.
2181
+ *
2182
+ * Examples:
2183
+ * - UIText: Returns the text bounds
2184
+ * - UIImage: Returns the image dimensions
2185
+ * - UIPanel: Returns size needed to contain children + padding
2186
+ *
2187
+ * @returns [width, height] in pixels
2188
+ */
2189
+ measureContent() {
2190
+ return [0, 0];
2191
+ }
2192
+ /**
2193
+ * Compute layout for this element and its children.
2194
+ * Called by UIManager each frame on root elements.
2195
+ *
2196
+ * @param viewport - The available space (screen or parent bounds)
2197
+ */
2198
+ layout(t) {
2199
+ const e = this.measureContent();
2200
+ this.computedSize = [
2201
+ this.size[0] > 0 ? this.size[0] : e[0],
2202
+ this.size[1] > 0 ? this.size[1] : e[1]
2203
+ ], this.computedPosition = this.computeAnchoredPosition(t), this.displayObject.position.set(this.computedPosition[0], this.computedPosition[1]), this.displayObject.alpha = this.alpha, this.displayObject.visible = this.visible, this.displayObject.zIndex = this.zIndex, this.layoutChildren(t), this.layoutDirty = !1;
2204
+ }
2205
+ /**
2206
+ * Layout child elements.
2207
+ * Override in VBox/HBox to implement custom layout logic.
2208
+ */
2209
+ layoutChildren(t) {
2210
+ for (const e of this.children)
2211
+ e.layout({
2212
+ width: this.computedSize[0],
2213
+ height: this.computedSize[1]
2214
+ });
2215
+ }
2216
+ /**
2217
+ * Compute position based on anchor point and offset.
2218
+ *
2219
+ * The anchor system works like this:
2220
+ * ┌─────────────────────────────────────────┐
2221
+ * │ (0,0) (w/2,0) (w,0) │
2222
+ * │ top-left top-center top-right │
2223
+ * │ │
2224
+ * │ (0,h/2) (w/2,h/2) (w,h/2) │
2225
+ * │ center-left center center-right │
2226
+ * │ │
2227
+ * │ (0,h) (w/2,h) (w,h) │
2228
+ * │ bottom-left bottom-center bottom-right │
2229
+ * └─────────────────────────────────────────┘
2230
+ */
2231
+ computeAnchoredPosition(t) {
2232
+ const { width: e, height: i } = t, [s, o] = this.computedSize, [a, n] = this.offset;
2233
+ let l = 0, h = 0;
2234
+ return this.anchor.includes("left") ? l = a : this.anchor.includes("right") ? l = e - s - a : l = (e - s) / 2 + a, this.anchor.includes("top") ? h = n : this.anchor.includes("bottom") ? h = i - o - n : h = (i - o) / 2 + n, [l, h];
2235
+ }
2236
+ // ==================== Hit Testing ====================
2237
+ /**
2238
+ * Get the world/screen position of this element.
2239
+ *
2240
+ * For nested elements, computedPosition is in LOCAL coordinates (relative to parent).
2241
+ * This method walks up the parent chain to compute the absolute screen position.
2242
+ *
2243
+ * Example:
2244
+ * - Panel at screen position (600, 400)
2245
+ * - Button at local position (10, 10)
2246
+ * - Button's world position = (610, 410)
2247
+ */
2248
+ getWorldPosition() {
2249
+ let t = this.computedPosition[0], e = this.computedPosition[1], i = this.parent;
2250
+ for (; i; )
2251
+ t += i.computedPosition[0], e += i.computedPosition[1], i = i.parent;
2252
+ return [t, e];
2253
+ }
2254
+ /**
2255
+ * Check if a screen point is inside this element's bounds.
2256
+ * Uses world position for accurate hit testing of nested elements.
2257
+ */
2258
+ containsPoint(t) {
2259
+ if (!this.visible) return !1;
2260
+ const [e, i] = t, [s, o] = this.getWorldPosition(), [a, n] = this.computedSize;
2261
+ return e >= s && e <= s + a && i >= o && i <= o + n;
2262
+ }
2263
+ /**
2264
+ * Find the topmost element at a screen point.
2265
+ * Searches children first (in reverse z-order) then self.
2266
+ */
2267
+ hitTest(t) {
2268
+ if (!this.visible) return null;
2269
+ const e = [...this.children].sort((i, s) => s.zIndex - i.zIndex);
2270
+ for (const i of e) {
2271
+ const s = i.hitTest(t);
2272
+ if (s) return s;
2273
+ }
2274
+ return this.containsPoint(t) ? this : null;
2275
+ }
2276
+ // ==================== Getters ====================
2277
+ /** Get computed position after layout */
2278
+ getComputedPosition() {
2279
+ return [...this.computedPosition];
2280
+ }
2281
+ /**
2282
+ * Offset the computed position after layout.
2283
+ * Used by layout containers (UIPanel) to adjust child positions for padding.
2284
+ * This keeps computedPosition in sync with displayObject.position.
2285
+ */
2286
+ offsetComputedPosition(t, e) {
2287
+ this.computedPosition[0] += t, this.computedPosition[1] += e;
2288
+ }
2289
+ /**
2290
+ * Set the computed position directly.
2291
+ * Used by layout containers (UIVBox, UIHBox) that completely override child positioning.
2292
+ * Also updates the displayObject position.
2293
+ */
2294
+ setComputedPosition(t, e) {
2295
+ this.computedPosition[0] = t, this.computedPosition[1] = e, this.displayObject.position.set(t, e);
2296
+ }
2297
+ /** Get computed size after layout */
2298
+ getComputedSize() {
2299
+ return [...this.computedSize];
2300
+ }
2301
+ /** Get computed bounds as {x, y, width, height} */
2302
+ getComputedBounds() {
2303
+ return {
2304
+ x: this.computedPosition[0],
2305
+ y: this.computedPosition[1],
2306
+ width: this.computedSize[0],
2307
+ height: this.computedSize[1]
2308
+ };
2309
+ }
2310
+ // ==================== Destruction ====================
2311
+ /**
2312
+ * Destroy this element and all children.
2313
+ */
2314
+ destroy() {
2315
+ for (const t of [...this.children])
2316
+ t.destroy();
2317
+ this.children = [], this.onDestroy(), this.parent && this.parent.removeChild(this), this.displayObject.destroy({ children: !0 });
2318
+ }
2319
+ }
2320
+ function z(r) {
2321
+ return "onPointerEnter" in r || "onPointerExit" in r || "onPointerDown" in r || "onPointerUp" in r;
2322
+ }
2323
+ class _t {
2324
+ /** Root UI elements (not parented to other UI elements) */
2325
+ roots = [];
2326
+ /** Reference to the renderer for viewport size */
2327
+ renderer;
2328
+ /** Currently hovered element */
2329
+ hoveredElement = null;
2330
+ /** Currently pressed element (mouse button held) */
2331
+ pressedElement = null;
2332
+ /** Whether the UI system is active */
2333
+ active = !0;
2334
+ constructor(t) {
2335
+ this.renderer = t;
2336
+ }
2337
+ // ==================== Root Management ====================
2338
+ /**
2339
+ * Add a root UI element.
2340
+ * Root elements are positioned relative to the screen.
2341
+ */
2342
+ addRoot(t) {
2343
+ if (this.roots.includes(t)) {
2344
+ console.warn(`UIElement ${t.id} is already a root`);
2345
+ return;
2346
+ }
2347
+ this.roots.push(t), this.renderer.addToUI(t.getDisplayObject()), t.awake();
2348
+ }
2349
+ /**
2350
+ * Remove a root UI element.
2351
+ */
2352
+ removeRoot(t) {
2353
+ const e = this.roots.indexOf(t);
2354
+ e !== -1 && (this.roots.splice(e, 1), this.renderer.removeFromUI(t.getDisplayObject()), t.onDestroy());
2355
+ }
2356
+ /**
2357
+ * Get all root elements.
2358
+ */
2359
+ getRoots() {
2360
+ return this.roots;
2361
+ }
2362
+ // ==================== Input Processing ====================
2363
+ /**
2364
+ * Process input events for the UI.
2365
+ * Call this BEFORE game input processing.
2366
+ *
2367
+ * @returns true if UI consumed the input (game should ignore it)
2368
+ */
2369
+ processInput() {
2370
+ if (!this.active) return !1;
2371
+ const t = y.mousePosition;
2372
+ let e = !1;
2373
+ const i = this.hitTestAll(t);
2374
+ if (i !== this.hoveredElement) {
2375
+ if (this.hoveredElement && z(this.hoveredElement)) {
2376
+ const s = this.createPointerEvent(t);
2377
+ this.hoveredElement.onPointerExit?.(s);
2378
+ }
2379
+ if (i && z(i)) {
2380
+ const s = this.createPointerEvent(t);
2381
+ i.onPointerEnter?.(s);
2382
+ }
2383
+ this.hoveredElement = i;
2384
+ }
2385
+ if (y.getMouseButtonDown(0) && i && z(i)) {
2386
+ this.pressedElement = i;
2387
+ const s = this.createPointerEvent(t, 0);
2388
+ i.onPointerDown?.(s), e = s.consumed || !0;
2389
+ }
2390
+ if (y.getMouseButtonUp(0)) {
2391
+ if (this.pressedElement && z(this.pressedElement)) {
2392
+ const s = this.createPointerEvent(t, 0);
2393
+ this.pressedElement.onPointerUp?.(s), e = s.consumed || e;
2394
+ }
2395
+ this.pressedElement = null;
2396
+ }
2397
+ return i && (e = !0), e;
2398
+ }
2399
+ /**
2400
+ * Hit test all root elements.
2401
+ * Returns the topmost element at the given point.
2402
+ */
2403
+ hitTestAll(t) {
2404
+ const e = [...this.roots].sort((i, s) => s.zIndex - i.zIndex);
2405
+ for (const i of e) {
2406
+ const s = i.hitTest(t);
2407
+ if (s) return s;
2408
+ }
2409
+ return null;
2410
+ }
2411
+ /**
2412
+ * Create a pointer event object.
2413
+ */
2414
+ createPointerEvent(t, e = 0) {
2415
+ return {
2416
+ position: [...t],
2417
+ button: e,
2418
+ consumed: !1
2419
+ };
2420
+ }
2421
+ // ==================== Update & Layout ====================
2422
+ /**
2423
+ * Update all UI elements.
2424
+ * Call once per frame.
2425
+ */
2426
+ update() {
2427
+ if (this.active)
2428
+ for (const t of this.roots)
2429
+ this.updateElement(t);
2430
+ }
2431
+ /**
2432
+ * Recursively update an element and its children.
2433
+ */
2434
+ updateElement(t) {
2435
+ t.update();
2436
+ for (const e of t.getChildren())
2437
+ this.updateElement(e);
2438
+ }
2439
+ /**
2440
+ * Run layout pass on all root elements.
2441
+ * Call once per frame after update.
2442
+ */
2443
+ layout() {
2444
+ if (!this.active) return;
2445
+ const t = this.renderer.getViewportSize();
2446
+ for (const e of this.roots)
2447
+ e.layout(t);
2448
+ }
2449
+ // ==================== Lifecycle ====================
2450
+ /**
2451
+ * Enable or disable the UI system.
2452
+ */
2453
+ setActive(t) {
2454
+ this.active = t;
2455
+ }
2456
+ /**
2457
+ * Check if the UI system is active.
2458
+ */
2459
+ isActive() {
2460
+ return this.active;
2461
+ }
2462
+ /**
2463
+ * Destroy the UI manager and all root elements.
2464
+ */
2465
+ destroy() {
2466
+ for (const t of [...this.roots])
2467
+ t.destroy(), this.renderer.removeFromUI(t.getDisplayObject());
2468
+ this.roots = [], this.hoveredElement = null, this.pressedElement = null;
2469
+ }
2470
+ // ==================== Utilities ====================
2471
+ /**
2472
+ * Find a UI element by ID.
2473
+ */
2474
+ findById(t) {
2475
+ for (const e of this.roots) {
2476
+ const i = this.findByIdInTree(e, t);
2477
+ if (i) return i;
2478
+ }
2479
+ return null;
2480
+ }
2481
+ /**
2482
+ * Recursively search for an element by ID.
2483
+ */
2484
+ findByIdInTree(t, e) {
2485
+ if (t.id === e) return t;
2486
+ for (const i of t.getChildren()) {
2487
+ const s = this.findByIdInTree(i, e);
2488
+ if (s) return s;
2489
+ }
2490
+ return null;
2491
+ }
2492
+ /**
2493
+ * Find a UI element by name.
2494
+ */
2495
+ findByName(t) {
2496
+ for (const e of this.roots) {
2497
+ const i = this.findByNameInTree(e, t);
2498
+ if (i) return i;
2499
+ }
2500
+ return null;
2501
+ }
2502
+ /**
2503
+ * Recursively search for an element by name.
2504
+ */
2505
+ findByNameInTree(t, e) {
2506
+ if (t.name === e) return t;
2507
+ for (const i of t.getChildren()) {
2508
+ const s = this.findByNameInTree(i, e);
2509
+ if (s) return s;
2510
+ }
2511
+ return null;
2512
+ }
2513
+ }
2514
+ class Bt extends P {
2515
+ /** The PixiJS Text object */
2516
+ textDisplay;
2517
+ /** Current text style settings */
2518
+ _text;
2519
+ _fontSize;
2520
+ _fontFamily;
2521
+ _color;
2522
+ _fontWeight;
2523
+ _align;
2524
+ _wordWrapWidth;
2525
+ constructor(t = {}) {
2526
+ super(t), this._text = t.text ?? "", this._fontSize = t.fontSize ?? 16, this._fontFamily = t.fontFamily ?? "Arial, sans-serif", this._color = t.color ?? "#ffffff", this._fontWeight = t.fontWeight ?? "normal", this._align = t.align ?? "left", this._wordWrapWidth = t.wordWrapWidth ?? 0;
2527
+ const e = this.createStyle();
2528
+ this.textDisplay = new q({ text: this._text, style: e }), this.displayObject.addChild(this.textDisplay);
2529
+ }
2530
+ // ==================== Text Properties ====================
2531
+ /** Get the current text content */
2532
+ get text() {
2533
+ return this._text;
2534
+ }
2535
+ /** Set the text content */
2536
+ set text(t) {
2537
+ this._text !== t && (this._text = t, this.textDisplay.text = t, this.markLayoutDirty());
2538
+ }
2539
+ /** Get font size in pixels */
2540
+ get fontSize() {
2541
+ return this._fontSize;
2542
+ }
2543
+ /** Set font size in pixels */
2544
+ set fontSize(t) {
2545
+ this._fontSize !== t && (this._fontSize = t, this.updateStyle(), this.markLayoutDirty());
2546
+ }
2547
+ /** Get font family */
2548
+ get fontFamily() {
2549
+ return this._fontFamily;
2550
+ }
2551
+ /** Set font family */
2552
+ set fontFamily(t) {
2553
+ this._fontFamily !== t && (this._fontFamily = t, this.updateStyle(), this.markLayoutDirty());
2554
+ }
2555
+ /** Get text color */
2556
+ get color() {
2557
+ return this._color;
2558
+ }
2559
+ /** Set text color */
2560
+ set color(t) {
2561
+ this._color !== t && (this._color = t, this.updateStyle());
2562
+ }
2563
+ /** Get font weight */
2564
+ get fontWeight() {
2565
+ return this._fontWeight;
2566
+ }
2567
+ /** Set font weight */
2568
+ set fontWeight(t) {
2569
+ this._fontWeight !== t && (this._fontWeight = t, this.updateStyle(), this.markLayoutDirty());
2570
+ }
2571
+ /** Get text alignment */
2572
+ get align() {
2573
+ return this._align;
2574
+ }
2575
+ /** Set text alignment */
2576
+ set align(t) {
2577
+ this._align !== t && (this._align = t, this.updateStyle());
2578
+ }
2579
+ /** Get word wrap width (0 = no wrap) */
2580
+ get wordWrapWidth() {
2581
+ return this._wordWrapWidth;
2582
+ }
2583
+ /** Set word wrap width (0 = no wrap) */
2584
+ set wordWrapWidth(t) {
2585
+ this._wordWrapWidth !== t && (this._wordWrapWidth = t, this.updateStyle(), this.markLayoutDirty());
2586
+ }
2587
+ // ==================== Internal ====================
2588
+ /**
2589
+ * Create a PixiJS TextStyle from our properties.
2590
+ */
2591
+ createStyle() {
2592
+ const t = typeof this._fontWeight == "number" ? this._fontWeight >= 600 ? "bold" : "normal" : this._fontWeight;
2593
+ return new Z({
2594
+ fontFamily: this._fontFamily,
2595
+ fontSize: this._fontSize,
2596
+ fontWeight: t,
2597
+ fill: this._color,
2598
+ align: this._align,
2599
+ wordWrap: this._wordWrapWidth > 0,
2600
+ wordWrapWidth: this._wordWrapWidth || void 0
2601
+ });
2602
+ }
2603
+ /**
2604
+ * Update the text style when properties change.
2605
+ */
2606
+ updateStyle() {
2607
+ this.textDisplay.style = this.createStyle();
2608
+ }
2609
+ // ==================== Layout ====================
2610
+ /**
2611
+ * Measure the text bounds as the content size.
2612
+ */
2613
+ measureContent() {
2614
+ const t = this.textDisplay.getBounds();
2615
+ return [t.width, t.height];
2616
+ }
2617
+ }
2618
+ class At extends P {
2619
+ /** The PixiJS Sprite or placeholder Graphics */
2620
+ imageDisplay;
2621
+ /** Image source path */
2622
+ _src;
2623
+ /** Explicit width (0 = use image width) */
2624
+ _width;
2625
+ /** Explicit height (0 = use image height) */
2626
+ _height;
2627
+ /** Tint color */
2628
+ _tint;
2629
+ /** Whether the texture has loaded */
2630
+ textureLoaded = !1;
2631
+ /** Natural image dimensions */
2632
+ naturalWidth = 0;
2633
+ naturalHeight = 0;
2634
+ constructor(t = {}) {
2635
+ super(t), this._src = t.src ?? "", this._width = t.width ?? 0, this._height = t.height ?? 0, this._tint = t.tint ?? 16777215;
2636
+ const e = this._width || 32, i = this._height || 32, s = new I();
2637
+ s.rect(0, 0, e, i), s.fill(8947848), this.imageDisplay = s, this.displayObject.addChild(this.imageDisplay), this._src && this.loadTexture(this._src);
2638
+ }
2639
+ // ==================== Properties ====================
2640
+ /** Get the image source path */
2641
+ get src() {
2642
+ return this._src;
2643
+ }
2644
+ /** Set the image source path (triggers async load) */
2645
+ set src(t) {
2646
+ this._src !== t && (this._src = t, this.textureLoaded = !1, t && this.loadTexture(t));
2647
+ }
2648
+ /** Get explicit width */
2649
+ get width() {
2650
+ return this._width;
2651
+ }
2652
+ /** Set explicit width (0 = use natural width) */
2653
+ set width(t) {
2654
+ this._width !== t && (this._width = t, this.updateImageSize(), this.markLayoutDirty());
2655
+ }
2656
+ /** Get explicit height */
2657
+ get height() {
2658
+ return this._height;
2659
+ }
2660
+ /** Set explicit height (0 = use natural height) */
2661
+ set height(t) {
2662
+ this._height !== t && (this._height = t, this.updateImageSize(), this.markLayoutDirty());
2663
+ }
2664
+ /** Get tint color */
2665
+ get tint() {
2666
+ return this._tint;
2667
+ }
2668
+ /** Set tint color */
2669
+ set tint(t) {
2670
+ this._tint = t, this.imageDisplay instanceof S && (this.imageDisplay.tint = t);
2671
+ }
2672
+ // ==================== Internal ====================
2673
+ /**
2674
+ * Load a texture from the given path.
2675
+ */
2676
+ async loadTexture(t) {
2677
+ try {
2678
+ const e = await K.load(t);
2679
+ e && e !== E.WHITE && this._src === t && this.replaceWithSprite(e);
2680
+ } catch {
2681
+ console.warn(`UIImage: Failed to load texture: ${t}`);
2682
+ }
2683
+ }
2684
+ /**
2685
+ * Replace the placeholder with a loaded sprite.
2686
+ */
2687
+ replaceWithSprite(t) {
2688
+ this.naturalWidth = t.width, this.naturalHeight = t.height;
2689
+ const e = new S(t);
2690
+ e.tint = this._tint, this.displayObject.removeChild(this.imageDisplay), this.imageDisplay instanceof I && this.imageDisplay.destroy(), this.imageDisplay = e, this.displayObject.addChild(e), this.textureLoaded = !0, this.updateImageSize(), this.markLayoutDirty();
2691
+ }
2692
+ /**
2693
+ * Update the sprite size based on explicit dimensions.
2694
+ */
2695
+ updateImageSize() {
2696
+ if (!(this.imageDisplay instanceof S)) return;
2697
+ const t = this._width || this.naturalWidth, e = this._height || this.naturalHeight;
2698
+ this.imageDisplay.width = t, this.imageDisplay.height = e;
2699
+ }
2700
+ // ==================== Layout ====================
2701
+ /**
2702
+ * Measure the image dimensions as content size.
2703
+ */
2704
+ measureContent() {
2705
+ return this.textureLoaded ? [
2706
+ this._width || this.naturalWidth,
2707
+ this._height || this.naturalHeight
2708
+ ] : [this._width || 32, this._height || 32];
2709
+ }
2710
+ }
2711
+ class ut extends P {
2712
+ /** The PixiJS Graphics for background rendering */
2713
+ background;
2714
+ /** Background color (protected for subclass access) */
2715
+ _backgroundColor;
2716
+ /** Border color */
2717
+ _borderColor;
2718
+ /** Border width in pixels */
2719
+ _borderWidth;
2720
+ /** Corner radius for rounded rectangles */
2721
+ _borderRadius;
2722
+ /** Inner padding [top, right, bottom, left] */
2723
+ _padding;
2724
+ constructor(t = {}) {
2725
+ super(t), this._backgroundColor = t.backgroundColor ?? 3355443, this._borderColor = t.borderColor ?? 0, this._borderWidth = t.borderWidth ?? 0, this._borderRadius = t.borderRadius ?? 0, this._padding = t.padding ?? [0, 0, 0, 0], this.background = new I(), this.background.zIndex = -1, this.displayObject.addChild(this.background);
2726
+ }
2727
+ // ==================== Properties ====================
2728
+ /** Get background color */
2729
+ get backgroundColor() {
2730
+ return this._backgroundColor;
2731
+ }
2732
+ /** Set background color */
2733
+ set backgroundColor(t) {
2734
+ this._backgroundColor !== t && (this._backgroundColor = t, this.redrawBackground());
2735
+ }
2736
+ /** Get border color */
2737
+ get borderColor() {
2738
+ return this._borderColor;
2739
+ }
2740
+ /** Set border color */
2741
+ set borderColor(t) {
2742
+ this._borderColor !== t && (this._borderColor = t, this.redrawBackground());
2743
+ }
2744
+ /** Get border width */
2745
+ get borderWidth() {
2746
+ return this._borderWidth;
2747
+ }
2748
+ /** Set border width */
2749
+ set borderWidth(t) {
2750
+ this._borderWidth !== t && (this._borderWidth = t, this.redrawBackground(), this.markLayoutDirty());
2751
+ }
2752
+ /** Get border radius */
2753
+ get borderRadius() {
2754
+ return this._borderRadius;
2755
+ }
2756
+ /** Set border radius */
2757
+ set borderRadius(t) {
2758
+ this._borderRadius !== t && (this._borderRadius = t, this.redrawBackground());
2759
+ }
2760
+ /** Get padding */
2761
+ get padding() {
2762
+ return [...this._padding];
2763
+ }
2764
+ /** Set padding [top, right, bottom, left] */
2765
+ set padding(t) {
2766
+ this._padding = [...t], this.markLayoutDirty();
2767
+ }
2768
+ // ==================== Internal ====================
2769
+ /**
2770
+ * Get the inner bounds (after padding).
2771
+ */
2772
+ getInnerBounds() {
2773
+ const [t, e, i, s] = this._padding;
2774
+ return {
2775
+ x: s,
2776
+ y: t,
2777
+ width: Math.max(0, this.computedSize[0] - s - e),
2778
+ height: Math.max(0, this.computedSize[1] - t - i)
2779
+ };
2780
+ }
2781
+ /**
2782
+ * Redraw the background graphics.
2783
+ */
2784
+ redrawBackground() {
2785
+ const [t, e] = this.computedSize;
2786
+ if (!(t <= 0 || e <= 0))
2787
+ if (this.background.clear(), this._borderWidth > 0) {
2788
+ this.background.roundRect(0, 0, t, e, this._borderRadius), this.background.fill(this._borderColor);
2789
+ const i = this._borderWidth, s = Math.max(0, this._borderRadius - i);
2790
+ this.background.roundRect(i, i, t - i * 2, e - i * 2, s), this.background.fill(this._backgroundColor);
2791
+ } else
2792
+ this.background.roundRect(0, 0, t, e, this._borderRadius), this.background.fill(this._backgroundColor);
2793
+ }
2794
+ // ==================== Layout ====================
2795
+ /**
2796
+ * Measure content size (size needed to contain children + padding).
2797
+ */
2798
+ measureContent() {
2799
+ const [t, e, i, s] = this._padding;
2800
+ let o = 0, a = 0;
2801
+ for (const n of this.children) {
2802
+ const l = n.getComputedSize(), h = n.getComputedPosition();
2803
+ o = Math.max(o, h[0] + l[0]), a = Math.max(a, h[1] + l[1]);
2804
+ }
2805
+ return [o + s + e, a + t + i];
2806
+ }
2807
+ /**
2808
+ * Override layout to position children within padding and redraw background.
2809
+ */
2810
+ layout(t) {
2811
+ super.layout(t), this.redrawBackground();
2812
+ }
2813
+ /**
2814
+ * Layout children within the padded content area.
2815
+ */
2816
+ layoutChildren(t) {
2817
+ const e = this.getInnerBounds();
2818
+ for (const i of this.children) {
2819
+ i.layout({ width: e.width, height: e.height });
2820
+ const s = i.getDisplayObject();
2821
+ s.position.x += e.x, s.position.y += e.y, i.offsetComputedPosition(e.x, e.y);
2822
+ }
2823
+ }
2824
+ }
2825
+ class Ot extends ut {
2826
+ /** Current button state */
2827
+ _state = "normal";
2828
+ /** Click handler */
2829
+ _onClick;
2830
+ /** Color when hovered */
2831
+ _hoverColor;
2832
+ /** Color when pressed */
2833
+ _pressedColor;
2834
+ /** Color when disabled */
2835
+ _disabledColor;
2836
+ /** Whether the button is disabled */
2837
+ _disabled;
2838
+ /** Store the normal background color for state changes */
2839
+ _normalColor;
2840
+ constructor(t = {}) {
2841
+ super(t), this._onClick = t.onClick ?? null, this._normalColor = t.backgroundColor ?? 3355443, this._hoverColor = t.hoverColor ?? this.lightenColor(this._normalColor, 0.2), this._pressedColor = t.pressedColor ?? this.darkenColor(this._normalColor, 0.2), this._disabledColor = t.disabledColor ?? 6710886, this._disabled = t.disabled ?? !1, this.updateVisualState();
2842
+ }
2843
+ // ==================== Properties ====================
2844
+ /** Get current button state */
2845
+ get state() {
2846
+ return this._state;
2847
+ }
2848
+ /** Get onClick handler */
2849
+ get onClick() {
2850
+ return this._onClick;
2851
+ }
2852
+ /** Set onClick handler */
2853
+ set onClick(t) {
2854
+ this._onClick = t;
2855
+ }
2856
+ /** Get hover color */
2857
+ get hoverColor() {
2858
+ return this._hoverColor;
2859
+ }
2860
+ /** Set hover color */
2861
+ set hoverColor(t) {
2862
+ this._hoverColor = t, this.updateVisualState();
2863
+ }
2864
+ /** Get pressed color */
2865
+ get pressedColor() {
2866
+ return this._pressedColor;
2867
+ }
2868
+ /** Set pressed color */
2869
+ set pressedColor(t) {
2870
+ this._pressedColor = t, this.updateVisualState();
2871
+ }
2872
+ /** Get disabled color */
2873
+ get disabledColor() {
2874
+ return this._disabledColor;
2875
+ }
2876
+ /** Set disabled color */
2877
+ set disabledColor(t) {
2878
+ this._disabledColor = t, this.updateVisualState();
2879
+ }
2880
+ /** Check if button is disabled */
2881
+ get disabled() {
2882
+ return this._disabled;
2883
+ }
2884
+ /** Set disabled state */
2885
+ set disabled(t) {
2886
+ this._disabled !== t && (this._disabled = t, this._state = t ? "disabled" : "normal", this.updateVisualState());
2887
+ }
2888
+ // ==================== Pointer Events (UIInteractive) ====================
2889
+ /**
2890
+ * Called when pointer enters button bounds.
2891
+ */
2892
+ onPointerEnter(t) {
2893
+ this._disabled || (this._state = "hover", this.updateVisualState());
2894
+ }
2895
+ /**
2896
+ * Called when pointer exits button bounds.
2897
+ */
2898
+ onPointerExit(t) {
2899
+ this._disabled || (this._state = "normal", this.updateVisualState());
2900
+ }
2901
+ /**
2902
+ * Called when pointer button is pressed on button.
2903
+ */
2904
+ onPointerDown(t) {
2905
+ this._disabled || (this._state = "pressed", this.updateVisualState(), t.consumed = !0);
2906
+ }
2907
+ /**
2908
+ * Called when pointer button is released on button.
2909
+ * Fires onClick if the button was being pressed.
2910
+ */
2911
+ onPointerUp(t) {
2912
+ this._disabled || (this._state === "pressed" && (this._state = "hover", this.updateVisualState(), this._onClick && this._onClick()), t.consumed = !0);
2913
+ }
2914
+ // ==================== Internal ====================
2915
+ /**
2916
+ * Update visual appearance based on current state.
2917
+ */
2918
+ updateVisualState() {
2919
+ let t;
2920
+ switch (this._state) {
2921
+ case "hover":
2922
+ t = this._hoverColor;
2923
+ break;
2924
+ case "pressed":
2925
+ t = this._pressedColor;
2926
+ break;
2927
+ case "disabled":
2928
+ t = this._disabledColor;
2929
+ break;
2930
+ default:
2931
+ t = this._normalColor;
2932
+ }
2933
+ this._backgroundColor = t, this.redrawBackground();
2934
+ }
2935
+ /**
2936
+ * Override backgroundColor setter to also update normalColor.
2937
+ */
2938
+ set backgroundColor(t) {
2939
+ this._normalColor = t, this._backgroundColor = t, this._hoverColor = this.lightenColor(t, 0.2), this._pressedColor = this.darkenColor(t, 0.2), this.updateVisualState();
2940
+ }
2941
+ get backgroundColor() {
2942
+ return this._normalColor;
2943
+ }
2944
+ // ==================== Color Utilities ====================
2945
+ /**
2946
+ * Lighten a color by a factor (0-1).
2947
+ */
2948
+ lightenColor(t, e) {
2949
+ const i = Math.min(255, (t >> 16 & 255) + 255 * e), s = Math.min(255, (t >> 8 & 255) + 255 * e), o = Math.min(255, (t & 255) + 255 * e);
2950
+ return Math.floor(i) << 16 | Math.floor(s) << 8 | Math.floor(o);
2951
+ }
2952
+ /**
2953
+ * Darken a color by a factor (0-1).
2954
+ */
2955
+ darkenColor(t, e) {
2956
+ const i = (t >> 16 & 255) * (1 - e), s = (t >> 8 & 255) * (1 - e), o = (t & 255) * (1 - e);
2957
+ return Math.floor(i) << 16 | Math.floor(s) << 8 | Math.floor(o);
2958
+ }
2959
+ }
2960
+ class Mt extends P {
2961
+ /** Space between children in pixels */
2962
+ _gap;
2963
+ /** Inner padding [top, right, bottom, left] */
2964
+ _padding;
2965
+ /** Cross-axis (horizontal) alignment of children */
2966
+ _align;
2967
+ constructor(t = {}) {
2968
+ super(t), this._gap = t.gap ?? 0, this._padding = t.padding ?? [0, 0, 0, 0], this._align = t.align ?? "start";
2969
+ }
2970
+ // ==================== Properties ====================
2971
+ /** Get gap between children */
2972
+ get gap() {
2973
+ return this._gap;
2974
+ }
2975
+ /** Set gap between children */
2976
+ set gap(t) {
2977
+ this._gap !== t && (this._gap = t, this.markLayoutDirty());
2978
+ }
2979
+ /** Get padding */
2980
+ get padding() {
2981
+ return [...this._padding];
2982
+ }
2983
+ /** Set padding [top, right, bottom, left] */
2984
+ set padding(t) {
2985
+ this._padding = [...t], this.markLayoutDirty();
2986
+ }
2987
+ /** Get alignment */
2988
+ get align() {
2989
+ return this._align;
2990
+ }
2991
+ /** Set alignment */
2992
+ set align(t) {
2993
+ this._align !== t && (this._align = t, this.markLayoutDirty());
2994
+ }
2995
+ // ==================== Layout ====================
2996
+ /**
2997
+ * Measure content size (sum of child heights + gaps + padding).
2998
+ *
2999
+ * ┌─────────────────────────────────────────┐
3000
+ * │ padding-top │
3001
+ * │ ┌─────────────────────────────────┐ │
3002
+ * │ │ Child 1 (measured height) │ │
3003
+ * │ └─────────────────────────────────┘ │
3004
+ * │ gap │
3005
+ * │ ┌─────────────────────────────────┐ │
3006
+ * │ │ Child 2 (measured height) │ │
3007
+ * │ └─────────────────────────────────┘ │
3008
+ * │ gap │
3009
+ * │ ┌─────────────────────────────────┐ │
3010
+ * │ │ Child 3 (measured height) │ │
3011
+ * │ └─────────────────────────────────┘ │
3012
+ * │ padding-bottom │
3013
+ * └─────────────────────────────────────────┘
3014
+ */
3015
+ measureContent() {
3016
+ const [t, e, i, s] = this._padding;
3017
+ let o = t + i, a = 0;
3018
+ for (let n = 0; n < this.children.length; n++) {
3019
+ const l = this.children[n];
3020
+ l.layout({ width: 0, height: 0 });
3021
+ const h = l.getComputedSize();
3022
+ o += h[1], a = Math.max(a, h[0]), n < this.children.length - 1 && (o += this._gap);
3023
+ }
3024
+ return [a + s + e, o];
3025
+ }
3026
+ /**
3027
+ * Layout children vertically with gaps.
3028
+ */
3029
+ layoutChildren(t) {
3030
+ const [e, i, s, o] = this._padding, a = this.computedSize[0] - o - i;
3031
+ let n = e;
3032
+ for (let l = 0; l < this.children.length; l++) {
3033
+ const h = this.children[l];
3034
+ h.layout({ width: a, height: 0 });
3035
+ const u = h.getComputedSize();
3036
+ let p = o;
3037
+ switch (this._align) {
3038
+ case "center":
3039
+ p = o + (a - u[0]) / 2;
3040
+ break;
3041
+ case "end":
3042
+ p = o + a - u[0];
3043
+ break;
3044
+ }
3045
+ h.setComputedPosition(p, n), n += u[1], l < this.children.length - 1 && (n += this._gap);
3046
+ }
3047
+ }
3048
+ }
3049
+ class Ft extends P {
3050
+ /** Space between children in pixels */
3051
+ _gap;
3052
+ /** Inner padding [top, right, bottom, left] */
3053
+ _padding;
3054
+ /** Cross-axis (vertical) alignment of children */
3055
+ _align;
3056
+ constructor(t = {}) {
3057
+ super(t), this._gap = t.gap ?? 0, this._padding = t.padding ?? [0, 0, 0, 0], this._align = t.align ?? "start";
3058
+ }
3059
+ // ==================== Properties ====================
3060
+ /** Get gap between children */
3061
+ get gap() {
3062
+ return this._gap;
3063
+ }
3064
+ /** Set gap between children */
3065
+ set gap(t) {
3066
+ this._gap !== t && (this._gap = t, this.markLayoutDirty());
3067
+ }
3068
+ /** Get padding */
3069
+ get padding() {
3070
+ return [...this._padding];
3071
+ }
3072
+ /** Set padding [top, right, bottom, left] */
3073
+ set padding(t) {
3074
+ this._padding = [...t], this.markLayoutDirty();
3075
+ }
3076
+ /** Get alignment */
3077
+ get align() {
3078
+ return this._align;
3079
+ }
3080
+ /** Set alignment */
3081
+ set align(t) {
3082
+ this._align !== t && (this._align = t, this.markLayoutDirty());
3083
+ }
3084
+ // ==================== Layout ====================
3085
+ /**
3086
+ * Measure content size (sum of child widths + gaps + padding).
3087
+ *
3088
+ * ┌───────────────────────────────────────────────────────────┐
3089
+ * │ pl │ Child1 │ gap │ Child2 │ gap │ Child3 │ pr │
3090
+ * └───────────────────────────────────────────────────────────┘
3091
+ * └─────────────────────────────────────┘
3092
+ * content area
3093
+ */
3094
+ measureContent() {
3095
+ const [t, e, i, s] = this._padding;
3096
+ let o = s + e, a = 0;
3097
+ for (let n = 0; n < this.children.length; n++) {
3098
+ const l = this.children[n];
3099
+ l.layout({ width: 0, height: 0 });
3100
+ const h = l.getComputedSize();
3101
+ o += h[0], a = Math.max(a, h[1]), n < this.children.length - 1 && (o += this._gap);
3102
+ }
3103
+ return [o, a + t + i];
3104
+ }
3105
+ /**
3106
+ * Layout children horizontally with gaps.
3107
+ */
3108
+ layoutChildren(t) {
3109
+ const [e, i, s, o] = this._padding, a = this.computedSize[1] - e - s;
3110
+ let n = o;
3111
+ for (let l = 0; l < this.children.length; l++) {
3112
+ const h = this.children[l];
3113
+ h.layout({ width: 0, height: a });
3114
+ const u = h.getComputedSize();
3115
+ let p = e;
3116
+ switch (this._align) {
3117
+ case "center":
3118
+ p = e + (a - u[1]) / 2;
3119
+ break;
3120
+ case "end":
3121
+ p = e + a - u[1];
3122
+ break;
3123
+ }
3124
+ h.setComputedPosition(n, p), n += u[0], l < this.children.length - 1 && (n += this._gap);
3125
+ }
3126
+ }
3127
+ }
3128
+ const pt = `
3129
+ @import url('https://fonts.googleapis.com/css2?family=Recursive:slnt,wght,CASL,MONO@0,400..900,0..1,1&family=Space+Grotesk:wght@400;700&display=swap');
3130
+
3131
+ /* Keyframes */
3132
+ @keyframes bonk-letter-in {
3133
+ 0% { transform: scale(0, 0) translateY(20px); opacity: 0; }
3134
+ 40% { transform: scale(1.4, 0.7) translateY(-8px); opacity: 1; }
3135
+ 60% { transform: scale(0.85, 1.15) translateY(3px); }
3136
+ 80% { transform: scale(1.05, 0.95) translateY(-1px); }
3137
+ 100% { transform: scale(1, 1) translateY(0); opacity: 1; }
3138
+ }
3139
+
3140
+ @keyframes bonk-float {
3141
+ 0%, 100% { transform: translateY(0); }
3142
+ 50% { transform: translateY(-3px); }
3143
+ }
3144
+
3145
+ @keyframes bonk-star {
3146
+ 0% { transform: scale(0) rotate(0deg); opacity: 0.9; }
3147
+ 50% { transform: scale(1.2) rotate(20deg); opacity: 0.5; }
3148
+ 100% { transform: scale(1.5) rotate(30deg); opacity: 0; }
3149
+ }
3150
+
3151
+ .bonk-tweaker {
3152
+ --bonk-accent: #f59e0b;
3153
+ --bonk-accent-rgb: 245, 158, 11;
3154
+ --bonk-accent-glow: #fbbf24;
3155
+ --bonk-font-mono: 'Recursive', 'Consolas', monospace;
3156
+ --bonk-font-ui: 'Space Grotesk', sans-serif;
3157
+ --bonk-ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
3158
+ --bonk-ease-out: cubic-bezier(0.16, 1, 0.3, 1);
3159
+ --bonk-transition-fast: 0.15s var(--bonk-ease-out);
3160
+ --bonk-transition-bounce: 0.25s var(--bonk-ease-spring);
3161
+
3162
+ position: fixed;
3163
+ top: 0;
3164
+ bottom: 0;
3165
+ width: 360px;
3166
+ background: rgba(9, 9, 11, 0.97);
3167
+ color: #a1a1aa;
3168
+ font-family: var(--bonk-font-mono);
3169
+ font-size: 12px;
3170
+ display: flex;
3171
+ flex-direction: column;
3172
+ z-index: 99999;
3173
+ border-left: 1px solid #27272a;
3174
+ box-shadow: -4px 0 20px rgba(0, 0, 0, 0.6);
3175
+ border-radius: 6px 0 0 6px;
3176
+ overflow: hidden;
3177
+ user-select: none;
3178
+ transition: transform 400ms var(--bonk-ease-out), opacity 300ms var(--bonk-ease-out);
3179
+ }
3180
+
3181
+ /* State classes */
3182
+ .bonk-tweaker.state-hidden {
3183
+ display: none;
3184
+ }
3185
+ .bonk-tweaker.state-active {
3186
+ transform: translateX(0);
3187
+ opacity: 1;
3188
+ }
3189
+ .bonk-tweaker.state-dormant {
3190
+ opacity: 0.12;
3191
+ transform: translateX(calc(100% - 20px));
3192
+ }
3193
+ .bonk-tweaker.left.state-dormant {
3194
+ transform: translateX(calc(-100% + 20px));
3195
+ }
3196
+ .bonk-tweaker.state-dormant.waking {
3197
+ transition: transform 600ms var(--bonk-ease-spring), opacity 300ms var(--bonk-ease-out);
3198
+ }
3199
+ .bonk-tweaker.state-dormant > * {
3200
+ pointer-events: none;
3201
+ }
3202
+ /* Accent bar on the visible dormant edge */
3203
+ .bonk-tweaker.right.state-dormant::before {
3204
+ content: '';
3205
+ position: absolute;
3206
+ top: 0;
3207
+ left: 0;
3208
+ width: 3px;
3209
+ height: 100%;
3210
+ background: linear-gradient(to bottom, var(--bonk-accent), rgba(var(--bonk-accent-rgb), 0.2));
3211
+ z-index: 1;
3212
+ }
3213
+ .bonk-tweaker.left.state-dormant::before {
3214
+ content: '';
3215
+ position: absolute;
3216
+ top: 0;
3217
+ right: 0;
3218
+ width: 3px;
3219
+ height: 100%;
3220
+ background: linear-gradient(to bottom, var(--bonk-accent), rgba(var(--bonk-accent-rgb), 0.2));
3221
+ z-index: 1;
3222
+ }
3223
+
3224
+ /* Theme blocks */
3225
+ .bonk-tweaker.theme-amber {
3226
+ --bonk-accent: #f59e0b;
3227
+ --bonk-accent-rgb: 245, 158, 11;
3228
+ --bonk-accent-glow: #fbbf24;
3229
+ }
3230
+ .bonk-tweaker.theme-lime {
3231
+ --bonk-accent: #84cc16;
3232
+ --bonk-accent-rgb: 132, 204, 22;
3233
+ --bonk-accent-glow: #a3e635;
3234
+ }
3235
+ .bonk-tweaker.theme-spring {
3236
+ --bonk-accent: #00e5a0;
3237
+ --bonk-accent-rgb: 0, 229, 160;
3238
+ --bonk-accent-glow: #34ffc6;
3239
+ }
3240
+
3241
+ .bonk-tweaker.right { right: 0; border-radius: 6px 0 0 6px; }
3242
+ .bonk-tweaker.left { left: 0; border-left: none; border-right: 1px solid #27272a; box-shadow: 4px 0 20px rgba(0, 0, 0, 0.6); border-radius: 0 6px 6px 0; }
3243
+
3244
+ /* Header */
3245
+ .bonk-tweaker-header {
3246
+ display: flex;
3247
+ align-items: center;
3248
+ padding: 8px 10px;
3249
+ gap: 8px;
3250
+ border-bottom: 1px solid #27272a;
3251
+ flex-shrink: 0;
3252
+ }
3253
+ .bonk-tweaker-search {
3254
+ flex: 1;
3255
+ background: #09090b;
3256
+ border: 1px solid #3f3f46;
3257
+ color: #e4e4e7;
3258
+ padding: 5px 8px;
3259
+ border-radius: 4px;
3260
+ font-family: inherit;
3261
+ font-size: 12px;
3262
+ outline: none;
3263
+ transition: border-color var(--bonk-transition-bounce);
3264
+ }
3265
+ .bonk-tweaker-search:focus {
3266
+ border-color: var(--bonk-accent);
3267
+ box-shadow: 0 0 0 1px var(--bonk-accent);
3268
+ }
3269
+ .bonk-tweaker-search::placeholder {
3270
+ color: #71717a;
3271
+ }
3272
+ .bonk-tweaker-close {
3273
+ background: none;
3274
+ border: none;
3275
+ color: #71717a;
3276
+ cursor: pointer;
3277
+ font-size: 18px;
3278
+ padding: 0 4px;
3279
+ line-height: 1;
3280
+ transition: color var(--bonk-transition-fast);
3281
+ }
3282
+ .bonk-tweaker-close:hover { color: #f87171; }
3283
+
3284
+ /* Logo */
3285
+ .bonk-tweaker-logo {
3286
+ text-align: center;
3287
+ padding: 12px 0 8px;
3288
+ border-bottom: 1px solid #27272a;
3289
+ flex-shrink: 0;
3290
+ position: relative;
3291
+ overflow: hidden;
3292
+ }
3293
+ .bonk-tweaker-logo-star {
3294
+ position: absolute;
3295
+ width: 60px;
3296
+ height: 60px;
3297
+ left: 50%;
3298
+ top: 50%;
3299
+ margin-left: -30px;
3300
+ margin-top: -34px;
3301
+ background: radial-gradient(circle, rgba(var(--bonk-accent-rgb), 0.3) 0%, rgba(var(--bonk-accent-rgb), 0.1) 30%, transparent 70%);
3302
+ border-radius: 50%;
3303
+ pointer-events: none;
3304
+ opacity: 0;
3305
+ }
3306
+ .bonk-tweaker-logo.entering .bonk-tweaker-logo-star {
3307
+ animation: bonk-star 0.6s 0.1s ease-out both;
3308
+ }
3309
+ .bonk-tweaker-logo-text {
3310
+ display: inline-flex;
3311
+ gap: 2px;
3312
+ position: relative;
3313
+ }
3314
+ .bonk-tweaker-logo-letter {
3315
+ display: inline-block;
3316
+ font-size: 28px;
3317
+ font-weight: 900;
3318
+ color: var(--bonk-accent);
3319
+ letter-spacing: 0.15em;
3320
+ text-shadow: 0 0 20px rgba(var(--bonk-accent-rgb), 0.3);
3321
+ opacity: 0;
3322
+ }
3323
+ .bonk-tweaker-logo.entering .bonk-tweaker-logo-letter {
3324
+ animation: bonk-letter-in 0.5s calc(var(--i) * 0.08s) ease-out both;
3325
+ }
3326
+ .bonk-tweaker-logo.idle .bonk-tweaker-logo-letter {
3327
+ opacity: 1;
3328
+ animation: bonk-float 3s calc(var(--i) * 0.15s) ease-in-out infinite;
3329
+ }
3330
+ .bonk-tweaker-logo-sub {
3331
+ font-family: var(--bonk-font-ui);
3332
+ font-size: 10px;
3333
+ color: #71717a;
3334
+ letter-spacing: 0.2em;
3335
+ text-transform: uppercase;
3336
+ margin-top: 2px;
3337
+ }
3338
+
3339
+ /* Scrollable body */
3340
+ .bonk-tweaker-body {
3341
+ flex: 1;
3342
+ overflow-y: auto;
3343
+ overflow-x: hidden;
3344
+ scrollbar-width: thin;
3345
+ scrollbar-color: #3f3f46 #18181b;
3346
+ }
3347
+ .bonk-tweaker-body::-webkit-scrollbar { width: 6px; }
3348
+ .bonk-tweaker-body::-webkit-scrollbar-track { background: #18181b; }
3349
+ .bonk-tweaker-body::-webkit-scrollbar-thumb { background: #3f3f46; border-radius: 4px; }
3350
+ .bonk-tweaker-body::-webkit-scrollbar-thumb:hover { background: var(--bonk-accent); }
3351
+
3352
+ /* Group */
3353
+ .bonk-tweaker-group {
3354
+ border-bottom: 1px solid #27272a;
3355
+ }
3356
+ .bonk-tweaker-group-header {
3357
+ display: flex;
3358
+ align-items: center;
3359
+ padding: 6px 10px;
3360
+ cursor: pointer;
3361
+ background: rgba(var(--bonk-accent-rgb), 0.03);
3362
+ transition: background var(--bonk-transition-fast);
3363
+ }
3364
+ .bonk-tweaker-group-header:hover {
3365
+ background: rgba(var(--bonk-accent-rgb), 0.06);
3366
+ }
3367
+ .bonk-tweaker-group-header:active {
3368
+ transform: scale(0.995) translateX(1px);
3369
+ }
3370
+ .bonk-tweaker-group-arrow {
3371
+ width: 16px;
3372
+ font-size: 10px;
3373
+ color: #71717a;
3374
+ flex-shrink: 0;
3375
+ }
3376
+ .bonk-tweaker-group-name {
3377
+ flex: 1;
3378
+ font-weight: 700;
3379
+ color: var(--bonk-accent);
3380
+ font-size: 10px;
3381
+ text-transform: uppercase;
3382
+ letter-spacing: 0.1em;
3383
+ }
3384
+ .bonk-tweaker-group-reset {
3385
+ background: none;
3386
+ border: none;
3387
+ color: #71717a;
3388
+ cursor: pointer;
3389
+ font-size: 14px;
3390
+ padding: 0 4px;
3391
+ transition: color var(--bonk-transition-fast);
3392
+ }
3393
+ .bonk-tweaker-group-reset:hover { color: #facc15; }
3394
+ .bonk-tweaker-group-body {
3395
+ padding: 2px 0;
3396
+ }
3397
+ .bonk-tweaker-group-body.collapsed {
3398
+ display: none;
3399
+ }
3400
+
3401
+ /* Sub-group (nested object) */
3402
+ .bonk-tweaker-subgroup {
3403
+ margin-left: 12px;
3404
+ border-left: 1px solid #27272a;
3405
+ }
3406
+ .bonk-tweaker-subgroup-header {
3407
+ display: flex;
3408
+ align-items: center;
3409
+ padding: 4px 10px;
3410
+ cursor: pointer;
3411
+ font-size: 11px;
3412
+ color: #a1a1aa;
3413
+ transition: background var(--bonk-transition-fast);
3414
+ }
3415
+ .bonk-tweaker-subgroup-header:hover {
3416
+ background: rgba(var(--bonk-accent-rgb), 0.04);
3417
+ }
3418
+
3419
+ .bonk-tweaker-subgroup-body.collapsed {
3420
+ display: none;
3421
+ }
3422
+
3423
+ /* Field row */
3424
+ .bonk-tweaker-field {
3425
+ display: flex;
3426
+ align-items: center;
3427
+ padding: 3px 10px 3px 26px;
3428
+ gap: 6px;
3429
+ min-height: 24px;
3430
+ transition: background var(--bonk-transition-fast);
3431
+ }
3432
+ .bonk-tweaker-field:hover {
3433
+ background: rgba(var(--bonk-accent-rgb), 0.03);
3434
+ }
3435
+ .bonk-tweaker-field.modified .bonk-tweaker-field-name {
3436
+ color: #facc15;
3437
+ }
3438
+ .bonk-tweaker-field.search-hidden {
3439
+ display: none;
3440
+ }
3441
+ .bonk-tweaker-field-name {
3442
+ flex: 0 0 auto;
3443
+ max-width: 140px;
3444
+ overflow: hidden;
3445
+ text-overflow: ellipsis;
3446
+ white-space: nowrap;
3447
+ font-size: 11px;
3448
+ color: #a1a1aa;
3449
+ }
3450
+ .bonk-tweaker-field-controls {
3451
+ flex: 1;
3452
+ display: flex;
3453
+ align-items: center;
3454
+ gap: 4px;
3455
+ justify-content: flex-end;
3456
+ }
3457
+
3458
+ /* Number slider */
3459
+ .bonk-tweaker-slider {
3460
+ flex: 1;
3461
+ max-width: 100px;
3462
+ height: 4px;
3463
+ -webkit-appearance: none;
3464
+ appearance: none;
3465
+ background: #3f3f46;
3466
+ border-radius: 2px;
3467
+ outline: none;
3468
+ }
3469
+ .bonk-tweaker-slider::-webkit-slider-thumb {
3470
+ -webkit-appearance: none;
3471
+ width: 10px;
3472
+ height: 14px;
3473
+ background: var(--bonk-accent);
3474
+ border-radius: 2px;
3475
+ cursor: pointer;
3476
+ }
3477
+ .bonk-tweaker-slider::-moz-range-thumb {
3478
+ width: 10px;
3479
+ height: 14px;
3480
+ background: var(--bonk-accent);
3481
+ border-radius: 2px;
3482
+ cursor: pointer;
3483
+ border: none;
3484
+ }
3485
+
3486
+ /* Number input */
3487
+ .bonk-tweaker-number {
3488
+ width: 60px;
3489
+ background: #09090b;
3490
+ border: 1px solid #3f3f46;
3491
+ color: #e4e4e7;
3492
+ padding: 2px 4px;
3493
+ border-radius: 4px;
3494
+ font-family: inherit;
3495
+ font-size: 11px;
3496
+ text-align: right;
3497
+ outline: none;
3498
+ transition: border-color var(--bonk-transition-bounce);
3499
+ }
3500
+ .bonk-tweaker-number:focus {
3501
+ border-color: var(--bonk-accent);
3502
+ box-shadow: 0 0 0 1px var(--bonk-accent);
3503
+ }
3504
+
3505
+ /* Color picker */
3506
+ .bonk-tweaker-color {
3507
+ width: 24px;
3508
+ height: 18px;
3509
+ border: 1px solid #3f3f46;
3510
+ border-radius: 4px;
3511
+ padding: 0;
3512
+ cursor: pointer;
3513
+ background: none;
3514
+ }
3515
+ .bonk-tweaker-color-hex {
3516
+ width: 70px;
3517
+ background: #09090b;
3518
+ border: 1px solid #3f3f46;
3519
+ color: #e4e4e7;
3520
+ padding: 2px 4px;
3521
+ border-radius: 4px;
3522
+ font-family: inherit;
3523
+ font-size: 11px;
3524
+ outline: none;
3525
+ transition: border-color var(--bonk-transition-bounce);
3526
+ }
3527
+ .bonk-tweaker-color-hex:focus {
3528
+ border-color: var(--bonk-accent);
3529
+ box-shadow: 0 0 0 1px var(--bonk-accent);
3530
+ }
3531
+
3532
+ /* Checkbox */
3533
+ .bonk-tweaker-checkbox {
3534
+ accent-color: var(--bonk-accent);
3535
+ cursor: pointer;
3536
+ }
3537
+
3538
+ /* Readonly display */
3539
+ .bonk-tweaker-readonly {
3540
+ color: #71717a;
3541
+ font-size: 11px;
3542
+ font-style: italic;
3543
+ }
3544
+
3545
+ /* Footer */
3546
+ .bonk-tweaker-footer {
3547
+ display: flex;
3548
+ padding: 8px 10px;
3549
+ gap: 6px;
3550
+ border-top: 1px solid #27272a;
3551
+ flex-shrink: 0;
3552
+ align-items: center;
3553
+ }
3554
+ .bonk-tweaker-btn {
3555
+ flex: 1;
3556
+ background: #27272a;
3557
+ border: 1px solid rgba(var(--bonk-accent-rgb), 0.3);
3558
+ color: var(--bonk-accent);
3559
+ padding: 5px 8px;
3560
+ border-radius: 4px;
3561
+ cursor: pointer;
3562
+ font-family: inherit;
3563
+ font-size: 11px;
3564
+ text-align: center;
3565
+ transition: background var(--bonk-transition-fast), color var(--bonk-transition-fast), transform var(--bonk-transition-bounce);
3566
+ }
3567
+ .bonk-tweaker-btn:hover {
3568
+ background: rgba(var(--bonk-accent-rgb), 0.1);
3569
+ color: var(--bonk-accent);
3570
+ }
3571
+ .bonk-tweaker-btn:active {
3572
+ transform: scale(0.96);
3573
+ }
3574
+
3575
+ /* Theme picker */
3576
+ .bonk-tweaker-theme-picker {
3577
+ display: flex;
3578
+ align-items: center;
3579
+ gap: 4px;
3580
+ padding: 0 4px;
3581
+ }
3582
+ .bonk-tweaker-theme-swatch {
3583
+ width: 16px;
3584
+ height: 16px;
3585
+ border-radius: 50%;
3586
+ border: 2px solid transparent;
3587
+ cursor: pointer;
3588
+ transition: transform var(--bonk-transition-bounce), border-color var(--bonk-transition-fast);
3589
+ }
3590
+ .bonk-tweaker-theme-swatch:hover {
3591
+ transform: scale(1.2);
3592
+ }
3593
+ .bonk-tweaker-theme-swatch.active {
3594
+ border-color: #e4e4e7;
3595
+ }
3596
+
3597
+ /* Group hidden by search */
3598
+ .bonk-tweaker-group.search-hidden {
3599
+ display: none;
3600
+ }
3601
+
3602
+ /* Floating toggle button */
3603
+ .bonk-tweaker-fab {
3604
+ --bonk-fab-accent: #f59e0b;
3605
+ position: fixed;
3606
+ bottom: 16px;
3607
+ width: 36px;
3608
+ height: 36px;
3609
+ border-radius: 50%;
3610
+ background: rgba(9, 9, 11, 0.9);
3611
+ border: 1px solid #27272a;
3612
+ color: var(--bonk-fab-accent);
3613
+ cursor: pointer;
3614
+ z-index: 99998;
3615
+ display: flex;
3616
+ align-items: center;
3617
+ justify-content: center;
3618
+ font-size: 18px;
3619
+ font-weight: 900;
3620
+ font-family: 'Space Grotesk', sans-serif;
3621
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.5);
3622
+ transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.15s ease-out, border-color 0.15s ease-out;
3623
+ opacity: 0.5;
3624
+ padding: 0;
3625
+ line-height: 1;
3626
+ }
3627
+ .bonk-tweaker-fab:hover {
3628
+ opacity: 1;
3629
+ transform: scale(1.15);
3630
+ border-color: var(--bonk-fab-accent);
3631
+ box-shadow: 0 2px 14px rgba(0, 0, 0, 0.5), 0 0 8px rgba(var(--bonk-fab-accent-rgb, 245, 158, 11), 0.2);
3632
+ }
3633
+ .bonk-tweaker-fab:active {
3634
+ transform: scale(0.92);
3635
+ }
3636
+ .bonk-tweaker-fab.right { right: 16px; }
3637
+ .bonk-tweaker-fab.left { left: 16px; }
3638
+ .bonk-tweaker-fab.theme-amber { --bonk-fab-accent: #f59e0b; --bonk-fab-accent-rgb: 245, 158, 11; }
3639
+ .bonk-tweaker-fab.theme-lime { --bonk-fab-accent: #84cc16; --bonk-fab-accent-rgb: 132, 204, 22; }
3640
+ .bonk-tweaker-fab.theme-spring { --bonk-fab-accent: #00e5a0; --bonk-fab-accent-rgb: 0, 229, 160; }
3641
+ .bonk-tweaker-fab.fab-hidden { display: none; }
3642
+ `;
3643
+ class mt {
3644
+ root = null;
3645
+ body = null;
3646
+ logo = null;
3647
+ searchInput = null;
3648
+ styleEl = null;
3649
+ fields = [];
3650
+ groupBodies = /* @__PURE__ */ new Map();
3651
+ onChange = () => {
3652
+ };
3653
+ onReset = () => {
3654
+ };
3655
+ onExport = () => "{}";
3656
+ onImport = () => {
3657
+ };
3658
+ getterInterval = null;
3659
+ logoIdleTimer = null;
3660
+ position = "right";
3661
+ width = 360;
3662
+ currentTheme = "amber";
3663
+ swatches = /* @__PURE__ */ new Map();
3664
+ fab = null;
3665
+ currentState = "hidden";
3666
+ outsideClickListener = null;
3667
+ wakingTimer = null;
3668
+ storagePrefix = "bonk-tweaker";
3669
+ setCallbacks(t, e, i, s) {
3670
+ this.onChange = t, this.onReset = e, this.onExport = i, this.onImport = s;
3671
+ }
3672
+ create(t, e, i) {
3673
+ this.position = t, this.width = e, this.storagePrefix = i, this.styleEl = document.createElement("style"), this.styleEl.textContent = pt, document.head.appendChild(this.styleEl), this.root = document.createElement("div"), this.root.className = `bonk-tweaker ${t} state-hidden`, this.root.style.width = `${e}px`, this.root.addEventListener("keydown", (c) => c.stopPropagation()), this.root.addEventListener("keyup", (c) => c.stopPropagation()), this.root.addEventListener("keypress", (c) => c.stopPropagation()), this.root.addEventListener("mouseenter", () => {
3674
+ this.currentState === "dormant" && this.wake();
3675
+ });
3676
+ const s = document.createElement("div");
3677
+ s.className = "bonk-tweaker-header", this.searchInput = document.createElement("input"), this.searchInput.className = "bonk-tweaker-search", this.searchInput.type = "text", this.searchInput.placeholder = "Search constants...", this.searchInput.addEventListener("input", () => this.applySearch());
3678
+ const o = document.createElement("button");
3679
+ o.className = "bonk-tweaker-close", o.textContent = "×", o.addEventListener("click", () => this.hide()), s.appendChild(this.searchInput), s.appendChild(o), this.body = document.createElement("div"), this.body.className = "bonk-tweaker-body";
3680
+ const a = document.createElement("div");
3681
+ a.className = "bonk-tweaker-footer";
3682
+ const n = this.createButton("Reset All", () => {
3683
+ this.onReset(), this.refreshAllFields();
3684
+ }), l = this.createButton("Export", () => {
3685
+ const c = this.onExport();
3686
+ navigator.clipboard.writeText(c).catch(() => {
3687
+ prompt("Copy preset JSON:", c);
3688
+ });
3689
+ }), h = this.createButton("Import", () => {
3690
+ const c = prompt("Paste preset JSON:");
3691
+ c && (this.onImport(c), this.refreshAllFields());
3692
+ });
3693
+ a.appendChild(n), a.appendChild(l), a.appendChild(h);
3694
+ const u = document.createElement("div");
3695
+ u.className = "bonk-tweaker-theme-picker";
3696
+ const p = [
3697
+ { name: "amber", color: "#f59e0b" },
3698
+ { name: "lime", color: "#84cc16" },
3699
+ { name: "spring", color: "#00e5a0" }
3700
+ ];
3701
+ for (const c of p) {
3702
+ const w = document.createElement("div");
3703
+ w.className = "bonk-tweaker-theme-swatch", w.style.backgroundColor = c.color, w.title = c.name, w.addEventListener("click", () => this.setTheme(c.name)), this.swatches.set(c.name, w), u.appendChild(w);
3704
+ }
3705
+ a.appendChild(u), this.logo = document.createElement("div"), this.logo.className = "bonk-tweaker-logo";
3706
+ const g = document.createElement("div");
3707
+ g.className = "bonk-tweaker-logo-star";
3708
+ const k = document.createElement("span");
3709
+ k.className = "bonk-tweaker-logo-text";
3710
+ const m = "BONK";
3711
+ for (let c = 0; c < m.length; c++) {
3712
+ const w = document.createElement("span");
3713
+ w.className = "bonk-tweaker-logo-letter", w.style.setProperty("--i", String(c)), w.textContent = m[c], k.appendChild(w);
3714
+ }
3715
+ const f = document.createElement("div");
3716
+ f.className = "bonk-tweaker-logo-sub", f.textContent = "tweaker", this.logo.appendChild(g), this.logo.appendChild(k), this.logo.appendChild(f), this.root.appendChild(s), this.root.appendChild(this.logo), this.root.appendChild(this.body), this.root.appendChild(a), document.body.appendChild(this.root);
3717
+ const x = localStorage.getItem(`${this.storagePrefix}-ui:theme`) ?? localStorage.getItem("bonk-tweaker-theme");
3718
+ x && this.swatches.has(x) ? this.setTheme(x) : this.setTheme(this.currentTheme), this.fab = document.createElement("button"), this.fab.className = `bonk-tweaker-fab ${t} theme-${this.currentTheme}`, this.fab.textContent = "B", this.fab.title = "Toggle Tweaker", this.fab.addEventListener("click", () => {
3719
+ this.currentState === "hidden" ? this.show() : this.hide();
3720
+ }), document.body.appendChild(this.fab);
3721
+ }
3722
+ addGroup(t) {
3723
+ if (!this.body) return;
3724
+ const e = document.createElement("div");
3725
+ e.className = "bonk-tweaker-group", e.dataset.groupName = t.name;
3726
+ const i = document.createElement("div");
3727
+ i.className = "bonk-tweaker-group-header";
3728
+ let s = t.options.collapsed ?? !1;
3729
+ try {
3730
+ const h = localStorage.getItem(`${this.storagePrefix}-ui:group:${t.name}`);
3731
+ h !== null && (s = h === "1");
3732
+ } catch {
3733
+ }
3734
+ const o = document.createElement("span");
3735
+ o.className = "bonk-tweaker-group-arrow", o.textContent = s ? "▶" : "▼";
3736
+ const a = document.createElement("span");
3737
+ a.className = "bonk-tweaker-group-name", a.textContent = t.name;
3738
+ const n = document.createElement("button");
3739
+ n.className = "bonk-tweaker-group-reset", n.textContent = "↺", n.title = `Reset ${t.name}`, n.addEventListener("click", (h) => {
3740
+ h.stopPropagation(), this.onReset(t.name), this.refreshGroupFields(t.name);
3741
+ }), i.appendChild(o), i.appendChild(a), i.appendChild(n);
3742
+ const l = document.createElement("div");
3743
+ l.className = `bonk-tweaker-group-body${s ? " collapsed" : ""}`, i.addEventListener("click", () => {
3744
+ const h = l.classList.toggle("collapsed");
3745
+ o.textContent = h ? "▶" : "▼";
3746
+ try {
3747
+ localStorage.setItem(`${this.storagePrefix}-ui:group:${t.name}`, h ? "1" : "0");
3748
+ } catch {
3749
+ }
3750
+ }), this.addFields(t, l, t.target, t.defaults, "", t.options.hints || {}), this.groupBodies.set(t.name, l), e.appendChild(i), e.appendChild(l), this.body.appendChild(e);
3751
+ }
3752
+ removeGroup(t) {
3753
+ if (!this.body) return;
3754
+ const e = this.body.querySelector(`[data-group-name="${t}"]`);
3755
+ e && e.remove(), this.fields = this.fields.filter((i) => i.groupName !== t), this.groupBodies.delete(t);
3756
+ }
3757
+ show() {
3758
+ if (this.root) {
3759
+ this.currentState = "active", this.applyStateClasses(), this.refreshAllFields(), this.playLogoEntrance(), this.setupDormantListeners(), this.getterInterval || (this.getterInterval = setInterval(() => this.refreshGetters(), 500)), this.fab?.classList.add("fab-hidden");
3760
+ try {
3761
+ localStorage.setItem(`${this.storagePrefix}-ui:visible`, "1");
3762
+ } catch {
3763
+ }
3764
+ }
3765
+ }
3766
+ hide() {
3767
+ if (this.root) {
3768
+ this.currentState = "hidden", this.applyStateClasses(), this.removeDormantListeners(), this.wakingTimer && (clearTimeout(this.wakingTimer), this.wakingTimer = null), this.getterInterval && (clearInterval(this.getterInterval), this.getterInterval = null), this.fab?.classList.remove("fab-hidden");
3769
+ try {
3770
+ localStorage.setItem(`${this.storagePrefix}-ui:visible`, "0");
3771
+ } catch {
3772
+ }
3773
+ }
3774
+ }
3775
+ isVisible() {
3776
+ return this.currentState !== "hidden";
3777
+ }
3778
+ restoreVisibility() {
3779
+ try {
3780
+ localStorage.getItem(`${this.storagePrefix}-ui:visible`) === "1" && this.show();
3781
+ } catch {
3782
+ }
3783
+ }
3784
+ getState() {
3785
+ return this.currentState;
3786
+ }
3787
+ goDormant() {
3788
+ !this.root || this.currentState !== "active" || (this.currentState = "dormant", this.applyStateClasses());
3789
+ }
3790
+ wake() {
3791
+ !this.root || this.currentState !== "dormant" || (this.currentState = "active", this.root.classList.add("waking"), this.applyStateClasses(), this.playLogoEntrance(), this.wakingTimer && clearTimeout(this.wakingTimer), this.wakingTimer = setTimeout(() => {
3792
+ this.root?.classList.remove("waking"), this.wakingTimer = null;
3793
+ }, 600));
3794
+ }
3795
+ applyStateClasses() {
3796
+ this.root && (this.root.classList.remove("state-hidden", "state-active", "state-dormant"), this.root.classList.add(`state-${this.currentState}`));
3797
+ }
3798
+ playLogoEntrance() {
3799
+ this.logo && (this.logoIdleTimer && (clearTimeout(this.logoIdleTimer), this.logoIdleTimer = null), this.logo.classList.remove("idle"), this.logo.classList.add("entering"), this.logoIdleTimer = setTimeout(() => {
3800
+ this.logo && (this.logo.classList.remove("entering"), this.logo.classList.add("idle"));
3801
+ }, 800));
3802
+ }
3803
+ setupDormantListeners() {
3804
+ this.outsideClickListener || (this.outsideClickListener = (t) => {
3805
+ this.currentState === "active" && this.root && !this.root.contains(t.target) && this.goDormant();
3806
+ }, document.addEventListener("pointerdown", this.outsideClickListener));
3807
+ }
3808
+ removeDormantListeners() {
3809
+ this.outsideClickListener && (document.removeEventListener("pointerdown", this.outsideClickListener), this.outsideClickListener = null);
3810
+ }
3811
+ setTheme(t) {
3812
+ if (this.root) {
3813
+ this.root.classList.remove("theme-amber", "theme-lime", "theme-spring"), this.root.classList.add(`theme-${t}`), this.currentTheme = t;
3814
+ try {
3815
+ localStorage.setItem(`${this.storagePrefix}-ui:theme`, t);
3816
+ } catch {
3817
+ }
3818
+ this.swatches.forEach((e, i) => {
3819
+ e.classList.toggle("active", i === t);
3820
+ }), this.fab && (this.fab.classList.remove("theme-amber", "theme-lime", "theme-spring"), this.fab.classList.add(`theme-${t}`));
3821
+ }
3822
+ }
3823
+ destroy() {
3824
+ this.getterInterval && clearInterval(this.getterInterval), this.logoIdleTimer && clearTimeout(this.logoIdleTimer), this.wakingTimer && clearTimeout(this.wakingTimer), this.removeDormantListeners(), this.root?.remove(), this.styleEl?.remove(), this.fab?.remove(), this.fields = [], this.groupBodies.clear(), this.root = null, this.body = null, this.logo = null, this.fab = null;
3825
+ }
3826
+ // === Field creation ===
3827
+ addFields(t, e, i, s, o, a) {
3828
+ for (const n of Object.keys(i)) {
3829
+ const l = o ? `${o}.${n}` : n, h = Object.getOwnPropertyDescriptor(i, n);
3830
+ if (h && h.get) {
3831
+ this.addReadonlyField(t.name, e, n, l, i);
3832
+ continue;
3833
+ }
3834
+ const u = i[n];
3835
+ switch (this.detectFieldType(n, u, a[l] || a[n])) {
3836
+ case "number":
3837
+ this.addNumberField(t.name, e, n, l, i, s);
3838
+ break;
3839
+ case "color":
3840
+ this.addColorField(t.name, e, n, l, i, s);
3841
+ break;
3842
+ case "boolean":
3843
+ this.addBooleanField(t.name, e, n, l, i, s);
3844
+ break;
3845
+ case "object":
3846
+ this.addSubgroup(t, e, n, l, i, s, a);
3847
+ break;
3848
+ case "readonly":
3849
+ this.addReadonlyField(t.name, e, n, l, i);
3850
+ break;
3851
+ }
3852
+ }
3853
+ }
3854
+ detectFieldType(t, e, i) {
3855
+ if (i === "color") return "color";
3856
+ if (i === "readonly") return "readonly";
3857
+ if (typeof e == "boolean") return "boolean";
3858
+ if (typeof e == "number") {
3859
+ const s = t.toUpperCase();
3860
+ return (s.includes("COLOR") || s.includes("TINT")) && e > 255 && Number.isInteger(e) ? "color" : "number";
3861
+ }
3862
+ return typeof e == "object" && e !== null && !Array.isArray(e) ? "object" : "readonly";
3863
+ }
3864
+ addNumberField(t, e, i, s, o, a) {
3865
+ const n = this.createFieldRow(i), l = n.querySelector(".bonk-tweaker-field-controls"), h = this.getNestedDefault(a, s), u = o[i], { min: p, max: g, step: k } = this.inferRange(u), m = document.createElement("input");
3866
+ m.type = "range", m.className = "bonk-tweaker-slider", m.min = String(p), m.max = String(g), m.step = String(k), m.value = String(u);
3867
+ const f = document.createElement("input");
3868
+ f.type = "number", f.className = "bonk-tweaker-number", f.value = this.formatNumber(u), f.step = String(k);
3869
+ const x = () => {
3870
+ const c = o[i] !== h;
3871
+ n.classList.toggle("modified", c);
3872
+ };
3873
+ m.addEventListener("input", () => {
3874
+ const c = parseFloat(m.value);
3875
+ f.value = this.formatNumber(c), o[i] = c, this.onChange(t, s, c), x();
3876
+ }), f.addEventListener("change", () => {
3877
+ const c = parseFloat(f.value);
3878
+ isNaN(c) || (o[i] = c, c < parseFloat(m.min) && (m.min = String(c * (c < 0 ? 2 : 0.5))), c > parseFloat(m.max) && (m.max = String(c * 1.5)), m.value = String(c), this.onChange(t, s, c), x());
3879
+ }), l.appendChild(m), l.appendChild(f), e.appendChild(n), x(), this.fields.push({
3880
+ groupName: t,
3881
+ path: s,
3882
+ defaultValue: h,
3883
+ row: n,
3884
+ update: (c) => {
3885
+ m.value = String(c), f.value = this.formatNumber(c), x();
3886
+ }
3887
+ });
3888
+ }
3889
+ addColorField(t, e, i, s, o, a) {
3890
+ const n = this.createFieldRow(i), l = n.querySelector(".bonk-tweaker-field-controls"), h = this.getNestedDefault(a, s), u = o[i], p = document.createElement("input");
3891
+ p.type = "color", p.className = "bonk-tweaker-color", p.value = "#" + u.toString(16).padStart(6, "0");
3892
+ const g = document.createElement("input");
3893
+ g.type = "text", g.className = "bonk-tweaker-color-hex", g.value = "0x" + u.toString(16).padStart(6, "0");
3894
+ const k = () => {
3895
+ const m = o[i] !== h;
3896
+ n.classList.toggle("modified", m);
3897
+ };
3898
+ p.addEventListener("input", () => {
3899
+ const m = p.value, f = parseInt(m.slice(1), 16);
3900
+ g.value = "0x" + f.toString(16).padStart(6, "0"), o[i] = f, this.onChange(t, s, f), k();
3901
+ }), g.addEventListener("change", () => {
3902
+ let m = g.value.trim();
3903
+ (m.startsWith("0x") || m.startsWith("0X")) && (m = m.slice(2)), m.startsWith("#") && (m = m.slice(1));
3904
+ const f = parseInt(m, 16);
3905
+ isNaN(f) || (p.value = "#" + f.toString(16).padStart(6, "0"), g.value = "0x" + f.toString(16).padStart(6, "0"), o[i] = f, this.onChange(t, s, f), k());
3906
+ }), l.appendChild(p), l.appendChild(g), e.appendChild(n), k(), this.fields.push({
3907
+ groupName: t,
3908
+ path: s,
3909
+ defaultValue: h,
3910
+ row: n,
3911
+ update: (m) => {
3912
+ const f = m;
3913
+ p.value = "#" + f.toString(16).padStart(6, "0"), g.value = "0x" + f.toString(16).padStart(6, "0"), k();
3914
+ }
3915
+ });
3916
+ }
3917
+ addBooleanField(t, e, i, s, o, a) {
3918
+ const n = this.createFieldRow(i), l = n.querySelector(".bonk-tweaker-field-controls"), h = this.getNestedDefault(a, s), u = document.createElement("input");
3919
+ u.type = "checkbox", u.className = "bonk-tweaker-checkbox", u.checked = o[i];
3920
+ const p = () => {
3921
+ const g = o[i] !== h;
3922
+ n.classList.toggle("modified", g);
3923
+ };
3924
+ u.addEventListener("change", () => {
3925
+ o[i] = u.checked, this.onChange(t, s, u.checked), p();
3926
+ }), l.appendChild(u), e.appendChild(n), p(), this.fields.push({
3927
+ groupName: t,
3928
+ path: s,
3929
+ defaultValue: h,
3930
+ row: n,
3931
+ update: (g) => {
3932
+ u.checked = g, p();
3933
+ }
3934
+ });
3935
+ }
3936
+ addReadonlyField(t, e, i, s, o) {
3937
+ const a = this.createFieldRow(i), n = a.querySelector(".bonk-tweaker-field-controls"), l = document.createElement("span");
3938
+ l.className = "bonk-tweaker-readonly";
3939
+ const h = o[i];
3940
+ l.textContent = this.formatReadonly(h), n.appendChild(l), e.appendChild(a), this.fields.push({
3941
+ groupName: t,
3942
+ path: s,
3943
+ defaultValue: void 0,
3944
+ row: a,
3945
+ update: (u) => {
3946
+ l.textContent = this.formatReadonly(u);
3947
+ }
3948
+ });
3949
+ }
3950
+ addSubgroup(t, e, i, s, o, a, n) {
3951
+ const l = o[i], h = this.getNestedDefault(a, s) || {};
3952
+ let u = !1;
3953
+ try {
3954
+ const c = localStorage.getItem(`${this.storagePrefix}-ui:subgroup:${t.name}:${s}`);
3955
+ c !== null && (u = c === "1");
3956
+ } catch {
3957
+ }
3958
+ const p = document.createElement("div");
3959
+ p.className = "bonk-tweaker-subgroup";
3960
+ const g = document.createElement("div");
3961
+ g.className = "bonk-tweaker-subgroup-header";
3962
+ const k = document.createElement("span");
3963
+ k.className = "bonk-tweaker-group-arrow", k.textContent = u ? "▶" : "▼";
3964
+ const m = document.createElement("span");
3965
+ m.textContent = i, g.appendChild(k), g.appendChild(m);
3966
+ const f = document.createElement("div");
3967
+ f.className = `bonk-tweaker-subgroup-body${u ? " collapsed" : ""}`, g.addEventListener("click", () => {
3968
+ const c = f.classList.toggle("collapsed");
3969
+ k.textContent = c ? "▶" : "▼";
3970
+ try {
3971
+ localStorage.setItem(`${this.storagePrefix}-ui:subgroup:${t.name}:${s}`, c ? "1" : "0");
3972
+ } catch {
3973
+ }
3974
+ });
3975
+ const x = {};
3976
+ for (const [c, w] of Object.entries(n))
3977
+ c.startsWith(s + ".") && (x[c] = w, x[c.slice(s.length + 1)] = w);
3978
+ this.addFields(t, f, l, h, s, x), p.appendChild(g), p.appendChild(f), e.appendChild(p);
3979
+ }
3980
+ // === Search ===
3981
+ applySearch() {
3982
+ if (!this.body || !this.searchInput) return;
3983
+ const t = this.searchInput.value.toLowerCase().trim();
3984
+ for (const i of this.fields) {
3985
+ const s = i.path.toLowerCase().includes(t), o = i.groupName.toLowerCase().includes(t);
3986
+ i.row.classList.toggle("search-hidden", !!(t && !s && !o));
3987
+ }
3988
+ const e = this.body.querySelectorAll(".bonk-tweaker-group");
3989
+ for (const i of e) {
3990
+ const o = (i.dataset.groupName || "").toLowerCase().includes(t), a = i.querySelector(".bonk-tweaker-field:not(.search-hidden)");
3991
+ if (i.classList.toggle("search-hidden", !!(t && !o && !a)), t && (o || a)) {
3992
+ const n = i.querySelector(".bonk-tweaker-group-body"), l = i.querySelector(".bonk-tweaker-group-arrow");
3993
+ n && n.classList.remove("collapsed"), l && (l.textContent = "▼");
3994
+ }
3995
+ }
3996
+ }
3997
+ // === Refresh ===
3998
+ refreshAllFields() {
3999
+ for (const t of this.fields) {
4000
+ const e = this.resolveFieldValue(t);
4001
+ e !== void 0 && t.update(e);
4002
+ }
4003
+ }
4004
+ refreshGroupFields(t) {
4005
+ for (const e of this.fields)
4006
+ if (e.groupName === t) {
4007
+ const i = this.resolveFieldValue(e);
4008
+ i !== void 0 && e.update(i);
4009
+ }
4010
+ }
4011
+ refreshGetters() {
4012
+ for (const t of this.fields)
4013
+ if (t.defaultValue === void 0) {
4014
+ const e = this.resolveFieldValue(t);
4015
+ e !== void 0 && t.update(e);
4016
+ }
4017
+ }
4018
+ resolveFieldValue(t) {
4019
+ }
4020
+ // === Utilities ===
4021
+ createFieldRow(t) {
4022
+ const e = document.createElement("div");
4023
+ e.className = "bonk-tweaker-field", e.dataset.fieldKey = t;
4024
+ const i = document.createElement("span");
4025
+ i.className = "bonk-tweaker-field-name", i.textContent = t, i.title = t;
4026
+ const s = document.createElement("div");
4027
+ return s.className = "bonk-tweaker-field-controls", e.appendChild(i), e.appendChild(s), e;
4028
+ }
4029
+ createButton(t, e) {
4030
+ const i = document.createElement("button");
4031
+ return i.className = "bonk-tweaker-btn", i.textContent = t, i.addEventListener("click", e), i;
4032
+ }
4033
+ inferRange(t) {
4034
+ if (t === 0) return { min: -1, max: 1, step: 0.01 };
4035
+ const e = Math.abs(t);
4036
+ let i, s, o;
4037
+ return e <= 1 ? (i = t < 0 ? t * 3 : 0, s = t < 0 ? -t : t * 3, o = 1e-3) : e <= 10 ? (i = t < 0 ? t * 3 : 0, s = t < 0 ? 0 : t * 3, o = 0.1) : (i = t < 0 ? t * 3 : 0, s = t < 0 ? 0 : t * 3, o = e < 100 ? 1 : Math.round(e / 100)), { min: i, max: s, step: o };
4038
+ }
4039
+ formatNumber(t) {
4040
+ return Number.isInteger(t) ? String(t) : parseFloat(t.toFixed(4)).toString();
4041
+ }
4042
+ formatReadonly(t) {
4043
+ return typeof t == "number" ? this.formatNumber(t) : typeof t == "string" ? `"${t}"` : Array.isArray(t) ? `[${t.join(", ")}]` : t == null ? String(t) : JSON.stringify(t);
4044
+ }
4045
+ getNestedDefault(t, e) {
4046
+ const i = e.split(".");
4047
+ let s = t;
4048
+ for (const o of i) {
4049
+ if (s == null || typeof s != "object") return;
4050
+ s = s[o];
4051
+ }
4052
+ return s;
4053
+ }
4054
+ }
4055
+ const U = {
4056
+ hotkey: "Backquote",
4057
+ position: "right",
4058
+ width: 360,
4059
+ storagePrefix: "bonk-tweaker",
4060
+ theme: "amber"
4061
+ };
4062
+ class d {
4063
+ static overlay = null;
4064
+ static groups = /* @__PURE__ */ new Map();
4065
+ static config = { ...U };
4066
+ static initialized = !1;
4067
+ static hotkeyHandler = null;
4068
+ /**
4069
+ * Initialize the tweaker. Call once at startup.
4070
+ * Creates the DOM overlay and binds the toggle hotkey.
4071
+ */
4072
+ static init(t) {
4073
+ d.initialized || (d.initialized = !0, d.config = { ...U, ...t }, d.overlay = new mt(), d.overlay.setCallbacks(
4074
+ (e, i, s) => d.onFieldChange(e, i, s),
4075
+ (e) => d.reset(e),
4076
+ () => d.exportPreset(),
4077
+ (e) => d.importPreset(e)
4078
+ ), d.overlay.create(d.config.position, d.config.width, d.config.storagePrefix), d.overlay.setTheme(d.config.theme), d.overlay.restoreVisibility(), d.hotkeyHandler = (e) => {
4079
+ e.code === d.config.hotkey && (e.preventDefault(), d.toggle());
4080
+ }, window.addEventListener("keydown", d.hotkeyHandler));
4081
+ }
4082
+ /**
4083
+ * Register a constants object for live editing.
4084
+ * Saved overrides from localStorage are applied immediately.
4085
+ */
4086
+ static register(t, e, i) {
4087
+ if (!d.initialized) return;
4088
+ const s = d.deepClone(e), o = {
4089
+ name: t,
4090
+ target: e,
4091
+ defaults: s,
4092
+ options: i || {}
4093
+ };
4094
+ d.groups.set(t, o), d.overlay?.addGroup(o), d.loadOverrides(o);
4095
+ }
4096
+ /**
4097
+ * Unregister a constants group.
4098
+ */
4099
+ static unregister(t) {
4100
+ d.groups.delete(t), d.overlay?.removeGroup(t);
4101
+ }
4102
+ /** Show the tweaker overlay */
4103
+ static show() {
4104
+ d.overlay?.show();
4105
+ }
4106
+ /** Hide the tweaker overlay */
4107
+ static hide() {
4108
+ d.overlay?.hide();
4109
+ }
4110
+ /** Toggle the tweaker overlay visibility */
4111
+ static toggle() {
4112
+ d.overlay?.isVisible() ? d.overlay.hide() : d.overlay?.show();
4113
+ }
4114
+ /** Send the tweaker to dormant mode (faded, slid aside) */
4115
+ static goDormant() {
4116
+ d.overlay?.goDormant();
4117
+ }
4118
+ /** Wake the tweaker from dormant mode */
4119
+ static wake() {
4120
+ d.overlay?.wake();
4121
+ }
4122
+ /** Get the current tweaker state */
4123
+ static getState() {
4124
+ return d.overlay?.getState() ?? "hidden";
4125
+ }
4126
+ /**
4127
+ * Reset a group (or all groups) to default values.
4128
+ * Clears localStorage overrides.
4129
+ */
4130
+ static reset(t) {
4131
+ if (t) {
4132
+ const e = d.groups.get(t);
4133
+ e && (d.applyDefaults(e), d.clearStorage(t));
4134
+ } else {
4135
+ for (const e of d.groups.values())
4136
+ d.applyDefaults(e);
4137
+ d.clearStorage();
4138
+ }
4139
+ d.overlay?.refreshAllFields();
4140
+ }
4141
+ /**
4142
+ * Export all current overrides as a JSON string.
4143
+ */
4144
+ static exportPreset() {
4145
+ const t = {};
4146
+ for (const [e, i] of d.groups) {
4147
+ const s = d.getOverrides(i);
4148
+ Object.keys(s).length > 0 && (t[e] = s);
4149
+ }
4150
+ return JSON.stringify(t, null, 2);
4151
+ }
4152
+ /**
4153
+ * Import a preset JSON string and apply overrides.
4154
+ */
4155
+ static importPreset(t) {
4156
+ try {
4157
+ const e = JSON.parse(t);
4158
+ for (const [i, s] of Object.entries(e)) {
4159
+ const o = d.groups.get(i);
4160
+ if (o)
4161
+ for (const [a, n] of Object.entries(s))
4162
+ d.setNestedValue(o.target, a, n), d.saveOverride(i, a, n);
4163
+ }
4164
+ d.overlay?.refreshAllFields();
4165
+ } catch {
4166
+ console.warn("[Tweaker] Invalid preset JSON");
4167
+ }
4168
+ }
4169
+ /** Clean up everything */
4170
+ static destroy() {
4171
+ d.hotkeyHandler && (window.removeEventListener("keydown", d.hotkeyHandler), d.hotkeyHandler = null), d.overlay?.destroy(), d.overlay = null, d.groups.clear(), d.initialized = !1;
4172
+ }
4173
+ // === Internal ===
4174
+ static onFieldChange(t, e, i) {
4175
+ d.saveOverride(t, e, i);
4176
+ }
4177
+ static storageKey(t, e) {
4178
+ return `${d.config.storagePrefix}:${t}:${e}`;
4179
+ }
4180
+ static saveOverride(t, e, i) {
4181
+ try {
4182
+ localStorage.setItem(d.storageKey(t, e), JSON.stringify(i));
4183
+ } catch {
4184
+ }
4185
+ }
4186
+ static loadOverrides(t) {
4187
+ const e = `${d.config.storagePrefix}:${t.name}:`;
4188
+ for (let i = 0; i < localStorage.length; i++) {
4189
+ const s = localStorage.key(i);
4190
+ if (!s || !s.startsWith(e)) continue;
4191
+ const o = s.slice(e.length);
4192
+ try {
4193
+ const a = JSON.parse(localStorage.getItem(s));
4194
+ d.setNestedValue(t.target, o, a);
4195
+ } catch {
4196
+ }
4197
+ }
4198
+ }
4199
+ static clearStorage(t) {
4200
+ const e = t ? `${d.config.storagePrefix}:${t}:` : `${d.config.storagePrefix}:`, i = [];
4201
+ for (let s = 0; s < localStorage.length; s++) {
4202
+ const o = localStorage.key(s);
4203
+ o && o.startsWith(e) && i.push(o);
4204
+ }
4205
+ for (const s of i) localStorage.removeItem(s);
4206
+ }
4207
+ static applyDefaults(t) {
4208
+ d.deepApply(t.target, t.defaults);
4209
+ }
4210
+ static getOverrides(t) {
4211
+ const e = {};
4212
+ return d.diffValues(t.target, t.defaults, "", e), e;
4213
+ }
4214
+ static diffValues(t, e, i, s) {
4215
+ for (const o of Object.keys(e)) {
4216
+ const a = Object.getOwnPropertyDescriptor(t, o);
4217
+ if (a && a.get) continue;
4218
+ const n = i ? `${i}.${o}` : o, l = t[o], h = e[o];
4219
+ typeof l == "object" && l !== null && !Array.isArray(l) && typeof h == "object" && h !== null && !Array.isArray(h) ? d.diffValues(
4220
+ l,
4221
+ h,
4222
+ n,
4223
+ s
4224
+ ) : l !== h && (s[n] = l);
4225
+ }
4226
+ }
4227
+ static setNestedValue(t, e, i) {
4228
+ const s = e.split(".");
4229
+ let o = t;
4230
+ for (let a = 0; a < s.length - 1; a++) {
4231
+ const n = o[s[a]];
4232
+ if (typeof n != "object" || n === null) return;
4233
+ o = n;
4234
+ }
4235
+ o[s[s.length - 1]] = i;
4236
+ }
4237
+ static deepClone(t) {
4238
+ const e = {};
4239
+ for (const i of Object.keys(t)) {
4240
+ const s = Object.getOwnPropertyDescriptor(t, i);
4241
+ if (s && s.get) {
4242
+ Object.defineProperty(e, i, {
4243
+ get: s.get,
4244
+ enumerable: !0,
4245
+ configurable: !0
4246
+ });
4247
+ continue;
4248
+ }
4249
+ const o = t[i];
4250
+ typeof o == "object" && o !== null && !Array.isArray(o) ? e[i] = d.deepClone(o) : Array.isArray(o) ? e[i] = [...o] : e[i] = o;
4251
+ }
4252
+ return e;
4253
+ }
4254
+ static deepApply(t, e) {
4255
+ for (const i of Object.keys(e)) {
4256
+ const s = Object.getOwnPropertyDescriptor(t, i);
4257
+ if (s && s.get) continue;
4258
+ const o = e[i];
4259
+ if (typeof o == "object" && o !== null && !Array.isArray(o)) {
4260
+ const a = t[i];
4261
+ typeof a == "object" && a !== null && !Array.isArray(a) && d.deepApply(a, o);
4262
+ } else
4263
+ t[i] = o;
4264
+ }
4265
+ }
4266
+ }
4267
+ export {
4268
+ Et as AnimatedSprite,
4269
+ N as AudioEvents,
4270
+ G as AudioManager,
4271
+ Dt as AudioSource,
4272
+ It as Camera,
4273
+ j as CollisionLayers,
4274
+ T as EngineEvents,
4275
+ at as EventEmitter,
4276
+ vt as Game,
4277
+ B as GlobalEvents,
4278
+ rt as GlobalScheduler,
4279
+ y as Input,
4280
+ Pt as Keys,
4281
+ st as MatterPhysicsWorld,
4282
+ J as PixiRenderer,
4283
+ lt as RigidBody,
4284
+ ot as Scheduler,
4285
+ St as Sprite,
4286
+ C as Time,
4287
+ $ as Transform,
4288
+ d as Tweaker,
4289
+ Ot as UIButton,
4290
+ P as UIElement,
4291
+ Ft as UIHBox,
4292
+ At as UIImage,
4293
+ _t as UIManager,
4294
+ ut as UIPanel,
4295
+ Bt as UIText,
4296
+ Mt as UIVBox,
4297
+ tt as createPhysicsWorld,
4298
+ v as getAudioManager,
4299
+ yt as getPhysicsBackends,
4300
+ Q as registerPhysicsBackend,
4301
+ zt as vec2,
4302
+ xt as wait,
4303
+ Ct as waitForCoroutine,
4304
+ kt as waitFrames,
4305
+ wt as waitUntil
4306
+ };
4307
+ //# sourceMappingURL=bonkjs.js.map