@cruxgarden/plasma-ui 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // src/PlasmaProvider.tsx
2
- import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
2
+ import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
3
 
4
4
  // src/shaders.ts
5
5
  var DEFAULT_MAX_SHAPES = 16;
@@ -178,7 +178,7 @@ void main(){
178
178
  const compFrag = `#version 300 es
179
179
  ${common}
180
180
  uniform sampler2D uH, uS, uTint, uBg, uBgM, uBgH, uFrost;
181
- uniform float uRefract, uDisp, uRim, uRimMode, uRimWidth, uSpec, uHair;
181
+ uniform float uRefract, uDisp, uRim, uRimMode, uRimWidth, uSpec, uHair, uShim, uGlow, uWash, uGrain;
182
182
  uniform vec3 uRimColor;
183
183
  out vec4 o;
184
184
 
@@ -235,7 +235,7 @@ void main(){
235
235
  // Palette phases run on viewport position, like the grain: a frame drawn for
236
236
  // a taller region must colour its viewport band exactly as the live frame does.
237
237
  vec2 vp = p - uView.xy;
238
- col += pal(uTime*.03 + vp.x/1400.) * .06 * smoothstep(.0, .45, hHere) * (1.+uEnergy);
238
+ col += pal(uTime*.03 + vp.x/1400.) * .06 * smoothstep(.0, .45, hHere) * (1.+uEnergy) * uGlow;
239
239
 
240
240
  if(sd > -2.){
241
241
  vec2 th = 2. / vec2(textureSize(uH, 0));
@@ -255,7 +255,12 @@ void main(){
255
255
  seen(p + off, fr).g,
256
256
  seen(p + off*(1.-disp), fr).b
257
257
  );
258
+ // uWash 0 hands the background through untouched - glass with no cast of
259
+ // its own. 1 is the original desaturate-and-lift that gives the material
260
+ // its body.
261
+ vec3 clearRefr = refr;
258
262
  refr = mix(refr, vec3(dot(refr, vec3(.333))), .18) * mix(1.08, .9, uLight) + .03*(1.-uLight);
263
+ refr = mix(clearRefr, refr, uWash);
259
264
  // frosted: milkier and a little brighter
260
265
  refr = mix(refr, mix(refr, vec3(dot(refr, vec3(.333))), .25) * mix(1.12, .97, uLight) + mix(.05, .03, uLight), fr);
261
266
  float hl = 1. - .55*uLight;
@@ -280,7 +285,7 @@ void main(){
280
285
  plasma += rimCol * fres * .45 * hl * uRim;
281
286
  plasma += vec3(1.) * spec * .75 * hl * uSpec;
282
287
  // faint shimmer across the body; fades out as the tint becomes opaque
283
- plasma += pal(uTime*.04 + vp.y/900. + uEnergy*.3) * .05 * lift * (1.+uEnergy*2.) * hl * (1. - talpha);
288
+ plasma += pal(uTime*.04 + vp.y/900. + uEnergy*.3) * .05 * lift * (1.+uEnergy*2.) * hl * (1. - talpha) * uShim;
284
289
  vec3 hairCol = uRimMode > .5 ? mix(vec3(1.), rimCol / 1.4, .6) : vec3(.9,.95,1.);
285
290
  plasma += hairCol * (1.-smoothstep(0., 1.6, abs(sd - .7))) * .4 * hl * uHair;
286
291
 
@@ -290,7 +295,7 @@ void main(){
290
295
  }
291
296
  // Keyed on viewport position, so a frame drawn for a taller region has the
292
297
  // same grain in its viewport band as the live frame that replaces it.
293
- col += (hash(p - uView.xy + uTime) - .5) * .025 * grain;
298
+ col += (hash(p - uView.xy + uTime) - .5) * .025 * grain * uGrain;
294
299
  o = vec4(col, 1.);
295
300
  }`;
296
301
  return { maskFrag, tintFrag, blurFrag, bgFrag, compFrag };
@@ -361,7 +366,11 @@ var UNIFORMS = [
361
366
  "uRimColor",
362
367
  "uRimWidth",
363
368
  "uSpec",
364
- "uHair"
369
+ "uHair",
370
+ "uShim",
371
+ "uGlow",
372
+ "uWash",
373
+ "uGrain"
365
374
  ];
366
375
  var MASK_SCALE = 0.5;
367
376
  var MAX_PIXELS = 26e5;
@@ -419,13 +428,25 @@ var _PlasmaRenderer = class _PlasmaRenderer {
419
428
  this.imgSrc = null;
420
429
  this.bgColor = null;
421
430
  this.srcEl = null;
431
+ // re-uploaded each frame
432
+ this.floatOK = false;
433
+ /** True between webglcontextlost and webglcontextrestored: draw nothing. */
434
+ this.lost = false;
435
+ this.warnedOverflow = false;
436
+ /**
437
+ * Set by destroy(). The canvas and its context outlive this renderer, so an
438
+ * async callback that lands afterwards would happily allocate on a context
439
+ * nothing can free it from.
440
+ */
441
+ this.destroyed = false;
442
+ /** Everything initGL() created, so destroy() can free it by hand. */
443
+ this.owned = { tex: [], fb: [], prog: [], buf: [] };
422
444
  this.recs = /* @__PURE__ */ new Map();
423
445
  this.nextId = 1;
424
446
  this.raf = 0;
425
447
  this.last = 0;
426
448
  this.time = 0;
427
449
  this.dpr = 1;
428
- this.resizeQueued = false;
429
450
  /** Coarse pointer = touch. Only there does a fling run on the compositor with rAF deferred. */
430
451
  this.coarse = typeof matchMedia === "function" && matchMedia("(pointer: coarse)").matches;
431
452
  this.frozen = false;
@@ -450,6 +471,31 @@ var _PlasmaRenderer = class _PlasmaRenderer {
450
471
  this.lightQuery = typeof matchMedia !== "undefined" ? matchMedia("(prefers-color-scheme: light)") : null;
451
472
  this.tintCache = /* @__PURE__ */ new Map();
452
473
  this.RP = new Float32Array(MAX_PULSES * 4);
474
+ /**
475
+ * A drag-resize reallocates eight render targets per distinct size, and the
476
+ * driver can drop the context under that. Without these two handlers the
477
+ * field simply stopped: the frame loop kept running against a dead context
478
+ * and nothing ever brought it back. preventDefault is what asks the browser
479
+ * to attempt a restore at all.
480
+ */
481
+ this.onContextLost = (e) => {
482
+ e.preventDefault();
483
+ this.lost = true;
484
+ cancelAnimationFrame(this.raf);
485
+ this.raf = 0;
486
+ };
487
+ this.onContextRestored = () => {
488
+ this.lost = false;
489
+ this.initGL();
490
+ this.last = 0;
491
+ if (this.frozen) {
492
+ clearTimeout(this.scrollIdle);
493
+ this.thaw();
494
+ return;
495
+ }
496
+ this.applyResize();
497
+ if (!document.hidden && !this.raf) this.raf = requestAnimationFrame(this.frame);
498
+ };
453
499
  /**
454
500
  * A hidden tab draws nothing anyone can see. Browsers throttle rAF there
455
501
  * but do not all stop it, so the loop stops itself and picks up where it
@@ -511,12 +557,8 @@ var _PlasmaRenderer = class _PlasmaRenderer {
511
557
  * otherwise reallocated every texture repeatedly mid-scroll.
512
558
  */
513
559
  this.resize = () => {
514
- if (this.resizeQueued) return;
515
- this.resizeQueued = true;
516
- requestAnimationFrame(() => {
517
- this.resizeQueued = false;
518
- this.applyResize();
519
- });
560
+ clearTimeout(this.resizeSettle);
561
+ this.resizeSettle = setTimeout(this.applyResize, 120);
520
562
  };
521
563
  this.applyResize = () => {
522
564
  if (this.frozen) {
@@ -559,6 +601,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
559
601
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
560
602
  };
561
603
  this.frame = (now2) => {
604
+ if (this.lost) {
605
+ this.raf = 0;
606
+ return;
607
+ }
562
608
  if (!this.oneShot) this.raf = requestAnimationFrame(this.frame);
563
609
  const dt = this.last ? Math.min((now2 - this.last) / 1e3, 0.05) : 0.016;
564
610
  this.last = now2;
@@ -679,6 +725,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
679
725
  r.drawn = { l, t, w: rgt - l, h: btm - t };
680
726
  });
681
727
  this.drawnScroll = [scrollX, scrollY];
728
+ if (list.length > this.max && !this.warnedOverflow) {
729
+ this.warnedOverflow = true;
730
+ console.warn(`[plasma-ui] ${list.length} plasma surfaces are on screen but maxSurfaces is ${this.max}; the rest are not drawn. Raise maxSurfaces on <PlasmaProvider>.`);
731
+ }
682
732
  this.lastList = list.slice(0, this.max);
683
733
  this.draw(this.lastList);
684
734
  };
@@ -689,33 +739,12 @@ var _PlasmaRenderer = class _PlasmaRenderer {
689
739
  this.gl = gl;
690
740
  this.settings = settings;
691
741
  this.max = Math.max(1, Math.round(settings.maxSurfaces || DEFAULT_MAX_SHAPES));
692
- this.P = new Float32Array(this.max * 4);
693
- this.R = new Float32Array(this.max * 4);
694
- this.F = new Float32Array(this.max);
695
- this.T = new Float32Array(this.max * 4);
696
- this.FR = new Float32Array(this.max);
697
- this.EL = new Float32Array(this.max);
698
- this.SOLO = new Float32Array(this.max);
699
- this.floatOK = !!gl.getExtension("EXT_color_buffer_float");
700
- const sh = makeShaders(this.max);
701
- this.progs = { bg: this.program(sh.bgFrag), mask: this.program(sh.maskFrag), tint: this.program(sh.tintFrag), blur: this.program(sh.blurFrag), comp: this.program(sh.compFrag) };
702
- const buf = gl.createBuffer();
703
- gl.bindBuffer(gl.ARRAY_BUFFER, buf);
704
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
705
- gl.enableVertexAttribArray(0);
706
- gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
707
- this.rtA = this.target();
708
- this.rtB = this.target();
709
- this.rtC = this.target();
710
- this.rtT = this.target();
711
- this.rtFr = this.target();
712
- this.rtBg = this.target();
713
- this.rtBgM = this.target();
714
- this.rtBgH = this.target();
715
- this.mrtFb = gl.createFramebuffer();
716
- this.configure(settings, true);
742
+ this.allocUniformArrays();
743
+ this.initGL();
717
744
  this.mouse.x = this.mouse.tx = innerWidth / 2;
718
745
  this.mouse.y = this.mouse.ty = innerHeight / 2;
746
+ canvas.addEventListener("webglcontextlost", this.onContextLost);
747
+ canvas.addEventListener("webglcontextrestored", this.onContextRestored);
719
748
  addEventListener("resize", this.resize);
720
749
  document.addEventListener("visibilitychange", this.onVisibility);
721
750
  if (this.coarse) addEventListener("scroll", this.onScroll, { passive: true });
@@ -761,7 +790,7 @@ var _PlasmaRenderer = class _PlasmaRenderer {
761
790
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
762
791
  this.imgTex = tex;
763
792
  const up = (source, w, h) => {
764
- if (!w || !h) return;
793
+ if (this.destroyed || !w || !h) return;
765
794
  gl.bindTexture(gl.TEXTURE_2D, this.imgTex);
766
795
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
767
796
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, source);
@@ -776,7 +805,7 @@ var _PlasmaRenderer = class _PlasmaRenderer {
776
805
  const img2 = src;
777
806
  if (img2.complete && img2.naturalWidth) up(img2, img2.naturalWidth, img2.naturalHeight);
778
807
  else img2.addEventListener("load", () => {
779
- if (this.imgSrc === src) up(img2, img2.naturalWidth, img2.naturalHeight);
808
+ if (!this.destroyed && this.imgSrc === src) up(img2, img2.naturalWidth, img2.naturalHeight);
780
809
  }, { once: true });
781
810
  }
782
811
  return;
@@ -790,7 +819,7 @@ var _PlasmaRenderer = class _PlasmaRenderer {
790
819
  const img = new Image();
791
820
  img.crossOrigin = "anonymous";
792
821
  img.onload = () => {
793
- if (this.imgSrc !== src) return;
822
+ if (this.destroyed || this.imgSrc !== src) return;
794
823
  const tex = gl.createTexture();
795
824
  gl.bindTexture(gl.TEXTURE_2D, tex);
796
825
  gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
@@ -808,7 +837,12 @@ var _PlasmaRenderer = class _PlasmaRenderer {
808
837
  }
809
838
  configure(s, immediate = false) {
810
839
  const qualityChanged = s.quality !== this.settings.quality;
840
+ const max = Math.max(1, Math.round(s.maxSurfaces || DEFAULT_MAX_SHAPES));
811
841
  this.settings = s;
842
+ if (max !== this.max) {
843
+ this.setMax(max);
844
+ return;
845
+ }
812
846
  this.loadBackground(s.background ?? null);
813
847
  this.colorTarget = s.colors.map(hexToRgb);
814
848
  if (immediate) {
@@ -862,12 +896,17 @@ var _PlasmaRenderer = class _PlasmaRenderer {
862
896
  }
863
897
  };
864
898
  }
865
- /** Layout boxes of all shapes (lean removed; animating draggables report their destination). */
866
- layoutBoxes(excludeId, fusingOnly = false) {
899
+ /**
900
+ * Layout boxes of all shapes (lean removed; animating draggables report
901
+ * their destination). Pass `group` to see only the surfaces in that group;
902
+ * omit it - as any caller written before groups existed does - to see them all.
903
+ */
904
+ layoutBoxes(excludeId, fusingOnly = false, group) {
867
905
  const out = [];
868
906
  this.recs.forEach((r) => {
869
907
  if (r.id === excludeId) return;
870
908
  if (fusingOnly && r.fuse === false) return;
909
+ if (group !== void 0 && (r.group ?? null) !== group) return;
871
910
  out.push(this.layoutBoxOf(r));
872
911
  });
873
912
  return out;
@@ -893,8 +932,78 @@ var _PlasmaRenderer = class _PlasmaRenderer {
893
932
  bump(e) {
894
933
  this.energy = Math.max(this.energy, Math.min(e, 1));
895
934
  }
935
+ /** Rebuild the shaders and uniform arrays for a new surface budget. */
936
+ setMax(max) {
937
+ this.max = max;
938
+ this.allocUniformArrays();
939
+ this.releaseGL();
940
+ this.initGL();
941
+ this.applyResize();
942
+ this.warnedOverflow = false;
943
+ }
944
+ allocUniformArrays() {
945
+ this.P = new Float32Array(this.max * 4);
946
+ this.R = new Float32Array(this.max * 4);
947
+ this.F = new Float32Array(this.max);
948
+ this.T = new Float32Array(this.max * 4);
949
+ this.FR = new Float32Array(this.max);
950
+ this.EL = new Float32Array(this.max);
951
+ this.SOLO = new Float32Array(this.max);
952
+ }
953
+ /** Delete every GL object this renderer created, leaving the canvas usable. */
954
+ releaseGL() {
955
+ const gl = this.gl;
956
+ if (!gl.isContextLost()) {
957
+ this.owned.tex.forEach((t) => gl.deleteTexture(t));
958
+ this.owned.fb.forEach((f) => gl.deleteFramebuffer(f));
959
+ this.owned.prog.forEach((pr) => gl.deleteProgram(pr));
960
+ this.owned.buf.forEach((b) => gl.deleteBuffer(b));
961
+ if (this.imgTex) gl.deleteTexture(this.imgTex);
962
+ }
963
+ this.imgTex = null;
964
+ this.imgSrc = null;
965
+ this.srcEl = null;
966
+ this.owned = { tex: [], fb: [], prog: [], buf: [] };
967
+ }
968
+ /**
969
+ * Every GL object this renderer owns. A lost context invalidates all of
970
+ * them, so creation lives here rather than in the constructor: the restore
971
+ * handler runs exactly the same path.
972
+ */
973
+ initGL() {
974
+ const gl = this.gl;
975
+ this.owned = { tex: [], fb: [], prog: [], buf: [] };
976
+ this.imgTex = null;
977
+ this.imgSrc = null;
978
+ this.srcEl = null;
979
+ this.floatOK = !!gl.getExtension("EXT_color_buffer_float");
980
+ const sh = makeShaders(this.max);
981
+ this.progs = { bg: this.program(sh.bgFrag), mask: this.program(sh.maskFrag), tint: this.program(sh.tintFrag), blur: this.program(sh.blurFrag), comp: this.program(sh.compFrag) };
982
+ const buf = gl.createBuffer();
983
+ if (buf) this.owned.buf.push(buf);
984
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
985
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
986
+ gl.enableVertexAttribArray(0);
987
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
988
+ this.rtA = this.target();
989
+ this.rtB = this.target();
990
+ this.rtC = this.target();
991
+ this.rtT = this.target();
992
+ this.rtFr = this.target();
993
+ this.rtBg = this.target();
994
+ this.rtBgM = this.target();
995
+ this.rtBgH = this.target();
996
+ this.mrtFb = gl.createFramebuffer();
997
+ this.owned.fb.push(this.mrtFb);
998
+ this.configure(this.settings, true);
999
+ this.canvas.width = this.canvas.height = 0;
1000
+ }
896
1001
  destroy() {
1002
+ this.destroyed = true;
897
1003
  cancelAnimationFrame(this.raf);
1004
+ clearTimeout(this.resizeSettle);
1005
+ this.canvas.removeEventListener("webglcontextlost", this.onContextLost);
1006
+ this.canvas.removeEventListener("webglcontextrestored", this.onContextRestored);
898
1007
  removeEventListener("resize", this.resize);
899
1008
  document.removeEventListener("visibilitychange", this.onVisibility);
900
1009
  removeEventListener("scroll", this.onScroll);
@@ -906,7 +1015,7 @@ var _PlasmaRenderer = class _PlasmaRenderer {
906
1015
  r.el.style.scale = "";
907
1016
  });
908
1017
  this.recs.clear();
909
- this.gl.getExtension("WEBGL_lose_context")?.loseContext();
1018
+ this.releaseGL();
910
1019
  }
911
1020
  freeze() {
912
1021
  const c = this.canvas;
@@ -1024,6 +1133,18 @@ var _PlasmaRenderer = class _PlasmaRenderer {
1024
1133
  gl.useProgram(bl.pr);
1025
1134
  gl.uniform1i(bl.u.uTex, 0);
1026
1135
  gl.activeTexture(gl.TEXTURE0);
1136
+ const bgBlur = Math.min(40, Math.max(0, s.backgroundBlur || 0));
1137
+ if (bgBlur > 0) {
1138
+ const step = bgBlur * this.dpr * MASK_SCALE / 4;
1139
+ pass(this.rtBg, this.rtBgM, 0, 0);
1140
+ for (let i = 0; i < 3; i++) {
1141
+ pass(this.rtBgM, this.rtB, step, 0);
1142
+ pass(this.rtB, this.rtBgM, 0, step);
1143
+ }
1144
+ gl.viewport(0, 0, this.rtBg.w, this.rtBg.h);
1145
+ pass(this.rtBgM, this.rtBg, 0, 0);
1146
+ gl.viewport(0, 0, this.rtA.w, this.rtA.h);
1147
+ }
1027
1148
  const n = list.length;
1028
1149
  let hasFrost = false, hasTint = false, hasElev = false;
1029
1150
  for (let i = 0; i < n; i++) {
@@ -1090,6 +1211,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
1090
1211
  gl.uniform1f(c.u.uRimWidth, s.rimWidth);
1091
1212
  gl.uniform1f(c.u.uSpec, s.highlight);
1092
1213
  gl.uniform1f(c.u.uHair, s.edgeLine);
1214
+ gl.uniform1f(c.u.uShim, s.shimmer);
1215
+ gl.uniform1f(c.u.uGlow, s.glow);
1216
+ gl.uniform1f(c.u.uWash, s.wash);
1217
+ gl.uniform1f(c.u.uGrain, s.grain);
1093
1218
  gl.activeTexture(gl.TEXTURE0);
1094
1219
  gl.bindTexture(gl.TEXTURE_2D, this.rtC.tex);
1095
1220
  gl.activeTexture(gl.TEXTURE1);
@@ -1136,19 +1261,45 @@ var _PlasmaRenderer = class _PlasmaRenderer {
1136
1261
  const sh = gl.createShader(type);
1137
1262
  gl.shaderSource(sh, src);
1138
1263
  gl.compileShader(sh);
1139
- if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh) || "shader compile failed");
1264
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
1265
+ const log = gl.getShaderInfoLog(sh);
1266
+ gl.deleteShader(sh);
1267
+ throw new Error(log || "shader compile failed");
1268
+ }
1140
1269
  return sh;
1141
1270
  };
1142
1271
  const pr = gl.createProgram();
1143
- gl.attachShader(pr, compile(gl.VERTEX_SHADER, vert));
1144
- gl.attachShader(pr, compile(gl.FRAGMENT_SHADER, fsrc));
1145
- gl.bindAttribLocation(pr, 0, "p");
1146
- gl.linkProgram(pr);
1147
- if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr) || "program link failed");
1272
+ const attached = [];
1273
+ try {
1274
+ const vs = compile(gl.VERTEX_SHADER, vert);
1275
+ let fs;
1276
+ try {
1277
+ fs = compile(gl.FRAGMENT_SHADER, fsrc);
1278
+ } catch (e) {
1279
+ gl.deleteShader(vs);
1280
+ throw e;
1281
+ }
1282
+ gl.attachShader(pr, vs);
1283
+ attached.push(vs);
1284
+ gl.attachShader(pr, fs);
1285
+ attached.push(fs);
1286
+ gl.bindAttribLocation(pr, 0, "p");
1287
+ gl.linkProgram(pr);
1288
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr) || "program link failed");
1289
+ } catch (e) {
1290
+ gl.deleteProgram(pr);
1291
+ throw e;
1292
+ } finally {
1293
+ attached.forEach((sh) => {
1294
+ gl.detachShader(pr, sh);
1295
+ gl.deleteShader(sh);
1296
+ });
1297
+ }
1148
1298
  const u = {};
1149
1299
  UNIFORMS.forEach((n) => {
1150
1300
  u[n] = gl.getUniformLocation(pr, n);
1151
1301
  });
1302
+ this.owned.prog.push(pr);
1152
1303
  return { pr, u };
1153
1304
  }
1154
1305
  target() {
@@ -1159,7 +1310,10 @@ var _PlasmaRenderer = class _PlasmaRenderer {
1159
1310
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1160
1311
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1161
1312
  gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1162
- return { tex, fb: gl.createFramebuffer(), w: 0, h: 0 };
1313
+ const fb = gl.createFramebuffer();
1314
+ this.owned.tex.push(tex);
1315
+ this.owned.fb.push(fb);
1316
+ return { tex, fb, w: 0, h: 0 };
1163
1317
  }
1164
1318
  };
1165
1319
  _PlasmaRenderer.RUNWAY = 1;
@@ -1169,21 +1323,35 @@ var PlasmaRenderer = _PlasmaRenderer;
1169
1323
  import { jsx, jsxs } from "react/jsx-runtime";
1170
1324
  var noop = () => {
1171
1325
  };
1172
- var PlasmaContext = createContext({
1173
- renderer: null,
1326
+ var DEFAULT_RUNTIME = { renderer: null, supported: false, reducedMotion: false, pulse: noop, bump: noop };
1327
+ var DEFAULT_DEFAULTS = {
1174
1328
  tint: "#ffffff",
1175
1329
  opacity: 0,
1176
1330
  frost: 0,
1177
1331
  radius: 26,
1178
- supported: false,
1179
1332
  grid: 24,
1180
1333
  magnet: 40,
1181
- spring: { stiffness: 170, damping: 16 },
1182
- reducedMotion: false,
1183
- pulse: noop,
1184
- bump: noop
1185
- });
1186
- var usePlasma = () => useContext(PlasmaContext);
1334
+ spring: { stiffness: 170, damping: 16 }
1335
+ };
1336
+ var RuntimeContext = createContext(DEFAULT_RUNTIME);
1337
+ var DefaultsContext = createContext(DEFAULT_DEFAULTS);
1338
+ var AttachContext = createContext(null);
1339
+ var usePlasmaRuntime = () => useContext(RuntimeContext);
1340
+ var usePlasmaDefaults = () => useContext(DefaultsContext);
1341
+ function usePlasma() {
1342
+ const runtime = usePlasmaRuntime();
1343
+ const defaults = usePlasmaDefaults();
1344
+ return useMemo(() => ({ ...runtime, ...defaults }), [runtime, defaults]);
1345
+ }
1346
+ var DEV = typeof process !== "undefined" && true;
1347
+ var useIsoLayoutEffect = typeof window !== "undefined" ? useLayoutEffect : useEffect;
1348
+ function useLatest(value) {
1349
+ const ref = useRef(value);
1350
+ useIsoLayoutEffect(() => {
1351
+ ref.current = value;
1352
+ });
1353
+ return ref;
1354
+ }
1187
1355
  var FALLBACK_CSS = `
1188
1356
  .plasma-panel{box-sizing:border-box}
1189
1357
  .plasma-fallback{
@@ -1206,6 +1374,31 @@ function useReducedMotion() {
1206
1374
  }, []);
1207
1375
  return reduced;
1208
1376
  }
1377
+ function PlasmaCanvas({ className, style, zIndex = -1 }) {
1378
+ const attach = useContext(AttachContext);
1379
+ const { supported } = usePlasmaRuntime();
1380
+ useEffect(() => {
1381
+ if (DEV && !attach) console.warn("[plasma-ui] <PlasmaCanvas> must be rendered inside a <PlasmaProvider>.");
1382
+ }, [attach]);
1383
+ return /* @__PURE__ */ jsx(
1384
+ "canvas",
1385
+ {
1386
+ ref: attach ?? void 0,
1387
+ "aria-hidden": "true",
1388
+ className,
1389
+ style: {
1390
+ position: "fixed",
1391
+ inset: 0,
1392
+ width: "100%",
1393
+ height: "100%",
1394
+ zIndex,
1395
+ pointerEvents: "none",
1396
+ display: supported ? "block" : "none",
1397
+ ...style
1398
+ }
1399
+ }
1400
+ );
1401
+ }
1209
1402
  function PlasmaProvider({
1210
1403
  children,
1211
1404
  mood = "tidal",
@@ -1228,6 +1421,11 @@ function PlasmaProvider({
1228
1421
  rimWidth = 1,
1229
1422
  highlight = 1,
1230
1423
  edgeLine = 1,
1424
+ shimmer = 1,
1425
+ glow = 1,
1426
+ wash = 1,
1427
+ grain = 1,
1428
+ backgroundBlur = 0,
1231
1429
  pointerDrop = true,
1232
1430
  ambientDrops = false,
1233
1431
  grid = 24,
@@ -1235,9 +1433,10 @@ function PlasmaProvider({
1235
1433
  quality = 1.25,
1236
1434
  maxSurfaces = 16,
1237
1435
  zIndex = -1,
1238
- freezeOnScroll = false
1436
+ freezeOnScroll = false,
1437
+ canvas = true
1239
1438
  }) {
1240
- const canvasRef = useRef(null);
1439
+ const [canvasEl, setCanvasEl] = useState(null);
1241
1440
  const [renderer, setRenderer] = useState(null);
1242
1441
  const [supported, setSupported] = useState(true);
1243
1442
  const reducedMotion = useReducedMotion();
@@ -1261,6 +1460,11 @@ function PlasmaProvider({
1261
1460
  rimWidth,
1262
1461
  highlight,
1263
1462
  edgeLine,
1463
+ shimmer,
1464
+ glow,
1465
+ wash,
1466
+ grain,
1467
+ backgroundBlur,
1264
1468
  viscosity,
1265
1469
  stretch,
1266
1470
  flow,
@@ -1269,91 +1473,55 @@ function PlasmaProvider({
1269
1473
  maxSurfaces,
1270
1474
  background: background ?? null
1271
1475
  };
1272
- const settingsRef = useRef(settings);
1273
- settingsRef.current = settings;
1274
- useLayoutEffect(() => {
1275
- const r = PlasmaRenderer.create(canvasRef.current, settingsRef.current);
1476
+ const settingsRef = useLatest(settings);
1477
+ useIsoLayoutEffect(() => {
1478
+ if (!canvasEl) return;
1479
+ const r = PlasmaRenderer.create(canvasEl, settingsRef.current);
1276
1480
  if (!r) {
1277
1481
  setSupported(false);
1278
1482
  return;
1279
1483
  }
1484
+ setSupported(true);
1280
1485
  setRenderer(r);
1281
1486
  return () => {
1282
1487
  r.destroy();
1283
1488
  setRenderer(null);
1284
1489
  };
1285
- }, []);
1490
+ }, [canvasEl]);
1491
+ useEffect(() => {
1492
+ if (!DEV || canvasEl) return;
1493
+ const t = setTimeout(() => console.warn("[plasma-ui] PlasmaProvider has no canvas. Either leave `canvas` on, or render a <PlasmaCanvas /> inside the provider."), 0);
1494
+ return () => clearTimeout(t);
1495
+ }, [canvasEl]);
1496
+ const settingsDeps = Object.values(settings).map((v) => Array.isArray(v) ? v.join() : v);
1286
1497
  useEffect(() => {
1287
1498
  renderer?.configure(settings);
1288
- }, [
1289
- renderer,
1290
- m.colors.join(),
1291
- settings.blend,
1292
- refraction,
1293
- dispersion,
1294
- rim,
1295
- smoothness,
1296
- settings.pointerDrop,
1297
- ambientDrops,
1298
- theme,
1299
- quality,
1300
- reducedMotion,
1301
- freezeOnScroll,
1302
- tint,
1303
- opacity,
1304
- rimColor,
1305
- rimWidth,
1306
- highlight,
1307
- edgeLine,
1308
- viscosity,
1309
- stretch,
1310
- flow,
1311
- frost,
1312
- elevation,
1313
- background
1314
- ]);
1499
+ }, [renderer, ...settingsDeps]);
1315
1500
  const vis = Math.min(Math.max(viscosity, 0), 1);
1316
1501
  const stiffK = vis < 0.5 ? 1.6 - 1.2 * vis : 1 - 1.1 * (vis - 0.5);
1317
1502
  const dampK = vis < 0.5 ? 0.6 + 0.8 * vis : 1 + 1.6 * (vis - 0.5);
1318
- const spring = { stiffness: m.spring.stiffness * stiffK, damping: m.spring.damping * dampK * Math.sqrt(stiffK) };
1319
- const value = useMemo(() => ({
1320
- renderer,
1321
- tint,
1322
- opacity,
1323
- frost,
1324
- radius,
1325
- supported,
1326
- grid,
1327
- magnet,
1328
- spring,
1329
- reducedMotion,
1330
- pulse: (x, y, s) => renderer?.pulse(x, y, s),
1331
- bump: (e) => renderer?.bump(e)
1332
- }), [renderer, tint, opacity, frost, radius, supported, grid, magnet, spring.stiffness, spring.damping, reducedMotion]);
1333
- return /* @__PURE__ */ jsxs(PlasmaContext.Provider, { value, children: [
1503
+ const stiffness = m.spring.stiffness * stiffK;
1504
+ const damping = m.spring.damping * dampK * Math.sqrt(stiffK);
1505
+ const rendererRef = useLatest(renderer);
1506
+ const pulse = useCallback((x, y, s) => rendererRef.current?.pulse(x, y, s), []);
1507
+ const bump = useCallback((e) => rendererRef.current?.bump(e), []);
1508
+ const runtime = useMemo(
1509
+ () => ({ renderer, supported, reducedMotion, pulse, bump }),
1510
+ [renderer, supported, reducedMotion, pulse, bump]
1511
+ );
1512
+ const defaults = useMemo(
1513
+ () => ({ tint, opacity, frost, radius, grid, magnet, spring: { stiffness, damping } }),
1514
+ [tint, opacity, frost, radius, grid, magnet, stiffness, damping]
1515
+ );
1516
+ return /* @__PURE__ */ jsx(RuntimeContext.Provider, { value: runtime, children: /* @__PURE__ */ jsx(DefaultsContext.Provider, { value: defaults, children: /* @__PURE__ */ jsxs(AttachContext.Provider, { value: setCanvasEl, children: [
1334
1517
  /* @__PURE__ */ jsx("style", { children: FALLBACK_CSS }),
1335
- /* @__PURE__ */ jsx(
1336
- "canvas",
1337
- {
1338
- ref: canvasRef,
1339
- "aria-hidden": "true",
1340
- style: {
1341
- position: "fixed",
1342
- inset: 0,
1343
- width: "100%",
1344
- height: "100%",
1345
- zIndex,
1346
- pointerEvents: "none",
1347
- display: supported ? "block" : "none"
1348
- }
1349
- }
1350
- ),
1518
+ canvas && /* @__PURE__ */ jsx(PlasmaCanvas, { zIndex }),
1351
1519
  children
1352
- ] });
1520
+ ] }) }) });
1353
1521
  }
1354
1522
 
1355
1523
  // src/Plasma.tsx
1356
- import { forwardRef, useCallback, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState2 } from "react";
1524
+ import { forwardRef, useCallback as useCallback2, useEffect as useEffect2, useRef as useRef2, useState as useState2, useSyncExternalStore } from "react";
1357
1525
 
1358
1526
  // src/spring.ts
1359
1527
  var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
@@ -1464,6 +1632,25 @@ function boxGap(a, b) {
1464
1632
  // src/Plasma.tsx
1465
1633
  import { jsx as jsx2 } from "react/jsx-runtime";
1466
1634
  var NO_DRAG = "button,a,input,textarea,select,label,[contenteditable],[data-plasma-nodrag]";
1635
+ var NO_SIDES = { top: false, right: false, bottom: false, left: false };
1636
+ var noSides = () => NO_SIDES;
1637
+ function createSidesStore() {
1638
+ let value = NO_SIDES;
1639
+ const subs = /* @__PURE__ */ new Set();
1640
+ return {
1641
+ subscribe(fn) {
1642
+ subs.add(fn);
1643
+ return () => {
1644
+ subs.delete(fn);
1645
+ };
1646
+ },
1647
+ get: () => value,
1648
+ set(next) {
1649
+ value = next;
1650
+ subs.forEach((fn) => fn());
1651
+ }
1652
+ };
1653
+ }
1467
1654
  function fallbackTint(hex, a) {
1468
1655
  if (!(a > 0) || !/^#([0-9a-f]{6})$/i.test(hex)) return null;
1469
1656
  const n = parseInt(hex.slice(1), 16);
@@ -1473,7 +1660,12 @@ function assignRef(ref, v) {
1473
1660
  if (typeof ref === "function") ref(v);
1474
1661
  else if (ref) ref.current = v;
1475
1662
  }
1476
- var Plasma = forwardRef(function Plasma2({
1663
+ function useConst(make) {
1664
+ const ref = useRef2(null);
1665
+ if (ref.current === null) ref.current = make();
1666
+ return ref.current;
1667
+ }
1668
+ var PlasmaInner = forwardRef(function Plasma({
1477
1669
  as: Comp = "div",
1478
1670
  radius,
1479
1671
  lean = 10,
@@ -1485,6 +1677,7 @@ var Plasma = forwardRef(function Plasma2({
1485
1677
  padding,
1486
1678
  draggable = false,
1487
1679
  snap = true,
1680
+ group,
1488
1681
  bounds,
1489
1682
  offset,
1490
1683
  defaultOffset,
@@ -1499,29 +1692,45 @@ var Plasma = forwardRef(function Plasma2({
1499
1692
  tabIndex,
1500
1693
  ...rest
1501
1694
  }, ref) {
1502
- const plasma = usePlasma();
1503
- const r = radius ?? plasma.radius;
1695
+ const runtime = usePlasmaRuntime();
1696
+ const defaults = usePlasmaDefaults();
1697
+ const r = radius ?? defaults.radius;
1504
1698
  const el = useRef2(null);
1505
1699
  const handle = useRef2(null);
1506
- const [x] = useState2(() => springValue(offset?.x ?? defaultOffset?.x ?? 0));
1507
- const [y] = useState2(() => springValue(offset?.y ?? defaultOffset?.y ?? 0));
1700
+ const x = useConst(() => springValue(offset?.x ?? defaultOffset?.x ?? 0));
1701
+ const y = useConst(() => springValue(offset?.y ?? defaultOffset?.y ?? 0));
1508
1702
  const dest = useRef2({ x: x.get(), y: y.get() });
1509
1703
  const anims = useRef2([]);
1510
1704
  const [dragging, setDragging] = useState2(false);
1511
1705
  const positioned = draggable || !!offset || !!defaultOffset;
1512
- const joinCb = useRef2(onJoinChange);
1513
- joinCb.current = onJoinChange;
1514
- const [sides, setSides] = useState2({ top: false, right: false, bottom: false, left: false });
1515
- const plasmaRef = useRef2(plasma);
1516
- plasmaRef.current = plasma;
1517
- const setRef = useCallback((node) => {
1706
+ const joinCb = useLatest(onJoinChange);
1707
+ const sidesStore = useConst(createSidesStore);
1708
+ const sides = useSyncExternalStore(sidesStore.subscribe, sidesStore.get, noSides);
1709
+ const runtimeRef = useLatest(runtime);
1710
+ const defaultsRef = useLatest(defaults);
1711
+ const boundsRef = useLatest(bounds);
1712
+ const snapRef = useLatest(snap);
1713
+ const onDragEndRef = useLatest(onDragEnd);
1714
+ const onDragStartRef = useLatest(onDragStart);
1715
+ const opts = {
1716
+ radius: r,
1717
+ lean: lean || 0,
1718
+ tint: tint ?? null,
1719
+ opacity: opacity ?? null,
1720
+ frost: frost ?? null,
1721
+ elevation: elevation ?? null,
1722
+ fuse,
1723
+ group: group ?? null
1724
+ };
1725
+ const optsRef = useLatest(opts);
1726
+ const setRef = useCallback2((node) => {
1518
1727
  el.current = node;
1519
1728
  assignRef(ref, node);
1520
1729
  }, [ref]);
1521
- useLayoutEffect2(() => {
1522
- const node = el.current, ren = plasma.renderer;
1730
+ useIsoLayoutEffect(() => {
1731
+ const node = el.current, ren = runtime.renderer;
1523
1732
  if (!node || !ren) return;
1524
- const h = ren.register(node, { radius: r, lean: lean || 0, tint: tint ?? null, opacity: opacity ?? null, frost: frost ?? null, elevation: elevation ?? null, fuse }, (j) => joinCb.current?.(j), setSides);
1733
+ const h = ren.register(node, optsRef.current, (j) => joinCb.current?.(j), sidesStore.set);
1525
1734
  if (positioned) {
1526
1735
  h.setLayoutBox(() => {
1527
1736
  const rect = node.getBoundingClientRect();
@@ -1540,11 +1749,11 @@ var Plasma = forwardRef(function Plasma2({
1540
1749
  h.remove();
1541
1750
  handle.current = null;
1542
1751
  };
1543
- }, [plasma.renderer, positioned]);
1544
- useEffect2(() => {
1545
- handle.current?.update({ radius: r, lean: lean || 0, tint: tint ?? null, opacity: opacity ?? null, frost: frost ?? null, elevation: elevation ?? null, fuse });
1546
- }, [r, lean, tint, opacity, frost, elevation, fuse]);
1547
- useLayoutEffect2(() => {
1752
+ }, [runtime.renderer, positioned]);
1753
+ useIsoLayoutEffect(() => {
1754
+ handle.current?.update(opts);
1755
+ }, [r, lean, tint, opacity, frost, elevation, fuse, group]);
1756
+ useIsoLayoutEffect(() => {
1548
1757
  const node = el.current;
1549
1758
  if (!node || !positioned) return;
1550
1759
  const apply = () => {
@@ -1557,18 +1766,18 @@ var Plasma = forwardRef(function Plasma2({
1557
1766
  b();
1558
1767
  };
1559
1768
  }, [positioned]);
1560
- const springTo = useCallback((tx, ty, vx = 0, vy = 0) => {
1769
+ const springTo = useCallback2((tx, ty, vx = 0, vy = 0) => {
1561
1770
  dest.current = { x: tx, y: ty };
1562
1771
  anims.current.forEach((a) => a.stop());
1563
- const { spring, reducedMotion } = plasmaRef.current;
1564
- if (reducedMotion) {
1772
+ const { spring } = defaultsRef.current;
1773
+ if (runtimeRef.current.reducedMotion) {
1565
1774
  x.set(tx);
1566
1775
  y.set(ty);
1567
1776
  anims.current = [];
1568
1777
  return;
1569
1778
  }
1570
- const opts = { stiffness: spring.stiffness, damping: spring.damping };
1571
- anims.current = [animateSpring(x, tx, { ...opts, velocity: vx }), animateSpring(y, ty, { ...opts, velocity: vy })];
1779
+ const o = { stiffness: spring.stiffness, damping: spring.damping };
1780
+ anims.current = [animateSpring(x, tx, { ...o, velocity: vx }), animateSpring(y, ty, { ...o, velocity: vy })];
1572
1781
  }, []);
1573
1782
  useEffect2(() => {
1574
1783
  if (!offset) return;
@@ -1576,15 +1785,26 @@ var Plasma = forwardRef(function Plasma2({
1576
1785
  springTo(offset.x, offset.y);
1577
1786
  }, [offset?.x, offset?.y]);
1578
1787
  useEffect2(() => () => anims.current.forEach((a) => a.stop()), []);
1579
- const boundsBox = () => {
1580
- const b = bounds?.current?.getBoundingClientRect();
1788
+ useEffect2(() => {
1789
+ if (!DEV) return;
1790
+ if (!runtime.renderer && runtime.supported) {
1791
+ console.warn("[plasma-ui] <Plasma> rendered outside a <PlasmaProvider>: it will draw as the plain CSS fallback.");
1792
+ }
1793
+ const s = style;
1794
+ if (positioned && s && ("transform" in s || "translate" in s || "scale" in s)) {
1795
+ console.warn("[plasma-ui] <Plasma> owns transform, translate and scale on its element. A `style` that sets any of them will fight the drag and lean animations.");
1796
+ }
1797
+ }, [runtime.renderer, runtime.supported, positioned, style]);
1798
+ const boundsBox = useCallback2(() => {
1799
+ const b = boundsRef.current?.current?.getBoundingClientRect();
1581
1800
  return b ? { l: b.left, t: b.top, w: b.width, h: b.height } : { l: 0, t: 0, w: innerWidth, h: innerHeight };
1582
- };
1583
- const settle = (proposed, vx = 0, vy = 0) => {
1801
+ }, []);
1802
+ const settle = useCallback2((proposed, vx = 0, vy = 0) => {
1584
1803
  const node = el.current, h = handle.current;
1585
- const { grid, magnet, renderer } = plasmaRef.current;
1804
+ const { grid, magnet } = defaultsRef.current;
1805
+ const { renderer } = runtimeRef.current;
1586
1806
  let target = proposed;
1587
- if (snap && h && renderer) {
1807
+ if (snapRef.current && h && renderer) {
1588
1808
  const rect = node.getBoundingClientRect();
1589
1809
  const w = node.offsetWidth, hh = node.offsetHeight;
1590
1810
  const lo = h.leanOffset();
@@ -1592,15 +1812,15 @@ var Plasma = forwardRef(function Plasma2({
1592
1812
  const baseT = rect.top + (rect.height - hh) / 2 - lo.y - y.get();
1593
1813
  const s = snapBox(
1594
1814
  { l: baseL + proposed.x, t: baseT + proposed.y, w, h: hh },
1595
- renderer.layoutBoxes(h.id, true),
1596
- { grid, magnet, bounds: boundsBox(), inset: bounds ? 0 : 8 }
1815
+ renderer.layoutBoxes(h.id, true, optsRef.current.group ?? null),
1816
+ { grid, magnet, bounds: boundsBox(), inset: boundsRef.current ? 0 : 8 }
1597
1817
  );
1598
1818
  target = { x: s.l - baseL, y: s.t - baseT };
1599
1819
  }
1600
1820
  springTo(target.x, target.y, vx, vy);
1601
- onDragEnd?.(target);
1602
- };
1603
- const handlePointerDown = (e) => {
1821
+ onDragEndRef.current?.(target);
1822
+ }, []);
1823
+ const handlePointerDown = useCallback2((e) => {
1604
1824
  onPointerDown?.(e);
1605
1825
  if (!draggable || e.defaultPrevented || e.button !== 0) return;
1606
1826
  if (e.target.closest(NO_DRAG)) return;
@@ -1618,12 +1838,12 @@ var Plasma = forwardRef(function Plasma2({
1618
1838
  const clamp = (v, a, b) => Math.min(Math.max(v, a), Math.max(a, b));
1619
1839
  handle.current?.setDragging(true);
1620
1840
  setDragging(true);
1621
- onDragStart?.();
1841
+ onDragStartRef.current?.();
1622
1842
  const move = (ev) => {
1623
1843
  x.set(clamp(start.x + ev.clientX - start.px, minX - 40, maxX + 40));
1624
1844
  y.set(clamp(start.y + ev.clientY - start.py, minY - 40, maxY + 40));
1625
1845
  dest.current = { x: x.get(), y: y.get() };
1626
- plasmaRef.current.bump(Math.hypot(x.getVelocity(), y.getVelocity()) / 1800);
1846
+ runtimeRef.current.bump(Math.hypot(x.getVelocity(), y.getVelocity()) / 1800);
1627
1847
  };
1628
1848
  const up = () => {
1629
1849
  node.removeEventListener("pointermove", move);
@@ -1637,17 +1857,17 @@ var Plasma = forwardRef(function Plasma2({
1637
1857
  node.addEventListener("pointermove", move);
1638
1858
  node.addEventListener("pointerup", up);
1639
1859
  node.addEventListener("pointercancel", up);
1640
- };
1641
- const handleKeyDown = (e) => {
1860
+ }, [draggable, onPointerDown]);
1861
+ const handleKeyDown = useCallback2((e) => {
1642
1862
  onKeyDown?.(e);
1643
1863
  if (!draggable || e.defaultPrevented || e.target !== el.current) return;
1644
- const step = plasmaRef.current.grid || 24;
1864
+ const step = defaultsRef.current.grid || 24;
1645
1865
  const d = { ArrowLeft: [-step, 0], ArrowRight: [step, 0], ArrowUp: [0, -step], ArrowDown: [0, step] }[e.key];
1646
1866
  if (!d) return;
1647
1867
  e.preventDefault();
1648
1868
  settle({ x: dest.current.x + d[0], y: dest.current.y + d[1] });
1649
- };
1650
- const classes = ["plasma-panel", plasma.supported ? "" : "plasma-fallback", className].filter(Boolean).join(" ");
1869
+ }, [draggable, onKeyDown]);
1870
+ const classes = ["plasma-panel", runtime.supported ? "" : "plasma-fallback", className].filter(Boolean).join(" ");
1651
1871
  return /* @__PURE__ */ jsx2(
1652
1872
  Comp,
1653
1873
  {
@@ -1659,7 +1879,7 @@ var Plasma = forwardRef(function Plasma2({
1659
1879
  padding: `${sides.top ? padding / 2 : padding}px ${sides.right ? padding / 2 : padding}px ${sides.bottom ? padding / 2 : padding}px ${sides.left ? padding / 2 : padding}px`,
1660
1880
  transition: "padding 250ms ease"
1661
1881
  },
1662
- ...plasma.supported ? null : fallbackTint(tint ?? plasma.tint, opacity ?? plasma.opacity),
1882
+ ...runtime.supported ? null : fallbackTint(tint ?? defaults.tint, opacity ?? defaults.opacity),
1663
1883
  ...style
1664
1884
  },
1665
1885
  "data-plasma-draggable": draggable || void 0,
@@ -1672,9 +1892,11 @@ var Plasma = forwardRef(function Plasma2({
1672
1892
  }
1673
1893
  );
1674
1894
  });
1895
+ var Plasma2 = PlasmaInner;
1675
1896
  export {
1676
1897
  DEFAULT_MAX_SHAPES,
1677
- Plasma,
1898
+ Plasma2 as Plasma,
1899
+ PlasmaCanvas,
1678
1900
  PlasmaProvider,
1679
1901
  PlasmaRenderer,
1680
1902
  boxGap,
@@ -1682,5 +1904,7 @@ export {
1682
1904
  moods,
1683
1905
  resolveMood,
1684
1906
  snapBox,
1685
- usePlasma
1907
+ usePlasma,
1908
+ usePlasmaDefaults,
1909
+ usePlasmaRuntime
1686
1910
  };