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.
- package/LICENSE +21 -0
- package/README.md +204 -2
- package/dist/distortion-wave.iife.min.js +159 -0
- package/dist/distortion-wave.js +1409 -0
- package/dist/distortion-wave.min.js +159 -0
- package/package.json +61 -4
- package/src/capture.js +295 -0
- package/src/core.js +169 -0
- package/src/focus.js +34 -0
- package/src/index.js +281 -0
- package/src/renderer.js +295 -0
- package/types/index.d.ts +81 -0
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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
|
+
};
|