asciify-engine 1.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -0
- package/dist/core.d.cts +3 -588
- package/dist/core.d.ts +3 -588
- package/dist/hover.cjs +1 -0
- package/dist/hover.d.cts +45 -0
- package/dist/hover.d.ts +45 -0
- package/dist/hover.js +1 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/types-w2CRgyk6.d.cts +589 -0
- package/dist/types-w2CRgyk6.d.ts +589 -0
- package/package.json +13 -2
- package/skills/asciify-engine/SKILL.md +9 -9
- package/skills/asciify-engine/references/layered-hero.md +1 -1
- package/skills/asciify-engine/references/media-api.md +1 -1
- package/skills/asciify-engine/references/studio-effects.md +13 -7
package/dist/hover.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";var t=[{value:"none",mode:"none",label:"Off",description:"Keep the image still. Hover stays available."},{value:"breathe",mode:"print",label:"Living Print",description:"Soft tonal movement. The character grid stays still."},{value:"wave",mode:"current",label:"Slow Current",description:"A gentle flow through the image, with steady edges."},{value:"rain",mode:"reform",label:"Reveal & Reform",description:"Disperse, gather, then hold the complete image."}];function e(e){return"orbit"===e?"current":t.find(t=>t.value===e||t.mode===e)?.mode??"none"}var i=(t,e,i)=>{const s=(t=>Math.max(0,Math.min(1,t)))((i-t)/(e-t));return s*s*(3-2*s)},s=(t,e)=>{const i=(.06711056*t+.00583715*e)%1*52.9829189;return i-Math.floor(i)},n=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 h(t,e,s,n,h){const a=n*Math.PI/6,o=i(0,.12,Math.min(e,1-e,s,1-s));if(h[0]=h[1]=h[2]=0,h[3]=1,"print"===t)h[2]=Math.sin(9*e+5*s+a)*Math.cos(8*s-3*e-a)*.065*o,h[3]=.9+.1*Math.sin(6*e-4*s+a);else if("current"===t)h[0]=Math.sin(9*s+a)*Math.cos(7*e-a)*6*o,h[1]=Math.cos(8*e+a)*Math.sin(6*s-a)*4*o;else if("reform"===t){const t=(n%12+12)%12,a=.8*e+.2*s+.08*Math.sin(8*e-5*s),o=i(1.8+1.2*a,3.8+1.2*a,t)*(1-i(5+1.7*a,7.2+1.7*a,t));h[2]=.95*-o,h[3]=1-.82*o}return h}function a(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 o=class{constructor(t,e){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]=a(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,h=this.height,a=Math.max(1,n/h),o=Math.max(1,h/n),r=s?(t-s.x)*a:0,l=s?(e-s.y)*o:0,c=Math.hypot(r,l);if(!s&&"dissolve"!==this.mode)return;if(s&&c<1e-4)return;const m=.035+.11*Math.max(.1,Math.min(1,i)),d=Math.min(128,Math.max(1,Math.ceil(c/(.45*m)))),u=c?r/c:1,f=c?l/c:0,g=Math.min(1,c/(m*d)*2.2);for(let i=0;i<d;i++){const r=(i+.5)/d,l=s?s.x+(t-s.x)*r:t,c=s?s.y+(e-s.y)*r:e,p=Math.max(0,Math.floor((l-2.5*m/a)*(n-1))),x=Math.min(n-1,Math.ceil((l+2.5*m/a)*(n-1))),v=Math.max(0,Math.floor((c-2.5*m/o)*(h-1))),M=Math.min(h-1,Math.ceil((c+2.5*m/o)*(h-1)));for(let t=v;t<=M;t++)for(let e=p;e<=x;e++){const i=(e/(n-1)-l)*a/m,s=(t/(h-1)-c)*o/m,r=Math.exp(1.5*-(i*i+s*s)),d=t*n+e;if("dissolve"===this.mode)this.ink[d]=Math.max(this.ink[d],r);else{const t=(-i*f+s*u)*r*2.8,e="silk"===this.mode?u*t:-s*r*2.5,n="silk"===this.mode?f*t:i*r*2.5;this.targetX[d]=Math.max(-1,Math.min(1,this.targetX[d]+e*g)),this.targetY[d]=Math.max(-1,Math.min(1,this.targetY[d]+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.edge[t]*32767+32768),h=Math.round(this.offsetY[t]*this.edge[t]*32767+32768);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=h>>>8,this.pixels[4*t+3]=255&h}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))),h=Math.floor(s),o=Math.floor(n),r=s-h,l=n-o,c=o*this.width+h,m=c+this.width,d=t=>(t[c]*(1-r)+t[c+1]*r)*(1-l)+(t[m]*(1-r)+t[m+1]*r)*l;i[0]=d(this.offsetX)*a(t,e,this.width/this.height)*.08,i[1]=d(this.offsetY)*a(t,e,this.width/this.height)*.08,i[2]="dissolve"===this.mode?6*-d(this.ink):0}},r=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 h of this.rings){const a=.015+h.age*(.15+.22*h.radius),o=.009+.014*h.radius,r=(Math.hypot((t-h.x)*i,(e-h.y)*s)-a)/o,l=Math.sin(Math.min(1,h.age/.07)*Math.PI/2)*Math.max(0,1-h.age/1.35)**2;n=Math.max(n,Math.exp(-r*r)*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 h=Math.round(65535*this.echo[n]),a=4*n;this.pixels[a]=h>>>8,this.pixels[a+1]=255&h}this.echoPeak=s,this.active||(this.echo.fill(0),this.pixels.fill(0))}},l=class{constructor(t,e){this.previous=null,this.remainder=0,this.peak=0,this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*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.pixels])t.fill(0);this.previous=null,this.peak=0,this.remainder=0}leave(){this.previous=null}move(t,e,i){const s=this.previous;if(this.previous={x:t,y:e},!s)return;const n=(t-s.x)*(this.width-1),h=(e-s.y)*(this.height-1),a=Math.hypot(n,h);if(a<.001)return;const o=Math.min(this.width,this.height)*(.038+.12*i),r=Math.min(128,Math.max(1,Math.ceil(a/(.5*o)))),l=a/o*.95/r;for(let i=0;i<r;i++){const a=(i+.5)/r,c=(s.x+(t-s.x)*a)*(this.width-1),m=(s.y+(e-s.y)*a)*(this.height-1),d=Math.max(1,Math.floor(c-2.5*o)),u=Math.min(this.width-2,Math.ceil(c+2.5*o)),f=Math.max(1,Math.floor(m-2.5*o)),g=Math.min(this.height-2,Math.ceil(m+2.5*o));for(let t=f;t<=g;t++)for(let e=d;e<=u;e++){const i=Math.exp(-((e-c)**2+(t-m)**2)/(o*o)),s=t*this.width+e;this.ink[s]=Math.min(1,this.ink[s]+i*l),this.u[s]=Math.max(-50,Math.min(50,this.u[s]+n*i*5/r)),this.v[s]=Math.max(-50,Math.min(50,this.v[s]+h*i*5/r))}}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))),h=e-s,a=i-n,o=n*this.width+s;return(t[o]*(1-h)+t[o+1]*h)*(1-a)+(t[o+this.width]*(1-h)+t[o+this.width+1]*h)*a}sample(t,e){return this.read(this.ink,t*(this.width-1),e*(this.height-1))}step(t){if(!this.active||!Number.isFinite(t))return;this.remainder+=Math.max(0,Math.min(.05,t));const e=1/60,i=this.width,s=this.height;for(;this.remainder+1e-8>=e;){this.remainder-=e;for(let t=1;t<s-1;t++)for(let s=1;s<i-1;s++){const n=t*i+s,h=s-this.u[n]*e,a=t-this.v[n]*e;this.a[n]=.972*this.read(this.u,h,a),this.b[n]=.972*this.read(this.v,h,a),this.c[n]=.964*this.read(this.ink,h,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 t=1;t<s-1;t++)for(let s=1;s<i-1;s++){const n=t*i+s,h=Math.abs(this.curl[n+1])-Math.abs(this.curl[n-1]),a=Math.abs(this.curl[n+i])-Math.abs(this.curl[n-i]),o=Math.hypot(h,a)+1e-4,r=this.curl[n]*e*5;this.u[n]+=a/o*r,this.v[n]-=h/o*r,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 t=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(-50,Math.min(50,this.u[n]-.5*(this.pressure[n+1]-this.pressure[n-1]))),this.v[n]=Math.max(-50,Math.min(50,this.v[n]-.5*(this.pressure[n+i]-this.pressure[n-i]))),t=Math.max(t,this.ink[n])}this.peak=t}if(this.active)for(let t=0;t<this.ink.length;t++){const e=Math.round(65535*Math.min(1,this.ink[t]));this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e}else this.clear()}},c=t=>["trail","contour","dissolve"].includes(t),m=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]=a(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 l(this.columns,this.rows),this.contour=new r(this.columns,this.rows),this.afterimage=new o(this.columns,this.rows),this.clear()}get densityTrail(){return["trail","contour","dissolve"].includes(this.mode)}get hasRefraction(){return this.active||this.lensStrength>15e-5}get active(){return["dissolve","silk","vortex"].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||this.afterimage.setMode(t),this.refraction.pixels="trail"===t?this.trail.pixels:"contour"===t?this.contour.pixels:["dissolve","silk","vortex"].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:0)}configure(t,e=.55,i=.2){this.setMode(t),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:0,this.refraction.focus[2]=this.densityTrail?this.amount:this.lensStrength*this.amount,this.refraction.focus[3]=this.radius}move(t,e){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"].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);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 i=this.previous;this.previous={x:t,y:e};const s=(t-i.x)*this.xScale,n=(e-i.y)*this.yScale,h=Math.hypot(s,n);if(h<1e-5)return;const a=.035+.1*this.radius,o=3*a,r=Math.min(192,Math.max(1,Math.ceil(h/(.5*a)))),l=95*Math.min(h,1)/r;for(let s=0;s<r;s++){const n=(s+.5)/r,h=i.x+(t-i.x)*n,c=i.y+(e-i.y)*n,m=Math.max(1,Math.floor((h-o/this.xScale)*(this.columns-1))),d=Math.min(this.columns-2,Math.ceil((h+o/this.xScale)*(this.columns-1))),u=Math.max(1,Math.floor((c-o/this.yScale)*(this.rows-1))),f=Math.min(this.rows-2,Math.ceil((c+o/this.yScale)*(this.rows-1)));for(let t=u;t<=f;t++)for(let e=m;e<=d;e++){const i=(e/(this.columns-1)-h)*this.xScale,s=(t/(this.rows-1)-c)*this.yScale,n=Math.exp(-(i*i+s*s)/(a*a))*l,o=t*this.columns+e;this.velocity[o]=Math.max(-12,this.velocity[o]-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"].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 h=1;h<this.columns-1;h++){const a=n*this.columns+h,o=this.heights[a],r=this.heights[a-1]+this.heights[a+1]+this.heights[a-this.columns]+this.heights[a+this.columns]-4*o,l=Math.min(h,n,this.columns-1-h,this.rows-1-n),c=l<5?.88+.024*l:1,m=(this.velocity[a]+(r*s*s-8*o)*e)*i*c;this.velocity[a]=m,this.next[a]=(o+m*e)*c,t=Math.max(t,Math.abs(this.next[a]),.1*Math.abs(m))}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,h=(this.heights[Math.max(0,t-1)*this.columns+e]-this.heights[Math.min(this.rows-1,t+1)*this.columns+e])*n,a=Math.tanh(s)*this.edgeMask[i],o=Math.tanh(h)*this.edgeMask[i];this.normals[2*i]=a,this.normals[2*i+1]=o;const r=Math.round(32767*a+32768),l=Math.round(32767*o+32768);this.refraction.pixels[4*i]=r>>>8,this.refraction.pixels[4*i+1]=255&r,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"].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))),h=Math.floor(s),a=Math.floor(n),o=s-h,r=n-a,l=2*(a*this.columns+h),c=l+2*this.columns;for(let t=0;t<2;t++)i[t]=.08*((this.normals[l+t]*(1-o)+this.normals[l+2+t]*o)*(1-r)+(this.normals[c+t]*(1-o)+this.normals[c+2+t]*o)*r);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,h,a,o]=i.focus,r=(t-n)*Math.max(1,s)/o,l=(e-h)*Math.max(1,1/s)/o;return"light"===i.mode?Math.exp(-3*(r*r+l*l))*a:"scan"===i.mode?(1.3*Math.exp(-r*r*110)-.3*Math.exp(-r*r*12))*Math.exp(-l*l*1.8)*a:0}(t,e,this.refraction,this.xScale/this.yScale):0}};function d(t){const e=Intl.Segmenter;return e?Array.from(new e(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function u(){const t=document.createElement("canvas"),e=document.createElement("canvas"),i=t.getContext("2d"),s=e.getContext("2d",{willReadFrequently:!0});let n,h=null;return{update(a,o,r,l,c,m,u,f){const g=u?.[0]?.length??Math.max(1,Math.round(o/l)),p=u?.length??Math.max(1,Math.round(r/c)),x=f??{charset:" .:-=+*#%@",customText:"",colorMode:"fullcolor"},v=x.charset+(x.charsetFrames?.join("")??""),M=function(t,e,i,s,n,h,a,o=4096,r=0){if(![i,s,n,h,a,o].every(t=>Number.isFinite(t)&&t>0))throw new Error("Glyph atlas dimensions must be positive and finite.");const l=JSON.stringify([e.charset,e.customText,i,s,n,h,a,o,r]);if(t?.key===l)return t;const c=[...new Set([" ",...d(e.charset),...d(e.customText??"")])];if(c.length>65536)throw new Error("This character set exceeds the glyph texture capacity.");const m=i/h,u=s/a,f=.9*Math.min(m/.55,u),g=Math.ceil(m*n)+4,p=Math.ceil(u*n)+4,x=Math.min(c.length,Math.floor(o/g));if(x<1||Math.ceil(c.length/x)*p>o)throw new Error("The glyph atlas exceeds the available GPU texture size.");return{key:l,glyphs:c,indices:new Map(c.map((t,e)=>[t,e])),cellWidth:m,cellHeight:u,fontSize:f,padding:2,tileWidth:g,tileHeight:p,atlasColumns:x,atlasWidth:x*g,atlasHeight:Math.ceil(c.length/x)*p}}(h,{...x,charset:v},o,r,m,g,p);if(M!==h&&(t.width=M.atlasWidth,t.height=M.atlasHeight,i.font=M.fontSize*m+'px "JetBrains Mono", monospace',i.textAlign="center",i.textBaseline="middle",i.fillStyle="#fff",M.glyphs.forEach((t,e)=>i.fillText(t,e%M.atlasColumns*M.tileWidth+M.padding+M.cellWidth*m*.5,Math.floor(e/M.atlasColumns)*M.tileHeight+M.padding+M.cellHeight*m*.5))),h=M,u){n=function(t,e,i,s,n=!1){const h=t.length,a=t[0]?.length??0,o=s?.cols===a&&s.rows===h?s:{cols:a,rows:h,indices:new Uint8Array(a*h*4),colors:new Uint8ClampedArray(a*h*4)};o.indices.fill(0),o.colors.fill(0);const r="accent"===i.colorMode?[1,3,5].map(t=>parseInt(i.accentColor.slice(t,t+2),16)):null;for(let s=0;s<h;s++){if(t[s].length!==a)throw new Error("Glyph frames must have a rectangular cell grid.");for(let h=0;h<a;h++){const l=t[s][h];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 m=4*(s*a+h);let d=l.r,u=l.g,f=l.b;if(r)[d,u,f]=r;else if("fullcolor"!==i.colorMode){const t=.299*d+.587*u+.114*f|0;d="matrix"===i.colorMode?0:t,u=t,f=d}(n||0!==d||0!==u||0!==f)&&(o.indices[m]=255&c,o.indices[m+1]=c>>>8,o.colors[m]=d,o.colors[m+1]=u,o.colors[m+2]=f,o.colors[m+3]=l.a)}}return o}(u,h.indices,x,n,!0);for(let t=0;t<p;t++)for(let e=0;e<g;e++){const i=u[t][e],s=4*(t*g+e);n.indices[s+2]=Math.round(.299*i.r+.587*i.g+.114*i.b)}}else{e.width=g,e.height=p,s.drawImage(a,0,0,g,p);const t=s.getImageData(0,0,g,p).data;n={cols:g,rows:p,indices:new Uint8Array(g*p*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);n.indices[e+2]=Math.round(255*i),n.indices[e]=Math.min(h.glyphs.length-1,Math.floor(i*h.glyphs.length))}}return{plan:h,packed:n,atlas:t,dots:"dots"===x.renderMode,dotSize:x.dotSizeRatio??.72}},destroy(){t.width=t.height=e.width=e.height=1,n=void 0,h=null}}}function f(t,i,a={}){let o={effect:"trail",strength:.55,radius:.2,animated:!1,fps:30,fontSize:7,charAspect:.58,charSpacing:1,paused:!1,charset:" .:-=+*#%@",customText:"",renderMode:"ascii",dotSizeRatio:.72,...a},r=new m(i.clientWidth/Math.max(1,i.clientHeight)),l=!1,d=!1,f=!0,g=0,p=0,x=-1/0,v=0,M=null,w=!1;const y=matchMedia("(prefers-reduced-motion: reduce)"),S=()=>{l||!d||document.hidden||g||(g=requestAnimationFrame(b))},E=function(t,i,a){const o=i.style.visibility;let r=document.createElement("canvas"),l=null,m=null,d=null,f=null;const g=[],p=[];let x=!1,v=!1,M=4096;const w=document.createElement("canvas"),y=document.createElement("canvas"),S=w.getContext("2d"),E=y.getContext("2d");let b=!1;const T=new n,A=u();let k=null,R={};const z=()=>{r.className="ascii-hover-surface",r.style.cssText="position:absolute;inset:0;width:100%;height:100%;pointer-events:none",r.setAttribute("role","img"),r.setAttribute("aria-label","ASCII artwork preview"),r.dataset.renderer=l?"surface-gpu":"surface-canvas",t.prepend(r),i.style.visibility="hidden"},_=()=>{l&&(g.forEach(t=>l.deleteTexture(t)),p.forEach(t=>l.deleteShader(t)),d&&l.deleteProgram(d),f&&l.deleteBuffer(f))},D=()=>{x||m||(r.removeEventListener("webglcontextlost",D),_(),r.remove(),l=null,r=document.createElement("canvas"),m=r.getContext("2d"),z(),a())};try{if(l=r.getContext("webgl",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!0}),!l)throw new Error("WebGL unavailable");M=l.getParameter(l.MAX_TEXTURE_SIZE);const t=(t,e)=>{const i=l.createShader(t);if(p.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(d=l.createProgram(),l.attachShader(d,t(l.VERTEX_SHADER,"attribute vec2 position; void main(){gl_Position=vec4(position,0.,1.);}")),l.attachShader(d,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;\n \nuniform float trailAmount;\nuniform float trailKind;\nfloat trailDensity(vec2 address) {\n vec2 encodedInk = texture2D(surface, (address * (surfaceSize - 1.0) + .5) / surfaceSize).rg;\n return (encodedInk.r * 65280.0 + encodedInk.g * 255.0) / 65535.0 * trailAmount;\n}\nvec3 trailInk(vec3 ink, float density) {\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 trailIndex(float index, float count, float density, vec2 cell) {\n // A stable cell pattern gives Dissolve a granular edge without random flicker.\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 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) return vec4(0.,0.,sin(uv.x*9.+uv.y*5.+p)*cos(uv.y*8.-uv.x*3.-p)*.065*edge,.9+.1*sin(uv.x*6.-uv.y*4.+p));\n if(motionMode<2.5) return vec4(sin(uv.y*9.+p)*cos(uv.x*7.-p)*6.*edge,cos(uv.x*8.+p)*sin(uv.y*6.-p)*4.*edge,0.,1.);\n float cycle=mod(motionTime,12.);\n float order=uv.x*.8+uv.y*.2+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(0.,0.,-gone*.95,1.-gone*.82);\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 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 * smoothstep(2.,8.,min(edge.x,edge.y)) / sceneSize;\n }\n vec4 ambient=ambientAt(uv);\n uv -= ambient.xy/sceneSize;\n pixel=uv*sceneSize;\n vec4 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 float tone=clamp(toneAt(uv)+ambient.z+(trailKind>1.5?-density:density)*.65,0.,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 gl_FragColor=vec4(hue*alpha,alpha); return;\n }\n if (trailAmount > 0. || 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 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 gl_FragColor = vec4(lit, ink.a)*ambient.w;\n }")),l.linkProgram(d),!l.getProgramParameter(d,l.LINK_STATUS))throw new Error("Surface shader link failed");l.useProgram(d),f=l.createBuffer(),l.bindBuffer(l.ARRAY_BUFFER,f),l.bufferData(l.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),l.STATIC_DRAW);const e=l.getAttribLocation(d,"position");l.enableVertexAttribArray(e),l.vertexAttribPointer(e,2,l.FLOAT,!1,0,0),R=Object.fromEntries(["artwork","surface","resolution","sceneSize","cellSize","surfaceSize","strength","hoverMode","hoverFocus","codes","cellColors","glyphAtlas","gridSize","atlasSize","tileSize","atlasColumns","padding","glyphCount","dots","dotSize","trailKind","trailAmount","motionMode","motionTime","fineDither","ditherAmount"].map(t=>[t,l.getUniformLocation(d,t)]));for(let t=0;t<5;t++){const e=l.createTexture();g.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(R.codes,2),l.uniform1i(R.cellColors,3),l.uniform1i(R.glyphAtlas,4),l.uniform1i(R.artwork,0),l.uniform1i(R.surface,1),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),l.disable(l.BLEND),l.disable(l.DEPTH_TEST),r.addEventListener("webglcontextlost",D),z()}catch{D()}return{get canvas(){return r},render(t,n,a,o,d,u,f,p,z=0){const _=e(p?.animationStyle),C=T.update(z,_,p?.animationSpeed),F=(t=>"terminal"===t?.artStyle&&!t.customText&&!t.charsetFrames?.length&&!/[A-Za-z]/.test(t.charset??""))(p);if(x||!a||!o||!i.width||!i.height)return;const I=Math.min(i.width/a,1.5,Math.sqrt(4e6/(a*o))),P=Math.max(1,Math.round(a*I)),U=Math.max(1,Math.round(o*I));if(r.width===P&&r.height===U||(r.width=P,r.height=U,v=!1),l&&(i.width>M||i.height>M)&&D(),(c(n.mode)||"none"!==_||F)&&(t||!v||!k)&&(k=A.update(i,a,o,d,u,I,f,p),l)){const{plan:t,packed:e,atlas:i}=k;for(const[t,i]of[[2,e.indices],[3,e.colors]])l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,g[t]),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,l.NEAREST),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,l.NEAREST),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,e.cols,e.rows,0,l.RGBA,l.UNSIGNED_BYTE,i);l.activeTexture(l.TEXTURE4),l.bindTexture(l.TEXTURE_2D,g[4]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),l.uniform2f(R.gridSize,e.cols,e.rows),l.uniform2f(R.atlasSize,t.atlasWidth,t.atlasHeight),l.uniform2f(R.tileSize,t.tileWidth,t.tileHeight),l.uniform1f(R.atlasColumns,t.atlasColumns),l.uniform1f(R.padding,t.padding),l.uniform1f(R.glyphCount,t.glyphs.length),l.uniform1f(R.dots,k.dots?1:0),l.uniform1f(R.dotSize,k.dotSize)}if(l)l.uniform1f(R.motionMode,(t=>["none","print","current","reform"].indexOf(t))(_)),l.uniform1f(R.motionTime,C),l.uniform1f(R.fineDither,F?1:0),l.uniform1f(R.ditherAmount,Math.max(0,Math.min(2,p?.ditherStrength??1))),l.uniform1f(R.trailKind,"dissolve"===n.mode?2:0),l.uniform1f(R.trailAmount,c(n.mode)?n.focus[2]:0),l.viewport(0,0,P,U),!t&&v||(l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,g[0]),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),v=!0),l.activeTexture(l.TEXTURE1),l.bindTexture(l.TEXTURE_2D,g[1]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,n.width,n.height,0,l.RGBA,l.UNSIGNED_BYTE,n.pixels),l.uniform2f(R.resolution,P,U),l.uniform2f(R.sceneSize,a,o),l.uniform2f(R.cellSize,d,u),l.uniform2f(R.surfaceSize,n.width,n.height),l.uniform1f(R.strength,n.strength),l.uniform1f(R.hoverMode,"light"===n.mode?1:"scan"===n.mode?2:0),l.uniform4fv(R.hoverFocus,n.focus),l.drawArrays(l.TRIANGLE_STRIP,0,4);else if(m)if(m.setTransform(I,0,0,I,0,0),m.clearRect(0,0,a,o),m.drawImage(i,0,0,a,o),("none"!==_||F)&&k){m.clearRect(0,0,a,o);const{plan:t,packed:e,atlas:i}=k,r=[0,0,0,1],l=Math.max(1,.32*Math.min(d,u));for(let a=0;a<e.rows;a++)for(let o=0;o<e.cols;o++){const f=(o+.5)/e.cols,g=(a+.5)/e.rows,x=4*(a*e.cols+o);if(e.colors[x+3]<10)continue;h(_,f,g,C,r);const v=4*(Math.min(n.height-1,Math.round(g*(n.height-1)))*n.width+Math.min(n.width-1,Math.round(f*(n.width-1)))),M=c(n.mode)?(256*n.pixels[v]+n.pixels[v+1])/65535*n.focus[2]:0,w=e.indices[x]+256*e.indices[x+1];let y="dissolve"===n.mode?w-Math.floor(M*t.glyphs.length*1.8):w+Math.floor(M*t.glyphs.length*.85);w>0&&(y+=r[2]*(t.glyphs.length-1)),y=Math.max(0,Math.min(t.glyphs.length-1,Math.floor(y+s(o,a))));let S=r[0],E=r[1];n.strength>0&&(S+=(256*n.pixels[v]+n.pixels[v+1]-32768)/32767*n.strength,E+=(256*n.pixels[v+2]+n.pixels[v+3]-32768)/32767*n.strength);const b=o*d+S,T=a*u+E,A=e.colors[x+3]/255*r[3],R=F?Math.max(1,e.colors[x],e.colors[x+1],e.colors[x+2]):255,z=`rgb(${e.colors[x]/R*255},${e.colors[x+1]/R*255},${e.colors[x+2]/R*255})`;if(m.fillStyle=z,F){const t=Math.max(0,Math.min(1,e.indices[x+2]/255+r[2]+.65*("dissolve"===n.mode?-M:M)));m.globalAlpha=A;for(let e=0;e<u;e+=l)for(let i=0;i<d;i+=l)t>.5+(s(Math.floor((b+i)/l),Math.floor((T+e)/l))-.5)*Math.max(0,Math.min(2,p?.ditherStrength??1))&&m.fillRect(b+i,T+e,Math.min(l,d-i),Math.min(l,u-e))}else if(y>0)if(m.globalAlpha=A,k.dots){const e=Math.min(.48,y/(t.glyphs.length-1)*.5*k.dotSize)*Math.min(d,u);m.beginPath(),m.arc(b+.5*d,T+.5*u,e,0,2*Math.PI),m.fill()}else m.drawImage(i,y%t.atlasColumns*t.tileWidth+t.padding,Math.floor(y/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*I,t.cellHeight*I,b,T,d,u),m.globalAlpha=1,m.globalCompositeOperation="source-atop",m.fillRect(b,T,d,u),m.globalCompositeOperation="source-over"}m.globalAlpha=1}else if(c(n.mode)&&k){const{plan:t,packed:e,atlas:i}=k;for(let s=0;s<e.rows;s++)for(let h=0;h<e.cols;h++){const a=4*(Math.min(n.height-1,Math.round((s+.5)/e.rows*(n.height-1)))*n.width+Math.min(n.width-1,Math.round((h+.5)/e.cols*(n.width-1)))),o=(256*n.pixels[a]+n.pixels[a+1])/65535*n.focus[2],r=4*(s*e.cols+h),l=.0137*(h*h*17+s*s*23+h*s*19),c=l-Math.floor(l),f=e.indices[r]+256*e.indices[r+1],g="dissolve"===n.mode?Math.max(0,f-Math.floor(o*t.glyphs.length*(1.35+.9*c))):Math.min(t.glyphs.length-1,f+Math.floor(o*t.glyphs.length*.85));if(g===f||e.colors[r+3]<10)continue;const p=h*d,x=s*u;if(m.clearRect(p,x,d,u),m.globalAlpha=e.colors[r+3]/255,k.dots){const e=Math.min(.48,g/(t.glyphs.length-1)*.5*k.dotSize)*Math.min(d,u);m.fillStyle="#fff",m.beginPath(),m.arc(p+.5*d,x+.5*u,e,0,2*Math.PI),m.fill()}else m.drawImage(i,g%t.atlasColumns*t.tileWidth+t.padding,Math.floor(g/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*I,t.cellHeight*I,p,x,d,u);m.globalAlpha=1,m.globalCompositeOperation="source-atop";const v=Math.max(e.colors[r],e.colors[r+1],e.colors[r+2]),M="dissolve"===n.mode?v:Math.max(v,Math.min(112,191*o)),w=v?M/v:0;m.fillStyle=`rgb(${v?e.colors[r]*w:M},${v?e.colors[r+1]*w:M},${v?e.colors[r+2]*w:M})`,m.fillRect(p,x,d,u),m.globalCompositeOperation="source-over"}}else if("light"===n.mode||"scan"===n.mode){!t&&b&&w.width===P&&w.height===U||(w.width=P,w.height=U,y.width=P,y.height=U,S.filter="brightness(3)",S.drawImage(i,0,0,P,U),S.filter="none",b=!0);const[e,s,h,r]=n.focus;if(h>.001){E.setTransform(1,0,0,1,0,0),E.clearRect(0,0,P,U),E.drawImage(w,0,0),E.globalCompositeOperation="destination-in",E.save(),E.translate(e*P,s*U),E.scale("scan"===n.mode?.13:1,1);const t=r*Math.min(P,U),i=E.createRadialGradient(0,0,0,0,0,t);i.addColorStop(0,`rgba(255,255,255,${h})`),i.addColorStop(.5,`rgba(255,255,255,${.4*h})`),i.addColorStop(1,"rgba(255,255,255,0)"),E.fillStyle=i,E.fillRect(10*-P,10*-U,20*P,20*U),E.restore(),E.globalCompositeOperation="source-over",m.globalCompositeOperation="source-atop",m.drawImage(y,0,0,a,o),m.globalCompositeOperation="source-over"}}else if(n.strength>0){const t=32,e=18,s=a/t,h=o/e;for(let r=0;r<e;r++)for(let l=0;l<t;l++){const c=(l+.5)/t,f=(r+.5)/e,g=Math.min(n.width-1,Math.round(c*(n.width-1))),p=4*(Math.min(n.height-1,Math.round(f*(n.height-1)))*n.width+g),x=n.pixels,v=Math.min(l*s/d,(a-(l+1)*s)/d,r*h/u,(o-(r+1)*h)/u),M=Math.max(0,Math.min(1,(v-2)/6)),w=M*M*(3-2*M),y=(256*x[p]+x[p+1]-32768)/32767*n.strength*w,S=(256*x[p+2]+x[p+3]-32768)/32767*n.strength*w;Math.abs(y)+Math.abs(S)<.02||(m.clearRect(l*s,r*h,s,h),m.drawImage(i,Math.max(0,Math.min(a-s,l*s-y))*i.width/a,Math.max(0,Math.min(o-h,r*h-S))*i.height/o,s*i.width/a,h*i.height/o,l*s,r*h,s,h))}}},destroy(){x=!0,A.destroy(),r.removeEventListener("webglcontextlost",D),_(),r.remove(),w.width=w.height=y.width=y.height=1,i.style.visibility=o,l?.getExtension("WEBGL_lose_context")?.loseContext()}}}(t,i,()=>queueMicrotask(()=>{f=!0,S()}));function b(t){if(g=0,l||!d||document.hidden)return;const s=i.clientWidth,n=i.clientHeight;if(!s||!n)return;const h=y.matches||o.paused;r.configure(o.effect,o.strength,o.radius);const a=r.active;if(!h){if(v+=p?Math.min(.1,(t-p)/1e3):0,w){const t=i.getBoundingClientRect();M&&t.width&&t.height?r.move((M.x-t.left)/t.width,(M.y-t.top)/t.height):r.leave(),w=!1}r.step(p?(t-p)/1e3:1/60)}const c=f||o.animated&&!h&&t-x>=1e3/Math.max(1,Math.min(60,o.fps))-.5;(c||a||r.active||!h&&"none"!==e(o.motion))&&(E.render(c,r.refraction,s,n,o.fontSize*o.charSpacing,o.fontSize/o.charAspect*o.charSpacing,c?o.getFrame?.():void 0,{charset:o.charset,customText:o.customText,renderMode:o.renderMode,dotSizeRatio:o.dotSizeRatio,colorMode:"fullcolor",animationStyle:o.motion,animationSpeed:o.motionSpeed??1,artStyle:o.fineDither?"terminal":"classic",ditherStrength:o.ditherStrength??1},v),c&&(x=t),f=!1),p=t,h||!o.animated&&!r.active&&"none"===e(o.motion)||S()}const T=()=>{r.clear(),M=null,w=!1,p=0,f=!0,cancelAnimationFrame(g),g=0,S()},A=t=>{if("touch"===t.pointerType||o.paused||y.matches)return;if(t.target instanceof Element&&t.target.closest("button,a,input,summary,select"))return void k();const e=i.getBoundingClientRect();t.clientX<e.left||t.clientX>e.right||t.clientY<e.top||t.clientY>e.bottom?k():(M={x:t.clientX,y:t.clientY},w=!0,S())},k=()=>{M=null,w=!0,S()},R=new IntersectionObserver(([t])=>{d=t.isIntersecting,T()});R.observe(t);const z=new ResizeObserver(()=>{r=new m(i.clientWidth/Math.max(1,i.clientHeight)),T()});return z.observe(i),t.addEventListener("pointermove",A),t.addEventListener("pointerleave",k),t.addEventListener("pointercancel",k),document.addEventListener("visibilitychange",T),y.addEventListener("change",T),{get canvas(){return E.canvas},invalidate(){f=!0,S()},update(t){o={...o,...t},T()},destroy(){l||(l=!0,cancelAnimationFrame(g),R.disconnect(),z.disconnect(),E.destroy(),t.removeEventListener("pointermove",A),t.removeEventListener("pointerleave",k),t.removeEventListener("pointercancel",k),document.removeEventListener("visibilitychange",T),y.removeEventListener("change",T))}}}exports.mountHover=f,exports.mountStudioHover=f;//# sourceMappingURL=hover.cjs.map
|
package/dist/hover.d.cts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { d as AsciiFrame } from './types-w2CRgyk6.cjs';
|
|
2
|
+
|
|
3
|
+
type AmbientMotion = 'none' | 'print' | 'current' | 'reform';
|
|
4
|
+
|
|
5
|
+
type HeroHover = 'none' | 'trail' | 'water' | 'contour' | 'dissolve' | 'silk' | 'vortex' | 'light' | 'scan';
|
|
6
|
+
|
|
7
|
+
type StudioHoverEffect = Exclude<HeroHover, 'light' | 'scan'>;
|
|
8
|
+
interface StudioHoverOptions {
|
|
9
|
+
effect?: StudioHoverEffect;
|
|
10
|
+
/** Supply the current object frame when available for exact glyph identities/colors. */
|
|
11
|
+
getFrame?: () => AsciiFrame | undefined;
|
|
12
|
+
motion?: AmbientMotion;
|
|
13
|
+
motionSpeed?: number;
|
|
14
|
+
fineDither?: boolean;
|
|
15
|
+
ditherStrength?: number;
|
|
16
|
+
strength?: number;
|
|
17
|
+
radius?: number;
|
|
18
|
+
/** Enable when the backing canvas is drawn by a video/animation renderer. */
|
|
19
|
+
animated?: boolean;
|
|
20
|
+
fps?: number;
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
charAspect?: number;
|
|
23
|
+
/** Match the backing engine character spacing. Default: 1. */
|
|
24
|
+
charSpacing?: number;
|
|
25
|
+
paused?: boolean;
|
|
26
|
+
charset?: string;
|
|
27
|
+
customText?: string;
|
|
28
|
+
renderMode?: 'ascii' | 'dots';
|
|
29
|
+
dotSizeRatio?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Optional studio surface layer. Keep engine hoverStrength: 0 to avoid two effects.
|
|
32
|
+
* Host must be a positioned wrapper fitted to the source canvas, e.g. width:fit-content.
|
|
33
|
+
* Call invalidate() after drawing a new still; destroy() before removing the host.
|
|
34
|
+
*/
|
|
35
|
+
declare function mountStudioHover(host: HTMLElement, source: HTMLCanvasElement, initial?: StudioHoverOptions): {
|
|
36
|
+
/** Visible canvas; use this for CSS layering or snapshots. */
|
|
37
|
+
readonly canvas: HTMLCanvasElement;
|
|
38
|
+
invalidate(): void;
|
|
39
|
+
update(next: StudioHoverOptions): void;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type HoverController = ReturnType<typeof mountStudioHover>;
|
|
44
|
+
|
|
45
|
+
export { type AmbientMotion, type HoverController, type StudioHoverOptions as HoverOptions, type StudioHoverEffect, type StudioHoverOptions, mountStudioHover as mountHover, mountStudioHover };
|
package/dist/hover.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { d as AsciiFrame } from './types-w2CRgyk6.js';
|
|
2
|
+
|
|
3
|
+
type AmbientMotion = 'none' | 'print' | 'current' | 'reform';
|
|
4
|
+
|
|
5
|
+
type HeroHover = 'none' | 'trail' | 'water' | 'contour' | 'dissolve' | 'silk' | 'vortex' | 'light' | 'scan';
|
|
6
|
+
|
|
7
|
+
type StudioHoverEffect = Exclude<HeroHover, 'light' | 'scan'>;
|
|
8
|
+
interface StudioHoverOptions {
|
|
9
|
+
effect?: StudioHoverEffect;
|
|
10
|
+
/** Supply the current object frame when available for exact glyph identities/colors. */
|
|
11
|
+
getFrame?: () => AsciiFrame | undefined;
|
|
12
|
+
motion?: AmbientMotion;
|
|
13
|
+
motionSpeed?: number;
|
|
14
|
+
fineDither?: boolean;
|
|
15
|
+
ditherStrength?: number;
|
|
16
|
+
strength?: number;
|
|
17
|
+
radius?: number;
|
|
18
|
+
/** Enable when the backing canvas is drawn by a video/animation renderer. */
|
|
19
|
+
animated?: boolean;
|
|
20
|
+
fps?: number;
|
|
21
|
+
fontSize?: number;
|
|
22
|
+
charAspect?: number;
|
|
23
|
+
/** Match the backing engine character spacing. Default: 1. */
|
|
24
|
+
charSpacing?: number;
|
|
25
|
+
paused?: boolean;
|
|
26
|
+
charset?: string;
|
|
27
|
+
customText?: string;
|
|
28
|
+
renderMode?: 'ascii' | 'dots';
|
|
29
|
+
dotSizeRatio?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Optional studio surface layer. Keep engine hoverStrength: 0 to avoid two effects.
|
|
32
|
+
* Host must be a positioned wrapper fitted to the source canvas, e.g. width:fit-content.
|
|
33
|
+
* Call invalidate() after drawing a new still; destroy() before removing the host.
|
|
34
|
+
*/
|
|
35
|
+
declare function mountStudioHover(host: HTMLElement, source: HTMLCanvasElement, initial?: StudioHoverOptions): {
|
|
36
|
+
/** Visible canvas; use this for CSS layering or snapshots. */
|
|
37
|
+
readonly canvas: HTMLCanvasElement;
|
|
38
|
+
invalidate(): void;
|
|
39
|
+
update(next: StudioHoverOptions): void;
|
|
40
|
+
destroy(): void;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
type HoverController = ReturnType<typeof mountStudioHover>;
|
|
44
|
+
|
|
45
|
+
export { type AmbientMotion, type HoverController, type StudioHoverOptions as HoverOptions, type StudioHoverEffect, type StudioHoverOptions, mountStudioHover as mountHover, mountStudioHover };
|
package/dist/hover.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var t=[{value:"none",mode:"none",label:"Off",description:"Keep the image still. Hover stays available."},{value:"breathe",mode:"print",label:"Living Print",description:"Soft tonal movement. The character grid stays still."},{value:"wave",mode:"current",label:"Slow Current",description:"A gentle flow through the image, with steady edges."},{value:"rain",mode:"reform",label:"Reveal & Reform",description:"Disperse, gather, then hold the complete image."}];function e(e){return"orbit"===e?"current":t.find(t=>t.value===e||t.mode===e)?.mode??"none"}var i=(t,e,i)=>{const s=(t=>Math.max(0,Math.min(1,t)))((i-t)/(e-t));return s*s*(3-2*s)},s=(t,e)=>{const i=(.06711056*t+.00583715*e)%1*52.9829189;return i-Math.floor(i)},n=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 h(t,e,s,n,h){const a=n*Math.PI/6,o=i(0,.12,Math.min(e,1-e,s,1-s));if(h[0]=h[1]=h[2]=0,h[3]=1,"print"===t)h[2]=Math.sin(9*e+5*s+a)*Math.cos(8*s-3*e-a)*.065*o,h[3]=.9+.1*Math.sin(6*e-4*s+a);else if("current"===t)h[0]=Math.sin(9*s+a)*Math.cos(7*e-a)*6*o,h[1]=Math.cos(8*e+a)*Math.sin(6*s-a)*4*o;else if("reform"===t){const t=(n%12+12)%12,a=.8*e+.2*s+.08*Math.sin(8*e-5*s),o=i(1.8+1.2*a,3.8+1.2*a,t)*(1-i(5+1.7*a,7.2+1.7*a,t));h[2]=.95*-o,h[3]=1-.82*o}return h}function a(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 o=class{constructor(t,e){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]=a(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,h=this.height,a=Math.max(1,n/h),o=Math.max(1,h/n),r=s?(t-s.x)*a:0,l=s?(e-s.y)*o:0,c=Math.hypot(r,l);if(!s&&"dissolve"!==this.mode)return;if(s&&c<1e-4)return;const m=.035+.11*Math.max(.1,Math.min(1,i)),d=Math.min(128,Math.max(1,Math.ceil(c/(.45*m)))),u=c?r/c:1,f=c?l/c:0,g=Math.min(1,c/(m*d)*2.2);for(let i=0;i<d;i++){const r=(i+.5)/d,l=s?s.x+(t-s.x)*r:t,c=s?s.y+(e-s.y)*r:e,p=Math.max(0,Math.floor((l-2.5*m/a)*(n-1))),x=Math.min(n-1,Math.ceil((l+2.5*m/a)*(n-1))),v=Math.max(0,Math.floor((c-2.5*m/o)*(h-1))),M=Math.min(h-1,Math.ceil((c+2.5*m/o)*(h-1)));for(let t=v;t<=M;t++)for(let e=p;e<=x;e++){const i=(e/(n-1)-l)*a/m,s=(t/(h-1)-c)*o/m,r=Math.exp(1.5*-(i*i+s*s)),d=t*n+e;if("dissolve"===this.mode)this.ink[d]=Math.max(this.ink[d],r);else{const t=(-i*f+s*u)*r*2.8,e="silk"===this.mode?u*t:-s*r*2.5,n="silk"===this.mode?f*t:i*r*2.5;this.targetX[d]=Math.max(-1,Math.min(1,this.targetX[d]+e*g)),this.targetY[d]=Math.max(-1,Math.min(1,this.targetY[d]+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.edge[t]*32767+32768),h=Math.round(this.offsetY[t]*this.edge[t]*32767+32768);this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e,this.pixels[4*t+2]=h>>>8,this.pixels[4*t+3]=255&h}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))),h=Math.floor(s),o=Math.floor(n),r=s-h,l=n-o,c=o*this.width+h,m=c+this.width,d=t=>(t[c]*(1-r)+t[c+1]*r)*(1-l)+(t[m]*(1-r)+t[m+1]*r)*l;i[0]=d(this.offsetX)*a(t,e,this.width/this.height)*.08,i[1]=d(this.offsetY)*a(t,e,this.width/this.height)*.08,i[2]="dissolve"===this.mode?6*-d(this.ink):0}},r=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 h of this.rings){const a=.015+h.age*(.15+.22*h.radius),o=.009+.014*h.radius,r=(Math.hypot((t-h.x)*i,(e-h.y)*s)-a)/o,l=Math.sin(Math.min(1,h.age/.07)*Math.PI/2)*Math.max(0,1-h.age/1.35)**2;n=Math.max(n,Math.exp(-r*r)*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 h=Math.round(65535*this.echo[n]),a=4*n;this.pixels[a]=h>>>8,this.pixels[a+1]=255&h}this.echoPeak=s,this.active||(this.echo.fill(0),this.pixels.fill(0))}},l=class{constructor(t,e){this.previous=null,this.remainder=0,this.peak=0,this.width=t,this.height=e;const i=t*e;this.pixels=new Uint8Array(4*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.pixels])t.fill(0);this.previous=null,this.peak=0,this.remainder=0}leave(){this.previous=null}move(t,e,i){const s=this.previous;if(this.previous={x:t,y:e},!s)return;const n=(t-s.x)*(this.width-1),h=(e-s.y)*(this.height-1),a=Math.hypot(n,h);if(a<.001)return;const o=Math.min(this.width,this.height)*(.038+.12*i),r=Math.min(128,Math.max(1,Math.ceil(a/(.5*o)))),l=a/o*.95/r;for(let i=0;i<r;i++){const a=(i+.5)/r,c=(s.x+(t-s.x)*a)*(this.width-1),m=(s.y+(e-s.y)*a)*(this.height-1),d=Math.max(1,Math.floor(c-2.5*o)),u=Math.min(this.width-2,Math.ceil(c+2.5*o)),f=Math.max(1,Math.floor(m-2.5*o)),g=Math.min(this.height-2,Math.ceil(m+2.5*o));for(let t=f;t<=g;t++)for(let e=d;e<=u;e++){const i=Math.exp(-((e-c)**2+(t-m)**2)/(o*o)),s=t*this.width+e;this.ink[s]=Math.min(1,this.ink[s]+i*l),this.u[s]=Math.max(-50,Math.min(50,this.u[s]+n*i*5/r)),this.v[s]=Math.max(-50,Math.min(50,this.v[s]+h*i*5/r))}}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))),h=e-s,a=i-n,o=n*this.width+s;return(t[o]*(1-h)+t[o+1]*h)*(1-a)+(t[o+this.width]*(1-h)+t[o+this.width+1]*h)*a}sample(t,e){return this.read(this.ink,t*(this.width-1),e*(this.height-1))}step(t){if(!this.active||!Number.isFinite(t))return;this.remainder+=Math.max(0,Math.min(.05,t));const e=1/60,i=this.width,s=this.height;for(;this.remainder+1e-8>=e;){this.remainder-=e;for(let t=1;t<s-1;t++)for(let s=1;s<i-1;s++){const n=t*i+s,h=s-this.u[n]*e,a=t-this.v[n]*e;this.a[n]=.972*this.read(this.u,h,a),this.b[n]=.972*this.read(this.v,h,a),this.c[n]=.964*this.read(this.ink,h,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 t=1;t<s-1;t++)for(let s=1;s<i-1;s++){const n=t*i+s,h=Math.abs(this.curl[n+1])-Math.abs(this.curl[n-1]),a=Math.abs(this.curl[n+i])-Math.abs(this.curl[n-i]),o=Math.hypot(h,a)+1e-4,r=this.curl[n]*e*5;this.u[n]+=a/o*r,this.v[n]-=h/o*r,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 t=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(-50,Math.min(50,this.u[n]-.5*(this.pressure[n+1]-this.pressure[n-1]))),this.v[n]=Math.max(-50,Math.min(50,this.v[n]-.5*(this.pressure[n+i]-this.pressure[n-i]))),t=Math.max(t,this.ink[n])}this.peak=t}if(this.active)for(let t=0;t<this.ink.length;t++){const e=Math.round(65535*Math.min(1,this.ink[t]));this.pixels[4*t]=e>>>8,this.pixels[4*t+1]=255&e}else this.clear()}},c=t=>["trail","contour","dissolve"].includes(t),m=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]=a(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 l(this.columns,this.rows),this.contour=new r(this.columns,this.rows),this.afterimage=new o(this.columns,this.rows),this.clear()}get densityTrail(){return["trail","contour","dissolve"].includes(this.mode)}get hasRefraction(){return this.active||this.lensStrength>15e-5}get active(){return["dissolve","silk","vortex"].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||this.afterimage.setMode(t),this.refraction.pixels="trail"===t?this.trail.pixels:"contour"===t?this.contour.pixels:["dissolve","silk","vortex"].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:0)}configure(t,e=.55,i=.2){this.setMode(t),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:0,this.refraction.focus[2]=this.densityTrail?this.amount:this.lensStrength*this.amount,this.refraction.focus[3]=this.radius}move(t,e){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"].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);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 i=this.previous;this.previous={x:t,y:e};const s=(t-i.x)*this.xScale,n=(e-i.y)*this.yScale,h=Math.hypot(s,n);if(h<1e-5)return;const a=.035+.1*this.radius,o=3*a,r=Math.min(192,Math.max(1,Math.ceil(h/(.5*a)))),l=95*Math.min(h,1)/r;for(let s=0;s<r;s++){const n=(s+.5)/r,h=i.x+(t-i.x)*n,c=i.y+(e-i.y)*n,m=Math.max(1,Math.floor((h-o/this.xScale)*(this.columns-1))),d=Math.min(this.columns-2,Math.ceil((h+o/this.xScale)*(this.columns-1))),u=Math.max(1,Math.floor((c-o/this.yScale)*(this.rows-1))),f=Math.min(this.rows-2,Math.ceil((c+o/this.yScale)*(this.rows-1)));for(let t=u;t<=f;t++)for(let e=m;e<=d;e++){const i=(e/(this.columns-1)-h)*this.xScale,s=(t/(this.rows-1)-c)*this.yScale,n=Math.exp(-(i*i+s*s)/(a*a))*l,o=t*this.columns+e;this.velocity[o]=Math.max(-12,this.velocity[o]-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"].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 h=1;h<this.columns-1;h++){const a=n*this.columns+h,o=this.heights[a],r=this.heights[a-1]+this.heights[a+1]+this.heights[a-this.columns]+this.heights[a+this.columns]-4*o,l=Math.min(h,n,this.columns-1-h,this.rows-1-n),c=l<5?.88+.024*l:1,m=(this.velocity[a]+(r*s*s-8*o)*e)*i*c;this.velocity[a]=m,this.next[a]=(o+m*e)*c,t=Math.max(t,Math.abs(this.next[a]),.1*Math.abs(m))}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,h=(this.heights[Math.max(0,t-1)*this.columns+e]-this.heights[Math.min(this.rows-1,t+1)*this.columns+e])*n,a=Math.tanh(s)*this.edgeMask[i],o=Math.tanh(h)*this.edgeMask[i];this.normals[2*i]=a,this.normals[2*i+1]=o;const r=Math.round(32767*a+32768),l=Math.round(32767*o+32768);this.refraction.pixels[4*i]=r>>>8,this.refraction.pixels[4*i+1]=255&r,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"].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))),h=Math.floor(s),a=Math.floor(n),o=s-h,r=n-a,l=2*(a*this.columns+h),c=l+2*this.columns;for(let t=0;t<2;t++)i[t]=.08*((this.normals[l+t]*(1-o)+this.normals[l+2+t]*o)*(1-r)+(this.normals[c+t]*(1-o)+this.normals[c+2+t]*o)*r);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,h,a,o]=i.focus,r=(t-n)*Math.max(1,s)/o,l=(e-h)*Math.max(1,1/s)/o;return"light"===i.mode?Math.exp(-3*(r*r+l*l))*a:"scan"===i.mode?(1.3*Math.exp(-r*r*110)-.3*Math.exp(-r*r*12))*Math.exp(-l*l*1.8)*a:0}(t,e,this.refraction,this.xScale/this.yScale):0}};function d(t){const e=Intl.Segmenter;return e?Array.from(new e(void 0,{granularity:"grapheme"}).segment(t),t=>t.segment):Array.from(t)}function u(){const t=document.createElement("canvas"),e=document.createElement("canvas"),i=t.getContext("2d"),s=e.getContext("2d",{willReadFrequently:!0});let n,h=null;return{update(a,o,r,l,c,m,u,f){const g=u?.[0]?.length??Math.max(1,Math.round(o/l)),p=u?.length??Math.max(1,Math.round(r/c)),x=f??{charset:" .:-=+*#%@",customText:"",colorMode:"fullcolor"},v=x.charset+(x.charsetFrames?.join("")??""),M=function(t,e,i,s,n,h,a,o=4096,r=0){if(![i,s,n,h,a,o].every(t=>Number.isFinite(t)&&t>0))throw new Error("Glyph atlas dimensions must be positive and finite.");const l=JSON.stringify([e.charset,e.customText,i,s,n,h,a,o,r]);if(t?.key===l)return t;const c=[...new Set([" ",...d(e.charset),...d(e.customText??"")])];if(c.length>65536)throw new Error("This character set exceeds the glyph texture capacity.");const m=i/h,u=s/a,f=.9*Math.min(m/.55,u),g=Math.ceil(m*n)+4,p=Math.ceil(u*n)+4,x=Math.min(c.length,Math.floor(o/g));if(x<1||Math.ceil(c.length/x)*p>o)throw new Error("The glyph atlas exceeds the available GPU texture size.");return{key:l,glyphs:c,indices:new Map(c.map((t,e)=>[t,e])),cellWidth:m,cellHeight:u,fontSize:f,padding:2,tileWidth:g,tileHeight:p,atlasColumns:x,atlasWidth:x*g,atlasHeight:Math.ceil(c.length/x)*p}}(h,{...x,charset:v},o,r,m,g,p);if(M!==h&&(t.width=M.atlasWidth,t.height=M.atlasHeight,i.font=M.fontSize*m+'px "JetBrains Mono", monospace',i.textAlign="center",i.textBaseline="middle",i.fillStyle="#fff",M.glyphs.forEach((t,e)=>i.fillText(t,e%M.atlasColumns*M.tileWidth+M.padding+M.cellWidth*m*.5,Math.floor(e/M.atlasColumns)*M.tileHeight+M.padding+M.cellHeight*m*.5))),h=M,u){n=function(t,e,i,s,n=!1){const h=t.length,a=t[0]?.length??0,o=s?.cols===a&&s.rows===h?s:{cols:a,rows:h,indices:new Uint8Array(a*h*4),colors:new Uint8ClampedArray(a*h*4)};o.indices.fill(0),o.colors.fill(0);const r="accent"===i.colorMode?[1,3,5].map(t=>parseInt(i.accentColor.slice(t,t+2),16)):null;for(let s=0;s<h;s++){if(t[s].length!==a)throw new Error("Glyph frames must have a rectangular cell grid.");for(let h=0;h<a;h++){const l=t[s][h];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 m=4*(s*a+h);let d=l.r,u=l.g,f=l.b;if(r)[d,u,f]=r;else if("fullcolor"!==i.colorMode){const t=.299*d+.587*u+.114*f|0;d="matrix"===i.colorMode?0:t,u=t,f=d}(n||0!==d||0!==u||0!==f)&&(o.indices[m]=255&c,o.indices[m+1]=c>>>8,o.colors[m]=d,o.colors[m+1]=u,o.colors[m+2]=f,o.colors[m+3]=l.a)}}return o}(u,h.indices,x,n,!0);for(let t=0;t<p;t++)for(let e=0;e<g;e++){const i=u[t][e],s=4*(t*g+e);n.indices[s+2]=Math.round(.299*i.r+.587*i.g+.114*i.b)}}else{e.width=g,e.height=p,s.drawImage(a,0,0,g,p);const t=s.getImageData(0,0,g,p).data;n={cols:g,rows:p,indices:new Uint8Array(g*p*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);n.indices[e+2]=Math.round(255*i),n.indices[e]=Math.min(h.glyphs.length-1,Math.floor(i*h.glyphs.length))}}return{plan:h,packed:n,atlas:t,dots:"dots"===x.renderMode,dotSize:x.dotSizeRatio??.72}},destroy(){t.width=t.height=e.width=e.height=1,n=void 0,h=null}}}function f(t,i,a={}){let o={effect:"trail",strength:.55,radius:.2,animated:!1,fps:30,fontSize:7,charAspect:.58,charSpacing:1,paused:!1,charset:" .:-=+*#%@",customText:"",renderMode:"ascii",dotSizeRatio:.72,...a},r=new m(i.clientWidth/Math.max(1,i.clientHeight)),l=!1,d=!1,f=!0,g=0,p=0,x=-1/0,v=0,M=null,w=!1;const y=matchMedia("(prefers-reduced-motion: reduce)"),S=()=>{l||!d||document.hidden||g||(g=requestAnimationFrame(b))},E=function(t,i,a){const o=i.style.visibility;let r=document.createElement("canvas"),l=null,m=null,d=null,f=null;const g=[],p=[];let x=!1,v=!1,M=4096;const w=document.createElement("canvas"),y=document.createElement("canvas"),S=w.getContext("2d"),E=y.getContext("2d");let b=!1;const T=new n,A=u();let k=null,R={};const z=()=>{r.className="ascii-hover-surface",r.style.cssText="position:absolute;inset:0;width:100%;height:100%;pointer-events:none",r.setAttribute("role","img"),r.setAttribute("aria-label","ASCII artwork preview"),r.dataset.renderer=l?"surface-gpu":"surface-canvas",t.prepend(r),i.style.visibility="hidden"},_=()=>{l&&(g.forEach(t=>l.deleteTexture(t)),p.forEach(t=>l.deleteShader(t)),d&&l.deleteProgram(d),f&&l.deleteBuffer(f))},D=()=>{x||m||(r.removeEventListener("webglcontextlost",D),_(),r.remove(),l=null,r=document.createElement("canvas"),m=r.getContext("2d"),z(),a())};try{if(l=r.getContext("webgl",{alpha:!0,antialias:!1,depth:!1,stencil:!1,premultipliedAlpha:!0,preserveDrawingBuffer:!0}),!l)throw new Error("WebGL unavailable");M=l.getParameter(l.MAX_TEXTURE_SIZE);const t=(t,e)=>{const i=l.createShader(t);if(p.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(d=l.createProgram(),l.attachShader(d,t(l.VERTEX_SHADER,"attribute vec2 position; void main(){gl_Position=vec4(position,0.,1.);}")),l.attachShader(d,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;\n \nuniform float trailAmount;\nuniform float trailKind;\nfloat trailDensity(vec2 address) {\n vec2 encodedInk = texture2D(surface, (address * (surfaceSize - 1.0) + .5) / surfaceSize).rg;\n return (encodedInk.r * 65280.0 + encodedInk.g * 255.0) / 65535.0 * trailAmount;\n}\nvec3 trailInk(vec3 ink, float density) {\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 trailIndex(float index, float count, float density, vec2 cell) {\n // A stable cell pattern gives Dissolve a granular edge without random flicker.\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 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) return vec4(0.,0.,sin(uv.x*9.+uv.y*5.+p)*cos(uv.y*8.-uv.x*3.-p)*.065*edge,.9+.1*sin(uv.x*6.-uv.y*4.+p));\n if(motionMode<2.5) return vec4(sin(uv.y*9.+p)*cos(uv.x*7.-p)*6.*edge,cos(uv.x*8.+p)*sin(uv.y*6.-p)*4.*edge,0.,1.);\n float cycle=mod(motionTime,12.);\n float order=uv.x*.8+uv.y*.2+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(0.,0.,-gone*.95,1.-gone*.82);\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 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 * smoothstep(2.,8.,min(edge.x,edge.y)) / sceneSize;\n }\n vec4 ambient=ambientAt(uv);\n uv -= ambient.xy/sceneSize;\n pixel=uv*sceneSize;\n vec4 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 float tone=clamp(toneAt(uv)+ambient.z+(trailKind>1.5?-density:density)*.65,0.,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 gl_FragColor=vec4(hue*alpha,alpha); return;\n }\n if (trailAmount > 0. || 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 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 gl_FragColor = vec4(lit, ink.a)*ambient.w;\n }")),l.linkProgram(d),!l.getProgramParameter(d,l.LINK_STATUS))throw new Error("Surface shader link failed");l.useProgram(d),f=l.createBuffer(),l.bindBuffer(l.ARRAY_BUFFER,f),l.bufferData(l.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,1,1]),l.STATIC_DRAW);const e=l.getAttribLocation(d,"position");l.enableVertexAttribArray(e),l.vertexAttribPointer(e,2,l.FLOAT,!1,0,0),R=Object.fromEntries(["artwork","surface","resolution","sceneSize","cellSize","surfaceSize","strength","hoverMode","hoverFocus","codes","cellColors","glyphAtlas","gridSize","atlasSize","tileSize","atlasColumns","padding","glyphCount","dots","dotSize","trailKind","trailAmount","motionMode","motionTime","fineDither","ditherAmount"].map(t=>[t,l.getUniformLocation(d,t)]));for(let t=0;t<5;t++){const e=l.createTexture();g.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(R.codes,2),l.uniform1i(R.cellColors,3),l.uniform1i(R.glyphAtlas,4),l.uniform1i(R.artwork,0),l.uniform1i(R.surface,1),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),l.disable(l.BLEND),l.disable(l.DEPTH_TEST),r.addEventListener("webglcontextlost",D),z()}catch{D()}return{get canvas(){return r},render(t,n,a,o,d,u,f,p,z=0){const _=e(p?.animationStyle),C=T.update(z,_,p?.animationSpeed),F=(t=>"terminal"===t?.artStyle&&!t.customText&&!t.charsetFrames?.length&&!/[A-Za-z]/.test(t.charset??""))(p);if(x||!a||!o||!i.width||!i.height)return;const I=Math.min(i.width/a,1.5,Math.sqrt(4e6/(a*o))),P=Math.max(1,Math.round(a*I)),U=Math.max(1,Math.round(o*I));if(r.width===P&&r.height===U||(r.width=P,r.height=U,v=!1),l&&(i.width>M||i.height>M)&&D(),(c(n.mode)||"none"!==_||F)&&(t||!v||!k)&&(k=A.update(i,a,o,d,u,I,f,p),l)){const{plan:t,packed:e,atlas:i}=k;for(const[t,i]of[[2,e.indices],[3,e.colors]])l.activeTexture(l.TEXTURE0+t),l.bindTexture(l.TEXTURE_2D,g[t]),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MIN_FILTER,l.NEAREST),l.texParameteri(l.TEXTURE_2D,l.TEXTURE_MAG_FILTER,l.NEAREST),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,e.cols,e.rows,0,l.RGBA,l.UNSIGNED_BYTE,i);l.activeTexture(l.TEXTURE4),l.bindTexture(l.TEXTURE_2D,g[4]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),l.uniform2f(R.gridSize,e.cols,e.rows),l.uniform2f(R.atlasSize,t.atlasWidth,t.atlasHeight),l.uniform2f(R.tileSize,t.tileWidth,t.tileHeight),l.uniform1f(R.atlasColumns,t.atlasColumns),l.uniform1f(R.padding,t.padding),l.uniform1f(R.glyphCount,t.glyphs.length),l.uniform1f(R.dots,k.dots?1:0),l.uniform1f(R.dotSize,k.dotSize)}if(l)l.uniform1f(R.motionMode,(t=>["none","print","current","reform"].indexOf(t))(_)),l.uniform1f(R.motionTime,C),l.uniform1f(R.fineDither,F?1:0),l.uniform1f(R.ditherAmount,Math.max(0,Math.min(2,p?.ditherStrength??1))),l.uniform1f(R.trailKind,"dissolve"===n.mode?2:0),l.uniform1f(R.trailAmount,c(n.mode)?n.focus[2]:0),l.viewport(0,0,P,U),!t&&v||(l.activeTexture(l.TEXTURE0),l.bindTexture(l.TEXTURE_2D,g[0]),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!0),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,l.RGBA,l.UNSIGNED_BYTE,i),l.pixelStorei(l.UNPACK_PREMULTIPLY_ALPHA_WEBGL,!1),v=!0),l.activeTexture(l.TEXTURE1),l.bindTexture(l.TEXTURE_2D,g[1]),l.texImage2D(l.TEXTURE_2D,0,l.RGBA,n.width,n.height,0,l.RGBA,l.UNSIGNED_BYTE,n.pixels),l.uniform2f(R.resolution,P,U),l.uniform2f(R.sceneSize,a,o),l.uniform2f(R.cellSize,d,u),l.uniform2f(R.surfaceSize,n.width,n.height),l.uniform1f(R.strength,n.strength),l.uniform1f(R.hoverMode,"light"===n.mode?1:"scan"===n.mode?2:0),l.uniform4fv(R.hoverFocus,n.focus),l.drawArrays(l.TRIANGLE_STRIP,0,4);else if(m)if(m.setTransform(I,0,0,I,0,0),m.clearRect(0,0,a,o),m.drawImage(i,0,0,a,o),("none"!==_||F)&&k){m.clearRect(0,0,a,o);const{plan:t,packed:e,atlas:i}=k,r=[0,0,0,1],l=Math.max(1,.32*Math.min(d,u));for(let a=0;a<e.rows;a++)for(let o=0;o<e.cols;o++){const f=(o+.5)/e.cols,g=(a+.5)/e.rows,x=4*(a*e.cols+o);if(e.colors[x+3]<10)continue;h(_,f,g,C,r);const v=4*(Math.min(n.height-1,Math.round(g*(n.height-1)))*n.width+Math.min(n.width-1,Math.round(f*(n.width-1)))),M=c(n.mode)?(256*n.pixels[v]+n.pixels[v+1])/65535*n.focus[2]:0,w=e.indices[x]+256*e.indices[x+1];let y="dissolve"===n.mode?w-Math.floor(M*t.glyphs.length*1.8):w+Math.floor(M*t.glyphs.length*.85);w>0&&(y+=r[2]*(t.glyphs.length-1)),y=Math.max(0,Math.min(t.glyphs.length-1,Math.floor(y+s(o,a))));let S=r[0],E=r[1];n.strength>0&&(S+=(256*n.pixels[v]+n.pixels[v+1]-32768)/32767*n.strength,E+=(256*n.pixels[v+2]+n.pixels[v+3]-32768)/32767*n.strength);const b=o*d+S,T=a*u+E,A=e.colors[x+3]/255*r[3],R=F?Math.max(1,e.colors[x],e.colors[x+1],e.colors[x+2]):255,z=`rgb(${e.colors[x]/R*255},${e.colors[x+1]/R*255},${e.colors[x+2]/R*255})`;if(m.fillStyle=z,F){const t=Math.max(0,Math.min(1,e.indices[x+2]/255+r[2]+.65*("dissolve"===n.mode?-M:M)));m.globalAlpha=A;for(let e=0;e<u;e+=l)for(let i=0;i<d;i+=l)t>.5+(s(Math.floor((b+i)/l),Math.floor((T+e)/l))-.5)*Math.max(0,Math.min(2,p?.ditherStrength??1))&&m.fillRect(b+i,T+e,Math.min(l,d-i),Math.min(l,u-e))}else if(y>0)if(m.globalAlpha=A,k.dots){const e=Math.min(.48,y/(t.glyphs.length-1)*.5*k.dotSize)*Math.min(d,u);m.beginPath(),m.arc(b+.5*d,T+.5*u,e,0,2*Math.PI),m.fill()}else m.drawImage(i,y%t.atlasColumns*t.tileWidth+t.padding,Math.floor(y/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*I,t.cellHeight*I,b,T,d,u),m.globalAlpha=1,m.globalCompositeOperation="source-atop",m.fillRect(b,T,d,u),m.globalCompositeOperation="source-over"}m.globalAlpha=1}else if(c(n.mode)&&k){const{plan:t,packed:e,atlas:i}=k;for(let s=0;s<e.rows;s++)for(let h=0;h<e.cols;h++){const a=4*(Math.min(n.height-1,Math.round((s+.5)/e.rows*(n.height-1)))*n.width+Math.min(n.width-1,Math.round((h+.5)/e.cols*(n.width-1)))),o=(256*n.pixels[a]+n.pixels[a+1])/65535*n.focus[2],r=4*(s*e.cols+h),l=.0137*(h*h*17+s*s*23+h*s*19),c=l-Math.floor(l),f=e.indices[r]+256*e.indices[r+1],g="dissolve"===n.mode?Math.max(0,f-Math.floor(o*t.glyphs.length*(1.35+.9*c))):Math.min(t.glyphs.length-1,f+Math.floor(o*t.glyphs.length*.85));if(g===f||e.colors[r+3]<10)continue;const p=h*d,x=s*u;if(m.clearRect(p,x,d,u),m.globalAlpha=e.colors[r+3]/255,k.dots){const e=Math.min(.48,g/(t.glyphs.length-1)*.5*k.dotSize)*Math.min(d,u);m.fillStyle="#fff",m.beginPath(),m.arc(p+.5*d,x+.5*u,e,0,2*Math.PI),m.fill()}else m.drawImage(i,g%t.atlasColumns*t.tileWidth+t.padding,Math.floor(g/t.atlasColumns)*t.tileHeight+t.padding,t.cellWidth*I,t.cellHeight*I,p,x,d,u);m.globalAlpha=1,m.globalCompositeOperation="source-atop";const v=Math.max(e.colors[r],e.colors[r+1],e.colors[r+2]),M="dissolve"===n.mode?v:Math.max(v,Math.min(112,191*o)),w=v?M/v:0;m.fillStyle=`rgb(${v?e.colors[r]*w:M},${v?e.colors[r+1]*w:M},${v?e.colors[r+2]*w:M})`,m.fillRect(p,x,d,u),m.globalCompositeOperation="source-over"}}else if("light"===n.mode||"scan"===n.mode){!t&&b&&w.width===P&&w.height===U||(w.width=P,w.height=U,y.width=P,y.height=U,S.filter="brightness(3)",S.drawImage(i,0,0,P,U),S.filter="none",b=!0);const[e,s,h,r]=n.focus;if(h>.001){E.setTransform(1,0,0,1,0,0),E.clearRect(0,0,P,U),E.drawImage(w,0,0),E.globalCompositeOperation="destination-in",E.save(),E.translate(e*P,s*U),E.scale("scan"===n.mode?.13:1,1);const t=r*Math.min(P,U),i=E.createRadialGradient(0,0,0,0,0,t);i.addColorStop(0,`rgba(255,255,255,${h})`),i.addColorStop(.5,`rgba(255,255,255,${.4*h})`),i.addColorStop(1,"rgba(255,255,255,0)"),E.fillStyle=i,E.fillRect(10*-P,10*-U,20*P,20*U),E.restore(),E.globalCompositeOperation="source-over",m.globalCompositeOperation="source-atop",m.drawImage(y,0,0,a,o),m.globalCompositeOperation="source-over"}}else if(n.strength>0){const t=32,e=18,s=a/t,h=o/e;for(let r=0;r<e;r++)for(let l=0;l<t;l++){const c=(l+.5)/t,f=(r+.5)/e,g=Math.min(n.width-1,Math.round(c*(n.width-1))),p=4*(Math.min(n.height-1,Math.round(f*(n.height-1)))*n.width+g),x=n.pixels,v=Math.min(l*s/d,(a-(l+1)*s)/d,r*h/u,(o-(r+1)*h)/u),M=Math.max(0,Math.min(1,(v-2)/6)),w=M*M*(3-2*M),y=(256*x[p]+x[p+1]-32768)/32767*n.strength*w,S=(256*x[p+2]+x[p+3]-32768)/32767*n.strength*w;Math.abs(y)+Math.abs(S)<.02||(m.clearRect(l*s,r*h,s,h),m.drawImage(i,Math.max(0,Math.min(a-s,l*s-y))*i.width/a,Math.max(0,Math.min(o-h,r*h-S))*i.height/o,s*i.width/a,h*i.height/o,l*s,r*h,s,h))}}},destroy(){x=!0,A.destroy(),r.removeEventListener("webglcontextlost",D),_(),r.remove(),w.width=w.height=y.width=y.height=1,i.style.visibility=o,l?.getExtension("WEBGL_lose_context")?.loseContext()}}}(t,i,()=>queueMicrotask(()=>{f=!0,S()}));function b(t){if(g=0,l||!d||document.hidden)return;const s=i.clientWidth,n=i.clientHeight;if(!s||!n)return;const h=y.matches||o.paused;r.configure(o.effect,o.strength,o.radius);const a=r.active;if(!h){if(v+=p?Math.min(.1,(t-p)/1e3):0,w){const t=i.getBoundingClientRect();M&&t.width&&t.height?r.move((M.x-t.left)/t.width,(M.y-t.top)/t.height):r.leave(),w=!1}r.step(p?(t-p)/1e3:1/60)}const c=f||o.animated&&!h&&t-x>=1e3/Math.max(1,Math.min(60,o.fps))-.5;(c||a||r.active||!h&&"none"!==e(o.motion))&&(E.render(c,r.refraction,s,n,o.fontSize*o.charSpacing,o.fontSize/o.charAspect*o.charSpacing,c?o.getFrame?.():void 0,{charset:o.charset,customText:o.customText,renderMode:o.renderMode,dotSizeRatio:o.dotSizeRatio,colorMode:"fullcolor",animationStyle:o.motion,animationSpeed:o.motionSpeed??1,artStyle:o.fineDither?"terminal":"classic",ditherStrength:o.ditherStrength??1},v),c&&(x=t),f=!1),p=t,h||!o.animated&&!r.active&&"none"===e(o.motion)||S()}const T=()=>{r.clear(),M=null,w=!1,p=0,f=!0,cancelAnimationFrame(g),g=0,S()},A=t=>{if("touch"===t.pointerType||o.paused||y.matches)return;if(t.target instanceof Element&&t.target.closest("button,a,input,summary,select"))return void k();const e=i.getBoundingClientRect();t.clientX<e.left||t.clientX>e.right||t.clientY<e.top||t.clientY>e.bottom?k():(M={x:t.clientX,y:t.clientY},w=!0,S())},k=()=>{M=null,w=!0,S()},R=new IntersectionObserver(([t])=>{d=t.isIntersecting,T()});R.observe(t);const z=new ResizeObserver(()=>{r=new m(i.clientWidth/Math.max(1,i.clientHeight)),T()});return z.observe(i),t.addEventListener("pointermove",A),t.addEventListener("pointerleave",k),t.addEventListener("pointercancel",k),document.addEventListener("visibilitychange",T),y.addEventListener("change",T),{get canvas(){return E.canvas},invalidate(){f=!0,S()},update(t){o={...o,...t},T()},destroy(){l||(l=!0,cancelAnimationFrame(g),R.disconnect(),z.disconnect(),E.destroy(),t.removeEventListener("pointermove",A),t.removeEventListener("pointerleave",k),t.removeEventListener("pointercancel",k),document.removeEventListener("visibilitychange",T),y.removeEventListener("change",T))}}}export{f as mountHover,f as mountStudioHover};//# sourceMappingURL=hover.js.map
|
package/dist/index.d.cts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { ART_STYLE_PRESETS, AnimationStyle, ArtStyle, AsciiCell, AsciiFrame, AsciiOptions, AsciiResult,
|
|
1
|
+
export { A as ART_STYLE_PRESETS, a as AnimationStyle, b as ArtStyle, c as AsciiCell, d as AsciiFrame, e as AsciiOptions, f as AsciiResult, C as CHARSETS, g as CHARSET_SEQUENCES, h as CharsetKey, i as CharsetSequenceKey, j as ColorMode, D as DEFAULT_OPTIONS, H as HOVER_PRESETS, k as HoverEffect, l as HoverPreset, m as HoverShape, L as LIVING_STYLE_PRESETS, n as LivingStylePresetKey, P as PALETTE_THEMES, o as PaletteTheme, R as RenderMode, S as SourceType } from './types-w2CRgyk6.cjs';
|
|
2
|
+
export { AsciiTextFrame, AsciifyLiveVideoOptions, AsciifySimpleOptions, AsciifyVideoOptions, BigTextOptions, CanvasBleed, CanvasObjectFit, CanvasRenderSizeInput, SnapshotOptions, TextBackgroundOptions, VideoScrollScrubOptions, WebcamOptions, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyLiveVideo, asciifyText, asciifyVideo, asciifyWebcam, buildTextFrame, captureSnapshot, clearAsciifyCaches, computeCanvasRenderSize, createVideoScrollScrub, gifToAsciiFrames, gifToAsciiTextFrames, imageToAsciiFrame, imageToAsciiTextFrame, renderFrameToCanvas, renderTextBackground, renderTextFrameToCanvas, renderTextToCanvas, snapshotAndDownload, videoToAsciiFrames, videoToAsciiTextFrames } from './core.cjs';
|
|
2
3
|
export { AsciiBackgroundOptions, AuroraBackgroundOptions, BACKGROUND_TYPES, BackgroundType, CircuitBackgroundOptions, DnaBackgroundOptions, FireBackgroundOptions, GridBackgroundOptions, MorphBackgroundOptions, MountWaveOptions, NoiseBackgroundOptions, PulseBackgroundOptions, RainBackgroundOptions, SilkBackgroundOptions, StarsBackgroundOptions, TerrainBackgroundOptions, VoidBackgroundOptions, WaveBackgroundOptions, asciiBackground, mountWaveBackground, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderVoidBackground, renderWaveBackground } from './backgrounds.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { ART_STYLE_PRESETS, AnimationStyle, ArtStyle, AsciiCell, AsciiFrame, AsciiOptions, AsciiResult,
|
|
1
|
+
export { A as ART_STYLE_PRESETS, a as AnimationStyle, b as ArtStyle, c as AsciiCell, d as AsciiFrame, e as AsciiOptions, f as AsciiResult, C as CHARSETS, g as CHARSET_SEQUENCES, h as CharsetKey, i as CharsetSequenceKey, j as ColorMode, D as DEFAULT_OPTIONS, H as HOVER_PRESETS, k as HoverEffect, l as HoverPreset, m as HoverShape, L as LIVING_STYLE_PRESETS, n as LivingStylePresetKey, P as PALETTE_THEMES, o as PaletteTheme, R as RenderMode, S as SourceType } from './types-w2CRgyk6.js';
|
|
2
|
+
export { AsciiTextFrame, AsciifyLiveVideoOptions, AsciifySimpleOptions, AsciifyVideoOptions, BigTextOptions, CanvasBleed, CanvasObjectFit, CanvasRenderSizeInput, SnapshotOptions, TextBackgroundOptions, VideoScrollScrubOptions, WebcamOptions, asciiText, asciiTextAnsi, asciify, asciifyGif, asciifyLiveVideo, asciifyText, asciifyVideo, asciifyWebcam, buildTextFrame, captureSnapshot, clearAsciifyCaches, computeCanvasRenderSize, createVideoScrollScrub, gifToAsciiFrames, gifToAsciiTextFrames, imageToAsciiFrame, imageToAsciiTextFrame, renderFrameToCanvas, renderTextBackground, renderTextFrameToCanvas, renderTextToCanvas, snapshotAndDownload, videoToAsciiFrames, videoToAsciiTextFrames } from './core.js';
|
|
2
3
|
export { AsciiBackgroundOptions, AuroraBackgroundOptions, BACKGROUND_TYPES, BackgroundType, CircuitBackgroundOptions, DnaBackgroundOptions, FireBackgroundOptions, GridBackgroundOptions, MorphBackgroundOptions, MountWaveOptions, NoiseBackgroundOptions, PulseBackgroundOptions, RainBackgroundOptions, SilkBackgroundOptions, StarsBackgroundOptions, TerrainBackgroundOptions, VoidBackgroundOptions, WaveBackgroundOptions, asciiBackground, mountWaveBackground, renderAuroraBackground, renderCircuitBackground, renderDnaBackground, renderFireBackground, renderGridBackground, renderMorphBackground, renderNoiseBackground, renderPulseBackground, renderRainBackground, renderSilkBackground, renderStarsBackground, renderTerrainBackground, renderVoidBackground, renderWaveBackground } from './backgrounds.js';
|