distortion-wave 0.0.0-stage → 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.
@@ -0,0 +1,1409 @@
1
+ /*! distortion-wave · MIT © Lucas Bonomi · https://github.com/LukyVj/distortion-wave */
2
+
3
+ // src/capture.js
4
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
5
+ var SVG_NS = "http://www.w3.org/2000/svg";
6
+ var XLINK_NS = "http://www.w3.org/1999/xlink";
7
+ var IGNORE_TAGS = /* @__PURE__ */ new Set(["script", "style", "link", "meta", "template", "noscript", "source", "track", "base", "title"]);
8
+ var URL_PROPS = /* @__PURE__ */ new Set(["background-image", "mask-image", "-webkit-mask-image", "border-image-source", "list-style-image"]);
9
+ var TRANSPARENT_PX = "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==";
10
+ var skipProp = (p) => p.startsWith("--") || p.startsWith("transition") || p.startsWith("animation") || p === "cursor" || p === "will-change" || p === "view-transition-name" || p === "pointer-events";
11
+ var sandbox = null;
12
+ var defaultsCache = /* @__PURE__ */ new Map();
13
+ function sandboxDoc() {
14
+ if (sandbox?.isConnected) return sandbox.contentDocument;
15
+ sandbox = document.createElement("iframe");
16
+ sandbox.setAttribute("aria-hidden", "true");
17
+ sandbox.setAttribute("data-dw-ignore", "");
18
+ sandbox.tabIndex = -1;
19
+ sandbox.style.cssText = "position:fixed;left:-99999px;top:0;width:16px;height:16px;border:0;opacity:0;pointer-events:none";
20
+ document.documentElement.appendChild(sandbox);
21
+ const d = sandbox.contentDocument;
22
+ d.open();
23
+ d.write("<!doctype html><html><head></head><body></body></html>");
24
+ d.close();
25
+ return d;
26
+ }
27
+ function defaultsFor(el) {
28
+ const svg = el.namespaceURI === SVG_NS;
29
+ const key = (svg ? "svg:" : "") + el.localName + (el.localName === "input" ? ":" + (el.type || "text") : "");
30
+ let d = defaultsCache.get(key);
31
+ if (d) return d;
32
+ const doc = sandboxDoc();
33
+ const probe = svg ? doc.createElementNS(SVG_NS, el.localName) : doc.createElement(el.localName);
34
+ if (el.localName === "input") try {
35
+ probe.type = el.type;
36
+ } catch {
37
+ }
38
+ doc.body.appendChild(probe);
39
+ const cs = sandbox.contentWindow.getComputedStyle(probe);
40
+ d = /* @__PURE__ */ new Map();
41
+ for (let i = 0; i < cs.length; i++) d.set(cs[i], cs.getPropertyValue(cs[i]));
42
+ probe.remove();
43
+ defaultsCache.set(key, d);
44
+ return d;
45
+ }
46
+ function styleText(cs, def, parentCS, isRoot, onUrl) {
47
+ let out = "";
48
+ for (let i = 0; i < cs.length; i++) {
49
+ const p = cs[i];
50
+ if (skipProp(p)) continue;
51
+ const v = cs.getPropertyValue(p);
52
+ if (!v) continue;
53
+ if (v === def.get(p) && (isRoot || !parentCS || parentCS.getPropertyValue(p) === v)) continue;
54
+ if (onUrl && URL_PROPS.has(p) && v.includes("url(")) onUrl(p, v);
55
+ out += `${p}:${v};`;
56
+ }
57
+ return out;
58
+ }
59
+ var dataUrlCache = /* @__PURE__ */ new Map();
60
+ function fetchDataURL(url) {
61
+ if (url.startsWith("data:")) return Promise.resolve(url);
62
+ let p = dataUrlCache.get(url);
63
+ if (!p) {
64
+ p = fetch(url, { mode: "cors", credentials: "same-origin" }).then((r) => {
65
+ if (!r.ok) throw new Error(`${r.status} ${url}`);
66
+ return r.blob();
67
+ }).then((b) => new Promise((res, rej) => {
68
+ const fr = new FileReader();
69
+ fr.onload = () => res(fr.result);
70
+ fr.onerror = rej;
71
+ fr.readAsDataURL(b);
72
+ }));
73
+ p.catch(() => dataUrlCache.delete(url));
74
+ dataUrlCache.set(url, p);
75
+ }
76
+ return p;
77
+ }
78
+ async function inlineUrls(value) {
79
+ const urls = [...value.matchAll(/url\((['"]?)(.*?)\1\)/g)].map((m) => m[2]).filter((u) => !u.startsWith("data:"));
80
+ let out = value;
81
+ for (const u of urls) {
82
+ try {
83
+ out = out.split(u).join(await fetchDataURL(new URL(u, document.baseURI).href));
84
+ } catch {
85
+ }
86
+ }
87
+ return out;
88
+ }
89
+ function drawableToDataURL(el) {
90
+ if (el.localName === "canvas") return el.toDataURL();
91
+ const c = document.createElement("canvas");
92
+ c.width = el.videoWidth || el.clientWidth;
93
+ c.height = el.videoHeight || el.clientHeight;
94
+ c.getContext("2d").drawImage(el, 0, 0, c.width, c.height);
95
+ return c.toDataURL();
96
+ }
97
+ async function inlineImage(img, url) {
98
+ try {
99
+ return await fetchDataURL(url);
100
+ } catch {
101
+ }
102
+ try {
103
+ const c = document.createElement("canvas");
104
+ c.width = img.naturalWidth;
105
+ c.height = img.naturalHeight;
106
+ c.getContext("2d").drawImage(img, 0, 0);
107
+ return c.toDataURL();
108
+ } catch {
109
+ return TRANSPARENT_PX;
110
+ }
111
+ }
112
+ var fontKey = "";
113
+ var fontCss = null;
114
+ function embeddedFonts() {
115
+ const faces = [...document.fonts].filter((f) => f.status === "loaded");
116
+ const key = faces.map((f) => `${f.family}|${f.style}|${f.weight}|${f.unicodeRange}`).join(";");
117
+ if (fontCss && key === fontKey) return fontCss;
118
+ fontKey = key;
119
+ fontCss = buildFontCss(faces);
120
+ return fontCss;
121
+ }
122
+ async function buildFontCss(faces) {
123
+ if (!faces.length) return "";
124
+ const norm = (s) => String(s || "").replace(/["']/g, "").replace(/\s+/g, "").toLowerCase();
125
+ const weightOk = (a, b) => {
126
+ const n = (x) => {
127
+ x = String(x || "400").trim().toLowerCase();
128
+ return { normal: "400", bold: "700" }[x] || x;
129
+ };
130
+ a = n(a);
131
+ b = n(b);
132
+ return a === b || a.includes(" ") || b.includes(" ");
133
+ };
134
+ const wanted = (st) => faces.some((f) => norm(f.family) === norm(st.getPropertyValue("font-family")) && norm(f.style) === norm(st.getPropertyValue("font-style") || "normal") && weightOk(f.weight, st.getPropertyValue("font-weight") || "400") && norm(f.unicodeRange) === norm(st.getPropertyValue("unicode-range") || "U+0-10FFFF"));
135
+ const rules = [];
136
+ const walk = (list, base) => {
137
+ for (const r of list) {
138
+ if (r instanceof CSSFontFaceRule) rules.push([r, base]);
139
+ else if (r instanceof CSSImportRule) {
140
+ try {
141
+ if (r.styleSheet) walk(r.styleSheet.cssRules, r.styleSheet.href || base);
142
+ } catch {
143
+ }
144
+ } else if (r.cssRules) walk(r.cssRules, base);
145
+ }
146
+ };
147
+ for (const sheet of [...document.styleSheets, ...document.adoptedStyleSheets || []]) {
148
+ const base = sheet.href || document.baseURI;
149
+ try {
150
+ walk(sheet.cssRules, base);
151
+ } catch {
152
+ if (!sheet.href) continue;
153
+ try {
154
+ const txt = await (await fetch(sheet.href)).text();
155
+ const s = new CSSStyleSheet();
156
+ s.replaceSync(txt.replace(/@import[^;]+;/g, ""));
157
+ walk(s.cssRules, sheet.href);
158
+ } catch {
159
+ }
160
+ }
161
+ }
162
+ const out = [];
163
+ for (const [r, base] of rules) {
164
+ if (!wanted(r.style)) continue;
165
+ const m = r.style.getPropertyValue("src").match(/url\((['"]?)(.*?)\1\)/);
166
+ if (!m) continue;
167
+ try {
168
+ const data = await fetchDataURL(new URL(m[2], base).href);
169
+ out.push(r.cssText.replace(/src\s*:[^;}]+;?/, `src:url("${data}");`));
170
+ } catch {
171
+ }
172
+ }
173
+ return out.join("\n");
174
+ }
175
+ function* composedChildren(node) {
176
+ const root = node.shadowRoot || node;
177
+ for (const ch of root.childNodes) {
178
+ if (ch.nodeType === 1 && ch.localName === "slot") {
179
+ const assigned = ch.assignedNodes({ flatten: true });
180
+ if (assigned.length) {
181
+ yield* assigned;
182
+ continue;
183
+ }
184
+ }
185
+ yield ch;
186
+ }
187
+ }
188
+ var spanProbe = null;
189
+ function cloneTree(src, parentCS, isRoot, ctx) {
190
+ if (src.nodeType === 3) return document.createTextNode(src.data);
191
+ if (src.nodeType !== 1) return null;
192
+ const tag = src.localName;
193
+ if (IGNORE_TAGS.has(tag) || src.hasAttribute("data-dw-ignore")) return null;
194
+ const cs = getComputedStyle(src);
195
+ if (cs.display === "none") return null;
196
+ let clone;
197
+ if (tag === "canvas" || tag === "video") {
198
+ clone = document.createElement("img");
199
+ try {
200
+ clone.setAttribute("src", drawableToDataURL(src));
201
+ } catch {
202
+ clone = document.createElement("div");
203
+ }
204
+ } else if (tag === "iframe" || tag === "embed" || tag === "object") {
205
+ clone = document.createElement("div");
206
+ } else {
207
+ clone = src.cloneNode(false);
208
+ }
209
+ for (const a of [...clone.attributes]) {
210
+ const n = a.name.toLowerCase();
211
+ if (n.startsWith("on") || n === "srcset" || n === "sizes" || n === "loading" || n === "style" || n === "class") clone.removeAttribute(a.name);
212
+ }
213
+ if (tag === "img") {
214
+ const url = src.currentSrc || src.src;
215
+ clone.setAttribute("src", TRANSPARENT_PX);
216
+ if (url) ctx.tasks.push(inlineImage(src, url).then((d) => clone.setAttribute("src", d)));
217
+ }
218
+ if (src.namespaceURI === SVG_NS && tag === "image") {
219
+ const href = src.getAttribute("href") || src.getAttributeNS(XLINK_NS, "href");
220
+ if (href && !href.startsWith("data:")) {
221
+ clone.removeAttribute("href");
222
+ clone.removeAttributeNS(XLINK_NS, "href");
223
+ ctx.tasks.push(fetchDataURL(new URL(href, document.baseURI).href).then((d) => clone.setAttribute("href", d)).catch(() => {
224
+ }));
225
+ }
226
+ }
227
+ const urlProps = [];
228
+ let st = styleText(cs, defaultsFor(src), parentCS, isRoot, (p, v) => urlProps.push([p, v]));
229
+ if (isRoot) st += ctx.rootOverrides(cs);
230
+ st += "transition:none;animation:none;";
231
+ clone.setAttribute("style", st);
232
+ for (const [p, v] of urlProps) ctx.tasks.push(inlineUrls(v).then((nv) => {
233
+ if (nv !== v) clone.setAttribute("style", clone.getAttribute("style").replace(`${p}:${v};`, `${p}:${nv};`));
234
+ }));
235
+ if (src.namespaceURI === HTML_NS || !src.namespaceURI) {
236
+ for (const ps of ["::before", "::after"]) {
237
+ const pcs = getComputedStyle(src, ps);
238
+ const content = pcs.getPropertyValue("content");
239
+ if (!content || content === "none" || content === "normal") continue;
240
+ const cls = "dw" + (ctx.n++).toString(36);
241
+ clone.classList.add(cls);
242
+ spanProbe ??= document.createElement("span");
243
+ ctx.css += `.${cls}${ps}{${styleText(pcs, defaultsFor(spanProbe), cs, false, null)}}`;
244
+ }
245
+ }
246
+ if (tag === "input") {
247
+ if (src.type === "checkbox" || src.type === "radio") {
248
+ if (src.checked) clone.setAttribute("checked", "");
249
+ else clone.removeAttribute("checked");
250
+ } else if (src.type !== "file" && src.type !== "password") clone.setAttribute("value", src.value);
251
+ }
252
+ if (tag === "textarea") {
253
+ clone.textContent = src.value;
254
+ return clone;
255
+ }
256
+ if (tag === "canvas" || tag === "video" || tag === "iframe" || tag === "embed" || tag === "object") return clone;
257
+ for (const ch of composedChildren(src)) {
258
+ const c = cloneTree(ch, cs, false, ctx);
259
+ if (c) clone.appendChild(c);
260
+ }
261
+ if (tag === "select") [...clone.querySelectorAll("option")].forEach((o, i) => {
262
+ if (i === src.selectedIndex) o.setAttribute("selected", "");
263
+ else o.removeAttribute("selected");
264
+ });
265
+ return clone;
266
+ }
267
+ var escapeXml = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
268
+ async function snapshotElement(el, { dpr = 1, clip = null, rootOpacity = null } = {}) {
269
+ const t0 = performance.now();
270
+ await document.fonts.ready;
271
+ const r = el.getBoundingClientRect();
272
+ const w = el.offsetWidth ?? r.width, h = el.offsetHeight ?? r.height;
273
+ const c = clip ?? { x: 0, y: 0, w, h };
274
+ const ctx = {
275
+ tasks: [],
276
+ css: "",
277
+ n: 0,
278
+ rootOverrides: (cs) => `margin:0!important;position:relative!important;inset:auto!important;float:none!important;transform:none!important;translate:none!important;rotate:none!important;scale:none!important;box-sizing:border-box!important;width:${w}px!important;height:${h}px!important;` + (cs.display.startsWith("inline") && cs.display !== "inline-block" && cs.display !== "inline-flex" && cs.display !== "inline-grid" ? "display:inline-block!important;" : "") + (rootOpacity != null ? `opacity:${rootOpacity}!important;` : "")
279
+ };
280
+ const clone = cloneTree(el, null, true, ctx);
281
+ if (!clone) throw new Error("distortion-wave: element is not rendered (display:none?)");
282
+ await Promise.all(ctx.tasks);
283
+ const fonts = await embeddedFonts();
284
+ const xml = new XMLSerializer().serializeToString(clone);
285
+ const cw = Math.max(1, Math.round(c.w)), ch = Math.max(1, Math.round(c.h));
286
+ const svg = `<svg xmlns="${SVG_NS}" width="${cw}" height="${ch}"><foreignObject x="0" y="0" width="${cw}" height="${ch}"><div xmlns="${HTML_NS}" style="position:relative;width:${cw}px;height:${ch}px;overflow:hidden;margin:0;padding:0"><style>${escapeXml(fonts + ctx.css)}</style><div style="position:absolute;left:${-c.x}px;top:${-c.y}px;width:${w}px;height:${h}px">${xml}</div></div></foreignObject></svg>`;
287
+ const img = new Image();
288
+ img.decoding = "async";
289
+ img.src = "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg);
290
+ await img.decode();
291
+ const canvas = document.createElement("canvas");
292
+ canvas.width = Math.max(1, Math.round(cw * dpr));
293
+ canvas.height = Math.max(1, Math.round(ch * dpr));
294
+ const g = canvas.getContext("2d", { willReadFrequently: false });
295
+ g.drawImage(img, 0, 0, canvas.width, canvas.height);
296
+ g.getImageData(0, 0, 1, 1);
297
+ return { canvas, w: cw, h: ch, dpr, ms: performance.now() - t0, bytes: svg.length };
298
+ }
299
+
300
+ // src/core.js
301
+ var TAU = Math.PI * 2;
302
+ var clamp = (x, a = 0, b = 1) => Math.min(Math.max(x, a), b);
303
+ var lerp = (a, b, t) => a + (b - a) * t;
304
+ var fract = (x) => x - Math.floor(x);
305
+ var smooth = (e0, e1, x) => {
306
+ const t = clamp((x - e0) / (e1 - e0 || 1e-6));
307
+ return t * t * (3 - 2 * t);
308
+ };
309
+ function cubicBezier(x1, y1, x2, y2) {
310
+ const b = (t, p1, p2) => {
311
+ const u = 1 - t;
312
+ return 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t;
313
+ };
314
+ return (x) => {
315
+ if (x <= 0) return 0;
316
+ if (x >= 1) return 1;
317
+ let lo = 0, hi = 1;
318
+ for (let i = 0; i < 22; i++) {
319
+ const m = (lo + hi) / 2;
320
+ if (b(m, x1, x2) < x) lo = m;
321
+ else hi = m;
322
+ }
323
+ return b((lo + hi) / 2, y1, y2);
324
+ };
325
+ }
326
+ var easeLaunch = cubicBezier(0.2, 1, 0.3, 1);
327
+ var easeClear = cubicBezier(0.45, 0, 0.55, 1);
328
+ function hexToOklab(hex) {
329
+ const n = parseInt(hex.replace("#", ""), 16);
330
+ const [r, g, b] = [n >> 16 & 255, n >> 8 & 255, n & 255].map((v) => {
331
+ v /= 255;
332
+ return v <= 0.04045 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
333
+ });
334
+ const l = Math.cbrt(0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b);
335
+ const m = Math.cbrt(0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b);
336
+ const s = Math.cbrt(0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b);
337
+ return [0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s, 1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s, 0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s];
338
+ }
339
+ var PALETTES = {
340
+ aurora: ["#FF4FD8", "#A35BFF", "#5B7CFF", "#38D9FF", "#FFD36B", "#FF7A5C"],
341
+ ocean: ["#3A7BFF", "#2EE6D6", "#7C5CFF", "#1FB6FF", "#B8F2FF"],
342
+ mint: ["#2BD67B", "#B6FF5C", "#1FC8A9", "#E8FFB0", "#3CE0C0"],
343
+ ember: ["#FF5A3C", "#FFB23C", "#FF3C8E", "#FFE07A", "#FF7F50"]
344
+ };
345
+ var BASE = {
346
+ type: "vertical",
347
+ speed: 1,
348
+ anticipation: 0.45,
349
+ travel: 2.2,
350
+ linger: -0.3,
351
+ rimFade: 2.8,
352
+ hairDelay: 0.45,
353
+ hairFade: 2.8,
354
+ pull: 22,
355
+ inhale: 50,
356
+ gatherGlow: 0.35,
357
+ gatherDots: 1,
358
+ amplitude: 32,
359
+ lensWidth: 140,
360
+ aberration: 0.75,
361
+ swell: 20,
362
+ blur: 18,
363
+ blurNear: 6,
364
+ blurAhead: 90,
365
+ blurBehind: 230,
366
+ lead: 120,
367
+ tail: 280,
368
+ band: 0.9,
369
+ rimWidth: 14,
370
+ rimSoftness: 320,
371
+ rimStrength: 0.85,
372
+ uneven: 1,
373
+ hairWidth: 1.5,
374
+ lean: 0.9,
375
+ margin: 60,
376
+ seam: 48,
377
+ particles: 64,
378
+ particleSize: 1.8,
379
+ particleLife: 2.1,
380
+ reach: 140,
381
+ spread: 1,
382
+ palette: "aurora"
383
+ };
384
+ var PRESETS = {
385
+ vertical: BASE,
386
+ horizontal: { ...BASE, type: "horizontal" },
387
+ center: {
388
+ ...BASE,
389
+ type: "center",
390
+ anticipation: 0.25,
391
+ travel: 3.2,
392
+ linger: -0.2,
393
+ rimFade: 2.4,
394
+ hairDelay: 0.9,
395
+ pull: 14,
396
+ inhale: 32,
397
+ gatherGlow: 0.2,
398
+ gatherDots: 0,
399
+ amplitude: 20,
400
+ lensWidth: 90,
401
+ aberration: 0.35,
402
+ swell: 13,
403
+ blur: 11,
404
+ blurAhead: 54,
405
+ blurBehind: 140,
406
+ lead: 80,
407
+ tail: 180,
408
+ band: 0.75,
409
+ rimStrength: 0.7,
410
+ rimSoftness: 200,
411
+ particles: 90,
412
+ particleLife: 3,
413
+ reach: 90,
414
+ spread: 0.65
415
+ }
416
+ };
417
+ var PX_KEYS = [
418
+ "pull",
419
+ "inhale",
420
+ "amplitude",
421
+ "lensWidth",
422
+ "swell",
423
+ "blur",
424
+ "blurNear",
425
+ "blurAhead",
426
+ "blurBehind",
427
+ "lead",
428
+ "tail",
429
+ "rimWidth",
430
+ "rimSoftness",
431
+ "reach",
432
+ "margin",
433
+ "seam"
434
+ ];
435
+ var T_KEYS = ["anticipation", "travel", "linger", "rimFade", "hairDelay", "hairFade", "particleLife"];
436
+ function scaleConfig(c, w, h) {
437
+ const k = clamp(Math.sqrt(w * h) / 516, 0.12, 1.8);
438
+ const ts = clamp(0.45 + 0.55 * Math.sqrt(k), 0.6, 1.15) / (c.speed || 1);
439
+ const o = { ...c, k };
440
+ for (const key of PX_KEYS) o[key] = c[key] * k;
441
+ for (const key of T_KEYS) o[key] = c[key] * ts;
442
+ o.blurNear = Math.max(o.blurNear, 1.2);
443
+ o.blur = Math.max(o.blur, o.blurNear);
444
+ o.rimWidth = Math.max(o.rimWidth, 2.5);
445
+ o.seam = Math.max(o.seam, 4);
446
+ o.anticipation = Math.max(o.anticipation, 0.2 / (c.speed || 1));
447
+ o.particles = Math.round(c.particles * clamp(k, 0.3, 1.5));
448
+ o.particleSize = c.particleSize * clamp(0.6 + 0.4 * k, 0.7, 1.3);
449
+ return o;
450
+ }
451
+ var totalDuration = (c) => c.anticipation + Math.max(c.travel * 0.6, c.linger + c.rimFade, c.linger + c.hairDelay + c.hairFade, c.particleLife);
452
+ function farthestCorner(ox, oy, tilt, squash, w, h) {
453
+ const c = Math.cos(tilt), s = Math.sin(tilt);
454
+ let best = 0;
455
+ for (const [x, y] of [[0, 0], [w, 0], [0, h], [w, h]]) {
456
+ const dx = x - ox, dy = y - oy;
457
+ best = Math.max(best, Math.hypot((c * dx + s * dy) * squash, -s * dx + c * dy));
458
+ }
459
+ return best;
460
+ }
461
+ function frameState(t, c, g) {
462
+ const ring = c.type === "center", horiz = c.type === "horizontal";
463
+ const T = totalDuration(c), time = clamp(t, 0, T);
464
+ const fly = time - c.anticipation, pre = smooth(0, c.anticipation, time);
465
+ const prog = clamp(fly / c.travel), e = easeLaunch(prog);
466
+ const s = g.src, cx = s.x + s.w / 2, cy = s.y + s.h / 2;
467
+ const r0 = Math.max(Math.min(s.w, s.h) / 2, 1);
468
+ const half = (horiz ? s.h : s.w) / 2, perp = (horiz ? s.w : s.h) / 2;
469
+ const base = horiz ? Math.PI / 2 : 0, ax = Math.cos(base), ay = Math.sin(base);
470
+ const dir = (g.w / 2 - cx) * Math.sin(base) + (g.h / 2 - cy) * -Math.cos(base) >= 0 ? 1 : -1;
471
+ let lean = 0;
472
+ if (!ring && g.touch) lean = clamp(((g.touch.x - cx) * ax + (g.touch.y - cy) * ay) / Math.max(half, 1), -1, 1) * c.lean;
473
+ const settle = smooth(0, 0.45, e);
474
+ const ox = cx + ax * lean * half * settle, oy = cy + ay * lean * half * settle;
475
+ const tilt = base + dir * lean * 0.45 * settle;
476
+ const squashMax = ring ? 1 : 1.4;
477
+ const reach = farthestCorner(ox, oy, tilt, squashMax, g.w, g.h) + c.swell * squashMax + c.margin;
478
+ const radius = fly < 0 ? r0 + c.inhale * (1 - pre) : r0 + (reach + c.margin * 1.6 - r0) * e;
479
+ const fade = 1 - smooth(0.2, 0.55, prog);
480
+ const lift = c.gatherGlow * 0.5;
481
+ const energy = smooth(0, 0.05, prog) * fade;
482
+ const covered = fly > 0 && radius - c.swell > reach;
483
+ return {
484
+ time,
485
+ fly,
486
+ pre,
487
+ prog,
488
+ moved: e,
489
+ ring,
490
+ dir,
491
+ r0,
492
+ half,
493
+ perp,
494
+ cx,
495
+ cy,
496
+ ox,
497
+ oy,
498
+ tilt,
499
+ squash: lerp(1, squashMax, smooth(0, 0.5, e)),
500
+ spine: ring ? 0 : Math.max(0, half - r0) * (1 - settle),
501
+ lx: Math.cos(tilt),
502
+ ly: Math.sin(tilt),
503
+ fx: dir * Math.sin(tilt),
504
+ fy: -dir * Math.cos(tilt),
505
+ radius,
506
+ swell: c.swell * smooth(0, 0.3, e),
507
+ energy,
508
+ pull: c.pull * pre * (1 - smooth(0, 0.22, fly)),
509
+ glow: fly < 0 ? lift * pre : lerp(lift, 1, smooth(0, 0.07, prog)) * fade,
510
+ clear: lerp(r0 - c.rimSoftness, reach + c.rimSoftness, easeClear((fly - c.linger) / c.rimFade)),
511
+ hairClear: lerp(r0 - c.rimSoftness, reach + c.rimSoftness, easeClear((fly - c.linger - c.hairDelay) / c.hairFade)),
512
+ rim: c.rimStrength * smooth(0, 0.1, prog) * (1 - smooth(0.9, 1, time / T)),
513
+ hold: ring && g.hold ? r0 : 0,
514
+ launched: fly > 0,
515
+ covered,
516
+ settled: covered && prog >= 0.55
517
+ // no more lens or blur → the live DOM can take over
518
+ };
519
+ }
520
+ function makeParticles(n, c) {
521
+ return Array.from({ length: n }, (_, i) => {
522
+ const h = (k) => fract((i + 1) * k);
523
+ return {
524
+ col: h(0.6180339887) * 2 - 1,
525
+ gy: h(0.7071067812),
526
+ head: h(0.1618033989) * TAU,
527
+ pace: 0.4 + 0.65 * Math.sqrt(h(0.7548776662)),
528
+ drift: 12 + 45 * h(0.569840291),
529
+ sway: 3 + 10 * h(0.4142135624),
530
+ swayHz: 2 + 3 * h(0.7320508076),
531
+ tw: 7 + 10 * h(0.2360679775),
532
+ ph: h(0.3819660113) * TAU,
533
+ hop: h(0.5497004779),
534
+ life: c.particleLife * (0.45 + 0.55 * h(0.6457513111)),
535
+ size: c.particleSize * (0.55 + 0.45 * h(0.8774) ** 2),
536
+ bright: 0.35 + 0.65 * h(0.6055512755)
537
+ };
538
+ });
539
+ }
540
+ function particleAt(P, s, c) {
541
+ const tw = 0.75 + 0.25 * Math.sin(s.time * P.tw + P.ph), k = c.k;
542
+ if (!s.launched) {
543
+ if (!c.gatherDots) return null;
544
+ const cyc = s.time / 0.32 + P.hop, f = fract(cyc);
545
+ const a2 = smooth(0, 0.3, f) * (1 - smooth(0.6, 1, f)) * P.bright * tw * smooth(0, c.anticipation * 0.35, s.time) * (1 - smooth(c.anticipation * 0.75, c.anticipation, s.time));
546
+ const fall = c.reach * (1 - f * f);
547
+ if (s.ring) {
548
+ const ang = P.head + Math.floor(cyc) * 2.399963;
549
+ const rr = s.r0 + 12 * k + fall;
550
+ return { x: s.cx + Math.cos(ang) * rr, y: s.cy + Math.sin(ang) * rr, a: a2 };
551
+ }
552
+ const lat2 = P.col * (s.half + 16 * k) * (1 - 0.35 * (1 - f)), fwd2 = s.perp + 10 * k + fall * (0.45 + 0.55 * P.gy);
553
+ return { x: s.cx + s.lx * lat2 + s.fx * fwd2, y: s.cy + s.ly * lat2 + s.fy * fwd2, a: a2 };
554
+ }
555
+ const fly = s.fly;
556
+ const a = (1 - smooth(P.life * 0.55, P.life, fly)) * P.bright * tw * smooth(0, 0.08, fly);
557
+ const sway = P.sway * Math.max(k, 0.3) * Math.sin(P.swayHz * fly + P.ph) * Math.min(fly / 0.25, 1);
558
+ const travel = (s.radius - s.r0) * P.pace * c.spread;
559
+ if (s.ring) {
560
+ const rr = s.r0 + travel + P.drift * k * fly, co = Math.cos(P.head), si = Math.sin(P.head);
561
+ return { x: s.ox + co * rr - si * sway, y: s.oy + si * rr + co * sway, a };
562
+ }
563
+ const lat = P.col * s.half * (1 + 0.25 * s.moved * P.pace) + sway;
564
+ const fwd = (s.r0 + travel) * (1 - 0.3 * P.col * P.col) + P.drift * k * fly;
565
+ return { x: s.ox + s.lx * lat + s.fx * fwd, y: s.oy + s.ly * lat + s.fy * fwd, a };
566
+ }
567
+
568
+ // src/renderer.js
569
+ var VS_FULL = `#version 300 es
570
+ out vec2 vUv;
571
+ void main() { vec2 p = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2)); vUv = p; gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0); }`;
572
+ var FS_COPY = `#version 300 es
573
+ precision highp float; in vec2 vUv; uniform sampler2D uTex; uniform float uLod; out vec4 o;
574
+ void main() { o = textureLod(uTex, vUv, uLod); }`;
575
+ var FS_BLUR = `#version 300 es
576
+ precision highp float; in vec2 vUv; uniform sampler2D uTex; uniform vec2 uStep; uniform float uSigma; out vec4 o;
577
+ void main() {
578
+ float R = min(ceil(uSigma * 3.0), 32.0); vec4 acc = vec4(0.0); float ws = 0.0;
579
+ for (int i = -32; i <= 32; i++) { float fi = float(i); if (abs(fi) > R) continue;
580
+ float w = exp(-0.5 * fi * fi / (uSigma * uSigma)); acc += textureLod(uTex, vUv + uStep * fi, 0.0) * w; ws += w; }
581
+ o = acc / ws;
582
+ }`;
583
+ var FS_MAIN = `#version 300 es
584
+ precision highp float; precision highp int;
585
+ uniform vec2 uSize, uRes;
586
+ uniform sampler2D uOld0, uOld1, uOld2, uNew0, uNew1, uNew2;
587
+ uniform vec4 uOldRect, uNewRect, uShape, uRadii; // rects in canvas CSS px; radii (br, tr, bl, tl)
588
+ uniform int uMode; // 0 before \xB7 1 live \xB7 2 after \xB7 3 crossfade \xB7 4 effects only (live DOM below)
589
+ uniform float uFade;
590
+ uniform bool uLaunched, uRing;
591
+ uniform vec2 uOrigin;
592
+ uniform float uTilt, uSpine, uRadius, uSquash, uTime, uSwell, uEnergy, uPull, uGlow, uRim, uClear, uHairClear, uHold, uK, uSeam;
593
+ uniform float uAmp, uLensW, uAberr, uBlurMax, uBlurNear, uBlurAhead, uBlurBehind;
594
+ uniform float uLead, uTail, uBand, uRimW, uRimSoft, uUneven, uHairW;
595
+ uniform vec3 uPal[8]; uniform int uPalN;
596
+ out vec4 outColor;
597
+ const float TAU = 6.28318530718;
598
+
599
+ mat2 rot(float a) { float c = cos(a), s = sin(a); return mat2(c, s, -s, c); }
600
+ float ign(vec2 p) { return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); }
601
+ float h11(float x) { return fract(sin(x * 127.1) * 43758.5453); }
602
+ float vnoise(float x) { float i = floor(x), f = fract(x); return mix(h11(i), h11(i + 1.0), f * f * (3.0 - 2.0 * f)) * 2.0 - 1.0; }
603
+ float sdRoundBox(vec2 p, vec2 b, vec4 r) { // y-down space: r = (bottom-right, top-right, bottom-left, top-left)
604
+ r.xy = (p.x > 0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y;
605
+ vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
606
+ }
607
+ struct Front { float d; vec2 n; vec2 q; float g; };
608
+ Front front(vec2 p) {
609
+ vec2 q = rot(-uTilt) * (p - uOrigin); float mx = q.x * uSquash;
610
+ vec2 v = vec2(max(abs(mx) - uSpine, 0.0), q.y); float len = max(length(v), 1e-3);
611
+ vec2 grad = vec2(v.x * sign(q.x) * uSquash, v.y) / len; float g = max(length(grad), 1e-3);
612
+ float wob;
613
+ if (uRing) { float a = atan(q.y, q.x); wob = 0.5 * sin(3.0 * a + uTime * 1.3) + 0.3 * sin(5.0 * a - uTime * 0.9 + 2.1) + 0.2 * sin(2.0 * a + uTime * 2.0); }
614
+ else { float s = mx * 0.005 / uK; wob = 0.7 * vnoise(s + uTime * 0.8) + 0.3 * vnoise(s * 2.3 - uTime * 1.1 + 5.0); }
615
+ Front f; f.d = (len - uRadius) / g + uSwell * wob; f.n = rot(uTilt) * (grad / g); f.q = q; f.g = g; return f;
616
+ }
617
+ // The silhouette is decided by p (undisplaced); the sample by sp (displaced, clamped to the rect).
618
+ // \u2192 content bends INSIDE the element, never revealing transparency or bleeding outside.
619
+ vec4 tap(sampler2D s, vec4 rect, vec2 p, vec2 sp) {
620
+ vec2 q = (p - rect.xy) / rect.zw;
621
+ if (q.x < 0.0 || q.y < 0.0 || q.x > 1.0 || q.y > 1.0) return vec4(0.0);
622
+ return textureLod(s, clamp((sp - rect.xy) / rect.zw, 0.0, 1.0), 0.0);
623
+ }
624
+ vec4 fetch3(sampler2D s0, sampler2D s1, sampler2D s2, vec4 rect, vec2 p, vec2 sp, float tn, float tf) {
625
+ vec4 c = tap(s0, rect, p, sp);
626
+ if (tn > 0.0) c = mix(c, tap(s1, rect, p, sp), tn);
627
+ if (tf > 0.0) c = mix(c, tap(s2, rect, p, sp), tf);
628
+ return c;
629
+ }
630
+ vec4 layerOld(vec2 p, vec2 sp, vec2 sl, float tn, float tf) {
631
+ if (dot(sl, sl) < 0.01) return fetch3(uOld0, uOld1, uOld2, uOldRect, p, sp, tn, tf);
632
+ vec4 r = fetch3(uOld0, uOld1, uOld2, uOldRect, p, sp + sl, tn, tf), g = fetch3(uOld0, uOld1, uOld2, uOldRect, p, sp, tn, tf), b = fetch3(uOld0, uOld1, uOld2, uOldRect, p, sp - sl, tn, tf);
633
+ return vec4(r.r, g.g, b.b, max(max(r.a, g.a), b.a));
634
+ }
635
+ vec4 layerNew(vec2 p, vec2 sp, vec2 sl, float tn, float tf) {
636
+ if (dot(sl, sl) < 0.01) return fetch3(uNew0, uNew1, uNew2, uNewRect, p, sp, tn, tf);
637
+ vec4 r = fetch3(uNew0, uNew1, uNew2, uNewRect, p, sp + sl, tn, tf), g = fetch3(uNew0, uNew1, uNew2, uNewRect, p, sp, tn, tf), b = fetch3(uNew0, uNew1, uNew2, uNewRect, p, sp - sl, tn, tf);
638
+ return vec4(r.r, g.g, b.b, max(max(r.a, g.a), b.a));
639
+ }
640
+ vec3 paletteAt(float u) {
641
+ float x = fract(u) * float(uPalN); int i = min(int(floor(x)), uPalN - 1); int j = (i + 1) % uPalN;
642
+ return mix(uPal[i], uPal[j], smoothstep(0.0, 1.0, fract(x)));
643
+ }
644
+ vec3 oklabToSrgb(vec3 c) {
645
+ float l = c.x + 0.3963377774 * c.y + 0.2158037573 * c.z, m = c.x - 0.1055613458 * c.y - 0.0638541728 * c.z, s = c.x - 0.0894841775 * c.y - 1.2914855480 * c.z;
646
+ l = l * l * l; m = m * m * m; s = s * s * s;
647
+ vec3 lin = clamp(vec3(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s), 0.0, 1.0);
648
+ return mix(12.92 * lin, 1.055 * pow(lin, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), lin));
649
+ }
650
+ void main() {
651
+ vec2 p = vec2(gl_FragCoord.x, uRes.y - gl_FragCoord.y) * (uSize.x / uRes.x);
652
+ float dn = (ign(gl_FragCoord.xy) - 0.5) / 255.0;
653
+ if (uMode == 0 || uMode == 2 || uMode == 3) {
654
+ vec4 a = tap(uOld0, uOldRect, p, p), b = tap(uNew0, uNewRect, p, p);
655
+ vec4 c = uMode == 0 ? a : uMode == 2 ? b : mix(a, b, uFade);
656
+ outColor = vec4(c.rgb + dn * c.a, c.a); return;
657
+ }
658
+ Front f = front(p);
659
+ vec2 r = p - uOrigin; float rl = max(length(r), 1.0);
660
+ float held = uHold > 0.0 ? smoothstep(uHold * 0.9, uHold * 1.8, rl) : 1.0;
661
+ vec4 col = vec4(0.0);
662
+ if (uMode == 1) {
663
+ float k = f.d / (uLensW * (f.d > 0.0 ? 0.8 : 1.6));
664
+ vec2 bulge = f.n * (uEnergy * uAmp * exp(-k * k) * held);
665
+ vec2 suck = (r / rl) * (uPull * (rl / (rl + 60.0 * uK)) * exp(-rl / (700.0 * uK)) * held);
666
+ vec2 sp = p - bulge + suck;
667
+ vec2 sl = (bulge * 0.5 - suck * exp(-rl / (240.0 * uK))) * uAberr;
668
+ float kb = f.d / (f.d > 0.0 ? uBlurAhead : uBlurBehind);
669
+ float bpx = uEnergy * uBlurMax * exp(-kb * kb) * held;
670
+ float tn = smoothstep(0.0, uBlurNear, bpx);
671
+ float tf = uBlurMax > uBlurNear ? smoothstep(uBlurNear, uBlurMax, bpx) : 0.0;
672
+ float cover = uLaunched ? smoothstep(-uSeam, uSeam, f.d) : 1.0;
673
+ if (cover >= 0.999) col = layerOld(p, sp, sl, tn, tf);
674
+ else if (cover <= 0.001) col = layerNew(p, sp, sl, tn, tf);
675
+ else col = mix(layerNew(p, sp, sl, tn, tf), layerOld(p, sp, sl, tn, tf), cover);
676
+ // the UNdisplaced alpha fixes the silhouette (rounded corners, transparent areas): no fill-in, no fringe outside
677
+ float aU = mix(tap(uNew0, uNewRect, p, p).a, tap(uOld0, uOldRect, p, p).a, cover);
678
+ col *= min(1.0, aU / max(col.a, 1e-4));
679
+ }
680
+ // element shape (per-corner radii): mask + rim
681
+ vec2 c = p - (uShape.xy + uShape.zw * 0.5);
682
+ float sd = sdRoundBox(c, uShape.zw * 0.5, uRadii);
683
+ float mask = clamp(0.5 - sd * (uRes.x / uSize.x), 0.0, 1.0);
684
+ float inset = max(-sd, 0.0);
685
+ float along = f.q.x * 0.0065 / uK, ang = atan(f.q.y, f.q.x);
686
+ float lobes = uRing ? 0.5 + 0.5 * sin(2.0 * ang + uTime * 1.4) * cos(ang - uTime * 0.6 + 0.8)
687
+ : 0.5 + 0.5 * sin(along * 1.4 + uTime * 1.3) * cos(along * 0.7 - uTime * 0.6 + 0.8);
688
+ float bandA = 0.0;
689
+ if (uGlow > 0.0 && f.d < uLead) {
690
+ if (f.d >= 0.0) { float x = f.d / uLead; bandA = exp(-4.5 * x * x); }
691
+ else { float x = clamp(-f.d / (uTail * mix(0.55, 1.35, lobes)), 0.0, 1.0); bandA = pow(1.0 - x, 2.4); }
692
+ bandA *= uGlow * uBand * held;
693
+ }
694
+ float around = atan(c.y / uShape.w, c.x / uShape.z);
695
+ float rimA = 0.0, hairA = 0.0, shimmer = 0.5;
696
+ if (uRim > 0.0 && inset < uRimW * 4.5) {
697
+ float lumps = 0.5 * sin(around * 3.0 + uTime * 0.7) + 0.3 * sin(around * 5.0 - uTime * 1.2 + 2.0) + 0.2 * sin(around * 9.0 + uTime * 2.3 + 4.0);
698
+ float thick = max(1.0 + uUneven * 0.55 * lumps, 0.15);
699
+ float hk = inset / (uRimW * thick);
700
+ float reached = 1.0 - smoothstep(-uLead * 0.6, uLead * 0.4, f.d);
701
+ float kept = smoothstep(-uRimSoft, uRimSoft, f.d + (uRadius - uClear) / f.g);
702
+ shimmer = 0.5 + 0.5 * sin(uTime * 3.2 - around * 4.0);
703
+ rimA = exp(-hk * hk) * reached * kept * uRim * (0.72 + 0.28 * shimmer);
704
+ float line = 1.0 - smoothstep(uHairW * 0.5, uHairW * 0.5 + 0.8, inset);
705
+ float hairKept = smoothstep(-uRimSoft, uRimSoft, f.d + (uRadius - uHairClear) / f.g);
706
+ hairA = line * reached * hairKept * uRim * 0.85 * mix(0.55, 1.0, smoothstep(0.2, 1.4, thick));
707
+ }
708
+ float a = (1.0 - (1.0 - bandA) * (1.0 - rimA)) * mask;
709
+ hairA *= mask;
710
+ if (a + hairA < 0.0005) { outColor = vec4(col.rgb + dn * col.a, col.a); return; }
711
+ float flow = 0.6 + (uRing ? ang / TAU : along * 0.22) + uTime * 0.12 + f.d * 0.0016 / uK;
712
+ vec3 lab = paletteAt(flow);
713
+ lab.x += 0.10 * exp(-pow((f.d - 28.0 * uK) / (48.0 * uK), 2.0)) - 0.12 * exp(-pow((f.d + 90.0 * uK) / (70.0 * uK), 2.0));
714
+ lab.yz *= 1.2;
715
+ lab = mix(lab, vec3(0.985, 0.0, 0.0), 0.65 * exp(-pow((f.d + 6.0 * uK) / max(24.0 * uK, 3.0), 2.0)));
716
+ vec3 rimLab = paletteAt(around / TAU + uTime * 0.25 + inset * 0.0012 / uK); rimLab.yz *= 1.1;
717
+ lab = mix(lab, rimLab, rimA / (rimA + bandA + 1e-4));
718
+ float ea = clamp(a, 0.0, 1.0);
719
+ vec4 o = vec4(oklabToSrgb(lab) * ea, ea) + col * (1.0 - ea);
720
+ float ha = clamp(hairA, 0.0, 1.0);
721
+ o = vec4(mix(oklabToSrgb(rimLab), vec3(1.0), 0.6 + 0.35 * shimmer) * ha, ha) + o * (1.0 - ha);
722
+ outColor = vec4(o.rgb + dn * 2.0 * o.a, o.a);
723
+ }`;
724
+ var VS_DOTS = `#version 300 es
725
+ layout(location = 0) in vec4 aDot; uniform vec2 uSize; uniform float uDpr; out float vA; out float vR; out float vPt;
726
+ void main() { gl_Position = vec4(aDot.x / uSize.x * 2.0 - 1.0, 1.0 - aDot.y / uSize.y * 2.0, 0.0, 1.0);
727
+ vPt = ceil((aDot.z + 1.0) * uDpr) + 2.0; gl_PointSize = vPt; vR = aDot.z * 0.5 * uDpr; vA = aDot.w; }`;
728
+ var FS_DOTS = `#version 300 es
729
+ precision highp float; in float vA; in float vR; in float vPt; out vec4 o;
730
+ void main() { float r = length(gl_PointCoord - 0.5) * vPt; float cov = clamp(vR - r + 0.5, 0.0, 1.0); o = vec4(vec3(cov * vA), cov * vA); }`;
731
+ function program(gl, vs, fs) {
732
+ const sh = (type, src) => {
733
+ const s = gl.createShader(type);
734
+ gl.shaderSource(s, src);
735
+ gl.compileShader(s);
736
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s) || "compile");
737
+ return s;
738
+ };
739
+ const p = gl.createProgram();
740
+ gl.attachShader(p, sh(gl.VERTEX_SHADER, vs));
741
+ gl.attachShader(p, sh(gl.FRAGMENT_SHADER, fs));
742
+ gl.linkProgram(p);
743
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(p) || "link");
744
+ const locs = /* @__PURE__ */ new Map();
745
+ return { p, u: (n) => {
746
+ if (!locs.has(n)) locs.set(n, gl.getUniformLocation(p, n));
747
+ return locs.get(n);
748
+ } };
749
+ }
750
+ var Renderer = class {
751
+ constructor() {
752
+ this.canvas = document.createElement("canvas");
753
+ this.canvas.setAttribute("data-dw-ignore", "");
754
+ this.canvas.setAttribute("aria-hidden", "true");
755
+ this.canvas.style.cssText = "position:fixed;left:0;top:0;pointer-events:none;z-index:2147483000;contain:strict;";
756
+ this.layers = {};
757
+ this.pal = new Float32Array(24);
758
+ this.palKey = "";
759
+ this.ok = false;
760
+ this.canvas.addEventListener("webglcontextlost", (e) => {
761
+ e.preventDefault();
762
+ this.ok = false;
763
+ });
764
+ const gl = this.canvas.getContext("webgl2", { alpha: true, premultipliedAlpha: true, antialias: false, depth: false, stencil: false, preserveDrawingBuffer: false });
765
+ if (!gl) return;
766
+ this.gl = gl;
767
+ this.main = program(gl, VS_FULL, FS_MAIN);
768
+ this.blur = program(gl, VS_FULL, FS_BLUR);
769
+ this.copy = program(gl, VS_FULL, FS_COPY);
770
+ this.dots = program(gl, VS_DOTS, FS_DOTS);
771
+ this.vao = gl.createVertexArray();
772
+ this.dvao = gl.createVertexArray();
773
+ this.dbuf = gl.createBuffer();
774
+ this.dcap = 0;
775
+ gl.bindVertexArray(this.dvao);
776
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.dbuf);
777
+ gl.enableVertexAttribArray(0);
778
+ gl.vertexAttribPointer(0, 4, gl.FLOAT, false, 16, 0);
779
+ gl.bindVertexArray(null);
780
+ this.maxTex = gl.getParameter(gl.MAX_TEXTURE_SIZE);
781
+ this.ok = true;
782
+ }
783
+ tex(w, h, src, mips = false) {
784
+ const gl = this.gl, t = gl.createTexture();
785
+ gl.bindTexture(gl.TEXTURE_2D, t);
786
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
787
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
788
+ if (src) gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, src);
789
+ else gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
790
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mips ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
791
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
792
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
793
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
794
+ if (mips) gl.generateMipmap(gl.TEXTURE_2D);
795
+ return t;
796
+ }
797
+ target(w, h) {
798
+ const gl = this.gl, tex = this.tex(w, h, null), fb = gl.createFramebuffer();
799
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
800
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
801
+ return { tex, fb };
802
+ }
803
+ pass(prog, fb, w, h) {
804
+ const gl = this.gl;
805
+ gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
806
+ gl.viewport(0, 0, w, h);
807
+ gl.useProgram(prog.p);
808
+ gl.bindVertexArray(this.vao);
809
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
810
+ }
811
+ blurred(sharp, w, h, sigma) {
812
+ const gl = this.gl, scale = Math.max(1, Math.round(sigma / 4));
813
+ const bw = Math.max(1, Math.round(w / scale)), bh = Math.max(1, Math.round(h / scale));
814
+ const A = this.target(bw, bh), B = this.target(bw, bh);
815
+ gl.activeTexture(gl.TEXTURE0);
816
+ gl.bindTexture(gl.TEXTURE_2D, sharp);
817
+ gl.useProgram(this.copy.p);
818
+ gl.uniform1i(this.copy.u("uTex"), 0);
819
+ gl.uniform1f(this.copy.u("uLod"), Math.log2(scale));
820
+ this.pass(this.copy, A.fb, bw, bh);
821
+ gl.useProgram(this.blur.p);
822
+ gl.uniform1i(this.blur.u("uTex"), 0);
823
+ gl.uniform1f(this.blur.u("uSigma"), Math.max(sigma / scale, 0.01));
824
+ gl.bindTexture(gl.TEXTURE_2D, A.tex);
825
+ gl.uniform2f(this.blur.u("uStep"), 1 / bw, 0);
826
+ this.pass(this.blur, B.fb, bw, bh);
827
+ gl.bindTexture(gl.TEXTURE_2D, B.tex);
828
+ gl.uniform2f(this.blur.u("uStep"), 0, 1 / bh);
829
+ this.pass(this.blur, A.fb, bw, bh);
830
+ gl.deleteFramebuffer(B.fb);
831
+ gl.deleteTexture(B.tex);
832
+ gl.deleteFramebuffer(A.fb);
833
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
834
+ return A.tex;
835
+ }
836
+ /** Builds sharp + 2 blur levels (once per capture, never per frame). */
837
+ setLayer(name, src, sigmaNear, sigmaFar) {
838
+ if (!this.ok) return;
839
+ this.dropLayer(name);
840
+ const sharp = this.tex(src.width, src.height, src, true);
841
+ this.layers[name] = { sharp, near: this.blurred(sharp, src.width, src.height, sigmaNear), far: this.blurred(sharp, src.width, src.height, sigmaFar) };
842
+ }
843
+ dropLayer(name) {
844
+ const L = this.layers[name];
845
+ if (!L || !this.gl) return;
846
+ for (const t of [L.sharp, L.near, L.far]) this.gl.deleteTexture(t);
847
+ delete this.layers[name];
848
+ }
849
+ clear() {
850
+ for (const n of Object.keys(this.layers)) this.dropLayer(n);
851
+ }
852
+ render(f) {
853
+ if (!this.ok) return;
854
+ const gl = this.gl, W = this.canvas.width, H = this.canvas.height, m = this.main, u = m.u;
855
+ const old = this.layers.old || this.layers.new, nxt = this.layers.new || this.layers.old;
856
+ if (!old) return;
857
+ gl.bindFramebuffer(gl.FRAMEBUFFER, null);
858
+ gl.viewport(0, 0, W, H);
859
+ gl.disable(gl.BLEND);
860
+ gl.useProgram(m.p);
861
+ [["uOld0", old.sharp], ["uOld1", old.near], ["uOld2", old.far], ["uNew0", nxt.sharp], ["uNew1", nxt.near], ["uNew2", nxt.far]].forEach(([n, t], i) => {
862
+ gl.activeTexture(gl.TEXTURE0 + i);
863
+ gl.bindTexture(gl.TEXTURE_2D, t);
864
+ gl.uniform1i(u(n), i);
865
+ });
866
+ const R = f.rects, v4 = (n, r) => gl.uniform4f(u(n), r.x, r.y, r.w, r.h);
867
+ gl.uniform2f(u("uSize"), f.w, f.h);
868
+ gl.uniform2f(u("uRes"), W, H);
869
+ v4("uOldRect", R.old);
870
+ v4("uNewRect", R.new || R.old);
871
+ v4("uShape", R.shape);
872
+ gl.uniform4f(u("uRadii"), R.radii.br, R.radii.tr, R.radii.bl, R.radii.tl);
873
+ gl.uniform1i(u("uMode"), f.mode);
874
+ gl.uniform1f(u("uFade"), f.fade || 0);
875
+ const s = f.s, c = f.cfg;
876
+ if (s) {
877
+ gl.uniform1i(u("uLaunched"), s.launched ? 1 : 0);
878
+ gl.uniform1i(u("uRing"), s.ring ? 1 : 0);
879
+ gl.uniform2f(u("uOrigin"), s.ox, s.oy);
880
+ const fl = {
881
+ uTilt: s.tilt,
882
+ uSpine: s.spine,
883
+ uRadius: s.radius,
884
+ uSquash: s.squash,
885
+ uTime: s.time,
886
+ uSwell: s.swell,
887
+ uEnergy: s.energy,
888
+ uPull: s.pull,
889
+ uGlow: s.glow,
890
+ uRim: s.rim,
891
+ uClear: s.clear,
892
+ uHairClear: s.hairClear,
893
+ uHold: s.hold,
894
+ uK: c.k,
895
+ uSeam: c.seam,
896
+ uAmp: c.amplitude,
897
+ uLensW: c.lensWidth,
898
+ uAberr: c.aberration,
899
+ uBlurMax: c.blur,
900
+ uBlurNear: c.blurNear,
901
+ uBlurAhead: c.blurAhead,
902
+ uBlurBehind: c.blurBehind,
903
+ uLead: c.lead,
904
+ uTail: c.tail,
905
+ uBand: c.band,
906
+ uRimW: c.rimWidth,
907
+ uRimSoft: c.rimSoftness,
908
+ uUneven: c.uneven,
909
+ uHairW: c.hairWidth
910
+ };
911
+ for (const k in fl) gl.uniform1f(u(k), fl[k]);
912
+ const cols = Array.isArray(c.palette) ? c.palette : PALETTES[c.palette] || PALETTES.aurora;
913
+ const key = cols.join();
914
+ if (this.palKey !== key) {
915
+ this.palKey = key;
916
+ cols.slice(0, 8).forEach((h, i) => this.pal.set(hexToOklab(h), i * 3));
917
+ }
918
+ gl.uniform3fv(u("uPal[0]"), this.pal);
919
+ gl.uniform1i(u("uPalN"), Math.min(cols.length, 8));
920
+ }
921
+ gl.bindVertexArray(this.vao);
922
+ gl.drawArrays(gl.TRIANGLES, 0, 3);
923
+ if (f.dotCount > 0) {
924
+ const d = this.dots;
925
+ gl.enable(gl.BLEND);
926
+ gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
927
+ gl.useProgram(d.p);
928
+ gl.uniform2f(d.u("uSize"), f.w, f.h);
929
+ gl.uniform1f(d.u("uDpr"), f.dpr);
930
+ gl.bindVertexArray(this.dvao);
931
+ gl.bindBuffer(gl.ARRAY_BUFFER, this.dbuf);
932
+ if (this.dcap < f.dots.length) {
933
+ gl.bufferData(gl.ARRAY_BUFFER, f.dots.byteLength, gl.DYNAMIC_DRAW);
934
+ this.dcap = f.dots.length;
935
+ }
936
+ gl.bufferSubData(gl.ARRAY_BUFFER, 0, f.dots, 0, f.dotCount * 4);
937
+ gl.drawArrays(gl.POINTS, 0, f.dotCount);
938
+ gl.disable(gl.BLEND);
939
+ }
940
+ gl.bindVertexArray(null);
941
+ }
942
+ };
943
+ var pool = [];
944
+ var alive = 0;
945
+ var MAX_CONTEXTS = 4;
946
+ function acquire() {
947
+ while (pool.length) {
948
+ const r = pool.pop();
949
+ if (r.ok) return r;
950
+ alive--;
951
+ }
952
+ if (alive >= MAX_CONTEXTS) return null;
953
+ try {
954
+ const r = new Renderer();
955
+ if (!r.ok) return null;
956
+ alive++;
957
+ return r;
958
+ } catch (e) {
959
+ console.warn("[distortion-wave]", e);
960
+ return null;
961
+ }
962
+ }
963
+ function release(r) {
964
+ r.clear();
965
+ r.canvas.remove();
966
+ if (r.ok) pool.push(r);
967
+ else alive--;
968
+ }
969
+ function warmupWave() {
970
+ const r = acquire();
971
+ if (r) release(r);
972
+ return !!r;
973
+ }
974
+
975
+ // src/focus.js
976
+ var FOCUSABLE = 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),summary,[contenteditable="true"],[tabindex]:not([tabindex="-1"])';
977
+ function captureFocus(root) {
978
+ const el = document.activeElement;
979
+ if (!el || el === document.body || !root.contains(el)) return null;
980
+ const path = [];
981
+ for (let n = el; n && n !== root; n = n.parentElement) path.unshift([...n.parentElement.children].indexOf(n));
982
+ const sel = "selectionStart" in el ? (() => {
983
+ try {
984
+ return [el.selectionStart, el.selectionEnd];
985
+ } catch {
986
+ return null;
987
+ }
988
+ })() : null;
989
+ return {
990
+ el,
991
+ id: el.id,
992
+ key: el.getAttribute("data-focus-key") || el.getAttribute("name"),
993
+ tag: el.localName,
994
+ path,
995
+ order: [...root.querySelectorAll(FOCUSABLE)].indexOf(el),
996
+ sel
997
+ };
998
+ }
999
+ function restoreFocus(info, root) {
1000
+ if (!info || info.el.isConnected) return;
1001
+ const now = document.activeElement;
1002
+ if (now && now !== document.body && now !== document.documentElement) return;
1003
+ let c = null;
1004
+ if (info.id) c = root.querySelector("#" + CSS.escape(info.id));
1005
+ if (!c && info.key) c = root.querySelector(`[data-focus-key="${CSS.escape(info.key)}"],[name="${CSS.escape(info.key)}"]`);
1006
+ if (!c) {
1007
+ let n = root;
1008
+ for (const i of info.path) n = n?.children[i];
1009
+ if (n && n !== root && n.localName === info.tag && n.matches(FOCUSABLE)) c = n;
1010
+ }
1011
+ if (!c && info.order >= 0) {
1012
+ const f = root.querySelectorAll(FOCUSABLE);
1013
+ c = f[Math.min(info.order, f.length - 1)] || null;
1014
+ }
1015
+ if (!c && root.matches(FOCUSABLE)) c = root;
1016
+ if (!c) return;
1017
+ c.focus({ preventScroll: true });
1018
+ if (info.sel && typeof c.setSelectionRange === "function") try {
1019
+ c.setSelectionRange(info.sel[0], info.sel[1]);
1020
+ } catch {
1021
+ }
1022
+ }
1023
+
1024
+ // src/index.js
1025
+ function parseRadii(cs, w, h) {
1026
+ const one = (v, side) => {
1027
+ const t = String(v).trim().split(/\s+/)[0];
1028
+ const n = parseFloat(t) || 0;
1029
+ return t.endsWith("%") ? n / 100 * side : n;
1030
+ };
1031
+ let tl = one(cs.borderTopLeftRadius, w), tr = one(cs.borderTopRightRadius, w), br = one(cs.borderBottomRightRadius, w), bl = one(cs.borderBottomLeftRadius, w);
1032
+ const f = Math.min(1, w / (tl + tr || 1e9), w / (bl + br || 1e9), h / (tl + bl || 1e9), h / (tr + br || 1e9));
1033
+ return { tl: tl * f, tr: tr * f, br: br * f, bl: bl * f };
1034
+ }
1035
+ function isFixed(el) {
1036
+ for (let n = el; n && n !== document.documentElement; n = n.parentElement) if (getComputedStyle(n).position === "fixed") return true;
1037
+ return false;
1038
+ }
1039
+ function measure(el) {
1040
+ const rect = el.getBoundingClientRect(), cs = getComputedStyle(el);
1041
+ const w = el.offsetWidth ?? rect.width, h = el.offsetHeight ?? rect.height;
1042
+ const x0 = Math.max(rect.left, 0), y0 = Math.max(rect.top, 0), x1 = Math.min(rect.right, innerWidth), y1 = Math.min(rect.bottom, innerHeight);
1043
+ const fixed = isFixed(el), sx = fixed ? 0 : scrollX, sy = fixed ? 0 : scrollY;
1044
+ return {
1045
+ w,
1046
+ h,
1047
+ fixed,
1048
+ opacity: cs.opacity,
1049
+ radii: parseRadii(cs, w, h),
1050
+ visible: x1 > x0 && y1 > y0,
1051
+ clip: { x: x0 - rect.left, y: y0 - rect.top, w: x1 - x0, h: y1 - y0 },
1052
+ // visible part, element space
1053
+ page: { x: x0 + sx, y: y0 + sy, w: x1 - x0, h: y1 - y0 },
1054
+ // same, page space (scroll-stable)
1055
+ el: { x: rect.left + sx, y: rect.top + sy, w: rect.width, h: rect.height }
1056
+ };
1057
+ }
1058
+ var union = (a, b) => {
1059
+ const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
1060
+ return { x, y, w: Math.max(a.x + a.w, b.x + b.w) - x, h: Math.max(a.y + a.h, b.y + b.h) - y };
1061
+ };
1062
+ var rel = (r, o) => ({ x: r.x - o.x, y: r.y - o.y, w: r.w, h: r.h });
1063
+ var nextFrame = () => new Promise((r) => requestAnimationFrame(() => r()));
1064
+ function deferred() {
1065
+ let res, rej;
1066
+ const p = new Promise((a, b) => {
1067
+ res = a;
1068
+ rej = b;
1069
+ });
1070
+ p.catch(() => {
1071
+ });
1072
+ return { p, res, rej };
1073
+ }
1074
+ function pickDpr(m, maxTex) {
1075
+ const area = Math.max(m.clip.w * m.clip.h, 1);
1076
+ return Math.max(0.5, Math.min(window.devicePixelRatio || 1, 2, Math.sqrt(42e5 / area), maxTex / Math.max(m.clip.w, m.clip.h, 1)));
1077
+ }
1078
+ var prepared = /* @__PURE__ */ new WeakMap();
1079
+ function prepareWave(target) {
1080
+ if (prepared.get(target)?.pending) return prepared.get(target).pending;
1081
+ const m = measure(target);
1082
+ if (!m.visible) return Promise.resolve(null);
1083
+ const entry = { dirty: false, m, dpr: pickDpr(m, 8192), at: performance.now() };
1084
+ entry.mo = new MutationObserver(() => {
1085
+ entry.dirty = true;
1086
+ });
1087
+ entry.mo.observe(target, { subtree: true, childList: true, attributes: true, characterData: true });
1088
+ entry.pending = snapshotElement(target, { dpr: entry.dpr, clip: m.clip }).then((s) => entry.shot = s, () => null);
1089
+ prepared.set(target, entry);
1090
+ return entry.pending;
1091
+ }
1092
+ function takePrepared(target, m) {
1093
+ const e = prepared.get(target);
1094
+ if (!e) return null;
1095
+ prepared.delete(target);
1096
+ e.mo.disconnect();
1097
+ const same = (a, b) => Math.abs(a.x - b.x) < 0.5 && Math.abs(a.y - b.y) < 0.5 && Math.abs(a.w - b.w) < 0.5 && Math.abs(a.h - b.h) < 0.5;
1098
+ if (e.dirty || !e.shot || performance.now() - e.at > 8e3 || !same(e.m.page, m.page)) return null;
1099
+ return e.shot;
1100
+ }
1101
+ var ACTIVE = /* @__PURE__ */ new WeakMap();
1102
+ var DEFAULTS = { variant: "auto", palette: "aurora", speed: 1, particles: true, reducedMotion: null, timeout: 3e3, preserveFocus: true };
1103
+ var WaveTransition = class {
1104
+ constructor(target, update, opts) {
1105
+ this.target = target;
1106
+ this.update = update || (() => {
1107
+ });
1108
+ this.o = { ...DEFAULTS, ...opts };
1109
+ this._d = { ready: deferred(), update: deferred(), covered: deferred(), finished: deferred() };
1110
+ this.ready = this._d.ready.p;
1111
+ this.updateCallbackDone = this._d.update.p;
1112
+ this.covered = this._d.covered.p;
1113
+ this.finished = this._d.finished.p;
1114
+ this.metrics = {};
1115
+ this._updated = false;
1116
+ this._skipped = false;
1117
+ this._done = false;
1118
+ this._run().catch((err) => this._abort(err));
1119
+ }
1120
+ skip() {
1121
+ this._skipped = true;
1122
+ if (this._loopStarted) this._finish();
1123
+ }
1124
+ async _runUpdate() {
1125
+ if (this._updated) return this._updatePromise;
1126
+ this._updated = true;
1127
+ const focus = this.o.preserveFocus === false ? null : captureFocus(this.target);
1128
+ this._updatePromise = (async () => {
1129
+ await this.update();
1130
+ restoreFocus(focus, this.target);
1131
+ })();
1132
+ this._updatePromise.then(() => this._d.update.res(), (e) => this._d.update.rej(e));
1133
+ return this._updatePromise;
1134
+ }
1135
+ async _run() {
1136
+ const prev = ACTIVE.get(this.target);
1137
+ ACTIVE.set(this.target, this);
1138
+ if (prev) {
1139
+ prev.skip();
1140
+ await prev.finished;
1141
+ }
1142
+ const o = this.o, target = this.target;
1143
+ const reduced = o.reducedMotion ?? matchMedia("(prefers-reduced-motion: reduce)").matches;
1144
+ const before = measure(target);
1145
+ const r = before.visible && target !== document.body && target !== document.documentElement ? acquire() : null;
1146
+ this.r = r;
1147
+ if (!r || this._skipped) return this._plain();
1148
+ const dpr = pickDpr(before, r.maxTex);
1149
+ let t0 = performance.now();
1150
+ const oldShot = takePrepared(target, before) || await snapshotElement(target, { dpr: Math.min(dpr, 2), clip: before.clip });
1151
+ this.metrics.captureBefore = Math.round(performance.now() - t0);
1152
+ if (this._skipped) return this._plain();
1153
+ const base = { ...PRESETS[this._variant(before)] || BASE };
1154
+ for (const k of Object.keys(o)) if (k in BASE && k !== "type" && k !== "particles") base[k] = o[k];
1155
+ if (o.particles === false) base.particles = 0;
1156
+ else if (typeof o.particles === "number") base.particles = o.particles;
1157
+ const cfg = scaleConfig(base, before.clip.w, before.clip.h);
1158
+ this.cfg = cfg;
1159
+ this.before = before;
1160
+ this.dpr = oldShot.dpr;
1161
+ this.particles = makeParticles(cfg.particles, cfg);
1162
+ this.dots = new Float32Array(Math.max(this.particles.length, 1) * 4);
1163
+ this.origin = this._originPage(before);
1164
+ this.canvasPage = before.page;
1165
+ this.rects = { old: rel(before.page, before.page), new: null, shape: rel(before.el, before.page), radii: before.radii };
1166
+ t0 = performance.now();
1167
+ r.setLayer("old", oldShot.canvas, cfg.blurNear * this.dpr, cfg.blur * this.dpr);
1168
+ this.metrics.upload = Math.round(performance.now() - t0);
1169
+ this._mount();
1170
+ this._hide();
1171
+ this.mode = reduced ? 0 : 1;
1172
+ this.fadeOnly = reduced;
1173
+ this._d.ready.res();
1174
+ this._startLoop();
1175
+ await this._runUpdate();
1176
+ await nextFrame();
1177
+ await Promise.all([...target.querySelectorAll("img")].map((i) => i.decode?.().catch(() => {
1178
+ })));
1179
+ if (this._done) return;
1180
+ const after = measure(target);
1181
+ t0 = performance.now();
1182
+ const newShot = after.visible ? await snapshotElement(target, { dpr: this.dpr, clip: after.clip, rootOpacity: before.opacity }) : null;
1183
+ this.metrics.captureAfter = Math.round(performance.now() - t0);
1184
+ if (this._done) return;
1185
+ if (!newShot) {
1186
+ this._finish();
1187
+ return;
1188
+ }
1189
+ r.setLayer("new", newShot.canvas, cfg.blurNear * this.dpr, cfg.blur * this.dpr);
1190
+ this.after = after;
1191
+ this.canvasPage = union(before.page, after.page);
1192
+ this.rects = { old: rel(before.page, this.canvasPage), new: rel(after.page, this.canvasPage), shape: rel(before.el, this.canvasPage), radii: before.radii };
1193
+ this.shapes = { from: rel(before.el, this.canvasPage), to: rel(after.el, this.canvasPage), r0: before.radii, r1: after.radii };
1194
+ this._resizeCanvas();
1195
+ this.gateOpen = true;
1196
+ if (this.fadeOnly) {
1197
+ this.mode = 3;
1198
+ this.fadeStart = this.now;
1199
+ }
1200
+ }
1201
+ _variant(m) {
1202
+ if (this.o.variant !== "auto") return this.o.variant;
1203
+ const s = this.o.source;
1204
+ if (!(s instanceof Element) || s === this.target) return "center";
1205
+ const r = s.getBoundingClientRect();
1206
+ return r.width >= r.height * 1.4 ? "vertical" : r.height >= r.width * 1.4 ? "horizontal" : "center";
1207
+ }
1208
+ _originPage(m) {
1209
+ const sx = m.fixed ? 0 : scrollX, sy = m.fixed ? 0 : scrollY, o = this.o;
1210
+ const pt = (v) => v && "clientX" in v ? { x: v.clientX, y: v.clientY } : v && "x" in v ? { x: v.x, y: v.y } : null;
1211
+ let touch = pt(o.focus);
1212
+ if (o.focus instanceof KeyboardEvent || o.focus && o.focus.detail === 0 && o.focus.type === "click") touch = null;
1213
+ let src;
1214
+ if (o.source instanceof Element && o.source !== this.target) {
1215
+ const r = o.source.getBoundingClientRect();
1216
+ src = { x: r.left, y: r.top, w: r.width, h: r.height };
1217
+ } else if (o.source && "width" in o.source) src = { x: o.source.x, y: o.source.y, w: o.source.width, h: o.source.height };
1218
+ else {
1219
+ const p = !(o.source instanceof Element) && pt(o.source) || touch || { x: m.el.x - sx + m.w / 2, y: m.el.y - sy + m.h / 2 };
1220
+ const d = Math.max(8, Math.min(m.w, m.h) * 0.08);
1221
+ src = { x: p.x - d, y: p.y - d, w: 2 * d, h: 2 * d };
1222
+ }
1223
+ return {
1224
+ src: { x: src.x + sx, y: src.y + sy, w: src.w, h: src.h },
1225
+ touch: touch && { x: touch.x + sx, y: touch.y + sy },
1226
+ hold: o.source instanceof Element && o.source !== this.target && this.target.contains(o.source)
1227
+ };
1228
+ }
1229
+ _mount() {
1230
+ const c = this.r.canvas;
1231
+ document.body.appendChild(c);
1232
+ this._resizeCanvas();
1233
+ }
1234
+ _resizeCanvas() {
1235
+ const c = this.r.canvas, p = this.canvasPage;
1236
+ c.width = Math.max(1, Math.round(p.w * this.dpr));
1237
+ c.height = Math.max(1, Math.round(p.h * this.dpr));
1238
+ c.style.width = p.w + "px";
1239
+ c.style.height = p.h + "px";
1240
+ this._place();
1241
+ }
1242
+ _place() {
1243
+ const p = this.canvasPage, f = this.before.fixed;
1244
+ this.r.canvas.style.transform = `translate3d(${p.x - (f ? 0 : scrollX)}px,${p.y - (f ? 0 : scrollY)}px,0)`;
1245
+ }
1246
+ _hide() {
1247
+ const s = this.target.style;
1248
+ this._saved = ["opacity", "transition"].map((k) => [k, s.getPropertyValue(k), s.getPropertyPriority(k)]);
1249
+ s.setProperty("transition", "none", "important");
1250
+ s.setProperty("opacity", "0", "important");
1251
+ }
1252
+ _show() {
1253
+ if (!this._saved) return;
1254
+ for (const [k, v, pr] of this._saved) {
1255
+ if (v) this.target.style.setProperty(k, v, pr);
1256
+ else this.target.style.removeProperty(k);
1257
+ }
1258
+ this._saved = null;
1259
+ }
1260
+ _startLoop() {
1261
+ this._loopStarted = true;
1262
+ this.held = 0;
1263
+ this.start = performance.now();
1264
+ this.now = this.start;
1265
+ this.last = this.start;
1266
+ const tick = (now) => {
1267
+ if (this._done) return;
1268
+ const dt = Math.min(now - this.last, 100);
1269
+ this.last = now;
1270
+ this.now = now;
1271
+ this.metrics.worstFrame = Math.max(this.metrics.worstFrame || 0, dt);
1272
+ this._place();
1273
+ let t = (now - this.start - this.held) / 1e3;
1274
+ const gateAt = this.cfg.anticipation - 1e-3;
1275
+ if (!this.gateOpen && t >= gateAt) {
1276
+ this.held += t * 1e3 - gateAt * 1e3;
1277
+ t = gateAt;
1278
+ if (now - this.start > this.o.timeout) {
1279
+ this._finish();
1280
+ return;
1281
+ }
1282
+ }
1283
+ this.t = t;
1284
+ if (this.mode === 3) {
1285
+ const k = clamp((now - this.fadeStart) / 220);
1286
+ this.r.render({ mode: 3, fade: k, w: this.canvasPage.w, h: this.canvasPage.h, rects: this.rects, dpr: this.dpr, dotCount: 0 });
1287
+ if (k >= 1) {
1288
+ this._finish();
1289
+ return;
1290
+ }
1291
+ } else if (this.mode === 0) {
1292
+ this.r.render({ mode: 0, w: this.canvasPage.w, h: this.canvasPage.h, rects: this.rects, dpr: this.dpr, dotCount: 0 });
1293
+ } else {
1294
+ const g = {
1295
+ w: this.canvasPage.w,
1296
+ h: this.canvasPage.h,
1297
+ src: rel(this.origin.src, this.canvasPage),
1298
+ touch: this.origin.touch && rel({ ...this.origin.touch, w: 0, h: 0 }, this.canvasPage),
1299
+ hold: this.origin.hold
1300
+ };
1301
+ const s = frameState(t, this.cfg, g);
1302
+ if (this.shapes) {
1303
+ const k = s.moved, A = this.shapes.from, B = this.shapes.to, ra = this.shapes.r0, rb = this.shapes.r1;
1304
+ this.rects.shape = { x: lerp(A.x, B.x, k), y: lerp(A.y, B.y, k), w: lerp(A.w, B.w, k), h: lerp(A.h, B.h, k) };
1305
+ this.rects.radii = { tl: lerp(ra.tl, rb.tl, k), tr: lerp(ra.tr, rb.tr, k), br: lerp(ra.br, rb.br, k), bl: lerp(ra.bl, rb.bl, k) };
1306
+ }
1307
+ if (s.launched && !this._launched) {
1308
+ this._launched = true;
1309
+ this.o.onLaunch?.();
1310
+ }
1311
+ if (s.covered && !this._covered) {
1312
+ this._covered = true;
1313
+ this._d.covered.res();
1314
+ this.o.onCovered?.();
1315
+ }
1316
+ if (s.settled && this.mode === 1) {
1317
+ this.mode = 4;
1318
+ this._show();
1319
+ this.o.onSettled?.();
1320
+ }
1321
+ let n = 0;
1322
+ for (const P of this.particles) {
1323
+ const q = particleAt(P, s, this.cfg);
1324
+ if (!q || q.a <= 2e-3) continue;
1325
+ this.dots[n * 4] = q.x;
1326
+ this.dots[n * 4 + 1] = q.y;
1327
+ this.dots[n * 4 + 2] = P.size;
1328
+ this.dots[n * 4 + 3] = Math.min(q.a, 1);
1329
+ n++;
1330
+ }
1331
+ this.r.render({ mode: this.mode, s, cfg: this.cfg, w: this.canvasPage.w, h: this.canvasPage.h, rects: this.rects, dpr: this.dpr, dots: this.dots, dotCount: n });
1332
+ if (t >= totalDuration(this.cfg)) {
1333
+ this._finish();
1334
+ return;
1335
+ }
1336
+ }
1337
+ if (this._skipped) {
1338
+ this._finish();
1339
+ return;
1340
+ }
1341
+ requestAnimationFrame(tick);
1342
+ };
1343
+ requestAnimationFrame(tick);
1344
+ }
1345
+ async _plain() {
1346
+ try {
1347
+ await this._runUpdate();
1348
+ } finally {
1349
+ this._done = true;
1350
+ this._release();
1351
+ this._d.ready.res();
1352
+ this._d.covered.res();
1353
+ this._d.finished.res();
1354
+ }
1355
+ }
1356
+ async _finish() {
1357
+ if (this._done) return;
1358
+ this._done = true;
1359
+ try {
1360
+ await this._runUpdate();
1361
+ } catch {
1362
+ }
1363
+ this._show();
1364
+ this._release();
1365
+ if (!this._covered) this._d.covered.res();
1366
+ this._d.finished.res();
1367
+ this.o.onEnd?.(this.metrics);
1368
+ }
1369
+ _release() {
1370
+ if (this.r) {
1371
+ release(this.r);
1372
+ this.r = null;
1373
+ }
1374
+ if (ACTIVE.get(this.target) === this) ACTIVE.delete(this.target);
1375
+ }
1376
+ _abort(err) {
1377
+ console.warn("[distortion-wave] repli sans animation :", err);
1378
+ this._done = true;
1379
+ this._show();
1380
+ this._release();
1381
+ (this._updated ? Promise.resolve() : this._runUpdate()).catch(() => {
1382
+ }).finally(() => {
1383
+ this._d.ready.res();
1384
+ this._d.covered.res();
1385
+ this._d.finished.res();
1386
+ });
1387
+ }
1388
+ };
1389
+ function distortionWave(target, update, options = {}) {
1390
+ return new WaveTransition(target, update, options);
1391
+ }
1392
+ function waveOnClick(trigger, target, update, options = {}) {
1393
+ const prep = () => prepareWave(target);
1394
+ trigger.addEventListener("pointerenter", prep);
1395
+ trigger.addEventListener("focus", prep);
1396
+ trigger.addEventListener("click", (e) => distortionWave(target, () => update(e), { source: trigger, focus: e, ...options }));
1397
+ }
1398
+ export {
1399
+ PALETTES,
1400
+ PRESETS,
1401
+ distortionWave,
1402
+ frameState,
1403
+ prepareWave,
1404
+ scaleConfig,
1405
+ snapshotElement,
1406
+ totalDuration,
1407
+ warmupWave,
1408
+ waveOnClick
1409
+ };