@guinetik/gcanvas 1.0.0 → 1.0.1

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 (68) hide show
  1. package/demos/fluid-simple.html +22 -0
  2. package/demos/fluid.html +37 -0
  3. package/demos/index.html +2 -0
  4. package/demos/js/blob.js +18 -5
  5. package/demos/js/fluid-simple.js +253 -0
  6. package/demos/js/fluid.js +527 -0
  7. package/demos/js/tde/accretiondisk.js +64 -11
  8. package/demos/js/tde/blackholescene.js +2 -2
  9. package/demos/js/tde/config.js +2 -2
  10. package/demos/js/tde/index.js +152 -27
  11. package/demos/js/tde/lensedstarfield.js +32 -25
  12. package/demos/js/tde/tdestar.js +78 -98
  13. package/demos/js/tde/tidalstream.js +23 -7
  14. package/docs/README.md +230 -222
  15. package/docs/api/FluidSystem.md +173 -0
  16. package/docs/concepts/architecture-overview.md +204 -204
  17. package/docs/concepts/rendering-pipeline.md +279 -279
  18. package/docs/concepts/two-layer-architecture.md +229 -229
  19. package/docs/fluid-dynamics.md +97 -0
  20. package/docs/getting-started/first-game.md +354 -354
  21. package/docs/getting-started/installation.md +175 -157
  22. package/docs/modules/collision/README.md +2 -2
  23. package/docs/modules/fluent/README.md +6 -6
  24. package/docs/modules/game/README.md +303 -303
  25. package/docs/modules/isometric-camera.md +2 -2
  26. package/docs/modules/isometric.md +1 -1
  27. package/docs/modules/painter/README.md +328 -328
  28. package/docs/modules/particle/README.md +3 -3
  29. package/docs/modules/shapes/README.md +221 -221
  30. package/docs/modules/shapes/base/euclidian.md +123 -123
  31. package/docs/modules/shapes/base/shape.md +262 -262
  32. package/docs/modules/shapes/base/transformable.md +243 -243
  33. package/docs/modules/state/README.md +2 -2
  34. package/docs/modules/util/README.md +1 -1
  35. package/docs/modules/util/camera3d.md +3 -3
  36. package/docs/modules/util/scene3d.md +1 -1
  37. package/package.json +3 -1
  38. package/readme.md +19 -5
  39. package/src/collision/collision.js +75 -0
  40. package/src/game/index.js +2 -1
  41. package/src/game/pipeline.js +3 -3
  42. package/src/game/systems/FluidSystem.js +835 -0
  43. package/src/game/systems/index.js +11 -0
  44. package/src/game/ui/button.js +39 -18
  45. package/src/game/ui/cursor.js +14 -0
  46. package/src/game/ui/fps.js +12 -4
  47. package/src/game/ui/index.js +2 -0
  48. package/src/game/ui/stepper.js +549 -0
  49. package/src/game/ui/theme.js +121 -0
  50. package/src/game/ui/togglebutton.js +9 -3
  51. package/src/game/ui/tooltip.js +11 -4
  52. package/src/math/fluid.js +507 -0
  53. package/src/math/index.js +2 -0
  54. package/src/mixins/anchor.js +17 -7
  55. package/src/motion/tweenetik.js +16 -0
  56. package/src/shapes/index.js +1 -0
  57. package/src/util/camera3d.js +218 -12
  58. package/types/fluent.d.ts +361 -0
  59. package/types/game.d.ts +303 -0
  60. package/types/index.d.ts +144 -5
  61. package/types/math.d.ts +361 -0
  62. package/types/motion.d.ts +271 -0
  63. package/types/particle.d.ts +373 -0
  64. package/types/shapes.d.ts +107 -9
  65. package/types/util.d.ts +353 -0
  66. package/types/webgl.d.ts +109 -0
  67. package/disk_example.png +0 -0
  68. package/tde.png +0 -0
@@ -32,6 +32,9 @@ export class Camera3D {
32
32
  * @param {number} [options.rotationX=0] - Initial X rotation (tilt up/down) in radians
33
33
  * @param {number} [options.rotationY=0] - Initial Y rotation (spin left/right) in radians
34
34
  * @param {number} [options.rotationZ=0] - Initial Z rotation (roll) in radians
35
+ * @param {number} [options.x=0] - Initial X position in world space
36
+ * @param {number} [options.y=0] - Initial Y position in world space
37
+ * @param {number} [options.z=0] - Initial Z position in world space
35
38
  * @param {number} [options.perspective=800] - Perspective distance (higher = less distortion)
36
39
  * @param {number} [options.sensitivity=0.005] - Mouse drag sensitivity
37
40
  * @param {number} [options.minRotationX=-1.5] - Minimum X rotation limit
@@ -49,10 +52,18 @@ export class Camera3D {
49
52
  this.rotationY = options.rotationY ?? 0;
50
53
  this.rotationZ = options.rotationZ ?? 0;
51
54
 
55
+ // Position state (camera location in world space)
56
+ this.x = options.x ?? 0;
57
+ this.y = options.y ?? 0;
58
+ this.z = options.z ?? 0;
59
+
52
60
  // Store initial values for reset
53
61
  this._initialRotationX = this.rotationX;
54
62
  this._initialRotationY = this.rotationY;
55
63
  this._initialRotationZ = this.rotationZ;
64
+ this._initialX = this.x;
65
+ this._initialY = this.y;
66
+ this._initialZ = this.z;
56
67
 
57
68
  // Perspective
58
69
  this.perspective = options.perspective ?? 800;
@@ -86,6 +97,20 @@ export class Camera3D {
86
97
  this._lastMouseY = 0;
87
98
  this._canvas = null;
88
99
  this._boundHandlers = null;
100
+
101
+ // Follow target state
102
+ this._followTarget = null;
103
+ this._followOffset = { x: 0, y: 0, z: 0 };
104
+ this._followLookAt = true; // Auto-orient to look at target's forward direction
105
+ this._followLerp = 0.1; // Interpolation speed (0-1, higher = snappier)
106
+
107
+ // Position animation state
108
+ this._targetX = null;
109
+ this._targetY = null;
110
+ this._targetZ = null;
111
+ this._targetRotationX = null;
112
+ this._targetRotationY = null;
113
+ this._positionLerp = 0.05; // Default smooth movement speed
89
114
  }
90
115
 
91
116
  /**
@@ -96,6 +121,12 @@ export class Camera3D {
96
121
  * @returns {{x: number, y: number, z: number, scale: number}} Projected 2D coordinates and depth info
97
122
  */
98
123
  project(x, y, z) {
124
+ // Translate world relative to camera position
125
+ // (Moving camera right = moving world left)
126
+ x -= this.x;
127
+ y -= this.y;
128
+ z -= this.z;
129
+
99
130
  // Rotate around Z axis (roll)
100
131
  if (this.rotationZ !== 0) {
101
132
  const cosZ = Math.cos(this.rotationZ);
@@ -141,12 +172,78 @@ export class Camera3D {
141
172
  }
142
173
 
143
174
  /**
144
- * Update camera for auto-rotation and inertia (call in update loop)
175
+ * Update camera for auto-rotation, inertia, and follow mode (call in update loop)
145
176
  * @param {number} dt - Delta time in seconds
146
177
  */
147
178
  update(dt) {
148
- // Apply inertia when not dragging
149
- if (this.inertia && !this._isDragging) {
179
+ // Follow target mode - position camera relative to target
180
+ if (this._followTarget) {
181
+ const target = this._followTarget;
182
+ const targetX = (target.x ?? 0) + this._followOffset.x;
183
+ const targetY = (target.y ?? 0) + this._followOffset.y;
184
+ const targetZ = (target.z ?? 0) + this._followOffset.z;
185
+
186
+ // Smooth interpolation to target position
187
+ this.x += (targetX - this.x) * this._followLerp;
188
+ this.y += (targetY - this.y) * this._followLerp;
189
+ this.z += (targetZ - this.z) * this._followLerp;
190
+
191
+ // Auto-orient to look toward origin (or specified look target)
192
+ if (this._followLookAt) {
193
+ const lookX = this._followLookAtTarget?.x ?? 0;
194
+ const lookY = this._followLookAtTarget?.y ?? 0;
195
+ const lookZ = this._followLookAtTarget?.z ?? 0;
196
+
197
+ // Calculate direction from camera to look target
198
+ const dx = lookX - this.x;
199
+ const dy = lookY - this.y;
200
+ const dz = lookZ - this.z;
201
+ const distXZ = Math.sqrt(dx * dx + dz * dz);
202
+
203
+ // Target rotation to face the look target
204
+ const targetRotY = Math.atan2(dx, dz);
205
+ const targetRotX = Math.atan2(-dy, distXZ);
206
+
207
+ // Smooth rotation interpolation
208
+ this.rotationY += this._angleDiff(this.rotationY, targetRotY) * this._followLerp;
209
+ this.rotationX += (targetRotX - this.rotationX) * this._followLerp;
210
+ }
211
+ }
212
+ // Animated position movement (moveTo)
213
+ else if (this._targetX !== null) {
214
+ const lerp = this._positionLerp;
215
+
216
+ // Interpolate position
217
+ this.x += (this._targetX - this.x) * lerp;
218
+ this.y += (this._targetY - this.y) * lerp;
219
+ this.z += (this._targetZ - this.z) * lerp;
220
+
221
+ // Interpolate rotation if targets set
222
+ if (this._targetRotationX !== null) {
223
+ this.rotationX += (this._targetRotationX - this.rotationX) * lerp;
224
+ }
225
+ if (this._targetRotationY !== null) {
226
+ this.rotationY += this._angleDiff(this.rotationY, this._targetRotationY) * lerp;
227
+ }
228
+
229
+ // Check if we've arrived (close enough)
230
+ const posDist = Math.abs(this._targetX - this.x) +
231
+ Math.abs(this._targetY - this.y) +
232
+ Math.abs(this._targetZ - this.z);
233
+ if (posDist < 0.1) {
234
+ this.x = this._targetX;
235
+ this.y = this._targetY;
236
+ this.z = this._targetZ;
237
+ this._targetX = null;
238
+ this._targetY = null;
239
+ this._targetZ = null;
240
+ this._targetRotationX = null;
241
+ this._targetRotationY = null;
242
+ }
243
+ }
244
+
245
+ // Apply inertia when not dragging and not following
246
+ if (this.inertia && !this._isDragging && !this._followTarget) {
150
247
  // Apply velocity to rotation
151
248
  if (Math.abs(this._velocityX) > 0.0001 || Math.abs(this._velocityY) > 0.0001) {
152
249
  this.rotationY += this._velocityY;
@@ -167,8 +264,8 @@ export class Camera3D {
167
264
  }
168
265
  }
169
266
 
170
- // Auto-rotate when not dragging and no significant velocity
171
- if (this.autoRotate && !this._isDragging) {
267
+ // Auto-rotate when not dragging, not following, and no significant velocity
268
+ if (this.autoRotate && !this._isDragging && !this._followTarget) {
172
269
  const hasVelocity = Math.abs(this._velocityX) > 0.001 || Math.abs(this._velocityY) > 0.001;
173
270
  if (!hasVelocity) {
174
271
  const delta = this.autoRotateSpeed * dt;
@@ -187,6 +284,17 @@ export class Camera3D {
187
284
  }
188
285
  }
189
286
 
287
+ /**
288
+ * Calculate shortest angle difference (handles wraparound)
289
+ * @private
290
+ */
291
+ _angleDiff(from, to) {
292
+ let diff = to - from;
293
+ while (diff > Math.PI) diff -= Math.PI * 2;
294
+ while (diff < -Math.PI) diff += Math.PI * 2;
295
+ return diff;
296
+ }
297
+
190
298
  /**
191
299
  * Enable mouse/touch drag rotation on a canvas
192
300
  * @param {HTMLCanvasElement} canvas - The canvas element to attach controls to
@@ -360,15 +468,22 @@ export class Camera3D {
360
468
  }
361
469
 
362
470
  /**
363
- * Reset rotation to initial values and stop inertia
471
+ * Reset rotation and position to initial values, stop inertia and following
364
472
  * @returns {Camera3D} Returns this for chaining
365
473
  */
366
474
  reset() {
367
475
  this.rotationX = this._initialRotationX;
368
476
  this.rotationY = this._initialRotationY;
369
477
  this.rotationZ = this._initialRotationZ;
478
+ this.x = this._initialX;
479
+ this.y = this._initialY;
480
+ this.z = this._initialZ;
370
481
  this._velocityX = 0;
371
482
  this._velocityY = 0;
483
+ this._followTarget = null;
484
+ this._targetX = null;
485
+ this._targetY = null;
486
+ this._targetZ = null;
372
487
  return this;
373
488
  }
374
489
 
@@ -382,6 +497,91 @@ export class Camera3D {
382
497
  return this;
383
498
  }
384
499
 
500
+ /**
501
+ * Set camera position in world space
502
+ * @param {number} x - X position
503
+ * @param {number} y - Y position
504
+ * @param {number} z - Z position
505
+ * @returns {Camera3D} Returns this for chaining
506
+ */
507
+ setPosition(x, y, z) {
508
+ this.x = x;
509
+ this.y = y;
510
+ this.z = z;
511
+ return this;
512
+ }
513
+
514
+ /**
515
+ * Animate camera to a new position (and optionally rotation)
516
+ * @param {number} x - Target X position
517
+ * @param {number} y - Target Y position
518
+ * @param {number} z - Target Z position
519
+ * @param {object} [options] - Animation options
520
+ * @param {number} [options.rotationX] - Target X rotation
521
+ * @param {number} [options.rotationY] - Target Y rotation
522
+ * @param {number} [options.lerp=0.05] - Interpolation speed (0-1)
523
+ * @returns {Camera3D} Returns this for chaining
524
+ */
525
+ moveTo(x, y, z, options = {}) {
526
+ this._targetX = x;
527
+ this._targetY = y;
528
+ this._targetZ = z;
529
+ this._targetRotationX = options.rotationX ?? null;
530
+ this._targetRotationY = options.rotationY ?? null;
531
+ this._positionLerp = options.lerp ?? 0.05;
532
+ return this;
533
+ }
534
+
535
+ /**
536
+ * Follow a target object (position camera relative to target)
537
+ * @param {object} target - Object with x, y, z properties to follow
538
+ * @param {object} [options] - Follow options
539
+ * @param {number} [options.offsetX=0] - X offset from target
540
+ * @param {number} [options.offsetY=0] - Y offset from target
541
+ * @param {number} [options.offsetZ=0] - Z offset from target
542
+ * @param {boolean} [options.lookAt=true] - Auto-orient to look at lookAtTarget
543
+ * @param {object} [options.lookAtTarget=null] - Point to look at (default: origin)
544
+ * @param {number} [options.lerp=0.1] - Interpolation speed (0-1, higher = snappier)
545
+ * @returns {Camera3D} Returns this for chaining
546
+ */
547
+ follow(target, options = {}) {
548
+ this._followTarget = target;
549
+ this._followOffset = {
550
+ x: options.offsetX ?? 0,
551
+ y: options.offsetY ?? 0,
552
+ z: options.offsetZ ?? 0,
553
+ };
554
+ this._followLookAt = options.lookAt ?? true;
555
+ this._followLookAtTarget = options.lookAtTarget ?? null;
556
+ this._followLerp = options.lerp ?? 0.1;
557
+ return this;
558
+ }
559
+
560
+ /**
561
+ * Stop following target and optionally reset to initial position
562
+ * @param {boolean} [resetPosition=false] - Whether to animate back to initial position
563
+ * @returns {Camera3D} Returns this for chaining
564
+ */
565
+ unfollow(resetPosition = false) {
566
+ this._followTarget = null;
567
+ if (resetPosition) {
568
+ this.moveTo(this._initialX, this._initialY, this._initialZ, {
569
+ rotationX: this._initialRotationX,
570
+ rotationY: this._initialRotationY,
571
+ lerp: 0.05,
572
+ });
573
+ }
574
+ return this;
575
+ }
576
+
577
+ /**
578
+ * Check if camera is currently following a target
579
+ * @returns {boolean} True if following a target
580
+ */
581
+ isFollowing() {
582
+ return this._followTarget !== null;
583
+ }
584
+
385
585
  /**
386
586
  * Set rotation angles
387
587
  * @param {number} x - X rotation in radians
@@ -424,15 +624,21 @@ export class Camera3D {
424
624
  }
425
625
 
426
626
  /**
427
- * Look at a specific point (sets rotation to face that direction)
428
- * @param {number} x - Target X
429
- * @param {number} y - Target Y
430
- * @param {number} z - Target Z
627
+ * Look at a specific point (sets rotation to face that direction from current position)
628
+ * @param {number} x - Target X in world space
629
+ * @param {number} y - Target Y in world space
630
+ * @param {number} z - Target Z in world space
431
631
  * @returns {Camera3D} Returns this for chaining
432
632
  */
433
633
  lookAt(x, y, z) {
434
- this.rotationY = Math.atan2(x, z);
435
- this.rotationX = Math.atan2(y, Math.sqrt(x * x + z * z));
634
+ // Calculate direction from camera position to target
635
+ const dx = x - this.x;
636
+ const dy = y - this.y;
637
+ const dz = z - this.z;
638
+ const distXZ = Math.sqrt(dx * dx + dz * dz);
639
+
640
+ this.rotationY = Math.atan2(dx, dz);
641
+ this.rotationX = Math.atan2(-dy, distXZ);
436
642
  return this;
437
643
  }
438
644
  }
@@ -0,0 +1,361 @@
1
+ /// <reference lib="es2015" />
2
+
3
+ /**
4
+ * GCanvas Fluent API Module
5
+ * Declarative, chainable API for rapid game and creative coding development.
6
+ * @module fluent
7
+ */
8
+
9
+ import { Game, Scene, GameObject, GameObjectOptions, SceneOptions } from './game';
10
+ import { Shape } from './shapes';
11
+ import { Easing, Motion, Tweenetik } from './motion';
12
+ import { Keys, Mouse } from './io';
13
+
14
+ // ==========================================================================
15
+ // Main Entry Points
16
+ // ==========================================================================
17
+
18
+ /**
19
+ * Main entry point for the fluent API.
20
+ * Creates a FluentGame instance with automatic canvas setup.
21
+ *
22
+ * @param options - Game configuration
23
+ * @returns FluentGame builder instance
24
+ *
25
+ * @example
26
+ * gcanvas({ bg: 'black' })
27
+ * .scene('game')
28
+ * .go({ x: 400, y: 300, name: 'player' })
29
+ * .circle({ radius: 30, fill: 'lime' })
30
+ * .start();
31
+ */
32
+ export function gcanvas(options?: FluentGameOptions): FluentGame;
33
+
34
+ /**
35
+ * Ultra-simple sketch mode for quick creative coding prototypes.
36
+ *
37
+ * @param w - Canvas width (default: 800)
38
+ * @param h - Canvas height (default: 600)
39
+ * @param bg - Background color (default: 'black')
40
+ * @returns SketchAPI builder instance
41
+ *
42
+ * @example
43
+ * sketch(800, 600, 'black')
44
+ * .circle(400, 300, 50, 'lime')
45
+ * .update((dt, ctx) => {
46
+ * ctx.shapes[0].x += Math.sin(ctx.time) * 2;
47
+ * })
48
+ * .start();
49
+ */
50
+ export function sketch(w?: number, h?: number, bg?: string): SketchAPI;
51
+
52
+ // ==========================================================================
53
+ // FluentGame Options
54
+ // ==========================================================================
55
+
56
+ /** Options for FluentGame constructor */
57
+ export interface FluentGameOptions {
58
+ /** Canvas element to use (auto-creates if not provided) */
59
+ canvas?: HTMLCanvasElement;
60
+ /** Canvas width when auto-creating (default: 800) */
61
+ width?: number;
62
+ /** Canvas height when auto-creating (default: 600) */
63
+ height?: number;
64
+ /** Background color */
65
+ bg?: string;
66
+ /** Enable fluid/responsive sizing (default: true for auto-created canvas) */
67
+ fluid?: boolean;
68
+ /** Container for auto-created canvas (default: document.body) */
69
+ container?: HTMLElement;
70
+ /** Target FPS (default: 60) */
71
+ fps?: number;
72
+ /** Pixel ratio for HiDPI displays */
73
+ pixelRatio?: number;
74
+ }
75
+
76
+ // ==========================================================================
77
+ // FluentGame Class
78
+ // ==========================================================================
79
+
80
+ /**
81
+ * Root builder class for the fluent API.
82
+ * Wraps the Game class with a declarative, chainable interface.
83
+ */
84
+ export class FluentGame {
85
+ constructor(options?: FluentGameOptions);
86
+
87
+ // Scene Management
88
+ /**
89
+ * Create or switch to a scene.
90
+ * @param name - Scene identifier
91
+ * @param options - Scene options
92
+ */
93
+ scene(name: string, options?: FluentSceneOptions): FluentScene;
94
+ /**
95
+ * Create scene with custom Scene class.
96
+ * @param name - Scene identifier
97
+ * @param sceneClass - Custom Scene class
98
+ * @param options - Scene options
99
+ */
100
+ scene(name: string, sceneClass: new (...args: any[]) => Scene, options?: FluentSceneOptions): FluentScene;
101
+ /**
102
+ * Create scene with custom Scene class (name derived from class).
103
+ * @param sceneClass - Custom Scene class
104
+ * @param options - Scene options
105
+ */
106
+ scene(sceneClass: new (...args: any[]) => Scene, options?: FluentSceneOptions): FluentScene;
107
+
108
+ /**
109
+ * Switch context to an existing scene (does not create).
110
+ * @param name - Scene name
111
+ */
112
+ inScene(name: string): FluentScene;
113
+
114
+ /**
115
+ * Shortcut: create GameObject in current/default scene.
116
+ * @param options - GameObject options
117
+ */
118
+ go(options?: GameObjectOptions): FluentGO;
119
+ /**
120
+ * Create GameObject with custom class.
121
+ * @param goClass - Custom GameObject class
122
+ * @param options - GameObject options
123
+ */
124
+ go(goClass: new (...args: any[]) => GameObject, options?: GameObjectOptions): FluentGO;
125
+
126
+ // Scene Visibility
127
+ /** Show a scene by name */
128
+ showScene(name: string): FluentGame;
129
+ /** Hide a scene by name */
130
+ hideScene(name: string): FluentGame;
131
+ /** Transition between scenes */
132
+ transition(from: string, to: string, options?: TransitionOptions): FluentGame;
133
+
134
+ // State Management
135
+ /** Set initial state */
136
+ state(initialState: Record<string, any>): FluentGame;
137
+ /** Get a state value */
138
+ getState<T = any>(key: string): T;
139
+ /** Set a state value */
140
+ setState(key: string, value: any): FluentGame;
141
+
142
+ // Events & Lifecycle
143
+ /**
144
+ * Register event handler.
145
+ * @param event - Event name (update, keydown:escape, click, etc.)
146
+ * @param handler - Handler function
147
+ */
148
+ on(event: string, handler: (ctx: FluentContext, e?: any) => void): FluentGame;
149
+ on(event: 'update', handler: (dt: number, ctx: FluentContext) => void): FluentGame;
150
+
151
+ // Plugins
152
+ /** Use a plugin or scene builder function */
153
+ use(plugin: (game: FluentGame) => void): FluentGame;
154
+
155
+ // Lifecycle
156
+ /** Start the game loop */
157
+ start(): FluentGame;
158
+ /** Stop the game loop */
159
+ stop(): FluentGame;
160
+ /** Restart the game */
161
+ restart(): FluentGame;
162
+
163
+ // Accessors
164
+ /** Underlying Game instance */
165
+ readonly game: Game;
166
+ /** Named object references */
167
+ readonly refs: Record<string, GameObject>;
168
+ /** All scenes */
169
+ readonly scenes: Map<string, Scene>;
170
+ /** Canvas element */
171
+ readonly canvas: HTMLCanvasElement;
172
+ /** Canvas width */
173
+ readonly width: number;
174
+ /** Canvas height */
175
+ readonly height: number;
176
+ }
177
+
178
+ // ==========================================================================
179
+ // FluentScene Class
180
+ // ==========================================================================
181
+
182
+ /** Options for fluent scene creation */
183
+ export interface FluentSceneOptions extends SceneOptions {
184
+ /** Scene z-index (default: 0) */
185
+ zIndex?: number;
186
+ /** Whether scene is visible (default: true) */
187
+ active?: boolean;
188
+ /** Scene enter callback */
189
+ onEnter?: (ctx: FluentContext) => void;
190
+ /** Scene exit callback */
191
+ onExit?: (ctx: FluentContext) => void;
192
+ }
193
+
194
+ /** Transition options */
195
+ export interface TransitionOptions {
196
+ /** Fade duration in seconds */
197
+ fade?: number;
198
+ /** Completion callback */
199
+ onComplete?: () => void;
200
+ }
201
+
202
+ /**
203
+ * Scene builder for the fluent API.
204
+ */
205
+ export class FluentScene {
206
+ /**
207
+ * Create a GameObject in this scene.
208
+ * @param options - GameObject options
209
+ */
210
+ go(options?: GameObjectOptions): FluentGO;
211
+ /**
212
+ * Create GameObject with custom class.
213
+ * @param goClass - Custom GameObject class
214
+ * @param options - GameObject options
215
+ */
216
+ go(goClass: new (...args: any[]) => GameObject, options?: GameObjectOptions): FluentGO;
217
+
218
+ /** Return to FluentGame context */
219
+ end(): FluentGame;
220
+
221
+ /** Access the underlying scene */
222
+ readonly sceneInstance: Scene;
223
+ }
224
+
225
+ // ==========================================================================
226
+ // FluentGO Class
227
+ // ==========================================================================
228
+
229
+ /**
230
+ * GameObject builder for the fluent API.
231
+ * Provides chainable methods for adding shapes and behaviors.
232
+ */
233
+ export class FluentGO {
234
+ // Shape methods
235
+ circle(options?: { radius?: number; fill?: string; stroke?: string }): FluentGO;
236
+ rect(options?: { width?: number; height?: number; fill?: string; stroke?: string }): FluentGO;
237
+ square(options?: { size?: number; fill?: string; stroke?: string }): FluentGO;
238
+ star(options?: { points?: number; radius?: number; innerRadius?: number; fill?: string }): FluentGO;
239
+ triangle(options?: { size?: number; fill?: string }): FluentGO;
240
+ hexagon(options?: { radius?: number; fill?: string }): FluentGO;
241
+ line(options?: { x2?: number; y2?: number; stroke?: string; lineWidth?: number }): FluentGO;
242
+ ring(options?: { innerRadius?: number; outerRadius?: number; fill?: string }): FluentGO;
243
+ text(content: string, options?: { fill?: string; font?: string }): FluentGO;
244
+
245
+ // Motion methods (apply behaviors)
246
+ pulse(options?: { min?: number; max?: number; duration?: number }): FluentGO;
247
+ orbit(options?: { radius?: number; speed?: number; centerX?: number; centerY?: number }): FluentGO;
248
+ oscillate(options?: { axis?: 'x' | 'y'; amplitude?: number; frequency?: number }): FluentGO;
249
+ bounce(options?: { height?: number; duration?: number }): FluentGO;
250
+ shake(options?: { intensity?: number; duration?: number }): FluentGO;
251
+ float(options?: { amplitude?: number; frequency?: number }): FluentGO;
252
+ spin(options?: { speed?: number }): FluentGO;
253
+
254
+ /** Return to FluentScene context */
255
+ end(): FluentScene;
256
+
257
+ /** Access the underlying GameObject */
258
+ readonly goInstance: GameObject;
259
+ }
260
+
261
+ // ==========================================================================
262
+ // FluentLayer Class
263
+ // ==========================================================================
264
+
265
+ /**
266
+ * Layer builder for organizing GameObjects at different z-levels.
267
+ */
268
+ export class FluentLayer {
269
+ /** Add a GameObject to this layer */
270
+ go(options?: GameObjectOptions): FluentGO;
271
+ /** Return to parent context */
272
+ end(): FluentScene;
273
+ }
274
+
275
+ // ==========================================================================
276
+ // Sketch API
277
+ // ==========================================================================
278
+
279
+ /** Context passed to sketch update function */
280
+ export interface SketchContext {
281
+ /** All created GameObjects */
282
+ shapes: GameObject[];
283
+ /** Elapsed time in seconds */
284
+ time: number;
285
+ /** Current frame number */
286
+ frame: number;
287
+ /** Canvas width */
288
+ width: number;
289
+ /** Canvas height */
290
+ height: number;
291
+ /** Mouse position */
292
+ mouse: { x: number; y: number };
293
+ /** Named object references */
294
+ refs: Record<string, GameObject>;
295
+ /** Underlying Game instance */
296
+ game: Game;
297
+ }
298
+
299
+ /**
300
+ * Ultra-simple sketch API for quick prototypes.
301
+ */
302
+ export interface SketchAPI {
303
+ // Shapes
304
+ circle(x: number, y: number, r: number, fill?: string): SketchAPI;
305
+ rect(x: number, y: number, w: number, h: number, fill?: string): SketchAPI;
306
+ square(x: number, y: number, size: number, fill?: string): SketchAPI;
307
+ star(x: number, y: number, points: number, r: number, fill?: string): SketchAPI;
308
+ triangle(x: number, y: number, size: number, fill?: string): SketchAPI;
309
+ hexagon(x: number, y: number, r: number, fill?: string): SketchAPI;
310
+ line(x1: number, y1: number, x2: number, y2: number, stroke?: string, lineWidth?: number): SketchAPI;
311
+ ring(x: number, y: number, innerRadius: number, outerRadius: number, fill?: string): SketchAPI;
312
+ text(content: string, x: number, y: number, opts?: { fill?: string; font?: string }): SketchAPI;
313
+
314
+ // Bulk creation
315
+ grid(cols: number, rows: number, spacing: number, shapeFn: (api: SketchAPI, x: number, y: number, col: number, row: number) => void): SketchAPI;
316
+ repeat(count: number, shapeFn: (api: SketchAPI, index: number, total: number) => void): SketchAPI;
317
+ radial(cx: number, cy: number, radius: number, count: number, shapeFn: (api: SketchAPI, x: number, y: number, angle: number, index: number) => void): SketchAPI;
318
+
319
+ // Lifecycle
320
+ setup(fn: (api: SketchAPI) => void): SketchAPI;
321
+ update(fn: (dt: number, ctx: SketchContext) => void): SketchAPI;
322
+ start(): FluentGame;
323
+
324
+ // Accessors
325
+ readonly width: number;
326
+ readonly height: number;
327
+ readonly game: FluentGame | null;
328
+ }
329
+
330
+ // ==========================================================================
331
+ // Fluent Context
332
+ // ==========================================================================
333
+
334
+ /** Context object passed to event handlers */
335
+ export interface FluentContext {
336
+ /** Named object references */
337
+ refs: Record<string, GameObject>;
338
+ /** Game state */
339
+ state: Record<string, any>;
340
+ /** All scenes by name */
341
+ scenes: Record<string, Scene>;
342
+ /** Underlying Game instance */
343
+ game: Game;
344
+ /** Canvas width */
345
+ width: number;
346
+ /** Canvas height */
347
+ height: number;
348
+ /** Show a scene */
349
+ showScene: (name: string) => void;
350
+ /** Hide a scene */
351
+ hideScene: (name: string) => void;
352
+ /** Transition between scenes */
353
+ transition: (from: string, to: string, opts?: TransitionOptions) => void;
354
+ }
355
+
356
+ // ==========================================================================
357
+ // Re-exports for convenience
358
+ // ==========================================================================
359
+
360
+ export { Motion, Easing, Tweenetik, Keys, Mouse };
361
+