asciify-engine 4.3.0 → 4.3.1

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/README.md CHANGED
@@ -541,3 +541,7 @@ The optional `asciify-engine/studio` entry includes 18 render styles, including
541
541
  ### More motion for still images (4.3)
542
542
 
543
543
  Studio includes six still-image motions plus Off: Caustics, Slow Current, Reveal & Reform, Sheen, Tidal Rings, and Living Grain. Sheen and Living Grain preserve the grid. Motion speed and strength are adjustable. Hover adds Magnetic Pull and Scatter alongside Trail, Water, Contour, Dissolve, Silk, and Vortex. Use `hover.effect` in Studio or `effect` in the optional `/hover` module; configure radius and strength separately. Existing defaults remain unchanged.
544
+
545
+ ### Studio correctness and performance (4.3.1)
546
+
547
+ Stationary compositions are reused during animated finishing. Hidden dither settings and zero-strength motion no longer keep still previews awake. Ordered dithering uses cached thresholds with byte-identical output; Scatter caches its spatial noise. CMYK now responds to tonal hovers and still motion. For a selected PNG/JPEG frame after changing motion speed, pass `time: player.time` and `motionTime: player.motionTime` to `exportStudio`. See the packaged project-integration skill reference for a live component and independent image/video export.
package/dist/hover.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";function t(t,e,i=()=>{}){const s=Array.isArray(e)?e:[e],n=document.createElement("canvas"),a=n.getContext("2d");let o,r=!0,h=!1,l=0;const c=()=>{h||(r=!0,i())},d=new ResizeObserver(c);d.observe(t),s.forEach(t=>d.observe(t));const f=new MutationObserver(c);return s.forEach(t=>f.observe(t,{subtree:!0,childList:!0,characterData:!0,attributes:!0})),document.fonts?.addEventListener("loadingdone",c),document.fonts?.ready.then(c),window.addEventListener("resize",c),{invalidate:c,read(){if(h)return;if(!r)return o;r=!1;const e=t.getBoundingClientRect(),i=Math.min(devicePixelRatio||1,1.5,Math.sqrt(4e6/Math.max(1,e.width*e.height)));n.width=Math.max(1,Math.round(e.width*i)),n.height=Math.max(1,Math.round(e.height*i)),a.setTransform(i,0,0,i,0,0),a.fillStyle="#fff",a.textBaseline="alphabetic";for(const t of s){const i=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(let t=i.nextNode();t;t=i.nextNode()){const i=t.parentElement;if(!i)continue;const s=getComputedStyle(i);if("hidden"===s.visibility||"none"===s.display)continue;a.font=`${s.fontStyle} ${s.fontWeight} ${s.fontSize} ${s.fontFamily}`;const n=a.measureText("Hg"),o=n.fontBoundingBoxAscent??.8*parseFloat(s.fontSize),r=n.fontBoundingBoxDescent??.2*parseFloat(s.fontSize),h=t.textContent??"",l=document.createRange();let c=0;for(const i of h){if(l.setStart(t,c),c+=i.length,l.setEnd(t,c),/\s/.test(i))continue;const s=l.getBoundingClientRect();s.width&&s.height&&a.fillText(i,s.left-e.left,s.top-e.top+(s.height-o-r)/2+o)}}}return o={canvas:n,revision:++l},o},destroy(){h=!0,d.disconnect(),f.disconnect(),document.fonts?.removeEventListener("loadingdone",c),window.removeEventListener("resize",c),n.width=n.height=1,o=void 0}}}var e=[{value:"none",mode:"none",label:"Off",description:"Keep the image still. Hover stays available."},{value:"caustics",mode:"caustics",label:"Caustics",description:"Soft ribbons of light travel across a stationary character grid."},{value:"current",mode:"current",label:"Slow Current",description:"A gentle flow through the image, with steady edges."},{value:"reform",mode:"reform",label:"Reveal & Reform",description:"Disperse, gather, then hold the complete image."},{value:"sheen",mode:"sheen",label:"Sheen",description:"A broad diagonal light sweep; characters stay anchored."},{value:"tidal",mode:"tidal",label:"Tidal Rings",description:"Concentric currents travel outward, with protected edges."},{value:"grain",mode:"grain",label:"Living Grain",description:"Slow, individually phased tonal changes on a stationary grid."}];function i(t){return e.find(e=>e.value===t||e.mode===t)?.mode??"none"}var s=(t,e,i)=>{const s=(t=>Math.max(0,Math.min(1,t)))((i-t)/(e-t));return s*s*(3-2*s)},n=(t,e)=>{const i=(.06711056*t+.00583715*e)%1*52.9829189;return i-Math.floor(i)},a=class{constructor(){this.last=null,this.mode="none",this.phase=0}update(t,e,i=1){return Number.isFinite(t)?((e!==this.mode||null!==this.last&&t<this.last)&&(this.mode=e,this.phase=0,this.last=t),null!==this.last&&(this.phase+=Math.max(0,t-this.last)*Math.max(.1,Math.min(3,Number.isFinite(i)?i:1))),this.last=t,this.phase):this.phase}};function o(t,e,i,a,o){if(o[0]=o[1]=o[2]=0,o[3]=1,"none"===t)return o;const r=a*Math.PI/6,h=s(0,.12,Math.min(e,1-e,i,1-i));if("caustics"===t){const t=Math.sin(9*e+5*i+r)+Math.sin(8*i-3*e-r),s=Math.pow(Math.max(0,1-.72*Math.abs(t)),3);o[2]=(.22*s-.045)*h}else if("current"===t)o[0]=Math.sin(9*i+r)*Math.cos(7*e-r)*10*h,o[1]=Math.cos(8*e+r)*Math.sin(6*i-r)*7*h;else if("sheen"===t)o[2]=.2*Math.pow(.5+.5*Math.cos(5*e+3*i-r),8)-.025;else if("tidal"===t){const t=e-.5,s=i-.5,n=Math.hypot(t,s),a=Math.sin(22*n-r)*h;o[0]=t*a*9,o[1]=s*a*9,o[2]=.035*a}else if("grain"===t){const t=n(Math.floor(160*e),Math.floor(100*i))*Math.PI*2;o[2]=.07*Math.sin(r+t)}else if("reform"===t){const t=(a%12+12)%12,l=n(Math.floor(160*e),Math.floor(100*i)),c=.65*e+.2*i+.15*l+.08*Math.sin(8*e-5*i),d=s(1.8+1.2*c,3.8+1.2*c,t)*(1-s(5+1.7*c,7.2+1.7*c,t));o[0]=Math.sin(9*i+r)*d*(1-d)*24*h,o[1]=-d*(1-d)*(8+12*l)*h,o[2]=.45*-d,o[3]=1-.9*d}return o}function r(t,e,i){const s=Math.min(t*Math.max(1,i),(1-t)*Math.max(1,i),e*Math.max(1,1/i),(1-e)*Math.max(1,1/i)),n=Math.max(0,Math.min(1,(s-.035)/.105));return n*n*n*(n*(6*n-15)+10)}var h=class{constructor(t,e){this.edgeSafe=!1,this.previous=null,this.peak=0,this.mode="dissolve",this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*i),this.ink=new Float32Array(i),this.targetX=new Float32Array(i),this.targetY=new Float32Array(i),this.offsetX=new Float32Array(i),this.offsetY=new Float32Array(i),this.edge=new Float32Array(i);for(let i=0;i<e;i++)for(let s=0;s<t;s++)this.edge[i*t+s]=r(s/(t-1),i/(e-1),t/e);this.clear()}get active(){return this.peak>2e-4}setMode(t){t!==this.mode&&(this.mode=t,this.clear())}clear(){for(const t of[this.ink,this.targetX,this.targetY,this.offsetX,this.offsetY,this.pixels])t.fill(0);if(this.previous=null,this.peak=0,"dissolve"!==this.mode)for(let t=0;t<this.pixels.length;t+=4)this.pixels[t]=this.pixels[t+2]=128}leave(){this.previous=null}move(t,e,i){if(!Number.isFinite(t+e+i))return;t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e));const s=this.previous;this.previous={x:t,y:e};const n=this.width,a=this.height,o=Math.max(1,n/a),r=Math.max(1,a/n),h=s?(t-s.x)*o:0,l=s?(e-s.y)*r:0,c=Math.hypot(h,l);if(!s&&"dissolve"!==this.mode)return;if(s&&c<1e-4)return;const d=.035+.11*Math.max(.1,Math.min(1,i)),f=Math.min(128,Math.max(1,Math.ceil(c/(.45*d)))),m=c?h/c:1,u=c?l/c:0,g=Math.min(1,c/(d*f)*2.2);for(let i=0;i<f;i++){const h=(i+.5)/f,l=s?s.x+(t-s.x)*h:t,c=s?s.y+(e-s.y)*h:e,p=Math.max(0,Math.floor((l-2.5*d/o)*(n-1))),x=Math.min(n-1,Math.ceil((l+2.5*d/o)*(n-1))),v=Math.max(0,Math.floor((c-2.5*d/r)*(a-1))),M=Math.min(a-1,Math.ceil((c+2.5*d/r)*(a-1)));for(let t=v;t<=M;t++)for(let e=p;e<=x;e++){const i=(e/(n-1)-l)*o/d,s=(t/(a-1)-c)*r/d,h=Math.exp(1.5*-(i*i+s*s)),f=t*n+e;if("dissolve"===this.mode)this.ink[f]=Math.max(this.ink[f],h);else{const n=(-i*u+s*m)*h*2.8,a=43758.5453*Math.sin(127.1*e+311.7*t),o=(a-Math.floor(a))*Math.PI*2,r="magnetic"===this.mode?-i*h*2.5:"scatter"===this.mode?(i+.7*Math.cos(o))*h*2:"silk"===this.mode?m*n:-s*h*2.5,l="magnetic"===this.mode?-s*h*2.5:"scatter"===this.mode?(s+.7*Math.sin(o))*h*2:"silk"===this.mode?u*n:i*h*2.5;this.targetX[f]=Math.max(-1,Math.min(1,this.targetX[f]+r*g)),this.targetY[f]=Math.max(-1,Math.min(1,this.targetY[f]+l*g))}}}this.peak=1}step(t){if(!this.active||!Number.isFinite(t))return;const e=Math.max(0,Math.min(.05,t)),i=Math.exp(-("dissolve"===this.mode?2.15:3.2)*e),s=1-Math.exp(-22*e);let n=0;for(let t=0;t<this.ink.length;t++)if("dissolve"===this.mode){this.ink[t]*=i,n=Math.max(n,this.ink[t]);const e=Math.round(65535*this.ink[t]);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e}else{this.targetX[t]*=i,this.targetY[t]*=i,this.offsetX[t]+=(this.targetX[t]-this.offsetX[t])*s,this.offsetY[t]+=(this.targetY[t]-this.offsetY[t])*s,n=Math.max(n,Math.abs(this.offsetX[t]),Math.abs(this.offsetY[t]),Math.abs(this.targetX[t]),Math.abs(this.targetY[t]));const e=Math.round(this.offsetX[t]*(this.edgeSafe?this.edge[t]:1)*32767+32768),a=Math.round(this.offsetY[t]*(this.edgeSafe?this.edge[t]:1)*32767+32768);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=a>>>8,this.pixels[4*t+3]=255&a}this.peak=n,this.active||this.clear()}sample(t,e,i){const s=Math.max(0,Math.min(this.width-1.001,t*(this.width-1))),n=Math.max(0,Math.min(this.height-1.001,e*(this.height-1))),a=Math.floor(s),o=Math.floor(n),h=s-a,l=n-o,c=o*this.width+a,d=c+this.width,f=t=>(t[c]*(1-h)+t[c+1]*h)*(1-l)+(t[d]*(1-h)+t[d+1]*h)*l;i[0]=f(this.offsetX)*(this.edgeSafe?r(t,e,this.width/this.height):1)*.08,i[1]=f(this.offsetY)*(this.edgeSafe?r(t,e,this.width/this.height):1)*.08,i[2]="dissolve"===this.mode?6*-f(this.ink):0}},l=class{constructor(t,e){this.rings=[],this.previous=null,this.elapsed=1,this.echoPeak=0,this.width=t,this.height=e,this.pixels=new Uint8Array(t*e*4),this.echo=new Float32Array(t*e)}get active(){return this.rings.length>0||this.echoPeak>2e-4}clear(){this.rings=[],this.previous=null,this.elapsed=1,this.pixels.fill(0),this.echo.fill(0),this.echoPeak=0}leave(){this.previous=null}move(t,e,i){const s=Math.max(1,this.width/this.height),n=Math.max(1,this.height/this.width);this.previous&&(Math.hypot((t-this.previous.x)*s,(e-this.previous.y)*n)<.065||this.elapsed<.3)||4!==this.rings.length&&(this.previous={x:t,y:e},this.elapsed=0,this.rings.push({x:t,y:e,age:0,radius:i}))}ringSample(t,e){const i=Math.max(1,this.width/this.height),s=Math.max(1,this.height/this.width);let n=0;for(const a of this.rings){const o=.015+a.age*(.15+.22*a.radius),r=.009+.014*a.radius,h=(Math.hypot((t-a.x)*i,(e-a.y)*s)-o)/r,l=Math.sin(Math.min(1,a.age/.07)*Math.PI/2)*Math.max(0,1-a.age/1.35)**2;n=Math.max(n,Math.exp(-h*h)*l)}return n}sample(t,e){const i=Math.max(0,Math.min(this.width-1,Math.round(t*(this.width-1)))),s=Math.max(0,Math.min(this.height-1,Math.round(e*(this.height-1))));return Math.max(this.ringSample(t,e),this.echo[s*this.width+i])}step(t){if(!Number.isFinite(t))return;const e=Math.max(0,Math.min(.05,t));this.elapsed+=e;for(const t of this.rings)t.age+=e;if(this.rings=this.rings.filter(t=>t.age<1.35),!this.active)return void this.pixels.fill(0);const i=Math.exp(-5.5*e);let s=0;for(let t=0;t<this.height;t++)for(let e=0;e<this.width;e++){const n=t*this.width+e;this.echo[n]=Math.max(this.ringSample(e/(this.width-1),t/(this.height-1)),this.echo[n]*i),s=Math.max(s,this.echo[n]);const a=Math.round(65535*this.echo[n]),o=4*n;this.pixels[o]=a>>>8,this.pixels[o+1]=255&a}this.echoPeak=s,this.active||(this.echo.fill(0),this.pixels.fill(0))}},c=class{constructor(t,e){this.previous=null,this.peak=0,this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*i),this.signal=new Float32Array(i),this.offsetX=new Float32Array(i),this.offsetY=new Float32Array(i),this.driftX=new Float32Array(i),this.driftY=new Float32Array(i),this.u=new Float32Array(i),this.v=new Float32Array(i),this.ink=new Float32Array(i),this.a=new Float32Array(i),this.b=new Float32Array(i),this.c=new Float32Array(i),this.pressure=new Float32Array(i),this.pressureNext=new Float32Array(i),this.divergence=new Float32Array(i),this.curl=new Float32Array(i)}get active(){return this.peak>2e-4}clear(){for(const t of[this.u,this.v,this.ink,this.a,this.b,this.c,this.pressure,this.pressureNext,this.divergence,this.curl,this.signal,this.pixels,this.offsetX,this.offsetY,this.driftX,this.driftY])t.fill(0);this.previous=null,this.peak=0}leave(){this.previous=null}move(t,e,i,s=performance.now()){if(!Number.isFinite(t+e+i+s))return;t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e)),i=Math.max(.1,Math.min(1,i));const n=this.previous;if(this.previous={x:t,y:e,time:s},!n)return;const a=(t-n.x)*(this.width-1),o=(e-n.y)*(this.height-1),r=Math.hypot(a,o);if(r<.001)return;const h=Math.min(this.width,this.height),l=s>n.time?Math.max(1/240,(s-n.time)/1e3):1/60,c=Math.min(12,r/h/l),d=1-Math.exp(-c/2.2),f=h*(.022+.075*i)*(.65+.95*d),m=Math.min(128,Math.max(1,Math.ceil(r/(.4*f)))),u=r/f*(.32+.22*d)/m,g=a/r,p=o/r,x=-p,v=g,M=r*(2+18*d)/m;for(let i=0;i<m;i++){const s=(i+.5)/m,a=(n.x+(t-n.x)*s)*(this.width-1),o=(n.y+(e-n.y)*s)*(this.height-1),r=Math.max(1,Math.floor(a-3*f)),h=Math.min(this.width-2,Math.ceil(a+3*f)),l=Math.max(1,Math.floor(o-3*f)),c=Math.min(this.height-2,Math.ceil(o+3*f));for(let t=l;t<=c;t++)for(let e=r;e<=h;e++){const i=((e-a)*g+(t-o)*p)/f,s=((e-a)*x+(t-o)*v)/f,n=Math.exp(-i*i*.65-s*s*1.7),r=t*this.width+e;this.ink[r]=1-(1-this.ink[r])*Math.exp(-n*u);const h=1-s*s*.55,l=i*s*.7;this.u[r]=Math.max(-80,Math.min(80,this.u[r]+(g*h+x*l)*n*M)),this.v[r]=Math.max(-80,Math.min(80,this.v[r]+(p*h+v*l)*n*M))}}this.peak=1}read(t,e,i){const s=0|(e=Math.max(.5,Math.min(this.width-1.5,e))),n=0|(i=Math.max(.5,Math.min(this.height-1.5,i))),a=e-s,o=i-n,r=n*this.width+s;return(t[r]*(1-a)+t[r+1]*a)*(1-o)+(t[r+this.width]*(1-a)+t[r+this.width+1]*a)*o}sample(t,e){return this.read(this.signal,t*(this.width-1),e*(this.height-1))}displacement(t,e,i){i[0]=this.read(this.offsetX,t*(this.width-1),e*(this.height-1)),i[1]=this.read(this.offsetY,t*(this.width-1),e*(this.height-1))}step(t){if(!this.active||!Number.isFinite(t))return;let e=Math.max(0,Math.min(.05,t));const i=this.width,s=this.height;for(;e>1e-7;){const t=Math.min(1/60,e);e-=t;const n=Math.exp(-1.9*t),a=Math.exp(-3.2*t);for(let e=1;e<s-1;e++)for(let o=1;o<i-1;o++){const r=e*i+o,h=Math.max(.5,Math.min(i-1.5,o-this.u[r]*t)),l=Math.max(.5,Math.min(s-1.5,e-this.v[r]*t)),c=0|h,d=0|l,f=h-c,m=l-d,u=d*i+c,g=(1-f)*(1-m),p=f*(1-m),x=(1-f)*m,v=f*m;this.a[r]=(this.u[u]*g+this.u[u+1]*p+this.u[u+i]*x+this.u[u+i+1]*v)*n,this.b[r]=(this.v[u]*g+this.v[u+1]*p+this.v[u+i]*x+this.v[u+i+1]*v)*n,this.c[r]=(this.ink[u]*g+this.ink[u+1]*p+this.ink[u+i]*x+this.ink[u+i+1]*v)*a}[this.u,this.a]=[this.a,this.u],[this.v,this.b]=[this.b,this.v],[this.ink,this.c]=[this.c,this.ink];for(let t=1;t<s-1;t++)for(let e=1;e<i-1;e++){const s=t*i+e;this.curl[s]=.5*(this.v[s+1]-this.v[s-1]-this.u[s+i]+this.u[s-i])}for(let e=1;e<s-1;e++)for(let s=1;s<i-1;s++){const n=e*i+s,a=Math.abs(this.curl[n+1])-Math.abs(this.curl[n-1]),o=Math.abs(this.curl[n+i])-Math.abs(this.curl[n-i]),r=Math.hypot(a,o)+1e-4,h=this.curl[n]*t*10,l=Math.hypot(this.u[n],this.v[n]);this.u[n]+=o/r*h,this.v[n]-=a/r*h;const c=Math.min(1,l/(Math.hypot(this.u[n],this.v[n])+1e-6));this.u[n]*=c,this.v[n]*=c,this.divergence[n]=-.5*(this.u[n+1]-this.u[n-1]+this.v[n+i]-this.v[n-i])}this.pressure.fill(0);for(let t=0;t<4;t++){for(let t=1;t<s-1;t++)for(let e=1;e<i-1;e++){const s=t*i+e;this.pressureNext[s]=.25*(this.divergence[s]+this.pressure[s-1]+this.pressure[s+1]+this.pressure[s-i]+this.pressure[s+i])}[this.pressure,this.pressureNext]=[this.pressureNext,this.pressure]}let o=0;for(let e=1;e<s-1;e++)for(let s=1;s<i-1;s++){const n=e*i+s;this.u[n]=Math.max(-80,Math.min(80,this.u[n]-.5*(this.pressure[n+1]-this.pressure[n-1]))),this.v[n]=Math.max(-80,Math.min(80,this.v[n]-.5*(this.pressure[n+i]-this.pressure[n-i]))),this.driftX[n]+=(3.6*this.u[n]-58*this.offsetX[n]-12*this.driftX[n])*t,this.driftY[n]+=(3.6*this.v[n]-58*this.offsetY[n]-12*this.driftY[n])*t,this.offsetX[n]=Math.max(-2,Math.min(2,this.offsetX[n]+this.driftX[n]*t)),this.offsetY[n]=Math.max(-2,Math.min(2,this.offsetY[n]+this.driftY[n]*t)),this.signal[n]=1-Math.exp(.06*-Math.hypot(this.u[n],this.v[n])-.16*this.ink[n]),o=Math.max(o,this.signal[n])}this.peak=o}if(this.active)for(let t=0;t<this.ink.length;t++){const e=Math.round(65535*Math.min(1,this.signal[t]));this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=Math.round(128+63.5*this.offsetX[t]),this.pixels[4*t+3]=Math.round(128+63.5*this.offsetY[t])}else this.clear()}},d=t=>["trail","contour","dissolve"].includes(t);function f(t,e,i){const s=Math.max(0,e-1),n=1-Math.exp(3.5*-Math.max(0,i));return Math.max(0,s-Math.abs(s-(t+n*s*2.1)))}function m(t,e,i,s,n,a,o,r,h,l,c,d=!1){const m=Math.min(n,a,i-1-n,s-1-a),u=Math.max(0,Math.min(1,(m-1)/4)),g=d?u*u*(3-2*u):1;o*=g;const p=1-Math.exp(3.5*-Math.max(0,o)),x=Math.max(0,Math.min(i-1,n-r*p*.026*i*g)),v=Math.max(0,Math.min(s-1,a-h*p*.026*s*g)),M=Math.floor(x),y=Math.floor(v),w=x-M,S=v-y;c.fill(0);for(let n=0;n<2;n++)for(let a=0;a<2;a++){const o=4*(Math.min(s-1,y+n)*i+Math.min(i-1,M+a)),r=(a?w:1-w)*(n?S:1-S);c[0]+=(t[o]+256*t[o+1])*r,c[1]+=t[o+2]/255*r;for(let t=0;t<4;t++)c[t+2]+=e[o+t]*r}const b=43758.5453*Math.sin(127.1*n+311.7*a),E=b-Math.floor(b);c[0]=Math.max(0,f(c[0],l,o)-p*(1-p)*E*3)}var u=class{constructor(t=1){this.mode="water",this.amount=.55,this.radius=.2,this.lensX=.5,this.lensY=.5,this.lensStrength=0,this.previous=null,this.energy=0,this.pending=!1,this.remainder=0,t=Math.max(.25,Math.min(4,t)),this.columns=Math.round(t>=1?128:128*t),this.rows=Math.round(t>=1?128/t:128),this.xScale=Math.max(1,t),this.yScale=Math.max(1,1/t),this.heights=new Float32Array(this.columns*this.rows),this.velocity=new Float32Array(this.heights.length),this.next=new Float32Array(this.heights.length),this.normals=new Float32Array(2*this.heights.length),this.edgeMask=new Float32Array(this.heights.length);for(let e=0;e<this.rows;e++)for(let i=0;i<this.columns;i++)this.edgeMask[e*this.columns+i]=r(i/(this.columns-1),e/(this.rows-1),t);this.refraction={width:this.columns,height:this.rows,pixels:new Uint8Array(4*this.heights.length),strength:30,mode:"water",focus:[.5,.5,0,.2]},this.waterPixels=this.refraction.pixels,this.trail=new c(this.columns,this.rows),this.contour=new l(this.columns,this.rows),this.afterimage=new h(this.columns,this.rows),this.clear()}get invertsDensity(){return"trail"===this.mode}get densityTrail(){return["trail","contour","dissolve"].includes(this.mode)}get hasRefraction(){return this.active||this.lensStrength>15e-5}get active(){return["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode)?this.afterimage.active:"contour"===this.mode?this.contour.active:"trail"===this.mode?this.trail.active:this.pending||this.energy>15e-5}setMode(t){t!==this.mode&&(this.clear(),this.mode=t,this.refraction.mode=t,"dissolve"!==t&&"silk"!==t&&"vortex"!==t&&"magnetic"!==t&&"scatter"!==t||this.afterimage.setMode(t),this.refraction.pixels="trail"===t?this.trail.pixels:"contour"===t?this.contour.pixels:["dissolve","silk","vortex","magnetic","scatter"].includes(t)?this.afterimage.pixels:this.waterPixels,this.refraction.strength="water"===t?30*this.amount/.55:"silk"===t?28*this.amount:"vortex"===t?38*this.amount:"magnetic"===t||"scatter"===t?32*this.amount:0)}configure(t,e=.55,i=.2,s=!1){this.setMode(t),this.refraction.edgeSafe=s,this.afterimage.edgeSafe=s,this.amount=Number.isFinite(e)?Math.max(0,Math.min(1,e)):.55,this.radius=Number.isFinite(i)?Math.max(.1,Math.min(1,i)):.2,this.refraction.strength="water"===t?30*this.amount/.55:"silk"===t?28*this.amount:"vortex"===t?38*this.amount:"magnetic"===t||"scatter"===t?32*this.amount:0,this.refraction.focus[2]=this.densityTrail?this.amount:this.lensStrength*this.amount,this.refraction.focus[3]=this.radius}move(t,e,i){if("none"===this.mode||0===this.amount)return;if(!Number.isFinite(t+e))return;if(t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e)),["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return void this.afterimage.move(t,e,this.radius);if("contour"===this.mode)return void this.contour.move(t,e,this.radius);if("trail"===this.mode)return void this.trail.move(t,e,this.radius,i);if("water"!==this.mode)return!this.previous&&this.lensStrength<.001&&(this.lensX=t,this.lensY=e),this.previous={x:t,y:e},void(this.pending=!0);if(!this.previous)return void(this.previous={x:t,y:e});const s=this.previous;this.previous={x:t,y:e};const n=(t-s.x)*this.xScale,a=(e-s.y)*this.yScale,o=Math.hypot(n,a);if(o<1e-5)return;const r=.035+.1*this.radius,h=3*r,l=Math.min(192,Math.max(1,Math.ceil(o/(.5*r)))),c=95*Math.min(o,1)/l;for(let i=0;i<l;i++){const n=(i+.5)/l,a=s.x+(t-s.x)*n,o=s.y+(e-s.y)*n,d=Math.max(1,Math.floor((a-h/this.xScale)*(this.columns-1))),f=Math.min(this.columns-2,Math.ceil((a+h/this.xScale)*(this.columns-1))),m=Math.max(1,Math.floor((o-h/this.yScale)*(this.rows-1))),u=Math.min(this.rows-2,Math.ceil((o+h/this.yScale)*(this.rows-1)));for(let t=m;t<=u;t++)for(let e=d;e<=f;e++){const i=(e/(this.columns-1)-a)*this.xScale,s=(t/(this.rows-1)-o)*this.yScale,n=Math.exp(-(i*i+s*s)/(r*r))*c,h=t*this.columns+e;this.velocity[h]=Math.max(-12,this.velocity[h]-n)}}this.pending=!0}leave(){this.afterimage.leave(),this.contour.leave(),this.trail.leave(),this.previous=null,"water"!==this.mode&&this.lensStrength&&(this.pending=!0)}clear(){this.trail.clear(),this.contour.clear(),this.afterimage.clear(),this.heights.fill(0),this.velocity.fill(0),this.next.fill(0),this.normals.fill(0),this.refraction.focus[2]=0,this.previous=null,this.lensStrength=0,this.energy=0,this.pending=!1,this.remainder=0;for(let t=0;t<this.waterPixels.length;t+=4)this.waterPixels[t]=this.waterPixels[t+2]=128,this.waterPixels[t+1]=this.waterPixels[t+3]=0}step(t){if(!this.active||!Number.isFinite(t))return;if(["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return void this.afterimage.step(t);if("contour"===this.mode)return void this.contour.step(t);if("trail"===this.mode)return void this.trail.step(t);if("water"!==this.mode)return void this.stepLight(Math.max(0,Math.min(.05,t)));this.remainder+=Math.max(0,Math.min(.05,t));const e=1/120,i=Math.exp(-3.8*e),s=.3*Math.min(this.columns,this.rows);for(;this.remainder+1e-8>=e;){this.remainder-=e;let t=0;for(let n=1;n<this.rows-1;n++)for(let a=1;a<this.columns-1;a++){const o=n*this.columns+a,r=this.heights[o],h=this.heights[o-1]+this.heights[o+1]+this.heights[o-this.columns]+this.heights[o+this.columns]-4*r,l=Math.min(a,n,this.columns-1-a,this.rows-1-n),c=l<5?.88+.024*l:1,d=(this.velocity[o]+(h*s*s-8*r)*e)*i*c;this.velocity[o]=d,this.next[o]=(r+d*e)*c,t=Math.max(t,Math.abs(this.next[o]),.1*Math.abs(d))}this.heights.set(this.next),this.energy=t,this.pending=!1}if(!this.active)return void this.clear();const n=.18*Math.min(this.columns,this.rows);for(let t=0;t<this.rows;t++)for(let e=0;e<this.columns;e++){const i=t*this.columns+e,s=(this.heights[t*this.columns+Math.max(0,e-1)]-this.heights[t*this.columns+Math.min(this.columns-1,e+1)])*n,a=(this.heights[Math.max(0,t-1)*this.columns+e]-this.heights[Math.min(this.rows-1,t+1)*this.columns+e])*n,o=Math.tanh(s)*(this.refraction.edgeSafe?this.edgeMask[i]:1),r=Math.tanh(a)*(this.refraction.edgeSafe?this.edgeMask[i]:1);this.normals[2*i]=o,this.normals[2*i+1]=r;const h=Math.round(32767*o+32768),l=Math.round(32767*r+32768);this.refraction.pixels[4*i]=h>>>8,this.refraction.pixels[4*i+1]=255&h,this.refraction.pixels[4*i+2]=l>>>8,this.refraction.pixels[4*i+3]=255&l}}stepLight(t){const e=this.previous,i=1-Math.exp(-30*t),s=1-Math.exp(-12*t);e&&(this.lensX+=(e.x-this.lensX)*i,this.lensY+=(e.y-this.lensY)*i),this.lensStrength+=((e?1:0)-this.lensStrength)*s,this.energy=Math.max(e?Math.abs(e.x-this.lensX)+Math.abs(e.y-this.lensY):0,Math.abs((e?1:0)-this.lensStrength)),this.pending=!1,e||this.active?this.refraction.focus=[this.lensX,this.lensY,this.lensStrength*this.amount,this.radius]:this.clear()}sample(t,e,i){if(["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return this.afterimage.sample(t,e,i),i[0]*=this.amount,i[1]*=this.amount,void(i[2]*=this.amount);if("contour"===this.mode)return i[0]=i[1]=0,void(i[2]=this.contour.sample(t,e)*this.amount*3);if("trail"===this.mode)return i[0]=i[1]=0,void(i[2]=this.trail.sample(t,e)*this.amount*3);const s=Math.max(0,Math.min(this.columns-1.001,t*(this.columns-1))),n=Math.max(0,Math.min(this.rows-1.001,e*(this.rows-1))),a=Math.floor(s),o=Math.floor(n),r=s-a,h=n-o,l=2*(o*this.columns+a),c=l+2*this.columns;for(let t=0;t<2;t++)i[t]=.08*((this.normals[l+t]*(1-r)+this.normals[l+2+t]*r)*(1-h)+(this.normals[c+t]*(1-r)+this.normals[c+2+t]*r)*h);i[0]*=this.amount/.55,i[1]*=this.amount/.55,i[2]="light"===this.mode||"scan"===this.mode?function(t,e,i,s){const[n,a,o,r]=i.focus,h=(t-n)*Math.max(1,s)/r,l=(e-a)*Math.max(1,1/s)/r;return"light"===i.mode?Math.exp(-3*(h*h+l*l))*o:"scan"===i.mode?(1.3*Math.exp(-h*h*110)-.3*Math.exp(-h*h*12))*Math.exp(-l*l*1.8)*o:0}(t,e,this.refraction,this.xScale/this.yScale):0}};var g=[{value:"clean",label:"Clean",description:"Crisp characters with no optical texture."},{value:"signal",label:"Signal",description:"Broken scanlines at the edges, with a clear center."},{value:"prism",label:"Prism",description:"Soft red/cyan separation at the edges, with a crisp center."},{value:"etch",label:"Etch",description:"A fine engraved texture, keeping the ink monochrome."}];function p(t){const e=Intl.Segmenter;return e?Array.from(new e(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function x(){const t=document.createElement("canvas"),e=document.createElement("canvas"),i=t.getContext("2d"),s=e.getContext("2d",{willReadFrequently:!0}),n=function(){const t=new WeakMap;return(e,i,s="")=>{if(!e)return i;let n=t.get(e);if(!n){const i=new Set;for(const t of e)for(const e of t)e.char&&i.add(e.char);n=[...i].sort(),t.set(e,n)}const a=i+s;return i+n.filter(t=>!a.includes(t)).join("")}}();let a,o=null;return{update(r,h,l,c,d,f,m,u){const g=m?.[0]?.length??Math.max(1,Math.round(h/c)),x=m?.length??Math.max(1,Math.round(l/d)),v=u??{charset:" .:-=+*#%@",customText:"",colorMode:"fullcolor"},M=n(m,v.charset+(v.charsetFrames?.join("")??""),v.customText),y=function(t,e,i,s,n,a,o,r=4096,h=0,l=1){if(![i,s,n,a,o,r].every(t=>Number.isFinite(t)&&t>0))throw new Error("Glyph atlas dimensions must be positive and finite.");const c=JSON.stringify([e.charset,e.customText,i,s,n,a,o,r,h,l]);if(t?.key===c)return t;const d=[...new Set([" ",...p(e.charset),...p(e.customText??"")])];if(d.length>65536)throw new Error("This character set exceeds the glyph texture capacity.");const f=i/a,m=s/o,u=.9*Math.min(f/.55,m)*l,g=Math.ceil(f*n)+6,x=Math.ceil(m*n)+6,v=Math.min(d.length,Math.floor(r/g));if(v<1||Math.ceil(d.length/v)*x>r)throw new Error("The glyph atlas exceeds the available GPU texture size.");return{key:c,glyphs:d,indices:new Map(d.map((t,e)=>[t,e])),cellWidth:f,cellHeight:m,fontSize:u,padding:3,tileWidth:g,tileHeight:x,atlasColumns:v,atlasWidth:v*g,atlasHeight:Math.ceil(d.length/v)*x}}(o,{...v,charset:M},h,l,f,g,x);if(y!==o&&(t.width=y.atlasWidth,t.height=y.atlasHeight,i.font=y.fontSize*f+'px "JetBrains Mono", monospace',i.textAlign="center",i.textBaseline="middle",i.fillStyle="#fff",y.glyphs.forEach((t,e)=>i.fillText(t,e%y.atlasColumns*y.tileWidth+y.padding+y.cellWidth*f*.5,Math.floor(e/y.atlasColumns)*y.tileHeight+y.padding+y.cellHeight*f*.5))),o=y,m)a=function(t,e,i,s,n=!1){const a=t.length,o=t[0]?.length??0,r=s?.cols===o&&s.rows===a?s:{cols:o,rows:a,indices:new Uint8Array(o*a*4),colors:new Uint8ClampedArray(o*a*4)};r.indices.fill(0),r.colors.fill(0);const h="accent"===i.colorMode?[1,3,5].map(t=>parseInt(i.accentColor.slice(t,t+2),16)):null;for(let s=0;s<a;s++){if(t[s].length!==o)throw new Error("Glyph frames must have a rectangular cell grid.");for(let a=0;a<o;a++){const l=t[s][a];if(!l.char||!n&&" "===l.char||l.a<10)continue;const c=e.get(l.char);if(void 0===c)throw new Error(`Glyph is absent from the configured character set: ${l.char}`);const d=4*(s*o+a);let f=l.r,m=l.g,u=l.b;if(h)[f,m,u]=h;else if("fullcolor"!==i.colorMode){const t=.299*f+.587*m+.114*u|0;f="matrix"===i.colorMode?0:t,m=t,u=f}(n||0!==f||0!==m||0!==u)&&(r.indices[d]=255&c,r.indices[d+1]=c>>>8,r.indices[d+2]=Math.round(.299*l.r+.587*l.g+.114*l.b),r.colors[d]=f,r.colors[d+1]=m,r.colors[d+2]=u,r.colors[d+3]=l.a)}}return r}(m,o.indices,v,a,!0);else{e.width=g,e.height=x,s.drawImage(r,0,0,g,x);const t=s.getImageData(0,0,g,x).data;a={cols:g,rows:x,indices:new Uint8Array(g*x*4),colors:new Uint8ClampedArray(t)};for(let e=0;e<t.length;e+=4){const i=Math.min(1,(.299*t[e]+.587*t[e+1]+.114*t[e+2])/255*(t[e+3]/255)*3);a.indices[e+2]=Math.round(255*i),a.indices[e]=Math.min(o.glyphs.length-1,Math.floor(i*o.glyphs.length))}}return{plan:o,packed:a,atlas:t,dots:"dots"===v.renderMode,dotSize:v.dotSizeRatio??.72}},reset(){o=null},destroy(){t.width=t.height=e.width=e.height=1,a=void 0,o=null}}}function v(t){return!!t&&"ascii"===t.renderMode&&!/\p{Extended_Pictographic}/u.test(t.customText||t.charset)&&!t.charsetFrames?.length&&["fullcolor","grayscale","matrix","accent"].includes(t.colorMode)&&("accent"!==t.colorMode||/^#[\da-f]{6}$/i.test(t.accentColor))}function M(t){for(let e=t;e;e=e.parentElement){const t=getComputedStyle(e).backgroundColor.match(/[\d.]+/g)?.map(Number);if(t&&t.length>=3&&(t[3]??1)>.5)return(.299*t[0]+.587*t[1]+.114*t[2])/255<.4?[10/255,10/255,10/255,1]:[250/255,249/255,247/255,1]}const e=document.documentElement.dataset.theme;if("dark"!==e&&"light"!==e&&!t.isConnected&&document.body)return M(document.body);return"dark"===e||"light"!==e&&(document.documentElement.classList.contains("dark")||matchMedia("(prefers-color-scheme: dark)").matches)?[10/255,10/255,10/255,1]:[250/255,249/255,247/255,1]}function y(t,e,s){const r=e.style.visibility;let h=document.createElement("canvas"),l=null,c=null,u=null,p=null;const y=[],w=[];let S=!1,b=!1,E=4096,T=null,A=0,k=0,z=0,R=0,D=!1;const C=function(){const t=document.createElement("canvas"),e=t.getContext("2d");return{paint(i,s,n,a){s&&(t.width===i.canvas.width&&t.height===i.canvas.height||(t.width=i.canvas.width,t.height=i.canvas.height),e.globalCompositeOperation="copy",e.drawImage(i.canvas,0,0),e.globalCompositeOperation="destination-in",e.drawImage(s.canvas,0,0,t.width,t.height),e.globalCompositeOperation="source-in",e.fillStyle="#090909",e.fillRect(0,0,t.width,t.height),e.globalCompositeOperation="source-over",i.drawImage(t,0,0,n,a))},destroy(){t.width=t.height=1}}}();let F;const _=document.createElement("canvas"),I=document.createElement("canvas"),P=_.getContext("2d"),X=I.getContext("2d");let L=!1;const U=new a,G=x();let N=null,Y={};const B=()=>{h.className="ascii-hover-surface",h.style.cssText="position:absolute;inset:0;width:100%;height:100%;pointer-events:none",h.setAttribute("role","img"),h.setAttribute("aria-label","ASCII artwork preview"),h.dataset.renderer=l?"surface-gpu":"surface-canvas",t.prepend(h),e.style.visibility="hidden"},H=()=>{l&&(y.forEach(t=>l.deleteTexture(t)),w.forEach(t=>l.deleteShader(t)),u&&l.deleteProgram(u),p&&l.deleteBuffer(p))},W=()=>{S||c||(h.removeEventListener("webglcontextlost",W),H(),h.remove(),l=null,h=document.createElement("canvas"),c=h.getContext("2d"),B(),s())};try{if(l=h.getContext("webgl",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!0}),!l)throw new Error("WebGL unavailable");E=l.getParameter(l.MAX_TEXTURE_SIZE);const t=(t,e)=>{const i=l.createShader(t);if(w.push(i),l.shaderSource(i,e),l.compileShader(i),!l.getShaderParameter(i,l.COMPILE_STATUS))throw new Error(l.getShaderInfoLog(i)||"Shader compilation failed");return i};if(u=l.createProgram(),l.attachShader(u,t(l.VERTEX_SHADER,"attribute vec2 position; void main(){gl_Position=vec4(position,0.,1.);}")),l.attachShader(u,t(l.FRAGMENT_SHADER,"\n precision highp float;\n uniform sampler2D artwork; uniform sampler2D surface;\n uniform vec2 resolution; uniform vec2 sceneSize; uniform vec2 cellSize;\n uniform vec2 surfaceSize; uniform float strength;\n \nuniform float hoverMode;\nuniform vec4 hoverFocus;\nfloat surfaceLight(vec2 uv, vec2 size) {\n vec2 delta = (uv - hoverFocus.xy) * size / min(size.x, size.y) / hoverFocus.w;\n float light = exp(-3.0 * dot(delta, delta));\n if (hoverMode > 1.5) light = (exp(-delta.x * delta.x * 110.0) * 1.3 - exp(-delta.x * delta.x * 12.0) * .3) * exp(-delta.y * delta.y * 1.8);\n return hoverMode > .5 ? light * hoverFocus.z : 0.0;\n}\nvec3 illuminate(vec3 ink, float light) {\n return mix(ink, vec3(1.0), max(0.0, light) * .82) * (1.0 + min(0.0, light));\n}\n\n uniform sampler2D codes; uniform sampler2D cellColors; uniform sampler2D glyphAtlas;\n uniform vec2 gridSize; uniform vec2 atlasSize; uniform vec2 tileSize;\n uniform float atlasColumns; uniform float padding; uniform float glyphCount;\n uniform float dots; uniform float dotSize; uniform float directGlyphs; uniform vec4 backdrop;\n uniform float edgeEffect; uniform sampler2D textMask; uniform float hasTextMask;\n \nuniform float finishMode;\nuniform float finishPixelRatio;\nuniform float finishSoftness;\nvec3 edgeCoverage(sampler2D sheet, vec2 uv, vec2 atlas, vec2 pixel, vec2 viewport, float amount) {\n float a=texture2D(sheet,uv).a;\n if(amount<=0.0 || finishMode<.5) return vec3(a);\n vec2 radial=(pixel/viewport-.5)*2.0;\n float edge=smoothstep(.3,1.05,length(radial));\n float strength=clamp(amount,0.0,1.0)*edge;\n if(strength<=0.0) return vec3(a);\n vec2 cssPixel=pixel/max(1.0,finishPixelRatio);\n // Etch is a single texture sample: linework, not an opacity wash.\n if(finishMode>1.5 && finishMode<2.5) {\n float hatch=step(.68,fract((cssPixel.y+cssPixel.x*.22)*.5));\n return vec3(a*(1.0-hatch*strength*.64));\n }\n // Samples remain inside the current atlas tile's three-pixel padding.\n if(finishMode>2.5) {\n float band=floor(cssPixel.y/3.0);\n float phase=fract(sin(band*12.9898)*43758.5453);\n float tear=(phase-.5)*3.6*strength;\n float displaced=texture2D(sheet,uv+vec2(tear,0.0)/atlas).a;\n float cut=step(.72,fract(cssPixel.y*.5));\n return vec3(mix(a,displaced,strength)*(1.0-cut*.55*strength));\n }\n // A five-tap lens: opposed red/cyan edges with a short soft shoulder.\n // The center remains untouched; all offsets stay within three atlas texels.\n vec2 direction=normalize(vec2(radial.x-radial.y*.3,radial.y+radial.x*.3)+vec2(.00001));\n vec2 shift=direction*(2.35+.45*finishSoftness)*strength/atlas;\n float left=texture2D(sheet,uv-shift).a,right=texture2D(sheet,uv+shift).a;\n float softLeft=texture2D(sheet,uv-shift*.45).a,softRight=texture2D(sheet,uv+shift*.45).a;\n float red=left*.7+softLeft*.22+a*.08;\n float cyan=right*.7+softRight*.22+a*.08;\n // Reuse the same taps for a short optical blur and peripheral fade.\n // No whole-canvas blur, extra pass or softened HTML controls.\n float soft=a*.4+(left+right+softLeft+softRight)*.15;\n vec3 fringe=mix(vec3(red,cyan,cyan),vec3(soft),finishSoftness*.45);\n return mix(vec3(a),fringe,strength)*(1.-finishSoftness*strength*.32);\n\n}\n\n \nuniform float edgeSafe;\nuniform float trailAmount;\nuniform float trailKind;\nvec4 trailField(vec2 address) {\n return texture2D(surface,(address*(surfaceSize-1.0)+.5)/surfaceSize);\n}\nfloat trailFieldDensity(vec4 field) {\n return (field.r*65280.0+field.g*255.0)/65535.0*trailAmount;\n}\nfloat trailDensity(vec2 address) { return trailFieldDensity(trailField(address)); }\nfloat trailPin(vec2 cell, vec2 grid) {\n vec2 edge=min(cell,grid-1.0-cell);\n return mix(1.,smoothstep(1.0,5.0,min(edge.x,edge.y)),edgeSafe);\n}\nvec2 trailSource(vec2 cell, vec2 grid, vec4 field) {\n vec2 home=(cell+.5)/grid;\n if(trailKind>.5 || trailAmount<=0.) return home;\n float pin=trailPin(cell,grid);\n float response=1.-exp(-trailFieldDensity(field)*pin*3.5);\n vec2 drift=(field.ba*255.-128.)/127.;\n return clamp(home-drift*response*.026*pin,.5/grid,1.-.5/grid);\n}\nvec2 decodedGlyph(vec4 code) {\n return vec2(floor(code.r*255.+.5)+floor(code.g*255.+.5)*256.,code.b);\n}\n// Bilinear interpolation happens before glyph selection, never between bitmap\n// letters. Four small metadata reads replace the old four full-glyph gathers.\nvec2 flowGlyph(sampler2D codes,vec2 uv,vec2 grid) {\n vec2 p=uv*grid-.5,b=floor(p),f=fract(p);\n return mix(mix(decodedGlyph(texture2D(codes,(b+.5)/grid)),decodedGlyph(texture2D(codes,(b+vec2(1.5,.5))/grid)),f.x),\n mix(decodedGlyph(texture2D(codes,(b+vec2(.5,1.5))/grid)),decodedGlyph(texture2D(codes,(b+1.5)/grid)),f.x),f.y);\n}\nvec3 trailInk(vec3 ink, float density) {\n if (trailKind < .5) return ink;\n float peak=max(ink.r,max(ink.g,ink.b));\n float raised=trailKind>1.5?peak:max(peak,min(.44,density*.75));\n return peak>.001 ? ink*(raised/peak) : vec3(raised);\n}\nfloat flowIndex(float index,float count,float density) {\n float last=max(0.,count-1.);\n float response=1.-exp(-max(0.,density)*3.5);\n return max(0.,last-abs(last-(index+response*last*2.1)));\n}\nfloat trailTone(float tone, float density) {\n if (trailKind < .5) return flowIndex(tone,2.,density);\n return clamp(tone + (trailKind > 1.5 ? -density : density) * .65, 0.0, 1.0);\n}\nfloat trailIndex(float index, float count, float density, vec2 cell) {\n float grain=fract((cell.x*cell.x*17.0+cell.y*cell.y*23.0+cell.x*cell.y*19.0)*.0137);\n if(trailKind>1.5) return max(0.0,index-floor(density*count*(1.35+grain*.9)));\n if(trailKind<.5) {\n float seed=fract(sin(dot(cell,vec2(127.1,311.7)))*43758.5453);\n float response=1.-exp(-max(0.,density)*3.5);\n return floor(max(0.,flowIndex(index,count,density)-response*(1.-response)*seed*3.)+.5);\n }\n return min(count-1.0,index+floor(density*count*.85));\n}\n\n \n uniform float motionMode; uniform float motionTime; uniform float fineDither; uniform float ditherAmount;\n float printGrain(vec2 p) { return fract(52.9829189*fract(dot(p,vec2(.06711056,.00583715)))); }\n vec4 ambientAt(vec2 uv) {\n float p=motionTime*.5235987756;\n float edge=smoothstep(0.,.12,min(min(uv.x,1.-uv.x),min(uv.y,1.-uv.y)));\n if(motionMode<.5) return vec4(0.,0.,0.,1.);\n if(motionMode<1.5) {\n float fold=sin(uv.x*9.+uv.y*5.+p)+sin(uv.y*8.-uv.x*3.-p);\n float light=pow(max(0.,1.-abs(fold)*.72),3.);\n return vec4(0.,0.,(light*.22-.045)*edge,1.);\n }\n if(motionMode<2.5) return vec4(sin(uv.y*9.+p)*cos(uv.x*7.-p)*10.*edge,cos(uv.x*8.+p)*sin(uv.y*6.-p)*7.*edge,0.,1.);\n if(motionMode>5.5) return vec4(0.,0.,sin(p+printGrain(floor(uv*vec2(160.,100.)))*6.28318530718)*.07,1.);\n if(motionMode>4.5) {\n vec2 delta=uv-.5; float wave=sin(length(delta)*22.-p)*edge;\n return vec4(delta*wave*9.,wave*.035,1.);\n }\n if(motionMode>3.5) return vec4(0.,0.,pow(.5+.5*cos(uv.x*5.+uv.y*3.-p),8.)*.2-.025,1.);\n float cycle=mod(motionTime,12.);\n float grain=printGrain(floor(uv*vec2(160.,100.)));\n float order=uv.x*.65+uv.y*.2+grain*.15+sin(uv.x*8.-uv.y*5.)*.08;\n float gone=smoothstep(1.8+order*1.2,3.8+order*1.2,cycle)*(1.-smoothstep(5.+order*1.7,7.2+order*1.7,cycle));\n return vec4(sin(uv.y*9.+p)*gone*(1.-gone)*24.*edge,-gone*(1.-gone)*(8.+grain*12.)*edge,-gone*.45,1.-gone*.9);\n }\n\n float toneAt(vec2 uv) {\n vec2 p=uv*gridSize-.5, b=floor(p), f=fract(p);\n return mix(mix(texture2D(codes,(b+.5)/gridSize).b,texture2D(codes,(b+vec2(1.5,.5))/gridSize).b,f.x),mix(texture2D(codes,(b+vec2(.5,1.5))/gridSize).b,texture2D(codes,(b+1.5)/gridSize).b,f.x),f.y);\n }\n vec4 flowingCharacter(vec2 pixel, vec2 cell, vec4 ambient) {\n if(cell.x<0. || cell.y<0. || cell.x>=gridSize.x || cell.y>=gridSize.y) return vec4(0.);\n vec2 address=(cell+.5)/gridSize;\n vec4 field=trailField(address);\n vec2 local=pixel-cell*cellSize;\n if(local.x<0. || local.y<0. || local.x>=cellSize.x || local.y>=cellSize.y) return vec4(0.);\n vec2 source=trailSource(cell,gridSize,field);\n vec2 encoded=flowGlyph(codes,source,gridSize);\n vec4 color=texture2D(cellColors,source);\n float original=encoded.x;\n float density=trailFieldDensity(field)*trailPin(cell,gridSize),index=trailIndex(original,glyphCount,density,cell);\n if(original>.5) index=clamp(floor(index+ambient.z*(glyphCount-1.)+printGrain(cell)),0.,glyphCount-1.);\n if(index<.5 || color.a<.01) return vec4(0.);\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local*resolution/sceneSize)/atlasSize).a;\n if(dots>.5) {\n vec2 d=(local-cellSize*.5)/min(cellSize.x,cellSize.y);\n float r=min(.48,(index/(glyphCount-1.))*.5*dotSize);\n alpha=1.-smoothstep(r-.06,r+.02,length(d));\n }\n vec3 coverage=dots>.5?vec3(alpha):edgeCoverage(glyphAtlas,(slot*tileSize+padding+local*resolution/sceneSize)/atlasSize,atlasSize,pixel,sceneSize,edgeEffect);\n coverage*=color.a;alpha=max(coverage.r,max(coverage.g,coverage.b));\n return vec4(trailInk(color.rgb,density)*coverage,alpha);\n }\n void main(){\n vec2 uv = vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y) / resolution;\n vec2 pixel = uv * sceneSize;\n if (strength > 0.) {\n vec4 normal = texture2D(surface, (uv * (surfaceSize - 1.) + .5) / surfaceSize) * 255.;\n vec2 slope = (vec2(normal.r * 256. + normal.g, normal.b * 256. + normal.a) - 32768.) / 32767.;\n vec2 edge = min(pixel, sceneSize - pixel) / cellSize;\n uv -= slope * strength * mix(1.,smoothstep(2.,8.,min(edge.x,edge.y)),edgeSafe) / sceneSize;\n }\n vec4 ambient=ambientAt(uv);\n uv -= ambient.xy/sceneSize;\n pixel=uv*sceneSize;\n vec4 ink = vec4(0.);\n if (directGlyphs > .5) {\n vec2 cell=floor(pixel/cellSize), address=(cell+.5)/gridSize;\n vec4 encoded=texture2D(codes,address), color=texture2D(cellColors,address);\n float index=floor(encoded.r*255.+.5)+floor(encoded.g*255.+.5)*256.;\n if(index>.5 && color.a>.01) {\n vec2 local=(pixel-cell*cellSize)*resolution/sceneSize;\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local)/atlasSize).a*color.a;\n vec3 coverage=edgeCoverage(glyphAtlas,(slot*tileSize+padding+local)/atlasSize,atlasSize,pixel,sceneSize,edgeEffect)*color.a;\n ink=vec4(color.rgb*coverage,max(coverage.r,max(coverage.g,coverage.b)));\n }\n } else ink = texture2D(artwork, uv);\n if (fineDither > .5) {\n vec2 cell=floor(pixel/cellSize),address=(cell+.5)/gridSize;\n vec4 color=texture2D(cellColors,address);\n float density=trailAmount>0.?trailDensity(address):0.;\n vec2 source=trailAmount>0. && trailKind<.5?trailSource(cell,gridSize,trailField(address)):uv;\n float tone=trailTone(clamp(toneAt(source)+ambient.z,0.,1.),density*(trailKind<.5?trailPin(cell,gridSize):1.));\n float pitch=max(1.,min(cellSize.x,cellSize.y)*.32);\n float grain=.5+(printGrain(floor(pixel/pitch))-.5)*ditherAmount;\n float alpha=(tone<.001?0.:tone>.999?1.:smoothstep(grain-.035,grain+.035,tone))*color.a*ambient.w;\n // Preserve hue; tone is carried by mark coverage rather than muddy ink.\n vec3 hue=color.rgb/max(.001,max(color.r,max(color.g,color.b)));\n if(hasTextMask>.5) hue=mix(hue,vec3(.035),texture2D(textMask,vec2(gl_FragCoord.x,resolution.y-gl_FragCoord.y)/resolution).a);\n gl_FragColor=vec4(hue*alpha,alpha); return;\n }\n if ((trailAmount > 0. && trailKind > .5) || motionMode > .5) {\n vec2 cell=floor(pixel/cellSize), address=(cell+.5)/gridSize;\n float density=trailAmount>0.?trailDensity(address):0.;\n vec4 encoded=texture2D(codes,address);\n float original=floor(encoded.r*255.+.5)+floor(encoded.g*255.+.5)*256.;\n float index=trailIndex(original,glyphCount,density,cell);\n if(original>.5) index=clamp(floor(index+ambient.z*(glyphCount-1.)+printGrain(cell)),0.,glyphCount-1.);\n vec4 color=texture2D(cellColors,address);\n if((index!=original || motionMode>.5) && color.a>.01) {\n vec2 local=(pixel-cell*cellSize)*resolution/sceneSize;\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local)/atlasSize).a;\n if(dots>.5) {\n vec2 d=(pixel-(cell+.5)*cellSize)/min(cellSize.x,cellSize.y);\n float r=min(.48,(index/(glyphCount-1.))*.5*dotSize);\n alpha=1.-smoothstep(r-.06,r+.02,length(d));\n }\n // Replace the cell, rather than blending two glyphs into a ghost.\n vec3 rgb=trailInk(color.rgb,density);\n ink=vec4(rgb*alpha*color.a,alpha*color.a);\n }\n }\n if(trailAmount>0. && trailKind<.5 && trailDensity((floor(pixel/cellSize)+.5)/gridSize)>.001) {\n ink=flowingCharacter(pixel,floor(pixel/cellSize),ambient);\n }\n vec3 straightInk = ink.a > .0 ? ink.rgb / ink.a : vec3(.0);\n float value = max(straightInk.r, max(straightInk.g, straightInk.b));\n // Lighting belongs to character ink; black/transparent gaps stay empty.\n float light = surfaceLight((floor(pixel / cellSize) + .5) * cellSize / sceneSize, sceneSize);\n vec3 lit = illuminate(straightInk, light * smoothstep(0., .22, value)) * ink.a;\n if(hasTextMask>.5) lit=mix(lit,vec3(.035)*ink.a,texture2D(textMask,vec2(gl_FragCoord.x,resolution.y-gl_FragCoord.y)/resolution).a);\n vec4 result = vec4(lit, ink.a)*ambient.w;\n gl_FragColor = directGlyphs > .5 ? result + backdrop*(1.-result.a) : result;\n }")),l.linkProgram(u),!l.getProgramParameter(u,l.LINK_STATUS))throw new Error("Surface shader link failed");l.useProgram(u),p=l.createBuffer(),l.bindBuffer(l.ARRAY_BUFFER,p),l.bufferData(l.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),l.STATIC_DRAW);const e=l.getAttribLocation(u,"position");l.enableVertexAttribArray(e),l.vertexAttribPointer(e,2,l.FLOAT,!1,0,0),Y=Object.fromEntries(["finishSoftness","finishMode","finishPixelRatio","edgeEffect","textMask","hasTextMask","artwork","surface","resolution","sceneSize","cellSize","surfaceSize","strength","hoverMode","hoverFocus","codes","cellColors","glyphAtlas","gridSize","atlasSize","tileSize","atlasColumns","padding","glyphCount","dots","dotSize","directGlyphs","backdrop","trailKind","edgeSafe","trailAmount","motionMode","motionTime","fineDither","ditherAmount"].map(t=>[t,l.getUniformLocation(u,t)]));for(let t=0;t<6;t++){const e=l.createTexture();y.push(e),l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,e),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,l.LINEAR),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,l.LINEAR),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,1,1,0,l.RGBA,l.UNSIGNED_BYTE,new Uint8Array(4)),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_S,l.CLAMP_TO_EDGE),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_T,l.CLAMP_TO_EDGE)}l.uniform1i(Y.textMask,5),l.uniform1i(Y.codes,2),l.uniform1i(Y.cellColors,3),l.uniform1i(Y.glyphAtlas,4),l.uniform1i(Y.artwork,0),l.uniform1i(Y.surface,1),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),l.disable(l.BLEND),l.disable(l.DEPTH_TEST),h.addEventListener("webglcontextlost",W),B()}catch{W()}const O=()=>{S||(G.reset(),N=null,b=!1,s())};return document.fonts?.addEventListener("loadingdone",O),document.fonts?.ready.then(O),{get canvas(){return h},canRenderGlyphs:t=>!!l&&!S&&v(t),render(t,s,a,r,u,p,x,w,B=0,H={}){const O=i(w?.animationStyle),K=U.update(B,O,w?.animationSpeed),$=(t=>!0===t?.fineDither&&!t.customText&&!t.charsetFrames?.length&&!/[A-Za-z]/.test(t.charset??""))(w),q=!!l&&!!x&&v(w);if(q!==D&&(b=!1,D=q),S||!a||!r||!e.width||!e.height)return;const j=Math.min(e.width/a,1.5,Math.sqrt(4e6/(a*r))),J=Math.max(1,Math.round(a*j)),V=Math.max(1,Math.round(r*j));if(h.width===J&&h.height===V||(h.width=J,h.height=V,b=!1),l&&(e.width>E||e.height>E)&&W(),(q||d(s.mode)||"none"!==O||$)&&(t||!b||!N)&&(N=G.update(e,a,r,u,p,j,x,w),l)){const{plan:t,packed:e,atlas:i}=N;for(const[t,i]of[[2,e.indices],[3,e.colors]])l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,y[t]),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,3===t?l.LINEAR:l.NEAREST),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,3===t?l.LINEAR:l.NEAREST),A!==e.cols||k!==e.rows?l.texImage2D(l.TEXTURE_2D,0,l.RGBA,e.cols,e.rows,0,l.RGBA,l.UNSIGNED_BYTE,i):l.texSubImage2D(l.TEXTURE_2D,0,0,0,e.cols,e.rows,l.RGBA,l.UNSIGNED_BYTE,i);A=e.cols,k=e.rows,T!==t&&(l.activeTexture(l.TEXTURE4),l.bindTexture(l.TEXTURE_2D,y[4]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),T=t),l.uniform2f(Y.gridSize,e.cols,e.rows),l.uniform2f(Y.atlasSize,t.atlasWidth,t.atlasHeight),l.uniform2f(Y.tileSize,t.tileWidth,t.tileHeight),l.uniform1f(Y.atlasColumns,t.atlasColumns),l.uniform1f(Y.padding,t.padding),l.uniform1f(Y.glyphCount,t.glyphs.length),l.uniform1f(Y.dots,N.dots?1:0),l.uniform1f(Y.dotSize,N.dotSize)}if(l){l.uniform1f(Y.motionMode,(t=>["none","caustics","current","reform","sheen","tidal","grain"].indexOf(t))(O)),l.uniform1f(Y.motionTime,K),l.uniform1f(Y.fineDither,$?1:0),l.uniform1f(Y.ditherAmount,Math.max(0,Math.min(2,w?.ditherStrength??1))),l.uniform1f(Y.trailKind,"dissolve"===s.mode?2:"contour"===s.mode?1:0),l.uniform1f(Y.edgeSafe,s.edgeSafe?1:0),l.uniform1f(Y.trailAmount,d(s.mode)?s.focus[2]:0),l.viewport(0,0,J,V),q||!t&&b||(l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,y[0]),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,e),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1)),l.uniform1f(Y.directGlyphs,q?1:0),!q||!t&&b||l.uniform4fv(Y.backdrop,H.textMask||function(t){const e=t.length,i=t[0]?.length??0;for(let s=0;s<e;s+=Math.max(1,e>>2))for(let e=0;e<i;e+=Math.max(1,i>>2))if(t[s][e].a<200)return!0;return!1}(x)?[0,0,0,0]:M(e)),b=!0,l.activeTexture(l.TEXTURE1),l.bindTexture(l.TEXTURE_2D,y[1]),z!==s.width||R!==s.height?(l.texImage2D(l.TEXTURE_2D,0,l.RGBA,s.width,s.height,0,l.RGBA,l.UNSIGNED_BYTE,s.pixels),z=s.width,R=s.height):l.texSubImage2D(l.TEXTURE_2D,0,0,0,s.width,s.height,l.RGBA,l.UNSIGNED_BYTE,s.pixels),l.uniform2f(Y.resolution,J,V),l.uniform2f(Y.sceneSize,a,r),l.uniform2f(Y.cellSize,u,p),l.uniform2f(Y.surfaceSize,s.width,s.height),l.uniform1f(Y.strength,s.strength),l.uniform1f(Y.hoverMode,"light"===s.mode?1:"scan"===s.mode?2:0),l.uniform4fv(Y.hoverFocus,s.focus),H.textMask&&H.textMask!==F&&(l.activeTexture(l.TEXTURE5),l.bindTexture(l.TEXTURE_2D,y[5]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,H.textMask.canvas),F=H.textMask),l.uniform1f(Y.hasTextMask,H.textMask?1:0);const i=function(t={}){const e=g.some(e=>e.value===t.filter)?t.filter:"prism",i=Number.isFinite(t.edgeEffect)?Math.max(0,Math.min(1,t.edgeEffect)):t.filter?1:0,s=Number.isFinite(t.edgeSoftness)?Math.max(0,Math.min(1,t.edgeSoftness)):0;return{mode:["clean","prism","etch","signal"].indexOf(e),amount:"clean"===e?0:i,softness:"prism"===e?s:0}}(H);l.uniform1f(Y.edgeEffect,i.amount),l.uniform1f(Y.finishMode,i.mode),l.uniform1f(Y.finishSoftness,i.softness),l.uniform1f(Y.finishPixelRatio,1),l.drawArrays(l.TRIANGLE_STRIP,0,4)}else if(c){if(c.setTransform(j,0,0,j,0,0),c.clearRect(0,0,a,r),c.drawImage(e,0,0,a,r),("none"!==O||$)&&N){c.clearRect(0,0,a,r);const{plan:t,packed:e,atlas:i}=N,h=[0,0,0,1],l=[0,0,0,0,0,0],g=Math.max(1,.32*Math.min(u,p));for(let a=0;a<e.rows;a++)for(let r=0;r<e.cols;r++){const x=(r+.5)/e.cols,v=(a+.5)/e.rows,M=4*(a*e.cols+r);if(e.colors[M+3]<10)continue;o(O,x,v,K,h);const y=4*(Math.min(s.height-1,Math.round(v*(s.height-1)))*s.width+Math.min(s.width-1,Math.round(x*(s.width-1)))),S=d(s.mode)?(256*s.pixels[y]+s.pixels[y+1])/65535*s.focus[2]:0,b=e.indices[M]+256*e.indices[M+1];"trail"===s.mode&&m(e.indices,e.colors,e.cols,e.rows,r,a,S,(s.pixels[y+2]-128)/127,(s.pixels[y+3]-128)/127,t.glyphs.length,l,s.edgeSafe);let E="trail"===s.mode?l[0]:"dissolve"===s.mode?b-Math.floor(S*t.glyphs.length*1.8):b+Math.floor(S*t.glyphs.length*.85);b>0&&(E+=h[2]*(t.glyphs.length-1)),E=Math.max(0,Math.min(t.glyphs.length-1,Math.floor(E+n(r,a))));let T=h[0],A=h[1];s.strength>0&&(T+=(256*s.pixels[y]+s.pixels[y+1]-32768)/32767*s.strength,A+=(256*s.pixels[y+2]+s.pixels[y+3]-32768)/32767*s.strength);const k=r*u+T,z=a*p+A,R=("trail"===s.mode?l[5]:e.colors[M+3])/255*h[3],D="trail"===s.mode?l[2]:e.colors[M],C="trail"===s.mode?l[3]:e.colors[M+1],F="trail"===s.mode?l[4]:e.colors[M+2],_=$?Math.max(1,D,C,F):255,I=`rgb(${D/_*255},${C/_*255},${F/_*255})`;if(c.fillStyle=I,$){const t="trail"===s.mode?f(Math.max(0,Math.min(1,l[1]+h[2])),2,S):Math.max(0,Math.min(1,e.indices[M+2]/255+h[2]+.65*("dissolve"===s.mode?-S:S)));c.globalAlpha=R;for(let e=0;e<p;e+=g)for(let i=0;i<u;i+=g)t>.5+(n(Math.floor((k+i)/g),Math.floor((z+e)/g))-.5)*Math.max(0,Math.min(2,w?.ditherStrength??1))&&c.fillRect(k+i,z+e,Math.min(g,u-i),Math.min(g,p-e))}else if(E>0)if(c.globalAlpha=R,N.dots){const e=Math.min(.48,E/(t.glyphs.length-1)*.5*N.dotSize)*Math.min(u,p);c.beginPath(),c.arc(k+.5*u,z+.5*p,e,0,2*Math.PI),c.fill()}else c.drawImage(i,E%t.atlasColumns*t.tileWidth+t.padding,Math.floor(E/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*j,t.cellHeight*j,k,z,u,p),c.globalAlpha=1,c.globalCompositeOperation="source-atop",c.fillRect(k,z,u,p),c.globalCompositeOperation="source-over"}c.globalAlpha=1}else if(d(s.mode)&&N){const{plan:t,packed:e,atlas:i}=N,n=[0,0,0,0,0,0];for(let a=0;a<e.rows;a++)for(let o=0;o<e.cols;o++){const r=4*(Math.min(s.height-1,Math.round((a+.5)/e.rows*(s.height-1)))*s.width+Math.min(s.width-1,Math.round((o+.5)/e.cols*(s.width-1)))),h=(256*s.pixels[r]+s.pixels[r+1])/65535*s.focus[2],l=4*(a*e.cols+o),d=.0137*(o*o*17+a*a*23+o*a*19),f=d-Math.floor(d);"trail"===s.mode&&m(e.indices,e.colors,e.cols,e.rows,o,a,h,(s.pixels[r+2]-128)/127,(s.pixels[r+3]-128)/127,t.glyphs.length,n,s.edgeSafe);const g=e.indices[l]+256*e.indices[l+1],x="trail"===s.mode?Math.round(n[0]):"dissolve"===s.mode?Math.max(0,g-Math.floor(h*t.glyphs.length*(1.35+.9*f))):Math.min(t.glyphs.length-1,g+Math.floor(h*t.glyphs.length*.85));if(x===g&&("trail"!==s.mode||h<1e-4)||e.colors[l+3]<10)continue;const v=o*u,M=a*p;if(c.clearRect(v,M,u,p),c.globalAlpha=("trail"===s.mode?n[5]:e.colors[l+3])/255,N.dots){const e=Math.min(.48,x/(t.glyphs.length-1)*.5*N.dotSize)*Math.min(u,p);c.fillStyle="#fff",c.beginPath(),c.arc(v+.5*u,M+.5*p,e,0,2*Math.PI),c.fill()}else c.drawImage(i,x%t.atlasColumns*t.tileWidth+t.padding,Math.floor(x/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*j,t.cellHeight*j,v,M,u,p);c.globalAlpha=1,c.globalCompositeOperation="source-atop";const y="trail"===s.mode?n[2]:e.colors[l],w="trail"===s.mode?n[3]:e.colors[l+1],S="trail"===s.mode?n[4]:e.colors[l+2],b=Math.max(y,w,S),E="dissolve"===s.mode||"trail"===s.mode?b:Math.max(b,Math.min(112,191*h)),T=b?E/b:0;c.fillStyle=`rgb(${b?y*T:E},${b?w*T:E},${b?S*T:E})`,c.fillRect(v,M,u,p),c.globalCompositeOperation="source-over"}}else if("light"===s.mode||"scan"===s.mode){!t&&L&&_.width===J&&_.height===V||(_.width=J,_.height=V,I.width=J,I.height=V,P.filter="brightness(3)",P.drawImage(e,0,0,J,V),P.filter="none",L=!0);const[i,n,o,h]=s.focus;if(o>.001){X.setTransform(1,0,0,1,0,0),X.clearRect(0,0,J,V),X.drawImage(_,0,0),X.globalCompositeOperation="destination-in",X.save(),X.translate(i*J,n*V),X.scale("scan"===s.mode?.13:1,1);const t=h*Math.min(J,V),e=X.createRadialGradient(0,0,0,0,0,t);e.addColorStop(0,`rgba(255,255,255,${o})`),e.addColorStop(.5,`rgba(255,255,255,${.4*o})`),e.addColorStop(1,"rgba(255,255,255,0)"),X.fillStyle=e,X.fillRect(10*-J,10*-V,20*J,20*V),X.restore(),X.globalCompositeOperation="source-over",c.globalCompositeOperation="source-atop",c.drawImage(I,0,0,a,r),c.globalCompositeOperation="source-over"}}else if(s.strength>0){const t=32,i=18,n=a/t,o=r/i;for(let h=0;h<i;h++)for(let l=0;l<t;l++){const d=(l+.5)/t,f=(h+.5)/i,m=Math.min(s.width-1,Math.round(d*(s.width-1))),g=4*(Math.min(s.height-1,Math.round(f*(s.height-1)))*s.width+m),x=s.pixels,v=Math.min(l*n/u,(a-(l+1)*n)/u,h*o/p,(r-(h+1)*o)/p),M=Math.max(0,Math.min(1,(v-2)/6)),y=s.edgeSafe?M*M*(3-2*M):1,w=(256*x[g]+x[g+1]-32768)/32767*s.strength*y,S=(256*x[g+2]+x[g+3]-32768)/32767*s.strength*y;Math.abs(w)+Math.abs(S)<.02||(c.clearRect(l*n,h*o,n,o),c.drawImage(e,Math.max(0,Math.min(a-n,l*n-w))*e.width/a,Math.max(0,Math.min(r-o,h*o-S))*e.height/r,n*e.width/a,o*e.height/r,l*n,h*o,n,o))}}C.paint(c,H.textMask,a,r)}},destroy(){S=!0,C.destroy(),document.fonts?.removeEventListener("loadingdone",O),G.destroy(),h.removeEventListener("webglcontextlost",W),H(),h.remove(),_.width=_.height=I.width=I.height=1,e.style.visibility=r,l?.getExtension("WEBGL_lose_context")?.loseContext()}}}function w(e,s,n={}){let a,o,r={effect:"trail",strength:.55,radius:n.effect&&"trail"!==n.effect?.2:.45,animated:!1,fps:30,fontSize:7,charAspect:.58,charSpacing:1,paused:!1,charset:" .:-=+*#%@",customText:"",renderMode:"ascii",dotSizeRatio:.72,...n},h=new u(s.clientWidth/Math.max(1,s.clientHeight)),l=!1,c=!1,d=!0,f=0,m=0,g=-1/0,p=0,x=null,v=!1,M=[];const w=matchMedia("(prefers-reduced-motion: reduce)"),S=()=>{l||!c||document.hidden||f||(f=requestAnimationFrame(E))},b=y(e,s,()=>queueMicrotask(()=>{d=!0,S()}));function E(t){if(f=0,l||!c||document.hidden)return;const e=s.clientWidth,n=s.clientHeight;if(!e||!n)return;const u=w.matches||r.paused;h.configure(r.effect,r.strength,r.radius,r.edgeSafe);const y=h.active;if(!u){if(p+=m?Math.min(.1,(t-m)/1e3):0,v){const t=s.getBoundingClientRect();if(t.width&&t.height)for(const e of M)h.move((e.x-t.left)/t.width,(e.y-t.top)/t.height,e.time);M=[],x||h.leave(),v=!1}h.step(m?(t-m)/1e3:1/60)}const E=d||r.animated&&!u&&t-g>=1e3/Math.max(1,Math.min(60,r.fps))-.5;(E||y||h.active||!u&&"none"!==i(r.motion))&&(E&&(a=r.getFrame?.()),b.render(E,h.refraction,e,n,r.fontSize*r.charSpacing,r.fontSize/r.charAspect*r.charSpacing,a,{charset:r.charset,customText:r.customText,renderMode:r.renderMode,dotSizeRatio:r.dotSizeRatio,colorMode:r.colorMode??"fullcolor",accentColor:r.accentColor??"#888888",animationStyle:r.motion,animationSpeed:r.motionSpeed??1,artStyle:"classic",fineDither:r.fineDither,ditherStrength:r.ditherStrength??1},p,{filter:r.filter,edgeEffect:r.edgeEffect,edgeSoftness:r.edgeSoftness,textMask:o?.read()}),E&&(g=t),d=!1),m=t,u||!r.animated&&!h.active&&"none"===i(r.motion)||S()}r.textMask&&(o=t(e,r.textMask,()=>{d=!0,S()}));const T=()=>{h.clear(),x=null,M=[],v=!1,m=0,d=!0,cancelAnimationFrame(f),f=0,S()},A=t=>{if("touch"===t.pointerType||r.paused||w.matches)return;if(t.target instanceof Element&&t.target.closest("button,a,input,summary,select"))return void k();const e=s.getBoundingClientRect();t.clientX<e.left||t.clientX>e.right||t.clientY<e.top||t.clientY>e.bottom?k():(x={x:t.clientX,y:t.clientY,time:t.timeStamp},M.push(x),M.length>6&&M.shift(),v=!0,S())},k=()=>{x=null,v=!0,S()},z=new IntersectionObserver(([t])=>{c=t.isIntersecting,T()});z.observe(e);const R=new ResizeObserver(()=>{h=new u(s.clientWidth/Math.max(1,s.clientHeight)),T()});return R.observe(s),e.addEventListener("pointermove",A),e.addEventListener("pointerleave",k),e.addEventListener("pointercancel",k),document.addEventListener("visibilitychange",T),w.addEventListener("change",T),{get canvas(){return b.canvas},invalidate(){o?.invalidate(),d=!0,S()},update(i){"textMask"in i&&(o?.destroy(),o=i.textMask?t(e,i.textMask,()=>{d=!0,S()}):void 0),r={...r,...i},T()},destroy(){l||(l=!0,cancelAnimationFrame(f),z.disconnect(),R.disconnect(),b.destroy(),o?.destroy(),e.removeEventListener("pointermove",A),e.removeEventListener("pointerleave",k),e.removeEventListener("pointercancel",k),document.removeEventListener("visibilitychange",T),w.removeEventListener("change",T))}}}exports.createTextMask=t,exports.mountHover=w,exports.mountStudioHover=w;//# sourceMappingURL=hover.cjs.map
1
+ "use strict";function t(t,e,i=()=>{}){const s=Array.isArray(e)?e:[e],n=document.createElement("canvas"),a=n.getContext("2d");let o,r=!0,h=!1,l=0;const c=()=>{h||(r=!0,i())},d=new ResizeObserver(c);d.observe(t),s.forEach(t=>d.observe(t));const f=new MutationObserver(c);return s.forEach(t=>f.observe(t,{subtree:!0,childList:!0,characterData:!0,attributes:!0})),document.fonts?.addEventListener("loadingdone",c),document.fonts?.ready.then(c),window.addEventListener("resize",c),{invalidate:c,read(){if(h)return;if(!r)return o;r=!1;const e=t.getBoundingClientRect(),i=Math.min(devicePixelRatio||1,1.5,Math.sqrt(4e6/Math.max(1,e.width*e.height)));n.width=Math.max(1,Math.round(e.width*i)),n.height=Math.max(1,Math.round(e.height*i)),a.setTransform(i,0,0,i,0,0),a.fillStyle="#fff",a.textBaseline="alphabetic";for(const t of s){const i=document.createTreeWalker(t,NodeFilter.SHOW_TEXT);for(let t=i.nextNode();t;t=i.nextNode()){const i=t.parentElement;if(!i)continue;const s=getComputedStyle(i);if("hidden"===s.visibility||"none"===s.display)continue;a.font=`${s.fontStyle} ${s.fontWeight} ${s.fontSize} ${s.fontFamily}`;const n=a.measureText("Hg"),o=n.fontBoundingBoxAscent??.8*parseFloat(s.fontSize),r=n.fontBoundingBoxDescent??.2*parseFloat(s.fontSize),h=t.textContent??"",l=document.createRange();let c=0;for(const i of h){if(l.setStart(t,c),c+=i.length,l.setEnd(t,c),/\s/.test(i))continue;const s=l.getBoundingClientRect();s.width&&s.height&&a.fillText(i,s.left-e.left,s.top-e.top+(s.height-o-r)/2+o)}}}return o={canvas:n,revision:++l},o},destroy(){h=!0,d.disconnect(),f.disconnect(),document.fonts?.removeEventListener("loadingdone",c),window.removeEventListener("resize",c),n.width=n.height=1,o=void 0}}}var e=[{value:"none",mode:"none",label:"Off",description:"Keep the image still. Hover stays available."},{value:"caustics",mode:"caustics",label:"Caustics",description:"Soft ribbons of light travel across a stationary character grid."},{value:"current",mode:"current",label:"Slow Current",description:"A gentle flow through the image, with steady edges."},{value:"reform",mode:"reform",label:"Reveal & Reform",description:"Disperse, gather, then hold the complete image."},{value:"sheen",mode:"sheen",label:"Sheen",description:"A broad diagonal light sweep; characters stay anchored."},{value:"tidal",mode:"tidal",label:"Tidal Rings",description:"Concentric currents travel outward, with protected edges."},{value:"grain",mode:"grain",label:"Living Grain",description:"Slow, individually phased tonal changes on a stationary grid."}];function i(t){return e.find(e=>e.value===t||e.mode===t)?.mode??"none"}var s=(t,e,i)=>{const s=(t=>Math.max(0,Math.min(1,t)))((i-t)/(e-t));return s*s*(3-2*s)},n=(t,e)=>{const i=(.06711056*t+.00583715*e)%1*52.9829189;return i-Math.floor(i)},a=class{constructor(){this.last=null,this.mode="none",this.phase=0}update(t,e,i=1){return Number.isFinite(t)?((e!==this.mode||null!==this.last&&t<this.last)&&(this.mode=e,this.phase=0,this.last=t),null!==this.last&&(this.phase+=Math.max(0,t-this.last)*Math.max(.1,Math.min(3,Number.isFinite(i)?i:1))),this.last=t,this.phase):this.phase}};function o(t,e,i,a,o){if(o[0]=o[1]=o[2]=0,o[3]=1,"none"===t)return o;const r=a*Math.PI/6,h=s(0,.12,Math.min(e,1-e,i,1-i));if("caustics"===t){const t=Math.sin(9*e+5*i+r)+Math.sin(8*i-3*e-r),s=Math.pow(Math.max(0,1-.72*Math.abs(t)),3);o[2]=(.22*s-.045)*h}else if("current"===t)o[0]=Math.sin(9*i+r)*Math.cos(7*e-r)*10*h,o[1]=Math.cos(8*e+r)*Math.sin(6*i-r)*7*h;else if("sheen"===t)o[2]=.2*Math.pow(.5+.5*Math.cos(5*e+3*i-r),8)-.025;else if("tidal"===t){const t=e-.5,s=i-.5,n=Math.hypot(t,s),a=Math.sin(22*n-r)*h;o[0]=t*a*9,o[1]=s*a*9,o[2]=.035*a}else if("grain"===t){const t=n(Math.floor(160*e),Math.floor(100*i))*Math.PI*2;o[2]=.07*Math.sin(r+t)}else if("reform"===t){const t=(a%12+12)%12,l=n(Math.floor(160*e),Math.floor(100*i)),c=.65*e+.2*i+.15*l+.08*Math.sin(8*e-5*i),d=s(1.8+1.2*c,3.8+1.2*c,t)*(1-s(5+1.7*c,7.2+1.7*c,t));o[0]=Math.sin(9*i+r)*d*(1-d)*24*h,o[1]=-d*(1-d)*(8+12*l)*h,o[2]=.45*-d,o[3]=1-.9*d}return o}function r(t,e,i){const s=Math.min(t*Math.max(1,i),(1-t)*Math.max(1,i),e*Math.max(1,1/i),(1-e)*Math.max(1,1/i)),n=Math.max(0,Math.min(1,(s-.035)/.105));return n*n*n*(n*(6*n-15)+10)}var h=class{constructor(t,e){this.edgeSafe=!1,this.previous=null,this.peak=0,this.scatterDirections=null,this.mode="dissolve",this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*i),this.ink=new Float32Array(i),this.targetX=new Float32Array(i),this.targetY=new Float32Array(i),this.offsetX=new Float32Array(i),this.offsetY=new Float32Array(i),this.edge=new Float32Array(i);for(let i=0;i<e;i++)for(let s=0;s<t;s++)this.edge[i*t+s]=r(s/(t-1),i/(e-1),t/e);this.clear()}get active(){return this.peak>2e-4}setMode(t){if("scatter"===t&&!this.scatterDirections){const t=this.scatterDirections=new Float64Array(this.width*this.height*2);for(let e=0;e<this.height;e++)for(let i=0;i<this.width;i++){const s=43758.5453*Math.sin(127.1*i+311.7*e),n=(s-Math.floor(s))*Math.PI*2,a=2*(e*this.width+i);t[a]=.7*Math.cos(n),t[a+1]=.7*Math.sin(n)}}t!==this.mode&&(this.mode=t,this.clear())}clear(){for(const t of[this.ink,this.targetX,this.targetY,this.offsetX,this.offsetY,this.pixels])t.fill(0);if(this.previous=null,this.peak=0,"dissolve"!==this.mode)for(let t=0;t<this.pixels.length;t+=4)this.pixels[t]=this.pixels[t+2]=128}leave(){this.previous=null}move(t,e,i){if(!Number.isFinite(t+e+i))return;t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e));const s=this.previous;this.previous={x:t,y:e};const n=this.width,a=this.height,o=Math.max(1,n/a),r=Math.max(1,a/n),h=s?(t-s.x)*o:0,l=s?(e-s.y)*r:0,c=Math.hypot(h,l);if(!s&&"dissolve"!==this.mode)return;if(s&&c<1e-4)return;const d=.035+.11*Math.max(.1,Math.min(1,i)),f=Math.min(128,Math.max(1,Math.ceil(c/(.45*d)))),m=c?h/c:1,u=c?l/c:0,g=Math.min(1,c/(d*f)*2.2);for(let i=0;i<f;i++){const h=(i+.5)/f,l=s?s.x+(t-s.x)*h:t,c=s?s.y+(e-s.y)*h:e,p=Math.max(0,Math.floor((l-2.5*d/o)*(n-1))),x=Math.min(n-1,Math.ceil((l+2.5*d/o)*(n-1))),v=Math.max(0,Math.floor((c-2.5*d/r)*(a-1))),M=Math.min(a-1,Math.ceil((c+2.5*d/r)*(a-1)));for(let t=v;t<=M;t++)for(let e=p;e<=x;e++){const i=(e/(n-1)-l)*o/d,s=(t/(a-1)-c)*r/d,h=Math.exp(1.5*-(i*i+s*s)),f=t*n+e;if("dissolve"===this.mode)this.ink[f]=Math.max(this.ink[f],h);else{const t=(-i*u+s*m)*h*2.8,e="magnetic"===this.mode?-i*h*2.5:"scatter"===this.mode?(i+this.scatterDirections[2*f])*h*2:"silk"===this.mode?m*t:-s*h*2.5,n="magnetic"===this.mode?-s*h*2.5:"scatter"===this.mode?(s+this.scatterDirections[2*f+1])*h*2:"silk"===this.mode?u*t:i*h*2.5;this.targetX[f]=Math.max(-1,Math.min(1,this.targetX[f]+e*g)),this.targetY[f]=Math.max(-1,Math.min(1,this.targetY[f]+n*g))}}}this.peak=1}step(t){if(!this.active||!Number.isFinite(t))return;const e=Math.max(0,Math.min(.05,t)),i=Math.exp(-("dissolve"===this.mode?2.15:3.2)*e),s=1-Math.exp(-22*e);let n=0;for(let t=0;t<this.ink.length;t++)if("dissolve"===this.mode){this.ink[t]*=i,n=Math.max(n,this.ink[t]);const e=Math.round(65535*this.ink[t]);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e}else{this.targetX[t]*=i,this.targetY[t]*=i,this.offsetX[t]+=(this.targetX[t]-this.offsetX[t])*s,this.offsetY[t]+=(this.targetY[t]-this.offsetY[t])*s,n=Math.max(n,Math.abs(this.offsetX[t]),Math.abs(this.offsetY[t]),Math.abs(this.targetX[t]),Math.abs(this.targetY[t]));const e=Math.round(this.offsetX[t]*(this.edgeSafe?this.edge[t]:1)*32767+32768),a=Math.round(this.offsetY[t]*(this.edgeSafe?this.edge[t]:1)*32767+32768);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=a>>>8,this.pixels[4*t+3]=255&a}this.peak=n,this.active||this.clear()}sample(t,e,i){const s=Math.max(0,Math.min(this.width-1.001,t*(this.width-1))),n=Math.max(0,Math.min(this.height-1.001,e*(this.height-1))),a=Math.floor(s),o=Math.floor(n),h=s-a,l=n-o,c=o*this.width+a,d=c+this.width,f=t=>(t[c]*(1-h)+t[c+1]*h)*(1-l)+(t[d]*(1-h)+t[d+1]*h)*l;i[0]=f(this.offsetX)*(this.edgeSafe?r(t,e,this.width/this.height):1)*.08,i[1]=f(this.offsetY)*(this.edgeSafe?r(t,e,this.width/this.height):1)*.08,i[2]="dissolve"===this.mode?6*-f(this.ink):0}},l=class{constructor(t,e){this.rings=[],this.previous=null,this.elapsed=1,this.echoPeak=0,this.width=t,this.height=e,this.pixels=new Uint8Array(t*e*4),this.echo=new Float32Array(t*e)}get active(){return this.rings.length>0||this.echoPeak>2e-4}clear(){this.rings=[],this.previous=null,this.elapsed=1,this.pixels.fill(0),this.echo.fill(0),this.echoPeak=0}leave(){this.previous=null}move(t,e,i){const s=Math.max(1,this.width/this.height),n=Math.max(1,this.height/this.width);this.previous&&(Math.hypot((t-this.previous.x)*s,(e-this.previous.y)*n)<.065||this.elapsed<.3)||4!==this.rings.length&&(this.previous={x:t,y:e},this.elapsed=0,this.rings.push({x:t,y:e,age:0,radius:i}))}ringSample(t,e){const i=Math.max(1,this.width/this.height),s=Math.max(1,this.height/this.width);let n=0;for(const a of this.rings){const o=.015+a.age*(.15+.22*a.radius),r=.009+.014*a.radius,h=(Math.hypot((t-a.x)*i,(e-a.y)*s)-o)/r,l=Math.sin(Math.min(1,a.age/.07)*Math.PI/2)*Math.max(0,1-a.age/1.35)**2;n=Math.max(n,Math.exp(-h*h)*l)}return n}sample(t,e){const i=Math.max(0,Math.min(this.width-1,Math.round(t*(this.width-1)))),s=Math.max(0,Math.min(this.height-1,Math.round(e*(this.height-1))));return Math.max(this.ringSample(t,e),this.echo[s*this.width+i])}step(t){if(!Number.isFinite(t))return;const e=Math.max(0,Math.min(.05,t));this.elapsed+=e;for(const t of this.rings)t.age+=e;if(this.rings=this.rings.filter(t=>t.age<1.35),!this.active)return void this.pixels.fill(0);const i=Math.exp(-5.5*e);let s=0;for(let t=0;t<this.height;t++)for(let e=0;e<this.width;e++){const n=t*this.width+e;this.echo[n]=Math.max(this.ringSample(e/(this.width-1),t/(this.height-1)),this.echo[n]*i),s=Math.max(s,this.echo[n]);const a=Math.round(65535*this.echo[n]),o=4*n;this.pixels[o]=a>>>8,this.pixels[o+1]=255&a}this.echoPeak=s,this.active||(this.echo.fill(0),this.pixels.fill(0))}},c=class{constructor(t,e){this.previous=null,this.peak=0,this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*i),this.signal=new Float32Array(i),this.offsetX=new Float32Array(i),this.offsetY=new Float32Array(i),this.driftX=new Float32Array(i),this.driftY=new Float32Array(i),this.u=new Float32Array(i),this.v=new Float32Array(i),this.ink=new Float32Array(i),this.a=new Float32Array(i),this.b=new Float32Array(i),this.c=new Float32Array(i),this.pressure=new Float32Array(i),this.pressureNext=new Float32Array(i),this.divergence=new Float32Array(i),this.curl=new Float32Array(i)}get active(){return this.peak>2e-4}clear(){for(const t of[this.u,this.v,this.ink,this.a,this.b,this.c,this.pressure,this.pressureNext,this.divergence,this.curl,this.signal,this.pixels,this.offsetX,this.offsetY,this.driftX,this.driftY])t.fill(0);this.previous=null,this.peak=0}leave(){this.previous=null}move(t,e,i,s=performance.now()){if(!Number.isFinite(t+e+i+s))return;t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e)),i=Math.max(.1,Math.min(1,i));const n=this.previous;if(this.previous={x:t,y:e,time:s},!n)return;const a=(t-n.x)*(this.width-1),o=(e-n.y)*(this.height-1),r=Math.hypot(a,o);if(r<.001)return;const h=Math.min(this.width,this.height),l=s>n.time?Math.max(1/240,(s-n.time)/1e3):1/60,c=Math.min(12,r/h/l),d=1-Math.exp(-c/2.2),f=h*(.022+.075*i)*(.65+.95*d),m=Math.min(128,Math.max(1,Math.ceil(r/(.4*f)))),u=r/f*(.32+.22*d)/m,g=a/r,p=o/r,x=-p,v=g,M=r*(2+18*d)/m;for(let i=0;i<m;i++){const s=(i+.5)/m,a=(n.x+(t-n.x)*s)*(this.width-1),o=(n.y+(e-n.y)*s)*(this.height-1),r=Math.max(1,Math.floor(a-3*f)),h=Math.min(this.width-2,Math.ceil(a+3*f)),l=Math.max(1,Math.floor(o-3*f)),c=Math.min(this.height-2,Math.ceil(o+3*f));for(let t=l;t<=c;t++)for(let e=r;e<=h;e++){const i=((e-a)*g+(t-o)*p)/f,s=((e-a)*x+(t-o)*v)/f,n=Math.exp(-i*i*.65-s*s*1.7),r=t*this.width+e;this.ink[r]=1-(1-this.ink[r])*Math.exp(-n*u);const h=1-s*s*.55,l=i*s*.7;this.u[r]=Math.max(-80,Math.min(80,this.u[r]+(g*h+x*l)*n*M)),this.v[r]=Math.max(-80,Math.min(80,this.v[r]+(p*h+v*l)*n*M))}}this.peak=1}read(t,e,i){const s=0|(e=Math.max(.5,Math.min(this.width-1.5,e))),n=0|(i=Math.max(.5,Math.min(this.height-1.5,i))),a=e-s,o=i-n,r=n*this.width+s;return(t[r]*(1-a)+t[r+1]*a)*(1-o)+(t[r+this.width]*(1-a)+t[r+this.width+1]*a)*o}sample(t,e){return this.read(this.signal,t*(this.width-1),e*(this.height-1))}displacement(t,e,i){i[0]=this.read(this.offsetX,t*(this.width-1),e*(this.height-1)),i[1]=this.read(this.offsetY,t*(this.width-1),e*(this.height-1))}step(t){if(!this.active||!Number.isFinite(t))return;let e=Math.max(0,Math.min(.05,t));const i=this.width,s=this.height;for(;e>1e-7;){const t=Math.min(1/60,e);e-=t;const n=Math.exp(-1.9*t),a=Math.exp(-3.2*t);for(let e=1;e<s-1;e++)for(let o=1;o<i-1;o++){const r=e*i+o,h=Math.max(.5,Math.min(i-1.5,o-this.u[r]*t)),l=Math.max(.5,Math.min(s-1.5,e-this.v[r]*t)),c=0|h,d=0|l,f=h-c,m=l-d,u=d*i+c,g=(1-f)*(1-m),p=f*(1-m),x=(1-f)*m,v=f*m;this.a[r]=(this.u[u]*g+this.u[u+1]*p+this.u[u+i]*x+this.u[u+i+1]*v)*n,this.b[r]=(this.v[u]*g+this.v[u+1]*p+this.v[u+i]*x+this.v[u+i+1]*v)*n,this.c[r]=(this.ink[u]*g+this.ink[u+1]*p+this.ink[u+i]*x+this.ink[u+i+1]*v)*a}[this.u,this.a]=[this.a,this.u],[this.v,this.b]=[this.b,this.v],[this.ink,this.c]=[this.c,this.ink];for(let t=1;t<s-1;t++)for(let e=1;e<i-1;e++){const s=t*i+e;this.curl[s]=.5*(this.v[s+1]-this.v[s-1]-this.u[s+i]+this.u[s-i])}for(let e=1;e<s-1;e++)for(let s=1;s<i-1;s++){const n=e*i+s,a=Math.abs(this.curl[n+1])-Math.abs(this.curl[n-1]),o=Math.abs(this.curl[n+i])-Math.abs(this.curl[n-i]),r=Math.hypot(a,o)+1e-4,h=this.curl[n]*t*10,l=Math.hypot(this.u[n],this.v[n]);this.u[n]+=o/r*h,this.v[n]-=a/r*h;const c=Math.min(1,l/(Math.hypot(this.u[n],this.v[n])+1e-6));this.u[n]*=c,this.v[n]*=c,this.divergence[n]=-.5*(this.u[n+1]-this.u[n-1]+this.v[n+i]-this.v[n-i])}this.pressure.fill(0);for(let t=0;t<4;t++){for(let t=1;t<s-1;t++)for(let e=1;e<i-1;e++){const s=t*i+e;this.pressureNext[s]=.25*(this.divergence[s]+this.pressure[s-1]+this.pressure[s+1]+this.pressure[s-i]+this.pressure[s+i])}[this.pressure,this.pressureNext]=[this.pressureNext,this.pressure]}let o=0;for(let e=1;e<s-1;e++)for(let s=1;s<i-1;s++){const n=e*i+s;this.u[n]=Math.max(-80,Math.min(80,this.u[n]-.5*(this.pressure[n+1]-this.pressure[n-1]))),this.v[n]=Math.max(-80,Math.min(80,this.v[n]-.5*(this.pressure[n+i]-this.pressure[n-i]))),this.driftX[n]+=(3.6*this.u[n]-58*this.offsetX[n]-12*this.driftX[n])*t,this.driftY[n]+=(3.6*this.v[n]-58*this.offsetY[n]-12*this.driftY[n])*t,this.offsetX[n]=Math.max(-2,Math.min(2,this.offsetX[n]+this.driftX[n]*t)),this.offsetY[n]=Math.max(-2,Math.min(2,this.offsetY[n]+this.driftY[n]*t)),this.signal[n]=1-Math.exp(.06*-Math.hypot(this.u[n],this.v[n])-.16*this.ink[n]),o=Math.max(o,this.signal[n])}this.peak=o}if(this.active)for(let t=0;t<this.ink.length;t++){const e=Math.round(65535*Math.min(1,this.signal[t]));this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=Math.round(128+63.5*this.offsetX[t]),this.pixels[4*t+3]=Math.round(128+63.5*this.offsetY[t])}else this.clear()}},d=t=>["trail","contour","dissolve"].includes(t);function f(t,e,i){const s=Math.max(0,e-1),n=1-Math.exp(3.5*-Math.max(0,i));return Math.max(0,s-Math.abs(s-(t+n*s*2.1)))}function m(t,e,i,s,n,a,o,r,h,l,c,d=!1){const m=Math.min(n,a,i-1-n,s-1-a),u=Math.max(0,Math.min(1,(m-1)/4)),g=d?u*u*(3-2*u):1;o*=g;const p=1-Math.exp(3.5*-Math.max(0,o)),x=Math.max(0,Math.min(i-1,n-r*p*.026*i*g)),v=Math.max(0,Math.min(s-1,a-h*p*.026*s*g)),M=Math.floor(x),y=Math.floor(v),w=x-M,S=v-y;c.fill(0);for(let n=0;n<2;n++)for(let a=0;a<2;a++){const o=4*(Math.min(s-1,y+n)*i+Math.min(i-1,M+a)),r=(a?w:1-w)*(n?S:1-S);c[0]+=(t[o]+256*t[o+1])*r,c[1]+=t[o+2]/255*r;for(let t=0;t<4;t++)c[t+2]+=e[o+t]*r}const b=43758.5453*Math.sin(127.1*n+311.7*a),E=b-Math.floor(b);c[0]=Math.max(0,f(c[0],l,o)-p*(1-p)*E*3)}var u=class{constructor(t=1){this.mode="water",this.amount=.55,this.radius=.2,this.lensX=.5,this.lensY=.5,this.lensStrength=0,this.previous=null,this.energy=0,this.pending=!1,this.remainder=0,t=Math.max(.25,Math.min(4,t)),this.columns=Math.round(t>=1?128:128*t),this.rows=Math.round(t>=1?128/t:128),this.xScale=Math.max(1,t),this.yScale=Math.max(1,1/t),this.heights=new Float32Array(this.columns*this.rows),this.velocity=new Float32Array(this.heights.length),this.next=new Float32Array(this.heights.length),this.normals=new Float32Array(2*this.heights.length),this.edgeMask=new Float32Array(this.heights.length);for(let e=0;e<this.rows;e++)for(let i=0;i<this.columns;i++)this.edgeMask[e*this.columns+i]=r(i/(this.columns-1),e/(this.rows-1),t);this.refraction={width:this.columns,height:this.rows,pixels:new Uint8Array(4*this.heights.length),strength:30,mode:"water",focus:[.5,.5,0,.2]},this.waterPixels=this.refraction.pixels,this.trail=new c(this.columns,this.rows),this.contour=new l(this.columns,this.rows),this.afterimage=new h(this.columns,this.rows),this.clear()}get invertsDensity(){return"trail"===this.mode}get densityTrail(){return["trail","contour","dissolve"].includes(this.mode)}get hasRefraction(){return this.active||this.lensStrength>15e-5}get active(){return["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode)?this.afterimage.active:"contour"===this.mode?this.contour.active:"trail"===this.mode?this.trail.active:this.pending||this.energy>15e-5}setMode(t){t!==this.mode&&(this.clear(),this.mode=t,this.refraction.mode=t,"dissolve"!==t&&"silk"!==t&&"vortex"!==t&&"magnetic"!==t&&"scatter"!==t||this.afterimage.setMode(t),this.refraction.pixels="trail"===t?this.trail.pixels:"contour"===t?this.contour.pixels:["dissolve","silk","vortex","magnetic","scatter"].includes(t)?this.afterimage.pixels:this.waterPixels,this.refraction.strength="water"===t?30*this.amount/.55:"silk"===t?28*this.amount:"vortex"===t?38*this.amount:"magnetic"===t||"scatter"===t?32*this.amount:0)}configure(t,e=.55,i=.2,s=!1){this.setMode(t),this.refraction.edgeSafe=s,this.afterimage.edgeSafe=s,this.amount=Number.isFinite(e)?Math.max(0,Math.min(1,e)):.55,this.radius=Number.isFinite(i)?Math.max(.1,Math.min(1,i)):.2,this.refraction.strength="water"===t?30*this.amount/.55:"silk"===t?28*this.amount:"vortex"===t?38*this.amount:"magnetic"===t||"scatter"===t?32*this.amount:0,this.refraction.focus[2]=this.densityTrail?this.amount:this.lensStrength*this.amount,this.refraction.focus[3]=this.radius}move(t,e,i){if("none"===this.mode||0===this.amount)return;if(!Number.isFinite(t+e))return;if(t=Math.max(0,Math.min(1,t)),e=Math.max(0,Math.min(1,e)),["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return void this.afterimage.move(t,e,this.radius);if("contour"===this.mode)return void this.contour.move(t,e,this.radius);if("trail"===this.mode)return void this.trail.move(t,e,this.radius,i);if("water"!==this.mode)return!this.previous&&this.lensStrength<.001&&(this.lensX=t,this.lensY=e),this.previous={x:t,y:e},void(this.pending=!0);if(!this.previous)return void(this.previous={x:t,y:e});const s=this.previous;this.previous={x:t,y:e};const n=(t-s.x)*this.xScale,a=(e-s.y)*this.yScale,o=Math.hypot(n,a);if(o<1e-5)return;const r=.035+.1*this.radius,h=3*r,l=Math.min(192,Math.max(1,Math.ceil(o/(.5*r)))),c=95*Math.min(o,1)/l;for(let i=0;i<l;i++){const n=(i+.5)/l,a=s.x+(t-s.x)*n,o=s.y+(e-s.y)*n,d=Math.max(1,Math.floor((a-h/this.xScale)*(this.columns-1))),f=Math.min(this.columns-2,Math.ceil((a+h/this.xScale)*(this.columns-1))),m=Math.max(1,Math.floor((o-h/this.yScale)*(this.rows-1))),u=Math.min(this.rows-2,Math.ceil((o+h/this.yScale)*(this.rows-1)));for(let t=m;t<=u;t++)for(let e=d;e<=f;e++){const i=(e/(this.columns-1)-a)*this.xScale,s=(t/(this.rows-1)-o)*this.yScale,n=Math.exp(-(i*i+s*s)/(r*r))*c,h=t*this.columns+e;this.velocity[h]=Math.max(-12,this.velocity[h]-n)}}this.pending=!0}leave(){this.afterimage.leave(),this.contour.leave(),this.trail.leave(),this.previous=null,"water"!==this.mode&&this.lensStrength&&(this.pending=!0)}clear(){this.trail.clear(),this.contour.clear(),this.afterimage.clear(),this.heights.fill(0),this.velocity.fill(0),this.next.fill(0),this.normals.fill(0),this.refraction.focus[2]=0,this.previous=null,this.lensStrength=0,this.energy=0,this.pending=!1,this.remainder=0;for(let t=0;t<this.waterPixels.length;t+=4)this.waterPixels[t]=this.waterPixels[t+2]=128,this.waterPixels[t+1]=this.waterPixels[t+3]=0}step(t){if(!this.active||!Number.isFinite(t))return;if(["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return void this.afterimage.step(t);if("contour"===this.mode)return void this.contour.step(t);if("trail"===this.mode)return void this.trail.step(t);if("water"!==this.mode)return void this.stepLight(Math.max(0,Math.min(.05,t)));this.remainder+=Math.max(0,Math.min(.05,t));const e=1/120,i=Math.exp(-3.8*e),s=.3*Math.min(this.columns,this.rows);for(;this.remainder+1e-8>=e;){this.remainder-=e;let t=0;for(let n=1;n<this.rows-1;n++)for(let a=1;a<this.columns-1;a++){const o=n*this.columns+a,r=this.heights[o],h=this.heights[o-1]+this.heights[o+1]+this.heights[o-this.columns]+this.heights[o+this.columns]-4*r,l=Math.min(a,n,this.columns-1-a,this.rows-1-n),c=l<5?.88+.024*l:1,d=(this.velocity[o]+(h*s*s-8*r)*e)*i*c;this.velocity[o]=d,this.next[o]=(r+d*e)*c,t=Math.max(t,Math.abs(this.next[o]),.1*Math.abs(d))}this.heights.set(this.next),this.energy=t,this.pending=!1}if(!this.active)return void this.clear();const n=.18*Math.min(this.columns,this.rows);for(let t=0;t<this.rows;t++)for(let e=0;e<this.columns;e++){const i=t*this.columns+e,s=(this.heights[t*this.columns+Math.max(0,e-1)]-this.heights[t*this.columns+Math.min(this.columns-1,e+1)])*n,a=(this.heights[Math.max(0,t-1)*this.columns+e]-this.heights[Math.min(this.rows-1,t+1)*this.columns+e])*n,o=Math.tanh(s)*(this.refraction.edgeSafe?this.edgeMask[i]:1),r=Math.tanh(a)*(this.refraction.edgeSafe?this.edgeMask[i]:1);this.normals[2*i]=o,this.normals[2*i+1]=r;const h=Math.round(32767*o+32768),l=Math.round(32767*r+32768);this.refraction.pixels[4*i]=h>>>8,this.refraction.pixels[4*i+1]=255&h,this.refraction.pixels[4*i+2]=l>>>8,this.refraction.pixels[4*i+3]=255&l}}stepLight(t){const e=this.previous,i=1-Math.exp(-30*t),s=1-Math.exp(-12*t);e&&(this.lensX+=(e.x-this.lensX)*i,this.lensY+=(e.y-this.lensY)*i),this.lensStrength+=((e?1:0)-this.lensStrength)*s,this.energy=Math.max(e?Math.abs(e.x-this.lensX)+Math.abs(e.y-this.lensY):0,Math.abs((e?1:0)-this.lensStrength)),this.pending=!1,e||this.active?this.refraction.focus=[this.lensX,this.lensY,this.lensStrength*this.amount,this.radius]:this.clear()}sample(t,e,i){if(["dissolve","silk","vortex","magnetic","scatter"].includes(this.mode))return this.afterimage.sample(t,e,i),i[0]*=this.amount,i[1]*=this.amount,void(i[2]*=this.amount);if("contour"===this.mode)return i[0]=i[1]=0,void(i[2]=this.contour.sample(t,e)*this.amount*3);if("trail"===this.mode)return i[0]=i[1]=0,void(i[2]=this.trail.sample(t,e)*this.amount*3);const s=Math.max(0,Math.min(this.columns-1.001,t*(this.columns-1))),n=Math.max(0,Math.min(this.rows-1.001,e*(this.rows-1))),a=Math.floor(s),o=Math.floor(n),r=s-a,h=n-o,l=2*(o*this.columns+a),c=l+2*this.columns;for(let t=0;t<2;t++)i[t]=.08*((this.normals[l+t]*(1-r)+this.normals[l+2+t]*r)*(1-h)+(this.normals[c+t]*(1-r)+this.normals[c+2+t]*r)*h);i[0]*=this.amount/.55,i[1]*=this.amount/.55,i[2]="light"===this.mode||"scan"===this.mode?function(t,e,i,s){const[n,a,o,r]=i.focus,h=(t-n)*Math.max(1,s)/r,l=(e-a)*Math.max(1,1/s)/r;return"light"===i.mode?Math.exp(-3*(h*h+l*l))*o:"scan"===i.mode?(1.3*Math.exp(-h*h*110)-.3*Math.exp(-h*h*12))*Math.exp(-l*l*1.8)*o:0}(t,e,this.refraction,this.xScale/this.yScale):0}};var g=[{value:"clean",label:"Clean",description:"Crisp characters with no optical texture."},{value:"signal",label:"Signal",description:"Broken scanlines at the edges, with a clear center."},{value:"prism",label:"Prism",description:"Soft red/cyan separation at the edges, with a crisp center."},{value:"etch",label:"Etch",description:"A fine engraved texture, keeping the ink monochrome."}];function p(t){const e=Intl.Segmenter;return e?Array.from(new e(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function x(){const t=document.createElement("canvas"),e=document.createElement("canvas"),i=t.getContext("2d"),s=e.getContext("2d",{willReadFrequently:!0}),n=function(){const t=new WeakMap;return(e,i,s="")=>{if(!e)return i;let n=t.get(e);if(!n){const i=new Set;for(const t of e)for(const e of t)e.char&&i.add(e.char);n=[...i].sort(),t.set(e,n)}const a=i+s;return i+n.filter(t=>!a.includes(t)).join("")}}();let a,o=null;return{update(r,h,l,c,d,f,m,u){const g=m?.[0]?.length??Math.max(1,Math.round(h/c)),x=m?.length??Math.max(1,Math.round(l/d)),v=u??{charset:" .:-=+*#%@",customText:"",colorMode:"fullcolor"},M=n(m,v.charset+(v.charsetFrames?.join("")??""),v.customText),y=function(t,e,i,s,n,a,o,r=4096,h=0,l=1){if(![i,s,n,a,o,r].every(t=>Number.isFinite(t)&&t>0))throw new Error("Glyph atlas dimensions must be positive and finite.");const c=JSON.stringify([e.charset,e.customText,i,s,n,a,o,r,h,l]);if(t?.key===c)return t;const d=[...new Set([" ",...p(e.charset),...p(e.customText??"")])];if(d.length>65536)throw new Error("This character set exceeds the glyph texture capacity.");const f=i/a,m=s/o,u=.9*Math.min(f/.55,m)*l,g=Math.ceil(f*n)+6,x=Math.ceil(m*n)+6,v=Math.min(d.length,Math.floor(r/g));if(v<1||Math.ceil(d.length/v)*x>r)throw new Error("The glyph atlas exceeds the available GPU texture size.");return{key:c,glyphs:d,indices:new Map(d.map((t,e)=>[t,e])),cellWidth:f,cellHeight:m,fontSize:u,padding:3,tileWidth:g,tileHeight:x,atlasColumns:v,atlasWidth:v*g,atlasHeight:Math.ceil(d.length/v)*x}}(o,{...v,charset:M},h,l,f,g,x);if(y!==o&&(t.width=y.atlasWidth,t.height=y.atlasHeight,i.font=y.fontSize*f+'px "JetBrains Mono", monospace',i.textAlign="center",i.textBaseline="middle",i.fillStyle="#fff",y.glyphs.forEach((t,e)=>i.fillText(t,e%y.atlasColumns*y.tileWidth+y.padding+y.cellWidth*f*.5,Math.floor(e/y.atlasColumns)*y.tileHeight+y.padding+y.cellHeight*f*.5))),o=y,m)a=function(t,e,i,s,n=!1){const a=t.length,o=t[0]?.length??0,r=s?.cols===o&&s.rows===a?s:{cols:o,rows:a,indices:new Uint8Array(o*a*4),colors:new Uint8ClampedArray(o*a*4)};r.indices.fill(0),r.colors.fill(0);const h="accent"===i.colorMode?[1,3,5].map(t=>parseInt(i.accentColor.slice(t,t+2),16)):null;for(let s=0;s<a;s++){if(t[s].length!==o)throw new Error("Glyph frames must have a rectangular cell grid.");for(let a=0;a<o;a++){const l=t[s][a];if(!l.char||!n&&" "===l.char||l.a<10)continue;const c=e.get(l.char);if(void 0===c)throw new Error(`Glyph is absent from the configured character set: ${l.char}`);const d=4*(s*o+a);let f=l.r,m=l.g,u=l.b;if(h)[f,m,u]=h;else if("fullcolor"!==i.colorMode){const t=.299*f+.587*m+.114*u|0;f="matrix"===i.colorMode?0:t,m=t,u=f}(n||0!==f||0!==m||0!==u)&&(r.indices[d]=255&c,r.indices[d+1]=c>>>8,r.indices[d+2]=Math.round(.299*l.r+.587*l.g+.114*l.b),r.colors[d]=f,r.colors[d+1]=m,r.colors[d+2]=u,r.colors[d+3]=l.a)}}return r}(m,o.indices,v,a,!0);else{e.width=g,e.height=x,s.drawImage(r,0,0,g,x);const t=s.getImageData(0,0,g,x).data;a={cols:g,rows:x,indices:new Uint8Array(g*x*4),colors:new Uint8ClampedArray(t)};for(let e=0;e<t.length;e+=4){const i=Math.min(1,(.299*t[e]+.587*t[e+1]+.114*t[e+2])/255*(t[e+3]/255)*3);a.indices[e+2]=Math.round(255*i),a.indices[e]=Math.min(o.glyphs.length-1,Math.floor(i*o.glyphs.length))}}return{plan:o,packed:a,atlas:t,dots:"dots"===v.renderMode,dotSize:v.dotSizeRatio??.72}},reset(){o=null},destroy(){t.width=t.height=e.width=e.height=1,a=void 0,o=null}}}function v(t){return!!t&&"ascii"===t.renderMode&&!/\p{Extended_Pictographic}/u.test(t.customText||t.charset)&&!t.charsetFrames?.length&&["fullcolor","grayscale","matrix","accent"].includes(t.colorMode)&&("accent"!==t.colorMode||/^#[\da-f]{6}$/i.test(t.accentColor))}function M(t){for(let e=t;e;e=e.parentElement){const t=getComputedStyle(e).backgroundColor.match(/[\d.]+/g)?.map(Number);if(t&&t.length>=3&&(t[3]??1)>.5)return(.299*t[0]+.587*t[1]+.114*t[2])/255<.4?[10/255,10/255,10/255,1]:[250/255,249/255,247/255,1]}const e=document.documentElement.dataset.theme;if("dark"!==e&&"light"!==e&&!t.isConnected&&document.body)return M(document.body);return"dark"===e||"light"!==e&&(document.documentElement.classList.contains("dark")||matchMedia("(prefers-color-scheme: dark)").matches)?[10/255,10/255,10/255,1]:[250/255,249/255,247/255,1]}function y(t,e,s){const r=e.style.visibility;let h=document.createElement("canvas"),l=null,c=null,u=null,p=null;const y=[],w=[];let S=!1,b=!1,E=4096,T=null,A=0,k=0,z=0,R=0,D=!1;const C=function(){const t=document.createElement("canvas"),e=t.getContext("2d");return{paint(i,s,n,a){s&&(t.width===i.canvas.width&&t.height===i.canvas.height||(t.width=i.canvas.width,t.height=i.canvas.height),e.globalCompositeOperation="copy",e.drawImage(i.canvas,0,0),e.globalCompositeOperation="destination-in",e.drawImage(s.canvas,0,0,t.width,t.height),e.globalCompositeOperation="source-in",e.fillStyle="#090909",e.fillRect(0,0,t.width,t.height),e.globalCompositeOperation="source-over",i.drawImage(t,0,0,n,a))},destroy(){t.width=t.height=1}}}();let F;const _=document.createElement("canvas"),I=document.createElement("canvas"),P=_.getContext("2d"),X=I.getContext("2d");let L=!1;const U=new a,G=x();let N=null,Y={};const B=()=>{h.className="ascii-hover-surface",h.style.cssText="position:absolute;inset:0;width:100%;height:100%;pointer-events:none",h.setAttribute("role","img"),h.setAttribute("aria-label","ASCII artwork preview"),h.dataset.renderer=l?"surface-gpu":"surface-canvas",t.prepend(h),e.style.visibility="hidden"},H=()=>{l&&(y.forEach(t=>l.deleteTexture(t)),w.forEach(t=>l.deleteShader(t)),u&&l.deleteProgram(u),p&&l.deleteBuffer(p))},W=()=>{S||c||(h.removeEventListener("webglcontextlost",W),H(),h.remove(),l=null,h=document.createElement("canvas"),c=h.getContext("2d"),B(),s())};try{if(l=h.getContext("webgl",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!0}),!l)throw new Error("WebGL unavailable");E=l.getParameter(l.MAX_TEXTURE_SIZE);const t=(t,e)=>{const i=l.createShader(t);if(w.push(i),l.shaderSource(i,e),l.compileShader(i),!l.getShaderParameter(i,l.COMPILE_STATUS))throw new Error(l.getShaderInfoLog(i)||"Shader compilation failed");return i};if(u=l.createProgram(),l.attachShader(u,t(l.VERTEX_SHADER,"attribute vec2 position; void main(){gl_Position=vec4(position,0.,1.);}")),l.attachShader(u,t(l.FRAGMENT_SHADER,"\n precision highp float;\n uniform sampler2D artwork; uniform sampler2D surface;\n uniform vec2 resolution; uniform vec2 sceneSize; uniform vec2 cellSize;\n uniform vec2 surfaceSize; uniform float strength;\n \nuniform float hoverMode;\nuniform vec4 hoverFocus;\nfloat surfaceLight(vec2 uv, vec2 size) {\n vec2 delta = (uv - hoverFocus.xy) * size / min(size.x, size.y) / hoverFocus.w;\n float light = exp(-3.0 * dot(delta, delta));\n if (hoverMode > 1.5) light = (exp(-delta.x * delta.x * 110.0) * 1.3 - exp(-delta.x * delta.x * 12.0) * .3) * exp(-delta.y * delta.y * 1.8);\n return hoverMode > .5 ? light * hoverFocus.z : 0.0;\n}\nvec3 illuminate(vec3 ink, float light) {\n return mix(ink, vec3(1.0), max(0.0, light) * .82) * (1.0 + min(0.0, light));\n}\n\n uniform sampler2D codes; uniform sampler2D cellColors; uniform sampler2D glyphAtlas;\n uniform vec2 gridSize; uniform vec2 atlasSize; uniform vec2 tileSize;\n uniform float atlasColumns; uniform float padding; uniform float glyphCount;\n uniform float dots; uniform float dotSize; uniform float directGlyphs; uniform vec4 backdrop;\n uniform float edgeEffect; uniform sampler2D textMask; uniform float hasTextMask;\n \nuniform float finishMode;\nuniform float finishPixelRatio;\nuniform float finishSoftness;\nvec3 edgeCoverage(sampler2D sheet, vec2 uv, vec2 atlas, vec2 pixel, vec2 viewport, float amount) {\n float a=texture2D(sheet,uv).a;\n if(amount<=0.0 || finishMode<.5) return vec3(a);\n vec2 radial=(pixel/viewport-.5)*2.0;\n float edge=smoothstep(.3,1.05,length(radial));\n float strength=clamp(amount,0.0,1.0)*edge;\n if(strength<=0.0) return vec3(a);\n vec2 cssPixel=pixel/max(1.0,finishPixelRatio);\n // Etch is a single texture sample: linework, not an opacity wash.\n if(finishMode>1.5 && finishMode<2.5) {\n float hatch=step(.68,fract((cssPixel.y+cssPixel.x*.22)*.5));\n return vec3(a*(1.0-hatch*strength*.64));\n }\n // Samples remain inside the current atlas tile's three-pixel padding.\n if(finishMode>2.5) {\n float band=floor(cssPixel.y/3.0);\n float phase=fract(sin(band*12.9898)*43758.5453);\n float tear=(phase-.5)*3.6*strength;\n float displaced=texture2D(sheet,uv+vec2(tear,0.0)/atlas).a;\n float cut=step(.72,fract(cssPixel.y*.5));\n return vec3(mix(a,displaced,strength)*(1.0-cut*.55*strength));\n }\n // A five-tap lens: opposed red/cyan edges with a short soft shoulder.\n // The center remains untouched; all offsets stay within three atlas texels.\n vec2 direction=normalize(vec2(radial.x-radial.y*.3,radial.y+radial.x*.3)+vec2(.00001));\n vec2 shift=direction*(2.35+.45*finishSoftness)*strength/atlas;\n float left=texture2D(sheet,uv-shift).a,right=texture2D(sheet,uv+shift).a;\n float softLeft=texture2D(sheet,uv-shift*.45).a,softRight=texture2D(sheet,uv+shift*.45).a;\n float red=left*.7+softLeft*.22+a*.08;\n float cyan=right*.7+softRight*.22+a*.08;\n // Reuse the same taps for a short optical blur and peripheral fade.\n // No whole-canvas blur, extra pass or softened HTML controls.\n float soft=a*.4+(left+right+softLeft+softRight)*.15;\n vec3 fringe=mix(vec3(red,cyan,cyan),vec3(soft),finishSoftness*.45);\n return mix(vec3(a),fringe,strength)*(1.-finishSoftness*strength*.32);\n\n}\n\n \nuniform float edgeSafe;\nuniform float trailAmount;\nuniform float trailKind;\nvec4 trailField(vec2 address) {\n return texture2D(surface,(address*(surfaceSize-1.0)+.5)/surfaceSize);\n}\nfloat trailFieldDensity(vec4 field) {\n return (field.r*65280.0+field.g*255.0)/65535.0*trailAmount;\n}\nfloat trailDensity(vec2 address) { return trailFieldDensity(trailField(address)); }\nfloat trailPin(vec2 cell, vec2 grid) {\n vec2 edge=min(cell,grid-1.0-cell);\n return mix(1.,smoothstep(1.0,5.0,min(edge.x,edge.y)),edgeSafe);\n}\nvec2 trailSource(vec2 cell, vec2 grid, vec4 field) {\n vec2 home=(cell+.5)/grid;\n if(trailKind>.5 || trailAmount<=0.) return home;\n float pin=trailPin(cell,grid);\n float response=1.-exp(-trailFieldDensity(field)*pin*3.5);\n vec2 drift=(field.ba*255.-128.)/127.;\n return clamp(home-drift*response*.026*pin,.5/grid,1.-.5/grid);\n}\nvec2 decodedGlyph(vec4 code) {\n return vec2(floor(code.r*255.+.5)+floor(code.g*255.+.5)*256.,code.b);\n}\n// Bilinear interpolation happens before glyph selection, never between bitmap\n// letters. Four small metadata reads replace the old four full-glyph gathers.\nvec2 flowGlyph(sampler2D codes,vec2 uv,vec2 grid) {\n vec2 p=uv*grid-.5,b=floor(p),f=fract(p);\n return mix(mix(decodedGlyph(texture2D(codes,(b+.5)/grid)),decodedGlyph(texture2D(codes,(b+vec2(1.5,.5))/grid)),f.x),\n mix(decodedGlyph(texture2D(codes,(b+vec2(.5,1.5))/grid)),decodedGlyph(texture2D(codes,(b+1.5)/grid)),f.x),f.y);\n}\nvec3 trailInk(vec3 ink, float density) {\n if (trailKind < .5) return ink;\n float peak=max(ink.r,max(ink.g,ink.b));\n float raised=trailKind>1.5?peak:max(peak,min(.44,density*.75));\n return peak>.001 ? ink*(raised/peak) : vec3(raised);\n}\nfloat flowIndex(float index,float count,float density) {\n float last=max(0.,count-1.);\n float response=1.-exp(-max(0.,density)*3.5);\n return max(0.,last-abs(last-(index+response*last*2.1)));\n}\nfloat trailTone(float tone, float density) {\n if (trailKind < .5) return flowIndex(tone,2.,density);\n return clamp(tone + (trailKind > 1.5 ? -density : density) * .65, 0.0, 1.0);\n}\nfloat trailIndex(float index, float count, float density, vec2 cell) {\n float grain=fract((cell.x*cell.x*17.0+cell.y*cell.y*23.0+cell.x*cell.y*19.0)*.0137);\n if(trailKind>1.5) return max(0.0,index-floor(density*count*(1.35+grain*.9)));\n if(trailKind<.5) {\n float seed=fract(sin(dot(cell,vec2(127.1,311.7)))*43758.5453);\n float response=1.-exp(-max(0.,density)*3.5);\n return floor(max(0.,flowIndex(index,count,density)-response*(1.-response)*seed*3.)+.5);\n }\n return min(count-1.0,index+floor(density*count*.85));\n}\n\n \n uniform float motionMode; uniform float motionTime; uniform float fineDither; uniform float ditherAmount;\n float printGrain(vec2 p) { return fract(52.9829189*fract(dot(p,vec2(.06711056,.00583715)))); }\n vec4 ambientAt(vec2 uv) {\n float p=motionTime*.5235987756;\n float edge=smoothstep(0.,.12,min(min(uv.x,1.-uv.x),min(uv.y,1.-uv.y)));\n if(motionMode<.5) return vec4(0.,0.,0.,1.);\n if(motionMode<1.5) {\n float fold=sin(uv.x*9.+uv.y*5.+p)+sin(uv.y*8.-uv.x*3.-p);\n float light=pow(max(0.,1.-abs(fold)*.72),3.);\n return vec4(0.,0.,(light*.22-.045)*edge,1.);\n }\n if(motionMode<2.5) return vec4(sin(uv.y*9.+p)*cos(uv.x*7.-p)*10.*edge,cos(uv.x*8.+p)*sin(uv.y*6.-p)*7.*edge,0.,1.);\n if(motionMode>5.5) return vec4(0.,0.,sin(p+printGrain(floor(uv*vec2(160.,100.)))*6.28318530718)*.07,1.);\n if(motionMode>4.5) {\n vec2 delta=uv-.5; float wave=sin(length(delta)*22.-p)*edge;\n return vec4(delta*wave*9.,wave*.035,1.);\n }\n if(motionMode>3.5) return vec4(0.,0.,pow(.5+.5*cos(uv.x*5.+uv.y*3.-p),8.)*.2-.025,1.);\n float cycle=mod(motionTime,12.);\n float grain=printGrain(floor(uv*vec2(160.,100.)));\n float order=uv.x*.65+uv.y*.2+grain*.15+sin(uv.x*8.-uv.y*5.)*.08;\n float gone=smoothstep(1.8+order*1.2,3.8+order*1.2,cycle)*(1.-smoothstep(5.+order*1.7,7.2+order*1.7,cycle));\n return vec4(sin(uv.y*9.+p)*gone*(1.-gone)*24.*edge,-gone*(1.-gone)*(8.+grain*12.)*edge,-gone*.45,1.-gone*.9);\n }\n\n float toneAt(vec2 uv) {\n vec2 p=uv*gridSize-.5, b=floor(p), f=fract(p);\n return mix(mix(texture2D(codes,(b+.5)/gridSize).b,texture2D(codes,(b+vec2(1.5,.5))/gridSize).b,f.x),mix(texture2D(codes,(b+vec2(.5,1.5))/gridSize).b,texture2D(codes,(b+1.5)/gridSize).b,f.x),f.y);\n }\n vec4 flowingCharacter(vec2 pixel, vec2 cell, vec4 ambient) {\n if(cell.x<0. || cell.y<0. || cell.x>=gridSize.x || cell.y>=gridSize.y) return vec4(0.);\n vec2 address=(cell+.5)/gridSize;\n vec4 field=trailField(address);\n vec2 local=pixel-cell*cellSize;\n if(local.x<0. || local.y<0. || local.x>=cellSize.x || local.y>=cellSize.y) return vec4(0.);\n vec2 source=trailSource(cell,gridSize,field);\n vec2 encoded=flowGlyph(codes,source,gridSize);\n vec4 color=texture2D(cellColors,source);\n float original=encoded.x;\n float density=trailFieldDensity(field)*trailPin(cell,gridSize),index=trailIndex(original,glyphCount,density,cell);\n if(original>.5) index=clamp(floor(index+ambient.z*(glyphCount-1.)+printGrain(cell)),0.,glyphCount-1.);\n if(index<.5 || color.a<.01) return vec4(0.);\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local*resolution/sceneSize)/atlasSize).a;\n if(dots>.5) {\n vec2 d=(local-cellSize*.5)/min(cellSize.x,cellSize.y);\n float r=min(.48,(index/(glyphCount-1.))*.5*dotSize);\n alpha=1.-smoothstep(r-.06,r+.02,length(d));\n }\n vec3 coverage=dots>.5?vec3(alpha):edgeCoverage(glyphAtlas,(slot*tileSize+padding+local*resolution/sceneSize)/atlasSize,atlasSize,pixel,sceneSize,edgeEffect);\n coverage*=color.a;alpha=max(coverage.r,max(coverage.g,coverage.b));\n return vec4(trailInk(color.rgb,density)*coverage,alpha);\n }\n void main(){\n vec2 uv = vec2(gl_FragCoord.x, resolution.y - gl_FragCoord.y) / resolution;\n vec2 pixel = uv * sceneSize;\n if (strength > 0.) {\n vec4 normal = texture2D(surface, (uv * (surfaceSize - 1.) + .5) / surfaceSize) * 255.;\n vec2 slope = (vec2(normal.r * 256. + normal.g, normal.b * 256. + normal.a) - 32768.) / 32767.;\n vec2 edge = min(pixel, sceneSize - pixel) / cellSize;\n uv -= slope * strength * mix(1.,smoothstep(2.,8.,min(edge.x,edge.y)),edgeSafe) / sceneSize;\n }\n vec4 ambient=ambientAt(uv);\n uv -= ambient.xy/sceneSize;\n pixel=uv*sceneSize;\n vec4 ink = vec4(0.);\n if (directGlyphs > .5) {\n vec2 cell=floor(pixel/cellSize), address=(cell+.5)/gridSize;\n vec4 encoded=texture2D(codes,address), color=texture2D(cellColors,address);\n float index=floor(encoded.r*255.+.5)+floor(encoded.g*255.+.5)*256.;\n if(index>.5 && color.a>.01) {\n vec2 local=(pixel-cell*cellSize)*resolution/sceneSize;\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local)/atlasSize).a*color.a;\n vec3 coverage=edgeCoverage(glyphAtlas,(slot*tileSize+padding+local)/atlasSize,atlasSize,pixel,sceneSize,edgeEffect)*color.a;\n ink=vec4(color.rgb*coverage,max(coverage.r,max(coverage.g,coverage.b)));\n }\n } else ink = texture2D(artwork, uv);\n if (fineDither > .5) {\n vec2 cell=floor(pixel/cellSize),address=(cell+.5)/gridSize;\n vec4 color=texture2D(cellColors,address);\n float density=trailAmount>0.?trailDensity(address):0.;\n vec2 source=trailAmount>0. && trailKind<.5?trailSource(cell,gridSize,trailField(address)):uv;\n float tone=trailTone(clamp(toneAt(source)+ambient.z,0.,1.),density*(trailKind<.5?trailPin(cell,gridSize):1.));\n float pitch=max(1.,min(cellSize.x,cellSize.y)*.32);\n float grain=.5+(printGrain(floor(pixel/pitch))-.5)*ditherAmount;\n float alpha=(tone<.001?0.:tone>.999?1.:smoothstep(grain-.035,grain+.035,tone))*color.a*ambient.w;\n // Preserve hue; tone is carried by mark coverage rather than muddy ink.\n vec3 hue=color.rgb/max(.001,max(color.r,max(color.g,color.b)));\n if(hasTextMask>.5) hue=mix(hue,vec3(.035),texture2D(textMask,vec2(gl_FragCoord.x,resolution.y-gl_FragCoord.y)/resolution).a);\n gl_FragColor=vec4(hue*alpha,alpha); return;\n }\n if ((trailAmount > 0. && trailKind > .5) || motionMode > .5) {\n vec2 cell=floor(pixel/cellSize), address=(cell+.5)/gridSize;\n float density=trailAmount>0.?trailDensity(address):0.;\n vec4 encoded=texture2D(codes,address);\n float original=floor(encoded.r*255.+.5)+floor(encoded.g*255.+.5)*256.;\n float index=trailIndex(original,glyphCount,density,cell);\n if(original>.5) index=clamp(floor(index+ambient.z*(glyphCount-1.)+printGrain(cell)),0.,glyphCount-1.);\n vec4 color=texture2D(cellColors,address);\n if((index!=original || motionMode>.5) && color.a>.01) {\n vec2 local=(pixel-cell*cellSize)*resolution/sceneSize;\n vec2 slot=vec2(mod(index,atlasColumns),floor(index/atlasColumns));\n float alpha=texture2D(glyphAtlas,(slot*tileSize+padding+local)/atlasSize).a;\n if(dots>.5) {\n vec2 d=(pixel-(cell+.5)*cellSize)/min(cellSize.x,cellSize.y);\n float r=min(.48,(index/(glyphCount-1.))*.5*dotSize);\n alpha=1.-smoothstep(r-.06,r+.02,length(d));\n }\n // Replace the cell, rather than blending two glyphs into a ghost.\n vec3 rgb=trailInk(color.rgb,density);\n ink=vec4(rgb*alpha*color.a,alpha*color.a);\n }\n }\n if(trailAmount>0. && trailKind<.5 && trailDensity((floor(pixel/cellSize)+.5)/gridSize)>.001) {\n ink=flowingCharacter(pixel,floor(pixel/cellSize),ambient);\n }\n vec3 straightInk = ink.a > .0 ? ink.rgb / ink.a : vec3(.0);\n float value = max(straightInk.r, max(straightInk.g, straightInk.b));\n // Lighting belongs to character ink; black/transparent gaps stay empty.\n float light = surfaceLight((floor(pixel / cellSize) + .5) * cellSize / sceneSize, sceneSize);\n vec3 lit = illuminate(straightInk, light * smoothstep(0., .22, value)) * ink.a;\n if(hasTextMask>.5) lit=mix(lit,vec3(.035)*ink.a,texture2D(textMask,vec2(gl_FragCoord.x,resolution.y-gl_FragCoord.y)/resolution).a);\n vec4 result = vec4(lit, ink.a)*ambient.w;\n gl_FragColor = directGlyphs > .5 ? result + backdrop*(1.-result.a) : result;\n }")),l.linkProgram(u),!l.getProgramParameter(u,l.LINK_STATUS))throw new Error("Surface shader link failed");l.useProgram(u),p=l.createBuffer(),l.bindBuffer(l.ARRAY_BUFFER,p),l.bufferData(l.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),l.STATIC_DRAW);const e=l.getAttribLocation(u,"position");l.enableVertexAttribArray(e),l.vertexAttribPointer(e,2,l.FLOAT,!1,0,0),Y=Object.fromEntries(["finishSoftness","finishMode","finishPixelRatio","edgeEffect","textMask","hasTextMask","artwork","surface","resolution","sceneSize","cellSize","surfaceSize","strength","hoverMode","hoverFocus","codes","cellColors","glyphAtlas","gridSize","atlasSize","tileSize","atlasColumns","padding","glyphCount","dots","dotSize","directGlyphs","backdrop","trailKind","edgeSafe","trailAmount","motionMode","motionTime","fineDither","ditherAmount"].map(t=>[t,l.getUniformLocation(u,t)]));for(let t=0;t<6;t++){const e=l.createTexture();y.push(e),l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,e),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,l.LINEAR),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,l.LINEAR),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,1,1,0,l.RGBA,l.UNSIGNED_BYTE,new Uint8Array(4)),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_S,l.CLAMP_TO_EDGE),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_WRAP_T,l.CLAMP_TO_EDGE)}l.uniform1i(Y.textMask,5),l.uniform1i(Y.codes,2),l.uniform1i(Y.cellColors,3),l.uniform1i(Y.glyphAtlas,4),l.uniform1i(Y.artwork,0),l.uniform1i(Y.surface,1),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),l.disable(l.BLEND),l.disable(l.DEPTH_TEST),h.addEventListener("webglcontextlost",W),B()}catch{W()}const O=()=>{S||(G.reset(),N=null,b=!1,s())};return document.fonts?.addEventListener("loadingdone",O),document.fonts?.ready.then(O),{get canvas(){return h},canRenderGlyphs:t=>!!l&&!S&&v(t),render(t,s,a,r,u,p,x,w,B=0,H={}){const O=i(w?.animationStyle),K=U.update(B,O,w?.animationSpeed),$=(t=>!0===t?.fineDither&&!t.customText&&!t.charsetFrames?.length&&!/[A-Za-z]/.test(t.charset??""))(w),q=!!l&&!!x&&v(w);if(q!==D&&(b=!1,D=q),S||!a||!r||!e.width||!e.height)return;const j=Math.min(e.width/a,1.5,Math.sqrt(4e6/(a*r))),J=Math.max(1,Math.round(a*j)),V=Math.max(1,Math.round(r*j));if(h.width===J&&h.height===V||(h.width=J,h.height=V,b=!1),l&&(e.width>E||e.height>E)&&W(),(q||d(s.mode)||"none"!==O||$)&&(t||!b||!N)&&(N=G.update(e,a,r,u,p,j,x,w),l)){const{plan:t,packed:e,atlas:i}=N;for(const[t,i]of[[2,e.indices],[3,e.colors]])l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,y[t]),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,3===t?l.LINEAR:l.NEAREST),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,3===t?l.LINEAR:l.NEAREST),A!==e.cols||k!==e.rows?l.texImage2D(l.TEXTURE_2D,0,l.RGBA,e.cols,e.rows,0,l.RGBA,l.UNSIGNED_BYTE,i):l.texSubImage2D(l.TEXTURE_2D,0,0,0,e.cols,e.rows,l.RGBA,l.UNSIGNED_BYTE,i);A=e.cols,k=e.rows,T!==t&&(l.activeTexture(l.TEXTURE4),l.bindTexture(l.TEXTURE_2D,y[4]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),T=t),l.uniform2f(Y.gridSize,e.cols,e.rows),l.uniform2f(Y.atlasSize,t.atlasWidth,t.atlasHeight),l.uniform2f(Y.tileSize,t.tileWidth,t.tileHeight),l.uniform1f(Y.atlasColumns,t.atlasColumns),l.uniform1f(Y.padding,t.padding),l.uniform1f(Y.glyphCount,t.glyphs.length),l.uniform1f(Y.dots,N.dots?1:0),l.uniform1f(Y.dotSize,N.dotSize)}if(l){l.uniform1f(Y.motionMode,(t=>["none","caustics","current","reform","sheen","tidal","grain"].indexOf(t))(O)),l.uniform1f(Y.motionTime,K),l.uniform1f(Y.fineDither,$?1:0),l.uniform1f(Y.ditherAmount,Math.max(0,Math.min(2,w?.ditherStrength??1))),l.uniform1f(Y.trailKind,"dissolve"===s.mode?2:"contour"===s.mode?1:0),l.uniform1f(Y.edgeSafe,s.edgeSafe?1:0),l.uniform1f(Y.trailAmount,d(s.mode)?s.focus[2]:0),l.viewport(0,0,J,V),q||!t&&b||(l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,y[0]),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,e),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1)),l.uniform1f(Y.directGlyphs,q?1:0),!q||!t&&b||l.uniform4fv(Y.backdrop,H.textMask||function(t){const e=t.length,i=t[0]?.length??0;for(let s=0;s<e;s+=Math.max(1,e>>2))for(let e=0;e<i;e+=Math.max(1,i>>2))if(t[s][e].a<200)return!0;return!1}(x)?[0,0,0,0]:M(e)),b=!0,l.activeTexture(l.TEXTURE1),l.bindTexture(l.TEXTURE_2D,y[1]),z!==s.width||R!==s.height?(l.texImage2D(l.TEXTURE_2D,0,l.RGBA,s.width,s.height,0,l.RGBA,l.UNSIGNED_BYTE,s.pixels),z=s.width,R=s.height):l.texSubImage2D(l.TEXTURE_2D,0,0,0,s.width,s.height,l.RGBA,l.UNSIGNED_BYTE,s.pixels),l.uniform2f(Y.resolution,J,V),l.uniform2f(Y.sceneSize,a,r),l.uniform2f(Y.cellSize,u,p),l.uniform2f(Y.surfaceSize,s.width,s.height),l.uniform1f(Y.strength,s.strength),l.uniform1f(Y.hoverMode,"light"===s.mode?1:"scan"===s.mode?2:0),l.uniform4fv(Y.hoverFocus,s.focus),H.textMask&&H.textMask!==F&&(l.activeTexture(l.TEXTURE5),l.bindTexture(l.TEXTURE_2D,y[5]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,H.textMask.canvas),F=H.textMask),l.uniform1f(Y.hasTextMask,H.textMask?1:0);const i=function(t={}){const e=g.some(e=>e.value===t.filter)?t.filter:"prism",i=Number.isFinite(t.edgeEffect)?Math.max(0,Math.min(1,t.edgeEffect)):t.filter?1:0,s=Number.isFinite(t.edgeSoftness)?Math.max(0,Math.min(1,t.edgeSoftness)):0;return{mode:["clean","prism","etch","signal"].indexOf(e),amount:"clean"===e?0:i,softness:"prism"===e?s:0}}(H);l.uniform1f(Y.edgeEffect,i.amount),l.uniform1f(Y.finishMode,i.mode),l.uniform1f(Y.finishSoftness,i.softness),l.uniform1f(Y.finishPixelRatio,1),l.drawArrays(l.TRIANGLE_STRIP,0,4)}else if(c){if(c.setTransform(j,0,0,j,0,0),c.clearRect(0,0,a,r),c.drawImage(e,0,0,a,r),("none"!==O||$)&&N){c.clearRect(0,0,a,r);const{plan:t,packed:e,atlas:i}=N,h=[0,0,0,1],l=[0,0,0,0,0,0],g=Math.max(1,.32*Math.min(u,p));for(let a=0;a<e.rows;a++)for(let r=0;r<e.cols;r++){const x=(r+.5)/e.cols,v=(a+.5)/e.rows,M=4*(a*e.cols+r);if(e.colors[M+3]<10)continue;o(O,x,v,K,h);const y=4*(Math.min(s.height-1,Math.round(v*(s.height-1)))*s.width+Math.min(s.width-1,Math.round(x*(s.width-1)))),S=d(s.mode)?(256*s.pixels[y]+s.pixels[y+1])/65535*s.focus[2]:0,b=e.indices[M]+256*e.indices[M+1];"trail"===s.mode&&m(e.indices,e.colors,e.cols,e.rows,r,a,S,(s.pixels[y+2]-128)/127,(s.pixels[y+3]-128)/127,t.glyphs.length,l,s.edgeSafe);let E="trail"===s.mode?l[0]:"dissolve"===s.mode?b-Math.floor(S*t.glyphs.length*1.8):b+Math.floor(S*t.glyphs.length*.85);b>0&&(E+=h[2]*(t.glyphs.length-1)),E=Math.max(0,Math.min(t.glyphs.length-1,Math.floor(E+n(r,a))));let T=h[0],A=h[1];s.strength>0&&(T+=(256*s.pixels[y]+s.pixels[y+1]-32768)/32767*s.strength,A+=(256*s.pixels[y+2]+s.pixels[y+3]-32768)/32767*s.strength);const k=r*u+T,z=a*p+A,R=("trail"===s.mode?l[5]:e.colors[M+3])/255*h[3],D="trail"===s.mode?l[2]:e.colors[M],C="trail"===s.mode?l[3]:e.colors[M+1],F="trail"===s.mode?l[4]:e.colors[M+2],_=$?Math.max(1,D,C,F):255,I=`rgb(${D/_*255},${C/_*255},${F/_*255})`;if(c.fillStyle=I,$){const t="trail"===s.mode?f(Math.max(0,Math.min(1,l[1]+h[2])),2,S):Math.max(0,Math.min(1,e.indices[M+2]/255+h[2]+.65*("dissolve"===s.mode?-S:S)));c.globalAlpha=R;for(let e=0;e<p;e+=g)for(let i=0;i<u;i+=g)t>.5+(n(Math.floor((k+i)/g),Math.floor((z+e)/g))-.5)*Math.max(0,Math.min(2,w?.ditherStrength??1))&&c.fillRect(k+i,z+e,Math.min(g,u-i),Math.min(g,p-e))}else if(E>0)if(c.globalAlpha=R,N.dots){const e=Math.min(.48,E/(t.glyphs.length-1)*.5*N.dotSize)*Math.min(u,p);c.beginPath(),c.arc(k+.5*u,z+.5*p,e,0,2*Math.PI),c.fill()}else c.drawImage(i,E%t.atlasColumns*t.tileWidth+t.padding,Math.floor(E/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*j,t.cellHeight*j,k,z,u,p),c.globalAlpha=1,c.globalCompositeOperation="source-atop",c.fillRect(k,z,u,p),c.globalCompositeOperation="source-over"}c.globalAlpha=1}else if(d(s.mode)&&N){const{plan:t,packed:e,atlas:i}=N,n=[0,0,0,0,0,0];for(let a=0;a<e.rows;a++)for(let o=0;o<e.cols;o++){const r=4*(Math.min(s.height-1,Math.round((a+.5)/e.rows*(s.height-1)))*s.width+Math.min(s.width-1,Math.round((o+.5)/e.cols*(s.width-1)))),h=(256*s.pixels[r]+s.pixels[r+1])/65535*s.focus[2],l=4*(a*e.cols+o),d=.0137*(o*o*17+a*a*23+o*a*19),f=d-Math.floor(d);"trail"===s.mode&&m(e.indices,e.colors,e.cols,e.rows,o,a,h,(s.pixels[r+2]-128)/127,(s.pixels[r+3]-128)/127,t.glyphs.length,n,s.edgeSafe);const g=e.indices[l]+256*e.indices[l+1],x="trail"===s.mode?Math.round(n[0]):"dissolve"===s.mode?Math.max(0,g-Math.floor(h*t.glyphs.length*(1.35+.9*f))):Math.min(t.glyphs.length-1,g+Math.floor(h*t.glyphs.length*.85));if(x===g&&("trail"!==s.mode||h<1e-4)||e.colors[l+3]<10)continue;const v=o*u,M=a*p;if(c.clearRect(v,M,u,p),c.globalAlpha=("trail"===s.mode?n[5]:e.colors[l+3])/255,N.dots){const e=Math.min(.48,x/(t.glyphs.length-1)*.5*N.dotSize)*Math.min(u,p);c.fillStyle="#fff",c.beginPath(),c.arc(v+.5*u,M+.5*p,e,0,2*Math.PI),c.fill()}else c.drawImage(i,x%t.atlasColumns*t.tileWidth+t.padding,Math.floor(x/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*j,t.cellHeight*j,v,M,u,p);c.globalAlpha=1,c.globalCompositeOperation="source-atop";const y="trail"===s.mode?n[2]:e.colors[l],w="trail"===s.mode?n[3]:e.colors[l+1],S="trail"===s.mode?n[4]:e.colors[l+2],b=Math.max(y,w,S),E="dissolve"===s.mode||"trail"===s.mode?b:Math.max(b,Math.min(112,191*h)),T=b?E/b:0;c.fillStyle=`rgb(${b?y*T:E},${b?w*T:E},${b?S*T:E})`,c.fillRect(v,M,u,p),c.globalCompositeOperation="source-over"}}else if("light"===s.mode||"scan"===s.mode){!t&&L&&_.width===J&&_.height===V||(_.width=J,_.height=V,I.width=J,I.height=V,P.filter="brightness(3)",P.drawImage(e,0,0,J,V),P.filter="none",L=!0);const[i,n,o,h]=s.focus;if(o>.001){X.setTransform(1,0,0,1,0,0),X.clearRect(0,0,J,V),X.drawImage(_,0,0),X.globalCompositeOperation="destination-in",X.save(),X.translate(i*J,n*V),X.scale("scan"===s.mode?.13:1,1);const t=h*Math.min(J,V),e=X.createRadialGradient(0,0,0,0,0,t);e.addColorStop(0,`rgba(255,255,255,${o})`),e.addColorStop(.5,`rgba(255,255,255,${.4*o})`),e.addColorStop(1,"rgba(255,255,255,0)"),X.fillStyle=e,X.fillRect(10*-J,10*-V,20*J,20*V),X.restore(),X.globalCompositeOperation="source-over",c.globalCompositeOperation="source-atop",c.drawImage(I,0,0,a,r),c.globalCompositeOperation="source-over"}}else if(s.strength>0){const t=32,i=18,n=a/t,o=r/i;for(let h=0;h<i;h++)for(let l=0;l<t;l++){const d=(l+.5)/t,f=(h+.5)/i,m=Math.min(s.width-1,Math.round(d*(s.width-1))),g=4*(Math.min(s.height-1,Math.round(f*(s.height-1)))*s.width+m),x=s.pixels,v=Math.min(l*n/u,(a-(l+1)*n)/u,h*o/p,(r-(h+1)*o)/p),M=Math.max(0,Math.min(1,(v-2)/6)),y=s.edgeSafe?M*M*(3-2*M):1,w=(256*x[g]+x[g+1]-32768)/32767*s.strength*y,S=(256*x[g+2]+x[g+3]-32768)/32767*s.strength*y;Math.abs(w)+Math.abs(S)<.02||(c.clearRect(l*n,h*o,n,o),c.drawImage(e,Math.max(0,Math.min(a-n,l*n-w))*e.width/a,Math.max(0,Math.min(r-o,h*o-S))*e.height/r,n*e.width/a,o*e.height/r,l*n,h*o,n,o))}}C.paint(c,H.textMask,a,r)}},destroy(){S=!0,C.destroy(),document.fonts?.removeEventListener("loadingdone",O),G.destroy(),h.removeEventListener("webglcontextlost",W),H(),h.remove(),_.width=_.height=I.width=I.height=1,e.style.visibility=r,l?.getExtension("WEBGL_lose_context")?.loseContext()}}}function w(e,s,n={}){let a,o,r={effect:"trail",strength:.55,radius:n.effect&&"trail"!==n.effect?.2:.45,animated:!1,fps:30,fontSize:7,charAspect:.58,charSpacing:1,paused:!1,charset:" .:-=+*#%@",customText:"",renderMode:"ascii",dotSizeRatio:.72,...n},h=new u(s.clientWidth/Math.max(1,s.clientHeight)),l=!1,c=!1,d=!0,f=0,m=0,g=-1/0,p=0,x=null,v=!1,M=[];const w=matchMedia("(prefers-reduced-motion: reduce)"),S=()=>{l||!c||document.hidden||f||(f=requestAnimationFrame(E))},b=y(e,s,()=>queueMicrotask(()=>{d=!0,S()}));function E(t){if(f=0,l||!c||document.hidden)return;const e=s.clientWidth,n=s.clientHeight;if(!e||!n)return;const u=w.matches||r.paused;h.configure(r.effect,r.strength,r.radius,r.edgeSafe);const y=h.active;if(!u){if(p+=m?Math.min(.1,(t-m)/1e3):0,v){const t=s.getBoundingClientRect();if(t.width&&t.height)for(const e of M)h.move((e.x-t.left)/t.width,(e.y-t.top)/t.height,e.time);M=[],x||h.leave(),v=!1}h.step(m?(t-m)/1e3:1/60)}const E=d||r.animated&&!u&&t-g>=1e3/Math.max(1,Math.min(60,r.fps))-.5;(E||y||h.active||!u&&"none"!==i(r.motion))&&(E&&(a=r.getFrame?.()),b.render(E,h.refraction,e,n,r.fontSize*r.charSpacing,r.fontSize/r.charAspect*r.charSpacing,a,{charset:r.charset,customText:r.customText,renderMode:r.renderMode,dotSizeRatio:r.dotSizeRatio,colorMode:r.colorMode??"fullcolor",accentColor:r.accentColor??"#888888",animationStyle:r.motion,animationSpeed:r.motionSpeed??1,artStyle:"classic",fineDither:r.fineDither,ditherStrength:r.ditherStrength??1},p,{filter:r.filter,edgeEffect:r.edgeEffect,edgeSoftness:r.edgeSoftness,textMask:o?.read()}),E&&(g=t),d=!1),m=t,u||!r.animated&&!h.active&&"none"===i(r.motion)||S()}r.textMask&&(o=t(e,r.textMask,()=>{d=!0,S()}));const T=()=>{h.clear(),x=null,M=[],v=!1,m=0,d=!0,cancelAnimationFrame(f),f=0,S()},A=t=>{if("touch"===t.pointerType||r.paused||w.matches)return;if(t.target instanceof Element&&t.target.closest("button,a,input,summary,select"))return void k();const e=s.getBoundingClientRect();t.clientX<e.left||t.clientX>e.right||t.clientY<e.top||t.clientY>e.bottom?k():(x={x:t.clientX,y:t.clientY,time:t.timeStamp},M.push(x),M.length>6&&M.shift(),v=!0,S())},k=()=>{x=null,v=!0,S()},z=new IntersectionObserver(([t])=>{c=t.isIntersecting,T()});z.observe(e);const R=new ResizeObserver(()=>{h=new u(s.clientWidth/Math.max(1,s.clientHeight)),T()});return R.observe(s),e.addEventListener("pointermove",A),e.addEventListener("pointerleave",k),e.addEventListener("pointercancel",k),document.addEventListener("visibilitychange",T),w.addEventListener("change",T),{get canvas(){return b.canvas},invalidate(){o?.invalidate(),d=!0,S()},update(i){"textMask"in i&&(o?.destroy(),o=i.textMask?t(e,i.textMask,()=>{d=!0,S()}):void 0),r={...r,...i},T()},destroy(){l||(l=!0,cancelAnimationFrame(f),z.disconnect(),R.disconnect(),b.destroy(),o?.destroy(),e.removeEventListener("pointermove",A),e.removeEventListener("pointerleave",k),e.removeEventListener("pointercancel",k),document.removeEventListener("visibilitychange",T),w.removeEventListener("change",T))}}}exports.createTextMask=t,exports.mountHover=w,exports.mountStudioHover=w;//# sourceMappingURL=hover.cjs.map