glass-pulse-fx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/chunk-GF5DSI6Z.js +1360 -0
- package/dist/chunk-GF5DSI6Z.js.map +1 -0
- package/dist/core.cjs +1371 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +49 -0
- package/dist/core.d.ts +49 -0
- package/dist/core.js +3 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +1492 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +32 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/presets.cjs +326 -0
- package/dist/presets.cjs.map +1 -0
- package/dist/presets.d.cts +28 -0
- package/dist/presets.d.ts +28 -0
- package/dist/presets.js +313 -0
- package/dist/presets.js.map +1 -0
- package/dist/types-qQT95c-N.d.cts +157 -0
- package/dist/types-qQT95c-N.d.ts +157 -0
- package/package.json +86 -0
package/dist/core.cjs
ADDED
|
@@ -0,0 +1,1371 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/engine/perf.ts
|
|
4
|
+
var GL_SIZE = 384;
|
|
5
|
+
var DPR = typeof window !== "undefined" ? Math.min(2, window.devicePixelRatio || 1) : 1;
|
|
6
|
+
var DEFAULT_FPS = 30;
|
|
7
|
+
function frameMsForFps(fps = DEFAULT_FPS) {
|
|
8
|
+
return 1e3 / fps;
|
|
9
|
+
}
|
|
10
|
+
var CROP_W = 140;
|
|
11
|
+
var CROP_H = 40;
|
|
12
|
+
var MIN_SAMPLE_SPAN = 0.9;
|
|
13
|
+
var SHADER_SCALE = {
|
|
14
|
+
pill: 1.6,
|
|
15
|
+
circle: 1.3,
|
|
16
|
+
rect: 1.7,
|
|
17
|
+
tag: 1.25,
|
|
18
|
+
card: 2.4,
|
|
19
|
+
icon: 1.15
|
|
20
|
+
};
|
|
21
|
+
var BLOOM_MAX = 240;
|
|
22
|
+
|
|
23
|
+
// src/engine/color.ts
|
|
24
|
+
var HEX = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i;
|
|
25
|
+
function isHexColor(c) {
|
|
26
|
+
return HEX.test(c.trim());
|
|
27
|
+
}
|
|
28
|
+
function expand(h) {
|
|
29
|
+
let s = h.slice(1);
|
|
30
|
+
if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
|
|
31
|
+
return parseInt(s, 16);
|
|
32
|
+
}
|
|
33
|
+
function hexToRgb(h) {
|
|
34
|
+
const n = expand(h);
|
|
35
|
+
return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
|
|
36
|
+
}
|
|
37
|
+
function hexToRgba(h, a) {
|
|
38
|
+
const n = expand(h);
|
|
39
|
+
return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`;
|
|
40
|
+
}
|
|
41
|
+
function withAlpha(color, a) {
|
|
42
|
+
return isHexColor(color) ? hexToRgba(color, a) : color;
|
|
43
|
+
}
|
|
44
|
+
function parseColor(c) {
|
|
45
|
+
const s = c.trim();
|
|
46
|
+
if (isHexColor(s)) {
|
|
47
|
+
const n = expand(s);
|
|
48
|
+
return { r: n >> 16 & 255, g: n >> 8 & 255, b: n & 255, a: 1 };
|
|
49
|
+
}
|
|
50
|
+
const m = s.match(/^rgba?\(([^)]+)\)$/i);
|
|
51
|
+
if (!m) return { r: 0, g: 0, b: 0, a: 0 };
|
|
52
|
+
const parts = m[1].split(/[,/]/).map((p) => parseFloat(p.trim()));
|
|
53
|
+
return { r: parts[0] || 0, g: parts[1] || 0, b: parts[2] || 0, a: parts[3] == null ? 1 : parts[3] };
|
|
54
|
+
}
|
|
55
|
+
function compositeOver(fg, bg) {
|
|
56
|
+
const a = fg.a + bg.a * (1 - fg.a);
|
|
57
|
+
if (a === 0) return { r: 0, g: 0, b: 0, a: 0 };
|
|
58
|
+
return {
|
|
59
|
+
r: (fg.r * fg.a + bg.r * bg.a * (1 - fg.a)) / a,
|
|
60
|
+
g: (fg.g * fg.a + bg.g * bg.a * (1 - fg.a)) / a,
|
|
61
|
+
b: (fg.b * fg.a + bg.b * bg.a * (1 - fg.a)) / a,
|
|
62
|
+
a
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function rgbToHex(c) {
|
|
66
|
+
const h = (n) => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, "0");
|
|
67
|
+
return `#${h(c.r)}${h(c.g)}${h(c.b)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/engine/effects/common.ts
|
|
71
|
+
var VERT = `attribute vec2 a_position; void main(){ gl_Position = vec4(a_position,0.0,1.0); }`;
|
|
72
|
+
var COMMON_GLSL = `
|
|
73
|
+
precision highp float;
|
|
74
|
+
uniform vec2 u_resolution;
|
|
75
|
+
uniform float u_time;
|
|
76
|
+
uniform vec3 u_color1,u_color2,u_color3,u_color4,u_color5;
|
|
77
|
+
uniform float u_count,u_speed,u_scale,u_bright;
|
|
78
|
+
|
|
79
|
+
vec3 paneColor(int i){
|
|
80
|
+
if(i<=0) return u_color1;
|
|
81
|
+
else if(i==1) return u_color2;
|
|
82
|
+
else if(i==2) return u_color3;
|
|
83
|
+
else if(i==3) return u_color4;
|
|
84
|
+
return u_color5;
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
var COMMON_UNIFORMS = [
|
|
88
|
+
"u_resolution",
|
|
89
|
+
"u_time",
|
|
90
|
+
"u_color1",
|
|
91
|
+
"u_color2",
|
|
92
|
+
"u_color3",
|
|
93
|
+
"u_color4",
|
|
94
|
+
"u_color5",
|
|
95
|
+
"u_count",
|
|
96
|
+
"u_speed",
|
|
97
|
+
"u_scale",
|
|
98
|
+
"u_bright"
|
|
99
|
+
];
|
|
100
|
+
function uploadCommon(gl, U, p) {
|
|
101
|
+
for (let i = 0; i < 5; i++) {
|
|
102
|
+
const c = hexToRgb(p.colors[i] ?? "#000000");
|
|
103
|
+
gl.uniform3f(U[`u_color${i + 1}`], c[0], c[1], c[2]);
|
|
104
|
+
}
|
|
105
|
+
gl.uniform1f(U.u_count, Math.min(5, Math.max(1, p.colors.length)));
|
|
106
|
+
gl.uniform1f(U.u_speed, p.speed);
|
|
107
|
+
gl.uniform1f(U.u_scale, p.scale);
|
|
108
|
+
gl.uniform1f(U.u_bright, p.bright);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/engine/effects/panes.ts
|
|
112
|
+
var WARP_N = 24;
|
|
113
|
+
var VELOCITY_PRESETS = [
|
|
114
|
+
{ label: "Uniform", curve: [1, 1, 0.33, 1, 0.67, 1] },
|
|
115
|
+
{ label: "Ease out", curve: [0.8, 0.35, 0.33, 0.72, 0.67, 0.45] },
|
|
116
|
+
{ label: "Ease out+", curve: [1, 0.15, 0.25, 0.55, 0.6, 0.2] },
|
|
117
|
+
{ label: "Ease in", curve: [0.35, 0.8, 0.33, 0.45, 0.67, 0.72] },
|
|
118
|
+
{ label: "Slow middle", curve: [1, 1, 0.33, 0.35, 0.67, 0.35] },
|
|
119
|
+
{ label: "Fast middle", curve: [0.45, 0.45, 0.33, 1, 0.67, 1] }
|
|
120
|
+
];
|
|
121
|
+
var lutKey = -1;
|
|
122
|
+
var lut = new Float32Array(WARP_N);
|
|
123
|
+
function warpLUT(p) {
|
|
124
|
+
const key = VELOCITY_PRESETS[Math.round(p.velocity)] ? Math.round(p.velocity) : 0;
|
|
125
|
+
if (key === lutKey) return lut;
|
|
126
|
+
lutKey = key;
|
|
127
|
+
const [a, b, c0x, c0y, c1x, c1y] = VELOCITY_PRESETS[key].curve;
|
|
128
|
+
const bx = (t) => {
|
|
129
|
+
const it = 1 - t;
|
|
130
|
+
return 3 * it * it * t * c0x + 3 * it * t * t * c1x + t * t * t;
|
|
131
|
+
};
|
|
132
|
+
const by = (t) => {
|
|
133
|
+
const it = 1 - t;
|
|
134
|
+
return it * it * it * a + 3 * it * it * t * c0y + 3 * it * t * t * c1y + t * t * t * b;
|
|
135
|
+
};
|
|
136
|
+
const bdx = (t) => {
|
|
137
|
+
const it = 1 - t;
|
|
138
|
+
return 3 * it * it * c0x + 6 * it * t * (c1x - c0x) + 3 * t * t * (1 - c1x);
|
|
139
|
+
};
|
|
140
|
+
const clamp01 = (v) => v < 0 ? 0 : v > 1 ? 1 : v;
|
|
141
|
+
const speed = (s) => {
|
|
142
|
+
let t = clamp01(s);
|
|
143
|
+
for (let i = 0; i < 6; i++) {
|
|
144
|
+
const d = bdx(t);
|
|
145
|
+
if (Math.abs(d) < 1e-5) break;
|
|
146
|
+
t = clamp01(t - (bx(t) - s) / d);
|
|
147
|
+
}
|
|
148
|
+
return Math.max(by(t), 0.05);
|
|
149
|
+
};
|
|
150
|
+
const M = 128;
|
|
151
|
+
const cum = new Float64Array(M + 1);
|
|
152
|
+
let prev = 1 / speed(0);
|
|
153
|
+
for (let i = 1; i <= M; i++) {
|
|
154
|
+
const cur = 1 / speed(i / M);
|
|
155
|
+
cum[i] = cum[i - 1] + (prev + cur) * 0.5 / M;
|
|
156
|
+
prev = cur;
|
|
157
|
+
}
|
|
158
|
+
const total = cum[M] || 1;
|
|
159
|
+
for (let j = 0; j < WARP_N; j++) {
|
|
160
|
+
const fi = j / (WARP_N - 1) * M;
|
|
161
|
+
const i0 = Math.min(M - 1, Math.floor(fi));
|
|
162
|
+
lut[j] = (cum[i0] + (cum[i0 + 1] - cum[i0]) * (fi - i0)) / total;
|
|
163
|
+
}
|
|
164
|
+
return lut;
|
|
165
|
+
}
|
|
166
|
+
var FRAG = COMMON_GLSL + `
|
|
167
|
+
uniform float u_angle, u_rampIn, u_rampOut, u_interval, u_motion;
|
|
168
|
+
// Precomputed position-remap (the integral of 1/speed) as a 24-point lookup table. The
|
|
169
|
+
// velocity curve is the same for every pixel and only changes when the handles move, so
|
|
170
|
+
// the CPU bakes this once per change \u2014 the shader just reads it (no per-pixel solve).
|
|
171
|
+
uniform float u_warp[24];
|
|
172
|
+
uniform vec3 u_color; // colorSpread (along), colorSkew (perpendicular), colorDrift (time)
|
|
173
|
+
|
|
174
|
+
// smooth cyclic gradient through the palette stops (u_count = colors.length)
|
|
175
|
+
vec3 paletteSmooth(float gc){
|
|
176
|
+
int count = int(u_count + 0.5);
|
|
177
|
+
float x = fract(gc) * float(count);
|
|
178
|
+
float fi = floor(x);
|
|
179
|
+
int i0 = int(fi);
|
|
180
|
+
int i1 = i0 + 1; if(i1 >= count) i1 = 0;
|
|
181
|
+
return mix(paneColor(i0), paneColor(i1), x - fi);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
float sampleWarp(float s){
|
|
185
|
+
float fi = clamp(s, 0.0, 1.0) * 23.0;
|
|
186
|
+
float i0 = floor(fi);
|
|
187
|
+
float fr = fi - i0;
|
|
188
|
+
int idx = int(i0);
|
|
189
|
+
float lo = u_warp[0];
|
|
190
|
+
float hi = u_warp[23];
|
|
191
|
+
for(int k = 0; k < 24; k++){ // constant-index read (portable on WebGL1)
|
|
192
|
+
if(k == idx) lo = u_warp[k];
|
|
193
|
+
if(k == idx + 1) hi = u_warp[k];
|
|
194
|
+
}
|
|
195
|
+
return mix(lo, hi, fr);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
void main(){
|
|
199
|
+
vec2 uv = gl_FragCoord.xy / u_resolution;
|
|
200
|
+
vec2 p = uv - 0.5;
|
|
201
|
+
vec2 dir = vec2(cos(u_angle), sin(u_angle));
|
|
202
|
+
|
|
203
|
+
// The pane is always one unit wide; interval expands the pane+gap cycle instead of
|
|
204
|
+
// stealing width from the pane. So increasing interval adds transparent space after
|
|
205
|
+
// each pane while the pane's ramp/hold/ramp shape stays the same.
|
|
206
|
+
float interval = clamp(u_interval, 0.0, 0.95);
|
|
207
|
+
float gap = interval / max(1.0 - interval, 0.05);
|
|
208
|
+
float cycle = 1.0 + gap;
|
|
209
|
+
|
|
210
|
+
// Radial/orbit need no aspect correction here: the EffectDef declares 'isotropic'
|
|
211
|
+
// sampling for those modes, so the compositor crops an aspect-true window and
|
|
212
|
+
// field-space circles/angles render screen-true on every shape.
|
|
213
|
+
|
|
214
|
+
// band position along the mode's 1D coordinate, remapped through the velocity warp,
|
|
215
|
+
// plus the colour-field coords: ca along the motion, cp perpendicular to it
|
|
216
|
+
float pos; float ca; float cp;
|
|
217
|
+
if(u_motion < 1.5){
|
|
218
|
+
float u = dot(p, dir);
|
|
219
|
+
float s = (u_motion > 0.5) ? clamp(abs(u) * 2.0, 0.0, 1.0) : clamp(u + 0.5, 0.0, 1.0);
|
|
220
|
+
float w01 = sampleWarp(s);
|
|
221
|
+
pos = ((u_motion > 0.5) ? w01 * 0.5 : w01 - 0.5) * u_scale - u_time * u_speed;
|
|
222
|
+
ca = u + 0.5;
|
|
223
|
+
cp = dot(p, vec2(-dir.y, dir.x)) + 0.5;
|
|
224
|
+
} else if(u_motion < 2.5){
|
|
225
|
+
float r = length(p) * 2.0;
|
|
226
|
+
pos = sampleWarp(clamp(r, 0.0, 1.0)) * 0.5 * u_scale - u_time * u_speed;
|
|
227
|
+
ca = r;
|
|
228
|
+
cp = fract((atan(p.y, p.x) - u_angle) / 6.2831853);
|
|
229
|
+
} else {
|
|
230
|
+
float theta = fract((atan(p.y, p.x) - u_angle) / 6.2831853);
|
|
231
|
+
float spokes = max(1.0, floor(u_scale + 0.5)); // whole cycles -> no seam at the wrap
|
|
232
|
+
pos = sampleWarp(theta) * spokes * cycle - u_time * u_speed;
|
|
233
|
+
ca = theta;
|
|
234
|
+
cp = length(p) * 2.0;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// color field \u2014 sampled independently of the band index, so colour can vary within and
|
|
238
|
+
// across a band: along the motion (spread), perpendicular (skew), and over time (drift)
|
|
239
|
+
vec3 c = paletteSmooth(ca * u_color.x + cp * u_color.y + u_time * u_color.z);
|
|
240
|
+
|
|
241
|
+
float f = mod(pos, cycle);
|
|
242
|
+
float ri = clamp(u_rampIn, 0.001, 1.0);
|
|
243
|
+
float ro = clamp(u_rampOut, 0.001, 1.0);
|
|
244
|
+
float env = 0.0;
|
|
245
|
+
if(f < 1.0){
|
|
246
|
+
env = smoothstep(0.0, ri, f) * (1.0 - smoothstep(1.0 - ro, 1.0, f));
|
|
247
|
+
}
|
|
248
|
+
gl_FragColor = vec4(c * u_bright, env);
|
|
249
|
+
}`;
|
|
250
|
+
var DEFAULT_PANE_COLORS = ["#FF2D9B", "#7A5CFF", "#19C3FF", "#15E6A4", "#FFD23F"];
|
|
251
|
+
var dark = {
|
|
252
|
+
colors: [...DEFAULT_PANE_COLORS],
|
|
253
|
+
speed: 0.25,
|
|
254
|
+
scale: 1.6,
|
|
255
|
+
bright: 1,
|
|
256
|
+
colorSpread: 2,
|
|
257
|
+
colorSkew: 0,
|
|
258
|
+
colorDrift: 0,
|
|
259
|
+
velocity: 1,
|
|
260
|
+
// Ease out
|
|
261
|
+
rampIn: 0.25,
|
|
262
|
+
rampOut: 0.5,
|
|
263
|
+
interval: 0.4,
|
|
264
|
+
angle: 0,
|
|
265
|
+
motion: 0
|
|
266
|
+
};
|
|
267
|
+
var light = {
|
|
268
|
+
colors: [...DEFAULT_PANE_COLORS],
|
|
269
|
+
speed: 0.25,
|
|
270
|
+
scale: 1.6,
|
|
271
|
+
bright: 1,
|
|
272
|
+
colorSpread: 2,
|
|
273
|
+
colorSkew: 0,
|
|
274
|
+
colorDrift: 0,
|
|
275
|
+
velocity: 1,
|
|
276
|
+
// Ease out
|
|
277
|
+
rampIn: 0.25,
|
|
278
|
+
rampOut: 0.5,
|
|
279
|
+
interval: 0.4,
|
|
280
|
+
angle: 0,
|
|
281
|
+
motion: 0
|
|
282
|
+
};
|
|
283
|
+
var panes = {
|
|
284
|
+
id: "panes",
|
|
285
|
+
label: "Panes",
|
|
286
|
+
frag: FRAG,
|
|
287
|
+
uniforms: [
|
|
288
|
+
...COMMON_UNIFORMS,
|
|
289
|
+
"u_angle",
|
|
290
|
+
"u_warp[0]",
|
|
291
|
+
"u_color",
|
|
292
|
+
"u_rampIn",
|
|
293
|
+
"u_rampOut",
|
|
294
|
+
"u_interval",
|
|
295
|
+
"u_motion"
|
|
296
|
+
],
|
|
297
|
+
defaults: { dark, light },
|
|
298
|
+
upload(gl, U, p) {
|
|
299
|
+
uploadCommon(gl, U, p);
|
|
300
|
+
gl.uniform1f(U.u_angle, p.angle * Math.PI / 180);
|
|
301
|
+
gl.uniform1fv(U["u_warp[0]"], warpLUT(p));
|
|
302
|
+
const motion = Math.round(p.motion);
|
|
303
|
+
const spread = motion === 3 ? Math.round(p.colorSpread) : p.colorSpread;
|
|
304
|
+
const skew = motion === 2 ? Math.round(p.colorSkew) : p.colorSkew;
|
|
305
|
+
gl.uniform3f(U.u_color, spread, skew, p.colorDrift);
|
|
306
|
+
gl.uniform1f(U.u_rampIn, p.rampIn);
|
|
307
|
+
gl.uniform1f(U.u_rampOut, p.rampOut);
|
|
308
|
+
gl.uniform1f(U.u_interval, p.interval);
|
|
309
|
+
gl.uniform1f(U.u_motion, p.motion);
|
|
310
|
+
},
|
|
311
|
+
// radial/orbit draw circles and angles — crop aspect-true so they render screen-true
|
|
312
|
+
sampling(p) {
|
|
313
|
+
return Math.round(p.motion) >= 2 ? "isotropic" : "banded";
|
|
314
|
+
},
|
|
315
|
+
controls: [
|
|
316
|
+
{ kind: "colors", label: "Palette" },
|
|
317
|
+
{ kind: "slider", key: "colorSpread", label: "Color spread", min: 0, max: 6, step: 0.1 },
|
|
318
|
+
{ kind: "slider", key: "colorSkew", label: "Color skew", min: 0, max: 6, step: 0.1 },
|
|
319
|
+
{ kind: "slider", key: "colorDrift", label: "Color drift", min: -1, max: 1, step: 0.01 },
|
|
320
|
+
{
|
|
321
|
+
kind: "select",
|
|
322
|
+
key: "motion",
|
|
323
|
+
label: "Motion",
|
|
324
|
+
options: [
|
|
325
|
+
{ label: "Linear", value: 0 },
|
|
326
|
+
{ label: "Center", value: 1 },
|
|
327
|
+
{ label: "Radial", value: 2 },
|
|
328
|
+
{ label: "Orbit", value: 3 }
|
|
329
|
+
]
|
|
330
|
+
},
|
|
331
|
+
{ kind: "slider", key: "speed", label: "Speed", min: -2, max: 2, step: 0.01 },
|
|
332
|
+
{
|
|
333
|
+
kind: "select",
|
|
334
|
+
key: "velocity",
|
|
335
|
+
label: "Velocity",
|
|
336
|
+
options: VELOCITY_PRESETS.map((v, i) => ({ label: v.label, value: i }))
|
|
337
|
+
},
|
|
338
|
+
{ kind: "slider", key: "scale", label: "Band density", min: 0.1, max: 4, step: 0.05 },
|
|
339
|
+
{ kind: "slider", key: "interval", label: "Interval", min: 0, max: 0.9, step: 0.02 },
|
|
340
|
+
{ kind: "slider", key: "rampIn", label: "Ramp out", min: 0.01, max: 1, step: 0.02 },
|
|
341
|
+
{ kind: "slider", key: "rampOut", label: "Ramp in", min: 0.01, max: 1, step: 0.02 },
|
|
342
|
+
{ kind: "slider", key: "angle", label: "Angle", min: 0, max: 360, step: 1, unit: "\xB0" },
|
|
343
|
+
{ kind: "slider", key: "bright", label: "Brightness", min: 0, max: 2, step: 0.05 }
|
|
344
|
+
]
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
// src/engine/effects/index.ts
|
|
348
|
+
var EFFECTS = { panes };
|
|
349
|
+
var EFFECT_IDS = Object.keys(EFFECTS);
|
|
350
|
+
function mergeEffectParams(base, patch) {
|
|
351
|
+
if (!patch) return { ...base, colors: [...base.colors] };
|
|
352
|
+
return {
|
|
353
|
+
...base,
|
|
354
|
+
...patch,
|
|
355
|
+
colors: patch.colors ? [...patch.colors] : [...base.colors]
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// src/engine/renderer/context.ts
|
|
360
|
+
var shared = null;
|
|
361
|
+
var refs = 0;
|
|
362
|
+
function acquireRenderer() {
|
|
363
|
+
refs++;
|
|
364
|
+
if (!shared) shared = createSharedRenderer();
|
|
365
|
+
return shared;
|
|
366
|
+
}
|
|
367
|
+
function releaseRenderer() {
|
|
368
|
+
refs = Math.max(0, refs - 1);
|
|
369
|
+
if (refs === 0 && shared) {
|
|
370
|
+
shared.destroy();
|
|
371
|
+
shared = null;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function compile(gl, type, src) {
|
|
375
|
+
const s = gl.createShader(type);
|
|
376
|
+
gl.shaderSource(s, src);
|
|
377
|
+
gl.compileShader(s);
|
|
378
|
+
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
|
|
379
|
+
const log = gl.getShaderInfoLog(s);
|
|
380
|
+
gl.deleteShader(s);
|
|
381
|
+
throw new Error("glass-pulse-fx: shader compile failed: " + log);
|
|
382
|
+
}
|
|
383
|
+
return s;
|
|
384
|
+
}
|
|
385
|
+
function createSharedRenderer() {
|
|
386
|
+
const glCanvas = document.createElement("canvas");
|
|
387
|
+
glCanvas.width = Math.round(GL_SIZE * DPR);
|
|
388
|
+
glCanvas.height = Math.round(GL_SIZE * DPR);
|
|
389
|
+
const gl = glCanvas.getContext("webgl", {
|
|
390
|
+
alpha: true,
|
|
391
|
+
premultipliedAlpha: false,
|
|
392
|
+
antialias: false,
|
|
393
|
+
preserveDrawingBuffer: true
|
|
394
|
+
});
|
|
395
|
+
if (!gl) throw new Error("glass-pulse-fx: WebGL not available");
|
|
396
|
+
let compiled = {};
|
|
397
|
+
let vbuf = null;
|
|
398
|
+
let lost = false;
|
|
399
|
+
function build() {
|
|
400
|
+
gl.disable(gl.BLEND);
|
|
401
|
+
vbuf = gl.createBuffer();
|
|
402
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
403
|
+
gl.bufferData(
|
|
404
|
+
gl.ARRAY_BUFFER,
|
|
405
|
+
new Float32Array([-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1]),
|
|
406
|
+
gl.STATIC_DRAW
|
|
407
|
+
);
|
|
408
|
+
compiled = {};
|
|
409
|
+
const vert = compile(gl, gl.VERTEX_SHADER, VERT);
|
|
410
|
+
for (const id of EFFECT_IDS) {
|
|
411
|
+
const def = EFFECTS[id];
|
|
412
|
+
const program = gl.createProgram();
|
|
413
|
+
gl.attachShader(program, vert);
|
|
414
|
+
gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, def.frag));
|
|
415
|
+
gl.linkProgram(program);
|
|
416
|
+
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
|
417
|
+
throw new Error(`glass-pulse-fx: link failed (${id}): ` + gl.getProgramInfoLog(program));
|
|
418
|
+
}
|
|
419
|
+
const U = {};
|
|
420
|
+
for (const n of def.uniforms) U[n] = gl.getUniformLocation(program, n);
|
|
421
|
+
compiled[id] = { program, U, posLoc: gl.getAttribLocation(program, "a_position"), def };
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
build();
|
|
425
|
+
const onLost = (e) => {
|
|
426
|
+
e.preventDefault();
|
|
427
|
+
lost = true;
|
|
428
|
+
};
|
|
429
|
+
const onRestored = () => {
|
|
430
|
+
lost = false;
|
|
431
|
+
build();
|
|
432
|
+
};
|
|
433
|
+
glCanvas.addEventListener("webglcontextlost", onLost);
|
|
434
|
+
glCanvas.addEventListener("webglcontextrestored", onRestored);
|
|
435
|
+
return {
|
|
436
|
+
glCanvas,
|
|
437
|
+
get available() {
|
|
438
|
+
return !lost;
|
|
439
|
+
},
|
|
440
|
+
renderEffect(effectId, params, timeSec) {
|
|
441
|
+
if (lost) return;
|
|
442
|
+
const ce = compiled[effectId];
|
|
443
|
+
if (!ce) return;
|
|
444
|
+
gl.useProgram(ce.program);
|
|
445
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbuf);
|
|
446
|
+
gl.enableVertexAttribArray(ce.posLoc);
|
|
447
|
+
gl.vertexAttribPointer(ce.posLoc, 2, gl.FLOAT, false, 0, 0);
|
|
448
|
+
gl.uniform2f(ce.U.u_resolution, glCanvas.width, glCanvas.height);
|
|
449
|
+
gl.uniform1f(ce.U.u_time, timeSec);
|
|
450
|
+
ce.def.upload(gl, ce.U, params);
|
|
451
|
+
gl.viewport(0, 0, glCanvas.width, glCanvas.height);
|
|
452
|
+
gl.clearColor(0, 0, 0, 0);
|
|
453
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
454
|
+
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
455
|
+
},
|
|
456
|
+
destroy() {
|
|
457
|
+
glCanvas.removeEventListener("webglcontextlost", onLost);
|
|
458
|
+
glCanvas.removeEventListener("webglcontextrestored", onRestored);
|
|
459
|
+
gl.getExtension("WEBGL_lose_context")?.loseContext();
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/engine/renderer/loop.ts
|
|
465
|
+
var registry = /* @__PURE__ */ new Set();
|
|
466
|
+
var renderer = null;
|
|
467
|
+
var rafId = 0;
|
|
468
|
+
var dirty = false;
|
|
469
|
+
var clockSec = 0;
|
|
470
|
+
var lastClockMs = 0;
|
|
471
|
+
var motionAllowed = true;
|
|
472
|
+
var hidden = false;
|
|
473
|
+
var gatesBound = false;
|
|
474
|
+
var motionMql = null;
|
|
475
|
+
function advanceClock(now, animate) {
|
|
476
|
+
if (animate) {
|
|
477
|
+
if (lastClockMs) clockSec += (now - lastClockMs) / 1e3;
|
|
478
|
+
lastClockMs = now;
|
|
479
|
+
} else {
|
|
480
|
+
lastClockMs = 0;
|
|
481
|
+
}
|
|
482
|
+
return clockSec;
|
|
483
|
+
}
|
|
484
|
+
function markDirty() {
|
|
485
|
+
dirty = true;
|
|
486
|
+
}
|
|
487
|
+
function addRuntime(rt, r) {
|
|
488
|
+
renderer = r;
|
|
489
|
+
registry.add(rt);
|
|
490
|
+
rt.needsPaint = true;
|
|
491
|
+
dirty = true;
|
|
492
|
+
ensureLoop();
|
|
493
|
+
}
|
|
494
|
+
function removeRuntime(rt) {
|
|
495
|
+
registry.delete(rt);
|
|
496
|
+
if (registry.size === 0) stopLoop();
|
|
497
|
+
}
|
|
498
|
+
function shouldAnimate() {
|
|
499
|
+
if (!motionAllowed || hidden) return false;
|
|
500
|
+
for (const rt of registry) if (rt.visible && !rt.paused) return true;
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
function ensureLoop() {
|
|
504
|
+
if (rafId) return;
|
|
505
|
+
bindGates();
|
|
506
|
+
rafId = requestAnimationFrame(tick);
|
|
507
|
+
}
|
|
508
|
+
function stopLoop() {
|
|
509
|
+
if (rafId) cancelAnimationFrame(rafId);
|
|
510
|
+
rafId = 0;
|
|
511
|
+
lastClockMs = 0;
|
|
512
|
+
unbindGates();
|
|
513
|
+
}
|
|
514
|
+
function tick(now) {
|
|
515
|
+
rafId = requestAnimationFrame(tick);
|
|
516
|
+
const animate = shouldAnimate();
|
|
517
|
+
const t = advanceClock(now, animate);
|
|
518
|
+
let anyNeedsPaint = false;
|
|
519
|
+
let anyDue = false;
|
|
520
|
+
for (const rt of registry) {
|
|
521
|
+
if (rt.needsPaint) anyNeedsPaint = true;
|
|
522
|
+
if (animate && rt.visible && !rt.paused && (rt.lastPaintMs === 0 || now - rt.lastPaintMs >= rt.frameMs)) {
|
|
523
|
+
anyDue = true;
|
|
524
|
+
}
|
|
525
|
+
if (anyNeedsPaint && anyDue) {
|
|
526
|
+
break;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
if (!anyDue && !dirty && !anyNeedsPaint) return;
|
|
530
|
+
const groups = /* @__PURE__ */ new Map();
|
|
531
|
+
for (const rt of registry) {
|
|
532
|
+
if (!rt.visible) continue;
|
|
533
|
+
const due = rt.needsPaint || animate && !rt.paused && (rt.lastPaintMs === 0 || now - rt.lastPaintMs >= rt.frameMs);
|
|
534
|
+
if (!due) continue;
|
|
535
|
+
let g = groups.get(rt.key);
|
|
536
|
+
if (!g) groups.set(rt.key, g = []);
|
|
537
|
+
g.push(rt);
|
|
538
|
+
}
|
|
539
|
+
if (!groups.size) {
|
|
540
|
+
dirty = false;
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
for (const group of groups.values()) {
|
|
544
|
+
if (renderer) {
|
|
545
|
+
renderer.renderEffect(group[0].effectId, group[0].params, t);
|
|
546
|
+
}
|
|
547
|
+
for (const rt of group) {
|
|
548
|
+
rt.paint();
|
|
549
|
+
rt.needsPaint = false;
|
|
550
|
+
if (animate && !rt.paused) rt.lastPaintMs = now;
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
dirty = false;
|
|
554
|
+
}
|
|
555
|
+
function onMotion() {
|
|
556
|
+
motionAllowed = !(motionMql && motionMql.matches);
|
|
557
|
+
dirty = true;
|
|
558
|
+
}
|
|
559
|
+
function onVisibility() {
|
|
560
|
+
hidden = document.hidden;
|
|
561
|
+
if (hidden) {
|
|
562
|
+
lastClockMs = 0;
|
|
563
|
+
} else {
|
|
564
|
+
for (const rt of registry) rt.lastPaintMs = 0;
|
|
565
|
+
}
|
|
566
|
+
dirty = true;
|
|
567
|
+
}
|
|
568
|
+
function bindGates() {
|
|
569
|
+
if (gatesBound || typeof window === "undefined") return;
|
|
570
|
+
gatesBound = true;
|
|
571
|
+
motionMql = window.matchMedia("(prefers-reduced-motion: reduce)");
|
|
572
|
+
motionAllowed = !motionMql.matches;
|
|
573
|
+
motionMql.addEventListener("change", onMotion);
|
|
574
|
+
hidden = document.hidden;
|
|
575
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
576
|
+
}
|
|
577
|
+
function unbindGates() {
|
|
578
|
+
if (!gatesBound) return;
|
|
579
|
+
gatesBound = false;
|
|
580
|
+
motionMql?.removeEventListener("change", onMotion);
|
|
581
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
582
|
+
motionMql = null;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/styles.ts
|
|
586
|
+
var PREFIX = "gpfx";
|
|
587
|
+
var CLASS = {
|
|
588
|
+
bloomOut: `${PREFIX}-bloom-out`,
|
|
589
|
+
bloomIn: `${PREFIX}-bloom-in`,
|
|
590
|
+
surfaceClip: `${PREFIX}-surface-clip`,
|
|
591
|
+
base: `${PREFIX}-base`,
|
|
592
|
+
shader: `${PREFIX}-shader`,
|
|
593
|
+
frost: `${PREFIX}-frost`,
|
|
594
|
+
rim: `${PREFIX}-rim`,
|
|
595
|
+
coreClip: `${PREFIX}-core-clip`,
|
|
596
|
+
core: `${PREFIX}-core`,
|
|
597
|
+
border: `${PREFIX}-border`
|
|
598
|
+
};
|
|
599
|
+
var STRUCT = {
|
|
600
|
+
bloomOut: { position: "absolute", pointerEvents: "none", zIndex: "0" },
|
|
601
|
+
bloomIn: { position: "absolute", pointerEvents: "none", zIndex: "1" },
|
|
602
|
+
surfaceClip: {
|
|
603
|
+
position: "absolute",
|
|
604
|
+
inset: "0",
|
|
605
|
+
pointerEvents: "none",
|
|
606
|
+
zIndex: "2",
|
|
607
|
+
overflow: "hidden"
|
|
608
|
+
},
|
|
609
|
+
base: { position: "absolute", inset: "0", pointerEvents: "none", zIndex: "0" },
|
|
610
|
+
shader: {
|
|
611
|
+
position: "absolute",
|
|
612
|
+
inset: "0",
|
|
613
|
+
width: "100%",
|
|
614
|
+
height: "100%",
|
|
615
|
+
pointerEvents: "none",
|
|
616
|
+
zIndex: "1"
|
|
617
|
+
},
|
|
618
|
+
frost: { position: "absolute", inset: "0", pointerEvents: "none", zIndex: "2" },
|
|
619
|
+
rim: {
|
|
620
|
+
position: "absolute",
|
|
621
|
+
inset: "0",
|
|
622
|
+
width: "100%",
|
|
623
|
+
height: "100%",
|
|
624
|
+
pointerEvents: "none",
|
|
625
|
+
zIndex: "3"
|
|
626
|
+
},
|
|
627
|
+
coreClip: {
|
|
628
|
+
position: "absolute",
|
|
629
|
+
inset: "0",
|
|
630
|
+
pointerEvents: "none",
|
|
631
|
+
zIndex: "3",
|
|
632
|
+
overflow: "hidden"
|
|
633
|
+
},
|
|
634
|
+
core: { position: "absolute" },
|
|
635
|
+
border: { position: "absolute", inset: "0", pointerEvents: "none", zIndex: "5" }
|
|
636
|
+
};
|
|
637
|
+
function assign(el, s) {
|
|
638
|
+
Object.assign(el.style, s);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// src/engine/renderer/compositor.ts
|
|
642
|
+
var supportsBackdrop = typeof CSS !== "undefined" && typeof CSS.supports === "function" && (CSS.supports("backdrop-filter", "blur(1px)") || CSS.supports("-webkit-backdrop-filter", "blur(1px)"));
|
|
643
|
+
var supportsMask = typeof CSS !== "undefined" && typeof CSS.supports === "function" && (CSS.supports("mask-image", "linear-gradient(#000, #000)") || CSS.supports("-webkit-mask-image", "linear-gradient(#000, #000)"));
|
|
644
|
+
var MASK_AA_SCALE = 2;
|
|
645
|
+
var RIM_AA_SCALE = 2;
|
|
646
|
+
function mk(tag, key) {
|
|
647
|
+
const e = document.createElement(tag);
|
|
648
|
+
e.className = CLASS[key];
|
|
649
|
+
assign(e, STRUCT[key]);
|
|
650
|
+
return e;
|
|
651
|
+
}
|
|
652
|
+
function svgMaskUrl(width, height, body) {
|
|
653
|
+
const rasterW = Math.max(1, Math.ceil(width * DPR * MASK_AA_SCALE));
|
|
654
|
+
const rasterH = Math.max(1, Math.ceil(height * DPR * MASK_AA_SCALE));
|
|
655
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${rasterW}" height="${rasterH}" viewBox="0 0 ${width} ${height}" preserveAspectRatio="none" shape-rendering="geometricPrecision">${body}</svg>`;
|
|
656
|
+
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
|
657
|
+
}
|
|
658
|
+
function roundedRectMaskUrl(width, height, radius, inset = 0) {
|
|
659
|
+
const x = Math.max(0, inset);
|
|
660
|
+
const y = Math.max(0, inset);
|
|
661
|
+
const w = Math.max(1, width - x * 2);
|
|
662
|
+
const h = Math.max(1, height - y * 2);
|
|
663
|
+
const r = Math.max(0, radius - inset);
|
|
664
|
+
return svgMaskUrl(width, height, `<rect x="${x}" y="${y}" width="${w}" height="${h}" rx="${r}" ry="${r}" fill="white"/>`);
|
|
665
|
+
}
|
|
666
|
+
function applyMask(el, mask) {
|
|
667
|
+
const webkitStyle = el.style;
|
|
668
|
+
el.style.maskImage = mask;
|
|
669
|
+
el.style.maskSize = "100% 100%";
|
|
670
|
+
el.style.maskRepeat = "no-repeat";
|
|
671
|
+
webkitStyle.webkitMaskImage = mask;
|
|
672
|
+
webkitStyle.webkitMaskSize = "100% 100%";
|
|
673
|
+
webkitStyle.webkitMaskRepeat = "no-repeat";
|
|
674
|
+
}
|
|
675
|
+
function clearMask(el) {
|
|
676
|
+
const webkitStyle = el.style;
|
|
677
|
+
el.style.maskImage = "";
|
|
678
|
+
el.style.maskSize = "";
|
|
679
|
+
el.style.maskRepeat = "";
|
|
680
|
+
webkitStyle.webkitMaskImage = "";
|
|
681
|
+
webkitStyle.webkitMaskSize = "";
|
|
682
|
+
webkitStyle.webkitMaskRepeat = "";
|
|
683
|
+
}
|
|
684
|
+
function bloomEnabled(size, level) {
|
|
685
|
+
return size > 0 && level > 0;
|
|
686
|
+
}
|
|
687
|
+
function createCompositor(el, kind, opts = {}) {
|
|
688
|
+
const degraded = !!opts.degraded;
|
|
689
|
+
let bloomClip = !!opts.bloomClip;
|
|
690
|
+
const shaderScale = SHADER_SCALE[kind];
|
|
691
|
+
const cs = getComputedStyle(el);
|
|
692
|
+
if (cs.position === "static") el.style.position = "relative";
|
|
693
|
+
el.style.isolation = "isolate";
|
|
694
|
+
const surfaceClip = mk("div", "surfaceClip");
|
|
695
|
+
const base = mk("div", "base");
|
|
696
|
+
const frost = mk("div", "frost");
|
|
697
|
+
const coreClip = mk("div", "coreClip");
|
|
698
|
+
const core = mk("div", "core");
|
|
699
|
+
coreClip.appendChild(core);
|
|
700
|
+
const border = mk("div", "border");
|
|
701
|
+
let plasma = null;
|
|
702
|
+
let pctx = null;
|
|
703
|
+
let rim = null;
|
|
704
|
+
let rctx = null;
|
|
705
|
+
let bIn = null;
|
|
706
|
+
let bOut = null;
|
|
707
|
+
const nodes = [];
|
|
708
|
+
surfaceClip.appendChild(base);
|
|
709
|
+
if (!degraded) {
|
|
710
|
+
const bloomOut = mk("canvas", "bloomOut");
|
|
711
|
+
const bloomIn = mk("canvas", "bloomIn");
|
|
712
|
+
plasma = mk("canvas", "shader");
|
|
713
|
+
rim = mk("canvas", "rim");
|
|
714
|
+
pctx = plasma.getContext("2d");
|
|
715
|
+
rctx = rim.getContext("2d");
|
|
716
|
+
bOut = { el: bloomOut, ctx: bloomOut.getContext("2d"), spread: 0, scale: 1, blur: 0, enabled: false };
|
|
717
|
+
bIn = { el: bloomIn, ctx: bloomIn.getContext("2d"), spread: 0, scale: 1, blur: 0, enabled: false };
|
|
718
|
+
surfaceClip.appendChild(plasma);
|
|
719
|
+
nodes.push(bloomOut, bloomIn);
|
|
720
|
+
}
|
|
721
|
+
surfaceClip.append(frost, coreClip);
|
|
722
|
+
nodes.push(surfaceClip);
|
|
723
|
+
if (rim) nodes.push(rim);
|
|
724
|
+
nodes.push(border);
|
|
725
|
+
const first = el.firstChild;
|
|
726
|
+
for (const n of nodes) el.insertBefore(n, first);
|
|
727
|
+
let cssW = 1;
|
|
728
|
+
let cssH = 1;
|
|
729
|
+
let exactW = 1;
|
|
730
|
+
let exactH = 1;
|
|
731
|
+
let layerX = 0;
|
|
732
|
+
let layerY = 0;
|
|
733
|
+
let cornerRadius = 0;
|
|
734
|
+
let settings = null;
|
|
735
|
+
let fill = "#000000";
|
|
736
|
+
let borderCfg = null;
|
|
737
|
+
let isotropic = false;
|
|
738
|
+
function clampRadius(value) {
|
|
739
|
+
const min = Math.min(cssW, cssH);
|
|
740
|
+
return Math.max(0, Math.min(value, min / 2));
|
|
741
|
+
}
|
|
742
|
+
function radiusFromCssToken(token, percentBasis) {
|
|
743
|
+
const value = parseFloat(token);
|
|
744
|
+
if (Number.isNaN(value)) return null;
|
|
745
|
+
return token.trim().endsWith("%") ? percentBasis * value / 100 : value;
|
|
746
|
+
}
|
|
747
|
+
function radiusFromCssValue(value) {
|
|
748
|
+
const trimmed = value.trim();
|
|
749
|
+
if (!trimmed || trimmed === "0px") return 0;
|
|
750
|
+
const [horizontalPart, verticalPart = horizontalPart] = trimmed.split("/").map((part) => part.trim());
|
|
751
|
+
const horizontal = horizontalPart.split(/\s+/)[0];
|
|
752
|
+
const vertical = verticalPart.split(/\s+/)[0];
|
|
753
|
+
const rx = radiusFromCssToken(horizontal, cssW);
|
|
754
|
+
const ry = radiusFromCssToken(vertical, cssH);
|
|
755
|
+
if (rx == null && ry == null) return null;
|
|
756
|
+
if (rx == null) return ry;
|
|
757
|
+
if (ry == null) return rx;
|
|
758
|
+
return Math.min(rx, ry);
|
|
759
|
+
}
|
|
760
|
+
function resolveRadius() {
|
|
761
|
+
const min = Math.min(cssW, cssH);
|
|
762
|
+
const r = opts.radius;
|
|
763
|
+
if (r != null) {
|
|
764
|
+
if (typeof r === "number") return clampRadius(r);
|
|
765
|
+
const s = r.trim();
|
|
766
|
+
if (s === "50%") return min / 2;
|
|
767
|
+
if (s.endsWith("%")) {
|
|
768
|
+
const pct = parseFloat(s);
|
|
769
|
+
return Number.isNaN(pct) ? 0 : clampRadius(min * pct / 100);
|
|
770
|
+
}
|
|
771
|
+
const px = parseFloat(s);
|
|
772
|
+
if (!Number.isNaN(px)) return clampRadius(px);
|
|
773
|
+
}
|
|
774
|
+
if (kind === "circle") return min / 2;
|
|
775
|
+
return clampRadius(radiusFromCssValue(getComputedStyle(el).borderRadius) ?? 0);
|
|
776
|
+
}
|
|
777
|
+
function maxLayerInset() {
|
|
778
|
+
return Math.max(0, Math.min(cssW, cssH) / 2 - 0.5);
|
|
779
|
+
}
|
|
780
|
+
function clampLayerInset(value) {
|
|
781
|
+
return Math.min(Math.max(0, value), maxLayerInset());
|
|
782
|
+
}
|
|
783
|
+
function frostInset() {
|
|
784
|
+
return settings ? clampLayerInset(settings.frostInset ?? 0) : 0;
|
|
785
|
+
}
|
|
786
|
+
function shaderInset() {
|
|
787
|
+
return settings ? clampLayerInset(settings.shaderInset ?? 0) : 0;
|
|
788
|
+
}
|
|
789
|
+
function rimVisible() {
|
|
790
|
+
return !!rim && supportsMask && frostInset() > shaderInset();
|
|
791
|
+
}
|
|
792
|
+
function mainShaderInset() {
|
|
793
|
+
return rimVisible() ? frostInset() : shaderInset();
|
|
794
|
+
}
|
|
795
|
+
function layerRect(inset, scaleX, scaleY, ox = 0, oy = 0) {
|
|
796
|
+
const i = clampLayerInset(inset);
|
|
797
|
+
return {
|
|
798
|
+
x: ox + i * scaleX,
|
|
799
|
+
y: oy + i * scaleY,
|
|
800
|
+
width: Math.max(1, (cssW - i * 2) * scaleX),
|
|
801
|
+
height: Math.max(1, (cssH - i * 2) * scaleY),
|
|
802
|
+
radius: Math.max(0, cornerRadius - i) * Math.min(scaleX, scaleY)
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function addRoundedRect(ctx, rect) {
|
|
806
|
+
ctx.roundRect(rect.x, rect.y, rect.width, rect.height, rect.radius);
|
|
807
|
+
}
|
|
808
|
+
function bloomClipPath(sp) {
|
|
809
|
+
return bloomClip ? `inset(${sp - layerY}px ${sp + layerX}px ${sp + layerY}px ${sp - layerX}px round ${cornerRadius}px)` : "none";
|
|
810
|
+
}
|
|
811
|
+
function layoutBloom(b, cfg) {
|
|
812
|
+
const { size, level } = cfg;
|
|
813
|
+
b.enabled = bloomEnabled(size, level);
|
|
814
|
+
if (!b.enabled) {
|
|
815
|
+
assign(b.el, {
|
|
816
|
+
display: "none",
|
|
817
|
+
filter: "none",
|
|
818
|
+
opacity: "0"
|
|
819
|
+
});
|
|
820
|
+
b.el.width = 1;
|
|
821
|
+
b.el.height = 1;
|
|
822
|
+
b.spread = 0;
|
|
823
|
+
b.scale = 1;
|
|
824
|
+
b.blur = 0;
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
const spread = Math.ceil(size * 2.2) + 5;
|
|
828
|
+
const cw = cssW + spread * 2;
|
|
829
|
+
const ch = cssH + spread * 2;
|
|
830
|
+
assign(b.el, {
|
|
831
|
+
display: "block",
|
|
832
|
+
left: layerX - spread + "px",
|
|
833
|
+
top: layerY - spread + "px",
|
|
834
|
+
width: cw + "px",
|
|
835
|
+
height: ch + "px",
|
|
836
|
+
filter: `blur(${size}px)`,
|
|
837
|
+
opacity: String(level)
|
|
838
|
+
});
|
|
839
|
+
const scale = Math.min(DPR, BLOOM_MAX / Math.max(cw, ch));
|
|
840
|
+
b.el.width = Math.max(1, Math.round(cw * scale));
|
|
841
|
+
b.el.height = Math.max(1, Math.round(ch * scale));
|
|
842
|
+
b.spread = spread;
|
|
843
|
+
b.scale = scale;
|
|
844
|
+
b.blur = size;
|
|
845
|
+
b.el.style.clipPath = bloomClipPath(spread);
|
|
846
|
+
}
|
|
847
|
+
function applyGlassStyle() {
|
|
848
|
+
if (!settings) return;
|
|
849
|
+
frost.style.background = withAlpha(fill, settings.frost);
|
|
850
|
+
const fi = frostInset();
|
|
851
|
+
if (plasma) {
|
|
852
|
+
plasma.style.inset = "0";
|
|
853
|
+
plasma.style.borderRadius = "0";
|
|
854
|
+
clearMask(plasma);
|
|
855
|
+
}
|
|
856
|
+
if (fi <= 0) {
|
|
857
|
+
frost.style.inset = "0";
|
|
858
|
+
frost.style.borderRadius = "0";
|
|
859
|
+
clearMask(frost);
|
|
860
|
+
if (rim) {
|
|
861
|
+
rim.style.display = "none";
|
|
862
|
+
clearMask(rim);
|
|
863
|
+
}
|
|
864
|
+
} else if (supportsMask) {
|
|
865
|
+
frost.style.inset = "0";
|
|
866
|
+
frost.style.borderRadius = "0";
|
|
867
|
+
const innerMask = roundedRectMaskUrl(exactW, exactH, cornerRadius, fi);
|
|
868
|
+
applyMask(frost, innerMask);
|
|
869
|
+
if (rim) {
|
|
870
|
+
rim.style.display = rimVisible() ? "block" : "none";
|
|
871
|
+
clearMask(rim);
|
|
872
|
+
}
|
|
873
|
+
} else {
|
|
874
|
+
frost.style.inset = fi + "px";
|
|
875
|
+
clearMask(frost);
|
|
876
|
+
if (rim) {
|
|
877
|
+
rim.style.display = "none";
|
|
878
|
+
clearMask(rim);
|
|
879
|
+
}
|
|
880
|
+
frost.style.borderRadius = Math.max(0, cornerRadius - fi) + "px";
|
|
881
|
+
}
|
|
882
|
+
if (supportsBackdrop && !degraded) {
|
|
883
|
+
const shouldFilter = settings.bgBlur > 0 || Math.abs(settings.saturate - 1) > 1e-3;
|
|
884
|
+
const bf = shouldFilter ? `saturate(${settings.saturate}) blur(${settings.bgBlur}px)` : "none";
|
|
885
|
+
frost.style.backdropFilter = bf;
|
|
886
|
+
frost.style.webkitBackdropFilter = bf;
|
|
887
|
+
}
|
|
888
|
+
const k = settings.coreProportional ? Math.min(cssW, cssH) / 52 : 1;
|
|
889
|
+
const inset = settings.coreInset * k;
|
|
890
|
+
core.style.inset = inset + "px";
|
|
891
|
+
core.style.borderRadius = Math.max(0, cornerRadius - inset) + "px";
|
|
892
|
+
core.style.background = fill;
|
|
893
|
+
core.style.filter = `blur(${settings.coreBlur * k}px)`;
|
|
894
|
+
core.style.opacity = String(settings.coreOpacity);
|
|
895
|
+
}
|
|
896
|
+
function applyBorder() {
|
|
897
|
+
if (!borderCfg) return;
|
|
898
|
+
border.style.borderRadius = cornerRadius + "px";
|
|
899
|
+
border.style.boxSizing = "border-box";
|
|
900
|
+
border.style.border = `${borderCfg.width}px solid ${withAlpha(
|
|
901
|
+
borderCfg.color,
|
|
902
|
+
borderCfg.opacity
|
|
903
|
+
)}`;
|
|
904
|
+
}
|
|
905
|
+
function applySurfaceClip() {
|
|
906
|
+
if (!supportsMask) {
|
|
907
|
+
surfaceClip.style.overflow = "hidden";
|
|
908
|
+
surfaceClip.style.borderRadius = cornerRadius + "px";
|
|
909
|
+
return;
|
|
910
|
+
}
|
|
911
|
+
const mask = roundedRectMaskUrl(exactW, exactH, cornerRadius);
|
|
912
|
+
const webkitStyle = surfaceClip.style;
|
|
913
|
+
surfaceClip.style.overflow = "visible";
|
|
914
|
+
surfaceClip.style.borderRadius = "0";
|
|
915
|
+
surfaceClip.style.maskImage = mask;
|
|
916
|
+
surfaceClip.style.maskSize = "100% 100%";
|
|
917
|
+
surfaceClip.style.maskRepeat = "no-repeat";
|
|
918
|
+
webkitStyle.webkitMaskImage = mask;
|
|
919
|
+
webkitStyle.webkitMaskSize = "100% 100%";
|
|
920
|
+
webkitStyle.webkitMaskRepeat = "no-repeat";
|
|
921
|
+
}
|
|
922
|
+
function measure() {
|
|
923
|
+
const r = el.getBoundingClientRect();
|
|
924
|
+
const mcs = getComputedStyle(el);
|
|
925
|
+
const borderL = parseFloat(mcs.borderLeftWidth) || 0;
|
|
926
|
+
const borderT = parseFloat(mcs.borderTopWidth) || 0;
|
|
927
|
+
const snappedLeft = Math.round(r.left * DPR) / DPR;
|
|
928
|
+
const snappedTop = Math.round(r.top * DPR) / DPR;
|
|
929
|
+
const snappedRight = Math.round(r.right * DPR) / DPR;
|
|
930
|
+
const snappedBottom = Math.round(r.bottom * DPR) / DPR;
|
|
931
|
+
layerX = snappedLeft - r.left - borderL;
|
|
932
|
+
layerY = snappedTop - r.top - borderT;
|
|
933
|
+
exactW = Math.max(1, snappedRight - snappedLeft);
|
|
934
|
+
exactH = Math.max(1, snappedBottom - snappedTop);
|
|
935
|
+
cssW = exactW;
|
|
936
|
+
cssH = exactH;
|
|
937
|
+
cornerRadius = resolveRadius();
|
|
938
|
+
if (opts.radius != null) el.style.borderRadius = cornerRadius + "px";
|
|
939
|
+
assign(surfaceClip, {
|
|
940
|
+
inset: "auto",
|
|
941
|
+
left: layerX + "px",
|
|
942
|
+
top: layerY + "px",
|
|
943
|
+
width: exactW + "px",
|
|
944
|
+
height: exactH + "px"
|
|
945
|
+
});
|
|
946
|
+
assign(border, {
|
|
947
|
+
inset: "auto",
|
|
948
|
+
left: layerX + "px",
|
|
949
|
+
top: layerY + "px",
|
|
950
|
+
width: exactW + "px",
|
|
951
|
+
height: exactH + "px"
|
|
952
|
+
});
|
|
953
|
+
if (rim) {
|
|
954
|
+
assign(rim, {
|
|
955
|
+
inset: "auto",
|
|
956
|
+
left: layerX + "px",
|
|
957
|
+
top: layerY + "px",
|
|
958
|
+
width: exactW + "px",
|
|
959
|
+
height: exactH + "px"
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
if (plasma) {
|
|
963
|
+
plasma.width = Math.round(exactW * DPR);
|
|
964
|
+
plasma.height = Math.round(exactH * DPR);
|
|
965
|
+
}
|
|
966
|
+
if (rim) {
|
|
967
|
+
rim.width = Math.round(exactW * DPR * RIM_AA_SCALE);
|
|
968
|
+
rim.height = Math.round(exactH * DPR * RIM_AA_SCALE);
|
|
969
|
+
}
|
|
970
|
+
applySurfaceClip();
|
|
971
|
+
if (!settings) return;
|
|
972
|
+
if (bOut) layoutBloom(bOut, settings.outerBloom);
|
|
973
|
+
if (bIn) layoutBloom(bIn, settings.innerBloom);
|
|
974
|
+
applyGlassStyle();
|
|
975
|
+
applyBorder();
|
|
976
|
+
}
|
|
977
|
+
function cropForSize(glCanvas, targetW, targetH) {
|
|
978
|
+
const cw = glCanvas.width;
|
|
979
|
+
const ch = glCanvas.height;
|
|
980
|
+
const safeW = Math.max(1, targetW);
|
|
981
|
+
const safeH = Math.max(1, targetH);
|
|
982
|
+
if (isotropic) {
|
|
983
|
+
let srcW2 = cw;
|
|
984
|
+
let srcH2 = cw * safeH / safeW;
|
|
985
|
+
if (srcH2 > ch) {
|
|
986
|
+
srcW2 = ch * safeW / safeH;
|
|
987
|
+
srcH2 = ch;
|
|
988
|
+
}
|
|
989
|
+
return { sx: (cw - srcW2) / 2, sy: (ch - srcH2) / 2, srcW: srcW2, srcH: srcH2 };
|
|
990
|
+
}
|
|
991
|
+
const sampleW = Math.max(safeW, CROP_W * shaderScale * MIN_SAMPLE_SPAN);
|
|
992
|
+
const sampleH = Math.max(safeH, CROP_H * shaderScale * MIN_SAMPLE_SPAN);
|
|
993
|
+
let srcW = sampleW * cw / CROP_W / shaderScale;
|
|
994
|
+
let srcH = sampleH * ch / CROP_H / shaderScale;
|
|
995
|
+
if (srcW > cw) srcW = cw;
|
|
996
|
+
if (srcH > ch) srcH = ch;
|
|
997
|
+
return { sx: Math.max(0, (cw - srcW) / 2), sy: Math.max(0, (ch - srcH) / 2), srcW, srcH };
|
|
998
|
+
}
|
|
999
|
+
function bloomSourceInset(b) {
|
|
1000
|
+
if (!settings) return 0;
|
|
1001
|
+
if (b.blur <= 0) return 0;
|
|
1002
|
+
const rim2 = settings.coreInset + Math.max(settings.coreBlur, b.blur) * 0.5;
|
|
1003
|
+
const sourceInset = Math.max(2, b.blur, rim2);
|
|
1004
|
+
const source = layerRect(shaderInset(), 1, 1);
|
|
1005
|
+
const maxInset = Math.max(0, Math.min(source.width, source.height) / 2 - 0.5);
|
|
1006
|
+
return Math.min(sourceInset, maxInset);
|
|
1007
|
+
}
|
|
1008
|
+
function attenuateBloomCorners(ctx, b, x, y, w, h, radius) {
|
|
1009
|
+
const min = Math.min(w, h);
|
|
1010
|
+
if (b.blur <= 0) return;
|
|
1011
|
+
if (radius <= 0 || radius >= min / 2 - 0.5) return;
|
|
1012
|
+
const zone = Math.min(min / 2, radius + b.blur * b.scale * 1.25);
|
|
1013
|
+
const amount = Math.min(0.55, 0.14 + b.blur / 45);
|
|
1014
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
1015
|
+
for (const [cx, cy] of [[x, y], [x + w, y], [x, y + h], [x + w, y + h]]) {
|
|
1016
|
+
const g = ctx.createRadialGradient(cx, cy, 0, cx, cy, zone);
|
|
1017
|
+
g.addColorStop(0, `rgba(0, 0, 0, ${amount})`);
|
|
1018
|
+
g.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
1019
|
+
ctx.fillStyle = g;
|
|
1020
|
+
ctx.fillRect(cx - zone, cy - zone, zone * 2, zone * 2);
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
function drawShaderField(ctx, crop, glCanvas, rect) {
|
|
1024
|
+
ctx.drawImage(glCanvas, crop.sx, crop.sy, crop.srcW, crop.srcH, rect.x, rect.y, rect.width, rect.height);
|
|
1025
|
+
}
|
|
1026
|
+
function paintBloom(b, crop, glCanvas) {
|
|
1027
|
+
if (!b.enabled) return;
|
|
1028
|
+
const ctx = b.ctx;
|
|
1029
|
+
const s = b.scale;
|
|
1030
|
+
const sp = b.spread;
|
|
1031
|
+
ctx.clearRect(0, 0, b.el.width, b.el.height);
|
|
1032
|
+
const sourceInset = bloomSourceInset(b);
|
|
1033
|
+
if (sourceInset <= 0) return;
|
|
1034
|
+
const outer = layerRect(shaderInset(), s, s, sp * s, sp * s);
|
|
1035
|
+
ctx.save();
|
|
1036
|
+
ctx.beginPath();
|
|
1037
|
+
addRoundedRect(ctx, outer);
|
|
1038
|
+
ctx.clip();
|
|
1039
|
+
drawShaderField(ctx, crop, glCanvas, outer);
|
|
1040
|
+
const inset = sourceInset * s;
|
|
1041
|
+
const inner = {
|
|
1042
|
+
x: outer.x + inset,
|
|
1043
|
+
y: outer.y + inset,
|
|
1044
|
+
width: outer.width - inset * 2,
|
|
1045
|
+
height: outer.height - inset * 2,
|
|
1046
|
+
radius: Math.max(0, outer.radius - inset)
|
|
1047
|
+
};
|
|
1048
|
+
if (inner.width > 0 && inner.height > 0) {
|
|
1049
|
+
ctx.globalCompositeOperation = "destination-out";
|
|
1050
|
+
ctx.beginPath();
|
|
1051
|
+
addRoundedRect(ctx, inner);
|
|
1052
|
+
ctx.fill();
|
|
1053
|
+
}
|
|
1054
|
+
attenuateBloomCorners(ctx, b, outer.x, outer.y, outer.width, outer.height, outer.radius);
|
|
1055
|
+
ctx.restore();
|
|
1056
|
+
}
|
|
1057
|
+
function paintMainShader(crop, glCanvas) {
|
|
1058
|
+
if (!plasma || !pctx) return;
|
|
1059
|
+
const dw = plasma.width;
|
|
1060
|
+
const dh = plasma.height;
|
|
1061
|
+
if (dw < 1 || dh < 1) return;
|
|
1062
|
+
const sx = dw / exactW;
|
|
1063
|
+
const sy = dh / exactH;
|
|
1064
|
+
const source = layerRect(shaderInset(), sx, sy);
|
|
1065
|
+
const visible = layerRect(mainShaderInset(), sx, sy);
|
|
1066
|
+
pctx.clearRect(0, 0, dw, dh);
|
|
1067
|
+
pctx.save();
|
|
1068
|
+
pctx.beginPath();
|
|
1069
|
+
addRoundedRect(pctx, visible);
|
|
1070
|
+
pctx.clip();
|
|
1071
|
+
drawShaderField(pctx, crop, glCanvas, source);
|
|
1072
|
+
pctx.restore();
|
|
1073
|
+
}
|
|
1074
|
+
function paintRim(crop, glCanvas) {
|
|
1075
|
+
if (!rim || !rctx || !settings) return;
|
|
1076
|
+
const dw = rim.width;
|
|
1077
|
+
const dh = rim.height;
|
|
1078
|
+
if (dw < 1 || dh < 1) return;
|
|
1079
|
+
rctx.clearRect(0, 0, dw, dh);
|
|
1080
|
+
if (rim.style.display === "none" || !rimVisible()) return;
|
|
1081
|
+
const sx = dw / exactW;
|
|
1082
|
+
const sy = dh / exactH;
|
|
1083
|
+
const outer = layerRect(shaderInset(), sx, sy);
|
|
1084
|
+
const inner = layerRect(frostInset(), sx, sy);
|
|
1085
|
+
rctx.save();
|
|
1086
|
+
rctx.imageSmoothingEnabled = true;
|
|
1087
|
+
rctx.imageSmoothingQuality = "high";
|
|
1088
|
+
rctx.beginPath();
|
|
1089
|
+
addRoundedRect(rctx, outer);
|
|
1090
|
+
addRoundedRect(rctx, inner);
|
|
1091
|
+
rctx.clip("evenodd");
|
|
1092
|
+
drawShaderField(rctx, crop, glCanvas, outer);
|
|
1093
|
+
rctx.restore();
|
|
1094
|
+
}
|
|
1095
|
+
function paint(glCanvas) {
|
|
1096
|
+
if (!plasma || !pctx) return;
|
|
1097
|
+
const source = layerRect(shaderInset(), 1, 1);
|
|
1098
|
+
const crop = cropForSize(glCanvas, source.width, source.height);
|
|
1099
|
+
if (bOut?.enabled) paintBloom(bOut, crop, glCanvas);
|
|
1100
|
+
if (bIn?.enabled) paintBloom(bIn, crop, glCanvas);
|
|
1101
|
+
paintMainShader(crop, glCanvas);
|
|
1102
|
+
paintRim(crop, glCanvas);
|
|
1103
|
+
}
|
|
1104
|
+
return {
|
|
1105
|
+
measure,
|
|
1106
|
+
applyStyle(s, f, b) {
|
|
1107
|
+
settings = s;
|
|
1108
|
+
fill = f;
|
|
1109
|
+
borderCfg = b;
|
|
1110
|
+
measure();
|
|
1111
|
+
},
|
|
1112
|
+
setSampling(v) {
|
|
1113
|
+
isotropic = v;
|
|
1114
|
+
},
|
|
1115
|
+
setBloomClip(v) {
|
|
1116
|
+
bloomClip = v;
|
|
1117
|
+
if (bOut?.enabled) bOut.el.style.clipPath = bloomClipPath(bOut.spread);
|
|
1118
|
+
if (bIn?.enabled) bIn.el.style.clipPath = bloomClipPath(bIn.spread);
|
|
1119
|
+
},
|
|
1120
|
+
paint,
|
|
1121
|
+
destroy() {
|
|
1122
|
+
for (const n of nodes) n.remove();
|
|
1123
|
+
}
|
|
1124
|
+
};
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
// src/engine/settings.ts
|
|
1128
|
+
var DEFAULT_SETTINGS = {
|
|
1129
|
+
dark: {
|
|
1130
|
+
bgBlur: 6,
|
|
1131
|
+
frost: 0.66,
|
|
1132
|
+
frostInset: 0,
|
|
1133
|
+
shaderInset: 0,
|
|
1134
|
+
coreInset: 8,
|
|
1135
|
+
coreBlur: 8,
|
|
1136
|
+
coreOpacity: 1,
|
|
1137
|
+
coreProportional: false,
|
|
1138
|
+
saturate: 1.3,
|
|
1139
|
+
innerBloom: { size: 2, level: 1 },
|
|
1140
|
+
outerBloom: { size: 16, level: 0.45 }
|
|
1141
|
+
},
|
|
1142
|
+
light: {
|
|
1143
|
+
bgBlur: 6,
|
|
1144
|
+
frost: 0.42,
|
|
1145
|
+
frostInset: 0,
|
|
1146
|
+
shaderInset: 0,
|
|
1147
|
+
coreInset: 8,
|
|
1148
|
+
coreBlur: 8,
|
|
1149
|
+
coreOpacity: 1,
|
|
1150
|
+
coreProportional: false,
|
|
1151
|
+
saturate: 1.55,
|
|
1152
|
+
innerBloom: { size: 2, level: 1 },
|
|
1153
|
+
outerBloom: { size: 18, level: 0.5 }
|
|
1154
|
+
}
|
|
1155
|
+
};
|
|
1156
|
+
var DEFAULT_BORDER = {
|
|
1157
|
+
dark: { width: 1, opacity: 0.3, color: "#808080" },
|
|
1158
|
+
// hsl(0 0% 50%)
|
|
1159
|
+
light: { width: 1, opacity: 0.3, color: "#6A6A72" }
|
|
1160
|
+
};
|
|
1161
|
+
function mergeBorder(base, patch) {
|
|
1162
|
+
return patch ? { ...base, ...patch } : { ...base };
|
|
1163
|
+
}
|
|
1164
|
+
var DEFAULT_FILL = {
|
|
1165
|
+
dark: "#0D0D12",
|
|
1166
|
+
light: "#EEF0F3"
|
|
1167
|
+
};
|
|
1168
|
+
function mergeSettings(base, patch) {
|
|
1169
|
+
if (!patch) {
|
|
1170
|
+
return {
|
|
1171
|
+
...base,
|
|
1172
|
+
innerBloom: { ...base.innerBloom },
|
|
1173
|
+
outerBloom: { ...base.outerBloom }
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
1176
|
+
return {
|
|
1177
|
+
...base,
|
|
1178
|
+
...patch,
|
|
1179
|
+
innerBloom: { ...base.innerBloom, ...patch.innerBloom },
|
|
1180
|
+
outerBloom: { ...base.outerBloom, ...patch.outerBloom }
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
|
|
1184
|
+
// src/core.ts
|
|
1185
|
+
var keyFor = (id, params) => id + ":" + JSON.stringify(params);
|
|
1186
|
+
function inferKind(el, radius) {
|
|
1187
|
+
if (radius === "50%") return "circle";
|
|
1188
|
+
const br = getComputedStyle(el).borderRadius;
|
|
1189
|
+
if (br && br.includes("%") && parseFloat(br) >= 50) return "circle";
|
|
1190
|
+
return "rect";
|
|
1191
|
+
}
|
|
1192
|
+
function backdropOf(el) {
|
|
1193
|
+
const layers = [];
|
|
1194
|
+
for (let n = el.parentElement; n; n = n.parentElement) {
|
|
1195
|
+
const c = parseColor(getComputedStyle(n).backgroundColor);
|
|
1196
|
+
if (c.a > 0) layers.push(c);
|
|
1197
|
+
if (c.a >= 1) break;
|
|
1198
|
+
}
|
|
1199
|
+
let out = layers.length && layers[layers.length - 1].a >= 1 ? layers.pop() : { r: 0, g: 0, b: 0, a: 1 };
|
|
1200
|
+
for (let i = layers.length - 1; i >= 0; i--) out = compositeOver(layers[i], out);
|
|
1201
|
+
return out;
|
|
1202
|
+
}
|
|
1203
|
+
function readHostFill(el) {
|
|
1204
|
+
const own = parseColor(getComputedStyle(el).backgroundColor);
|
|
1205
|
+
if (own.a <= 0) return null;
|
|
1206
|
+
if (own.a >= 1) return rgbToHex(own);
|
|
1207
|
+
return rgbToHex(compositeOver(own, backdropOf(el)));
|
|
1208
|
+
}
|
|
1209
|
+
function combinePatches(a, b) {
|
|
1210
|
+
if (!a || !b) return a ?? b;
|
|
1211
|
+
const out = { ...a, ...b };
|
|
1212
|
+
if (a.innerBloom && b.innerBloom) out.innerBloom = { ...a.innerBloom, ...b.innerBloom };
|
|
1213
|
+
if (a.outerBloom && b.outerBloom) out.outerBloom = { ...a.outerBloom, ...b.outerBloom };
|
|
1214
|
+
return out;
|
|
1215
|
+
}
|
|
1216
|
+
function createGlass(target, opts = {}) {
|
|
1217
|
+
const preset = opts.preset;
|
|
1218
|
+
let theme = opts.theme ?? "dark";
|
|
1219
|
+
let effect = opts.effect ?? preset?.effect ?? "panes";
|
|
1220
|
+
let userParams = {};
|
|
1221
|
+
function rememberParams(patch) {
|
|
1222
|
+
if (!patch) return;
|
|
1223
|
+
userParams = { ...userParams, ...patch };
|
|
1224
|
+
if (patch.colors) userParams.colors = [...patch.colors];
|
|
1225
|
+
}
|
|
1226
|
+
rememberParams(preset?.effectParams);
|
|
1227
|
+
rememberParams(opts.effectParams);
|
|
1228
|
+
let params = mergeEffectParams(EFFECTS[effect].defaults[theme], userParams);
|
|
1229
|
+
const fillPinned = opts.fill != null;
|
|
1230
|
+
let fill = opts.fill ?? readHostFill(target) ?? DEFAULT_FILL[theme];
|
|
1231
|
+
const basePatch = combinePatches(preset?.settings, opts.settings);
|
|
1232
|
+
let settings = mergeSettings(DEFAULT_SETTINGS[theme], basePatch);
|
|
1233
|
+
const noRim = { width: 0, opacity: 0, color: "transparent" };
|
|
1234
|
+
let borderPatch = { ...opts.border };
|
|
1235
|
+
let border = opts.border ? mergeBorder(DEFAULT_BORDER[theme], borderPatch) : noRim;
|
|
1236
|
+
const kind = opts.kind ?? inferKind(target, opts.radius);
|
|
1237
|
+
let renderer2 = null;
|
|
1238
|
+
let degraded = false;
|
|
1239
|
+
try {
|
|
1240
|
+
renderer2 = acquireRenderer();
|
|
1241
|
+
} catch {
|
|
1242
|
+
degraded = true;
|
|
1243
|
+
}
|
|
1244
|
+
const comp = createCompositor(target, kind, {
|
|
1245
|
+
radius: opts.radius,
|
|
1246
|
+
degraded,
|
|
1247
|
+
bloomClip: opts.bloomClip
|
|
1248
|
+
});
|
|
1249
|
+
comp.setSampling(EFFECTS[effect].sampling?.(params) === "isotropic");
|
|
1250
|
+
comp.applyStyle(settings, fill, border);
|
|
1251
|
+
const rt = {
|
|
1252
|
+
key: keyFor(effect, params),
|
|
1253
|
+
effectId: effect,
|
|
1254
|
+
params,
|
|
1255
|
+
frameMs: frameMsForFps(opts.fps),
|
|
1256
|
+
lastPaintMs: 0,
|
|
1257
|
+
paused: opts.paused ?? false,
|
|
1258
|
+
visible: true,
|
|
1259
|
+
needsPaint: true,
|
|
1260
|
+
paint() {
|
|
1261
|
+
if (renderer2) comp.paint(renderer2.glCanvas);
|
|
1262
|
+
}
|
|
1263
|
+
};
|
|
1264
|
+
let ro = null;
|
|
1265
|
+
let io = null;
|
|
1266
|
+
if (!degraded && renderer2) {
|
|
1267
|
+
addRuntime(rt, renderer2);
|
|
1268
|
+
let rafScheduled = false;
|
|
1269
|
+
ro = new ResizeObserver(() => {
|
|
1270
|
+
if (rafScheduled) return;
|
|
1271
|
+
rafScheduled = true;
|
|
1272
|
+
requestAnimationFrame(() => {
|
|
1273
|
+
rafScheduled = false;
|
|
1274
|
+
comp.measure();
|
|
1275
|
+
rt.needsPaint = true;
|
|
1276
|
+
markDirty();
|
|
1277
|
+
});
|
|
1278
|
+
});
|
|
1279
|
+
ro.observe(target);
|
|
1280
|
+
io = new IntersectionObserver((entries) => {
|
|
1281
|
+
for (const e of entries) rt.visible = e.isIntersecting;
|
|
1282
|
+
markDirty();
|
|
1283
|
+
});
|
|
1284
|
+
io.observe(target);
|
|
1285
|
+
}
|
|
1286
|
+
function restyle() {
|
|
1287
|
+
comp.applyStyle(settings, fill, border);
|
|
1288
|
+
rt.needsPaint = true;
|
|
1289
|
+
markDirty();
|
|
1290
|
+
}
|
|
1291
|
+
function syncEffect() {
|
|
1292
|
+
rt.effectId = effect;
|
|
1293
|
+
rt.params = params;
|
|
1294
|
+
rt.key = keyFor(effect, params);
|
|
1295
|
+
comp.setSampling(EFFECTS[effect].sampling?.(params) === "isotropic");
|
|
1296
|
+
rt.needsPaint = true;
|
|
1297
|
+
markDirty();
|
|
1298
|
+
}
|
|
1299
|
+
function setRuntimeFps(fps) {
|
|
1300
|
+
rt.frameMs = frameMsForFps(fps);
|
|
1301
|
+
rt.lastPaintMs = 0;
|
|
1302
|
+
rt.needsPaint = true;
|
|
1303
|
+
markDirty();
|
|
1304
|
+
}
|
|
1305
|
+
return {
|
|
1306
|
+
update(patch) {
|
|
1307
|
+
settings = mergeSettings(settings, patch);
|
|
1308
|
+
restyle();
|
|
1309
|
+
},
|
|
1310
|
+
setEffect(id) {
|
|
1311
|
+
effect = id;
|
|
1312
|
+
userParams = {};
|
|
1313
|
+
params = mergeEffectParams(EFFECTS[id].defaults[theme]);
|
|
1314
|
+
syncEffect();
|
|
1315
|
+
},
|
|
1316
|
+
setEffectParams(patch) {
|
|
1317
|
+
rememberParams(patch);
|
|
1318
|
+
params = mergeEffectParams(params, patch);
|
|
1319
|
+
syncEffect();
|
|
1320
|
+
},
|
|
1321
|
+
setTheme(t) {
|
|
1322
|
+
theme = t;
|
|
1323
|
+
settings = mergeSettings(DEFAULT_SETTINGS[theme], basePatch);
|
|
1324
|
+
if (!fillPinned) fill = readHostFill(target) ?? DEFAULT_FILL[theme];
|
|
1325
|
+
border = opts.border ? mergeBorder(DEFAULT_BORDER[theme], borderPatch) : noRim;
|
|
1326
|
+
params = mergeEffectParams(EFFECTS[effect].defaults[theme], userParams);
|
|
1327
|
+
syncEffect();
|
|
1328
|
+
restyle();
|
|
1329
|
+
},
|
|
1330
|
+
setFill(color) {
|
|
1331
|
+
fill = color;
|
|
1332
|
+
restyle();
|
|
1333
|
+
},
|
|
1334
|
+
setBorder(patch) {
|
|
1335
|
+
borderPatch = { ...borderPatch, ...patch };
|
|
1336
|
+
border = mergeBorder(border, patch);
|
|
1337
|
+
restyle();
|
|
1338
|
+
},
|
|
1339
|
+
setFps(fps) {
|
|
1340
|
+
setRuntimeFps(fps);
|
|
1341
|
+
},
|
|
1342
|
+
setBloomClip(clip) {
|
|
1343
|
+
comp.setBloomClip(clip);
|
|
1344
|
+
},
|
|
1345
|
+
setPaused(paused) {
|
|
1346
|
+
rt.paused = paused;
|
|
1347
|
+
rt.needsPaint = true;
|
|
1348
|
+
markDirty();
|
|
1349
|
+
},
|
|
1350
|
+
destroy() {
|
|
1351
|
+
ro?.disconnect();
|
|
1352
|
+
io?.disconnect();
|
|
1353
|
+
removeRuntime(rt);
|
|
1354
|
+
comp.destroy();
|
|
1355
|
+
if (!degraded) releaseRenderer();
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
exports.DEFAULT_BORDER = DEFAULT_BORDER;
|
|
1361
|
+
exports.DEFAULT_FILL = DEFAULT_FILL;
|
|
1362
|
+
exports.DEFAULT_SETTINGS = DEFAULT_SETTINGS;
|
|
1363
|
+
exports.EFFECTS = EFFECTS;
|
|
1364
|
+
exports.EFFECT_IDS = EFFECT_IDS;
|
|
1365
|
+
exports.VELOCITY_PRESETS = VELOCITY_PRESETS;
|
|
1366
|
+
exports.createGlass = createGlass;
|
|
1367
|
+
exports.mergeBorder = mergeBorder;
|
|
1368
|
+
exports.mergeEffectParams = mergeEffectParams;
|
|
1369
|
+
exports.mergeSettings = mergeSettings;
|
|
1370
|
+
//# sourceMappingURL=core.cjs.map
|
|
1371
|
+
//# sourceMappingURL=core.cjs.map
|