@cruxgarden/plasma-ui 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,1522 @@
1
+ // src/PlasmaProvider.tsx
2
+ import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
3
+
4
+ // src/shaders.ts
5
+ var DEFAULT_MAX_SHAPES = 16;
6
+ var MAX_PULSES = 4;
7
+ var vert = `#version 300 es
8
+ in vec2 p; void main(){ gl_Position = vec4(p, 0., 1.); }`;
9
+ function makeShaders(MAX_SHAPES) {
10
+ const common = `
11
+ precision highp float;
12
+ uniform vec2 uRes; uniform float uScale, uTime, uGoo, uEnergy, uLight, uMouseAmt, uDropR, uAmbient, uScroll, uVisc, uFlow;
13
+ uniform vec2 uMouse;
14
+ uniform vec4 uP[${MAX_SHAPES}]; uniform vec4 uR[${MAX_SHAPES}]; uniform float uF[${MAX_SHAPES}]; uniform vec4 uT[${MAX_SHAPES}]; uniform float uFr[${MAX_SHAPES}]; uniform float uEl[${MAX_SHAPES}]; uniform float uSolo[${MAX_SHAPES}];
15
+ uniform int uCount;
16
+ uniform vec4 uRip[${MAX_PULSES}];
17
+ uniform vec3 uA, uB, uC;
18
+
19
+ float hash(vec2 p){ p = fract(p*vec2(123.34,456.21)); p += dot(p,p+45.32); return fract(p.x*p.y); }
20
+ float noise(vec2 p){
21
+ vec2 i=floor(p), f=fract(p); f=f*f*(3.-2.*f);
22
+ return mix(mix(hash(i),hash(i+vec2(1,0)),f.x), mix(hash(i+vec2(0,1)),hash(i+vec2(1,1)),f.x), f.y);
23
+ }
24
+ float fbm(vec2 p){ float v=0., a=.5; for(int i=0;i<4;i++){ v+=a*noise(p); p=p*2.03+vec2(1.7,9.2); a*=.5; } return v; }
25
+ float smin(float a, float b, float k){ float h=max(k-abs(a-b),0.)/k; return min(a,b)-h*h*k*.25; }
26
+
27
+ // Rounded box distance plus its outward direction.
28
+ // r holds per-corner radii (top-right, bottom-right, top-left, bottom-left), y pointing down.
29
+ // The last component is 1 near a squared-off corner (one that sits against a neighbor),
30
+ // where blending would otherwise raise a bump at the seam.
31
+ vec4 sdBoxG(vec2 p, vec2 b, vec4 rc, float rmax){
32
+ float r = p.x > 0. ? (p.y > 0. ? rc.y : rc.x) : (p.y > 0. ? rc.w : rc.z);
33
+ vec2 q = abs(p) - b + r;
34
+ vec2 mq = max(q, 0.);
35
+ float lo = length(mq);
36
+ vec2 g = lo > 0. ? mq / lo : (q.x > q.y ? vec2(1., 0.) : vec2(0., 1.));
37
+ float sharp = (mq.x > 0. && mq.y > 0.) ? 1. - smoothstep(0., rmax * .35 + .01, r) : 0.;
38
+ return vec4(lo + min(max(q.x, q.y), 0.) - r, g * sign(p + 1e-5), sharp);
39
+ }
40
+
41
+ float scene(vec2 p){
42
+ float d = 1e4; // fusing surfaces, blended
43
+ float ds = 1e4; // solo surfaces (fuse={false}), hard union only
44
+ vec2 g = vec2(0.);
45
+ float sh = 0.;
46
+ for(int i=0;i<uCount;i++){
47
+ float f = uF[i];
48
+ if(f < .005) continue;
49
+ vec2 c = uP[i].xy; vec2 h = uP[i].zw * f;
50
+ float rmax = max(max(uR[i].x, uR[i].y), max(uR[i].z, uR[i].w));
51
+ vec4 bx = sdBoxG(p-c, h, min(uR[i], vec4(min(h.x, h.y))), rmax);
52
+ if(uSolo[i] > .5){ ds = min(ds, bx.x); continue; }
53
+ if(d > 1e3){ d = bx.x; g = bx.yz; sh = bx.w; continue; }
54
+ // Blend only where surfaces face different ways (corners, gaps, steps);
55
+ // edges that face the same way stay perfectly straight.
56
+ float k = uGoo * clamp((1. - dot(g, bx.yz)) / .8, 0., 1.) * (1. - sh) * (1. - bx.w);
57
+ float nd = k < .5 ? min(d, bx.x) : smin(d, bx.x, k);
58
+ if(bx.x < d){ g = bx.yz; sh = bx.w; }
59
+ d = nd;
60
+ }
61
+ d = min(d, ds);
62
+ if(uAmbient > .5){
63
+ float t = uTime*.4;
64
+ vec2 home = vec2(uRes.x*.88, uRes.y*.74);
65
+ d = smin(d, length(p - home - vec2(cos(t),sin(t*1.3))*70.) - 38., uGoo);
66
+ d = smin(d, length(p - home - vec2(cos(t*1.7+2.),sin(t*.9+1.))*90.) - 26., uGoo);
67
+ d = smin(d, length(p - home - vec2(sin(t*.7),cos(t*1.1))*40.) - 20., uGoo);
68
+ }
69
+ if(uDropR > 0.){
70
+ d = smin(d, length(p-uMouse) - uDropR*uMouseAmt, 18.);
71
+ float md = length(p-uMouse);
72
+ d -= 3. * uMouseAmt * exp(-md*md/4000.);
73
+ }
74
+ for(int i=0;i<${MAX_PULSES};i++){
75
+ float age = uTime - uRip[i].z;
76
+ if(age < 0. || age > 3.) continue;
77
+ float ring = length(p-uRip[i].xy) - age*mix(680., 330., uVisc);
78
+ d -= uRip[i].w * mix(5., 2., uVisc) * exp(-ring*ring/900.) * exp(-age*mix(.8, 2.6, uVisc));
79
+ }
80
+ // slow ripple along the outline
81
+ if(uFlow > 0.) d += uFlow * 5. * (noise(p/70. + vec2(uTime*.23, -uTime*.17)) - .5) * mix(1.4, .6, uVisc);
82
+ return d;
83
+ }
84
+ vec2 fragPos(){ return vec2(gl_FragCoord.x, uRes.y*uScale - gl_FragCoord.y) / uScale; }
85
+ `;
86
+ const maskFrag = `#version 300 es
87
+ ${common}
88
+ out vec4 o;
89
+ void main(){ o = vec4(smoothstep(1.5, -1.5, scene(fragPos()))); }`;
90
+ const tintFrag = `#version 300 es
91
+ ${common}
92
+ layout(location = 0) out vec4 o;
93
+ layout(location = 1) out vec4 o2;
94
+ void main(){
95
+ vec2 p = fragPos();
96
+ float m = smoothstep(1.5, -1.5, scene(p));
97
+ vec4 acc = vec4(0.); float fr = 0.; float el = 0.; float wsum = 0.;
98
+ for(int i=0;i<uCount;i++){
99
+ if(uF[i] < .005) continue;
100
+ vec2 h = uP[i].zw * uF[i];
101
+ float rmax = max(max(uR[i].x, uR[i].y), max(uR[i].z, uR[i].w));
102
+ float d = sdBoxG(p - uP[i].xy, h, min(uR[i], vec4(min(h.x, h.y))), rmax).x;
103
+ float w = exp(-max(d, 0.) / 18.) * (1. + smoothstep(0., -24., d) * 4.);
104
+ acc += uT[i] * w; fr += uFr[i] * w; el += uEl[i] * w; wsum += w;
105
+ }
106
+ vec4 t = wsum > 0. ? acc / wsum : vec4(0.);
107
+ o = vec4(t.rgb * m, t.a * m);
108
+ o2 = vec4((wsum > 0. ? fr / wsum : 0.) * m, (wsum > 0. ? el / wsum : 0.) * m, 0., 1.);
109
+ }`;
110
+ const blurFrag = `#version 300 es
111
+ precision highp float;
112
+ uniform sampler2D uTex; uniform vec2 uDir, uOut;
113
+ out vec4 o;
114
+ void main(){
115
+ vec2 ts = vec2(textureSize(uTex, 0));
116
+ vec2 uv = gl_FragCoord.xy / uOut;
117
+ vec2 st = uDir / ts;
118
+ vec4 s = texture(uTex, uv) * .1964825501511404;
119
+ s += (texture(uTex, uv + st*1.411764705882353) + texture(uTex, uv - st*1.411764705882353)) * .2969069646728344;
120
+ s += (texture(uTex, uv + st*3.2941176470588234) + texture(uTex, uv - st*3.2941176470588234)) * .09447039785044732;
121
+ s += (texture(uTex, uv + st*5.176470588235294) + texture(uTex, uv - st*5.176470588235294)) * .010381362401148057;
122
+ o = s;
123
+ }`;
124
+ const bgFrag = `#version 300 es
125
+ ${common}
126
+ uniform sampler2D uImg;
127
+ uniform vec2 uImgRes;
128
+ uniform float uHasImg;
129
+ uniform vec3 uBgColor;
130
+ uniform float uBgSolid;
131
+ out vec4 o;
132
+ vec3 bg(vec2 p){
133
+ p.y += uScroll;
134
+ vec2 q = p/520.;
135
+ float t = uTime*.035;
136
+ vec2 w = vec2(fbm(q+t), fbm(q+vec2(5.2,1.3)-t));
137
+ float n = fbm(q*1.4 + w*1.8 + t*.6);
138
+ vec3 col = mix(uA, uB, smoothstep(.25,.75,n));
139
+ col = mix(col, uC, smoothstep(.55,.9, w.x*n*1.6));
140
+ float lines = abs(fract(n*14.)-.5);
141
+ col += (1.-smoothstep(0.,.06,lines)) * .07 * (0.6+uEnergy);
142
+ vec2 v = (p - vec2(0., uScroll))/uRes - .5;
143
+ col *= .55 + .45*smoothstep(1.2,.2,length(v));
144
+ return mix(col, mix(vec3(.90,.93,.95), col, .42), uLight);
145
+ }
146
+ void main(){
147
+ vec2 p = fragPos();
148
+ vec2 bp = p;
149
+ for(int i=0;i<${MAX_PULSES};i++){
150
+ float age = uTime - uRip[i].z;
151
+ if(age < 0. || age > 3.) continue;
152
+ vec2 dir = normalize(p-uRip[i].xy+.001);
153
+ float ring = length(p-uRip[i].xy) - age*mix(680., 330., uVisc);
154
+ bp -= dir * uRip[i].w * mix(18., 9., uVisc) * exp(-ring*ring/2500.) * exp(-age*mix(.7, 2.2, uVisc));
155
+ }
156
+ if (uBgSolid > .5) {
157
+ // solid color background: subtle luminance drift so refraction stays visible
158
+ float shade = (fbm(p*.0012 + vec2(uTime*.02, -uTime*.015)) - .5) * .10
159
+ + (fbm(p*.004 + 7.3) - .5) * .04
160
+ + length(bp - p) * .004;
161
+ o = vec4(uBgColor * (1. + shade), 1.);
162
+ return;
163
+ }
164
+ if (uHasImg > .5) {
165
+ // image background: slow swirl plus the pulse warp above
166
+ vec2 q = bp;
167
+ q += (vec2(sin(q.y*.006 + uTime*.22), cos(q.x*.005 + uTime*.17)) * 7.
168
+ + vec2(fbm(q*.004 + uTime*.05) - .5, fbm(q*.004 + 31.7 - uTime*.04) - .5) * 22.)
169
+ * (.5 + uFlow*.5);
170
+ // cover-fit the image to the canvas
171
+ float sc = max(uRes.x / uImgRes.x, uRes.y / uImgRes.y);
172
+ vec2 uv = (q - .5*uRes) / (uImgRes * sc) + .5;
173
+ o = vec4(texture(uImg, clamp(uv, 0., 1.)).rgb, 1.);
174
+ return;
175
+ }
176
+ o = vec4(bg(bp), 1.);
177
+ }`;
178
+ const compFrag = `#version 300 es
179
+ ${common}
180
+ uniform sampler2D uH, uS, uTint, uBg, uBgM, uBgH, uFrost;
181
+ uniform float uRefract, uDisp, uRim, uRimMode, uRimWidth, uSpec, uHair;
182
+ uniform vec3 uRimColor;
183
+ out vec4 o;
184
+
185
+ vec3 pal(float t){ return .5 + .5*cos(6.2831*(t + vec3(0., .33, .67))); }
186
+ float H(vec2 uv){ return texture(uH, uv).r; }
187
+ vec2 uvAt(vec2 q){ return vec2(q.x, uRes.y - q.y) / uRes; }
188
+ // background seen through glass with frost f: sharp, then medium, then heavy blur
189
+ vec3 seen(vec2 q, float f){
190
+ vec2 u = uvAt(q);
191
+ vec3 sharp = texture(uBg, u).rgb;
192
+ if(f < .002) return sharp;
193
+ vec3 soft = mix(texture(uBgM, u).rgb, texture(uBgH, u).rgb, smoothstep(.45, 1., f));
194
+ return mix(sharp, soft, smoothstep(0., .45, f));
195
+ }
196
+ // B-spline bicubic from four bilinear taps keeps curved outlines round.
197
+ float S(vec2 uv){
198
+ vec2 ts = vec2(textureSize(uS, 0));
199
+ vec2 st = uv*ts - .5;
200
+ vec2 i = floor(st), f = st - i;
201
+ vec2 f2 = f*f, f3 = f2*f;
202
+ vec2 w0 = (1. - 3.*f + 3.*f2 - f3)/6.;
203
+ vec2 w1 = (4. - 6.*f2 + 3.*f3)/6.;
204
+ vec2 w2 = (1. + 3.*f + 3.*f2 - 3.*f3)/6.;
205
+ vec2 w3 = f3/6.;
206
+ vec2 g0 = w0 + w1, g1 = w2 + w3;
207
+ vec2 h0 = (w1/g0 - 1. + i + .5)/ts;
208
+ vec2 h1 = (w3/g1 + 1. + i + .5)/ts;
209
+ return g0.y*(g0.x*texture(uS, vec2(h0.x,h0.y)).r + g1.x*texture(uS, vec2(h1.x,h0.y)).r)
210
+ + g1.y*(g0.x*texture(uS, vec2(h0.x,h1.y)).r + g1.x*texture(uS, vec2(h1.x,h1.y)).r);
211
+ }
212
+
213
+ void main(){
214
+ vec2 p = fragPos();
215
+ vec2 uv = gl_FragCoord.xy / (uRes*uScale);
216
+ vec2 tx = 1. / vec2(textureSize(uS, 0));
217
+ float cssPerTexel = uRes.x / float(textureSize(uS, 0).x);
218
+
219
+ float s = S(uv);
220
+ vec2 gs = vec2(S(uv+vec2(tx.x,0.)) - S(uv-vec2(tx.x,0.)), S(uv+vec2(0.,tx.y)) - S(uv-vec2(0.,tx.y))) / (2.*cssPerTexel);
221
+ float sd = clamp((s - .5) / max(length(gs), 1e-3), -60., 60.);
222
+
223
+ vec3 back = texture(uBg, uv).rgb;
224
+ float grain = 1.;
225
+
226
+ // shadow: offset and strength follow the surface's elevation
227
+ float msk = max(texture(uS, uv).r, 1e-3);
228
+ float elHere = clamp(texture(uFrost, uv).g / msk, 0., 1.);
229
+ float elCast = clamp(texture(uFrost, uv - vec2(0., 20. / uRes.y)).g / max(texture(uS, uv - vec2(0., 20. / uRes.y)).r, 1e-3), 0., 1.);
230
+ float el = max(elHere, elCast);
231
+ float hs = H(uv + vec2(0., (4. + el * 28.) / uRes.y));
232
+ float shStr = .5 * smoothstep(0., .12, el) * clamp(el + .35, 0., 1.);
233
+ vec3 col = back * (1. - shStr*smoothstep(.02, .55, hs)*(1.-uLight*.6));
234
+ float hHere = H(uv);
235
+ col += pal(uTime*.03 + p.x/1400.) * .06 * smoothstep(.0, .45, hHere) * (1.+uEnergy);
236
+
237
+ if(sd > -2.){
238
+ vec2 th = 2. / vec2(textureSize(uH, 0));
239
+ vec2 g = vec2(H(uv+vec2(th.x,0.)) - H(uv-vec2(th.x,0.)), H(uv+vec2(0.,th.y)) - H(uv-vec2(0.,th.y)));
240
+ g.y = -g.y;
241
+ float slope = clamp(length(g) / .16, 0., 1.);
242
+ vec2 n = -g / (length(g) + 1e-4);
243
+ float bevel = 1. - smoothstep(.42, .97, hHere);
244
+ float depth = 1. - bevel;
245
+ float lift = sqrt(1. - bevel*bevel);
246
+
247
+ vec2 off = -n * pow(bevel, 2.2) * 50. * slope * uRefract;
248
+ float disp = (.16 + uEnergy*.1) * uDisp;
249
+ float fr = clamp(texture(uFrost, uv).r / msk, 0., 1.);
250
+ vec3 refr = vec3(
251
+ seen(p + off*(1.+disp), fr).r,
252
+ seen(p + off, fr).g,
253
+ seen(p + off*(1.-disp), fr).b
254
+ );
255
+ refr = mix(refr, vec3(dot(refr, vec3(.333))), .18) * mix(1.08, .9, uLight) + .03*(1.-uLight);
256
+ // frosted glass: milkier and a little brighter
257
+ refr = mix(refr, mix(refr, vec3(dot(refr, vec3(.333))), .25) * mix(1.12, .97, uLight) + mix(.05, .03, uLight), fr);
258
+ float hl = 1. - .55*uLight;
259
+
260
+ // tint: un-premultiply by the equally blurred silhouette
261
+ vec4 tn = texture(uTint, uv);
262
+ vec3 tcol = clamp(tn.rgb / msk, 0., 1.);
263
+ float talpha = clamp(tn.a / msk, 0., 1.);
264
+ // a little of the background shows through partial tints; full opacity is a flat color
265
+ refr = mix(refr, tcol * mix(1., .92, uLight) + refr * .08 * (1. - talpha), talpha);
266
+
267
+ vec2 L = normalize(uMouse - p + vec2(0., -200.));
268
+ float spec = pow(max(dot(n, L), 0.), 26.) * pow(bevel, 2.) * slope;
269
+ float fres = pow(bevel, 5. / max(uRimWidth, .05)) * slope;
270
+ // rim color: 0 iridescent, 1 solid color, 2 each surface's tint
271
+ vec3 rimCol = pal(dot(n, L)*.35*slope + depth*.8 + uTime*.04 + uEnergy*.3);
272
+ float facing = .75 + .5 * max(dot(n, L), 0.) * slope;
273
+ if (uRimMode > .5 && uRimMode < 1.5) rimCol = uRimColor * facing * 1.4;
274
+ else if (uRimMode > 1.5) rimCol = tcol * facing * 1.4;
275
+
276
+ vec3 glass = refr;
277
+ glass += rimCol * fres * .45 * hl * uRim;
278
+ glass += vec3(1.) * spec * .75 * hl * uSpec;
279
+ // faint shimmer across the body; fades out as the tint becomes opaque
280
+ glass += pal(uTime*.04 + p.y/900. + uEnergy*.3) * .05 * lift * (1.+uEnergy*2.) * hl * (1. - talpha);
281
+ vec3 hairCol = uRimMode > .5 ? mix(vec3(1.), rimCol / 1.4, .6) : vec3(.9,.95,1.);
282
+ glass += hairCol * (1.-smoothstep(0., 1.6, abs(sd - .7))) * .4 * hl * uHair;
283
+
284
+ float a = smoothstep(-.8, .8, sd);
285
+ col = mix(col, glass, a);
286
+ grain = 1. - a; // grain is background-only; panels stay clean
287
+ }
288
+ col += (hash(p + uTime) - .5) * .025 * grain;
289
+ o = vec4(col, 1.);
290
+ }`;
291
+ return { maskFrag, tintFrag, blurFrag, bgFrag, compFrag };
292
+ }
293
+
294
+ // src/moods.ts
295
+ var moods = {
296
+ tidal: { colors: ["#04111c", "#0f4c5c", "#6a5acd"], blend: 40, spring: { stiffness: 170, damping: 16 } },
297
+ aurora: { colors: ["#050b12", "#0f5e46", "#b04bd6"], blend: 40, spring: { stiffness: 120, damping: 11 } },
298
+ ember: { colors: ["#12060a", "#6b1a2a", "#e39a3b"], blend: 40, spring: { stiffness: 260, damping: 20 } }
299
+ };
300
+ function resolveMood(mood) {
301
+ return typeof mood === "string" ? moods[mood] ?? moods.tidal : mood;
302
+ }
303
+ function hexToRgb(hex) {
304
+ const h = hex.replace("#", "");
305
+ const n = h.length === 3 ? h.split("").map((c) => c + c).join("") : h;
306
+ return [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) / 255);
307
+ }
308
+
309
+ // src/renderer.ts
310
+ var UNIFORMS = [
311
+ "uRes",
312
+ "uScale",
313
+ "uTime",
314
+ "uGoo",
315
+ "uEnergy",
316
+ "uLight",
317
+ "uMouseAmt",
318
+ "uDropR",
319
+ "uAmbient",
320
+ "uScroll",
321
+ "uMouse",
322
+ "uP",
323
+ "uR",
324
+ "uF",
325
+ "uT",
326
+ "uFr",
327
+ "uEl",
328
+ "uSolo",
329
+ "uTint",
330
+ "uImg",
331
+ "uImgRes",
332
+ "uHasImg",
333
+ "uBgColor",
334
+ "uBgSolid",
335
+ "uBg",
336
+ "uBgM",
337
+ "uBgH",
338
+ "uFrost",
339
+ "uOut",
340
+ "uCount",
341
+ "uRip",
342
+ "uA",
343
+ "uB",
344
+ "uC",
345
+ "uH",
346
+ "uS",
347
+ "uTex",
348
+ "uDir",
349
+ "uVisc",
350
+ "uFlow",
351
+ "uRefract",
352
+ "uDisp",
353
+ "uRim",
354
+ "uRimMode",
355
+ "uRimColor",
356
+ "uRimWidth",
357
+ "uSpec",
358
+ "uHair"
359
+ ];
360
+ var MASK_SCALE = 0.5;
361
+ function elementBox(el) {
362
+ const r = el.getBoundingClientRect();
363
+ const w = el.offsetWidth, h = el.offsetHeight;
364
+ return { l: r.left + (r.width - w) / 2, t: r.top + (r.height - h) / 2, w, h };
365
+ }
366
+ var near = (d) => d <= 0 ? 1 : d >= 6 ? 0 : 1 - d / 6 * (d / 6) * (3 - 2 * d / 6);
367
+ function cornerRadii(a, radius, all, self) {
368
+ const aR = a.l + a.w, aB = a.t + a.h;
369
+ const corners = [[1, -1], [1, 1], [-1, -1], [-1, 1]];
370
+ return corners.map(([sx, sy]) => {
371
+ let f = 0;
372
+ for (let j = 0; j < all.length && f < 1; j++) {
373
+ if (j === self) continue;
374
+ const b = all[j], bR = b.l + b.w, bB = b.t + b.h;
375
+ const beyondX = (sx > 0 ? bR > aR + 1 : b.l < a.l - 1) && (sy < 0 ? bB > a.t + 1 : b.t < aB - 1);
376
+ if (beyondX) {
377
+ const touch = near(sx > 0 ? b.l - aR : a.l - bR);
378
+ const cover = near(sy < 0 ? b.t - a.t : aB - bB);
379
+ f = Math.max(f, touch * cover);
380
+ }
381
+ const beyondY = (sy < 0 ? b.t < a.t - 1 : bB > aB + 1) && (sx > 0 ? b.l < aR - 1 : bR > a.l + 1);
382
+ if (beyondY) {
383
+ const touch = near(sy < 0 ? a.t - bB : b.t - aB);
384
+ const cover = near(sx > 0 ? aR - bR : b.l - a.l);
385
+ f = Math.max(f, touch * cover);
386
+ }
387
+ }
388
+ return radius * (1 - f);
389
+ });
390
+ }
391
+ function parseCssColor(src) {
392
+ if (typeof document === "undefined") return null;
393
+ const ctx = parseCssColor._c ?? (parseCssColor._c = document.createElement("canvas").getContext("2d"));
394
+ if (!ctx) return null;
395
+ ctx.fillStyle = "#010203";
396
+ ctx.fillStyle = src;
397
+ const a = ctx.fillStyle;
398
+ ctx.fillStyle = "#040506";
399
+ ctx.fillStyle = src;
400
+ const b = ctx.fillStyle;
401
+ if (a !== b) return null;
402
+ const m = /^#([0-9a-f]{6})$/i.exec(a);
403
+ if (m) return [0, 2, 4].map((i) => parseInt(m[1].slice(i, i + 2), 16) / 255);
404
+ const r = /^rgba?\(([\d.]+),\s*([\d.]+),\s*([\d.]+)/.exec(a);
405
+ if (r) return [+r[1] / 255, +r[2] / 255, +r[3] / 255];
406
+ return null;
407
+ }
408
+ var PlasmaRenderer = class _PlasmaRenderer {
409
+ constructor(canvas, gl, settings) {
410
+ this.imgTex = null;
411
+ this.imgRes = [1, 1];
412
+ this.imgSrc = null;
413
+ this.bgColor = null;
414
+ this.srcEl = null;
415
+ this.recs = /* @__PURE__ */ new Map();
416
+ this.nextId = 1;
417
+ this.raf = 0;
418
+ this.last = 0;
419
+ this.time = 0;
420
+ this.dpr = 1;
421
+ this.energy = 0;
422
+ this.mouse = { x: 0, y: 0, tx: 0, ty: 0, amt: 0, target: 0 };
423
+ this.pulses = Array.from({ length: MAX_PULSES }, () => [0, 0, -99, 0]);
424
+ this.pulseIdx = 0;
425
+ this.colors = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
426
+ this.colorTarget = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
427
+ this.blend = 40;
428
+ this.lightQuery = typeof matchMedia !== "undefined" ? matchMedia("(prefers-color-scheme: light)") : null;
429
+ this.tintCache = /* @__PURE__ */ new Map();
430
+ this.RP = new Float32Array(MAX_PULSES * 4);
431
+ this.onPointer = (e) => {
432
+ this.mouse.tx = e.clientX;
433
+ this.mouse.ty = e.clientY;
434
+ this.mouse.target = 1;
435
+ };
436
+ this.onLeave = () => {
437
+ this.mouse.target = 0;
438
+ };
439
+ this.resize = () => {
440
+ const gl = this.gl;
441
+ this.dpr = Math.min(devicePixelRatio || 1, this.settings.quality);
442
+ this.canvas.width = Math.max(1, Math.round(innerWidth * this.dpr));
443
+ this.canvas.height = Math.max(1, Math.round(innerHeight * this.dpr));
444
+ const w = Math.max(1, Math.round(this.canvas.width * MASK_SCALE));
445
+ const h = Math.max(1, Math.round(this.canvas.height * MASK_SCALE));
446
+ const size = (t, tw, th) => {
447
+ t.w = tw;
448
+ t.h = th;
449
+ gl.bindTexture(gl.TEXTURE_2D, t.tex);
450
+ if (this.floatOK) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA16F, tw, th, 0, gl.RGBA, gl.HALF_FLOAT, null);
451
+ else gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, tw, th, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
452
+ gl.bindFramebuffer(gl.FRAMEBUFFER, t.fb);
453
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.tex, 0);
454
+ };
455
+ [this.rtA, this.rtB, this.rtC, this.rtT, this.rtFr, this.rtBgM, this.rtBgH].forEach((t) => size(t, w, h));
456
+ size(this.rtBg, this.canvas.width, this.canvas.height);
457
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.mrtFb);
458
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.rtT.tex, 0);
459
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT1, gl.TEXTURE_2D, this.rtFr.tex, 0);
460
+ gl.drawBuffers([gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1]);
461
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
462
+ };
463
+ this.frame = (now2) => {
464
+ this.raf = requestAnimationFrame(this.frame);
465
+ const dt = this.last ? Math.min((now2 - this.last) / 1e3, 0.05) : 0.016;
466
+ this.last = now2;
467
+ const s = this.settings;
468
+ this.time += dt * (s.reducedMotion ? 0.4 : 1);
469
+ const m = this.mouse;
470
+ m.x += (m.tx - m.x) * 0.18;
471
+ m.y += (m.ty - m.y) * 0.18;
472
+ m.amt += (m.target - m.amt) * Math.min(1, dt * 4);
473
+ const ce = 1 - Math.exp(-dt * 2);
474
+ this.colors.forEach((c, i) => c.forEach((v2, j) => {
475
+ c[j] = v2 + (this.colorTarget[i][j] - v2) * ce;
476
+ }));
477
+ this.blend += (s.blend - this.blend) * ce;
478
+ this.energy *= Math.exp(-dt * 1.2);
479
+ const vw = innerWidth, vh = innerHeight;
480
+ const list = [];
481
+ this.recs.forEach((r) => {
482
+ if (!r.el.isConnected) return;
483
+ if (!s.reducedMotion) {
484
+ r.formV += (170 * (1 - r.form) - 12 * r.formV) * dt;
485
+ r.form += r.formV * dt;
486
+ } else r.form = 1;
487
+ const b = elementBox(r.el);
488
+ r.box = b;
489
+ if (b.w === 0 || b.l > vw + 80 || b.t > vh + 80 || b.l + b.w < -80 || b.t + b.h < -80) {
490
+ r.sp.live = false;
491
+ return;
492
+ }
493
+ list.push(r);
494
+ });
495
+ const boxes = list.map((r) => this.layoutBoxOf(r));
496
+ const joinGap = Math.max(1.5, this.blend * 0.5);
497
+ list.forEach((r, i) => {
498
+ const a = boxes[i];
499
+ const sides = { top: false, right: false, bottom: false, left: false };
500
+ const iSolo = list[i].fuse === false;
501
+ boxes.forEach((b, j) => {
502
+ if (j === i || iSolo || list[j].fuse === false) return;
503
+ const overX = b.l < a.l + a.w - 1 && b.l + b.w > a.l + 1;
504
+ const overY = b.t < a.t + a.h - 1 && b.t + b.h > a.t + 1;
505
+ if (overX) {
506
+ if (a.t - (b.t + b.h) < joinGap && b.t < a.t) sides.top = true;
507
+ if (b.t - (a.t + a.h) < joinGap && b.t + b.h > a.t + a.h) sides.bottom = true;
508
+ }
509
+ if (overY) {
510
+ if (a.l - (b.l + b.w) < joinGap && b.l < a.l) sides.left = true;
511
+ if (b.l - (a.l + a.w) < joinGap && b.l + b.w > a.l + a.w) sides.right = true;
512
+ }
513
+ });
514
+ const joined = sides.top || sides.right || sides.bottom || sides.left;
515
+ if (joined !== r.joined) {
516
+ r.joined = joined;
517
+ r.onJoin?.(joined);
518
+ }
519
+ const key = `${+sides.top}${+sides.right}${+sides.bottom}${+sides.left}`;
520
+ if (key !== r.sidesKey) {
521
+ r.sidesKey = key;
522
+ r.onSides?.(sides);
523
+ }
524
+ let tx = 0, ty = 0;
525
+ if (r.lean > 0 && !joined && !r.dragging && !s.reducedMotion) {
526
+ const dx = m.tx - (a.l + a.w / 2), dy = m.ty - (a.t + a.h / 2);
527
+ const pull = Math.exp(-(dx * dx + dy * dy) / 12e4) * r.lean * m.amt;
528
+ const len = Math.hypot(dx, dy) || 1;
529
+ tx = dx / len * pull;
530
+ ty = dy / len * pull;
531
+ }
532
+ const le = 1 - Math.exp(-dt * 3);
533
+ r.lx += (tx - r.lx) * le;
534
+ r.ly += (ty - r.ly) * le;
535
+ r.el.style.translate = `${r.lx.toFixed(2)}px ${r.ly.toFixed(2)}px`;
536
+ const u = (this.time - r.pulseAt) / 0.36;
537
+ if (r.pulseAt >= 0 && u >= 0 && u <= 1 && !s.reducedMotion) r.el.style.scale = String(1 + 0.04 * r.pulseS * Math.sin(Math.PI * u));
538
+ else if (r.el.style.scale) r.el.style.scale = "";
539
+ });
540
+ const v = Math.min(Math.max(s.viscosity, 0), 1);
541
+ const st = Math.max(s.stretch, 0);
542
+ const stiff = (900 - 810 * v) / Math.max(st * st, 1e-4);
543
+ const zeta = 0.28 + 0.87 * v;
544
+ const damp = 2 * zeta * Math.sqrt(stiff);
545
+ const sx = scrollX, sy = scrollY;
546
+ const steps = Math.max(1, Math.ceil(dt * 120));
547
+ const h = dt / steps;
548
+ list.forEach((r) => {
549
+ const rr = r.el.getBoundingClientRect();
550
+ const tgt = [rr.left + sx, rr.top + sy, rr.right + sx, rr.bottom + sy];
551
+ const sp = r.sp;
552
+ if (!sp.live || st < 0.01 || s.reducedMotion) {
553
+ sp.e = tgt.slice();
554
+ sp.v = [0, 0, 0, 0];
555
+ sp.live = true;
556
+ } else {
557
+ for (let k = 0; k < steps; k++) for (let j = 0; j < 4; j++) {
558
+ sp.v[j] += (stiff * (tgt[j] - sp.e[j]) - damp * sp.v[j]) * h;
559
+ sp.e[j] += sp.v[j] * h;
560
+ }
561
+ for (let j = 0; j < 4; j++) if (Math.abs(tgt[j] - sp.e[j]) < 0.02 && Math.abs(sp.v[j]) < 0.05) {
562
+ sp.e[j] = tgt[j];
563
+ sp.v[j] = 0;
564
+ }
565
+ }
566
+ const l = Math.min(tgt[0], sp.e[0]) - sx, t = Math.min(tgt[1], sp.e[1]) - sy;
567
+ const rgt = Math.max(tgt[2], sp.e[2]) - sx, btm = Math.max(tgt[3], sp.e[3]) - sy;
568
+ r.drawn = { l, t, w: rgt - l, h: btm - t };
569
+ });
570
+ this.draw(list.slice(0, this.max));
571
+ };
572
+ this.canvas = canvas;
573
+ this.gl = gl;
574
+ this.settings = settings;
575
+ this.max = Math.max(1, Math.round(settings.maxSurfaces || DEFAULT_MAX_SHAPES));
576
+ this.P = new Float32Array(this.max * 4);
577
+ this.R = new Float32Array(this.max * 4);
578
+ this.F = new Float32Array(this.max);
579
+ this.T = new Float32Array(this.max * 4);
580
+ this.FR = new Float32Array(this.max);
581
+ this.EL = new Float32Array(this.max);
582
+ this.SOLO = new Float32Array(this.max);
583
+ this.floatOK = !!gl.getExtension("EXT_color_buffer_float");
584
+ const sh = makeShaders(this.max);
585
+ 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) };
586
+ const buf = gl.createBuffer();
587
+ gl.bindBuffer(gl.ARRAY_BUFFER, buf);
588
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
589
+ gl.enableVertexAttribArray(0);
590
+ gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
591
+ this.rtA = this.target();
592
+ this.rtB = this.target();
593
+ this.rtC = this.target();
594
+ this.rtT = this.target();
595
+ this.rtFr = this.target();
596
+ this.rtBg = this.target();
597
+ this.rtBgM = this.target();
598
+ this.rtBgH = this.target();
599
+ this.mrtFb = gl.createFramebuffer();
600
+ this.configure(settings, true);
601
+ this.mouse.x = this.mouse.tx = innerWidth / 2;
602
+ this.mouse.y = this.mouse.ty = innerHeight / 2;
603
+ addEventListener("resize", this.resize);
604
+ addEventListener("pointermove", this.onPointer, { passive: true });
605
+ document.addEventListener("pointerleave", this.onLeave);
606
+ this.resize();
607
+ this.raf = requestAnimationFrame(this.frame);
608
+ }
609
+ /** Returns null when WebGL2 is unavailable. */
610
+ static create(canvas, settings) {
611
+ const gl = canvas.getContext("webgl2", { antialias: false, premultipliedAlpha: false });
612
+ if (!gl) return null;
613
+ try {
614
+ return new _PlasmaRenderer(canvas, gl, settings);
615
+ } catch (e) {
616
+ console.error("[plasma-ui]", e);
617
+ return null;
618
+ }
619
+ }
620
+ loadBackground(src) {
621
+ if (src === this.imgSrc) return;
622
+ this.imgSrc = src;
623
+ const gl = this.gl;
624
+ const clearTex = () => {
625
+ if (this.imgTex) {
626
+ gl.deleteTexture(this.imgTex);
627
+ this.imgTex = null;
628
+ }
629
+ };
630
+ this.bgColor = null;
631
+ this.srcEl = null;
632
+ if (!src) {
633
+ clearTex();
634
+ return;
635
+ }
636
+ if (typeof src !== "string") {
637
+ clearTex();
638
+ const tex = gl.createTexture();
639
+ gl.bindTexture(gl.TEXTURE_2D, tex);
640
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
641
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
642
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
643
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
644
+ this.imgTex = tex;
645
+ const up = (source, w, h) => {
646
+ if (!w || !h) return;
647
+ gl.bindTexture(gl.TEXTURE_2D, this.imgTex);
648
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
649
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, source);
650
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
651
+ this.imgRes = [w, h];
652
+ };
653
+ if (src instanceof HTMLCanvasElement || typeof HTMLVideoElement !== "undefined" && src instanceof HTMLVideoElement) {
654
+ this.srcEl = src;
655
+ } else if (typeof ImageBitmap !== "undefined" && src instanceof ImageBitmap) {
656
+ up(src, src.width, src.height);
657
+ } else {
658
+ const img2 = src;
659
+ if (img2.complete && img2.naturalWidth) up(img2, img2.naturalWidth, img2.naturalHeight);
660
+ else img2.addEventListener("load", () => {
661
+ if (this.imgSrc === src) up(img2, img2.naturalWidth, img2.naturalHeight);
662
+ }, { once: true });
663
+ }
664
+ return;
665
+ }
666
+ const rgb = parseCssColor(src);
667
+ if (rgb) {
668
+ this.bgColor = rgb;
669
+ clearTex();
670
+ return;
671
+ }
672
+ const img = new Image();
673
+ img.crossOrigin = "anonymous";
674
+ img.onload = () => {
675
+ if (this.imgSrc !== src) return;
676
+ const tex = gl.createTexture();
677
+ gl.bindTexture(gl.TEXTURE_2D, tex);
678
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
679
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, img);
680
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
681
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
682
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
683
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
684
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
685
+ if (this.imgTex) gl.deleteTexture(this.imgTex);
686
+ this.imgTex = tex;
687
+ this.imgRes = [img.naturalWidth, img.naturalHeight];
688
+ };
689
+ img.src = src;
690
+ }
691
+ configure(s, immediate = false) {
692
+ const qualityChanged = s.quality !== this.settings.quality;
693
+ this.settings = s;
694
+ this.loadBackground(s.background ?? null);
695
+ this.colorTarget = s.colors.map(hexToRgb);
696
+ if (immediate) {
697
+ this.colors = this.colorTarget.map((c) => [...c]);
698
+ this.blend = s.blend;
699
+ }
700
+ if (qualityChanged) this.resize();
701
+ }
702
+ register(el, o, onJoin, onSides) {
703
+ const id = this.nextId++;
704
+ const rec = {
705
+ id,
706
+ el,
707
+ ...o,
708
+ form: this.settings.reducedMotion ? 1 : 0,
709
+ formV: 0,
710
+ removing: false,
711
+ lx: 0,
712
+ ly: 0,
713
+ joined: false,
714
+ dragging: false,
715
+ layoutBox: null,
716
+ pulseAt: -1,
717
+ pulseS: 0,
718
+ box: null,
719
+ onJoin,
720
+ onSides,
721
+ sidesKey: "",
722
+ sp: { e: [0, 0, 0, 0], v: [0, 0, 0, 0], live: false },
723
+ drawn: null,
724
+ elevNow: -1
725
+ };
726
+ this.recs.set(id, rec);
727
+ return {
728
+ id,
729
+ update: (p) => Object.assign(rec, p),
730
+ setLayoutBox: (fn) => {
731
+ rec.layoutBox = fn;
732
+ },
733
+ setDragging: (on) => {
734
+ rec.dragging = on;
735
+ },
736
+ leanOffset: () => ({ x: rec.lx, y: rec.ly }),
737
+ isJoined: () => rec.joined,
738
+ remove: () => {
739
+ el.style.translate = "";
740
+ el.style.scale = "";
741
+ this.recs.delete(id);
742
+ }
743
+ };
744
+ }
745
+ /** Layout boxes of all shapes (lean removed; animating draggables report their destination). */
746
+ layoutBoxes(excludeId, fusingOnly = false) {
747
+ const out = [];
748
+ this.recs.forEach((r) => {
749
+ if (r.id === excludeId) return;
750
+ if (fusingOnly && r.fuse === false) return;
751
+ out.push(this.layoutBoxOf(r));
752
+ });
753
+ return out;
754
+ }
755
+ layoutBoxOf(r) {
756
+ if (r.layoutBox) return r.layoutBox();
757
+ const b = elementBox(r.el);
758
+ return { l: b.l - r.lx, t: b.t - r.ly, w: b.w, h: b.h };
759
+ }
760
+ /** Send a pulse through the material from a viewport point. */
761
+ pulse(x, y, strength = 1) {
762
+ this.pulses[this.pulseIdx] = [x, y, this.time, strength];
763
+ this.pulseIdx = (this.pulseIdx + 1) % MAX_PULSES;
764
+ this.recs.forEach((r) => {
765
+ const b = elementBox(r.el);
766
+ const dist = Math.hypot(b.l + b.w / 2 - x, b.t + b.h / 2 - y);
767
+ r.pulseAt = this.time + dist / 520;
768
+ r.pulseS = strength;
769
+ });
770
+ this.bump(0.9 * strength);
771
+ }
772
+ /** Raise the material's energy (brightens contours and color); it decays on its own. */
773
+ bump(e) {
774
+ this.energy = Math.max(this.energy, Math.min(e, 1));
775
+ }
776
+ destroy() {
777
+ cancelAnimationFrame(this.raf);
778
+ removeEventListener("resize", this.resize);
779
+ removeEventListener("pointermove", this.onPointer);
780
+ document.removeEventListener("pointerleave", this.onLeave);
781
+ this.recs.forEach((r) => {
782
+ r.el.style.translate = "";
783
+ r.el.style.scale = "";
784
+ });
785
+ this.recs.clear();
786
+ this.gl.getExtension("WEBGL_lose_context")?.loseContext();
787
+ }
788
+ isLight() {
789
+ const t = this.settings.theme;
790
+ if (t !== "auto") return t === "light";
791
+ const attr = document.documentElement.dataset.theme;
792
+ if (attr === "light" || attr === "dark") return attr === "light";
793
+ return !!this.lightQuery?.matches;
794
+ }
795
+ draw(list) {
796
+ const gl = this.gl, s = this.settings;
797
+ const drawn = list.map((r) => r.drawn ?? elementBox(r.el));
798
+ list.forEach((r, i) => {
799
+ const b = drawn[i];
800
+ this.P.set([b.l + b.w / 2, b.t + b.h / 2, b.w / 2, b.h / 2], i * 4);
801
+ const squareAgainst = r.fuse === false ? [] : drawn.filter((_, j) => j !== i && list[j].fuse !== false);
802
+ this.R.set(cornerRadii(b, r.radius, squareAgainst, -1), i * 4);
803
+ this.F[i] = Math.max(0, r.form);
804
+ const [tr, tg, tb] = this.rgb(r.tint ?? s.tint);
805
+ this.T.set([tr, tg, tb, Math.min(Math.max(r.opacity ?? s.opacity, 0), 1)], i * 4);
806
+ this.FR[i] = Math.min(Math.max(r.frost ?? s.frost, 0), 1);
807
+ const base = Math.min(Math.max(r.elevation ?? s.elevation, 0), 1);
808
+ const target = r.dragging ? Math.min(base + 0.35, 1) : base;
809
+ r.elevNow = r.elevNow < 0 ? target : r.elevNow + (target - r.elevNow) * 0.12;
810
+ this.EL[i] = r.elevNow;
811
+ this.SOLO[i] = r.fuse === false ? 1 : 0;
812
+ });
813
+ this.pulses.forEach((p, i) => this.RP.set(p, i * 4));
814
+ const light = this.isLight() ? 1 : 0;
815
+ const setCommon = (u, scale) => {
816
+ gl.uniform2f(u.uRes, innerWidth, innerHeight);
817
+ gl.uniform1f(u.uScale, scale);
818
+ gl.uniform1f(u.uTime, this.time);
819
+ gl.uniform1f(u.uGoo, this.blend);
820
+ gl.uniform1f(u.uEnergy, this.energy);
821
+ gl.uniform1f(u.uLight, light);
822
+ gl.uniform1f(u.uMouseAmt, this.mouse.amt);
823
+ gl.uniform1f(u.uDropR, s.pointerDrop ? 15 : 0);
824
+ gl.uniform1f(u.uAmbient, s.ambientDrops ? 1 : 0);
825
+ gl.uniform1f(u.uScroll, scrollY * 0.25);
826
+ gl.uniform1f(u.uVisc, Math.min(Math.max(s.viscosity, 0), 1));
827
+ gl.uniform1f(u.uFlow, Math.max(s.flow, 0));
828
+ gl.uniform2f(u.uMouse, this.mouse.x, this.mouse.y);
829
+ gl.uniform4fv(u.uP, this.P);
830
+ gl.uniform4fv(u.uR, this.R);
831
+ gl.uniform1fv(u.uF, this.F);
832
+ gl.uniform4fv(u.uT, this.T);
833
+ gl.uniform1fv(u.uFr, this.FR);
834
+ gl.uniform1fv(u.uEl, this.EL);
835
+ gl.uniform1fv(u.uSolo, this.SOLO);
836
+ gl.uniform1i(u.uCount, list.length);
837
+ gl.uniform4fv(u.uRip, this.RP);
838
+ gl.uniform3fv(u.uA, this.colors[0]);
839
+ gl.uniform3fv(u.uB, this.colors[1]);
840
+ gl.uniform3fv(u.uC, this.colors[2]);
841
+ };
842
+ const bl = this.progs.blur;
843
+ const pass = (src, dst, dx, dy) => {
844
+ gl.bindFramebuffer(gl.FRAMEBUFFER, dst.fb);
845
+ gl.bindTexture(gl.TEXTURE_2D, src.tex);
846
+ gl.uniform2f(bl.u.uDir, dx, dy);
847
+ gl.uniform2f(bl.u.uOut, dst.w, dst.h);
848
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
849
+ };
850
+ const k = this.dpr / 1.25;
851
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.rtBg.fb);
852
+ gl.viewport(0, 0, this.rtBg.w, this.rtBg.h);
853
+ gl.useProgram(this.progs.bg.pr);
854
+ setCommon(this.progs.bg.u, this.dpr);
855
+ gl.activeTexture(gl.TEXTURE7);
856
+ if (this.srcEl && this.imgTex) {
857
+ const el = this.srcEl;
858
+ const ready = el instanceof HTMLCanvasElement ? el.width > 0 : el.readyState >= 2;
859
+ if (ready) {
860
+ const w = el instanceof HTMLCanvasElement ? el.width : el.videoWidth;
861
+ const h = el instanceof HTMLCanvasElement ? el.height : el.videoHeight;
862
+ gl.bindTexture(gl.TEXTURE_2D, this.imgTex);
863
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
864
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, el);
865
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
866
+ this.imgRes = [w, h];
867
+ }
868
+ }
869
+ gl.bindTexture(gl.TEXTURE_2D, this.imgTex);
870
+ gl.uniform1i(this.progs.bg.u.uImg, 7);
871
+ gl.uniform2f(this.progs.bg.u.uImgRes, this.imgRes[0], this.imgRes[1]);
872
+ gl.uniform1f(this.progs.bg.u.uHasImg, this.imgTex ? 1 : 0);
873
+ const bc = this.bgColor;
874
+ gl.uniform3f(this.progs.bg.u.uBgColor, bc?.[0] ?? 0, bc?.[1] ?? 0, bc?.[2] ?? 0);
875
+ gl.uniform1f(this.progs.bg.u.uBgSolid, bc ? 1 : 0);
876
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
877
+ gl.activeTexture(gl.TEXTURE0);
878
+ gl.viewport(0, 0, this.rtA.w, this.rtA.h);
879
+ gl.useProgram(bl.pr);
880
+ gl.uniform1i(bl.u.uTex, 0);
881
+ gl.activeTexture(gl.TEXTURE0);
882
+ if (list.some((_, i) => this.FR[i] > 2e-3)) {
883
+ pass(this.rtBg, this.rtBgM, 1.5 * k, 0);
884
+ pass(this.rtBgM, this.rtB, 0, 2.5 * k);
885
+ pass(this.rtB, this.rtBgM, 2.5 * k, 0);
886
+ pass(this.rtBgM, this.rtB, 0, 2.5 * k);
887
+ pass(this.rtB, this.rtBgM, 2.5 * k, 0);
888
+ pass(this.rtBgM, this.rtB, 0, 2.5 * k);
889
+ pass(this.rtB, this.rtBgM, 0, 0);
890
+ pass(this.rtBgM, this.rtB, 5 * k, 0);
891
+ pass(this.rtB, this.rtBgH, 0, 5 * k);
892
+ pass(this.rtBgH, this.rtB, 5 * k, 0);
893
+ pass(this.rtB, this.rtBgH, 0, 5 * k);
894
+ pass(this.rtBgH, this.rtB, 5 * k, 0);
895
+ pass(this.rtB, this.rtBgH, 0, 5 * k);
896
+ }
897
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.rtA.fb);
898
+ gl.viewport(0, 0, this.rtA.w, this.rtA.h);
899
+ gl.useProgram(this.progs.mask.pr);
900
+ setCommon(this.progs.mask.u, this.dpr * MASK_SCALE);
901
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
902
+ gl.bindFramebuffer(gl.FRAMEBUFFER, this.mrtFb);
903
+ gl.useProgram(this.progs.tint.pr);
904
+ setCommon(this.progs.tint.u, this.dpr * MASK_SCALE);
905
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
906
+ gl.useProgram(bl.pr);
907
+ gl.uniform1i(bl.u.uTex, 0);
908
+ gl.activeTexture(gl.TEXTURE0);
909
+ const so = 1.4 * k * s.smoothness;
910
+ for (let n = 0; n < 3; n++) {
911
+ pass(this.rtA, this.rtB, so, 0);
912
+ pass(this.rtB, this.rtA, 0, so);
913
+ }
914
+ for (let n = 0; n < 3; n++) {
915
+ pass(this.rtT, this.rtB, so, 0);
916
+ pass(this.rtB, this.rtT, 0, so);
917
+ }
918
+ for (let n = 0; n < 3; n++) {
919
+ pass(this.rtFr, this.rtB, so, 0);
920
+ pass(this.rtB, this.rtFr, 0, so);
921
+ }
922
+ pass(this.rtA, this.rtB, 2 * k, 0);
923
+ pass(this.rtB, this.rtC, 0, 2 * k);
924
+ for (let n = 0; n < 3; n++) {
925
+ pass(this.rtC, this.rtB, 2 * k, 0);
926
+ pass(this.rtB, this.rtC, 0, 2 * k);
927
+ }
928
+ const c = this.progs.comp;
929
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
930
+ gl.viewport(0, 0, this.canvas.width, this.canvas.height);
931
+ gl.useProgram(c.pr);
932
+ setCommon(c.u, this.dpr);
933
+ gl.uniform1f(c.u.uRefract, s.refraction);
934
+ gl.uniform1f(c.u.uDisp, s.dispersion);
935
+ gl.uniform1f(c.u.uRim, s.rim);
936
+ const mode = s.rimColor === "iridescent" ? 0 : s.rimColor === "tint" ? 2 : 1;
937
+ gl.uniform1f(c.u.uRimMode, mode);
938
+ gl.uniform3fv(c.u.uRimColor, mode === 1 ? this.rgb(s.rimColor) : [1, 1, 1]);
939
+ gl.uniform1f(c.u.uRimWidth, s.rimWidth);
940
+ gl.uniform1f(c.u.uSpec, s.highlight);
941
+ gl.uniform1f(c.u.uHair, s.edgeLine);
942
+ gl.activeTexture(gl.TEXTURE0);
943
+ gl.bindTexture(gl.TEXTURE_2D, this.rtC.tex);
944
+ gl.activeTexture(gl.TEXTURE1);
945
+ gl.bindTexture(gl.TEXTURE_2D, this.rtA.tex);
946
+ gl.activeTexture(gl.TEXTURE2);
947
+ gl.bindTexture(gl.TEXTURE_2D, this.rtT.tex);
948
+ gl.activeTexture(gl.TEXTURE3);
949
+ gl.bindTexture(gl.TEXTURE_2D, this.rtBg.tex);
950
+ gl.activeTexture(gl.TEXTURE4);
951
+ gl.bindTexture(gl.TEXTURE_2D, this.rtBgM.tex);
952
+ gl.activeTexture(gl.TEXTURE5);
953
+ gl.bindTexture(gl.TEXTURE_2D, this.rtBgH.tex);
954
+ gl.activeTexture(gl.TEXTURE6);
955
+ gl.bindTexture(gl.TEXTURE_2D, this.rtFr.tex);
956
+ gl.uniform1i(c.u.uBg, 3);
957
+ gl.uniform1i(c.u.uBgM, 4);
958
+ gl.uniform1i(c.u.uBgH, 5);
959
+ gl.uniform1i(c.u.uFrost, 6);
960
+ gl.uniform1i(c.u.uH, 0);
961
+ gl.uniform1i(c.u.uS, 1);
962
+ gl.uniform1i(c.u.uTint, 2);
963
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
964
+ gl.activeTexture(gl.TEXTURE0);
965
+ }
966
+ rgb(hex) {
967
+ let c = this.tintCache.get(hex);
968
+ if (!c) {
969
+ c = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(hex) ? hexToRgb(hex) : [1, 1, 1];
970
+ this.tintCache.set(hex, c);
971
+ }
972
+ return c;
973
+ }
974
+ program(fsrc) {
975
+ const gl = this.gl;
976
+ const compile = (type, src) => {
977
+ const sh = gl.createShader(type);
978
+ gl.shaderSource(sh, src);
979
+ gl.compileShader(sh);
980
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(sh) || "shader compile failed");
981
+ return sh;
982
+ };
983
+ const pr = gl.createProgram();
984
+ gl.attachShader(pr, compile(gl.VERTEX_SHADER, vert));
985
+ gl.attachShader(pr, compile(gl.FRAGMENT_SHADER, fsrc));
986
+ gl.bindAttribLocation(pr, 0, "p");
987
+ gl.linkProgram(pr);
988
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(pr) || "program link failed");
989
+ const u = {};
990
+ UNIFORMS.forEach((n) => {
991
+ u[n] = gl.getUniformLocation(pr, n);
992
+ });
993
+ return { pr, u };
994
+ }
995
+ target() {
996
+ const gl = this.gl;
997
+ const tex = gl.createTexture();
998
+ gl.bindTexture(gl.TEXTURE_2D, tex);
999
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
1000
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
1001
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
1002
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
1003
+ return { tex, fb: gl.createFramebuffer(), w: 0, h: 0 };
1004
+ }
1005
+ };
1006
+
1007
+ // src/PlasmaProvider.tsx
1008
+ import { jsx, jsxs } from "react/jsx-runtime";
1009
+ var noop = () => {
1010
+ };
1011
+ var PlasmaContext = createContext({
1012
+ renderer: null,
1013
+ tint: "#ffffff",
1014
+ opacity: 0,
1015
+ frost: 0,
1016
+ radius: 26,
1017
+ supported: false,
1018
+ grid: 24,
1019
+ magnet: 40,
1020
+ spring: { stiffness: 170, damping: 16 },
1021
+ reducedMotion: false,
1022
+ pulse: noop,
1023
+ bump: noop
1024
+ });
1025
+ var usePlasma = () => useContext(PlasmaContext);
1026
+ var FALLBACK_CSS = `
1027
+ .plasma-glass{box-sizing:border-box}
1028
+ .plasma-fallback{
1029
+ background:linear-gradient(160deg,rgb(255 255 255/.14),rgb(255 255 255/.05));
1030
+ -webkit-backdrop-filter:blur(18px) saturate(160%);backdrop-filter:blur(18px) saturate(160%);
1031
+ border:1px solid rgb(255 255 255/.22);
1032
+ box-shadow:inset 0 1px 0 rgb(255 255 255/.25),0 20px 40px -20px rgb(0 0 0/.5);
1033
+ }
1034
+ .plasma-glass[data-plasma-draggable]{touch-action:none;cursor:grab}
1035
+ .plasma-glass[data-plasma-dragging]{cursor:grabbing;user-select:none}
1036
+ `;
1037
+ function useReducedMotion() {
1038
+ const [reduced, setReduced] = useState(false);
1039
+ useEffect(() => {
1040
+ const q = matchMedia("(prefers-reduced-motion: reduce)");
1041
+ const on = () => setReduced(q.matches);
1042
+ on();
1043
+ q.addEventListener("change", on);
1044
+ return () => q.removeEventListener("change", on);
1045
+ }, []);
1046
+ return reduced;
1047
+ }
1048
+ function PlasmaProvider({
1049
+ children,
1050
+ mood = "tidal",
1051
+ theme = "auto",
1052
+ blend,
1053
+ refraction = 1,
1054
+ dispersion = 1,
1055
+ rim = 1,
1056
+ smoothness = 1,
1057
+ background,
1058
+ radius = 26,
1059
+ tint = "#ffffff",
1060
+ opacity = 0,
1061
+ frost = 0,
1062
+ elevation = 0.35,
1063
+ viscosity = 0.5,
1064
+ stretch = 1,
1065
+ flow = 0,
1066
+ rimColor = "iridescent",
1067
+ rimWidth = 1,
1068
+ highlight = 1,
1069
+ edgeLine = 1,
1070
+ pointerDrop = true,
1071
+ ambientDrops = false,
1072
+ grid = 24,
1073
+ magnet = 40,
1074
+ quality = 1.25,
1075
+ maxSurfaces = 16,
1076
+ zIndex = -1
1077
+ }) {
1078
+ const canvasRef = useRef(null);
1079
+ const [renderer, setRenderer] = useState(null);
1080
+ const [supported, setSupported] = useState(true);
1081
+ const reducedMotion = useReducedMotion();
1082
+ const m = resolveMood(mood);
1083
+ const settings = {
1084
+ colors: m.colors,
1085
+ blend: blend ?? m.blend,
1086
+ refraction,
1087
+ dispersion,
1088
+ rim,
1089
+ smoothness,
1090
+ pointerDrop: pointerDrop && !reducedMotion,
1091
+ ambientDrops,
1092
+ theme,
1093
+ quality,
1094
+ reducedMotion,
1095
+ tint,
1096
+ opacity,
1097
+ rimColor,
1098
+ rimWidth,
1099
+ highlight,
1100
+ edgeLine,
1101
+ viscosity,
1102
+ stretch,
1103
+ flow,
1104
+ frost,
1105
+ elevation,
1106
+ maxSurfaces,
1107
+ background: background ?? null
1108
+ };
1109
+ const settingsRef = useRef(settings);
1110
+ settingsRef.current = settings;
1111
+ useLayoutEffect(() => {
1112
+ const r = PlasmaRenderer.create(canvasRef.current, settingsRef.current);
1113
+ if (!r) {
1114
+ setSupported(false);
1115
+ return;
1116
+ }
1117
+ setRenderer(r);
1118
+ return () => {
1119
+ r.destroy();
1120
+ setRenderer(null);
1121
+ };
1122
+ }, []);
1123
+ useEffect(() => {
1124
+ renderer?.configure(settings);
1125
+ }, [
1126
+ renderer,
1127
+ m.colors.join(),
1128
+ settings.blend,
1129
+ refraction,
1130
+ dispersion,
1131
+ rim,
1132
+ smoothness,
1133
+ settings.pointerDrop,
1134
+ ambientDrops,
1135
+ theme,
1136
+ quality,
1137
+ reducedMotion,
1138
+ tint,
1139
+ opacity,
1140
+ rimColor,
1141
+ rimWidth,
1142
+ highlight,
1143
+ edgeLine,
1144
+ viscosity,
1145
+ stretch,
1146
+ flow,
1147
+ frost,
1148
+ elevation,
1149
+ background
1150
+ ]);
1151
+ const vis = Math.min(Math.max(viscosity, 0), 1);
1152
+ const stiffK = vis < 0.5 ? 1.6 - 1.2 * vis : 1 - 1.1 * (vis - 0.5);
1153
+ const dampK = vis < 0.5 ? 0.6 + 0.8 * vis : 1 + 1.6 * (vis - 0.5);
1154
+ const spring = { stiffness: m.spring.stiffness * stiffK, damping: m.spring.damping * dampK * Math.sqrt(stiffK) };
1155
+ const value = useMemo(() => ({
1156
+ renderer,
1157
+ tint,
1158
+ opacity,
1159
+ frost,
1160
+ radius,
1161
+ supported,
1162
+ grid,
1163
+ magnet,
1164
+ spring,
1165
+ reducedMotion,
1166
+ pulse: (x, y, s) => renderer?.pulse(x, y, s),
1167
+ bump: (e) => renderer?.bump(e)
1168
+ }), [renderer, tint, opacity, frost, radius, supported, grid, magnet, spring.stiffness, spring.damping, reducedMotion]);
1169
+ return /* @__PURE__ */ jsxs(PlasmaContext.Provider, { value, children: [
1170
+ /* @__PURE__ */ jsx("style", { children: FALLBACK_CSS }),
1171
+ /* @__PURE__ */ jsx(
1172
+ "canvas",
1173
+ {
1174
+ ref: canvasRef,
1175
+ "aria-hidden": "true",
1176
+ style: {
1177
+ position: "fixed",
1178
+ inset: 0,
1179
+ width: "100%",
1180
+ height: "100%",
1181
+ zIndex,
1182
+ pointerEvents: "none",
1183
+ display: supported ? "block" : "none"
1184
+ }
1185
+ }
1186
+ ),
1187
+ children
1188
+ ] });
1189
+ }
1190
+
1191
+ // src/Plasma.tsx
1192
+ import { forwardRef, useCallback, useEffect as useEffect2, useLayoutEffect as useLayoutEffect2, useRef as useRef2, useState as useState2 } from "react";
1193
+
1194
+ // src/spring.ts
1195
+ var now = () => typeof performance !== "undefined" ? performance.now() : Date.now();
1196
+ function springValue(initial) {
1197
+ let v = initial;
1198
+ const hist = [[0, initial]];
1199
+ const subs = /* @__PURE__ */ new Set();
1200
+ return {
1201
+ get: () => v,
1202
+ set(next) {
1203
+ if (next === v) return;
1204
+ v = next;
1205
+ const t = now();
1206
+ const last = hist[hist.length - 1];
1207
+ if (t - last[0] < 1) last[1] = next;
1208
+ else {
1209
+ hist.push([t, next]);
1210
+ if (hist.length > 6) hist.shift();
1211
+ }
1212
+ subs.forEach((fn) => fn(v));
1213
+ },
1214
+ on(fn) {
1215
+ subs.add(fn);
1216
+ return () => subs.delete(fn);
1217
+ },
1218
+ getVelocity() {
1219
+ const t = now();
1220
+ const newest = hist[hist.length - 1];
1221
+ if (t - newest[0] > 100) return 0;
1222
+ for (let i = 0; i < hist.length - 1; i++) {
1223
+ const [rt, rv] = hist[i];
1224
+ if (newest[0] - rt <= 100) {
1225
+ const dt = newest[0] - rt;
1226
+ return dt >= 4 ? (newest[1] - rv) / dt * 1e3 : 0;
1227
+ }
1228
+ }
1229
+ return 0;
1230
+ }
1231
+ };
1232
+ }
1233
+ function animateSpring(value, to, o) {
1234
+ let x = value.get();
1235
+ let vel = o.velocity ?? 0;
1236
+ let raf = 0;
1237
+ let last = now();
1238
+ const tick = () => {
1239
+ const t = now();
1240
+ const dt = Math.min((t - last) / 1e3, 0.05);
1241
+ last = t;
1242
+ const steps = Math.max(1, Math.ceil(dt * 240));
1243
+ const h = dt / steps;
1244
+ for (let i = 0; i < steps; i++) {
1245
+ vel += (o.stiffness * (to - x) - o.damping * vel) * h;
1246
+ x += vel * h;
1247
+ }
1248
+ if (Math.abs(vel) < 1 && Math.abs(to - x) < 0.05) {
1249
+ value.set(to);
1250
+ return;
1251
+ }
1252
+ value.set(x);
1253
+ raf = requestAnimationFrame(tick);
1254
+ };
1255
+ raf = requestAnimationFrame(tick);
1256
+ return { stop: () => cancelAnimationFrame(raf) };
1257
+ }
1258
+
1259
+ // src/snap.ts
1260
+ function snapBox(b, others, o) {
1261
+ const { grid, magnet, bounds } = o;
1262
+ const inset = o.inset ?? 0;
1263
+ const pick = (cur, cands) => {
1264
+ let best = null, bd = magnet;
1265
+ for (const v of cands) {
1266
+ const d = Math.abs(v - cur);
1267
+ if (d < bd) {
1268
+ bd = d;
1269
+ best = v;
1270
+ }
1271
+ }
1272
+ return best;
1273
+ };
1274
+ const nearX = (n) => b.t < n.t + n.h + magnet && b.t + b.h > n.t - magnet;
1275
+ const nearY = (n) => b.l < n.l + n.w + magnet && b.l + b.w > n.l - magnet;
1276
+ let l = pick(b.l, others.flatMap((n) => nearX(n) ? [n.l, n.l + n.w - b.w, n.l + n.w, n.l - b.w] : []));
1277
+ let t = pick(b.t, others.flatMap((n) => nearY(n) ? [n.t, n.t + n.h - b.h, n.t + n.h, n.t - b.h] : []));
1278
+ if (l === null) l = grid > 0 ? bounds.l + Math.round((b.l - bounds.l) / grid) * grid : b.l;
1279
+ if (t === null) t = grid > 0 ? bounds.t + Math.round((b.t - bounds.t) / grid) * grid : b.t;
1280
+ for (const n of others) {
1281
+ const ox = Math.min(l + b.w, n.l + n.w) - Math.max(l, n.l);
1282
+ const oy = Math.min(t + b.h, n.t + n.h) - Math.max(t, n.t);
1283
+ if (ox > 2 && oy > 2) {
1284
+ if (ox < oy) l = l + b.w / 2 < n.l + n.w / 2 ? n.l - b.w : n.l + n.w;
1285
+ else t = t + b.h / 2 < n.t + n.h / 2 ? n.t - b.h : n.t + n.h;
1286
+ }
1287
+ }
1288
+ const clamp = (v, lo, hi) => Math.min(Math.max(v, lo), Math.max(lo, hi));
1289
+ return {
1290
+ l: clamp(l, bounds.l + inset, bounds.l + bounds.w - b.w - inset),
1291
+ t: clamp(t, bounds.t + inset, bounds.t + bounds.h - b.h - inset)
1292
+ };
1293
+ }
1294
+ function boxGap(a, b) {
1295
+ const gx = Math.max(a.l - (b.l + b.w), b.l - (a.l + a.w));
1296
+ const gy = Math.max(a.t - (b.t + b.h), b.t - (a.t + a.h));
1297
+ return Math.max(gx, gy);
1298
+ }
1299
+
1300
+ // src/Plasma.tsx
1301
+ import { jsx as jsx2 } from "react/jsx-runtime";
1302
+ var NO_DRAG = "button,a,input,textarea,select,label,[contenteditable],[data-plasma-nodrag]";
1303
+ function fallbackTint(hex, a) {
1304
+ if (!(a > 0) || !/^#([0-9a-f]{6})$/i.test(hex)) return null;
1305
+ const n = parseInt(hex.slice(1), 16);
1306
+ return { backgroundColor: `rgb(${n >> 16} ${n >> 8 & 255} ${n & 255} / ${Math.min(a, 1) * 0.85})` };
1307
+ }
1308
+ function assignRef(ref, v) {
1309
+ if (typeof ref === "function") ref(v);
1310
+ else if (ref) ref.current = v;
1311
+ }
1312
+ var Plasma = forwardRef(function Plasma2({
1313
+ as: Comp = "div",
1314
+ radius,
1315
+ lean = 10,
1316
+ tint,
1317
+ opacity,
1318
+ frost,
1319
+ elevation,
1320
+ fuse,
1321
+ padding,
1322
+ draggable = false,
1323
+ snap = true,
1324
+ bounds,
1325
+ offset,
1326
+ defaultOffset,
1327
+ onDragStart,
1328
+ onDragEnd,
1329
+ onJoinChange,
1330
+ className,
1331
+ style,
1332
+ children,
1333
+ onPointerDown,
1334
+ onKeyDown,
1335
+ tabIndex,
1336
+ ...rest
1337
+ }, ref) {
1338
+ const plasma = usePlasma();
1339
+ const r = radius ?? plasma.radius;
1340
+ const el = useRef2(null);
1341
+ const handle = useRef2(null);
1342
+ const [x] = useState2(() => springValue(offset?.x ?? defaultOffset?.x ?? 0));
1343
+ const [y] = useState2(() => springValue(offset?.y ?? defaultOffset?.y ?? 0));
1344
+ const dest = useRef2({ x: x.get(), y: y.get() });
1345
+ const anims = useRef2([]);
1346
+ const [dragging, setDragging] = useState2(false);
1347
+ const positioned = draggable || !!offset || !!defaultOffset;
1348
+ const joinCb = useRef2(onJoinChange);
1349
+ joinCb.current = onJoinChange;
1350
+ const [sides, setSides] = useState2({ top: false, right: false, bottom: false, left: false });
1351
+ const plasmaRef = useRef2(plasma);
1352
+ plasmaRef.current = plasma;
1353
+ const setRef = useCallback((node) => {
1354
+ el.current = node;
1355
+ assignRef(ref, node);
1356
+ }, [ref]);
1357
+ useLayoutEffect2(() => {
1358
+ const node = el.current, ren = plasma.renderer;
1359
+ if (!node || !ren) return;
1360
+ 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);
1361
+ if (positioned) {
1362
+ h.setLayoutBox(() => {
1363
+ const rect = node.getBoundingClientRect();
1364
+ const w = node.offsetWidth, hh = node.offsetHeight;
1365
+ const lo = h.leanOffset();
1366
+ return {
1367
+ l: rect.left + (rect.width - w) / 2 - lo.x - x.get() + dest.current.x,
1368
+ t: rect.top + (rect.height - hh) / 2 - lo.y - y.get() + dest.current.y,
1369
+ w,
1370
+ h: hh
1371
+ };
1372
+ });
1373
+ }
1374
+ handle.current = h;
1375
+ return () => {
1376
+ h.remove();
1377
+ handle.current = null;
1378
+ };
1379
+ }, [plasma.renderer, positioned]);
1380
+ useEffect2(() => {
1381
+ handle.current?.update({ radius: r, lean: lean || 0, tint: tint ?? null, opacity: opacity ?? null, frost: frost ?? null, elevation: elevation ?? null, fuse });
1382
+ }, [r, lean, tint, opacity, frost, elevation, fuse]);
1383
+ useLayoutEffect2(() => {
1384
+ const node = el.current;
1385
+ if (!node || !positioned) return;
1386
+ const apply = () => {
1387
+ node.style.transform = `translate3d(${x.get()}px, ${y.get()}px, 0)`;
1388
+ };
1389
+ apply();
1390
+ const a = x.on(apply), b = y.on(apply);
1391
+ return () => {
1392
+ a();
1393
+ b();
1394
+ };
1395
+ }, [positioned]);
1396
+ const springTo = useCallback((tx, ty, vx = 0, vy = 0) => {
1397
+ dest.current = { x: tx, y: ty };
1398
+ anims.current.forEach((a) => a.stop());
1399
+ const { spring, reducedMotion } = plasmaRef.current;
1400
+ if (reducedMotion) {
1401
+ x.set(tx);
1402
+ y.set(ty);
1403
+ anims.current = [];
1404
+ return;
1405
+ }
1406
+ const opts = { stiffness: spring.stiffness, damping: spring.damping };
1407
+ anims.current = [animateSpring(x, tx, { ...opts, velocity: vx }), animateSpring(y, ty, { ...opts, velocity: vy })];
1408
+ }, []);
1409
+ useEffect2(() => {
1410
+ if (!offset) return;
1411
+ if (offset.x === dest.current.x && offset.y === dest.current.y) return;
1412
+ springTo(offset.x, offset.y);
1413
+ }, [offset?.x, offset?.y]);
1414
+ useEffect2(() => () => anims.current.forEach((a) => a.stop()), []);
1415
+ const boundsBox = () => {
1416
+ const b = bounds?.current?.getBoundingClientRect();
1417
+ return b ? { l: b.left, t: b.top, w: b.width, h: b.height } : { l: 0, t: 0, w: innerWidth, h: innerHeight };
1418
+ };
1419
+ const settle = (proposed, vx = 0, vy = 0) => {
1420
+ const node = el.current, h = handle.current;
1421
+ const { grid, magnet, renderer } = plasmaRef.current;
1422
+ let target = proposed;
1423
+ if (snap && h && renderer) {
1424
+ const rect = node.getBoundingClientRect();
1425
+ const w = node.offsetWidth, hh = node.offsetHeight;
1426
+ const lo = h.leanOffset();
1427
+ const baseL = rect.left + (rect.width - w) / 2 - lo.x - x.get();
1428
+ const baseT = rect.top + (rect.height - hh) / 2 - lo.y - y.get();
1429
+ const s = snapBox(
1430
+ { l: baseL + proposed.x, t: baseT + proposed.y, w, h: hh },
1431
+ renderer.layoutBoxes(h.id, true),
1432
+ { grid, magnet, bounds: boundsBox(), inset: bounds ? 0 : 8 }
1433
+ );
1434
+ target = { x: s.l - baseL, y: s.t - baseT };
1435
+ }
1436
+ springTo(target.x, target.y, vx, vy);
1437
+ onDragEnd?.(target);
1438
+ };
1439
+ const handlePointerDown = (e) => {
1440
+ onPointerDown?.(e);
1441
+ if (!draggable || e.defaultPrevented || e.button !== 0) return;
1442
+ if (e.target.closest(NO_DRAG)) return;
1443
+ const node = el.current;
1444
+ node.setPointerCapture(e.pointerId);
1445
+ anims.current.forEach((a) => a.stop());
1446
+ const start = { px: e.clientX, py: e.clientY, x: x.get(), y: y.get() };
1447
+ const bb = boundsBox();
1448
+ const rect = node.getBoundingClientRect();
1449
+ const lo = handle.current?.leanOffset() ?? { x: 0, y: 0 };
1450
+ const baseL = rect.left + (rect.width - node.offsetWidth) / 2 - lo.x - start.x;
1451
+ const baseT = rect.top + (rect.height - node.offsetHeight) / 2 - lo.y - start.y;
1452
+ const minX = bb.l - baseL, maxX = bb.l + bb.w - node.offsetWidth - baseL;
1453
+ const minY = bb.t - baseT, maxY = bb.t + bb.h - node.offsetHeight - baseT;
1454
+ const clamp = (v, a, b) => Math.min(Math.max(v, a), Math.max(a, b));
1455
+ handle.current?.setDragging(true);
1456
+ setDragging(true);
1457
+ onDragStart?.();
1458
+ const move = (ev) => {
1459
+ x.set(clamp(start.x + ev.clientX - start.px, minX - 40, maxX + 40));
1460
+ y.set(clamp(start.y + ev.clientY - start.py, minY - 40, maxY + 40));
1461
+ dest.current = { x: x.get(), y: y.get() };
1462
+ plasmaRef.current.bump(Math.hypot(x.getVelocity(), y.getVelocity()) / 1800);
1463
+ };
1464
+ const up = () => {
1465
+ node.removeEventListener("pointermove", move);
1466
+ node.removeEventListener("pointerup", up);
1467
+ node.removeEventListener("pointercancel", up);
1468
+ handle.current?.setDragging(false);
1469
+ setDragging(false);
1470
+ const vx = x.getVelocity(), vy = y.getVelocity();
1471
+ settle({ x: x.get() + vx * 0.15, y: y.get() + vy * 0.15 }, vx, vy);
1472
+ };
1473
+ node.addEventListener("pointermove", move);
1474
+ node.addEventListener("pointerup", up);
1475
+ node.addEventListener("pointercancel", up);
1476
+ };
1477
+ const handleKeyDown = (e) => {
1478
+ onKeyDown?.(e);
1479
+ if (!draggable || e.defaultPrevented || e.target !== el.current) return;
1480
+ const step = plasmaRef.current.grid || 24;
1481
+ const d = { ArrowLeft: [-step, 0], ArrowRight: [step, 0], ArrowUp: [0, -step], ArrowDown: [0, step] }[e.key];
1482
+ if (!d) return;
1483
+ e.preventDefault();
1484
+ settle({ x: dest.current.x + d[0], y: dest.current.y + d[1] });
1485
+ };
1486
+ const classes = ["plasma-glass", plasma.supported ? "" : "plasma-fallback", className].filter(Boolean).join(" ");
1487
+ return /* @__PURE__ */ jsx2(
1488
+ Comp,
1489
+ {
1490
+ ref: setRef,
1491
+ className: classes,
1492
+ style: {
1493
+ borderRadius: r,
1494
+ ...padding != null && {
1495
+ 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`,
1496
+ transition: "padding 250ms ease"
1497
+ },
1498
+ ...plasma.supported ? null : fallbackTint(tint ?? plasma.tint, opacity ?? plasma.opacity),
1499
+ ...style
1500
+ },
1501
+ "data-plasma-draggable": draggable || void 0,
1502
+ "data-plasma-dragging": dragging || void 0,
1503
+ tabIndex: tabIndex ?? (draggable ? 0 : void 0),
1504
+ onPointerDown: handlePointerDown,
1505
+ onKeyDown: handleKeyDown,
1506
+ ...rest,
1507
+ children
1508
+ }
1509
+ );
1510
+ });
1511
+ export {
1512
+ DEFAULT_MAX_SHAPES,
1513
+ Plasma,
1514
+ PlasmaProvider,
1515
+ PlasmaRenderer,
1516
+ boxGap,
1517
+ makeShaders,
1518
+ moods,
1519
+ resolveMood,
1520
+ snapBox,
1521
+ usePlasma
1522
+ };