@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/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/dist/Plasma.d.ts +41 -0
- package/dist/PlasmaProvider.d.ts +79 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1522 -0
- package/dist/moods.d.ts +40 -0
- package/dist/renderer.d.ts +143 -0
- package/dist/shaders.d.ts +14 -0
- package/dist/snap.d.ts +27 -0
- package/dist/spring.d.ts +25 -0
- package/package.json +74 -0
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
|
+
};
|