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,159 @@
1
+ /*! distortion-wave · MIT © Lucas Bonomi · https://github.com/LukyVj/distortion-wave */
2
+ var ut="http://www.w3.org/1999/xhtml",k="http://www.w3.org/2000/svg",rt="http://www.w3.org/1999/xlink",Tt=new Set(["script","style","link","meta","template","noscript","source","track","base","title"]),Mt=new Set(["background-image","mask-image","-webkit-mask-image","border-image-source","list-style-image"]),ht="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",Ft=e=>e.startsWith("--")||e.startsWith("transition")||e.startsWith("animation")||e==="cursor"||e==="will-change"||e==="view-transition-name"||e==="pointer-events",w=null,nt=new Map;function Pt(){if(w?.isConnected)return w.contentDocument;w=document.createElement("iframe"),w.setAttribute("aria-hidden","true"),w.setAttribute("data-dw-ignore",""),w.tabIndex=-1,w.style.cssText="position:fixed;left:-99999px;top:0;width:16px;height:16px;border:0;opacity:0;pointer-events:none",document.documentElement.appendChild(w);let e=w.contentDocument;return e.open(),e.write("<!doctype html><html><head></head><body></body></html>"),e.close(),e}function ot(e){let t=e.namespaceURI===k,i=(t?"svg:":"")+e.localName+(e.localName==="input"?":"+(e.type||"text"):""),n=nt.get(i);if(n)return n;let r=Pt(),a=t?r.createElementNS(k,e.localName):r.createElement(e.localName);if(e.localName==="input")try{a.type=e.type}catch{}r.body.appendChild(a);let o=w.contentWindow.getComputedStyle(a);n=new Map;for(let l=0;l<o.length;l++)n.set(o[l],o.getPropertyValue(o[l]));return a.remove(),nt.set(i,n),n}function st(e,t,i,n,r){let a="";for(let o=0;o<e.length;o++){let l=e[o];if(Ft(l))continue;let s=e.getPropertyValue(l);s&&(s===t.get(l)&&(n||!i||i.getPropertyValue(l)===s)||(r&&Mt.has(l)&&s.includes("url(")&&r(l,s),a+=`${l}:${s};`))}return a}var $=new Map;function C(e){if(e.startsWith("data:"))return Promise.resolve(e);let t=$.get(e);return t||(t=fetch(e,{mode:"cors",credentials:"same-origin"}).then(i=>{if(!i.ok)throw new Error(`${i.status} ${e}`);return i.blob()}).then(i=>new Promise((n,r)=>{let a=new FileReader;a.onload=()=>n(a.result),a.onerror=r,a.readAsDataURL(i)})),t.catch(()=>$.delete(e)),$.set(e,t)),t}async function Nt(e){let t=[...e.matchAll(/url\((['"]?)(.*?)\1\)/g)].map(n=>n[2]).filter(n=>!n.startsWith("data:")),i=e;for(let n of t)try{i=i.split(n).join(await C(new URL(n,document.baseURI).href))}catch{}return i}function Lt(e){if(e.localName==="canvas")return e.toDataURL();let t=document.createElement("canvas");return t.width=e.videoWidth||e.clientWidth,t.height=e.videoHeight||e.clientHeight,t.getContext("2d").drawImage(e,0,0,t.width,t.height),t.toDataURL()}async function kt(e,t){try{return await C(t)}catch{}try{let i=document.createElement("canvas");return i.width=e.naturalWidth,i.height=e.naturalHeight,i.getContext("2d").drawImage(e,0,0),i.toDataURL()}catch{return ht}}var lt="",L=null;function Ct(){let e=[...document.fonts].filter(i=>i.status==="loaded"),t=e.map(i=>`${i.family}|${i.style}|${i.weight}|${i.unicodeRange}`).join(";");return L&&t===lt||(lt=t,L=Dt(e)),L}async function Dt(e){if(!e.length)return"";let t=l=>String(l||"").replace(/["']/g,"").replace(/\s+/g,"").toLowerCase(),i=(l,s)=>{let c=u=>(u=String(u||"400").trim().toLowerCase(),{normal:"400",bold:"700"}[u]||u);return l=c(l),s=c(s),l===s||l.includes(" ")||s.includes(" ")},n=l=>e.some(s=>t(s.family)===t(l.getPropertyValue("font-family"))&&t(s.style)===t(l.getPropertyValue("font-style")||"normal")&&i(s.weight,l.getPropertyValue("font-weight")||"400")&&t(s.unicodeRange)===t(l.getPropertyValue("unicode-range")||"U+0-10FFFF")),r=[],a=(l,s)=>{for(let c of l)if(c instanceof CSSFontFaceRule)r.push([c,s]);else if(c instanceof CSSImportRule)try{c.styleSheet&&a(c.styleSheet.cssRules,c.styleSheet.href||s)}catch{}else c.cssRules&&a(c.cssRules,s)};for(let l of[...document.styleSheets,...document.adoptedStyleSheets||[]]){let s=l.href||document.baseURI;try{a(l.cssRules,s)}catch{if(!l.href)continue;try{let c=await(await fetch(l.href)).text(),u=new CSSStyleSheet;u.replaceSync(c.replace(/@import[^;]+;/g,"")),a(u.cssRules,l.href)}catch{}}}let o=[];for(let[l,s]of r){if(!n(l.style))continue;let c=l.style.getPropertyValue("src").match(/url\((['"]?)(.*?)\1\)/);if(c)try{let u=await C(new URL(c[2],s).href);o.push(l.cssText.replace(/src\s*:[^;}]+;?/,`src:url("${u}");`))}catch{}}return o.join(`
3
+ `)}function*Ut(e){let t=e.shadowRoot||e;for(let i of t.childNodes){if(i.nodeType===1&&i.localName==="slot"){let n=i.assignedNodes({flatten:!0});if(n.length){yield*n;continue}}yield i}}var ct=null;function dt(e,t,i,n){if(e.nodeType===3)return document.createTextNode(e.data);if(e.nodeType!==1)return null;let r=e.localName;if(Tt.has(r)||e.hasAttribute("data-dw-ignore"))return null;let a=getComputedStyle(e);if(a.display==="none")return null;let o;if(r==="canvas"||r==="video"){o=document.createElement("img");try{o.setAttribute("src",Lt(e))}catch{o=document.createElement("div")}}else r==="iframe"||r==="embed"||r==="object"?o=document.createElement("div"):o=e.cloneNode(!1);for(let c of[...o.attributes]){let u=c.name.toLowerCase();(u.startsWith("on")||u==="srcset"||u==="sizes"||u==="loading"||u==="style"||u==="class")&&o.removeAttribute(c.name)}if(r==="img"){let c=e.currentSrc||e.src;o.setAttribute("src",ht),c&&n.tasks.push(kt(e,c).then(u=>o.setAttribute("src",u)))}if(e.namespaceURI===k&&r==="image"){let c=e.getAttribute("href")||e.getAttributeNS(rt,"href");c&&!c.startsWith("data:")&&(o.removeAttribute("href"),o.removeAttributeNS(rt,"href"),n.tasks.push(C(new URL(c,document.baseURI).href).then(u=>o.setAttribute("href",u)).catch(()=>{})))}let l=[],s=st(a,ot(e),t,i,(c,u)=>l.push([c,u]));i&&(s+=n.rootOverrides(a)),s+="transition:none;animation:none;",o.setAttribute("style",s);for(let[c,u]of l)n.tasks.push(Nt(u).then(h=>{h!==u&&o.setAttribute("style",o.getAttribute("style").replace(`${c}:${u};`,`${c}:${h};`))}));if(e.namespaceURI===ut||!e.namespaceURI)for(let c of["::before","::after"]){let u=getComputedStyle(e,c),h=u.getPropertyValue("content");if(!h||h==="none"||h==="normal")continue;let d="dw"+(n.n++).toString(36);o.classList.add(d),ct??=document.createElement("span"),n.css+=`.${d}${c}{${st(u,ot(ct),a,!1,null)}}`}if(r==="input"&&(e.type==="checkbox"||e.type==="radio"?e.checked?o.setAttribute("checked",""):o.removeAttribute("checked"):e.type!=="file"&&e.type!=="password"&&o.setAttribute("value",e.value)),r==="textarea")return o.textContent=e.value,o;if(r==="canvas"||r==="video"||r==="iframe"||r==="embed"||r==="object")return o;for(let c of Ut(e)){let u=dt(c,a,!1,n);u&&o.appendChild(u)}return r==="select"&&[...o.querySelectorAll("option")].forEach((c,u)=>{u===e.selectedIndex?c.setAttribute("selected",""):c.removeAttribute("selected")}),o}var Bt=e=>e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;");async function D(e,{dpr:t=1,clip:i=null,rootOpacity:n=null}={}){let r=performance.now();await document.fonts.ready;let a=e.getBoundingClientRect(),o=e.offsetWidth??a.width,l=e.offsetHeight??a.height,s=i??{x:0,y:0,w:o,h:l},c={tasks:[],css:"",n:0,rootOverrides:R=>`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:${o}px!important;height:${l}px!important;`+(R.display.startsWith("inline")&&R.display!=="inline-block"&&R.display!=="inline-flex"&&R.display!=="inline-grid"?"display:inline-block!important;":"")+(n!=null?`opacity:${n}!important;`:"")},u=dt(e,null,!0,c);if(!u)throw new Error("distortion-wave: element is not rendered (display:none?)");await Promise.all(c.tasks);let h=await Ct(),d=new XMLSerializer().serializeToString(u),p=Math.max(1,Math.round(s.w)),f=Math.max(1,Math.round(s.h)),m=`<svg xmlns="${k}" width="${p}" height="${f}"><foreignObject x="0" y="0" width="${p}" height="${f}"><div xmlns="${ut}" style="position:relative;width:${p}px;height:${f}px;overflow:hidden;margin:0;padding:0"><style>${Bt(h+c.css)}</style><div style="position:absolute;left:${-s.x}px;top:${-s.y}px;width:${o}px;height:${l}px">${d}</div></div></foreignObject></svg>`,x=new Image;x.decoding="async",x.src="data:image/svg+xml;charset=utf-8,"+encodeURIComponent(m),await x.decode();let g=document.createElement("canvas");g.width=Math.max(1,Math.round(p*t)),g.height=Math.max(1,Math.round(f*t));let S=g.getContext("2d",{willReadFrequently:!1});return S.drawImage(x,0,0,g.width,g.height),S.getImageData(0,0,1,1),{canvas:g,w:p,h:f,dpr:t,ms:performance.now()-r,bytes:m.length}}var ft=Math.PI*2,b=(e,t=0,i=1)=>Math.min(Math.max(e,t),i),v=(e,t,i)=>e+(t-e)*i,mt=e=>e-Math.floor(e),y=(e,t,i)=>{let n=b((i-e)/(t-e||1e-6));return n*n*(3-2*n)};function gt(e,t,i,n){let r=(a,o,l)=>{let s=1-a;return 3*s*s*a*o+3*s*a*a*l+a*a*a};return a=>{if(a<=0)return 0;if(a>=1)return 1;let o=0,l=1;for(let s=0;s<22;s++){let c=(o+l)/2;r(c,e,i)<a?o=c:l=c}return r((o+l)/2,t,n)}}var Ot=gt(.2,1,.3,1),pt=gt(.45,0,.55,1);function yt(e){let t=parseInt(e.replace("#",""),16),[i,n,r]=[t>>16&255,t>>8&255,t&255].map(s=>(s/=255,s<=.04045?s/12.92:((s+.055)/1.055)**2.4)),a=Math.cbrt(.4122214708*i+.5363325363*n+.0514459929*r),o=Math.cbrt(.2119034982*i+.6806995451*n+.1073969566*r),l=Math.cbrt(.0883024619*i+.2817188376*n+.6299787005*r);return[.2104542553*a+.793617785*o-.0040720468*l,1.9779984951*a-2.428592205*o+.4505937099*l,.0259040371*a+.7827717662*o-.808675766*l]}var U={aurora:["#FF4FD8","#A35BFF","#5B7CFF","#38D9FF","#FFD36B","#FF7A5C"],ocean:["#3A7BFF","#2EE6D6","#7C5CFF","#1FB6FF","#B8F2FF"],mint:["#2BD67B","#B6FF5C","#1FC8A9","#E8FFB0","#3CE0C0"],ember:["#FF5A3C","#FFB23C","#FF3C8E","#FFE07A","#FF7F50"]},E={type:"vertical",speed:1,anticipation:.45,travel:2.2,linger:-.3,rimFade:2.8,hairDelay:.45,hairFade:2.8,pull:22,inhale:50,gatherGlow:.35,gatherDots:1,amplitude:32,lensWidth:140,aberration:.75,swell:20,blur:18,blurNear:6,blurAhead:90,blurBehind:230,lead:120,tail:280,band:.9,rimWidth:14,rimSoftness:320,rimStrength:.85,uneven:1,hairWidth:1.5,lean:.9,margin:60,seam:48,particles:64,particleSize:1.8,particleLife:2.1,reach:140,spread:1,palette:"aurora"},xt={vertical:E,horizontal:{...E,type:"horizontal"},center:{...E,type:"center",anticipation:.25,travel:3.2,linger:-.2,rimFade:2.4,hairDelay:.9,pull:14,inhale:32,gatherGlow:.2,gatherDots:0,amplitude:20,lensWidth:90,aberration:.35,swell:13,blur:11,blurAhead:54,blurBehind:140,lead:80,tail:180,band:.75,rimStrength:.7,rimSoftness:200,particles:90,particleLife:3,reach:90,spread:.65}},It=["pull","inhale","amplitude","lensWidth","swell","blur","blurNear","blurAhead","blurBehind","lead","tail","rimWidth","rimSoftness","reach","margin","seam"],Wt=["anticipation","travel","linger","rimFade","hairDelay","hairFade","particleLife"];function vt(e,t,i){let n=b(Math.sqrt(t*i)/516,.12,1.8),r=b(.45+.55*Math.sqrt(n),.6,1.15)/(e.speed||1),a={...e,k:n};for(let o of It)a[o]=e[o]*n;for(let o of Wt)a[o]=e[o]*r;return a.blurNear=Math.max(a.blurNear,1.2),a.blur=Math.max(a.blur,a.blurNear),a.rimWidth=Math.max(a.rimWidth,2.5),a.seam=Math.max(a.seam,4),a.anticipation=Math.max(a.anticipation,.2/(e.speed||1)),a.particles=Math.round(e.particles*b(n,.3,1.5)),a.particleSize=e.particleSize*b(.6+.4*n,.7,1.3),a}var X=e=>e.anticipation+Math.max(e.travel*.6,e.linger+e.rimFade,e.linger+e.hairDelay+e.hairFade,e.particleLife);function qt(e,t,i,n,r,a){let o=Math.cos(i),l=Math.sin(i),s=0;for(let[c,u]of[[0,0],[r,0],[0,a],[r,a]]){let h=c-e,d=u-t;s=Math.max(s,Math.hypot((o*h+l*d)*n,-l*h+o*d))}return s}function wt(e,t,i){let n=t.type==="center",r=t.type==="horizontal",a=X(t),o=b(e,0,a),l=o-t.anticipation,s=y(0,t.anticipation,o),c=b(l/t.travel),u=Ot(c),h=i.src,d=h.x+h.w/2,p=h.y+h.h/2,f=Math.max(Math.min(h.w,h.h)/2,1),m=(r?h.h:h.w)/2,x=(r?h.w:h.h)/2,g=r?Math.PI/2:0,S=Math.cos(g),R=Math.sin(g),M=(i.w/2-d)*Math.sin(g)+(i.h/2-p)*-Math.cos(g)>=0?1:-1,F=0;!n&&i.touch&&(F=b(((i.touch.x-d)*S+(i.touch.y-p)*R)/Math.max(m,1),-1,1)*t.lean);let P=y(0,.45,u),Z=d+S*F*m*P,J=p+R*F*m*P,_=g+M*F*.45*P,z=n?1:1.4,N=qt(Z,J,_,z,i.w,i.h)+t.swell*z+t.margin,tt=l<0?f+t.inhale*(1-s):f+(N+t.margin*1.6-f)*u,et=1-y(.2,.55,c),it=t.gatherGlow*.5,Et=y(0,.05,c)*et,at=l>0&&tt-t.swell>N;return{time:o,fly:l,pre:s,prog:c,moved:u,ring:n,dir:M,r0:f,half:m,perp:x,cx:d,cy:p,ox:Z,oy:J,tilt:_,squash:v(1,z,y(0,.5,u)),spine:n?0:Math.max(0,m-f)*(1-P),lx:Math.cos(_),ly:Math.sin(_),fx:M*Math.sin(_),fy:-M*Math.cos(_),radius:tt,swell:t.swell*y(0,.3,u),energy:Et,pull:t.pull*s*(1-y(0,.22,l)),glow:l<0?it*s:v(it,1,y(0,.07,c))*et,clear:v(f-t.rimSoftness,N+t.rimSoftness,pt((l-t.linger)/t.rimFade)),hairClear:v(f-t.rimSoftness,N+t.rimSoftness,pt((l-t.linger-t.hairDelay)/t.hairFade)),rim:t.rimStrength*y(0,.1,c)*(1-y(.9,1,o/a)),hold:n&&i.hold?f:0,launched:l>0,covered:at,settled:at&&c>=.55}}function bt(e,t){return Array.from({length:e},(i,n)=>{let r=a=>mt((n+1)*a);return{col:r(.6180339887)*2-1,gy:r(.7071067812),head:r(.1618033989)*ft,pace:.4+.65*Math.sqrt(r(.7548776662)),drift:12+45*r(.569840291),sway:3+10*r(.4142135624),swayHz:2+3*r(.7320508076),tw:7+10*r(.2360679775),ph:r(.3819660113)*ft,hop:r(.5497004779),life:t.particleLife*(.45+.55*r(.6457513111)),size:t.particleSize*(.55+.45*r(.8774)**2),bright:.35+.65*r(.6055512755)}})}function At(e,t,i){let n=.75+.25*Math.sin(t.time*e.tw+e.ph),r=i.k;if(!t.launched){if(!i.gatherDots)return null;let h=t.time/.32+e.hop,d=mt(h),p=y(0,.3,d)*(1-y(.6,1,d))*e.bright*n*y(0,i.anticipation*.35,t.time)*(1-y(i.anticipation*.75,i.anticipation,t.time)),f=i.reach*(1-d*d);if(t.ring){let g=e.head+Math.floor(h)*2.399963,S=t.r0+12*r+f;return{x:t.cx+Math.cos(g)*S,y:t.cy+Math.sin(g)*S,a:p}}let m=e.col*(t.half+16*r)*(1-.35*(1-d)),x=t.perp+10*r+f*(.45+.55*e.gy);return{x:t.cx+t.lx*m+t.fx*x,y:t.cy+t.ly*m+t.fy*x,a:p}}let a=t.fly,o=(1-y(e.life*.55,e.life,a))*e.bright*n*y(0,.08,a),l=e.sway*Math.max(r,.3)*Math.sin(e.swayHz*a+e.ph)*Math.min(a/.25,1),s=(t.radius-t.r0)*e.pace*i.spread;if(t.ring){let h=t.r0+s+e.drift*r*a,d=Math.cos(e.head),p=Math.sin(e.head);return{x:t.ox+d*h-p*l,y:t.oy+p*h+d*l,a:o}}let c=e.col*t.half*(1+.25*t.moved*e.pace)+l,u=(t.r0+s)*(1-.3*e.col*e.col)+e.drift*r*a;return{x:t.ox+t.lx*c+t.fx*u,y:t.oy+t.ly*c+t.fy*u,a:o}}var K=`#version 300 es
4
+ out vec2 vUv;
5
+ 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); }`,zt=`#version 300 es
6
+ precision highp float; in vec2 vUv; uniform sampler2D uTex; uniform float uLod; out vec4 o;
7
+ void main() { o = textureLod(uTex, vUv, uLod); }`,$t=`#version 300 es
8
+ precision highp float; in vec2 vUv; uniform sampler2D uTex; uniform vec2 uStep; uniform float uSigma; out vec4 o;
9
+ void main() {
10
+ float R = min(ceil(uSigma * 3.0), 32.0); vec4 acc = vec4(0.0); float ws = 0.0;
11
+ for (int i = -32; i <= 32; i++) { float fi = float(i); if (abs(fi) > R) continue;
12
+ float w = exp(-0.5 * fi * fi / (uSigma * uSigma)); acc += textureLod(uTex, vUv + uStep * fi, 0.0) * w; ws += w; }
13
+ o = acc / ws;
14
+ }`,Xt=`#version 300 es
15
+ precision highp float; precision highp int;
16
+ uniform vec2 uSize, uRes;
17
+ uniform sampler2D uOld0, uOld1, uOld2, uNew0, uNew1, uNew2;
18
+ uniform vec4 uOldRect, uNewRect, uShape, uRadii; // rects in canvas CSS px; radii (br, tr, bl, tl)
19
+ uniform int uMode; // 0 before \xB7 1 live \xB7 2 after \xB7 3 crossfade \xB7 4 effects only (live DOM below)
20
+ uniform float uFade;
21
+ uniform bool uLaunched, uRing;
22
+ uniform vec2 uOrigin;
23
+ uniform float uTilt, uSpine, uRadius, uSquash, uTime, uSwell, uEnergy, uPull, uGlow, uRim, uClear, uHairClear, uHold, uK, uSeam;
24
+ uniform float uAmp, uLensW, uAberr, uBlurMax, uBlurNear, uBlurAhead, uBlurBehind;
25
+ uniform float uLead, uTail, uBand, uRimW, uRimSoft, uUneven, uHairW;
26
+ uniform vec3 uPal[8]; uniform int uPalN;
27
+ out vec4 outColor;
28
+ const float TAU = 6.28318530718;
29
+
30
+ mat2 rot(float a) { float c = cos(a), s = sin(a); return mat2(c, s, -s, c); }
31
+ float ign(vec2 p) { return fract(52.9829189 * fract(dot(p, vec2(0.06711056, 0.00583715)))); }
32
+ float h11(float x) { return fract(sin(x * 127.1) * 43758.5453); }
33
+ 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; }
34
+ float sdRoundBox(vec2 p, vec2 b, vec4 r) { // y-down space: r = (bottom-right, top-right, bottom-left, top-left)
35
+ r.xy = (p.x > 0.0) ? r.xy : r.zw; r.x = (p.y > 0.0) ? r.x : r.y;
36
+ vec2 q = abs(p) - b + r.x; return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r.x;
37
+ }
38
+ struct Front { float d; vec2 n; vec2 q; float g; };
39
+ Front front(vec2 p) {
40
+ vec2 q = rot(-uTilt) * (p - uOrigin); float mx = q.x * uSquash;
41
+ vec2 v = vec2(max(abs(mx) - uSpine, 0.0), q.y); float len = max(length(v), 1e-3);
42
+ vec2 grad = vec2(v.x * sign(q.x) * uSquash, v.y) / len; float g = max(length(grad), 1e-3);
43
+ float wob;
44
+ 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); }
45
+ 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); }
46
+ Front f; f.d = (len - uRadius) / g + uSwell * wob; f.n = rot(uTilt) * (grad / g); f.q = q; f.g = g; return f;
47
+ }
48
+ // The silhouette is decided by p (undisplaced); the sample by sp (displaced, clamped to the rect).
49
+ // \u2192 content bends INSIDE the element, never revealing transparency or bleeding outside.
50
+ vec4 tap(sampler2D s, vec4 rect, vec2 p, vec2 sp) {
51
+ vec2 q = (p - rect.xy) / rect.zw;
52
+ if (q.x < 0.0 || q.y < 0.0 || q.x > 1.0 || q.y > 1.0) return vec4(0.0);
53
+ return textureLod(s, clamp((sp - rect.xy) / rect.zw, 0.0, 1.0), 0.0);
54
+ }
55
+ vec4 fetch3(sampler2D s0, sampler2D s1, sampler2D s2, vec4 rect, vec2 p, vec2 sp, float tn, float tf) {
56
+ vec4 c = tap(s0, rect, p, sp);
57
+ if (tn > 0.0) c = mix(c, tap(s1, rect, p, sp), tn);
58
+ if (tf > 0.0) c = mix(c, tap(s2, rect, p, sp), tf);
59
+ return c;
60
+ }
61
+ vec4 layerOld(vec2 p, vec2 sp, vec2 sl, float tn, float tf) {
62
+ if (dot(sl, sl) < 0.01) return fetch3(uOld0, uOld1, uOld2, uOldRect, p, sp, tn, tf);
63
+ 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);
64
+ return vec4(r.r, g.g, b.b, max(max(r.a, g.a), b.a));
65
+ }
66
+ vec4 layerNew(vec2 p, vec2 sp, vec2 sl, float tn, float tf) {
67
+ if (dot(sl, sl) < 0.01) return fetch3(uNew0, uNew1, uNew2, uNewRect, p, sp, tn, tf);
68
+ 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);
69
+ return vec4(r.r, g.g, b.b, max(max(r.a, g.a), b.a));
70
+ }
71
+ vec3 paletteAt(float u) {
72
+ float x = fract(u) * float(uPalN); int i = min(int(floor(x)), uPalN - 1); int j = (i + 1) % uPalN;
73
+ return mix(uPal[i], uPal[j], smoothstep(0.0, 1.0, fract(x)));
74
+ }
75
+ vec3 oklabToSrgb(vec3 c) {
76
+ 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;
77
+ l = l * l * l; m = m * m * m; s = s * s * s;
78
+ 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);
79
+ return mix(12.92 * lin, 1.055 * pow(lin, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), lin));
80
+ }
81
+ void main() {
82
+ vec2 p = vec2(gl_FragCoord.x, uRes.y - gl_FragCoord.y) * (uSize.x / uRes.x);
83
+ float dn = (ign(gl_FragCoord.xy) - 0.5) / 255.0;
84
+ if (uMode == 0 || uMode == 2 || uMode == 3) {
85
+ vec4 a = tap(uOld0, uOldRect, p, p), b = tap(uNew0, uNewRect, p, p);
86
+ vec4 c = uMode == 0 ? a : uMode == 2 ? b : mix(a, b, uFade);
87
+ outColor = vec4(c.rgb + dn * c.a, c.a); return;
88
+ }
89
+ Front f = front(p);
90
+ vec2 r = p - uOrigin; float rl = max(length(r), 1.0);
91
+ float held = uHold > 0.0 ? smoothstep(uHold * 0.9, uHold * 1.8, rl) : 1.0;
92
+ vec4 col = vec4(0.0);
93
+ if (uMode == 1) {
94
+ float k = f.d / (uLensW * (f.d > 0.0 ? 0.8 : 1.6));
95
+ vec2 bulge = f.n * (uEnergy * uAmp * exp(-k * k) * held);
96
+ vec2 suck = (r / rl) * (uPull * (rl / (rl + 60.0 * uK)) * exp(-rl / (700.0 * uK)) * held);
97
+ vec2 sp = p - bulge + suck;
98
+ vec2 sl = (bulge * 0.5 - suck * exp(-rl / (240.0 * uK))) * uAberr;
99
+ float kb = f.d / (f.d > 0.0 ? uBlurAhead : uBlurBehind);
100
+ float bpx = uEnergy * uBlurMax * exp(-kb * kb) * held;
101
+ float tn = smoothstep(0.0, uBlurNear, bpx);
102
+ float tf = uBlurMax > uBlurNear ? smoothstep(uBlurNear, uBlurMax, bpx) : 0.0;
103
+ float cover = uLaunched ? smoothstep(-uSeam, uSeam, f.d) : 1.0;
104
+ if (cover >= 0.999) col = layerOld(p, sp, sl, tn, tf);
105
+ else if (cover <= 0.001) col = layerNew(p, sp, sl, tn, tf);
106
+ else col = mix(layerNew(p, sp, sl, tn, tf), layerOld(p, sp, sl, tn, tf), cover);
107
+ // the UNdisplaced alpha fixes the silhouette (rounded corners, transparent areas): no fill-in, no fringe outside
108
+ float aU = mix(tap(uNew0, uNewRect, p, p).a, tap(uOld0, uOldRect, p, p).a, cover);
109
+ col *= min(1.0, aU / max(col.a, 1e-4));
110
+ }
111
+ // element shape (per-corner radii): mask + rim
112
+ vec2 c = p - (uShape.xy + uShape.zw * 0.5);
113
+ float sd = sdRoundBox(c, uShape.zw * 0.5, uRadii);
114
+ float mask = clamp(0.5 - sd * (uRes.x / uSize.x), 0.0, 1.0);
115
+ float inset = max(-sd, 0.0);
116
+ float along = f.q.x * 0.0065 / uK, ang = atan(f.q.y, f.q.x);
117
+ float lobes = uRing ? 0.5 + 0.5 * sin(2.0 * ang + uTime * 1.4) * cos(ang - uTime * 0.6 + 0.8)
118
+ : 0.5 + 0.5 * sin(along * 1.4 + uTime * 1.3) * cos(along * 0.7 - uTime * 0.6 + 0.8);
119
+ float bandA = 0.0;
120
+ if (uGlow > 0.0 && f.d < uLead) {
121
+ if (f.d >= 0.0) { float x = f.d / uLead; bandA = exp(-4.5 * x * x); }
122
+ else { float x = clamp(-f.d / (uTail * mix(0.55, 1.35, lobes)), 0.0, 1.0); bandA = pow(1.0 - x, 2.4); }
123
+ bandA *= uGlow * uBand * held;
124
+ }
125
+ float around = atan(c.y / uShape.w, c.x / uShape.z);
126
+ float rimA = 0.0, hairA = 0.0, shimmer = 0.5;
127
+ if (uRim > 0.0 && inset < uRimW * 4.5) {
128
+ 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);
129
+ float thick = max(1.0 + uUneven * 0.55 * lumps, 0.15);
130
+ float hk = inset / (uRimW * thick);
131
+ float reached = 1.0 - smoothstep(-uLead * 0.6, uLead * 0.4, f.d);
132
+ float kept = smoothstep(-uRimSoft, uRimSoft, f.d + (uRadius - uClear) / f.g);
133
+ shimmer = 0.5 + 0.5 * sin(uTime * 3.2 - around * 4.0);
134
+ rimA = exp(-hk * hk) * reached * kept * uRim * (0.72 + 0.28 * shimmer);
135
+ float line = 1.0 - smoothstep(uHairW * 0.5, uHairW * 0.5 + 0.8, inset);
136
+ float hairKept = smoothstep(-uRimSoft, uRimSoft, f.d + (uRadius - uHairClear) / f.g);
137
+ hairA = line * reached * hairKept * uRim * 0.85 * mix(0.55, 1.0, smoothstep(0.2, 1.4, thick));
138
+ }
139
+ float a = (1.0 - (1.0 - bandA) * (1.0 - rimA)) * mask;
140
+ hairA *= mask;
141
+ if (a + hairA < 0.0005) { outColor = vec4(col.rgb + dn * col.a, col.a); return; }
142
+ float flow = 0.6 + (uRing ? ang / TAU : along * 0.22) + uTime * 0.12 + f.d * 0.0016 / uK;
143
+ vec3 lab = paletteAt(flow);
144
+ 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));
145
+ lab.yz *= 1.2;
146
+ 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)));
147
+ vec3 rimLab = paletteAt(around / TAU + uTime * 0.25 + inset * 0.0012 / uK); rimLab.yz *= 1.1;
148
+ lab = mix(lab, rimLab, rimA / (rimA + bandA + 1e-4));
149
+ float ea = clamp(a, 0.0, 1.0);
150
+ vec4 o = vec4(oklabToSrgb(lab) * ea, ea) + col * (1.0 - ea);
151
+ float ha = clamp(hairA, 0.0, 1.0);
152
+ o = vec4(mix(oklabToSrgb(rimLab), vec3(1.0), 0.6 + 0.35 * shimmer) * ha, ha) + o * (1.0 - ha);
153
+ outColor = vec4(o.rgb + dn * 2.0 * o.a, o.a);
154
+ }`,Kt=`#version 300 es
155
+ layout(location = 0) in vec4 aDot; uniform vec2 uSize; uniform float uDpr; out float vA; out float vR; out float vPt;
156
+ 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);
157
+ vPt = ceil((aDot.z + 1.0) * uDpr) + 2.0; gl_PointSize = vPt; vR = aDot.z * 0.5 * uDpr; vA = aDot.w; }`,Ht=`#version 300 es
158
+ precision highp float; in float vA; in float vR; in float vPt; out vec4 o;
159
+ 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); }`;function B(e,t,i){let n=(o,l)=>{let s=e.createShader(o);if(e.shaderSource(s,l),e.compileShader(s),!e.getShaderParameter(s,e.COMPILE_STATUS))throw new Error(e.getShaderInfoLog(s)||"compile");return s},r=e.createProgram();if(e.attachShader(r,n(e.VERTEX_SHADER,t)),e.attachShader(r,n(e.FRAGMENT_SHADER,i)),e.linkProgram(r),!e.getProgramParameter(r,e.LINK_STATUS))throw new Error(e.getProgramInfoLog(r)||"link");let a=new Map;return{p:r,u:o=>(a.has(o)||a.set(o,e.getUniformLocation(r,o)),a.get(o))}}var H=class{constructor(){this.canvas=document.createElement("canvas"),this.canvas.setAttribute("data-dw-ignore",""),this.canvas.setAttribute("aria-hidden","true"),this.canvas.style.cssText="position:fixed;left:0;top:0;pointer-events:none;z-index:2147483000;contain:strict;",this.layers={},this.pal=new Float32Array(24),this.palKey="",this.ok=!1,this.canvas.addEventListener("webglcontextlost",i=>{i.preventDefault(),this.ok=!1});let t=this.canvas.getContext("webgl2",{alpha:!0,premultipliedAlpha:!0,antialias:!1,depth:!1,stencil:!1,preserveDrawingBuffer:!1});t&&(this.gl=t,this.main=B(t,K,Xt),this.blur=B(t,K,$t),this.copy=B(t,K,zt),this.dots=B(t,Kt,Ht),this.vao=t.createVertexArray(),this.dvao=t.createVertexArray(),this.dbuf=t.createBuffer(),this.dcap=0,t.bindVertexArray(this.dvao),t.bindBuffer(t.ARRAY_BUFFER,this.dbuf),t.enableVertexAttribArray(0),t.vertexAttribPointer(0,4,t.FLOAT,!1,16,0),t.bindVertexArray(null),this.maxTex=t.getParameter(t.MAX_TEXTURE_SIZE),this.ok=!0)}tex(t,i,n,r=!1){let a=this.gl,o=a.createTexture();return a.bindTexture(a.TEXTURE_2D,o),a.pixelStorei(a.UNPACK_FLIP_Y_WEBGL,!1),a.pixelStorei(a.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),n?a.texImage2D(a.TEXTURE_2D,0,a.RGBA8,a.RGBA,a.UNSIGNED_BYTE,n):a.texImage2D(a.TEXTURE_2D,0,a.RGBA8,t,i,0,a.RGBA,a.UNSIGNED_BYTE,null),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MIN_FILTER,r?a.LINEAR_MIPMAP_LINEAR:a.LINEAR),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_MAG_FILTER,a.LINEAR),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_WRAP_S,a.CLAMP_TO_EDGE),a.texParameteri(a.TEXTURE_2D,a.TEXTURE_WRAP_T,a.CLAMP_TO_EDGE),r&&a.generateMipmap(a.TEXTURE_2D),o}target(t,i){let n=this.gl,r=this.tex(t,i,null),a=n.createFramebuffer();return n.bindFramebuffer(n.FRAMEBUFFER,a),n.framebufferTexture2D(n.FRAMEBUFFER,n.COLOR_ATTACHMENT0,n.TEXTURE_2D,r,0),{tex:r,fb:a}}pass(t,i,n,r){let a=this.gl;a.bindFramebuffer(a.FRAMEBUFFER,i),a.viewport(0,0,n,r),a.useProgram(t.p),a.bindVertexArray(this.vao),a.drawArrays(a.TRIANGLES,0,3)}blurred(t,i,n,r){let a=this.gl,o=Math.max(1,Math.round(r/4)),l=Math.max(1,Math.round(i/o)),s=Math.max(1,Math.round(n/o)),c=this.target(l,s),u=this.target(l,s);return a.activeTexture(a.TEXTURE0),a.bindTexture(a.TEXTURE_2D,t),a.useProgram(this.copy.p),a.uniform1i(this.copy.u("uTex"),0),a.uniform1f(this.copy.u("uLod"),Math.log2(o)),this.pass(this.copy,c.fb,l,s),a.useProgram(this.blur.p),a.uniform1i(this.blur.u("uTex"),0),a.uniform1f(this.blur.u("uSigma"),Math.max(r/o,.01)),a.bindTexture(a.TEXTURE_2D,c.tex),a.uniform2f(this.blur.u("uStep"),1/l,0),this.pass(this.blur,u.fb,l,s),a.bindTexture(a.TEXTURE_2D,u.tex),a.uniform2f(this.blur.u("uStep"),0,1/s),this.pass(this.blur,c.fb,l,s),a.deleteFramebuffer(u.fb),a.deleteTexture(u.tex),a.deleteFramebuffer(c.fb),a.bindFramebuffer(a.FRAMEBUFFER,null),c.tex}setLayer(t,i,n,r){if(!this.ok)return;this.dropLayer(t);let a=this.tex(i.width,i.height,i,!0);this.layers[t]={sharp:a,near:this.blurred(a,i.width,i.height,n),far:this.blurred(a,i.width,i.height,r)}}dropLayer(t){let i=this.layers[t];if(!(!i||!this.gl)){for(let n of[i.sharp,i.near,i.far])this.gl.deleteTexture(n);delete this.layers[t]}}clear(){for(let t of Object.keys(this.layers))this.dropLayer(t)}render(t){if(!this.ok)return;let i=this.gl,n=this.canvas.width,r=this.canvas.height,a=this.main,o=a.u,l=this.layers.old||this.layers.new,s=this.layers.new||this.layers.old;if(!l)return;i.bindFramebuffer(i.FRAMEBUFFER,null),i.viewport(0,0,n,r),i.disable(i.BLEND),i.useProgram(a.p),[["uOld0",l.sharp],["uOld1",l.near],["uOld2",l.far],["uNew0",s.sharp],["uNew1",s.near],["uNew2",s.far]].forEach(([p,f],m)=>{i.activeTexture(i.TEXTURE0+m),i.bindTexture(i.TEXTURE_2D,f),i.uniform1i(o(p),m)});let c=t.rects,u=(p,f)=>i.uniform4f(o(p),f.x,f.y,f.w,f.h);i.uniform2f(o("uSize"),t.w,t.h),i.uniform2f(o("uRes"),n,r),u("uOldRect",c.old),u("uNewRect",c.new||c.old),u("uShape",c.shape),i.uniform4f(o("uRadii"),c.radii.br,c.radii.tr,c.radii.bl,c.radii.tl),i.uniform1i(o("uMode"),t.mode),i.uniform1f(o("uFade"),t.fade||0);let h=t.s,d=t.cfg;if(h){i.uniform1i(o("uLaunched"),h.launched?1:0),i.uniform1i(o("uRing"),h.ring?1:0),i.uniform2f(o("uOrigin"),h.ox,h.oy);let p={uTilt:h.tilt,uSpine:h.spine,uRadius:h.radius,uSquash:h.squash,uTime:h.time,uSwell:h.swell,uEnergy:h.energy,uPull:h.pull,uGlow:h.glow,uRim:h.rim,uClear:h.clear,uHairClear:h.hairClear,uHold:h.hold,uK:d.k,uSeam:d.seam,uAmp:d.amplitude,uLensW:d.lensWidth,uAberr:d.aberration,uBlurMax:d.blur,uBlurNear:d.blurNear,uBlurAhead:d.blurAhead,uBlurBehind:d.blurBehind,uLead:d.lead,uTail:d.tail,uBand:d.band,uRimW:d.rimWidth,uRimSoft:d.rimSoftness,uUneven:d.uneven,uHairW:d.hairWidth};for(let x in p)i.uniform1f(o(x),p[x]);let f=Array.isArray(d.palette)?d.palette:U[d.palette]||U.aurora,m=f.join();this.palKey!==m&&(this.palKey=m,f.slice(0,8).forEach((x,g)=>this.pal.set(yt(x),g*3))),i.uniform3fv(o("uPal[0]"),this.pal),i.uniform1i(o("uPalN"),Math.min(f.length,8))}if(i.bindVertexArray(this.vao),i.drawArrays(i.TRIANGLES,0,3),t.dotCount>0){let p=this.dots;i.enable(i.BLEND),i.blendFunc(i.ONE,i.ONE_MINUS_SRC_ALPHA),i.useProgram(p.p),i.uniform2f(p.u("uSize"),t.w,t.h),i.uniform1f(p.u("uDpr"),t.dpr),i.bindVertexArray(this.dvao),i.bindBuffer(i.ARRAY_BUFFER,this.dbuf),this.dcap<t.dots.length&&(i.bufferData(i.ARRAY_BUFFER,t.dots.byteLength,i.DYNAMIC_DRAW),this.dcap=t.dots.length),i.bufferSubData(i.ARRAY_BUFFER,0,t.dots,0,t.dotCount*4),i.drawArrays(i.POINTS,0,t.dotCount),i.disable(i.BLEND)}i.bindVertexArray(null)}},V=[],O=0,Vt=4;function G(){for(;V.length;){let e=V.pop();if(e.ok)return e;O--}if(O>=Vt)return null;try{let e=new H;return e.ok?(O++,e):null}catch(e){return console.warn("[distortion-wave]",e),null}}function Y(e){e.clear(),e.canvas.remove(),e.ok?V.push(e):O--}function Gt(){let e=G();return e&&Y(e),!!e}var I='a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),summary,[contenteditable="true"],[tabindex]:not([tabindex="-1"])';function St(e){let t=document.activeElement;if(!t||t===document.body||!e.contains(t))return null;let i=[];for(let r=t;r&&r!==e;r=r.parentElement)i.unshift([...r.parentElement.children].indexOf(r));let n="selectionStart"in t?(()=>{try{return[t.selectionStart,t.selectionEnd]}catch{return null}})():null;return{el:t,id:t.id,key:t.getAttribute("data-focus-key")||t.getAttribute("name"),tag:t.localName,path:i,order:[...e.querySelectorAll(I)].indexOf(t),sel:n}}function Rt(e,t){if(!e||e.el.isConnected)return;let i=document.activeElement;if(i&&i!==document.body&&i!==document.documentElement)return;let n=null;if(e.id&&(n=t.querySelector("#"+CSS.escape(e.id))),!n&&e.key&&(n=t.querySelector(`[data-focus-key="${CSS.escape(e.key)}"],[name="${CSS.escape(e.key)}"]`)),!n){let r=t;for(let a of e.path)r=r?.children[a];r&&r!==t&&r.localName===e.tag&&r.matches(I)&&(n=r)}if(!n&&e.order>=0){let r=t.querySelectorAll(I);n=r[Math.min(e.order,r.length-1)]||null}if(!n&&t.matches(I)&&(n=t),!!n&&(n.focus({preventScroll:!0}),e.sel&&typeof n.setSelectionRange=="function"))try{n.setSelectionRange(e.sel[0],e.sel[1])}catch{}}function Yt(e,t,i){let n=(c,u)=>{let h=String(c).trim().split(/\s+/)[0],d=parseFloat(h)||0;return h.endsWith("%")?d/100*u:d},r=n(e.borderTopLeftRadius,t),a=n(e.borderTopRightRadius,t),o=n(e.borderBottomRightRadius,t),l=n(e.borderBottomLeftRadius,t),s=Math.min(1,t/(r+a||1e9),t/(l+o||1e9),i/(r+l||1e9),i/(a+o||1e9));return{tl:r*s,tr:a*s,br:o*s,bl:l*s}}function jt(e){for(let t=e;t&&t!==document.documentElement;t=t.parentElement)if(getComputedStyle(t).position==="fixed")return!0;return!1}function j(e){let t=e.getBoundingClientRect(),i=getComputedStyle(e),n=e.offsetWidth??t.width,r=e.offsetHeight??t.height,a=Math.max(t.left,0),o=Math.max(t.top,0),l=Math.min(t.right,innerWidth),s=Math.min(t.bottom,innerHeight),c=jt(e),u=c?0:scrollX,h=c?0:scrollY;return{w:n,h:r,fixed:c,opacity:i.opacity,radii:Yt(i,n,r),visible:l>a&&s>o,clip:{x:a-t.left,y:o-t.top,w:l-a,h:s-o},page:{x:a+u,y:o+h,w:l-a,h:s-o},el:{x:t.left+u,y:t.top+h,w:t.width,h:t.height}}}var Qt=(e,t)=>{let i=Math.min(e.x,t.x),n=Math.min(e.y,t.y);return{x:i,y:n,w:Math.max(e.x+e.w,t.x+t.w)-i,h:Math.max(e.y+e.h,t.y+t.h)-n}},A=(e,t)=>({x:e.x-t.x,y:e.y-t.y,w:e.w,h:e.h}),Zt=()=>new Promise(e=>requestAnimationFrame(()=>e()));function W(){let e,t,i=new Promise((n,r)=>{e=n,t=r});return i.catch(()=>{}),{p:i,res:e,rej:t}}function _t(e,t){let i=Math.max(e.clip.w*e.clip.h,1);return Math.max(.5,Math.min(window.devicePixelRatio||1,2,Math.sqrt(42e5/i),t/Math.max(e.clip.w,e.clip.h,1)))}var T=new WeakMap;function Jt(e){if(T.get(e)?.pending)return T.get(e).pending;let t=j(e);if(!t.visible)return Promise.resolve(null);let i={dirty:!1,m:t,dpr:_t(t,8192),at:performance.now()};return i.mo=new MutationObserver(()=>{i.dirty=!0}),i.mo.observe(e,{subtree:!0,childList:!0,attributes:!0,characterData:!0}),i.pending=D(e,{dpr:i.dpr,clip:t.clip}).then(n=>i.shot=n,()=>null),T.set(e,i),i.pending}function te(e,t){let i=T.get(e);if(!i)return null;T.delete(e),i.mo.disconnect();let n=(r,a)=>Math.abs(r.x-a.x)<.5&&Math.abs(r.y-a.y)<.5&&Math.abs(r.w-a.w)<.5&&Math.abs(r.h-a.h)<.5;return i.dirty||!i.shot||performance.now()-i.at>8e3||!n(i.m.page,t.page)?null:i.shot}var q=new WeakMap,ee={variant:"auto",palette:"aurora",speed:1,particles:!0,reducedMotion:null,timeout:3e3,preserveFocus:!0},Q=class{constructor(t,i,n){this.target=t,this.update=i||(()=>{}),this.o={...ee,...n},this._d={ready:W(),update:W(),covered:W(),finished:W()},this.ready=this._d.ready.p,this.updateCallbackDone=this._d.update.p,this.covered=this._d.covered.p,this.finished=this._d.finished.p,this.metrics={},this._updated=!1,this._skipped=!1,this._done=!1,this._run().catch(r=>this._abort(r))}skip(){this._skipped=!0,this._loopStarted&&this._finish()}async _runUpdate(){if(this._updated)return this._updatePromise;this._updated=!0;let t=this.o.preserveFocus===!1?null:St(this.target);return this._updatePromise=(async()=>{await this.update(),Rt(t,this.target)})(),this._updatePromise.then(()=>this._d.update.res(),i=>this._d.update.rej(i)),this._updatePromise}async _run(){let t=q.get(this.target);q.set(this.target,this),t&&(t.skip(),await t.finished);let i=this.o,n=this.target,r=i.reducedMotion??matchMedia("(prefers-reduced-motion: reduce)").matches,a=j(n),o=a.visible&&n!==document.body&&n!==document.documentElement?G():null;if(this.r=o,!o||this._skipped)return this._plain();let l=_t(a,o.maxTex),s=performance.now(),c=te(n,a)||await D(n,{dpr:Math.min(l,2),clip:a.clip});if(this.metrics.captureBefore=Math.round(performance.now()-s),this._skipped)return this._plain();let u={...xt[this._variant(a)]||E};for(let f of Object.keys(i))f in E&&f!=="type"&&f!=="particles"&&(u[f]=i[f]);i.particles===!1?u.particles=0:typeof i.particles=="number"&&(u.particles=i.particles);let h=vt(u,a.clip.w,a.clip.h);if(this.cfg=h,this.before=a,this.dpr=c.dpr,this.particles=bt(h.particles,h),this.dots=new Float32Array(Math.max(this.particles.length,1)*4),this.origin=this._originPage(a),this.canvasPage=a.page,this.rects={old:A(a.page,a.page),new:null,shape:A(a.el,a.page),radii:a.radii},s=performance.now(),o.setLayer("old",c.canvas,h.blurNear*this.dpr,h.blur*this.dpr),this.metrics.upload=Math.round(performance.now()-s),this._mount(),this._hide(),this.mode=r?0:1,this.fadeOnly=r,this._d.ready.res(),this._startLoop(),await this._runUpdate(),await Zt(),await Promise.all([...n.querySelectorAll("img")].map(f=>f.decode?.().catch(()=>{}))),this._done)return;let d=j(n);s=performance.now();let p=d.visible?await D(n,{dpr:this.dpr,clip:d.clip,rootOpacity:a.opacity}):null;if(this.metrics.captureAfter=Math.round(performance.now()-s),!this._done){if(!p){this._finish();return}o.setLayer("new",p.canvas,h.blurNear*this.dpr,h.blur*this.dpr),this.after=d,this.canvasPage=Qt(a.page,d.page),this.rects={old:A(a.page,this.canvasPage),new:A(d.page,this.canvasPage),shape:A(a.el,this.canvasPage),radii:a.radii},this.shapes={from:A(a.el,this.canvasPage),to:A(d.el,this.canvasPage),r0:a.radii,r1:d.radii},this._resizeCanvas(),this.gateOpen=!0,this.fadeOnly&&(this.mode=3,this.fadeStart=this.now)}}_variant(t){if(this.o.variant!=="auto")return this.o.variant;let i=this.o.source;if(!(i instanceof Element)||i===this.target)return"center";let n=i.getBoundingClientRect();return n.width>=n.height*1.4?"vertical":n.height>=n.width*1.4?"horizontal":"center"}_originPage(t){let i=t.fixed?0:scrollX,n=t.fixed?0:scrollY,r=this.o,a=s=>s&&"clientX"in s?{x:s.clientX,y:s.clientY}:s&&"x"in s?{x:s.x,y:s.y}:null,o=a(r.focus);(r.focus instanceof KeyboardEvent||r.focus&&r.focus.detail===0&&r.focus.type==="click")&&(o=null);let l;if(r.source instanceof Element&&r.source!==this.target){let s=r.source.getBoundingClientRect();l={x:s.left,y:s.top,w:s.width,h:s.height}}else if(r.source&&"width"in r.source)l={x:r.source.x,y:r.source.y,w:r.source.width,h:r.source.height};else{let s=!(r.source instanceof Element)&&a(r.source)||o||{x:t.el.x-i+t.w/2,y:t.el.y-n+t.h/2},c=Math.max(8,Math.min(t.w,t.h)*.08);l={x:s.x-c,y:s.y-c,w:2*c,h:2*c}}return{src:{x:l.x+i,y:l.y+n,w:l.w,h:l.h},touch:o&&{x:o.x+i,y:o.y+n},hold:r.source instanceof Element&&r.source!==this.target&&this.target.contains(r.source)}}_mount(){let t=this.r.canvas;document.body.appendChild(t),this._resizeCanvas()}_resizeCanvas(){let t=this.r.canvas,i=this.canvasPage;t.width=Math.max(1,Math.round(i.w*this.dpr)),t.height=Math.max(1,Math.round(i.h*this.dpr)),t.style.width=i.w+"px",t.style.height=i.h+"px",this._place()}_place(){let t=this.canvasPage,i=this.before.fixed;this.r.canvas.style.transform=`translate3d(${t.x-(i?0:scrollX)}px,${t.y-(i?0:scrollY)}px,0)`}_hide(){let t=this.target.style;this._saved=["opacity","transition"].map(i=>[i,t.getPropertyValue(i),t.getPropertyPriority(i)]),t.setProperty("transition","none","important"),t.setProperty("opacity","0","important")}_show(){if(this._saved){for(let[t,i,n]of this._saved)i?this.target.style.setProperty(t,i,n):this.target.style.removeProperty(t);this._saved=null}}_startLoop(){this._loopStarted=!0,this.held=0,this.start=performance.now(),this.now=this.start,this.last=this.start;let t=i=>{if(this._done)return;let n=Math.min(i-this.last,100);this.last=i,this.now=i,this.metrics.worstFrame=Math.max(this.metrics.worstFrame||0,n),this._place();let r=(i-this.start-this.held)/1e3,a=this.cfg.anticipation-.001;if(!this.gateOpen&&r>=a&&(this.held+=r*1e3-a*1e3,r=a,i-this.start>this.o.timeout)){this._finish();return}if(this.t=r,this.mode===3){let o=b((i-this.fadeStart)/220);if(this.r.render({mode:3,fade:o,w:this.canvasPage.w,h:this.canvasPage.h,rects:this.rects,dpr:this.dpr,dotCount:0}),o>=1){this._finish();return}}else if(this.mode===0)this.r.render({mode:0,w:this.canvasPage.w,h:this.canvasPage.h,rects:this.rects,dpr:this.dpr,dotCount:0});else{let o={w:this.canvasPage.w,h:this.canvasPage.h,src:A(this.origin.src,this.canvasPage),touch:this.origin.touch&&A({...this.origin.touch,w:0,h:0},this.canvasPage),hold:this.origin.hold},l=wt(r,this.cfg,o);if(this.shapes){let c=l.moved,u=this.shapes.from,h=this.shapes.to,d=this.shapes.r0,p=this.shapes.r1;this.rects.shape={x:v(u.x,h.x,c),y:v(u.y,h.y,c),w:v(u.w,h.w,c),h:v(u.h,h.h,c)},this.rects.radii={tl:v(d.tl,p.tl,c),tr:v(d.tr,p.tr,c),br:v(d.br,p.br,c),bl:v(d.bl,p.bl,c)}}l.launched&&!this._launched&&(this._launched=!0,this.o.onLaunch?.()),l.covered&&!this._covered&&(this._covered=!0,this._d.covered.res(),this.o.onCovered?.()),l.settled&&this.mode===1&&(this.mode=4,this._show(),this.o.onSettled?.());let s=0;for(let c of this.particles){let u=At(c,l,this.cfg);!u||u.a<=.002||(this.dots[s*4]=u.x,this.dots[s*4+1]=u.y,this.dots[s*4+2]=c.size,this.dots[s*4+3]=Math.min(u.a,1),s++)}if(this.r.render({mode:this.mode,s:l,cfg:this.cfg,w:this.canvasPage.w,h:this.canvasPage.h,rects:this.rects,dpr:this.dpr,dots:this.dots,dotCount:s}),r>=X(this.cfg)){this._finish();return}}if(this._skipped){this._finish();return}requestAnimationFrame(t)};requestAnimationFrame(t)}async _plain(){try{await this._runUpdate()}finally{this._done=!0,this._release(),this._d.ready.res(),this._d.covered.res(),this._d.finished.res()}}async _finish(){if(!this._done){this._done=!0;try{await this._runUpdate()}catch{}this._show(),this._release(),this._covered||this._d.covered.res(),this._d.finished.res(),this.o.onEnd?.(this.metrics)}}_release(){this.r&&(Y(this.r),this.r=null),q.get(this.target)===this&&q.delete(this.target)}_abort(t){console.warn("[distortion-wave] repli sans animation :",t),this._done=!0,this._show(),this._release(),(this._updated?Promise.resolve():this._runUpdate()).catch(()=>{}).finally(()=>{this._d.ready.res(),this._d.covered.res(),this._d.finished.res()})}};function ie(e,t,i={}){return new Q(e,t,i)}function de(e,t,i,n={}){let r=()=>Jt(t);e.addEventListener("pointerenter",r),e.addEventListener("focus",r),e.addEventListener("click",a=>ie(t,()=>i(a),{source:e,focus:a,...n}))}export{U as PALETTES,xt as PRESETS,ie as distortionWave,wt as frameState,Jt as prepareWave,vt as scaleConfig,D as snapshotElement,X as totalDuration,Gt as warmupWave,de as waveOnClick};
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "distortion-wave",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "A distortion-wave transition for any DOM element. Snapshot, update, and let one WebGL2 pass carry the old state into the new one.",
5
+ "type": "module",
6
+ "main": "./dist/distortion-wave.js",
7
+ "module": "./dist/distortion-wave.js",
8
+ "types": "./types/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./types/index.d.ts",
12
+ "import": "./dist/distortion-wave.js"
13
+ },
14
+ "./src/*": "./src/*"
15
+ },
16
+ "unpkg": "./dist/distortion-wave.iife.min.js",
17
+ "jsdelivr": "./dist/distortion-wave.iife.min.js",
18
+ "files": [
19
+ "dist",
20
+ "src",
21
+ "types",
22
+ "README.md",
23
+ "LICENSE"
24
+ ],
25
+ "sideEffects": false,
26
+ "scripts": {
27
+ "build": "node scripts/build.mjs",
28
+ "test": "node --test test/*.test.js",
29
+ "test:browser": "node scripts/browser-test.mjs",
30
+ "lint": "eslint src test scripts",
31
+ "demo": "npm run build && npx --yes serve .",
32
+ "prepublishOnly": "npm run build && npm test"
33
+ },
34
+ "keywords": [
35
+ "webgl",
36
+ "webgl2",
37
+ "transition",
38
+ "view-transitions",
39
+ "shader",
40
+ "distortion",
41
+ "animation",
42
+ "dom",
43
+ "creative-coding"
44
+ ],
45
+ "author": "Lucas Bonomi (https://lucasbonomi.com)",
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/LukyVj/distortion-wave.git"
50
+ },
51
+ "publishConfig": {
52
+ "access": "public"
53
+ },
54
+ "engines": {
55
+ "node": ">=18"
56
+ },
57
+ "devDependencies": {
58
+ "esbuild": "^0.25.0",
59
+ "eslint": "^9.0.0",
60
+ "globals": "^15.0.0",
61
+ "playwright": "^1.48.0"
62
+ }
63
+ }
package/src/capture.js ADDED
@@ -0,0 +1,295 @@
1
+ // capture.js — any DOM element → bitmap (through SVG <foreignObject>)
2
+ // Part of distortion-wave · MIT © Lucas Bonomi
3
+ //
4
+ // We clone the (composed) tree, open shadow DOM included, inlining every node's
5
+ // COMPUTED style, then serialize it into an SVG that the browser rasterizes itself
6
+ // (native fidelity: text, flex/grid, shadows, gradients, filters, inline SVG…).
7
+ // External resources (images, backgrounds, fonts) are turned into data URLs,
8
+ // otherwise they would not load inside an SVG image.
9
+ //
10
+ // Known limits: iframes (replaced by an empty box), cross-origin content without
11
+ // CORS, inner scroll positions, <use href="#…"> pointing outside the target.
12
+
13
+ const HTML_NS = 'http://www.w3.org/1999/xhtml';
14
+ const SVG_NS = 'http://www.w3.org/2000/svg';
15
+ const XLINK_NS = 'http://www.w3.org/1999/xlink';
16
+ const IGNORE_TAGS = new Set(['script', 'style', 'link', 'meta', 'template', 'noscript', 'source', 'track', 'base', 'title']);
17
+ const URL_PROPS = new Set(['background-image', 'mask-image', '-webkit-mask-image', 'border-image-source', 'list-style-image']);
18
+ const TRANSPARENT_PX = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==';
19
+ const skipProp = (p) => p.startsWith('--') || p.startsWith('transition') || p.startsWith('animation')
20
+ || p === 'cursor' || p === 'will-change' || p === 'view-transition-name' || p === 'pointer-events';
21
+
22
+ // ── Per-tag default styles, measured in a blank iframe ──────────────────────
23
+ let sandbox = null;
24
+ const defaultsCache = new Map();
25
+ function sandboxDoc() {
26
+ if (sandbox?.isConnected) return sandbox.contentDocument;
27
+ sandbox = document.createElement('iframe');
28
+ sandbox.setAttribute('aria-hidden', 'true');
29
+ sandbox.setAttribute('data-dw-ignore', '');
30
+ sandbox.tabIndex = -1;
31
+ sandbox.style.cssText = 'position:fixed;left:-99999px;top:0;width:16px;height:16px;border:0;opacity:0;pointer-events:none';
32
+ document.documentElement.appendChild(sandbox);
33
+ const d = sandbox.contentDocument;
34
+ d.open(); d.write('<!doctype html><html><head></head><body></body></html>'); d.close();
35
+ return d;
36
+ }
37
+ function defaultsFor(el) {
38
+ const svg = el.namespaceURI === SVG_NS;
39
+ const key = (svg ? 'svg:' : '') + el.localName + (el.localName === 'input' ? ':' + (el.type || 'text') : '');
40
+ let d = defaultsCache.get(key);
41
+ if (d) return d;
42
+ const doc = sandboxDoc();
43
+ const probe = svg ? doc.createElementNS(SVG_NS, el.localName) : doc.createElement(el.localName);
44
+ if (el.localName === 'input') try { probe.type = el.type; } catch {}
45
+ doc.body.appendChild(probe);
46
+ const cs = sandbox.contentWindow.getComputedStyle(probe);
47
+ d = new Map();
48
+ for (let i = 0; i < cs.length; i++) d.set(cs[i], cs.getPropertyValue(cs[i]));
49
+ probe.remove();
50
+ defaultsCache.set(key, d);
51
+ return d;
52
+ }
53
+
54
+ // Safe omission rule WITHOUT a list of inherited properties:
55
+ // a property is skipped only if its value equals the tag default AND the parent's value.
56
+ // → non-inherited: falls back to the default ✓ ; inherited: inherits from the parent ✓.
57
+ function styleText(cs, def, parentCS, isRoot, onUrl) {
58
+ let out = '';
59
+ for (let i = 0; i < cs.length; i++) {
60
+ const p = cs[i];
61
+ if (skipProp(p)) continue;
62
+ const v = cs.getPropertyValue(p);
63
+ if (!v) continue;
64
+ if (v === def.get(p) && (isRoot || !parentCS || parentCS.getPropertyValue(p) === v)) continue;
65
+ if (onUrl && URL_PROPS.has(p) && v.includes('url(')) onUrl(p, v);
66
+ out += `${p}:${v};`;
67
+ }
68
+ return out;
69
+ }
70
+
71
+ // ── Resources → data URLs (cached per URL) ──────────────────────────────────
72
+ const dataUrlCache = new Map();
73
+ export function fetchDataURL(url) {
74
+ if (url.startsWith('data:')) return Promise.resolve(url);
75
+ let p = dataUrlCache.get(url);
76
+ if (!p) {
77
+ p = fetch(url, { mode: 'cors', credentials: 'same-origin' })
78
+ .then((r) => { if (!r.ok) throw new Error(`${r.status} ${url}`); return r.blob(); })
79
+ .then((b) => new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(fr.result); fr.onerror = rej; fr.readAsDataURL(b); }));
80
+ p.catch(() => dataUrlCache.delete(url));
81
+ dataUrlCache.set(url, p);
82
+ }
83
+ return p;
84
+ }
85
+ async function inlineUrls(value) {
86
+ const urls = [...value.matchAll(/url\((['"]?)(.*?)\1\)/g)].map((m) => m[2]).filter((u) => !u.startsWith('data:'));
87
+ let out = value;
88
+ for (const u of urls) {
89
+ try { out = out.split(u).join(await fetchDataURL(new URL(u, document.baseURI).href)); } catch {}
90
+ }
91
+ return out;
92
+ }
93
+ function drawableToDataURL(el) {
94
+ if (el.localName === 'canvas') return el.toDataURL();
95
+ const c = document.createElement('canvas'); // <video>: current frame
96
+ c.width = el.videoWidth || el.clientWidth; c.height = el.videoHeight || el.clientHeight;
97
+ c.getContext('2d').drawImage(el, 0, 0, c.width, c.height);
98
+ return c.toDataURL();
99
+ }
100
+ async function inlineImage(img, url) {
101
+ try { return await fetchDataURL(url); } catch {}
102
+ try { // already-decoded, untainted image
103
+ const c = document.createElement('canvas');
104
+ c.width = img.naturalWidth; c.height = img.naturalHeight;
105
+ c.getContext('2d').drawImage(img, 0, 0);
106
+ return c.toDataURL();
107
+ } catch { return TRANSPARENT_PX; }
108
+ }
109
+
110
+ // ── Fonts: embed only the @font-face rules actually loaded ───────────────────
111
+ let fontKey = '', fontCss = null;
112
+ function embeddedFonts() {
113
+ const faces = [...document.fonts].filter((f) => f.status === 'loaded');
114
+ const key = faces.map((f) => `${f.family}|${f.style}|${f.weight}|${f.unicodeRange}`).join(';');
115
+ if (fontCss && key === fontKey) return fontCss;
116
+ fontKey = key;
117
+ fontCss = buildFontCss(faces);
118
+ return fontCss;
119
+ }
120
+ async function buildFontCss(faces) {
121
+ if (!faces.length) return '';
122
+ const norm = (s) => String(s || '').replace(/["']/g, '').replace(/\s+/g, '').toLowerCase();
123
+ const weightOk = (a, b) => { // a range ("100 900") accepts anything
124
+ const n = (x) => { x = String(x || '400').trim().toLowerCase(); return { normal: '400', bold: '700' }[x] || x; };
125
+ a = n(a); b = n(b);
126
+ return a === b || a.includes(' ') || b.includes(' ');
127
+ };
128
+ const wanted = (st) => faces.some((f) => norm(f.family) === norm(st.getPropertyValue('font-family'))
129
+ && norm(f.style) === norm(st.getPropertyValue('font-style') || 'normal')
130
+ && weightOk(f.weight, st.getPropertyValue('font-weight') || '400')
131
+ && norm(f.unicodeRange) === norm(st.getPropertyValue('unicode-range') || 'U+0-10FFFF'));
132
+ const rules = [];
133
+ const walk = (list, base) => {
134
+ for (const r of list) {
135
+ if (r instanceof CSSFontFaceRule) rules.push([r, base]);
136
+ else if (r instanceof CSSImportRule) { try { if (r.styleSheet) walk(r.styleSheet.cssRules, r.styleSheet.href || base); } catch {} }
137
+ else if (r.cssRules) walk(r.cssRules, base);
138
+ }
139
+ };
140
+ for (const sheet of [...document.styleSheets, ...(document.adoptedStyleSheets || [])]) {
141
+ const base = sheet.href || document.baseURI;
142
+ try { walk(sheet.cssRules, base); } catch {
143
+ if (!sheet.href) continue; // cross-origin sheet: re-read its text
144
+ try {
145
+ const txt = await (await fetch(sheet.href)).text();
146
+ const s = new CSSStyleSheet(); s.replaceSync(txt.replace(/@import[^;]+;/g, ''));
147
+ walk(s.cssRules, sheet.href);
148
+ } catch {}
149
+ }
150
+ }
151
+ const out = [];
152
+ for (const [r, base] of rules) {
153
+ if (!wanted(r.style)) continue;
154
+ const m = r.style.getPropertyValue('src').match(/url\((['"]?)(.*?)\1\)/);
155
+ if (!m) continue;
156
+ try {
157
+ const data = await fetchDataURL(new URL(m[2], base).href);
158
+ out.push(r.cssText.replace(/src\s*:[^;}]+;?/, `src:url("${data}");`));
159
+ } catch {}
160
+ }
161
+ return out.join('\n');
162
+ }
163
+
164
+ // ── Cloning ──────────────────────────────────────────────────────────────────
165
+ function* composedChildren(node) {
166
+ const root = node.shadowRoot || node; // open shadow DOM
167
+ for (const ch of root.childNodes) {
168
+ if (ch.nodeType === 1 && ch.localName === 'slot') {
169
+ const assigned = ch.assignedNodes({ flatten: true });
170
+ if (assigned.length) { yield* assigned; continue; }
171
+ }
172
+ yield ch;
173
+ }
174
+ }
175
+ let spanProbe = null;
176
+ function cloneTree(src, parentCS, isRoot, ctx) {
177
+ if (src.nodeType === 3) return document.createTextNode(src.data);
178
+ if (src.nodeType !== 1) return null;
179
+ const tag = src.localName;
180
+ if (IGNORE_TAGS.has(tag) || src.hasAttribute('data-dw-ignore')) return null;
181
+ const cs = getComputedStyle(src);
182
+ if (cs.display === 'none') return null;
183
+
184
+ let clone;
185
+ if (tag === 'canvas' || tag === 'video') {
186
+ clone = document.createElement('img');
187
+ try { clone.setAttribute('src', drawableToDataURL(src)); } catch { clone = document.createElement('div'); }
188
+ } else if (tag === 'iframe' || tag === 'embed' || tag === 'object') {
189
+ clone = document.createElement('div');
190
+ } else {
191
+ clone = src.cloneNode(false);
192
+ }
193
+ for (const a of [...clone.attributes]) {
194
+ const n = a.name.toLowerCase();
195
+ // keep ids: inline SVGs reference them (gradients, clipPath, <use>)
196
+ if (n.startsWith('on') || n === 'srcset' || n === 'sizes' || n === 'loading' || n === 'style' || n === 'class') clone.removeAttribute(a.name);
197
+ }
198
+ if (tag === 'img') {
199
+ const url = src.currentSrc || src.src;
200
+ clone.setAttribute('src', TRANSPARENT_PX);
201
+ if (url) ctx.tasks.push(inlineImage(src, url).then((d) => clone.setAttribute('src', d)));
202
+ }
203
+ if (src.namespaceURI === SVG_NS && tag === 'image') {
204
+ const href = src.getAttribute('href') || src.getAttributeNS(XLINK_NS, 'href');
205
+ if (href && !href.startsWith('data:')) {
206
+ clone.removeAttribute('href'); clone.removeAttributeNS(XLINK_NS, 'href');
207
+ ctx.tasks.push(fetchDataURL(new URL(href, document.baseURI).href).then((d) => clone.setAttribute('href', d)).catch(() => {}));
208
+ }
209
+ }
210
+
211
+ const urlProps = [];
212
+ let st = styleText(cs, defaultsFor(src), parentCS, isRoot, (p, v) => urlProps.push([p, v]));
213
+ if (isRoot) st += ctx.rootOverrides(cs);
214
+ st += 'transition:none;animation:none;';
215
+ clone.setAttribute('style', st);
216
+ for (const [p, v] of urlProps) ctx.tasks.push(inlineUrls(v).then((nv) => {
217
+ if (nv !== v) clone.setAttribute('style', clone.getAttribute('style').replace(`${p}:${v};`, `${p}:${nv};`));
218
+ }));
219
+
220
+ if (src.namespaceURI === HTML_NS || !src.namespaceURI) { // ::before / ::after → CSS rules
221
+ for (const ps of ['::before', '::after']) {
222
+ const pcs = getComputedStyle(src, ps);
223
+ const content = pcs.getPropertyValue('content');
224
+ if (!content || content === 'none' || content === 'normal') continue;
225
+ const cls = 'dw' + (ctx.n++).toString(36);
226
+ clone.classList.add(cls);
227
+ spanProbe ??= document.createElement('span');
228
+ ctx.css += `.${cls}${ps}{${styleText(pcs, defaultsFor(spanProbe), cs, false, null)}}`;
229
+ }
230
+ }
231
+
232
+ // form control state (properties are not attributes)
233
+ if (tag === 'input') {
234
+ if (src.type === 'checkbox' || src.type === 'radio') { if (src.checked) clone.setAttribute('checked', ''); else clone.removeAttribute('checked'); }
235
+ else if (src.type !== 'file' && src.type !== 'password') clone.setAttribute('value', src.value);
236
+ }
237
+ if (tag === 'textarea') { clone.textContent = src.value; return clone; }
238
+ if (tag === 'canvas' || tag === 'video' || tag === 'iframe' || tag === 'embed' || tag === 'object') return clone;
239
+
240
+ for (const ch of composedChildren(src)) {
241
+ const c = cloneTree(ch, cs, false, ctx);
242
+ if (c) clone.appendChild(c);
243
+ }
244
+ if (tag === 'select') [...clone.querySelectorAll('option')].forEach((o, i) => {
245
+ if (i === src.selectedIndex) o.setAttribute('selected', ''); else o.removeAttribute('selected');
246
+ });
247
+ return clone;
248
+ }
249
+
250
+ const escapeXml = (s) => s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
251
+
252
+ /**
253
+ * Rasterizes an element as it is currently rendered.
254
+ * @param {Element} el
255
+ * @param {{ dpr?: number, clip?: {x,y,w,h}, rootOpacity?: string }} o
256
+ * clip: part of the element (CSS px, relative to its top-left corner) — typically
257
+ * the part visible in the viewport, to bound the cost.
258
+ * @returns {Promise<{ canvas: HTMLCanvasElement, w: number, h: number, dpr: number, ms: number, bytes: number }>}
259
+ */
260
+ export async function snapshotElement(el, { dpr = 1, clip = null, rootOpacity = null } = {}) {
261
+ const t0 = performance.now();
262
+ await document.fonts.ready;
263
+ const r = el.getBoundingClientRect();
264
+ const w = el.offsetWidth ?? r.width, h = el.offsetHeight ?? r.height;
265
+ const c = clip ?? { x: 0, y: 0, w, h };
266
+ const ctx = {
267
+ tasks: [], css: '', n: 0,
268
+ rootOverrides: (cs) => `margin:0!important;position:relative!important;inset:auto!important;float:none!important;`
269
+ + `transform:none!important;translate:none!important;rotate:none!important;scale:none!important;`
270
+ + `box-sizing:border-box!important;width:${w}px!important;height:${h}px!important;`
271
+ + (cs.display.startsWith('inline') && cs.display !== 'inline-block' && cs.display !== 'inline-flex' && cs.display !== 'inline-grid' ? 'display:inline-block!important;' : '')
272
+ + (rootOpacity != null ? `opacity:${rootOpacity}!important;` : ''),
273
+ };
274
+ const clone = cloneTree(el, null, true, ctx);
275
+ if (!clone) throw new Error('distortion-wave: element is not rendered (display:none?)');
276
+ await Promise.all(ctx.tasks);
277
+ const fonts = await embeddedFonts();
278
+ const xml = new XMLSerializer().serializeToString(clone);
279
+ const cw = Math.max(1, Math.round(c.w)), ch = Math.max(1, Math.round(c.h));
280
+ const svg = `<svg xmlns="${SVG_NS}" width="${cw}" height="${ch}"><foreignObject x="0" y="0" width="${cw}" height="${ch}">`
281
+ + `<div xmlns="${HTML_NS}" style="position:relative;width:${cw}px;height:${ch}px;overflow:hidden;margin:0;padding:0">`
282
+ + `<style>${escapeXml(fonts + ctx.css)}</style>`
283
+ + `<div style="position:absolute;left:${-c.x}px;top:${-c.y}px;width:${w}px;height:${h}px">${xml}</div>`
284
+ + `</div></foreignObject></svg>`;
285
+ const img = new Image();
286
+ img.decoding = 'async';
287
+ img.src = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svg);
288
+ await img.decode();
289
+ const canvas = document.createElement('canvas');
290
+ canvas.width = Math.max(1, Math.round(cw * dpr)); canvas.height = Math.max(1, Math.round(ch * dpr));
291
+ const g = canvas.getContext('2d', { willReadFrequently: false });
292
+ g.drawImage(img, 0, 0, canvas.width, canvas.height);
293
+ g.getImageData(0, 0, 1, 1); // throws SecurityError if tainted → fallback
294
+ return { canvas, w: cw, h: ch, dpr, ms: performance.now() - t0, bytes: svg.length };
295
+ }