@rosalana/sandbox 0.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.
@@ -0,0 +1,1107 @@
1
+ var S = Object.defineProperty;
2
+ var L = (o, t, e) => t in o ? S(o, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : o[t] = e;
3
+ var i = (o, t, e) => L(o, typeof t != "symbol" ? t + "" : t, e);
4
+ class l {
5
+ constructor(t, e, r, s) {
6
+ this.target = t, this.type = e, this.listener = r, this.options = s, this.target.addEventListener(
7
+ this.type,
8
+ this.listener,
9
+ this.options
10
+ );
11
+ }
12
+ remove() {
13
+ this.target.removeEventListener(
14
+ this.type,
15
+ this.listener,
16
+ this.options
17
+ );
18
+ }
19
+ static on(t, e, r, s) {
20
+ return t.addEventListener(e, r, s), () => t.removeEventListener(e, r, s);
21
+ }
22
+ }
23
+ class u extends Error {
24
+ constructor(t, e) {
25
+ super(t), this.code = e, this.name = "SandboxError";
26
+ }
27
+ }
28
+ class w extends u {
29
+ constructor(t) {
30
+ const e = t === "not_supported" ? "WebGL is not supported in this browser." : "Failed to create WebGL context. The GPU may be unavailable.";
31
+ super(
32
+ e,
33
+ t === "not_supported" ? "WEBGL_NOT_SUPPORTED" : "CONTEXT_CREATION_FAILED"
34
+ ), this.name = "SandboxContextError";
35
+ }
36
+ }
37
+ class k extends u {
38
+ constructor(t, e) {
39
+ super(
40
+ `Vertex and fragment shader WebGL versions do not match (${t} vs ${e})`,
41
+ "SHADER_VERSION_MISMATCH"
42
+ ), this.vertexVersion = t, this.fragmentVersion = e, this.name = "SandboxShaderVersionMismatchError";
43
+ }
44
+ }
45
+ class f extends u {
46
+ constructor(e, r, s) {
47
+ const n = f.parseErrorLines(s), a = n.length > 0 ? ` at line(s): ${n.join(", ")}` : "";
48
+ super(
49
+ `${e} shader compilation failed${a}
50
+
51
+ ${s}`,
52
+ "SHADER_COMPILATION_FAILED"
53
+ );
54
+ /** Line numbers where errors occurred */
55
+ i(this, "lines");
56
+ this.shaderType = e, this.source = r, this.infoLog = s, this.name = "SandboxShaderCompilationError", this.lines = n;
57
+ }
58
+ /** Parse error log to extract line numbers */
59
+ static parseErrorLines(e) {
60
+ const r = [
61
+ /ERROR:\s*\d*:(\d+)/g,
62
+ // Chrome/ANGLE: ERROR: 0:15
63
+ /(\d+):(\d+)\(\d+\):/g,
64
+ // Mesa: 0:15(0):
65
+ /^(\d+):/gm
66
+ // Simple: 15:
67
+ ], s = /* @__PURE__ */ new Set();
68
+ for (const n of r) {
69
+ let a;
70
+ for (; (a = n.exec(e)) !== null; ) {
71
+ const c = parseInt(a[1], 10);
72
+ c > 0 && s.add(c);
73
+ }
74
+ }
75
+ return [...s].sort((n, a) => n - a);
76
+ }
77
+ }
78
+ class m extends u {
79
+ constructor(t) {
80
+ super(`Shader program linking failed
81
+
82
+ ${t}`, "PROGRAM_LINK_FAILED"), this.infoLog = t, this.name = "SandboxProgramError";
83
+ }
84
+ }
85
+ class R {
86
+ constructor() {
87
+ /** Total elapsed time in seconds */
88
+ i(this, "time", 0);
89
+ /** Delta time since last frame in seconds */
90
+ i(this, "delta", 0);
91
+ /** Frame counter */
92
+ i(this, "frame", 0);
93
+ /** Is clock running */
94
+ i(this, "running", !1);
95
+ i(this, "startTime", 0);
96
+ i(this, "lastTime", 0);
97
+ i(this, "rafId", null);
98
+ i(this, "callback", null);
99
+ this.loop = this.loop.bind(this);
100
+ }
101
+ /**
102
+ * Start the animation loop with a render callback.
103
+ */
104
+ start(t) {
105
+ if (this.running) return this;
106
+ this.callback = t, this.running = !0;
107
+ const e = performance.now();
108
+ return this.startTime = e, this.lastTime = e, this.rafId = requestAnimationFrame(this.loop), this;
109
+ }
110
+ /**
111
+ * Stop the animation loop.
112
+ * Clock state is preserved for resume.
113
+ */
114
+ stop() {
115
+ return this.running ? (this.running = !1, this.rafId !== null && (cancelAnimationFrame(this.rafId), this.rafId = null), this) : this;
116
+ }
117
+ /**
118
+ * Reset clock to initial state.
119
+ */
120
+ reset() {
121
+ return this.stop(), this.time = 0, this.delta = 0, this.frame = 0, this;
122
+ }
123
+ /**
124
+ * Get current clock state snapshot.
125
+ */
126
+ getState() {
127
+ return {
128
+ time: this.time,
129
+ delta: this.delta,
130
+ frame: this.frame
131
+ };
132
+ }
133
+ /**
134
+ * Advance clock by one tick (for single-shot rendering).
135
+ * Useful when autoplay is disabled.
136
+ */
137
+ tick(t = 0) {
138
+ return this.delta = t, this.time += t, this.frame++, this.callback && this.callback(this.getState()), this;
139
+ }
140
+ /**
141
+ * Set time directly (for deterministic rendering).
142
+ */
143
+ setTime(t) {
144
+ return this.time = t, this;
145
+ }
146
+ /**
147
+ * Cleanup.
148
+ */
149
+ destroy() {
150
+ this.stop(), this.callback = null;
151
+ }
152
+ /**
153
+ * Internal animation frame handler.
154
+ */
155
+ loop(t) {
156
+ this.running && (this.delta = (t - this.lastTime) / 1e3, this.lastTime = t, this.time = (t - this.startTime) / 1e3, this.frame++, this.callback && this.callback(this.getState()), this.rafId = requestAnimationFrame(this.loop));
157
+ }
158
+ }
159
+ class p {
160
+ constructor(t) {
161
+ i(this, "gl");
162
+ i(this, "vao", null);
163
+ i(this, "vbo", null);
164
+ i(this, "ibo", null);
165
+ i(this, "vertexCount", 0);
166
+ i(this, "indexCount", 0);
167
+ i(this, "useIndices", !1);
168
+ // WebGL1 VAO extension (if available)
169
+ i(this, "vaoExt", null);
170
+ i(this, "isWebGL2");
171
+ this.gl = t, this.isWebGL2 = t instanceof WebGL2RenderingContext, this.isWebGL2 || (this.vaoExt = t.getExtension("OES_vertex_array_object"));
172
+ }
173
+ /**
174
+ * Create a fullscreen quad geometry.
175
+ * This is the most common use case for shader effects.
176
+ */
177
+ static fullscreenQuad(t) {
178
+ const e = new p(t), r = new Float32Array([
179
+ // position texcoord
180
+ -1,
181
+ -1,
182
+ 0,
183
+ 0,
184
+ // bottom-left
185
+ 1,
186
+ -1,
187
+ 1,
188
+ 0,
189
+ // bottom-right
190
+ -1,
191
+ 1,
192
+ 0,
193
+ 1,
194
+ // top-left
195
+ 1,
196
+ 1,
197
+ 1,
198
+ 1
199
+ // top-right
200
+ ]), s = new Uint16Array([
201
+ 0,
202
+ 1,
203
+ 2,
204
+ // first triangle
205
+ 2,
206
+ 1,
207
+ 3
208
+ // second triangle
209
+ ]);
210
+ return e.setup(r, s), e;
211
+ }
212
+ /**
213
+ * Setup geometry from vertex and index data.
214
+ */
215
+ setup(t, e) {
216
+ const r = this.gl;
217
+ return this.createVAO(), this.bindVAO(), this.vbo = r.createBuffer(), r.bindBuffer(r.ARRAY_BUFFER, this.vbo), r.bufferData(r.ARRAY_BUFFER, t, r.STATIC_DRAW), this.vertexCount = t.length / 4, e && (this.ibo = r.createBuffer(), r.bindBuffer(r.ELEMENT_ARRAY_BUFFER, this.ibo), r.bufferData(r.ELEMENT_ARRAY_BUFFER, e, r.STATIC_DRAW), this.indexCount = e.length, this.useIndices = !0), this.unbindVAO(), this;
218
+ }
219
+ /**
220
+ * Link vertex attributes to shader program.
221
+ * Call this after compiling shaders.
222
+ */
223
+ linkAttributes(t) {
224
+ const e = this.gl;
225
+ this.bindVAO(), e.bindBuffer(e.ARRAY_BUFFER, this.vbo);
226
+ const r = 4 * Float32Array.BYTES_PER_ELEMENT, s = this.getPositionLocation(t);
227
+ s >= 0 && (e.enableVertexAttribArray(s), e.vertexAttribPointer(s, 2, e.FLOAT, !1, r, 0));
228
+ const n = this.getTexcoordLocation(t);
229
+ return n >= 0 && (e.enableVertexAttribArray(n), e.vertexAttribPointer(
230
+ n,
231
+ 2,
232
+ e.FLOAT,
233
+ !1,
234
+ r,
235
+ 2 * Float32Array.BYTES_PER_ELEMENT
236
+ )), this.useIndices && e.bindBuffer(e.ELEMENT_ARRAY_BUFFER, this.ibo), this.unbindVAO(), this;
237
+ }
238
+ /**
239
+ * Bind geometry for rendering.
240
+ */
241
+ bind() {
242
+ return this.bindVAO(), this;
243
+ }
244
+ /**
245
+ * Unbind geometry.
246
+ */
247
+ unbind() {
248
+ return this.unbindVAO(), this;
249
+ }
250
+ /**
251
+ * Draw the geometry.
252
+ */
253
+ draw() {
254
+ const t = this.gl;
255
+ return this.bindVAO(), this.useIndices ? t.drawElements(t.TRIANGLES, this.indexCount, t.UNSIGNED_SHORT, 0) : t.drawArrays(t.TRIANGLE_STRIP, 0, this.vertexCount), this;
256
+ }
257
+ /**
258
+ * Cleanup all GPU resources.
259
+ */
260
+ destroy() {
261
+ const t = this.gl;
262
+ this.deleteVAO(), this.vbo && (t.deleteBuffer(this.vbo), this.vbo = null), this.ibo && (t.deleteBuffer(this.ibo), this.ibo = null);
263
+ }
264
+ /**
265
+ * Get position attribute location.
266
+ * Tries common naming conventions.
267
+ */
268
+ getPositionLocation(t) {
269
+ let e = t.getAttribLocation("a_position");
270
+ return e >= 0 || (e = t.getAttribLocation("aPosition"), e >= 0) || (e = t.getAttribLocation("position"), e >= 0) ? e : -1;
271
+ }
272
+ /**
273
+ * Get texcoord attribute location.
274
+ * Tries common naming conventions.
275
+ */
276
+ getTexcoordLocation(t) {
277
+ let e = t.getAttribLocation("a_texcoord");
278
+ return e >= 0 || (e = t.getAttribLocation("aTexCoord"), e >= 0) || (e = t.getAttribLocation("texcoord"), e >= 0) || (e = t.getAttribLocation("a_uv"), e >= 0) ? e : -1;
279
+ }
280
+ // ============================================================================
281
+ // VAO helpers (handle WebGL1 vs WebGL2 differences)
282
+ // ============================================================================
283
+ createVAO() {
284
+ this.isWebGL2 ? this.vao = this.gl.createVertexArray() : this.vaoExt && (this.vao = this.vaoExt.createVertexArrayOES());
285
+ }
286
+ bindVAO() {
287
+ this.vao && (this.isWebGL2 ? this.gl.bindVertexArray(this.vao) : this.vaoExt && this.vaoExt.bindVertexArrayOES(this.vao));
288
+ }
289
+ unbindVAO() {
290
+ this.isWebGL2 ? this.gl.bindVertexArray(null) : this.vaoExt && this.vaoExt.bindVertexArrayOES(null);
291
+ }
292
+ deleteVAO() {
293
+ this.vao && (this.isWebGL2 ? this.gl.deleteVertexArray(this.vao) : this.vaoExt && this.vaoExt.deleteVertexArrayOES(this.vao), this.vao = null);
294
+ }
295
+ }
296
+ class h {
297
+ constructor(t) {
298
+ i(this, "gl");
299
+ i(this, "program", null);
300
+ i(this, "vertexShader", null);
301
+ i(this, "fragmentShader", null);
302
+ i(this, "version", 1);
303
+ this.gl = t;
304
+ }
305
+ /**
306
+ * Detect WebGL version from shader source.
307
+ * Looks for "#version 300 es" directive.
308
+ */
309
+ static detectVersion(t) {
310
+ return /^\s*#version\s+300\s+es/m.test(t) ? 2 : 1;
311
+ }
312
+ /**
313
+ * Compile shaders and link program.
314
+ * @throws ShaderCompilationError if compilation fails
315
+ * @throws ProgramLinkError if linking fails
316
+ */
317
+ compile(t, e) {
318
+ this.destroy();
319
+ const r = h.detectVersion(t), s = h.detectVersion(e);
320
+ if (r != s)
321
+ throw new k(r, s);
322
+ return this.version = Math.max(r, s), this.vertexShader = this.compileShader("vertex", t), this.fragmentShader = this.compileShader("fragment", e), this.linkProgram(), this;
323
+ }
324
+ /**
325
+ * Bind this program for rendering.
326
+ */
327
+ use() {
328
+ return this.program && this.gl.useProgram(this.program), this;
329
+ }
330
+ /**
331
+ * Get the compiled WebGL program.
332
+ */
333
+ getProgram() {
334
+ return this.program;
335
+ }
336
+ /**
337
+ * Get detected WebGL version.
338
+ */
339
+ getVersion() {
340
+ return this.version;
341
+ }
342
+ /**
343
+ * Get attribute location.
344
+ */
345
+ getAttribLocation(t) {
346
+ return this.program ? this.gl.getAttribLocation(this.program, t) : -1;
347
+ }
348
+ /**
349
+ * Get uniform location.
350
+ */
351
+ getUniformLocation(t) {
352
+ return this.program ? this.gl.getUniformLocation(this.program, t) : null;
353
+ }
354
+ /**
355
+ * Cleanup all GPU resources.
356
+ */
357
+ destroy() {
358
+ const t = this.gl;
359
+ this.program && (this.vertexShader && t.detachShader(this.program, this.vertexShader), this.fragmentShader && t.detachShader(this.program, this.fragmentShader), t.deleteProgram(this.program), this.program = null), this.vertexShader && (t.deleteShader(this.vertexShader), this.vertexShader = null), this.fragmentShader && (t.deleteShader(this.fragmentShader), this.fragmentShader = null);
360
+ }
361
+ /**
362
+ * Compile a single shader.
363
+ * @throws ShaderCompilationError if compilation fails
364
+ */
365
+ compileShader(t, e) {
366
+ const r = this.gl, s = t === "vertex" ? r.VERTEX_SHADER : r.FRAGMENT_SHADER, n = r.createShader(s);
367
+ if (!n)
368
+ throw new f(
369
+ t,
370
+ e,
371
+ "Failed to create shader object"
372
+ );
373
+ if (r.shaderSource(n, e), r.compileShader(n), !r.getShaderParameter(n, r.COMPILE_STATUS)) {
374
+ const c = r.getShaderInfoLog(n) || "Unknown error";
375
+ throw r.deleteShader(n), new f(t, e, c);
376
+ }
377
+ return n;
378
+ }
379
+ /**
380
+ * Link vertex and fragment shaders into a program.
381
+ * @throws ProgramLinkError if linking fails
382
+ */
383
+ linkProgram() {
384
+ const t = this.gl;
385
+ if (!this.vertexShader || !this.fragmentShader)
386
+ throw new m("Shaders not compiled");
387
+ const e = t.createProgram();
388
+ if (!e)
389
+ throw new m("Failed to create program object");
390
+ if (t.attachShader(e, this.vertexShader), t.attachShader(e, this.fragmentShader), t.linkProgram(e), !t.getProgramParameter(e, t.LINK_STATUS)) {
391
+ const s = t.getProgramInfoLog(e) || "Unknown error";
392
+ throw t.deleteProgram(e), new m(s);
393
+ }
394
+ this.program = e;
395
+ }
396
+ }
397
+ class x {
398
+ constructor(t, e) {
399
+ i(this, "name");
400
+ i(this, "method");
401
+ i(this, "isArray");
402
+ i(this, "isMatrix");
403
+ i(this, "location", null);
404
+ i(this, "locationResolved", !1);
405
+ i(this, "value");
406
+ this.name = t, this.value = e;
407
+ const r = x.inferMethodInfo(e);
408
+ this.method = r.method, this.isArray = r.isArray, this.isMatrix = r.isMatrix;
409
+ }
410
+ /**
411
+ * Infer WebGL method and metadata from value type.
412
+ */
413
+ static inferMethodInfo(t) {
414
+ if (typeof t == "boolean")
415
+ return { method: "uniform1i", isArray: !1, isMatrix: !1 };
416
+ if (typeof t == "number")
417
+ return { method: "uniform1f", isArray: !1, isMatrix: !1 };
418
+ if (!Array.isArray(t))
419
+ return { method: "uniform1f", isArray: !1, isMatrix: !1 };
420
+ const e = t.length, r = t[0];
421
+ if (Array.isArray(r))
422
+ switch (r.length) {
423
+ case 2:
424
+ return { method: "uniform2fv", isArray: !0, isMatrix: !1 };
425
+ case 3:
426
+ return { method: "uniform3fv", isArray: !0, isMatrix: !1 };
427
+ case 4:
428
+ return { method: "uniform4fv", isArray: !0, isMatrix: !1 };
429
+ default:
430
+ return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
431
+ }
432
+ switch (e) {
433
+ case 2:
434
+ return { method: "uniform2fv", isArray: !1, isMatrix: !1 };
435
+ case 3:
436
+ return { method: "uniform3fv", isArray: !1, isMatrix: !1 };
437
+ case 4:
438
+ return { method: "uniform4fv", isArray: !1, isMatrix: !1 };
439
+ case 9:
440
+ return { method: "uniformMatrix3fv", isArray: !1, isMatrix: !0 };
441
+ case 16:
442
+ return { method: "uniformMatrix4fv", isArray: !1, isMatrix: !0 };
443
+ default:
444
+ return { method: "uniform1fv", isArray: !0, isMatrix: !1 };
445
+ }
446
+ }
447
+ /**
448
+ * Resolve and cache uniform location from program.
449
+ * Returns null if uniform doesn't exist (optimized out by compiler, etc.)
450
+ */
451
+ resolveLocation(t, e) {
452
+ return this.locationResolved || (this.location = t.getUniformLocation(e, this.name), this.locationResolved = !0), this.location;
453
+ }
454
+ /**
455
+ * Invalidate cached location (call when program changes).
456
+ */
457
+ invalidateLocation() {
458
+ this.location = null, this.locationResolved = !1;
459
+ }
460
+ /**
461
+ * Update value (doesn't upload to GPU until upload() is called).
462
+ */
463
+ setValue(t) {
464
+ this.value = t;
465
+ }
466
+ /**
467
+ * Get current value.
468
+ */
469
+ getValue() {
470
+ return this.value;
471
+ }
472
+ /**
473
+ * Upload current value to GPU.
474
+ * @param gl - WebGL context
475
+ * @param program - Current WebGL program (for location resolution)
476
+ */
477
+ upload(t, e) {
478
+ const r = this.resolveLocation(t, e);
479
+ if (r === null)
480
+ return;
481
+ const s = this.value;
482
+ let n;
483
+ switch (typeof s == "boolean" ? n = s ? 1 : 0 : typeof s == "number" ? n = s : this.isArray && Array.isArray(s[0]) ? n = new Float32Array(
484
+ s.flat()
485
+ ) : n = new Float32Array(s), this.method) {
486
+ case "uniform1f":
487
+ t.uniform1f(r, n);
488
+ break;
489
+ case "uniform1i":
490
+ t.uniform1i(r, n);
491
+ break;
492
+ case "uniform1fv":
493
+ t.uniform1fv(r, n);
494
+ break;
495
+ case "uniform2fv":
496
+ t.uniform2fv(r, n);
497
+ break;
498
+ case "uniform3fv":
499
+ t.uniform3fv(r, n);
500
+ break;
501
+ case "uniform4fv":
502
+ t.uniform4fv(r, n);
503
+ break;
504
+ case "uniformMatrix2fv":
505
+ t.uniformMatrix2fv(r, !1, n);
506
+ break;
507
+ case "uniformMatrix3fv":
508
+ t.uniformMatrix3fv(r, !1, n);
509
+ break;
510
+ case "uniformMatrix4fv":
511
+ t.uniformMatrix4fv(r, !1, n);
512
+ break;
513
+ }
514
+ }
515
+ }
516
+ const d = class d {
517
+ constructor(t) {
518
+ i(this, "gl");
519
+ i(this, "program", null);
520
+ i(this, "uniforms", /* @__PURE__ */ new Map());
521
+ this.gl = t;
522
+ }
523
+ /**
524
+ * Attach to a WebGL program.
525
+ * Invalidates all cached locations since they're program-specific.
526
+ */
527
+ attachProgram(t) {
528
+ this.program = t;
529
+ for (const e of this.uniforms.values())
530
+ e.invalidateLocation();
531
+ return this;
532
+ }
533
+ /**
534
+ * Set a uniform value.
535
+ * Creates the uniform if it doesn't exist, updates if it does.
536
+ */
537
+ set(t, e) {
538
+ const r = this.uniforms.get(t);
539
+ return r ? r.setValue(e) : this.uniforms.set(t, new x(t, e)), this;
540
+ }
541
+ /**
542
+ * Set multiple uniforms at once.
543
+ */
544
+ setMany(t) {
545
+ for (const [e, r] of Object.entries(t))
546
+ this.set(e, r);
547
+ return this;
548
+ }
549
+ /**
550
+ * Get current uniform value.
551
+ */
552
+ get(t) {
553
+ var e;
554
+ return (e = this.uniforms.get(t)) == null ? void 0 : e.getValue();
555
+ }
556
+ /**
557
+ * Check if uniform exists.
558
+ */
559
+ has(t) {
560
+ return this.uniforms.has(t);
561
+ }
562
+ /**
563
+ * Remove a uniform.
564
+ */
565
+ delete(t) {
566
+ return this.uniforms.delete(t);
567
+ }
568
+ /**
569
+ * Upload all uniforms to GPU.
570
+ * Requires a program to be attached.
571
+ */
572
+ uploadAll() {
573
+ if (!this.program)
574
+ return this;
575
+ for (const t of this.uniforms.values())
576
+ t.upload(this.gl, this.program);
577
+ return this;
578
+ }
579
+ /**
580
+ * Upload only built-in uniforms (u_resolution, u_time, u_delta, u_mouse, u_frame).
581
+ * Call this every frame with current values.
582
+ */
583
+ uploadBuiltIns(t, e, r) {
584
+ if (this.set("u_resolution", e), this.set("u_time", t.time), this.set("u_delta", t.delta), this.set("u_mouse", r), this.set("u_frame", t.frame), !this.program)
585
+ return this;
586
+ for (const s of d.BUILT_INS) {
587
+ const n = this.uniforms.get(s);
588
+ n && n.upload(this.gl, this.program);
589
+ }
590
+ return this;
591
+ }
592
+ /**
593
+ * Clear all uniforms.
594
+ */
595
+ clear() {
596
+ this.uniforms.clear();
597
+ }
598
+ /**
599
+ * Cleanup.
600
+ */
601
+ destroy() {
602
+ this.uniforms.clear(), this.program = null;
603
+ }
604
+ /**
605
+ * Get all uniform names.
606
+ */
607
+ keys() {
608
+ return this.uniforms.keys();
609
+ }
610
+ /**
611
+ * Get uniform count.
612
+ */
613
+ get size() {
614
+ return this.uniforms.size;
615
+ }
616
+ };
617
+ /** Built-in uniform names that are handled automatically */
618
+ i(d, "BUILT_INS", /* @__PURE__ */ new Set([
619
+ "u_resolution",
620
+ "u_time",
621
+ "u_delta",
622
+ "u_mouse",
623
+ "u_frame"
624
+ ]));
625
+ let v = d;
626
+ class b {
627
+ constructor() {
628
+ i(this, "hooks", /* @__PURE__ */ new Map());
629
+ }
630
+ id() {
631
+ return Math.random().toString(36).substring(2, 10);
632
+ }
633
+ /** Add a new hook, returns a removal function */
634
+ add(t) {
635
+ const e = this.id();
636
+ return this.hooks.set(e, t), () => this.remove(e);
637
+ }
638
+ /** Remove a hook by its ID */
639
+ remove(t) {
640
+ this.hooks.delete(t);
641
+ }
642
+ /** Run all hooks with the given state */
643
+ run(t) {
644
+ for (const [e, r] of this.hooks)
645
+ r(t) === !1 && this.remove(e);
646
+ }
647
+ destroy() {
648
+ this.hooks.clear();
649
+ }
650
+ }
651
+ class _ {
652
+ constructor(t, e) {
653
+ i(this, "canvas");
654
+ i(this, "gl");
655
+ i(this, "options");
656
+ i(this, "onBeforeHooks", new b());
657
+ i(this, "onAfterHooks", new b());
658
+ i(this, "_program");
659
+ i(this, "_geometry");
660
+ i(this, "_uniforms");
661
+ i(this, "_clock");
662
+ i(this, "_resolution", [1, 1]);
663
+ i(this, "_mouse", [0, 0]);
664
+ i(this, "_version", 1);
665
+ i(this, "playing", !1);
666
+ this.canvas = t, this.options = e, this.gl = this.initContext(), this.enableExtensions(), this._program = new h(this.gl), this._geometry = p.fullscreenQuad(this.gl), this._uniforms = new v(this.gl), this._clock = new R(), this.options.onBeforeRender && this.onBeforeHooks.add(this.options.onBeforeRender), this.options.onAfterRender && this.onAfterHooks.add(this.options.onAfterRender), this.onRender = this.onRender.bind(this);
667
+ }
668
+ /**
669
+ * Factory method to create and setup WebGL instance.
670
+ */
671
+ static setup(t, e) {
672
+ const r = new _(t, e);
673
+ return e.vertex && e.fragment && r.shader(e.vertex, e.fragment), e.uniforms && r._uniforms.setMany(e.uniforms), r;
674
+ }
675
+ /**
676
+ * Initialize WebGL context.
677
+ * Tries WebGL2 first, falls back to WebGL1.
678
+ * Context errors are fatal but still reported via onError.
679
+ */
680
+ initContext() {
681
+ const t = {
682
+ antialias: this.options.antialias,
683
+ preserveDrawingBuffer: this.options.preserveDrawingBuffer,
684
+ alpha: !0,
685
+ depth: !1,
686
+ stencil: !1
687
+ }, e = this.canvas.getContext("webgl2", t);
688
+ if (e)
689
+ return this._version = 2, e;
690
+ const r = this.canvas.getContext("webgl", t);
691
+ if (r)
692
+ return this._version = 1, r;
693
+ const s = new w("not_supported");
694
+ throw this.options.onError(s), s;
695
+ }
696
+ /**
697
+ * Enable useful WebGL extensions.
698
+ */
699
+ enableExtensions() {
700
+ this.gl.getExtension("OES_standard_derivatives"), this.gl.getExtension("OES_texture_float"), this.gl.getExtension("OES_texture_float_linear"), this._version === 1 && this.gl.getExtension("OES_vertex_array_object");
701
+ }
702
+ /**
703
+ * Set viewport dimensions.
704
+ */
705
+ viewport(t, e, r, s) {
706
+ return this.canvas.width = r, this.canvas.height = s, this.gl.viewport(t, e, r, s), this._resolution = [r, s], this;
707
+ }
708
+ /**
709
+ * Set the clock time
710
+ */
711
+ clock(t) {
712
+ return this._clock.setTime(t), this;
713
+ }
714
+ /**
715
+ * Update mouse position.
716
+ */
717
+ mouse(t, e) {
718
+ return this._mouse = [t, e], this;
719
+ }
720
+ /**
721
+ * Set a uniform value.
722
+ */
723
+ uniform(t, e) {
724
+ return this._uniforms.set(t, e), this;
725
+ }
726
+ /**
727
+ * Set multiple uniforms.
728
+ */
729
+ uniforms(t) {
730
+ return this._uniforms.setMany(t), this;
731
+ }
732
+ /**
733
+ * Get current uniform value.
734
+ */
735
+ getUniform(t) {
736
+ return this._uniforms.get(t);
737
+ }
738
+ /**
739
+ * Compile and link shaders.
740
+ * Errors are handled via onError callback, never thrown.
741
+ */
742
+ shader(t, e) {
743
+ try {
744
+ this._program.compile(t, e), this._version = this._program.getVersion(), this._geometry.linkAttributes(this._program);
745
+ const r = this._program.getProgram();
746
+ r && this._uniforms.attachProgram(r);
747
+ } catch (r) {
748
+ r instanceof u && this.options.onError(r);
749
+ }
750
+ return this;
751
+ }
752
+ /**
753
+ * Start animation loop.
754
+ */
755
+ play() {
756
+ return this.playing ? this : (this.playing = !0, this._clock.start(this.onRender), this);
757
+ }
758
+ /**
759
+ * Stop animation loop.
760
+ */
761
+ pause() {
762
+ if (!this.playing) return this;
763
+ const t = this._clock.getState();
764
+ return this.onBeforeHooks.run(t), this.playing = !1, this._clock.stop(), this.onAfterHooks.run(t), this;
765
+ }
766
+ /**
767
+ * Render a single frame.
768
+ */
769
+ render() {
770
+ return this.onRender(this._clock.getState()), this;
771
+ }
772
+ /**
773
+ * Get WebGL context.
774
+ */
775
+ getContext() {
776
+ return this.gl;
777
+ }
778
+ /**
779
+ * Get detected WebGL version.
780
+ */
781
+ getVersion() {
782
+ return this._version;
783
+ }
784
+ /**
785
+ * Cleanup all resources.
786
+ */
787
+ destroy() {
788
+ this.pause(), this._clock.destroy(), this._geometry.destroy(), this._program.destroy(), this._uniforms.destroy(), this.onAfterHooks.destroy(), this.onBeforeHooks.destroy();
789
+ }
790
+ /**
791
+ * Internal render callback.
792
+ */
793
+ onRender(t) {
794
+ const e = this.gl;
795
+ this.onBeforeHooks.run(t), e.clearColor(0, 0, 0, 0), e.clear(e.COLOR_BUFFER_BIT), this._program.use(), this._uniforms.uploadBuiltIns(t, this._resolution, this._mouse), this._uniforms.uploadAll(), this._geometry.bind(), this._geometry.draw(), this.onAfterHooks.run(t);
796
+ }
797
+ }
798
+ const g = `#ifdef GL_ES
799
+ precision mediump float;
800
+ #endif
801
+
802
+ attribute vec2 a_position;
803
+ attribute vec2 a_texcoord;
804
+
805
+ varying vec2 v_texcoord;
806
+
807
+ void main() {
808
+ v_texcoord = a_texcoord;
809
+ gl_Position = vec4(a_position, 0.0, 1.0);
810
+ }
811
+ `, A = `#ifdef GL_ES
812
+ precision mediump float;
813
+ #endif
814
+
815
+ uniform vec2 u_resolution;
816
+ uniform float u_time;
817
+
818
+ varying vec2 v_texcoord;
819
+
820
+ void main() {
821
+ vec2 uv = v_texcoord;
822
+ vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
823
+ gl_FragColor = vec4(color, 1.0);
824
+ }
825
+ `, y = `#version 300 es
826
+
827
+ in vec2 a_position;
828
+ in vec2 a_texcoord;
829
+
830
+ out vec2 v_texcoord;
831
+
832
+ void main() {
833
+ v_texcoord = a_texcoord;
834
+ gl_Position = vec4(a_position, 0.0, 1.0);
835
+ }`, M = `#version 300 es
836
+ precision highp float;
837
+
838
+ uniform vec2 u_resolution;
839
+ uniform float u_time;
840
+ uniform vec2 u_mouse;
841
+
842
+ in vec2 v_texcoord;
843
+ out vec4 fragColor;
844
+
845
+ void main() {
846
+ vec2 uv = gl_FragCoord.xy / u_resolution;
847
+ vec3 color = vec3(uv.x, uv.y, 0.5 + 0.5 * sin(u_time));
848
+ fragColor = vec4(color, 1.0);
849
+ }`;
850
+ class E {
851
+ constructor(t, e) {
852
+ /** Active event listeners */
853
+ i(this, "listeners", []);
854
+ /** HTML canvas element */
855
+ i(this, "canvas");
856
+ /** Resolved options */
857
+ i(this, "options");
858
+ /** WebGL engine */
859
+ i(this, "engine");
860
+ this.canvas = t, this.options = this.resolveOptions(e), this.engine = _.setup(this.canvas, this.options), this.setupListeners(), this.setViewport(), this.options.onLoad(), this.options.autoplay && this.play();
861
+ }
862
+ /**
863
+ * Sandbox - A lightweight WebGL wrapper for shader effects.
864
+ *
865
+ * @example
866
+ * // Static rendering
867
+ * const sandbox = Sandbox.create(canvas, {
868
+ * fragment: myShader,
869
+ * autoplay: false,
870
+ * });
871
+ * sandbox.setUniforms({ u_time: 1.5 }).render();
872
+ *
873
+ * @example
874
+ * // Animation loop
875
+ * const sandbox = Sandbox.create(canvas, {
876
+ * fragment: myShader,
877
+ * autoplay: true,
878
+ * });
879
+ */
880
+ static create(t, e) {
881
+ return new E(t, e);
882
+ }
883
+ resolveOptions(t) {
884
+ const e = {
885
+ vertex: g,
886
+ fragment: A,
887
+ autoplay: !0,
888
+ pauseWhenHidden: !0,
889
+ dpr: "auto",
890
+ preserveDrawingBuffer: !1,
891
+ antialias: !0,
892
+ onError: (r) => {
893
+ console.error(
894
+ "Oops!",
895
+ r,
896
+ `
897
+ You can handle errors programmatically by providing an onError callback to suppress this log and implement custom fallback behavior.`
898
+ );
899
+ },
900
+ onLoad: () => {
901
+ },
902
+ onBeforeRender: null,
903
+ onAfterRender: null,
904
+ uniforms: {}
905
+ };
906
+ if (t != null && t.vertex && !(t != null && t.fragment)) {
907
+ const r = h.detectVersion(t.vertex);
908
+ e.vertex = t.vertex, e.fragment = r === 2 ? M : A;
909
+ }
910
+ if (t != null && t.fragment && !(t != null && t.vertex)) {
911
+ const r = h.detectVersion(t.fragment);
912
+ e.fragment = t.fragment, e.vertex = r === 2 ? y : g;
913
+ }
914
+ return { ...e, ...t };
915
+ }
916
+ setupListeners() {
917
+ this.listeners.push(
918
+ // Window resize
919
+ l.on(window, "resize", () => {
920
+ this.setViewport();
921
+ }),
922
+ // Canvas resize
923
+ l.on(this.canvas, "resize", () => {
924
+ this.setViewport();
925
+ }),
926
+ // Visibility check on scroll
927
+ l.on(document, "scroll", () => {
928
+ this.options.pauseWhenHidden && (this.isInViewport() ? this.play() : this.pause());
929
+ }),
930
+ // Mouse tracking
931
+ l.on(document, "mousemove", (t) => {
932
+ this.setMouse(t.clientX || t.pageX, t.clientY || t.pageY);
933
+ }),
934
+ // Touch tracking
935
+ l.on(document, "touchmove", (t) => {
936
+ t.touches.length > 0 && this.setMouse(t.touches[0].clientX, t.touches[0].clientY);
937
+ })
938
+ );
939
+ }
940
+ destroyListeners() {
941
+ this.listeners.forEach((t) => t()), this.listeners = [];
942
+ }
943
+ setViewport() {
944
+ const t = this.options.dpr === "auto" ? Math.min(2, window.devicePixelRatio || 1) : this.options.dpr, e = this.canvas.clientWidth || this.canvas.width || 1, r = this.canvas.clientHeight || this.canvas.height || 1;
945
+ this.engine.viewport(
946
+ 0,
947
+ 0,
948
+ Math.max(1, Math.floor(e * t)),
949
+ Math.max(1, Math.floor(r * t))
950
+ );
951
+ }
952
+ isInViewport() {
953
+ const t = this.canvas.getBoundingClientRect();
954
+ return t.bottom >= 0 && t.right >= 0 && t.top <= (window.innerHeight || document.documentElement.clientHeight) && t.left <= (window.innerWidth || document.documentElement.clientWidth);
955
+ }
956
+ setMouse(t, e) {
957
+ const r = this.canvas.getBoundingClientRect();
958
+ t >= r.left && t <= r.right && e >= r.top && e <= r.bottom && this.engine.mouse(t - r.left, e - r.top);
959
+ }
960
+ /**
961
+ * Set a single uniform value with type checking.
962
+ * @example
963
+ * sandbox.setUniform<Vec3>("u_color", [1, 0, 0]);
964
+ * sandbox.setUniform<number>("u_time", 1.5);
965
+ * sandbox.setUniform<Vec3[]>("u_colors", [[1, 0, 0], [0, 1, 0]]);
966
+ */
967
+ setUniform(t, e) {
968
+ return this.engine.uniform(t, e), this;
969
+ }
970
+ /**
971
+ * Set multiple uniforms at once with type checking.
972
+ * @example
973
+ * interface MyUniforms extends UniformSchema {
974
+ * u_time: number;
975
+ * u_resolution: Vec2;
976
+ * u_colors: Vec3[];
977
+ * }
978
+ * sandbox.setUniforms<MyUniforms>({
979
+ * u_time: 1.5,
980
+ * u_resolution: [800, 600],
981
+ * u_colors: [[1, 0, 0], [0, 1, 0]],
982
+ * });
983
+ */
984
+ setUniforms(t) {
985
+ return this.engine.uniforms(t), this;
986
+ }
987
+ /**
988
+ * Get current uniform value.
989
+ */
990
+ getUniform(t) {
991
+ return this.engine.getUniform(t);
992
+ }
993
+ /**
994
+ * Update shaders.
995
+ * @example
996
+ * sandbox.setShader(vertexSource, fragmentSource);
997
+ */
998
+ setShader(t, e) {
999
+ return this.engine.shader(t, e), this;
1000
+ }
1001
+ /**
1002
+ * Update only fragment shader (uses default vertex).
1003
+ * @example
1004
+ * sandbox.setFragment(fragmentSource);
1005
+ */
1006
+ setFragment(t) {
1007
+ const r = this.webglVersion() === 1 ? g : y;
1008
+ return this.engine.shader(r, t), this;
1009
+ }
1010
+ /**
1011
+ * Add a runtime render hook.
1012
+ */
1013
+ hook(t, e = "before") {
1014
+ return e === "before" ? this.engine.onBeforeHooks.add(t) : this.engine.onAfterHooks.add(t);
1015
+ }
1016
+ /**
1017
+ * Start animation loop.
1018
+ */
1019
+ play() {
1020
+ return this.engine.play(), this;
1021
+ }
1022
+ /**
1023
+ * Start animation loop at specific time (in seconds).
1024
+ */
1025
+ playAt(t) {
1026
+ return this.engine.clock(t), this.engine.play(), this;
1027
+ }
1028
+ /**
1029
+ * Stop animation loop.
1030
+ */
1031
+ pause() {
1032
+ return this.engine.pause(), this;
1033
+ }
1034
+ /**
1035
+ * Pause animation loop at specific time (in seconds).
1036
+ */
1037
+ pauseAt(t) {
1038
+ const e = this.hook((r) => {
1039
+ r.time >= t && (e(), this.pause());
1040
+ }, "after");
1041
+ return this;
1042
+ }
1043
+ /**
1044
+ * Toggle play/pause state.
1045
+ */
1046
+ toggle() {
1047
+ return this.engine.playing ? this.pause() : this.play(), this;
1048
+ }
1049
+ /**
1050
+ * Set current time (in seconds).
1051
+ */
1052
+ time(t) {
1053
+ return this.engine.clock(t), this;
1054
+ }
1055
+ /**
1056
+ * Render a single frame (for static rendering).
1057
+ * @example
1058
+ * sandbox.time(1.4).render();
1059
+ */
1060
+ render() {
1061
+ return this.engine.render(), this;
1062
+ }
1063
+ /**
1064
+ * Render at specific time (for deterministic output).
1065
+ * @example
1066
+ * sandbox.renderAt(2.5); // Render as if 2.5 seconds elapsed
1067
+ */
1068
+ renderAt(t) {
1069
+ return this.engine.clock(t), this.engine.render(), this;
1070
+ }
1071
+ /**
1072
+ * Check if currently playing.
1073
+ */
1074
+ isPlaying() {
1075
+ return this.engine.playing;
1076
+ }
1077
+ /**
1078
+ * Get WebGL version using (1 or 2).
1079
+ */
1080
+ webglVersion() {
1081
+ return this.engine.getVersion();
1082
+ }
1083
+ /**
1084
+ * Get canvas element.
1085
+ */
1086
+ canvasElement() {
1087
+ return this.canvas;
1088
+ }
1089
+ /**
1090
+ * Destroy sandbox and release all resources.
1091
+ * @example
1092
+ * onUnmounted(() => {
1093
+ * sandbox.destroy();
1094
+ * });
1095
+ */
1096
+ destroy() {
1097
+ this.destroyListeners(), this.engine.destroy();
1098
+ }
1099
+ }
1100
+ export {
1101
+ E as Sandbox,
1102
+ w as SandboxContextError,
1103
+ u as SandboxError,
1104
+ m as SandboxProgramError,
1105
+ f as SandboxShaderCompilationError,
1106
+ k as SandboxShaderVersionMismatchError
1107
+ };