lumiglass 1.1.1 → 1.2.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.
@@ -1,673 +1,652 @@
1
- var LiquidGlassEngine = (() => {
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
7
- var __export = (target, all) => {
8
- for (var name in all)
9
- __defProp(target, name, { get: all[name], enumerable: true });
10
- };
11
- var __copyProps = (to, from, except, desc) => {
12
- if (from && typeof from === "object" || typeof from === "function") {
13
- for (let key of __getOwnPropNames(from))
14
- if (!__hasOwnProp.call(to, key) && key !== except)
15
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
- }
17
- return to;
1
+ /* ============================================================
2
+ Lumi Glass · glass-liquid-engine.js (自研实现)
3
+ ------------------------------------------------------------
4
+ 本文件为完全自研的 WebGL 液态玻璃渲染引擎,不包含任何第三方代码。
5
+ 对外契约:
6
+ window.LiquidGlassEngine = { LiquidGlass, default }
7
+ new LiquidGlass(canvas, opts)
8
+ opts: { refraction, dispersion, frost, bezel, mergeK, rim, mode, liquid }
9
+ 实例: .failed / .scan(root) / .set(opts) / .setSource(src,{live}) / .destroy()
10
+ 静态: LiquidGlass.PRESETS
11
+ DOM 契约(与 glass-webgl.js 的标记约定一致):
12
+ [data-glass=preset] [data-frost] [data-tint] [data-layer] [data-grow] [data-merge]
13
+ [data-glass-solid](v2 仅收集不动样式,solid 仍由 DOM 自绘)
14
+ 渲染思路(自研):
15
+ 1. 壁纸 canvas 作为纹理上传;CPU 侧 drawImage+filter 生成 1/4 分辨率模糊图
16
+ 2. 片元着色器对每个玻璃形状求圆角矩形 SDF,min / 平滑并集合成场
17
+ 3. 场梯度作折射法线:近边缘按指数衰减位移采样背景,RGB 三通道位移量
18
+ 不同产生色散;霜化 = 锐利图与模糊图按形状 frost 插值
19
+ 4. 边缘高光:SDF 等值带 × (法线·光向),暗色模式换冷色并减半
20
+ 5. 两层合成:layer0(面板)先压在壁纸上,layer1(控件)再压在其上
21
+ 6. 滚动帧零 DOM 读取:元素位置缓存文档坐标,滚动帧纯算术平移
22
+ 已知限制(相对 v1):
23
+ - data-glass-solid 不入 shader 折射,仍由 DOM 自绘(视觉差异极小)
24
+ - 无按压弹簧/形变动画(liquid 参数暂未消费)
25
+ ============================================================ */
26
+ (function () {
27
+ 'use strict';
28
+
29
+ var MAXG = 48;
30
+
31
+ /* 形状预设。键名与 data-glass 值对应,数值为自研调校。 */
32
+ var PRESETS = {
33
+ button: { grow: 0.14, frost: 0.15, tint: 0, merge: 0, layer: 0 },
34
+ bar: { grow: 0.03, frost: 0.45, tint: 0, merge: 0, layer: 0 },
35
+ card: { grow: 0, frost: 1, tint: 0.2, merge: 0, layer: 0 },
36
+ sheet: { grow: 0, frost: 1, tint: 0.28, merge: 1, layer: 0 },
37
+ knob: { grow: 0.22, frost: 0.06, tint: 0, merge: 0, layer: 1 },
38
+ blob: { grow: 0.1, frost: 0.1, tint: 0, merge: 1, layer: 0 }
18
39
  };
19
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
- var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
21
-
22
- // engine.ts
23
- var engine_exports = {};
24
- __export(engine_exports, {
25
- LiquidGlass: () => LiquidGlass,
26
- default: () => engine_default
27
- });
28
-
29
- // budget.ts
30
- function selectShapes(cands, max) {
31
- const byId = new Map(cands.map((c) => [c.id, c]));
32
- const depth = (c) => {
33
- let d = 0;
34
- for (let p = c.parent; p !== null && byId.has(p); p = byId.get(p).parent) d++;
35
- return d;
36
- };
37
- const enriched = cands.map((c) => ({ c, d: depth(c) })).filter((x) => x.d <= 1);
38
- const out = /* @__PURE__ */ new Map();
39
- const reserveChild = Math.min(14, Math.floor(max * 0.45));
40
- const cap0 = Math.max(6, max - reserveChild);
41
- const d0 = enriched.filter((x) => x.d === 0).sort((a, b) => (b.c.knob || 0) - (a.c.knob || 0) || b.c.area - a.c.area);
42
- for (const { c, d } of d0) {
43
- if (out.size >= cap0) break;
44
- out.set(c.id, d);
45
- }
46
- const d1 = enriched.filter((x) => x.d === 1 && out.has(x.c.parent)).sort((a, b) => (b.c.knob || 0) - (a.c.knob || 0) || b.c.area - a.c.area);
47
- for (const { c, d } of d1) {
48
- if (out.size >= max) break;
49
- out.set(c.id, d);
50
- }
51
- for (const { c, d } of d0) {
52
- if (out.size >= max) break;
53
- if (out.has(c.id)) continue;
54
- out.set(c.id, d);
55
- }
56
- return out;
40
+
41
+ var VS =
42
+ 'attribute vec2 p;varying vec2 vUv;' +
43
+ 'void main(){vUv=vec2(p.x*.5+.5,.5-p.y*.5);gl_Position=vec4(p,0.,1.);}';
44
+
45
+ var FS = [
46
+ 'precision highp float;',
47
+ '#define MAXG ' + MAXG,
48
+ 'varying vec2 vUv;',
49
+ 'uniform sampler2D uTex;', // 壁纸原图
50
+ 'uniform sampler2D uBlur;', // 壁纸模糊图
51
+ 'uniform vec2 uRes;', // 画布 CSS 尺寸
52
+ 'uniform vec4 uFit;', // 纹理 UV = vUv * uFit.xy + uFit.zw
53
+ 'uniform vec4 uG[' + MAXG + '];', // cx, cy, hw, hh(px,y 向下)
54
+ 'uniform vec4 uA[' + MAXG + '];', // radius, layer, frost, tintAmt
55
+ 'uniform vec4 uB[' + MAXG + '];', // merge, tintR, tintG, tintB
56
+ 'uniform int uGN;',
57
+ 'uniform float uBezel;',
58
+ 'uniform float uStrength;',
59
+ 'uniform float uDisp;',
60
+ 'uniform float uMergeK;',
61
+ 'uniform float uDark;',
62
+ 'uniform float uRim;',
63
+ 'uniform vec2 uPtr;', // 指针位置(画布 px)
64
+ 'uniform float uPtrOn;', // 指针是否在页面上
65
+
66
+ /* 圆角矩形 SDF(经典公式,公有数学) */
67
+ 'float sdRR(vec2 p, vec2 h, float r){',
68
+ ' r = min(r, min(h.x, h.y));',
69
+ ' vec2 q = abs(p) - h + r;',
70
+ ' return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;',
71
+ '}',
72
+
73
+ /* SDF 解析梯度:指向最近表面的单位向量 */
74
+ 'vec2 sdGrad(vec2 pw, vec2 h, float r){',
75
+ ' r = min(r, min(h.x, h.y));',
76
+ ' vec2 q = abs(pw) - h + r;',
77
+ ' vec2 s = vec2(pw.x >= 0.0 ? 1.0 : -1.0, pw.y >= 0.0 ? 1.0 : -1.0);',
78
+ ' if (q.x > 0.0 && q.y > 0.0) return normalize(max(q, 0.0) * s);',
79
+ ' if (q.x > 0.0) return vec2(s.x, 0.0);',
80
+ ' if (q.y > 0.0) return vec2(0.0, s.y);',
81
+ ' return (q.x > q.y) ? vec2(s.x, 0.0) : vec2(0.0, s.y);',
82
+ '}',
83
+
84
+ /* 多项式平滑并集(公有数学) */
85
+ 'float smin(float a, float b, float k){',
86
+ ' float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);',
87
+ ' return mix(b, a, h) - k * h * (1.0 - h);',
88
+ '}',
89
+
90
+ /* 场求值:返回带符号距离;带出最近形状的 frost/tint 与法线。
91
+ merge 形状彼此平滑并集(距离小于 uMergeK 时),其余取硬 min。
92
+ 同时带出指针在该形状边框上的最近点 ptrPt 与该形状的指针接近度 ptrK,
93
+ 供 surf() 在边框上画跟随光。 */
94
+ 'float field(vec2 p, float layer, out vec2 grad, out float frost, out vec3 tintC, out float tintA,',
95
+ ' out vec2 ptrPt, out float ptrK, out float ptrR){',
96
+ ' float dHard = 1e4; float dMerge = 1e4;',
97
+ ' vec2 gHard = vec2(0.0); vec2 gMerge = vec2(0.0);',
98
+ ' float fH = 0.5; vec3 tH = vec3(0.5); float aH = 0.0;',
99
+ ' float fM = 0.5; vec3 tM = vec3(0.5); float aM = 0.0;',
100
+ ' vec4 gH = vec4(0.0); vec4 gM = vec4(0.0);',
101
+ ' float rH = 0.0; float rM = 0.0;',
102
+ ' vec4 gS = vec4(0.0); float rS = 0.0; float dS = 1e4;',
103
+ ' ptrPt = p; ptrK = 0.0; ptrR = 90.0;',
104
+ ' for (int i = 0; i < MAXG; i++){',
105
+ ' if (i >= uGN) break;',
106
+ ' if (abs(uA[i].y - layer) > 0.5) continue;',
107
+ ' vec2 pw = p - uG[i].xy;',
108
+ ' float di = sdRR(pw, uG[i].zw, uA[i].x);',
109
+ ' vec2 gi = sdGrad(pw, uG[i].zw, uA[i].x);',
110
+ ' if (uB[i].x > 0.5) {',
111
+ ' if (dMerge > 9e3) {',
112
+ ' dMerge = di; gMerge = gi; fM = uA[i].z; tM = uB[i].yzw; aM = uA[i].w;',
113
+ ' gM = uG[i]; rM = uA[i].x;',
114
+ ' } else if (di < uMergeK) {',
115
+ ' float h = clamp(0.5 + 0.5 * (di - dMerge) / uMergeK, 0.0, 1.0);',
116
+ ' gMerge = mix(gMerge, gi, h);',
117
+ ' dMerge = mix(dMerge, di, h) - uMergeK * h * (1.0 - h);',
118
+ ' fM = mix(fM, uA[i].z, h); tM = mix(tM, uB[i].yzw, h); aM = mix(aM, uA[i].w, h);',
119
+ ' } else if (di < dHard) {',
120
+ ' dHard = di; gHard = gi; fH = uA[i].z; tH = uB[i].yzw; aH = uA[i].w;',
121
+ ' gH = uG[i]; rH = uA[i].x;',
122
+ ' }',
123
+ ' } else if (di < dHard) {',
124
+ ' dHard = di; gHard = gi; fH = uA[i].z; tH = uB[i].yzw; aH = uA[i].w;',
125
+ ' gH = uG[i]; rH = uA[i].x;',
126
+ ' }',
127
+ ' }',
128
+ ' if (dMerge < dHard) { grad = gMerge; frost = fM; tintC = tM; tintA = aM; dS = dMerge; gS = gM; rS = rM; }',
129
+ ' else { grad = gHard; frost = fH; tintC = tH; tintA = aH; dS = dHard; gS = gH; rS = rH; }',
130
+ /* 指针 → 该形状边框最近点:沿指针处法线走 |d| 即落在框上。
131
+ 光斑半径随元素短边缩放:小按钮局部亮,大面板亮一段弧 */
132
+ ' if (uPtrOn > 0.5 && dS < 9e3) {',
133
+ ' vec2 pl = uPtr - gS.xy;',
134
+ ' float dp = sdRR(pl, gS.zw, rS);',
135
+ ' vec2 gp = sdGrad(pl, gS.zw, rS);',
136
+ ' ptrPt = uPtr - gp * dp;',
137
+ ' ptrR = clamp(min(gS.z, gS.w) * 2.4, 40.0, 300.0);',
138
+ ' float reach = clamp(min(gS.z, gS.w) * 1.6, 72.0, 280.0);',
139
+ ' ptrK = 1.0 - smoothstep(reach, reach * 2.5, max(dp, 0.0));',
140
+ ' }',
141
+ ' return dS;',
142
+ '}',
143
+
144
+ /* 单层玻璃面:输入该像素的场参数,输出 premultiplied 前的 (rgb, a)
145
+ px2uv: 屏幕 px → 源图 UV 的换算(cover 映射下 = uFit.xy / uRes) */
146
+ 'vec4 surf(float d, vec2 n, float fr, vec3 tc, float ta, vec2 uv, vec2 suv,',
147
+ ' vec2 p, vec2 ptrPt, float ptrK, float ptrR){',
148
+ ' float hot = 0.0;',
149
+ ' float a = 1.0 - smoothstep(-0.75, 0.0, d);',
150
+ /* 折射:边缘位移最大,向内部指数衰减;RGB 位移不同 → 色散 */
151
+ ' float k = exp(d / max(uBezel, 1.0));',
152
+ ' float str = k * uStrength;',
153
+ ' vec2 px2uv = vec2(uFit.x / uRes.x, uFit.y / uRes.y);',
154
+ ' vec2 offR = n * str * (1.0 + uDisp) * px2uv;',
155
+ ' vec2 offG = n * str * px2uv;',
156
+ ' vec2 offB = n * str * (1.0 - uDisp) * px2uv;',
157
+ ' vec3 sharp;',
158
+ ' sharp.r = texture2D(uTex, clamp(uv + offR, 0.001, 0.999)).r;',
159
+ ' sharp.g = texture2D(uTex, clamp(uv + offG, 0.001, 0.999)).g;',
160
+ ' sharp.b = texture2D(uTex, clamp(uv + offB, 0.001, 0.999)).b;',
161
+ /* 模糊图与屏幕 1:1 对应,位移按屏幕 UV 计 */
162
+ ' vec3 bl = texture2D(uBlur, clamp(suv + n * str / uRes, 0.001, 0.999)).rgb;',
163
+ ' vec3 col = mix(sharp, bl, clamp(fr, 0.0, 1.0));',
164
+ /* 调色:增饱和 → 暗/亮模式各自的亮度曲线 → 着色 */
165
+ ' float lum = dot(col, vec3(0.299, 0.587, 0.114));',
166
+ ' col = mix(vec3(lum), col, 1.2);',
167
+ ' col = mix(col * 1.02 + 0.03, col * 0.78 + 0.015, uDark);',
168
+ ' col = mix(col, tc, clamp(ta, 0.0, 1.0));',
169
+ /* 边缘高光:等值带 × 光向(左上来光)。亮色主题底色已亮,
170
+ 高光减半防止粗白边;暗色保持全强度冷光 */
171
+ ' float band = exp(-abs(d) / max(uBezel * 0.25, 1.0));',
172
+ ' vec2 L = normalize(vec2(-0.55, -0.83));',
173
+ ' float lit = clamp(dot(n, L) * 0.5 + 0.5, 0.0, 1.0);',
174
+ ' vec3 rimCol = mix(vec3(1.0), vec3(0.75, 0.85, 1.0), uDark);',
175
+ /* 指针跟光:指针投影到本形状边框上的那个点,周围一段边框被点亮。
176
+ 只加到边框带 band / 轮廓线 line 上,玻璃内部不铺光。 */
177
+ ' if (uPtrOn > 0.5 && ptrK > 0.001) {',
178
+ ' float dd = length(p - ptrPt) / ptrR;',
179
+ ' hot = exp(-dd * dd * 1.8) * ptrK;',
180
+ ' band = max(band, hot * 0.95 * exp(-abs(d) / 2.2));',
181
+ ' }',
182
+ ' float rimI = uRim * mix(0.45, 1.0, uDark) * (1.0 + 2.6 * hot);',
183
+ ' col += rimCol * (rimI * band * (0.22 + 0.55 * lit));',
184
+ ' col -= rimCol * (rimI * 0.25 * band * (1.0 - lit));',
185
+ /* 外沿 1px 亮线,托出轮廓;靠近指针处一起变亮 */
186
+ ' float line = 1.0 - smoothstep(0.0, 1.2, abs(d + 0.6));',
187
+ ' col += rimCol * line * mix(0.05, 0.12, uDark) * (1.0 + 2.0 * hot);',
188
+ ' return vec4(col, a);',
189
+ '}',
190
+
191
+ 'void main(){',
192
+ ' vec2 p = vUv * uRes;',
193
+ ' vec2 uv = vUv * uFit.xy + uFit.zw;',
194
+ ' vec3 col = texture2D(uTex, uv).rgb;', // 底:壁纸
195
+ ' float d0; vec2 n0; float fr0; vec3 tc0; float ta0; vec2 pt0; float pk0; float pr0;',
196
+ ' d0 = field(p, 0.0, n0, fr0, tc0, ta0, pt0, pk0, pr0);',
197
+ ' float d1; vec2 n1; float fr1; vec3 tc1; float ta1; vec2 pt1; float pk1; float pr1;',
198
+ ' d1 = field(p, 1.0, n1, fr1, tc1, ta1, pt1, pk1, pr1);',
199
+ ' if (d0 < 0.0) { vec4 s = surf(d0, n0, fr0, tc0, ta0, uv, vUv, p, pt0, pk0, pr0); col = mix(col, s.rgb, s.a); }',
200
+ ' if (d1 < 0.0) { vec4 s = surf(d1, n1, fr1, tc1, ta1, uv, vUv, p, pt1, pk1, pr1); col = mix(col, s.rgb, s.a); }',
201
+ ' gl_FragColor = vec4(col, 1.0);',
202
+ '}'
203
+ ].join('\n');
204
+
205
+ function clamp(v, a, b) { return v < a ? a : v > b ? b : v; }
206
+
207
+ function parseRadius(cs) {
208
+ var v = cs.borderTopLeftRadius || '0';
209
+ var m = String(v).match(/([\d.]+)/);
210
+ return m ? parseFloat(m[1]) : 0;
57
211
  }
58
212
 
59
- // occlusion.ts
60
- var MIN_ALPHA = 0.05;
61
- function alpha(color) {
62
- if (!color || color === "transparent") return 0;
63
- const slash = color.match(/\/\s*([\d.]+)(%?)\s*\)$/);
64
- if (slash) return parseFloat(slash[1]) / (slash[2] ? 100 : 1);
65
- const rgba = color.match(/^rgba\([^,]+,[^,]+,[^,]+,\s*([\d.]+)\s*\)$/);
66
- if (rgba) return parseFloat(rgba[1]);
213
+ function alphaOf(color) {
214
+ if (!color || color === 'transparent') return 0;
215
+ var m = String(color).match(/rgba?\([^)]*,\s*([\d.]+)\s*\)$/);
216
+ if (m) return parseFloat(m[1]);
217
+ if (String(color).indexOf('#') === 0 && color.length >= 8) {
218
+ return parseInt(color.slice(7, 9), 16) / 255;
219
+ }
67
220
  return 1;
68
221
  }
69
- function paintsOverBackdrop(s) {
70
- return s.backdropFilter !== "" && s.backdropFilter !== "none" || s.backgroundImage !== "" && s.backgroundImage !== "none" || alpha(s.backgroundColor) > MIN_ALPHA;
71
- }
72
- function effectiveOpacity(el) {
73
- let o = 1;
74
- for (let p = el; p && p !== document.body && o > 0; p = p.parentElement) {
75
- const v = parseFloat(getComputedStyle(p).opacity);
76
- if (!Number.isNaN(v)) o *= v;
222
+
223
+ /* 遮挡判定:祖先有不透明底 / 背景图 / 自带磨砂 → 该玻璃不渲染 */
224
+ function blockedByAncestor(el) {
225
+ var p = el.parentElement;
226
+ while (p && p !== document.body) {
227
+ var cs = getComputedStyle(p);
228
+ var bf = cs.backdropFilter || cs.webkitBackdropFilter;
229
+ if ((bf && bf !== 'none') ||
230
+ (cs.backgroundImage && cs.backgroundImage !== 'none') ||
231
+ alphaOf(cs.backgroundColor) > 0.05) return true;
232
+ p = p.parentElement;
77
233
  }
78
- return o;
234
+ return false;
79
235
  }
80
236
 
81
- // engine.ts
82
- var MAXG = 32;
83
- var MAXS = 16;
84
- var PRESETS = {
85
- button: { grow: 0.14, squash: 1, frost: 0.15, tint: 0, merge: 0, layer: 0 },
86
- bar: { grow: 0.03, squash: 0.4, frost: 0.45, tint: 0, merge: 0, layer: 0 },
87
- card: { grow: 0, squash: 0, frost: 1, tint: 0.2, merge: 0, layer: 0 },
88
- sheet: { grow: 0, squash: 0, frost: 1, tint: 0.28, merge: 1, layer: 0 },
89
- knob: { grow: 0.22, squash: 0.6, frost: 0.06, tint: 0, tintPressed: 0.06, tintColor: [1, 1, 1], merge: 0, layer: 1 },
90
- blob: { grow: 0.1, squash: 1.6, frost: 0.1, tint: 0, merge: 1, layer: 0 }
91
- };
92
- var VS = "attribute vec2 p;varying vec2 vUv;void main(){vUv=p*.5+.5;vUv.y=1.-vUv.y;gl_Position=vec4(p,0.,1.);}";
93
- var FS = `precision highp float;
94
- #define MAXG ${MAXG}
95
- #define MAXS ${MAXS}
96
- varying vec2 vUv;
97
- uniform sampler2D uTex;uniform sampler2D uBlur;uniform vec2 uRes;uniform vec4 uFit;
98
- uniform vec4 uG[MAXG];uniform vec4 uGP[MAXG];uniform vec4 uGT[MAXG];uniform int uGN;
99
- uniform vec4 uS[MAXS];uniform vec4 uSC[MAXS];uniform vec4 uSP[MAXS];uniform int uSN;
100
- uniform float uBezel;uniform float uStrength;uniform float uDisp;uniform float uMergeK;uniform float uDark;uniform float uRim;uniform vec2 uPtr;
101
- float sdRR(vec2 p,vec2 b,float r){r=min(r,min(b.x,b.y));vec2 q=abs(p)-b+r;return length(max(q,0.))+min(max(q.x,q.y),0.)-r;}
102
- 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;}
103
- float lay(vec4 q){return q.y>1.5?1.:0.;}
104
- float field(vec2 p,float L){
105
- float dm=1e4;float dh=1e4;
106
- for(int i=0;i<MAXG;i++){if(i>=uGN)break;
107
- if(uGT[i].w<.002||abs(lay(uGP[i])-L)>.5)continue;
108
- float d=sdRR(p-uG[i].xy,uG[i].zw,uGP[i].x);
109
- if(uGP[i].y>.5&&uGP[i].y<1.5)dm=smin(dm,d,uMergeK);else dh=min(dh,d);}
110
- return min(dm,dh);}
111
- void nearest(vec2 p,float L,out vec4 P,out vec4 T,out float hm){
112
- float best=1e4;P=vec4(0.);T=vec4(0.);hm=1.;
113
- for(int i=0;i<MAXG;i++){if(i>=uGN)break;
114
- if(uGT[i].w<.002||abs(lay(uGP[i])-L)>.5)continue;
115
- float d=sdRR(p-uG[i].xy,uG[i].zw,uGP[i].x);
116
- if(d<best){best=d;P=uGP[i];T=uGT[i];hm=min(uG[i].z,uG[i].w);}}}
117
- vec3 bg(vec2 px,float fr){vec2 u=clamp(px/uRes,0.,1.);vec3 c=texture2D(uTex,u*uFit.xy+uFit.zw).rgb;
118
- if(fr>.001)c=mix(c,texture2D(uBlur,u).rgb,fr);return c;}
119
- vec3 solids(vec2 px,vec3 c,float L){
120
- for(int i=0;i<MAXS;i++){if(i>=uSN)break;if(abs(uSP[i].y-L)>.5)continue;
121
- float d=sdRR(px-uS[i].xy,uS[i].zw,uSP[i].x);c=mix(c,uSC[i].rgb,clamp(.5-d,0.,1.)*uSC[i].a);}
122
- return c;}
123
- vec3 scene(vec2 px,float fr){return solids(px,bg(px,fr),0.);}
124
- vec3 finish(vec3 col,vec2 px,vec2 g,float x,float inside,vec4 P,vec4 T){
125
- float l=dot(col,vec3(.299,.587,.114));col=mix(vec3(l),col,1.2);
126
- col=uDark>.5?col*.78+.015:col*1.02+.03;
127
- col=mix(col,T.rgb,P.z);
128
- vec2 Ld=normalize(uPtr-px+vec2(.001));float fc=dot(g,Ld);float rim=pow(1.-x,6.);
129
- float hi=rim*(max(fc,0.)*.85+max(-fc,0.)*.3)+(1.-smoothstep(0.,1.5,inside))*.22;
130
- return col+vec3(hi*(uDark>.5?.6:1.)*uRim);}
131
- float shadow(vec2 px,float L){return 1.-smoothstep(-16.,34.,field(px-vec2(0.,10.),L));}
132
- vec3 shade0(vec2 px){
133
- float d=field(px,0.);vec3 base=scene(px,0.);
134
- if(d<60.)base*=1.-shadow(px,0.)*(uDark>.5?.32:.16)*smoothstep(0.,2.,d);
135
- if(d>=.5)return base;
136
- vec4 P;vec4 T;float hm;nearest(px,0.,P,T,hm);
137
- float bez=min(uBezel,hm*.85);float inside=max(-d,0.);float x=clamp(inside/bez,0.,1.);
138
- vec2 e=vec2(.75,0.);
139
- vec2 g=vec2(field(px+e.xy,0.)-field(px-e.xy,0.),field(px+e.yx,0.)-field(px-e.yx,0.));g/=max(length(g),1e-4);
140
- vec2 off=-g*uStrength*min(1.,hm/40.)*pow(1.-x,2.2);
141
- float fr=P.w;vec3 col;
142
- col.r=scene(px+off*(1.+uDisp),fr).r;col.g=scene(px+off,fr).g;col.b=scene(px+off*(1.-uDisp),fr).b;
143
- col=finish(col,px,g,x,inside,P,T);
144
- return mix(base,col,clamp(.5-d,0.,1.)*T.w);}
145
- vec3 top0(vec2 px){return solids(px,shade0(px),1.);}
146
- void main(){
147
- vec2 px=vUv*uRes;float d=field(px,1.);vec3 base=top0(px);
148
- if(d<60.)base*=1.-shadow(px,1.)*(uDark>.5?.3:.14)*smoothstep(0.,2.,d);
149
- if(d>=.5){gl_FragColor=vec4(base,1.);return;}
150
- vec4 P;vec4 T;float hm;nearest(px,1.,P,T,hm);
151
- float bez=min(uBezel,hm*.85);float inside=max(-d,0.);float x=clamp(inside/bez,0.,1.);
152
- vec2 e=vec2(.75,0.);
153
- vec2 g=vec2(field(px+e.xy,1.)-field(px-e.xy,1.),field(px+e.yx,1.)-field(px-e.yx,1.));g/=max(length(g),1e-4);
154
- vec2 off=-g*uStrength*min(1.,hm/40.)*pow(1.-x,2.2);
155
- vec3 col;col.r=top0(px+off*(1.+uDisp)).r;col.g=top0(px+off).g;col.b=top0(px+off*(1.-uDisp)).b;
156
- col=finish(col,px,g,x,inside,P,T);
157
- gl_FragColor=vec4(mix(base,col,clamp(.5-d,0.,1.)*T.w),1.);}`;
158
- var colorCache = /* @__PURE__ */ new Map();
159
- var colorCtx = null;
160
- function parseColor(s) {
161
- const hit = colorCache.get(s);
162
- if (hit) return hit;
163
- let out = [0, 0, 0, 0];
164
- if (s && s !== "transparent") {
165
- colorCtx != null ? colorCtx : colorCtx = Object.assign(document.createElement("canvas"), { width: 1, height: 1 }).getContext("2d", { willReadFrequently: true });
166
- if (colorCtx) {
167
- colorCtx.clearRect(0, 0, 1, 1);
168
- colorCtx.fillStyle = s;
169
- colorCtx.fillRect(0, 0, 1, 1);
170
- const [r, g, b, a] = colorCtx.getImageData(0, 0, 1, 1).data;
171
- out = a ? [r / 255, g / 255, b / 255, a / 255] : [0, 0, 0, 0];
237
+ function hexToRgb(hex) {
238
+ var m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(hex || '').trim());
239
+ if (!m) return null;
240
+ var s = m[1];
241
+ if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
242
+ return [parseInt(s.slice(0, 2), 16) / 255,
243
+ parseInt(s.slice(2, 4), 16) / 255,
244
+ parseInt(s.slice(4, 6), 16) / 255];
245
+ }
246
+
247
+ function cssColorToRgb(v) {
248
+ if (!v || v === 'none') return null;
249
+ var m = String(v).match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
250
+ if (m) return [+m[1] / 255, +m[2] / 255, +m[3] / 255];
251
+ return hexToRgb(v);
252
+ }
253
+
254
+ function LiquidGlass(canvas, opts) {
255
+ opts = opts || {};
256
+ this.canvas = canvas;
257
+ this.o = {
258
+ refraction: opts.refraction != null ? opts.refraction : 18,
259
+ dispersion: opts.dispersion != null ? opts.dispersion : 0.05,
260
+ frost: opts.frost != null ? opts.frost : 12,
261
+ bezel: opts.bezel != null ? opts.bezel : 14,
262
+ mergeK: opts.mergeK != null ? opts.mergeK : 30,
263
+ rim: opts.rim != null ? opts.rim : 0.55,
264
+ mode: opts.mode || 'light',
265
+ liquid: !!opts.liquid
266
+ };
267
+ this.items = [];
268
+ this.solids = [];
269
+ this.src = null;
270
+ this.live = false;
271
+ this.dirty = true;
272
+ this.ready = false;
273
+ this.failed = false;
274
+ this.size = { w: 0, h: 0 };
275
+ this._sx = 0; this._sy = 0; this._fc = 0;
276
+ this._fit = [1, 1, 0, 0];
277
+ /* 指针:ptr = 目标位置,ptrS = 平滑后位置(制造跟随的拖尾感) */
278
+ this.ptr = { x: -9999, y: -9999, on: false };
279
+ this.ptrS = { x: -9999, y: -9999, on: false };
280
+ this._rc = null; this._rct = 0;
281
+ this.u = {};
282
+ this._gBuf = new Float32Array(MAXG * 4);
283
+ this._aBuf = new Float32Array(MAXG * 4);
284
+ this._bBuf = new Float32Array(MAXG * 4);
285
+
286
+ var gl = canvas.getContext('webgl', { alpha: false, antialias: false, depth: false, stencil: false })
287
+ || canvas.getContext('experimental-webgl', { alpha: false });
288
+ if (!gl) { this.failed = true; return; }
289
+ this.gl = gl;
290
+
291
+ var pr = gl.createProgram();
292
+ var mk = function (type, srcText) {
293
+ var sh = gl.createShader(type);
294
+ gl.shaderSource(sh, srcText);
295
+ gl.compileShader(sh);
296
+ if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
297
+ console.warn('LiquidGlassNext shader:', gl.getShaderInfoLog(sh));
298
+ return null;
172
299
  }
300
+ return sh;
301
+ };
302
+ var vs = mk(gl.VERTEX_SHADER, VS), fs = mk(gl.FRAGMENT_SHADER, FS);
303
+ if (!vs || !fs) { this.failed = true; return; }
304
+ gl.attachShader(pr, vs); gl.attachShader(pr, fs);
305
+ gl.linkProgram(pr);
306
+ if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) {
307
+ console.warn('LiquidGlassNext link:', gl.getProgramInfoLog(pr));
308
+ this.failed = true; return;
173
309
  }
174
- if (colorCache.size > 256) colorCache.clear();
175
- colorCache.set(s, out);
176
- return out;
310
+ this.program = pr;
311
+ gl.useProgram(pr);
312
+
313
+ var b = gl.createBuffer();
314
+ gl.bindBuffer(gl.ARRAY_BUFFER, b);
315
+ gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
316
+ var loc = gl.getAttribLocation(pr, 'p');
317
+ gl.enableVertexAttribArray(loc);
318
+ gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
319
+
320
+ var self = this;
321
+ ['uTex', 'uBlur', 'uRes', 'uFit', 'uGN', 'uBezel', 'uStrength', 'uDisp', 'uMergeK', 'uDark', 'uRim', 'uPtr', 'uPtrOn']
322
+ .forEach(function (n) { self.u[n] = gl.getUniformLocation(pr, n); });
323
+ ['uG', 'uA', 'uB'].forEach(function (n) { self.u[n] = gl.getUniformLocation(pr, n + '[0]'); });
324
+
325
+ /* ---- 指针监听:只登记坐标,重活留到渲染帧,不在事件里读布局 ---- */
326
+ this._onMove = function (e) {
327
+ var t = (window.performance && performance.now) ? performance.now() : Date.now();
328
+ if (!self._rc || t - self._rct > 300) {
329
+ self._rc = canvas.getBoundingClientRect(); self._rct = t;
330
+ }
331
+ self.ptr.x = e.clientX - self._rc.left;
332
+ self.ptr.y = e.clientY - self._rc.top;
333
+ self.ptr.on = true;
334
+ };
335
+ this._onLeave = function () { self.ptr.on = false; };
336
+ this._onUp = function (e) {
337
+ if (e && e.pointerType && e.pointerType !== 'mouse') self.ptr.on = false;
338
+ };
339
+ window.addEventListener('pointermove', this._onMove, { passive: true });
340
+ window.addEventListener('pointerdown', this._onMove, { passive: true });
341
+ window.addEventListener('pointerup', this._onUp, { passive: true });
342
+ document.addEventListener('pointerleave', this._onLeave);
343
+ window.addEventListener('blur', this._onLeave);
344
+
345
+ this.tex = this._tex(0);
346
+ this.blurTex = this._tex(1);
347
+ gl.uniform1i(this.u.uTex, 0);
348
+ gl.uniform1i(this.u.uBlur, 1);
349
+
350
+ this.blurC = document.createElement('canvas');
351
+
352
+ this._raf = requestAnimationFrame(function loop() {
353
+ if (!self._dead) { self._frame(); self._raf = requestAnimationFrame(loop); }
354
+ });
177
355
  }
178
- var len = (v, ref) => !v ? 0 : v.trim().endsWith("%") ? parseFloat(v) / 100 * ref : parseFloat(v) || 0;
179
- /* 最近的「可滚动祖先」:内部滚动区(侧栏/表格/手机屏幕)滚动时 window.scrollY 不变,
180
- 靠它把滚动量也纳入缓存平移,否则这类容器只能逐帧重读布局 */
181
- var scrollParentOf = (el) => {
182
- for (let p = el.parentElement; p && p !== document.body; p = p.parentElement) {
183
- const s = getComputedStyle(p);
184
- const scrollable = /^(auto|scroll)$/.test(s.overflowY) || /^(auto|scroll)$/.test(s.overflowX);
185
- if (scrollable && (p.scrollHeight > p.clientHeight + 1 || p.scrollWidth > p.clientWidth + 1)) return p;
356
+
357
+ LiquidGlass.prototype._tex = function (unit) {
358
+ var gl = this.gl, t = gl.createTexture();
359
+ gl.activeTexture(gl.TEXTURE0 + unit);
360
+ gl.bindTexture(gl.TEXTURE_2D, t);
361
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
362
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
363
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
364
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
365
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
366
+ new Uint8Array([128, 128, 128, 255]));
367
+ return t;
368
+ };
369
+
370
+ LiquidGlass.prototype.set = function (o) {
371
+ for (var k in (o || {})) this.o[k] = o[k];
372
+ this.dirty = true;
373
+ };
374
+
375
+ LiquidGlass.prototype.setSource = function (src, opt) {
376
+ opt = opt || {};
377
+ this.src = src;
378
+ this.live = opt.live != null ? !!opt.live
379
+ : (typeof HTMLVideoElement !== 'undefined' && src instanceof HTMLVideoElement);
380
+ this.dirty = true;
381
+ };
382
+
383
+ LiquidGlass.prototype.invalidate = function () { this.dirty = true; };
384
+
385
+ LiquidGlass.prototype.add = function (el, opts) {
386
+ for (var i = 0; i < this.items.length; i++) if (this.items[i].el === el) return this.items[i];
387
+ var d = el.dataset, pre = PRESETS[(opts && opts.preset) || d.glass] || PRESETS.bar;
388
+ var it = {
389
+ el: el,
390
+ grow: pre.grow, frost: pre.frost, tint: pre.tint, merge: pre.merge, layer: pre.layer,
391
+ dl: 0, dt: 0, dw: 0, dh: 0, rad: 0,
392
+ tintC: [1, 1, 1], hidden: false, blocked: false, parent: null, live: false
393
+ };
394
+ this.items.push(it);
395
+ return it;
396
+ };
397
+
398
+ LiquidGlass.prototype.remove = function (el) {
399
+ el.removeAttribute('data-glass-live');
400
+ this.items = this.items.filter(function (i) { return i.el !== el; });
401
+ this.solids = this.solids.filter(function (s) { return s.el !== el; });
402
+ };
403
+
404
+ /* 每帧打标:shader 本帧实际绘制的元素才挂 data-glass-live,
405
+ 其余退回 CSS 自绘玻璃(backdrop-filter 直接糊引擎画布) */
406
+ LiquidGlass.prototype._syncLive = function () {
407
+ var items = this.items;
408
+ for (var i = 0; i < items.length; i++) {
409
+ var it = items[i], want = !it.blocked && !it.hidden && it._drawn === true;
410
+ if (it.live === want) continue;
411
+ it.live = want;
412
+ if (want) it.el.setAttribute('data-glass-live', '');
413
+ else it.el.removeAttribute('data-glass-live');
186
414
  }
187
- return null;
188
415
  };
189
- var LiquidGlass = class {
190
- constructor(canvas, opts = {}) {
191
- __publicField(this, "canvas");
192
- __publicField(this, "failed", false);
193
- __publicField(this, "o");
194
- __publicField(this, "gl");
195
- __publicField(this, "program");
196
- __publicField(this, "buffer");
197
- __publicField(this, "u", {});
198
- __publicField(this, "tex");
199
- __publicField(this, "blurTex");
200
- __publicField(this, "blurC");
201
- __publicField(this, "items", []);
202
- __publicField(this, "solids", []);
203
- __publicField(this, "ptr", null);
204
- __publicField(this, "tilt", null);
205
- __publicField(this, "lastPtr", 0);
206
- __publicField(this, "light", { x: -400, y: -600 });
207
- __publicField(this, "fit", [1, 1, 0, 0]);
208
- __publicField(this, "size", { w: 0, h: 0 });
209
- __publicField(this, "src", null);
210
- __publicField(this, "live", false);
211
- __publicField(this, "dirty", false);
212
- __publicField(this, "ready", false);
213
- __publicField(this, "raf", 0);
214
- __publicField(this, "frames", 0);
215
- __publicField(this, "nextId", 1);
216
- __publicField(this, "bG", new Float32Array(MAXG * 4));
217
- __publicField(this, "bGP", new Float32Array(MAXG * 4));
218
- __publicField(this, "bGT", new Float32Array(MAXG * 4));
219
- __publicField(this, "bS", new Float32Array(MAXS * 4));
220
- __publicField(this, "bSC", new Float32Array(MAXS * 4));
221
- __publicField(this, "bSP", new Float32Array(MAXS * 4));
222
- __publicField(this, "_onPtr", (e) => {
223
- this.ptr = { x: e.clientX, y: e.clientY };
224
- this.lastPtr = performance.now();
225
- });
226
- __publicField(this, "_onUp", () => this.items.forEach((i) => {
227
- if (!i.held) i.pressed = false;
228
- }));
229
- __publicField(this, "_onTilt", (e) => {
230
- if (e.gamma != null && e.beta != null && performance.now() - this.lastPtr > 3e3) {
231
- this.tilt = { g: e.gamma, b: e.beta };
232
- this.ptr = null;
416
+
417
+ LiquidGlass.prototype.scan = function (root) {
418
+ root = root || document;
419
+ var self = this;
420
+ var seen = [];
421
+ Array.prototype.forEach.call(root.querySelectorAll('[data-glass]'), function (el) {
422
+ seen.push(el);
423
+ for (var i = 0; i < self.items.length; i++) if (self.items[i].el === el) return;
424
+ self.add(el);
425
+ });
426
+ /* 父子关系:最近的带 data-glass 祖先。子元素画在 layer 1,叠在父层玻璃之上 */
427
+ this.items.forEach(function (it) {
428
+ var p = it.el.parentElement, found = null;
429
+ while (p && p !== document.body) {
430
+ if (p.hasAttribute && p.hasAttribute('data-glass')) {
431
+ for (var k = 0; k < self.items.length; k++) if (self.items[k].el === p) { found = self.items[k]; break; }
432
+ if (found) break;
233
433
  }
234
- });
235
- __publicField(this, "_loop", () => {
236
- /* 页面不可见时不绘制(纯省电,不影响观感) */
237
- if (document.hidden) { this.raf = requestAnimationFrame(this._loop); return; }
238
- this._frame();
239
- this.raf = requestAnimationFrame(this._loop);
240
- });
241
- this.canvas = canvas;
242
- this.o = { refraction: 28, dispersion: 0.1, frost: 14, bezel: 26, mergeK: 40, rim: 0.75, mode: "light", liquid: true, ...opts };
243
- const gl = canvas.getContext("webgl", { antialias: false });
244
- if (!gl || gl.isContextLost()) {
245
- this.failed = true;
246
- console.warn("LiquidGlass: WebGL unavailable");
247
- return;
434
+ p = p.parentElement;
248
435
  }
249
- this.gl = gl;
250
- const sh = (t, s) => {
251
- const o = gl.createShader(t);
252
- gl.shaderSource(o, s);
253
- gl.compileShader(o);
254
- if (!gl.getShaderParameter(o, gl.COMPILE_STATUS)) console.error(gl.getShaderInfoLog(o));
255
- return o;
256
- };
257
- const pr = gl.createProgram();
258
- const vs = sh(gl.VERTEX_SHADER, VS), fs = sh(gl.FRAGMENT_SHADER, FS);
259
- gl.attachShader(pr, vs);
260
- gl.attachShader(pr, fs);
261
- gl.linkProgram(pr);
262
- gl.deleteShader(vs);
263
- gl.deleteShader(fs);
264
- if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) {
265
- console.error("LiquidGlass:", gl.getProgramInfoLog(pr));
266
- gl.deleteProgram(pr);
267
- this.failed = true;
268
- return;
269
- }
270
- gl.useProgram(pr);
271
- this.program = pr;
272
- const b = gl.createBuffer();
273
- gl.bindBuffer(gl.ARRAY_BUFFER, b);
274
- this.buffer = b;
275
- gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
276
- const loc = gl.getAttribLocation(pr, "p");
277
- gl.enableVertexAttribArray(loc);
278
- gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
279
- ["uTex", "uBlur", "uRes", "uFit", "uGN", "uSN", "uBezel", "uStrength", "uDisp", "uMergeK", "uDark", "uRim", "uPtr"].forEach((n) => this.u[n] = gl.getUniformLocation(pr, n));
280
- ["uG", "uGP", "uGT", "uS", "uSC", "uSP"].forEach((n) => this.u[n] = gl.getUniformLocation(pr, n + "[0]"));
281
- this.tex = this._tex(0);
282
- this.blurTex = this._tex(1);
283
- gl.uniform1i(this.u.uTex, 0);
284
- gl.uniform1i(this.u.uBlur, 1);
285
- this.blurC = document.createElement("canvas");
286
- window.addEventListener("pointermove", this._onPtr);
287
- window.addEventListener("pointerup", this._onUp);
288
- window.addEventListener("pointercancel", this._onUp);
289
- window.addEventListener("deviceorientation", this._onTilt);
290
- this.raf = requestAnimationFrame(this._loop);
291
- }
292
- _tex(unit) {
293
- const gl = this.gl, t = gl.createTexture();
294
- gl.activeTexture(gl.TEXTURE0 + unit);
295
- gl.bindTexture(gl.TEXTURE_2D, t);
296
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
297
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
298
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
299
- gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
300
- return t;
301
- }
302
- set(o) {
303
- const f = this.o.frost;
304
- Object.assign(this.o, o);
305
- if (o.frost !== void 0 && o.frost !== f) this.dirty = true;
306
- }
307
- setSource(src, { live } = {}) {
308
- this.src = src;
309
- this.live = live != null ? live : typeof HTMLVideoElement !== "undefined" && src instanceof HTMLVideoElement;
310
- this.dirty = true;
311
- }
312
- invalidate() {
313
- this.dirty = true;
436
+ it.parent = found;
437
+ });
438
+ this.items = this.items.filter(function (i) {
439
+ if (i.el.isConnected && seen.indexOf(i.el) >= 0) return true;
440
+ i.el.removeAttribute('data-glass-live');
441
+ return false;
442
+ });
443
+ this.solids = this.solids.filter(function (s) {
444
+ return s.el.isConnected && s.el.hasAttribute('data-glass-solid');
445
+ });
446
+ Array.prototype.forEach.call(root.querySelectorAll('[data-glass-solid]'), function (el) {
447
+ for (var i = 0; i < self.solids.length; i++) if (self.solids[i].el === el) return;
448
+ self.solids.push({ el: el, layer: parseFloat(el.dataset.layer || '0') });
449
+ });
450
+ this._occl();
451
+ this.dirty = true;
452
+ };
453
+
454
+ LiquidGlass.prototype._occl = function () {
455
+ this.items.forEach(function (it) {
456
+ it.blocked = it.el.isConnected ? blockedByAncestor(it.el) : true;
457
+ });
458
+ };
459
+
460
+ LiquidGlass.prototype.destroy = function () {
461
+ this._dead = true;
462
+ if (this._raf) cancelAnimationFrame(this._raf);
463
+ if (this._onMove) {
464
+ window.removeEventListener('pointermove', this._onMove);
465
+ window.removeEventListener('pointerdown', this._onMove);
466
+ window.removeEventListener('pointerup', this._onUp);
467
+ document.removeEventListener('pointerleave', this._onLeave);
468
+ window.removeEventListener('blur', this._onLeave);
314
469
  }
315
- scan(rootEl) {
316
- const keep = (el, attr) => el.isConnected && el.hasAttribute(attr);
317
- this.items.filter((i) => !keep(i.el, "data-glass")).forEach((i) => this._detach(i));
318
- this.items = this.items.filter((i) => keep(i.el, "data-glass"));
319
- this.solids.filter((s) => s.live && !keep(s.el, "data-glass-solid")).forEach((s) => {
320
- s.el.style.opacity = s.opacity;
321
- });
322
- this.solids = this.solids.filter((s) => keep(s.el, "data-glass-solid"));
323
- rootEl.querySelectorAll("[data-glass]").forEach((el) => this.add(el));
324
- rootEl.querySelectorAll("[data-glass-solid]").forEach((el) => this.addSolid(el));
325
- this.refreshOcclusion();
470
+ document.documentElement.classList.remove('lg-ready');
471
+ this.items.forEach(function (i) { i.el.removeAttribute('data-glass-live'); });
472
+ var gl = this.gl;
473
+ if (gl) {
474
+ if (this.tex) gl.deleteTexture(this.tex);
475
+ if (this.blurTex) gl.deleteTexture(this.blurTex);
476
+ if (this.program) gl.deleteProgram(this.program);
326
477
  }
327
- /** Links each item to its closest glass ancestor (nested glass is drawn on the top layer) and
328
- blocks items under any other ancestor that paints: the canvas is behind it. Backgrounds
329
- change with state and theme, so the frame loop calls this periodically too. */
330
- refreshOcclusion() {
331
- const byEl = new Map(this.items.map((i) => [i.el, i]));
332
- const link = (x) => {
333
- var _a;
334
- x.parent = null;
335
- x.blocked = false;
336
- for (let p = x.el.parentElement; p && p !== document.body; p = p.parentElement) {
337
- const g = byEl.get(p);
338
- if (g) {
339
- (_a = x.parent) != null ? _a : x.parent = g;
340
- continue;
341
- }
342
- if (paintsOverBackdrop(getComputedStyle(p))) {
343
- x.blocked = true;
344
- break;
345
- }
346
- }
347
- };
348
- this.items.forEach(link);
349
- this.solids.forEach(link);
350
- this.solids.forEach((s) => {
351
- if (s.el.closest(".slider, .lg-slider")) {
352
- s.blocked = false;
353
- s.parent = null;
478
+ };
479
+
480
+ LiquidGlass.prototype._frame = function () {
481
+ var gl = this.gl, cv = this.canvas;
482
+ var W = cv.clientWidth, H = cv.clientHeight;
483
+ if (!W || !H || document.hidden) return;
484
+
485
+ /* ---- 滚动帧零 DOM 读取:位置 = 文档坐标缓存 − scroll ---- */
486
+ var sx = window.scrollX, sy = window.scrollY;
487
+ var scrolling = sx !== this._sx || sy !== this._sy;
488
+ this._sx = sx; this._sy = sy;
489
+
490
+ var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
491
+ var bw = Math.round(W * dpr), bh = Math.round(H * dpr);
492
+ if (cv.width !== bw || cv.height !== bh) { cv.width = bw; cv.height = bh; this.dirty = true; }
493
+ gl.viewport(0, 0, bw, bh);
494
+ if (W !== this.size.w || H !== this.size.h) { this.size = { w: W, h: H }; this.dirty = true; }
495
+
496
+ /* ---- 采集几何 → uniform 缓冲 ---- */
497
+ var gN = 0;
498
+ var gB = this._gBuf, aB = this._aBuf, bB = this._bBuf;
499
+ var items = this.items;
500
+ for (var i = 0; i < items.length; i++) items[i]._drawn = false;
501
+
502
+ /* solid 件(滑块轨道/进度条)不进 shader:引擎画布位于页面最底层,
503
+ 任何带自身背景的容器都会遮住它。纯色件一律交回 DOM 自绘。 */
504
+
505
+ for (var i = 0; i < items.length && gN < MAXG; i++) {
506
+ var it = items[i], el = it.el;
507
+ if (!scrolling) it.blocked = blockedByAncestor(el);
508
+ if (it.blocked) continue;
509
+ if (!scrolling || !it.dw) {
510
+ var cs = getComputedStyle(el);
511
+ if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) < 0.05) {
512
+ it.hidden = true; continue;
354
513
  }
355
- });
356
- this.items.forEach((it) => {
357
- const el = it.el;
358
- if (el.dataset.glass === "knob" && el.closest(".slider, .lg-slider, .sw")) {
359
- it.blocked = false;
360
- it.parent = null;
514
+ it.hidden = false;
515
+ var r = el.getBoundingClientRect();
516
+ if (r.width < 2 || r.height < 2) { it.hidden = true; continue; }
517
+ it.dl = r.left + sx; it.dt = r.top + sy;
518
+ it.dw = r.width; it.dh = r.height;
519
+ it.rad = parseRadius(cs);
520
+ /* data-* 覆盖每帧重读(glass-webgl 可能在 scan 后打标/改标) */
521
+ var dset = el.dataset;
522
+ ['tint', 'frost', 'layer', 'merge', 'grow'].forEach(function (k) {
523
+ if (dset[k] !== undefined) { var v = parseFloat(dset[k]); if (!isNaN(v)) it[k] = v; }
524
+ });
525
+ /* 着色解析:保留 CSS 颜色的 alpha(transparent → 不着色,由 DOM wash 兜底) */
526
+ var dark = this.o.mode === 'dark';
527
+ var tv = cs.getPropertyValue('--lg-tint').trim();
528
+ var tc = null, taMul = 0;
529
+ if (tv && tv !== 'transparent' && tv !== 'none') {
530
+ tc = cssColorToRgb(tv);
531
+ if (tc) taMul = alphaOf(tv);
361
532
  }
362
- });
363
- }
364
- add(el, opts = {}) {
365
- const existing = this.items.find((i) => i.el === el);
366
- if (existing) return existing;
367
- const d = el.dataset, pre = PRESETS[opts.preset || d.glass] || PRESETS.bar;
368
- const ov = {};
369
- ["tint", "frost", "layer", "merge", "grow", "squash"].forEach((k) => {
370
- if (d[k] !== void 0) ov[k] = parseFloat(d[k]);
371
- });
372
- const it = {
373
- ...pre,
374
- ...ov,
375
- ...opts,
376
- el,
377
- sx: 1,
378
- sy: 1,
379
- vx: 0,
380
- vy: 0,
381
- v: { x: 0, y: 0 },
382
- last: null,
383
- pressed: false,
384
- held: false,
385
- scs: "",
386
- t: 0,
387
- id: this.nextId++,
388
- parent: null,
389
- blocked: false,
390
- live: false,
391
- down: () => {
392
- it.pressed = true;
533
+ if (!tc) {
534
+ var bgv = cs.backgroundColor;
535
+ tc = cssColorToRgb(bgv);
536
+ if (tc) taMul = alphaOf(bgv);
393
537
  }
394
- };
395
- it.t = it.tint;
396
- el.addEventListener("pointerdown", it.down);
397
- this.items.push(it);
398
- return it;
399
- }
400
- addSolid(el) {
401
- if (this.solids.some((s) => s.el === el)) return;
402
- this.solids.push({ el, layer: parseFloat(el.dataset.layer || "0"), opacity: el.style.opacity, live: false, parent: null, blocked: false });
403
- }
404
- press(el, on) {
405
- const it = this.items.find((i) => i.el === el);
406
- if (it) {
407
- it.pressed = on;
408
- it.held = on;
538
+ if (!tc) tc = dark ? [0.09, 0.09, 0.11] : [1, 1, 1];
539
+ it.tintC = tc;
540
+ it.tintA = taMul;
409
541
  }
410
- }
411
- remove(el) {
412
- const it = this.items.find((i) => i.el === el);
413
- if (it) this._detach(it);
414
- const s = this.solids.find((s2) => s2.el === el);
415
- if (s == null ? void 0 : s.live) el.style.opacity = s.opacity;
416
- this.items = this.items.filter((i) => i.el !== el);
417
- this.solids = this.solids.filter((s2) => s2.el !== el);
418
- }
419
- /** Stops rendering and releases GL resources. The canvas stays usable, so a remount
420
- (React strict mode, HMR) can create a new engine on the same element. */
421
- destroy() {
422
- cancelAnimationFrame(this.raf);
423
- document.documentElement.classList.remove("lg-ready");
424
- window.removeEventListener("pointermove", this._onPtr);
425
- window.removeEventListener("pointerup", this._onUp);
426
- window.removeEventListener("pointercancel", this._onUp);
427
- window.removeEventListener("deviceorientation", this._onTilt);
428
- this.items.forEach((i) => this._detach(i));
429
- this.solids.forEach((s) => {
430
- if (s.live) s.el.style.opacity = s.opacity;
431
- });
432
- this.items = [];
433
- this.solids = [];
434
- const gl = this.gl;
435
- if (gl && !this.failed) {
436
- gl.deleteTexture(this.tex);
437
- gl.deleteTexture(this.blurTex);
438
- gl.deleteBuffer(this.buffer);
439
- gl.deleteProgram(this.program);
440
- gl.clearColor(0, 0, 0, 0);
441
- gl.clear(gl.COLOR_BUFFER_BIT);
542
+ if (it.hidden) continue;
543
+
544
+ var hw = it.dw / 2, hh = it.dh / 2;
545
+ var grow = it.grow || 0;
546
+ if (grow > 0) {
547
+ var pad = grow * Math.min(hw, hh);
548
+ hw += pad; hh += pad;
442
549
  }
550
+ /* 中心永远取元素几何中心:grow 只向外扩边,不得移动中心
551
+ (否则玻璃整体向右下偏 pad px,表现为"内容不居中/外圈偏") */
552
+ var x = it.dl - sx + it.dw / 2, y = it.dt - sy + it.dh / 2;
553
+ if (x + hw < 0 || x - hw > W || y + hh < 0 || y - hh > H) continue;
554
+
555
+ var o4 = gN * 4;
556
+ /* 嵌套深度:子元素(父链上有其他玻璃)画在 layer 1,叠在父层之上 */
557
+ var depth = 0, anc = it.parent;
558
+ while (anc) { depth++; anc = anc.parent; }
559
+ if (depth > 1) continue;
560
+ gB[o4] = x; gB[o4 + 1] = y; gB[o4 + 2] = hw; gB[o4 + 3] = hh;
561
+ aB[o4] = Math.min(it.rad, Math.min(hw, hh));
562
+ aB[o4 + 1] = (depth >= 1 || it.layer >= 1) ? 1 : 0;
563
+ aB[o4 + 2] = clamp(it.frost, 0, 1);
564
+ aB[o4 + 3] = clamp(it.tint * (it.tintA != null ? it.tintA : 1), 0, 1);
565
+ bB[o4] = it.merge ? 1 : 0;
566
+ bB[o4 + 1] = it.tintC[0]; bB[o4 + 2] = it.tintC[1]; bB[o4 + 3] = it.tintC[2];
567
+ it._drawn = true;
568
+ gN++;
443
569
  }
444
- _detach(it) {
445
- it.el.removeEventListener("pointerdown", it.down);
446
- this._setLive(it, false);
447
- }
448
- /* data-glass-live marks elements the shader draws this frame; only those drop their CSS glass. */
449
- _setLive(it, live) {
450
- if (it.live === live) return;
451
- it.live = live;
452
- if (live) it.el.setAttribute("data-glass-live", "");
570
+ this._syncLive();
571
+
572
+ /* ---- 纹理上传(live 隔帧;脏时必传) ---- */
573
+ if (this.src && (this.dirty || (this.live && (this._fc % 2 === 0)))) this._upload();
574
+
575
+ gl.useProgram(this.program);
576
+ var f = this._fit;
577
+ gl.uniform4f(this.u.uFit, f[0], f[1], f[2], f[3]);
578
+ gl.uniform2f(this.u.uRes, W, H);
579
+ gl.uniform1i(this.u.uGN, gN);
580
+ gl.uniform1f(this.u.uBezel, this.o.bezel);
581
+ gl.uniform1f(this.u.uStrength, this.o.refraction);
582
+ gl.uniform1f(this.u.uDisp, this.o.dispersion);
583
+ gl.uniform1f(this.u.uMergeK, this.o.mergeK);
584
+ gl.uniform1f(this.u.uDark, this.o.mode === 'dark' ? 1 : 0);
585
+ gl.uniform1f(this.u.uRim, this.o.rim);
586
+
587
+ /* 指针平滑:进入页面时直接就位,移动时带一点跟手延迟 → 光沿边框滑动 */
588
+ var tp = this.ptr, sp = this.ptrS;
589
+ if (tp.on) {
590
+ if (!sp.on) { sp.x = tp.x; sp.y = tp.y; sp.on = true; }
453
591
  else {
454
- it.el.removeAttribute("data-glass-live");
455
- it.el.style.scale = "";
456
- it.scs = "";
457
- it.sx = it.sy = 1;
458
- it.vx = it.vy = 0;
592
+ sp.x += (tp.x - sp.x) * 0.22;
593
+ sp.y += (tp.y - sp.y) * 0.22;
594
+ if (Math.abs(tp.x - sp.x) < 0.4 && Math.abs(tp.y - sp.y) < 0.4) { sp.x = tp.x; sp.y = tp.y; }
459
595
  }
596
+ } else {
597
+ sp.on = false;
460
598
  }
461
- _upload(W, H) {
462
- const gl = this.gl, s = this.src;
463
- if (s instanceof HTMLVideoElement && s.readyState < 2) return;
464
- const sw = s.videoWidth || s.naturalWidth || s.width;
465
- const sh = s.videoHeight || s.naturalHeight || s.height;
466
- if (!sw || !sh) return;
467
- const A = W / H, B = sw / sh;
468
- let fx = 1, fy = 1, ox = 0, oy = 0;
469
- if (B > A) {
470
- fx = A / B;
471
- ox = (1 - fx) / 2;
472
- } else {
473
- fy = B / A;
474
- oy = (1 - fy) / 2;
475
- }
476
- this.fit = [fx, fy, ox, oy];
477
- try {
478
- gl.activeTexture(gl.TEXTURE0);
479
- gl.bindTexture(gl.TEXTURE_2D, this.tex);
480
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, s);
481
- const bc = this.blurC, bw = Math.max(2, Math.round(W / 4)), bh = Math.max(2, Math.round(H / 4));
482
- if (bc.width !== bw) bc.width = bw;
483
- if (bc.height !== bh) bc.height = bh;
484
- const x = bc.getContext("2d"), br = Math.max(this.o.frost / 4, 0.01), pad = br * 3;
485
- x.filter = `blur(${br}px)`;
486
- x.drawImage(s, ox * sw, oy * sh, fx * sw, fy * sh, -pad, -pad, bw + 2 * pad, bh + 2 * pad);
487
- gl.activeTexture(gl.TEXTURE1);
488
- gl.bindTexture(gl.TEXTURE_2D, this.blurTex);
489
- gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bc);
490
- this.ready = true;
491
- document.documentElement.classList.add("lg-ready");
492
- } catch (e) {
493
- console.warn("LiquidGlass: source not usable (CORS?)", e);
494
- }
495
- this.dirty = false;
599
+ gl.uniform2f(this.u.uPtr, sp.x, sp.y);
600
+ gl.uniform1f(this.u.uPtrOn, sp.on ? 1 : 0);
601
+ if (gN) {
602
+ gl.uniform4fv(this.u.uG, gB);
603
+ gl.uniform4fv(this.u.uA, aB);
604
+ gl.uniform4fv(this.u.uB, bB);
496
605
  }
497
- _frame() {
498
- var _a, _b;
499
- const gl = this.gl, cv = this.canvas;
500
- const W = cv.clientWidth, H = cv.clientHeight;
501
- if (!W || !H) return;
502
- /* 滚动帧零布局读取:整页滚动时元素视口位置 = 文档坐标缓存 − scroll,
503
- 纯算术平移完全精确;非滚动帧(指针/动画)才读真实 DOM 并回写缓存。
504
- 内部滚动容器场景 scrollX/Y 不变 → 自动退回逐帧读取,行为同旧版 */
505
- const sx = window.scrollX, sy = window.scrollY;
506
- const scrolling = sx !== this._sx || sy !== this._sy;
507
- this._sx = sx; this._sy = sy;
508
- const cr = scrolling && this._cr ? this._cr : (this._cr = cv.getBoundingClientRect()), k = W / cr.width;
509
- /* 大片视口按 1× 渲染:这层本来就是模糊的折射/磨砂,放大后肉眼看不出,
510
- 但 DPR1.5 意味着 2.25 倍像素 —— 视网膜屏上这是最大的单项开销 */
511
- const dpr = Math.min(window.devicePixelRatio || 1, 1.5), bw = Math.round(W * dpr), bh = Math.round(H * dpr);
512
- if (cv.width !== bw || cv.height !== bh) {
513
- cv.width = bw;
514
- cv.height = bh;
515
- }
516
- gl.viewport(0, 0, bw, bh);
517
- if (W !== this.size.w || H !== this.size.h) {
518
- this.size = { w: W, h: H };
519
- this.dirty = true;
520
- }
521
- /* 滚动期间冻结壁纸纹理:纹理喂的是 fixed 壁纸,不随页面/容器滚动移动,
522
- 冻结不改变画面;省掉滚动中每帧的「全屏 2D blur + 全尺寸纹理上传」——这是滚动掉帧的主因。
523
- 非滚动帧仍隔帧更新(壁纸缓慢漂移,30fps 更新肉眼无差) */
524
- let anyScroll = scrolling;
525
- if (!anyScroll) {
526
- for (const it of this.items) {
527
- if (it.sco && it.dl !== void 0 && (it.sco.scrollTop !== it.sTop || it.sco.scrollLeft !== it.sLeft)) { anyScroll = true; break; }
528
- }
529
- }
530
- if (this.src && (this.dirty || (!anyScroll && this.live && this.frames % 2 === 0))) this._upload(W, H);
531
- if (!this.ready) return;
532
- if (++this.frames % 30 === 0) this.refreshOcclusion();
533
- let tx = -W * 0.2, ty = -H * 0.6;
534
- if (this.ptr) {
535
- tx = (this.ptr.x - cr.left) * k;
536
- ty = (this.ptr.y - cr.top) * k;
537
- } else if (this.tilt) {
538
- tx = W / 2 + this.tilt.g / 30 * W;
539
- ty = H / 2 + (this.tilt.b - 45) / 30 * H;
540
- }
541
- this.light.x += (tx - this.light.x) * 0.12;
542
- this.light.y += (ty - this.light.y) * 0.12;
543
- const dark = this.o.mode === "dark", liquid = this.o.liquid;
544
- const vis = /* @__PURE__ */ new Map();
545
- const cands = [];
546
- for (const it of this.items) {
547
- const el = it.el;
548
- if (!el.isConnected || it.blocked) continue;
549
- let r;
550
- if (it.sco === void 0) it.sco = scrollParentOf(el);
551
- const sdY = it.sco ? it.sco.scrollTop - it.sTop : 0;
552
- const sdX = it.sco ? it.sco.scrollLeft - it.sLeft : 0;
553
- if (it.dl !== void 0 && (scrolling || sdY || sdX)) {
554
- r = { left: it.dl - sx - sdX, top: it.dt - sy - sdY, right: it.dl - sx - sdX + it.dw, bottom: it.dt - sy - sdY + it.dh, width: it.dw, height: it.dh };
555
- } else {
556
- r = el.getBoundingClientRect();
557
- it.dl = r.left + sx; it.dt = r.top + sy; it.dw = r.width; it.dh = r.height;
558
- if (it.sco) { it.sTop = it.sco.scrollTop; it.sLeft = it.sco.scrollLeft; }
559
- it.ow = el.offsetWidth || r.width;
560
- const cs0 = getComputedStyle(el);
561
- it.vis = cs0.visibility === "hidden";
562
- it.rad0 = len(cs0.borderTopLeftRadius, it.ow);
563
- it.tv = cs0.getPropertyValue("--lg-tint").trim();
564
- it.op = effectiveOpacity(el);
565
- }
566
- if (r.width < 1 || r.height < 1) continue;
567
- if (r.bottom < cr.top - 40 || r.top > cr.bottom + 40 || r.right < cr.left - 40 || r.left > cr.right + 40) continue;
568
- if (it.vis) continue;
569
- vis.set(it.id, { it, r });
570
- cands.push({ id: it.id, parent: (_b = (_a = it.parent) == null ? void 0 : _a.id) != null ? _b : null, area: r.width * r.height, knob: it.el.dataset.glass === "knob" ? 1 : 0 });
571
- }
572
- const chosen = selectShapes(cands, MAXG);
573
- for (const it of this.items) this._setLive(it, chosen.has(it.id));
574
- let n = 0;
575
- for (const [id, depth] of chosen) {
576
- const { it, r } = vis.get(id);
577
- const el = it.el;
578
- const op = it.op;
579
- const cx = (r.left + r.width / 2 - cr.left) * k, cy = (r.top + r.height / 2 - cr.top) * k;
580
- const hx = r.width / 2 * k / it.sx, hy = r.height / 2 * k / it.sy;
581
- if (it.last) {
582
- it.v.x = it.v.x * 0.6 + (cx - it.last.x) * 0.4;
583
- it.v.y = it.v.y * 0.6 + (cy - it.last.y) * 0.4;
584
- }
585
- it.last = { x: cx, y: cy };
586
- const g = it.pressed && liquid ? 1 + it.grow : 1, sq = liquid ? it.squash : 0;
587
- const ax = Math.abs(it.v.x), ay = Math.abs(it.v.y);
588
- const tX = g * (1 + Math.min(ax * 0.018 * sq, 0.35) - Math.min(ay * 9e-3 * sq, 0.18));
589
- const tY = g * (1 + Math.min(ay * 0.018 * sq, 0.35) - Math.min(ax * 9e-3 * sq, 0.18));
590
- if (liquid) {
591
- it.vx = (it.vx + (tX - it.sx) * 0.22) * 0.7;
592
- it.sx += it.vx;
593
- it.vy = (it.vy + (tY - it.sy) * 0.22) * 0.7;
594
- it.sy += it.vy;
595
- } else {
596
- it.sx = tX;
597
- it.sy = tY;
598
- it.vx = it.vy = 0;
599
- }
600
- const scs = Math.abs(it.sx - 1) < 2e-3 && Math.abs(it.sy - 1) < 2e-3 ? "" : `${it.sx.toFixed(4)} ${it.sy.toFixed(4)}`;
601
- if (scs !== it.scs) {
602
- el.style.scale = scs;
603
- it.scs = scs;
604
- }
605
- const Hx = hx * it.sx, Hy = hy * it.sy, ow = it.ow || r.width;
606
- const rad = Math.min(it.rad0 * (hx * 2 / ow) * Math.min(it.sx, it.sy), Hx, Hy);
607
- const ds = el.dataset;
608
- if (ds.tint !== void 0) it.tint = parseFloat(ds.tint);
609
- if (ds.grow !== void 0) it.grow = parseFloat(ds.grow);
610
- const tt = it.pressed && it.tintPressed !== void 0 ? it.tintPressed : it.tint;
611
- it.t += (tt - it.t) * 0.2;
612
- const tv = it.tv;
613
- const custom = tv && tv !== "transparent" ? parseColor(tv) : null;
614
- const tc = it.tintColor || (custom ? [custom[0], custom[1], custom[2]] : dark ? [0.07, 0.07, 0.09] : [1, 1, 1]);
615
- const layer = Math.max(depth, it.layer >= 1 ? 1 : 0);
616
- this.bG.set([cx, cy, Hx, Hy], n * 4);
617
- this.bGP.set([rad, layer >= 1 ? 2 : it.merge ? 1 : 0, it.t, it.frost], n * 4);
618
- this.bGT.set([tc[0], tc[1], tc[2], op], n * 4);
619
- n++;
620
- }
621
- let m = 0;
622
- for (const s of this.solids) {
623
- const el = s.el;
624
- let r;
625
- if (scrolling && s.dl !== void 0) {
626
- r = { left: s.dl - sx, top: s.dt - sy, width: s.dw, height: s.dh };
627
- } else if (el.isConnected) {
628
- r = el.getBoundingClientRect();
629
- s.dl = r.left + sx; s.dt = r.top + sy; s.dw = r.width; s.dh = r.height;
630
- s.ow = el.offsetWidth || r.width;
631
- const cs0 = getComputedStyle(el);
632
- s.solidSrc = el.dataset.solidColor || cs0.getPropertyValue("--lg-solid").trim() || cs0.backgroundColor;
633
- s.rad0 = len(cs0.borderTopLeftRadius, s.ow);
634
- } else {
635
- r = null;
636
- }
637
- const draw = m < MAXS && !s.blocked && (!s.parent || s.parent.live) && !!r && r.width >= 1 && r.height >= 1;
638
- if (draw !== s.live) {
639
- s.live = draw;
640
- el.style.opacity = draw ? "0" : s.opacity;
641
- }
642
- if (!draw) continue;
643
- const hx = r.width / 2 * k, hy = r.height / 2 * k;
644
- this.bS.set([(r.left + r.width / 2 - cr.left) * k, (r.top + r.height / 2 - cr.top) * k, hx, hy], m * 4);
645
- this.bSC.set(parseColor(s.solidSrc), m * 4);
646
- this.bSP.set([Math.min(s.rad0 * (hx * 2 / s.ow), hx, hy), s.layer, 0, 0], m * 4);
647
- m++;
648
- }
649
- const u = this.u, o = this.o;
650
- gl.uniform2f(u.uRes, W, H);
651
- gl.uniform4fv(u.uFit, this.fit);
652
- gl.uniform4fv(u.uG, this.bG);
653
- gl.uniform4fv(u.uGP, this.bGP);
654
- gl.uniform4fv(u.uGT, this.bGT);
655
- gl.uniform1i(u.uGN, n);
656
- gl.uniform4fv(u.uS, this.bS);
657
- gl.uniform4fv(u.uSC, this.bSC);
658
- gl.uniform4fv(u.uSP, this.bSP);
659
- gl.uniform1i(u.uSN, m);
660
- gl.uniform1f(u.uBezel, o.bezel);
661
- gl.uniform1f(u.uStrength, o.refraction);
662
- gl.uniform1f(u.uDisp, o.dispersion);
663
- gl.uniform1f(u.uMergeK, Math.max(o.mergeK, 1e-3));
664
- gl.uniform1f(u.uDark, dark ? 1 : 0);
665
- gl.uniform1f(u.uRim, o.rim != null ? o.rim : 0.75);
666
- gl.uniform2f(u.uPtr, this.light.x, this.light.y);
667
- gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
606
+
607
+ this._fc++;
608
+ gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
609
+ this.dirty = false;
610
+ };
611
+
612
+ /* 上传壁纸原图 + CPU 模糊图(cover 铺满,uFit 记录屏幕→纹理映射) */
613
+ LiquidGlass.prototype._upload = function () {
614
+ var gl = this.gl, s = this.src;
615
+ try {
616
+ /* 源尺寸用位图尺寸(纹理 0-1 UV 对应位图;CSS 尺寸由 uFit 换算承担) */
617
+ var sw = s.videoWidth || s.width, sh = s.videoHeight || s.height;
618
+ if (!sw || !sh) return;
619
+ var W = this.size.w, H = this.size.h;
620
+ var sc = Math.max(W / sw, H / sh);
621
+ var dw = sw * sc, dh = sh * sc;
622
+ var dx = (W - dw) / 2, dy = (H - dh) / 2;
623
+
624
+ gl.activeTexture(gl.TEXTURE0);
625
+ gl.bindTexture(gl.TEXTURE_2D, this.tex);
626
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, s);
627
+
628
+ /* 壁纸 1/4 分辨率 + CPU blur —— 玻璃霜化只看这张 */
629
+ var bc = this.blurC, bw = Math.max(2, Math.round(W / 4)), bh = Math.max(2, Math.round(H / 4));
630
+ if (bc.width !== bw) bc.width = bw;
631
+ if (bc.height !== bh) bc.height = bh;
632
+ var x = bc.getContext('2d');
633
+ x.filter = 'blur(' + Math.max(this.o.frost / 4, 0.5).toFixed(2) + 'px)';
634
+ x.clearRect(0, 0, bw, bh);
635
+ x.drawImage(s, 0, 0, sw, sh, dx / 4, dy / 4, dw / 4, dh / 4);
636
+
637
+ gl.activeTexture(gl.TEXTURE1);
638
+ gl.bindTexture(gl.TEXTURE_2D, this.blurTex);
639
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bc);
640
+
641
+ this._fit = [W / dw, H / dh, -dx / dw, -dy / dh];
642
+ this.ready = true;
643
+ document.documentElement.classList.add('lg-ready');
644
+ } catch (e) {
645
+ console.warn('LiquidGlassNext: source upload failed', e);
668
646
  }
647
+ this.dirty = false;
669
648
  };
670
- __publicField(LiquidGlass, "PRESETS", PRESETS);
671
- var engine_default = LiquidGlass;
672
- return __toCommonJS(engine_exports);
649
+
650
+ LiquidGlass.PRESETS = PRESETS;
651
+ window.LiquidGlassEngine = { LiquidGlass: LiquidGlass, default: LiquidGlass };
673
652
  })();