0xf113…933e

All memos sent from and to 0xf113…933e.

function parsePalette(e){const t=Object.fromEntries(e.split("|").map(e=>{const t=e.indexOf("=");return[e.slice(0,t),e.slice(t+1)]})),r=e=>e.trim().split(/\s+/).map(Number);return{bg:r(t.bg),grid:r(t.grid),highlight:r(t.highlight),inks:t.inks.split(";").map(e=>{const t=r(e);return{color:t.slice(0,4),weight:t[4]}})}}palette=parsePalette(palette);class Random{constructor(e){this.useA=!1,this.prngA=this.sfc32(e.slice(2,34)),this.prngB=this.sfc32(e.slice(34,66));for(let e=0;e<1e5;e++)this.prngA(),this.prngB()}sfc32(e){let t=parseInt(e.slice(0,8),16),r=parseInt(e.slice(8,16),16),n=parseInt(e.slice(16,24),16),o=parseInt(e.slice(24,32),16);return function(){t|=0,r|=0,n|=0,o|=0;let e=(t+r|0)+o|0;return o=o+1|0,t=r^r>>>9,r=n+(n<<3)|0,n=n<<21|n>>>11,n=n+e|0,(e>>>0)/4294967296}}g(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}range(e,t){return e+this.g()*(t-e)}int(e,t){return Math.floor(this.range(e,t+1))}pick(e){return e[Math.floor(this.g()*e.length)]}chance(e){return this.g()<e}}let R=new Random(HASH);const wordStream={fetching:!1,done:WORDS.length>=WORD_COUNT,failed:!1,nextOffset:WORDS.length};let customRpcUrl="";function getRpcUrls(){return customRpcUrl?[customRpcUrl,...RPC_URLS.filter(e=>e!==customRpcUrl)]:RPC_URLS}function setCustomRpcUrl(){const e=prompt("Custom RPC URL:",customRpcUrl||"https://...");if(!e)return;const t=e.trim();/^https?:\/\//i.test(t)?(customRpcUrl=t,wordStream.fetching=!1,wordStream.failed=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.nextOffset=WORDS.length,maybeFetchMoreWords(),requestRun(),console.log("Custom RPC set:",customRpcUrl)):alert("Please enter a valid http(s) RPC URL.")}const STRUCT_W=4e3,STRUCT_H=5e3,STRUCT_ASPECT=.8;let customRenderWidth=null,customRenderHeight=null,lastGoodWidth=1200,lastGoodHeight=1500,restartQueued=!1,blackFrameCount=0,lastProgressFrame=0,lastProgressPlacementIndex=0;const MAX_BLACK_FRAMES=90,MAX_STUCK_FRAMES=240;let renderLoopRunning=!1;function requestRun(){renderLoopRunning||(renderLoopRunning=!0,requestAnimationFrame(run))}const GRAIN_SEED=parseInt(HASH.slice(2,10),16)/4294967295,settings={aspect:.8,washStrength:R.range(.1,.8),smudgeChance:R.range(.1,.24),textureAmount:R.range(.025,.08),pauseFrames:0,revealFrames:2,revealSpeed:1,backgroundWashChance:.01,backgroundWashMinWords:5,backgroundWashMaxWords:50,backgroundWashColor:palette.highlight,baseGrainAmount:.02,baseMottleAmount:8e-4,gridDefinitions:[...Array(5)].map((e,t)=>(co=t>0?Math.floor(R.range(70,130)):Math.floor(R.range(45,65)),ro=Math.floor(1.25*co),{name:t.toString(),cols:co,rows:ro,rotation:R.g()/25-.02,scale:1,offsetX:0,offsetY:0,letterScale:.8})),showReference:!1};function shuffled(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=R.int(0,e);[t[e],t[r]]=[t[r],t[e]]}return t}const view=document.getElementById("view"),ref=document.getElementById("ref"),refCtx=ref.getContext("2d",{alpha:!1});let gl,vertexBuffer,washProgram,screenProgram,refTexture,width=0,height=0,dpr=1,frameBuffers=[],frameBufferId=0,resizeQueued=!1,isResizing=!1;const MIN_GL_SIZE=2,state={frame:0,grids:[],placements:[],activePlacementIndex:0},vertexShader="\n in vec3 pos;\n out vec2 vUv;\n\n void main() {\n gl_Position = vec4(pos, 1.0);\n vUv = (pos.xy + 1.0) * 0.5;\n }\n",washShader="\n uniform sampler2D previousFrame;\n uniform sampler2D referenceMask;\n uniform float frame;\n uniform float dirx;\n uniform float diry;\n uniform float washStrength;\n uniform float smudgeChance;\n uniform vec4 bgColor;\n uniform vec4 inkColor;\n uniform vec4 highlightColor;\n\n uniform vec2 resolution;\n uniform float grainSeed;\n uniform float baseGrainAmount;\n uniform float baseMottleAmount;\n\n in vec2 vUv;\n out vec4 col;\n\n vec3 random3(vec3 c) {\n float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0)));\n vec3 r;\n r.z = fract(512.0 * j);\n j *= 0.125;\n r.x = fract(512.0 * j);\n j *= 0.125;\n r.y = fract(512.0 * j);\n return r - 0.5;\n }\n\n const float F3 = 0.3333333;\n const float G3 = 0.1666667;\n\n float simplex3(vec3 p) {\n vec3 s = floor(p + dot(p, vec3(F3)));\n vec3 x = p - s + dot(s, vec3(G3));\n vec3 e = step(vec3(0.0), x - x.yzx);\n vec3 i1 = e * (1.0 - e.zxy);\n vec3 i2 = 1.0 - e.zxy * (1.0 - e);\n vec3 x1 = x - i1 + G3;\n vec3 x2 = x - i2 + 2.0 * G3;\n vec3 x3 = x - 1.0 + 3.0 * G3;\n vec4 w, d;\n w.x = dot(x, x);\n w.y = dot(x1, x1);\n w.z = dot(x2, x2);\n w.w = dot(x3, x3);\n w = max(0.6 - w, 0.0);\n d.x = dot(random3(s), x);\n d.y = dot(random3(s + i1), x1);\n d.z = dot(random3(s + i2), x2);\n d.w = dot(random3(s + 1.0), x3);\n w *= w;\n w *= w;\n d *= w;\n return dot(d, vec4(52.0));\n }\n\n float hash12(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33 + grainSeed * 17.17);\n return fract((p3.x + p3.y) * p3.z);\n }\n\n void main() {\n vec4 ref = texture(referenceMask, vUv);\n vec4 prev = texture(previousFrame, vUv);\n\n col = prev;\n\n if (frame < 0.5) {\n vec2 px = gl_FragCoord.xy;\n\n float fine = hash12(px) - 0.5;\n\n float fine2 = hash12(px * 0.5 + 91.7) - 0.5;\n float mottle = simplex3(vec3(\n vUv.x * 95.0 + grainSeed * 13.0,\n vUv.y * 95.0 - grainSeed * 9.0,\n 4.0\n ));\n\n float paper =\n fine * baseGrainAmount +\n fine2 * baseGrainAmount * 0.45 +\n mottle * baseMottleAmount;\n\n col.rgb += vec3(paper);\n }\n\n if (ref.g > 0.001) {\n float rnd = simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 50.0,\n grainSeed * 100000.0\n ));\n\n vec4 textureBG = mix(\n bgColor,\n vec4(\n bgColor.rgb - vec3(\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 500.0,\n (vUv.y - frame / 1000.0) * 500.0,\n grainSeed * 100000.0\n )) / 80.\n ),\n 1.0\n ),\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 5.0,\n (vUv.y - frame / 1000.0) / 4.0,\n grainSeed * 100000.0\n ))\n );\n\n // This is the core wash behavior from the second project:\n // replace/scratch toward textured background, instead of dragging black blocks.\n col = mix(\n textureBG,\n prev,\n simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 5000.0,\n grainSeed * 100000.0\n ))*4.\n );\n\n // Directional smudge, but only sometimes.\n if (rnd < smudgeChance) {\n col = mix(\n texture(previousFrame, vec2(\n vUv.x + 0.003 * dirx,\n vUv.y + 0.003 * diry\n )),\n textureBG,\n rnd\n );\n }\n\n // Let the green mask control strength.\n col = mix(bgColor, col, ref.g * washStrength);\n }\n\n if (ref.b > 0.01) {\n float paintMask = ref.b;\n\n float roughA = simplex3(vec3(\n vUv.x * 900.0,\n vUv.y * 900.0,\n frame * 0.012\n ));\n\n float roughB = simplex3(vec3(\n vUv.x * 2200.0,\n vUv.y * 2200.0,\n 19.0\n ));\n\n float brokenMask = paintMask;\n brokenMask *= smoothstep(-0.45, 0.55, roughA);\n brokenMask *= 0.72 + roughB * 0.28;\n\n vec4 noisyPaint = highlightColor;\n noisyPaint.rgb += vec3(roughB * 0.10 + roughA * 0.035);\n\n vec2 dragUv = vUv + vec2(\n roughA * 0.0025,\n roughB * 0.0015\n );\n\n vec4 draggedSurface = texture(previousFrame, dragUv);\n\n vec4 paintOnSurface = mix(draggedSurface, noisyPaint, brokenMask);\n\n col = mix(col, paintOnSurface, brokenMask);\n }\n\n // Red = foreground ink. The incoming reference contains only the current word.\n if (ref.r > 0.01) {\n float smearNoise = simplex3(vec3(\n vUv.x * 900.0 + frame * 0.03,\n vUv.y * 220.0 - frame * 0.02,\n grainSeed * 9999.0\n ));\n\n float smearMask = ref.r * smoothstep(-0.35, 0.55, smearNoise);\n\n vec2 smearUv = vUv + vec2(\n -0.006 * dirx,\n 0.002 * diry\n );\n\n vec4 draggedInk = texture(previousFrame, smearUv);\n\n col = mix(col, draggedInk, smearMask * 0.65);\n\n vec4 brokenInk = inkColor;\n\n col = mix(col, brokenInk, ref.r);\n }\n }\n",screenShader="\n uniform sampler2D buffer;\n in vec2 vUv;\n out vec4 col;\n\n void main() {\n col = texture(buffer, vec2(vUv.x, 1.0 - vUv.y));\n }\n";async function maybeFetchMoreWords(){if(!wordStream.fetching&&!wordStream.done&&!wordStream.failed)if(RPC_URLS.length){wordStream.fetching=!0;try{const e=await fetchNotesSlice(wordStream.nextOffset,FETCH_BATCH_SIZE);if(!e.length)return void(wordStream.done=!0);for(const t of e)WORDS.push(t),addWordPlacement(t);wordStream.nextOffset+=e.length,wordStream.nextOffset>=WORD_COUNT&&(wordStream.done=!0)}catch(e){console.warn("RPC word fetch failed:",e),wordStream.failed=!0}finally{wordStream.fetching=!1}}else wordStream.failed=!0}async function fetchNotesSlice(e,t){let r=null;for(const n of getRpcUrls())try{return await fetchNotesSliceFromRpc(n,TOKEN_ID,e,t)}catch(e){r=e,console.warn("RPC failed, trying next:",n,e)}throw r||new Error("No RPC worked")}const NOTES_SLICE_SELECTOR="0x91cf7995";async function fetchNotesSliceFromRpc(e,t,r,n){const o=NOTES_SLICE_SELECTOR+encodeUint256(t)+encodeUint256(r)+encodeUint256(n),a=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:CONTRACT_ADDRESS,data:o},"latest"]})}),s=await a.json();if(s.error)throw new Error(s.error.message||"RPC error");return s.result&&"0x"!==s.result?decodeStringArray(s.result):[]}function encodeUint256(e){return BigInt(e).toString(16).padStart(64,"0")}function decodeStringArray(e){e=e.replace(/^0x/,"");const t=t=>{const r=2*t;return BigInt("0x"+e.slice(r,r+64))},r=(t,r)=>{const n=2*t;return e.slice(n,n+2*r)},n=Number(t(0)),o=Number(t(n)),a=[];for(let e=0;e<o;e++){const o=n+32+Number(t(n+32+32*e)),s=r(o+32,Number(t(o)));a.push(hexToUtf8(s))}return a}function hexToUtf8(e){const t=[];for(let r=0;r<e.length;r+=2)t.push(parseInt(e.slice(r,r+2),16));return(new TextDecoder).decode(new Uint8Array(t)).replace(/\0/g,"").trim()}function varyColor(e,t=.01){return[Math.max(0,Math.min(1,e[0]+R.range(-t,t))),Math.max(0,Math.min(1,e[1]+R.range(-t,t))),Math.max(0,Math.min(1,e[2]+R.range(-t,t))),e[3]??1]}function init(){if(gl=view.getContext("webgl2",{alpha:!1,antialias:!1,preserveDrawingBuffer:!0}),!gl)return void(document.body.innerHTML="WebGL2 is required.");vertexBuffer=createVertexBuffer(),washProgram=createProgram(vertexShader,washShader),screenProgram=createProgram(vertexShader,screenShader),createGrids(),createWordPlacements(),window.addEventListener("resize",requestResize),window.addEventListener("keydown",handleKey),view.addEventListener("webglcontextlost",e=>{e.preventDefault(),console.warn("WebGL context lost")},!1),view.addEventListener("webglcontextrestored",()=>{console.warn("WebGL context restored, rebuilding"),hardRebuild()},!1);new ResizeObserver(()=>requestResize()).observe(document.body),resize(),requestRun()}function resetRandom(){R=new Random(HASH)}function requestResize(){resizeQueued||(resizeQueued=!0,requestAnimationFrame(()=>{resizeQueued=!1,resize()}))}function resetWordsToDefault(){WORDS.length=0,WORDS.push(...INITIAL_WORDS),wordStream.fetching=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.failed=!1,wordStream.nextOffset=WORDS.length}let lastRenderedPixelCount=0;function restartAfterResize(){resetWordsToDefault(),resetRandom(),state.frame=0,state.activePlacementIndex=0,createGrids(),createWordPlacements(),drawBaseReference(),refTexture&&gl.deleteTexture(refTexture),refTexture=createTexture(width,height,ref),resetFrameBuffers(),window.__RESIDUAL_NOTES_RENDERED__=!1,blackFrameCount=0,lastProgressFrame=0,lastRenderedPixelCount=width*height,requestRun()}let renderW=null,renderH=null;function resize(){const e=1*Math.floor(window.innerWidth||document.documentElement.clientWidth||0),t=1*Math.floor(window.innerHeight||document.documentElement.clientHeight||0);if(e<20||t<20)return void requestAnimationFrame(requestResize);let r,n;e/t>settings.aspect?(n=t,r=t*settings.aspect):(r=e,n=e/settings.aspect),dpr=Math.max(1,window.devicePixelRatio||1);const o=renderW??Math.max(20,Math.round(r*dpr)),a=renderH??Math.max(20,Math.round(n*dpr));if(!(o===width&&a===height)){if(width>0&&height>0&&!(o*a>width*height))return view.style.width=`${r}px`,void(view.style.height=`${n}px`);isResizing=!0,width=o,height=a,view.width=width,view.height=height,view.style.display="block",ref.width=width,ref.height=height,ref.style.display=settings.showReference?"block":"none",restartAfterResize(),isResizing=!1}}function setCustomRenderSize(){const e=prompt("Render width in pixels:",width);if(!e)return;const t=Math.max(100,Math.floor(Number(e)));Number.isFinite(t)&&(renderW=t,renderH=Math.round(renderW/settings.aspect),resize())}function useStructureSpace(){refCtx.setTransform(width/4e3,0,0,height/5e3,0,0)}function resetFrameBuffers(){for(const e of frameBuffers)gl.deleteFramebuffer(e.buffer),gl.deleteTexture(e.texture);frameBuffers=[createFrameBuffer(width,height,ref),createFrameBuffer(width,height,null)],frameBufferId=0}function queueHardRebuild(e="unknown"){restartQueued||(restartQueued=!0,console.warn("Queueing hard rebuild:",e),requestAnimationFrame(()=>{restartQueued=!1,hardRebuild(e)}))}function hardRebuild(e="unknown"){if(console.warn("Hard rebuild:",e),gl&&!gl.isContextLost())try{if(resize(),width<MIN_GL_SIZE||height<MIN_GL_SIZE)return;restartAfterResize(),blackFrameCount=0,lastProgressFrame=state.frame,window.__RESIDUAL_NOTES_RENDERED__=!1}catch(e){console.warn("Hard rebuild failed:",e)}}function assertFramebufferHealthy(e=""){const t=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return t===gl.FRAMEBUFFER_COMPLETE||(queueHardRebuild(`bad framebuffer ${e}: ${t}`),!1)}function drawBaseReference(){useStructureSpace(),refCtx.fillStyle=rgb(palette.bg),refCtx.fillRect(0,0,4e3,5e3),refCtx.fillStyle=rgb(palette.grid);const e=Math.floor(settings.gridDefinitions[0].cols),t=Math.floor(settings.gridDefinitions[0].rows);for(let t=2;t<e-2;t++)refCtx.fillRect(4e3/e*t,0,1,5e3);for(let e=2;e<t-2;e++)refCtx.fillRect(0,5e3/t*e,4e3,1)}function createGrids(){state.grids=settings.gridDefinitions.map((e,t)=>({id:t,name:e.name||`grid ${t}`,cols:Math.floor(e.cols),rows:Math.floor(e.rows),rotation:e.rotation||0,scale:e.scale||1,offsetX:e.offsetX||0,offsetY:e.offsetY||0,letterScale:e.letterScale||.8,occupied:null}))}function createWordPlacements(){state.placements=[];let e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords);for(const t of WORDS)addWordPlacement(t),e--,e<=0&&R.chance(settings.backgroundWashChance)&&(addBackgroundWashPlacement(),e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords))}function addWordPlacement(e){if(!e||!String(e).trim())return!1;const t=String(e).toUpperCase(),r=tryCreateWordPlacement(t)||tryCreateWordPlacement(t)||tryCreateWordPlacement(t);return!!r&&(state.placements.push(r),!0)}function addBackgroundWashPlacement(){state.placements.push({type:"backgroundWash",color:R.chance(.15)?palette.highlight:darkenedBgColor(.1),dirx:R.range(-1,1),diry:R.range(-1,1),washStrength:R.range(.03,.16),smudgeChance:R.range(.1,.32),x:4e3*R.range(-.08,.85),y:5e3*R.range(-.08,.85),w:4e3*R.range(.18,.55),h:5e3*R.range(.12,.38),seed:9999*R.g(),alpha:R.range(.02,.175),dripChance:R.range(.08,.28),force:1})}function tryCreateWordPlacement(e){for(let t=0;t<80;t++){const t=R.pick(state.grids),r=R.pick(["right","down","right","down","right","down","right","down","right","down","right","down","right","down","diagDown","diagUp"]),n=randomStartForTextBlock(t,e,r),o=textBlockCells(n.x,n.y,e,r,t);if(cellsFit(t,o))return{word:e,grid:t,direction:r,cells:o,color:varyColor(pickInkForWord(),.05),dirx:R.range(-2,2),diry:R.range(-2,2),washSeed:R.g(),washStrength:R.range(.05,.35),smudgeChance:R.range(.1,.32),washPad:R.range(.04,.2),washGaps:0,washStampCount:R.int(1,6),circle:R.chance(.065),underline:R.chance(.05),strikethrough:R.chance(.05),lettergrid:R.chance(.01),square:R.chance(.01),cross:R.chance(.01),plus:R.chance(.005),force:1}}return null}function randomStartForTextBlock(e,t,r){const n=Math.min(String(t).length,"right"===r?e.cols:e.rows),o=R.int(0,e.cols-1),a=R.int(0,e.rows-1);return"right"===r?{x:Math.max(0,Math.min(e.cols-1,o-Math.floor(n*R.range(.25,.65)))),y:Math.max(0,Math.min(e.rows-1,a-R.int(0,2)))}:"down"===r?{x:Math.max(0,Math.min(e.cols-1,o-R.int(0,2))),y:Math.max(0,Math.min(e.rows-1,a-Math.floor(n*R.range(.25,.65))))}:{x:o,y:a}}function textBlockCells(e,t,r,n,o){if("diagDown"===n||"diagUp"===n)return diagonalTextCells(e,t,r,n);const a=String(r).split(/\s+/),s=[],i="right"===n?Math.max(1,o.cols-e):Math.max(1,o.rows-t);let l=0,f=0;for(const r of a){if(f+(f>0?1:0)+r.length>i&&(l++,f=0),"right"===n&&t+l>=o.rows)break;if("down"===n&&e+l>=o.cols)break;f>0&&(addTextCharCell(s,e,t,n,f,l," "),f++);for(let a=0;a<r.length&&(f>=i&&(l++,f=0),!("right"===n&&t+l>=o.rows))&&!("down"===n&&e+l>=o.cols);a++)addTextCharCell(s,e,t,n,f,l,r[a]),f++}return s.map((e,t)=>({...e,index:t}))}function addTextCharCell(e,t,r,n,o,a,s){"right"===n&&e.push({x:t+o,y:r+a,char:s}),"down"===n&&e.push({x:t+a,y:r+o,char:s})}function diagonalTextCells(e,t,r,n){const o=[];for(let a=0;a<r.length;a++){let s=e+a,i="diagDown"===n?t+a:t-a;o.push({x:s,y:i,char:r[a],index:a})}return o}function cellsFit(e,t){return t.every(t=>t.x>=0&&t.y>=0&&t.x<e.cols&&t.y<e.rows)}function pickInkForWord(){return weightedPick(palette.inks).color}function weightedPick(e){const t=e.reduce((e,t)=>e+t.weight,0);let r=R.g()*t;for(const t of e)if(r-=t.weight,r<=0)return t;return e[e.length-1]}function clearReferenceToBlack(){refCtx.setTransform(1,0,0,1,0,0),refCtx.fillStyle="black",refCtx.fillRect(0,0,width,height)}function drawPlacementReference(e,t){if("backgroundWash"===e.type)return void drawBackgroundWashReference(e,t);clearReferenceToBlack(),refCtx.save(),useStructureSpace(),applyGridTransform(e.grid);const r=Math.ceil(e.cells.length*t),n=e.cells.slice(0,r);drawWashForCells(e.grid,n,e.washPad,e),drawWordCells(e.grid,n,e),e.underline&&t>.72&&drawUnderline(e),e.circle&&t>.84&&drawCircleAroundWord(e),e.strikethrough&&t>.65&&drawStrikethrough(e),e.lettergrid&&t>.7&&drawGridAroundLetters(e,n),e.square&&t>.82&&drawSquareAroundWord(e),e.cross&&t>.78&&drawCrossThroughWord(e),e.plus&&t>.78&&drawPlusThroughWord(e),refCtx.restore()}function applyGridTransform(e){refCtx.translate(2e3+4e3*e.offsetX,2500+5e3*e.offsetY),refCtx.rotate(e.rotation),refCtx.scale(e.scale,e.scale),refCtx.translate(-2e3,-2500)}function drawWashForCells(e,t,r,n){const o=4e3/e.cols,a=5e3/e.rows;refCtx.save(),refCtx.fillStyle="rgb(0,255,0)";for(const e of t){const t=n.washStampCount??1;for(let s=0;s<t;s++){const t=1e3*n.washSeed+97*s;if(stable01(e.x,e.y,e.index+t)<n.washGaps)continue;const i=stable01(e.x,e.y,101+t),l=stable01(e.x,e.y,202+t),f=stableNoise(e.x,e.y,303+t)*o*.22,c=stableNoise(e.x,e.y,404+t)*a*.22,d=o*(.04+.14*i),g=a*(.04+.14*l),h=o*r,u=a*r;refCtx.fillRect(e.x*o+d-h+f,e.y*a+g-u+c,o-2*d+2*h,a-2*g+2*u)}}refCtx.restore()}function stable01(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)}function drawWordCells(e,t,r){const n=4e3/e.cols,o=5e3/e.rows;refCtx.fillStyle="rgb(255,0,0)",refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(1,.09*Math.min(n,o)),refCtx.lineCap="round",refCtx.lineJoin="round";for(const e of t)drawLetterInCell(e.char,e.x*n,e.y*o,n,o,r,e.index)}function drawLetterInCell(e,t,r,n,o,a,s){if(" "===e)return;const i=Math.min(n,o)*RangedLetterScale(a,s),l=stableNoise(t,r,s+1)*n*.1,f=stableNoise(t,r,s+2)*o*.1,c=.05*stableNoise(t,r,s+3);refCtx.save(),refCtx.translate(t+.5*n+l,r+.54*o+f),refCtx.rotate(c),refCtx.font=`${i}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`,refCtx.textAlign="center",refCtx.textBaseline="middle",refCtx.fillText(e,0,0),refCtx.restore()}function RangedLetterScale(e,t){return e.grid.letterScale}function darkenedBgColor(e=.1){return[Math.max(0,palette.bg[0]-e),Math.max(0,palette.bg[1]-e),Math.max(0,palette.bg[2]-e),palette.bg[3]??1]}function drawUnderline(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round";const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.84*r,l=a.x*t+.85*t,f=a.y*r+.84*r,c=l-s,d=f-i,g=Math.max(1e-4,Math.sqrt(c*c+d*d)),h=-d/g,u=c/g,x=.28*Math.min(t,r);refCtx.beginPath(),refCtx.moveTo(s+h*x,i+u*x),refCtx.lineTo(l+h*x+stableNoise(l,f,9)*t*.25,f+u*x+stableNoise(s,i,10)*r*.25),refCtx.stroke(),refCtx.restore()}function drawStrikethrough(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.5*r,l=a.x*t+.85*t,f=a.y*r+.5*r;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round",refCtx.beginPath(),refCtx.moveTo(s+stableNoise(s,i,21)*t*.18,i+stableNoise(s,i,22)*r*.18),refCtx.lineTo(l+stableNoise(l,f,23)*t*.18,f+stableNoise(l,f,24)*r*.18),refCtx.stroke(),refCtx.restore()}function drawCircleAroundWord(e){const t=wordBox(e),r=.5*(t.x1+t.x2),n=.5*(t.y1+t.y2),o=.62*Math.abs(t.x2-t.x1)+4e3/e.grid.cols,a=.62*Math.abs(t.y2-t.y1)+5e3/e.grid.rows,s=.35*stableNoise(r,n,4),i=refCtx.lineWidth,l=refCtx.strokeStyle;refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineCap="round";const f=stableNoise(r,n,999),c=1+Math.floor(5*f);for(let e=0;e<c;e++){const t=1e3+137*e,l=stableNoise(r,n,t+1),f=stableNoise(r,n,t+2),c=stableNoise(r,n,t+3),d=stableNoise(r,n,t+4),g=stableNoise(r,n,t+5),h=.94+.18*g,u=.18+.95*stableNoise(r,n,t+6),x=.35+.65*d,m=4e3*l*.006,C=5e3*f*.006,w=o*h*(1+.04*c),b=a*h*(1-.04*c),R=g*Math.PI*2,p=stableNoise(r,n,t+7)<.45?.55+.35*stableNoise(r,n,t+8):1,v=R+2*Math.PI*p;refCtx.save(),refCtx.globalAlpha*=x,refCtx.lineWidth=i*u,refCtx.beginPath(),refCtx.ellipse(r+m,n+C,w,b,s+.22*d,R,v),refCtx.stroke(),refCtx.restore()}refCtx.lineWidth=i,refCtx.strokeStyle=l}function drawGridAroundLetters(e,t){const r=4e3/e.grid.cols,n=5e3/e.grid.rows,o=t.filter(e=>" "!==e.char);if(o.length){refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.5,.045*Math.min(r,n)),refCtx.lineCap="square",refCtx.lineJoin="miter";for(const t of o){const o=t.x*r,a=t.y*n,s=.025*Math.min(r,n),i=t.index+1e3*e.washSeed;refCtx.beginPath(),refCtx.rect(o+stableNoise(t.x,t.y,i+1)*s,a+stableNoise(t.x,t.y,i+2)*s,r+stableNoise(t.x,t.y,i+3)*s,n+stableNoise(t.x,t.y,i+4)*s),refCtx.stroke()}refCtx.restore()}}function drawSquareAroundWord(e){const t=expandedWordBox(e,.22),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round";const n=.12*r;refCtx.beginPath(),refCtx.moveTo(t.x1+stableNoise(t.x1,t.y1,301)*n,t.y1+stableNoise(t.x1,t.y1,302)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y1,303)*n,t.y1+stableNoise(t.x2,t.y1,304)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y2,305)*n,t.y2+stableNoise(t.x2,t.y2,306)*n),refCtx.lineTo(t.x1+stableNoise(t.x1,t.y2,307)*n,t.y2+stableNoise(t.x1,t.y2,308)*n),refCtx.closePath(),refCtx.stroke(),refCtx.restore()}function drawCrossThroughWord(e){const t=expandedWordBox(e,.12),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,t.y1,t.x2,t.y2,r,401),drawNoisyLine(t.x2,t.y1,t.x1,t.y2,r,411),refCtx.restore()}function drawPlusThroughWord(e){const t=expandedWordBox(e,.1),r=Math.min(4e3/e.gr*wid.cols,5e3/e.grid.rows),n=.5*(t.x1+t.x2),o=.5*(t.y1+t.y2);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,o,t.x2,o,r,501),drawNoisyLine(n,t.y1,n,t.y2,r,511),refCtx.restore()}function drawBackgroundWashReference(e,t){clearReferenceToBlack(),refCtx.save(),useStructureSpace(),refCtx.globalAlpha=e.alpha*t,refCtx.fillStyle="rgb(0,0,255)",refCtx.strokeStyle="rgb(0,0,255)",refCtx.lineCap="round",refCtx.lineJoin="round";const r=e.y+.42*e.h;for(let n=0;n<44;n++){const o=n/Math.max(1,43),a=e.x+e.w*o+stableNoise(e.seed,n,11)*e.w*.025,s=Math.sin(o*Math.PI),i=r-e.h*(.25+.75*s+.45*stable01(e.seed,n,12)),l=r+e.h*(.15+.55*s+.75*stable01(e.seed,n,13)),f=e.w*(.018+.045*stable01(e.seed,n,14));drawVerticalPaintColumn(a,i,l,f,e.seed+91*n,t),stable01(e.seed,n,77)<e.dripChance&&drawPaintDrip(a+stableNoise(e.seed,n,21)*f,l-.05*e.h,e.h*(.35+1.4*stable01(e.seed,n,22)),e.seed+200*n)}for(let n=0;n<9;n++){const o=r+stableNoise(e.seed,n,301)*e.h*.45;refCtx.globalAlpha=e.alpha*t*.22,refCtx.lineWidth=e.h*(.025+.045*stable01(e.seed,n,302)),drawNoisyHorizontalStroke(e.x+stableNoise(e.seed,n,303)*e.w*.05,o,e.x+e.w+stableNoise(e.seed,n,304)*e.w*.05,o+stableNoise(e.seed,n,305)*e.h*.18,e.seed+400*n)}refCtx.restore()}function drawVerticalPaintColumn(e,t,r,n,o,a){refCtx.beginPath();for(let a=0;a<=12;a++){const s=t+(r-t)*(a/12),i=e-.5*n+stableNoise(o,a,31)*n*.75;0===a?refCtx.moveTo(i,s):refCtx.lineTo(i,s)}for(let a=12;a>=0;a--){const s=t+(r-t)*(a/12),i=e+.5*n+stableNoise(o,a,41)*n*.75;refCtx.lineTo(i,s)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalAlpha*=.35;const s=2+Math.floor(5*stable01(o,1,55));for(let a=0;a<s;a++){const s=e+stableNoise(o,a,61)*n*.45;refCtx.lineWidth=n*(.08+.18*stable01(o,a,62)),refCtx.beginPath(),refCtx.moveTo(s,t+stableNoise(o,a,63)*n),refCtx.lineTo(s+stableNoise(o,a,64)*n*.45,r-stable01(o,a,65)*n),refCtx.stroke()}refCtx.restore(),refCtx.save(),refCtx.globalCompositeOperation="destination-out";for(let s=0;s<22;s++){const i=stable01(o,s,71)<.5?-1:1,l=t+stable01(o,s,72)*(r-t),f=e+i*n*(.3+.45*stable01(o,s,73));refCtx.globalAlpha=a*(.04+.14*stable01(o,s,74)),refCtx.beginPath(),refCtx.ellipse(f,l,n*(.15+.55*stable01(o,s,75)),n*(.25+1.25*stable01(o,s,76)),stableNoise(o,s,77),0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawNoisyHorizontalStroke(e,t,r,n,o){refCtx.beginPath();for(let a=0;a<=18;a++){const s=a/18,i=e+(r-e)*s,l=t+(n-t)*s+5e3*stableNoise(o,a,91)*.006;0===a?refCtx.moveTo(i,l):refCtx.lineTo(i,l)}refCtx.stroke()}function drawPainterlyBlob(e,t,r,n,o,a){const s=[];for(let a=0;a<28;a++){const i=a/28*Math.PI*2,l=1+.12*stableNoise(o,a,31)+.06*stableNoise(o,a,32)+.06*Math.sin(2*i+o)+.04*Math.sin(5*i+.37*o);s.push({x:e+Math.cos(i)*r*.5*l,y:t+Math.sin(i)*n*.5*l})}refCtx.beginPath();for(let e=0;e<s.length;e++){const t=s[(e-1+s.length)%s.length],r=s[e],n=s[(e+1)%s.length],o={x:.5*(t.x+r.x),y:.5*(t.y+r.y)},a={x:.5*(r.x+n.x),y:.5*(r.y+n.y)};0===e&&refCtx.moveTo(o.x,o.y),refCtx.quadraticCurveTo(r.x,r.y,a.x,a.y)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalCompositeOperation="destination-out",refCtx.globalAlpha=.1*a;for(let s=0;s<160;s++){const i=stable01(o,s,41)*Math.PI*2,l=Math.sqrt(stable01(o,s,42)),f=Math.cos(i)*l,c=Math.sin(i)*l,d=e+f*r*.48,g=t+c*n*.48,h=Math.min(1,Math.sqrt(f*f+c*c)),u=Math.pow(h,2.2),x=r*(.004+.09*u*stable01(o,s,43)),m=n*(.003+.05*u*stable01(o,s,44));refCtx.globalAlpha=(.01+.18*u)*a,refCtx.beginPath(),refCtx.ellipse(d,g,x,m,stableNoise(o,s,45)*Math.PI,0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawPaintDrip(e,t,r,n){const o=.045*r;refCtx.beginPath(),refCtx.moveTo(e,t);for(let a=1;a<=5;a++){const s=a/5;refCtx.lineTo(e+stableNoise(n,a,51)*o,t+r*s)}refCtx.lineWidth=Math.max(1,4.8),refCtx.lineCap="round",refCtx.strokeStyle="rgb(0,0,255)",refCtx.stroke()}function drawNoisyLine(e,t,r,n,o,a){const s=.16*o;refCtx.beginPath(),refCtx.moveTo(e+stableNoise(e,t,a+1)*s,t+stableNoise(e,t,a+2)*s),refCtx.lineTo(r+stableNoise(r,n,a+3)*s,n+stableNoise(r,n,a+4)*s),refCtx.stroke()}function expandedWordBox(e,t=.15){const r=wordBox(e),n=4e3/e.grid.cols,o=5e3/e.grid.rows;return{x1:r.x1-n*t,y1:r.y1-o*t,x2:r.x2+n*t,y2:r.y2+o*t}}function wordBox(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.map(e=>e.x*t),o=e.cells.map(e=>e.y*r);return{x1:Math.min(...n),y1:Math.min(...o),x2:Math.max(...n)+t,y2:Math.max(...o)+r}}function stableNoise(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)-.5}function run(){if(renderLoopRunning=!1,isResizing||width<MIN_GL_SIZE||height<MIN_GL_SIZE||!refTexture||frameBuffers.length<2)return void requestRun();maybeFetchMoreWords();const e=state.placements[state.activePlacementIndex];if(!e)return renderScreen(),state.frame%30==0&&sampleVisibleCanvasHealth(),void(window.__RESIDUAL_NOTES_RENDERED__||(window.__RESIDUAL_NOTES_RENDERED__=!0,window.dispatchEvent(new Event("residual-notes:rendered"))));const t=settings.pauseFrames+settings.revealFrames,r=state.frame%t;let n=0,o=!1;if(r<settings.pauseFrames)clearReferenceToBlack();else{const t=r-settings.pauseFrames;n=Math.min(1,(t+1)*settings.revealSpeed/settings.revealFrames),o=t>=settings.revealFrames-1,drawPlacementReference(e,n)}updateReferenceTexture(),renderWash(e),renderScreen(),(o||r===t-1)&&state.activePlacementIndex++,state.frame++,requestRun()}function renderWash(e){const t=frameBufferId,r=1^frameBufferId;frameBufferId=r,useProgram(washProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,frameBuffers[r].buffer),assertFramebufferHealthy("renderWash")&&(gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[t].texture),gl.uniform1i(gl.getUniformLocation(washProgram,"previousFrame"),0),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.uniform1i(gl.getUniformLocation(washProgram,"referenceMask"),1),gl.uniform1f(gl.getUniformLocation(washProgram,"frame"),state.frame),gl.uniform1f(gl.getUniformLocation(washProgram,"dirx"),e.dirx),gl.uniform1f(gl.getUniformLocation(washProgram,"diry"),e.diry),gl.uniform1f(gl.getUniformLocation(washProgram,"washStrength"),e.washStrength??settings.washStrength),gl.uniform1f(gl.getUniformLocation(washProgram,"smudgeChance"),e.smudgeChance??settings.smudgeChance),gl.uniform4fv(gl.getUniformLocation(washProgram,"bgColor"),palette.bg),gl.uniform4fv(gl.getUniformLocation(washProgram,"inkColor"),e.color),gl.uniform4fv(gl.getUniformLocation(washProgram,"highlightColor"),"backgroundWash"===e.type?e.color:palette.highlight),gl.uniform2f(gl.getUniformLocation(washProgram,"resolution"),width,height),gl.uniform1f(gl.getUniformLocation(washProgram,"grainSeed"),GRAIN_SEED),gl.uniform1f(gl.getUniformLocation(washProgram,"baseGrainAmount"),settings.baseGrainAmount),gl.uniform1f(gl.getUniformLocation(washProgram,"baseMottleAmount"),settings.baseMottleAmount),gl.drawArrays(gl.TRIANGLE_STRIP,0,4))}function sampleVisibleCanvasHealth(){if(!gl||gl.isContextLost())return;const e=new Uint8Array(4);gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.readPixels(Math.floor(.5*width),Math.floor(.5*height),1,1,gl.RGBA,gl.UNSIGNED_BYTE,e);e[0]+e[1]+e[2]<8&&state.frame>20?blackFrameCount++:blackFrameCount=0,blackFrameCount>90&&queueHardRebuild("visible canvas stayed black"),state.activePlacementIndex!==lastProgressPlacementIndex&&(lastProgressPlacementIndex=state.activePlacementIndex,lastProgressFrame=state.frame);state.activePlacementIndex>0&&state.activePlacementIndex<state.placements.length&&state.frame-lastProgressFrame>240&&queueHardRebuild("render loop appears stuck")}function renderScreen(){useProgram(screenProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[frameBufferId].texture),gl.uniform1i(gl.getUniformLocation(screenProgram,"buffer"),0),gl.drawArrays(gl.TRIANGLE_STRIP,0,4)}function useProgram(e){gl.useProgram(e);const t=gl.getAttribLocation(e,"pos");gl.enableVertexAttribArray(t),gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer),gl.vertexAttribPointer(t,3,gl.FLOAT,!1,0,0)}function createProgram(e,t){const r=gl.createProgram(),n=createShader(`#version 300 es\n${e}`,gl.VERTEX_SHADER),o=createShader(`#version 300 es\nprecision highp float;\n${t}`,gl.FRAGMENT_SHADER);return gl.attachShader(r,n),gl.attachShader(r,o),gl.linkProgram(r),gl.getProgramParameter(r,gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(r)),r}function createShader(e,t){const r=gl.createShader(t);return gl.shaderSource(r,e),gl.compileShader(r),gl.getShaderParameter(r,gl.COMPILE_STATUS)||console.error(gl.getShaderInfoLog(r)),r}function createVertexBuffer(){const e=gl.createBuffer();return gl.bindBuffer(gl.ARRAY_BUFFER,e),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,1,0,-1,-1,0,1,1,0,1,-1,0]),gl.STATIC_DRAW),e}function createFrameBuffer(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createFramebuffer(),o=createTexture(e,t,r);gl.bindFramebuffer(gl.FRAMEBUFFER,n),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,o,0);const a=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return a!==gl.FRAMEBUFFER_COMPLETE&&console.warn("Framebuffer incomplete:",a,e,t),gl.bindFramebuffer(gl.FRAMEBUFFER,null),{buffer:n,texture:o}}function createTexture(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createTexture();return gl.bindTexture(gl.TEXTURE_2D,n),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),r&&r.width>0&&r.height>0?gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,r):gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,e,t,0,gl.RGBA,gl.UNSIGNED_BYTE,null),n}function updateReferenceTexture(){refTexture&&(!ref||ref.width<MIN_GL_SIZE||ref.height<MIN_GL_SIZE||(gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,gl.RGBA,gl.UNSIGNED_BYTE,ref)))}function rgb(e){return`rgb(${Math.floor(255*e[0])}, ${Math.floor(255*e[1])}, ${Math.floor(255*e[2])})`}function clamp255(e){return Math.max(0,Math.min(255,e))}function handleKey(e){"s"===e.key&&savePNG(),"m"===e.key&&(settings.showReference=!settings.showReference,ref.style.display=settings.showReference?"block":"none"),"c"===e.key&&setCustomRenderSize(),"p"===e.key.toLowerCase()&&setCustomRpcUrl()}function savePNG(){renderScreen();const e=document.createElement("a");e.download=`residual-notes-${HASH.slice(2,10)}-${width}x${height}.png`,e.href=view.toDataURL("image/png"),e.click()}function restart(){restartAfterResize()}init();
function parsePalette(e){const t=Object.fromEntries(e.split("|").map(e=>{const t=e.indexOf("=");return[e.slice(0,t),e.slice(t+1)]})),r=e=>e.trim().split(/\s+/).map(Number);return{bg:r(t.bg),grid:r(t.grid),highlight:r(t.highlight),inks:t.inks.split(";").map(e=>{const t=r(e);return{color:t.slice(0,4),weight:t[4]}})}}palette=parsePalette(palette);class Random{constructor(e){this.useA=!1,this.prngA=this.sfc32(e.slice(2,34)),this.prngB=this.sfc32(e.slice(34,66));for(let e=0;e<1e5;e++)this.prngA(),this.prngB()}sfc32(e){let t=parseInt(e.slice(0,8),16),r=parseInt(e.slice(8,16),16),n=parseInt(e.slice(16,24),16),o=parseInt(e.slice(24,32),16);return function(){t|=0,r|=0,n|=0,o|=0;let e=(t+r|0)+o|0;return o=o+1|0,t=r^r>>>9,r=n+(n<<3)|0,n=n<<21|n>>>11,n=n+e|0,(e>>>0)/4294967296}}g(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}range(e,t){return e+this.g()*(t-e)}int(e,t){return Math.floor(this.range(e,t+1))}pick(e){return e[Math.floor(this.g()*e.length)]}chance(e){return this.g()<e}}let R=new Random(HASH);const wordStream={fetching:!1,done:WORDS.length>=WORD_COUNT,failed:!1,nextOffset:WORDS.length};let customRpcUrl="";function getRpcUrls(){return customRpcUrl?[customRpcUrl,...RPC_URLS.filter(e=>e!==customRpcUrl)]:RPC_URLS}function setCustomRpcUrl(){const e=prompt("Custom RPC URL:",customRpcUrl||"https://...");if(!e)return;const t=e.trim();/^https?:\/\//i.test(t)?(customRpcUrl=t,wordStream.fetching=!1,wordStream.failed=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.nextOffset=WORDS.length,maybeFetchMoreWords(),requestRun(),console.log("Custom RPC set:",customRpcUrl)):alert("Please enter a valid http(s) RPC URL.")}const STRUCT_W=4e3,STRUCT_H=5e3,STRUCT_ASPECT=.8;let customRenderWidth=null,customRenderHeight=null,lastGoodWidth=1200,lastGoodHeight=1500,restartQueued=!1,blackFrameCount=0,lastProgressFrame=0,lastProgressPlacementIndex=0;const MAX_BLACK_FRAMES=90,MAX_STUCK_FRAMES=240;let renderLoopRunning=!1;function requestRun(){renderLoopRunning||(renderLoopRunning=!0,requestAnimationFrame(run))}const GRAIN_SEED=parseInt(HASH.slice(2,10),16)/4294967295,settings={aspect:.8,washStrength:R.range(.1,.8),smudgeChance:R.range(.1,.24),textureAmount:R.range(.025,.08),pauseFrames:0,revealFrames:2,revealSpeed:1,backgroundWashChance:.01,backgroundWashMinWords:5,backgroundWashMaxWords:50,backgroundWashColor:palette.highlight,baseGrainAmount:.02,baseMottleAmount:8e-4,gridDefinitions:[...Array(5)].map((e,t)=>(co=t>0?Math.floor(R.range(70,130)):Math.floor(R.range(45,65)),ro=Math.floor(1.25*co),{name:t.toString(),cols:co,rows:ro,rotation:R.g()/25-.02,scale:1,offsetX:0,offsetY:0,letterScale:.8})),showReference:!1};function shuffled(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=R.int(0,e);[t[e],t[r]]=[t[r],t[e]]}return t}const view=document.getElementById("view"),ref=document.getElementById("ref"),refCtx=ref.getContext("2d",{alpha:!1});let gl,vertexBuffer,washProgram,screenProgram,refTexture,width=0,height=0,dpr=1,frameBuffers=[],frameBufferId=0,resizeQueued=!1,isResizing=!1;const MIN_GL_SIZE=2,state={frame:0,grids:[],placements:[],activePlacementIndex:0},vertexShader="\n in vec3 pos;\n out vec2 vUv;\n\n void main() {\n gl_Position = vec4(pos, 1.0);\n vUv = (pos.xy + 1.0) * 0.5;\n }\n",washShader="\n uniform sampler2D previousFrame;\n uniform sampler2D referenceMask;\n uniform float frame;\n uniform float dirx;\n uniform float diry;\n uniform float washStrength;\n uniform float smudgeChance;\n uniform float textureAmount;\n uniform vec4 bgColor;\n uniform vec4 inkColor;\n uniform vec4 highlightColor;\n\n uniform vec2 resolution;\n uniform float grainSeed;\n uniform float baseGrainAmount;\n uniform float baseMottleAmount;\n\n in vec2 vUv;\n out vec4 col;\n\n vec3 random3(vec3 c) {\n float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0)));\n vec3 r;\n r.z = fract(512.0 * j);\n j *= 0.125;\n r.x = fract(512.0 * j);\n j *= 0.125;\n r.y = fract(512.0 * j);\n return r - 0.5;\n }\n\n const float F3 = 0.3333333;\n const float G3 = 0.1666667;\n\n float simplex3(vec3 p) {\n vec3 s = floor(p + dot(p, vec3(F3)));\n vec3 x = p - s + dot(s, vec3(G3));\n vec3 e = step(vec3(0.0), x - x.yzx);\n vec3 i1 = e * (1.0 - e.zxy);\n vec3 i2 = 1.0 - e.zxy * (1.0 - e);\n vec3 x1 = x - i1 + G3;\n vec3 x2 = x - i2 + 2.0 * G3;\n vec3 x3 = x - 1.0 + 3.0 * G3;\n vec4 w, d;\n w.x = dot(x, x);\n w.y = dot(x1, x1);\n w.z = dot(x2, x2);\n w.w = dot(x3, x3);\n w = max(0.6 - w, 0.0);\n d.x = dot(random3(s), x);\n d.y = dot(random3(s + i1), x1);\n d.z = dot(random3(s + i2), x2);\n d.w = dot(random3(s + 1.0), x3);\n w *= w;\n w *= w;\n d *= w;\n return dot(d, vec4(52.0));\n }\n\n float hash12(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33 + grainSeed * 17.17);\n return fract((p3.x + p3.y) * p3.z);\n }\n\n void main() {\n vec4 ref = texture(referenceMask, vUv);\n vec4 prev = texture(previousFrame, vUv);\n\n float grainA = simplex3(vec3(vUv.x * 380.0 + frame * 0.011, vUv.y * 380.0 - frame * 0.013, 11.0));\n float grainB = simplex3(vec3(vUv.x * 7500.0, vUv.y * 7500.0, frame * 0.018));\n\n vec4 texturedBg = bgColor + vec4(vec3(grainB * textureAmount), 0.0);\n\n col = prev;\n\n // Only bake paper grain into the very first rendered frame.\n // After that it becomes part of previousFrame.\n if (frame < 0.5) {\n vec2 px = gl_FragCoord.xy;\n\n float fine = hash12(px) - 0.5;\n\n float fine2 = hash12(px * 0.5 + 91.7) - 0.5;\n float mottle = simplex3(vec3(\n vUv.x * 95.0 + grainSeed * 13.0,\n vUv.y * 95.0 - grainSeed * 9.0,\n 4.0\n ));\n\n float paper =\n fine * baseGrainAmount +\n fine2 * baseGrainAmount * 0.45 +\n mottle * baseMottleAmount;\n\n col.rgb += vec3(paper);\n }\n\n if (ref.g > 0.001) {\n float rnd = simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 50.0,\n grainSeed * 100000.0\n ));\n\n vec4 textureBG = mix(\n bgColor,\n vec4(\n bgColor.rgb - vec3(\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 500.0,\n (vUv.y - frame / 1000.0) * 500.0,\n grainSeed * 100000.0\n )) / 80.\n ),\n 1.0\n ),\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 5.0,\n (vUv.y - frame / 1000.0) / 4.0,\n grainSeed * 100000.0\n ))\n );\n\n // This is the core wash behavior from the second project:\n // replace/scratch toward textured background, instead of dragging black blocks.\n col = mix(\n textureBG,\n // vec4(1.,0.,0.,1.),\n // bgColor,\n prev,\n simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 5000.0,\n grainSeed * 100000.0\n ))*4.\n );\n\n // Directional smudge, but only sometimes.\n if (rnd < smudgeChance) {\n col = mix(\n texture(previousFrame, vec2(\n vUv.x + 0.003 * dirx,\n vUv.y + 0.003 * diry\n )),\n textureBG,\n rnd\n );\n }\n\n // Let the green mask control strength.\n prev = bgColor;\n col = mix(prev, col, ref.g * washStrength);\n }\n\n if (ref.b > 0.01) {\n float paintMask = ref.b;\n\n float roughA = simplex3(vec3(\n vUv.x * 900.0,\n vUv.y * 900.0,\n frame * 0.012\n ));\n\n float roughB = simplex3(vec3(\n vUv.x * 2200.0,\n vUv.y * 2200.0,\n 19.0\n ));\n\n float brokenMask = paintMask;\n brokenMask *= smoothstep(-0.45, 0.55, roughA);\n brokenMask *= 0.72 + roughB * 0.28;\n\n vec4 noisyPaint = highlightColor;\n noisyPaint.rgb += vec3(roughB * 0.10 + roughA * 0.035);\n\n vec2 dragUv = vUv + vec2(\n roughA * 0.0025,\n roughB * 0.0015\n );\n\n vec4 draggedSurface = texture(previousFrame, dragUv);\n\n vec4 paintOnSurface = mix(draggedSurface, noisyPaint, brokenMask);\n\n col = mix(col, paintOnSurface, brokenMask);\n }\n\n // Red = foreground ink. The incoming reference contains only the current word.\n if (ref.r > 0.01) {\n float smearNoise = simplex3(vec3(\n vUv.x * 900.0 + frame * 0.03,\n vUv.y * 220.0 - frame * 0.02,\n grainSeed * 9999.0\n ));\n\n float smearMask = ref.r * smoothstep(-0.35, 0.55, smearNoise);\n\n vec2 smearUv = vUv + vec2(\n -0.006 * dirx,\n 0.002 * diry\n );\n\n vec4 draggedInk = texture(previousFrame, smearUv);\n\n col = mix(col, draggedInk, smearMask * 0.65);\n\n vec4 brokenInk = inkColor;\n brokenInk.rgb += vec3(smearNoise * 0.16);\n\n col = mix(col, brokenInk, ref.r);\n }\n }\n",screenShader="\n uniform sampler2D buffer;\n in vec2 vUv;\n out vec4 col;\n\n void main() {\n col = texture(buffer, vec2(vUv.x, 1.0 - vUv.y));\n }\n";async function maybeFetchMoreWords(){if(!wordStream.fetching&&!wordStream.done&&!wordStream.failed)if(RPC_URLS.length){wordStream.fetching=!0;try{const e=await fetchNotesSlice(wordStream.nextOffset,FETCH_BATCH_SIZE);if(!e.length)return void(wordStream.done=!0);for(const t of e)WORDS.push(t),addWordPlacement(t);wordStream.nextOffset+=e.length,wordStream.nextOffset>=WORD_COUNT&&(wordStream.done=!0)}catch(e){console.warn("RPC word fetch failed:",e),wordStream.failed=!0}finally{wordStream.fetching=!1}}else wordStream.failed=!0}async function fetchNotesSlice(e,t){let r=null;for(const n of getRpcUrls())try{return await fetchNotesSliceFromRpc(n,TOKEN_ID,e,t)}catch(e){r=e,console.warn("RPC failed, trying next:",n,e)}throw r||new Error("No RPC worked")}const NOTES_SLICE_SELECTOR="0x91cf7995";async function fetchNotesSliceFromRpc(e,t,r,n){const o=NOTES_SLICE_SELECTOR+encodeUint256(t)+encodeUint256(r)+encodeUint256(n),a=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:CONTRACT_ADDRESS,data:o},"latest"]})}),s=await a.json();if(s.error)throw new Error(s.error.message||"RPC error");return s.result&&"0x"!==s.result?decodeStringArray(s.result):[]}function encodeUint256(e){return BigInt(e).toString(16).padStart(64,"0")}function decodeStringArray(e){e=e.replace(/^0x/,"");const t=t=>{const r=2*t;return BigInt("0x"+e.slice(r,r+64))},r=(t,r)=>{const n=2*t;return e.slice(n,n+2*r)},n=Number(t(0)),o=Number(t(n)),a=[];for(let e=0;e<o;e++){const o=n+32+Number(t(n+32+32*e)),s=r(o+32,Number(t(o)));a.push(hexToUtf8(s))}return a}function hexToUtf8(e){const t=[];for(let r=0;r<e.length;r+=2)t.push(parseInt(e.slice(r,r+2),16));return(new TextDecoder).decode(new Uint8Array(t)).replace(/\0/g,"").trim()}function varyColor(e,t=.01){return[Math.max(0,Math.min(1,e[0]+R.range(-t,t))),Math.max(0,Math.min(1,e[1]+R.range(-t,t))),Math.max(0,Math.min(1,e[2]+R.range(-t,t))),e[3]??1]}function init(){if(gl=view.getContext("webgl2",{alpha:!1,antialias:!1,preserveDrawingBuffer:!0}),!gl)return void(document.body.innerHTML="WebGL2 is required.");vertexBuffer=createVertexBuffer(),washProgram=createProgram(vertexShader,washShader),screenProgram=createProgram(vertexShader,screenShader),createGrids(),createWordPlacements(),window.addEventListener("resize",requestResize),window.addEventListener("keydown",handleKey),view.addEventListener("webglcontextlost",e=>{e.preventDefault(),console.warn("WebGL context lost")},!1),view.addEventListener("webglcontextrestored",()=>{console.warn("WebGL context restored, rebuilding"),hardRebuild()},!1);new ResizeObserver(()=>requestResize()).observe(document.body),resize(),requestRun()}function resetRandom(){R=new Random(HASH)}function requestResize(){resizeQueued||(resizeQueued=!0,requestAnimationFrame(()=>{resizeQueued=!1,resize()}))}function resetWordsToDefault(){WORDS.length=0,WORDS.push(...INITIAL_WORDS),wordStream.fetching=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.failed=!1,wordStream.nextOffset=WORDS.length}let lastRenderedPixelCount=0;function restartAfterResize(){resetWordsToDefault(),resetRandom(),state.frame=0,state.activePlacementIndex=0,createGrids(),createWordPlacements(),drawBaseReference(),refTexture&&gl.deleteTexture(refTexture),refTexture=createTexture(width,height,ref),resetFrameBuffers(),window.__RESIDUAL_NOTES_RENDERED__=!1,blackFrameCount=0,lastProgressFrame=0,lastRenderedPixelCount=width*height,requestRun()}let renderW=null,renderH=null;function resize(){const e=1*Math.floor(window.innerWidth||document.documentElement.clientWidth||0),t=1*Math.floor(window.innerHeight||document.documentElement.clientHeight||0);if(e<20||t<20)return void requestAnimationFrame(requestResize);let r,n;e/t>settings.aspect?(n=t,r=t*settings.aspect):(r=e,n=e/settings.aspect),dpr=Math.max(1,window.devicePixelRatio||1);const o=renderW??Math.max(20,Math.round(r*dpr)),a=renderH??Math.max(20,Math.round(n*dpr));if(!(o===width&&a===height)){if(width>0&&height>0&&!(o*a>width*height))return view.style.width=`${r}px`,void(view.style.height=`${n}px`);isResizing=!0,width=o,height=a,view.width=width,view.height=height,view.style.display="block",ref.width=width,ref.height=height,ref.style.display=settings.showReference?"block":"none",restartAfterResize(),isResizing=!1}}function setCustomRenderSize(){const e=prompt("Render width in pixels:",width);if(!e)return;const t=Math.max(100,Math.floor(Number(e)));Number.isFinite(t)&&(renderW=t,renderH=Math.round(renderW/settings.aspect),resize())}function useStructureSpace(){refCtx.setTransform(width/4e3,0,0,height/5e3,0,0)}function resetFrameBuffers(){for(const e of frameBuffers)gl.deleteFramebuffer(e.buffer),gl.deleteTexture(e.texture);frameBuffers=[createFrameBuffer(width,height,ref),createFrameBuffer(width,height,null)],frameBufferId=0}function queueHardRebuild(e="unknown"){restartQueued||(restartQueued=!0,console.warn("Queueing hard rebuild:",e),requestAnimationFrame(()=>{restartQueued=!1,hardRebuild(e)}))}function hardRebuild(e="unknown"){if(console.warn("Hard rebuild:",e),gl&&!gl.isContextLost())try{if(resize(),width<MIN_GL_SIZE||height<MIN_GL_SIZE)return;restartAfterResize(),blackFrameCount=0,lastProgressFrame=state.frame,window.__RESIDUAL_NOTES_RENDERED__=!1}catch(e){console.warn("Hard rebuild failed:",e)}}function assertFramebufferHealthy(e=""){const t=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return t===gl.FRAMEBUFFER_COMPLETE||(queueHardRebuild(`bad framebuffer ${e}: ${t}`),!1)}function drawBaseReference(){useStructureSpace(),refCtx.fillStyle=rgb(palette.bg),refCtx.fillRect(0,0,4e3,5e3),refCtx.fillStyle=rgb(palette.grid);const e=Math.floor(settings.gridDefinitions[0].cols),t=Math.floor(settings.gridDefinitions[0].rows);for(let t=2;t<e-2;t++)refCtx.fillRect(4e3/e*t,0,1,5e3);for(let e=2;e<t-2;e++)refCtx.fillRect(0,5e3/t*e,4e3,1)}function createGrids(){state.grids=settings.gridDefinitions.map((e,t)=>({id:t,name:e.name||`grid ${t}`,cols:Math.floor(e.cols),rows:Math.floor(e.rows),rotation:e.rotation||0,scale:e.scale||1,offsetX:e.offsetX||0,offsetY:e.offsetY||0,letterScale:e.letterScale||.8,occupied:null}))}function createWordPlacements(){state.placements=[];let e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords);for(const t of WORDS)addWordPlacement(t),e--,e<=0&&R.chance(settings.backgroundWashChance)&&(addBackgroundWashPlacement(),e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords))}function addWordPlacement(e){if(!e||!String(e).trim())return!1;const t=String(e).toUpperCase(),r=tryCreateWordPlacement(t)||tryCreateWordPlacement(t)||tryCreateWordPlacement(t);return!!r&&(state.placements.push(r),!0)}function addBackgroundWashPlacement(){state.placements.push({type:"backgroundWash",color:R.chance(.15)?palette.highlight:darkenedBgColor(.1),dirx:R.range(-1,1),diry:R.range(-1,1),washStrength:R.range(.03,.16),smudgeChance:R.range(.1,.32),x:4e3*R.range(-.08,.85),y:5e3*R.range(-.08,.85),w:4e3*R.range(.18,.55),h:5e3*R.range(.12,.38),seed:9999*R.g(),alpha:R.range(.02,.175),dripChance:R.range(.08,.28),force:1})}function tryCreateWordPlacement(e){for(let t=0;t<80;t++){const t=R.pick(state.grids),r=R.pick(["right","down","right","down","right","down","right","down","right","down","right","down","right","down","diagDown","diagUp"]),n=randomStartForTextBlock(t,e,r),o=textBlockCells(n.x,n.y,e,r,t);if(cellsFit(t,o))return{word:e,grid:t,direction:r,cells:o,color:varyColor(pickInkForWord(),.05),dirx:R.range(-2,2),diry:R.range(-2,2),washSeed:R.g(),washStrength:R.range(.05,.35),smudgeChance:R.range(.1,.32),washPad:R.range(.04,.2),washGaps:0,washStampCount:R.int(1,6),circle:R.chance(.065),underline:R.chance(.05),strikethrough:R.chance(.05),lettergrid:R.chance(.01),square:R.chance(.01),cross:R.chance(.01),plus:R.chance(.005),force:1}}return null}function randomStartForTextBlock(e,t,r){const n=Math.min(String(t).length,"right"===r?e.cols:e.rows),o=R.int(0,e.cols-1),a=R.int(0,e.rows-1);return"right"===r?{x:Math.max(0,Math.min(e.cols-1,o-Math.floor(n*R.range(.25,.65)))),y:Math.max(0,Math.min(e.rows-1,a-R.int(0,2)))}:"down"===r?{x:Math.max(0,Math.min(e.cols-1,o-R.int(0,2))),y:Math.max(0,Math.min(e.rows-1,a-Math.floor(n*R.range(.25,.65))))}:{x:o,y:a}}function textBlockCells(e,t,r,n,o){if("diagDown"===n||"diagUp"===n)return diagonalTextCells(e,t,r,n);const a=String(r).split(/\s+/),s=[],i="right"===n?Math.max(1,o.cols-e):Math.max(1,o.rows-t);let l=0,f=0;for(const r of a){if(f+(f>0?1:0)+r.length>i&&(l++,f=0),"right"===n&&t+l>=o.rows)break;if("down"===n&&e+l>=o.cols)break;f>0&&(addTextCharCell(s,e,t,n,f,l," "),f++);for(let a=0;a<r.length&&(f>=i&&(l++,f=0),!("right"===n&&t+l>=o.rows))&&!("down"===n&&e+l>=o.cols);a++)addTextCharCell(s,e,t,n,f,l,r[a]),f++}return s.map((e,t)=>({...e,index:t}))}function addTextCharCell(e,t,r,n,o,a,s){"right"===n&&e.push({x:t+o,y:r+a,char:s}),"down"===n&&e.push({x:t+a,y:r+o,char:s})}function diagonalTextCells(e,t,r,n){const o=[];for(let a=0;a<r.length;a++){let s=e+a,i="diagDown"===n?t+a:t-a;o.push({x:s,y:i,char:r[a],index:a})}return o}function cellsFit(e,t){return t.every(t=>t.x>=0&&t.y>=0&&t.x<e.cols&&t.y<e.rows)}function pickInkForWord(){return weightedPick(palette.inks).color}function weightedPick(e){const t=e.reduce((e,t)=>e+t.weight,0);let r=R.g()*t;for(const t of e)if(r-=t.weight,r<=0)return t;return e[e.length-1]}function clearReferenceToBlack(){refCtx.setTransform(1,0,0,1,0,0),refCtx.fillStyle="black",refCtx.fillRect(0,0,width,height)}function drawPlacementReference(e,t){if("backgroundWash"===e.type)return void drawBackgroundWashReference(e,t);clearReferenceToBlack(),refCtx.save(),useStructureSpace(),applyGridTransform(e.grid);const r=Math.ceil(e.cells.length*t),n=e.cells.slice(0,r);drawWashForCells(e.grid,n,e.washPad,e),drawWordCells(e.grid,n,e),e.underline&&t>.72&&drawUnderline(e),e.circle&&t>.84&&drawCircleAroundWord(e),e.strikethrough&&t>.65&&drawStrikethrough(e),e.lettergrid&&t>.7&&drawGridAroundLetters(e,n),e.square&&t>.82&&drawSquareAroundWord(e),e.cross&&t>.78&&drawCrossThroughWord(e),e.plus&&t>.78&&drawPlusThroughWord(e),refCtx.restore()}function applyGridTransform(e){refCtx.translate(2e3+4e3*e.offsetX,2500+5e3*e.offsetY),refCtx.rotate(e.rotation),refCtx.scale(e.scale,e.scale),refCtx.translate(-2e3,-2500)}function drawWashForCells(e,t,r,n){const o=4e3/e.cols,a=5e3/e.rows;refCtx.save(),refCtx.fillStyle="rgb(0,255,0)";for(const e of t){const t=n.washStampCount??1;for(let s=0;s<t;s++){const t=1e3*n.washSeed+97*s;if(stable01(e.x,e.y,e.index+t)<n.washGaps)continue;const i=stable01(e.x,e.y,101+t),l=stable01(e.x,e.y,202+t),f=stableNoise(e.x,e.y,303+t)*o*.22,c=stableNoise(e.x,e.y,404+t)*a*.22,d=o*(.04+.14*i),g=a*(.04+.14*l),u=o*r,h=a*r;refCtx.fillRect(e.x*o+d-u+f,e.y*a+g-h+c,o-2*d+2*u,a-2*g+2*h)}}refCtx.restore()}function stable01(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)}function drawWordCells(e,t,r){const n=4e3/e.cols,o=5e3/e.rows;refCtx.fillStyle="rgb(255,0,0)",refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(1,.09*Math.min(n,o)),refCtx.lineCap="round",refCtx.lineJoin="round";for(const e of t)drawLetterInCell(e.char,e.x*n,e.y*o,n,o,r,e.index)}function drawLetterInCell(e,t,r,n,o,a,s){if(" "===e)return;const i=Math.min(n,o)*RangedLetterScale(a,s),l=stableNoise(t,r,s+1)*n*.1,f=stableNoise(t,r,s+2)*o*.1,c=.05*stableNoise(t,r,s+3);refCtx.save(),refCtx.translate(t+.5*n+l,r+.54*o+f),refCtx.rotate(c),refCtx.font=`${i}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`,refCtx.textAlign="center",refCtx.textBaseline="middle",refCtx.fillText(e,0,0),refCtx.restore()}function RangedLetterScale(e,t){return e.grid.letterScale}function darkenedBgColor(e=.1){return[Math.max(0,palette.bg[0]-e),Math.max(0,palette.bg[1]-e),Math.max(0,palette.bg[2]-e),palette.bg[3]??1]}function drawUnderline(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round";const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.84*r,l=a.x*t+.85*t,f=a.y*r+.84*r,c=l-s,d=f-i,g=Math.max(1e-4,Math.sqrt(c*c+d*d)),u=-d/g,h=c/g,x=.28*Math.min(t,r);refCtx.beginPath(),refCtx.moveTo(s+u*x,i+h*x),refCtx.lineTo(l+u*x+stableNoise(l,f,9)*t*.25,f+h*x+stableNoise(s,i,10)*r*.25),refCtx.stroke(),refCtx.restore()}function drawStrikethrough(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.5*r,l=a.x*t+.85*t,f=a.y*r+.5*r;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round",refCtx.beginPath(),refCtx.moveTo(s+stableNoise(s,i,21)*t*.18,i+stableNoise(s,i,22)*r*.18),refCtx.lineTo(l+stableNoise(l,f,23)*t*.18,f+stableNoise(l,f,24)*r*.18),refCtx.stroke(),refCtx.restore()}function drawCircleAroundWord(e){const t=wordBox(e),r=.5*(t.x1+t.x2),n=.5*(t.y1+t.y2),o=.62*Math.abs(t.x2-t.x1)+4e3/e.grid.cols,a=.62*Math.abs(t.y2-t.y1)+5e3/e.grid.rows,s=.35*stableNoise(r,n,4),i=refCtx.lineWidth,l=refCtx.strokeStyle;refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineCap="round";const f=stableNoise(r,n,999),c=1+Math.floor(5*f);for(let e=0;e<c;e++){const t=1e3+137*e,l=stableNoise(r,n,t+1),f=stableNoise(r,n,t+2),c=stableNoise(r,n,t+3),d=stableNoise(r,n,t+4),g=stableNoise(r,n,t+5),u=.94+.18*g,h=.18+.95*stableNoise(r,n,t+6),x=.35+.65*d,m=4e3*l*.006,C=5e3*f*.006,w=o*u*(1+.04*c),b=a*u*(1-.04*c),R=g*Math.PI*2,p=stableNoise(r,n,t+7)<.45?.55+.35*stableNoise(r,n,t+8):1,v=R+2*Math.PI*p;refCtx.save(),refCtx.globalAlpha*=x,refCtx.lineWidth=i*h,refCtx.beginPath(),refCtx.ellipse(r+m,n+C,w,b,s+.22*d,R,v),refCtx.stroke(),refCtx.restore()}refCtx.lineWidth=i,refCtx.strokeStyle=l}function drawGridAroundLetters(e,t){const r=4e3/e.grid.cols,n=5e3/e.grid.rows,o=t.filter(e=>" "!==e.char);if(o.length){refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.5,.045*Math.min(r,n)),refCtx.lineCap="square",refCtx.lineJoin="miter";for(const t of o){const o=t.x*r,a=t.y*n,s=.025*Math.min(r,n),i=t.index+1e3*e.washSeed;refCtx.beginPath(),refCtx.rect(o+stableNoise(t.x,t.y,i+1)*s,a+stableNoise(t.x,t.y,i+2)*s,r+stableNoise(t.x,t.y,i+3)*s,n+stableNoise(t.x,t.y,i+4)*s),refCtx.stroke()}refCtx.restore()}}function drawSquareAroundWord(e){const t=expandedWordBox(e,.22),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round";const n=.12*r;refCtx.beginPath(),refCtx.moveTo(t.x1+stableNoise(t.x1,t.y1,301)*n,t.y1+stableNoise(t.x1,t.y1,302)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y1,303)*n,t.y1+stableNoise(t.x2,t.y1,304)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y2,305)*n,t.y2+stableNoise(t.x2,t.y2,306)*n),refCtx.lineTo(t.x1+stableNoise(t.x1,t.y2,307)*n,t.y2+stableNoise(t.x1,t.y2,308)*n),refCtx.closePath(),refCtx.stroke(),refCtx.restore()}function drawCrossThroughWord(e){const t=expandedWordBox(e,.12),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,t.y1,t.x2,t.y2,r,401),drawNoisyLine(t.x2,t.y1,t.x1,t.y2,r,411),refCtx.restore()}function drawPlusThroughWord(e){const t=expandedWordBox(e,.1),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows),n=.5*(t.x1+t.x2),o=.5*(t.y1+t.y2);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,o,t.x2,o,r,501),drawNoisyLine(n,t.y1,n,t.y2,r,511),refCtx.restore()}function drawBackgroundWashReference(e,t){clearReferenceToBlack(),refCtx.save(),useStructureSpace(),refCtx.globalAlpha=e.alpha*t,refCtx.fillStyle="rgb(0,0,255)",refCtx.strokeStyle="rgb(0,0,255)",refCtx.lineCap="round",refCtx.lineJoin="round";const r=e.y+.42*e.h;for(let n=0;n<44;n++){const o=n/Math.max(1,43),a=e.x+e.w*o+stableNoise(e.seed,n,11)*e.w*.025,s=Math.sin(o*Math.PI),i=r-e.h*(.25+.75*s+.45*stable01(e.seed,n,12)),l=r+e.h*(.15+.55*s+.75*stable01(e.seed,n,13)),f=e.w*(.018+.045*stable01(e.seed,n,14));drawVerticalPaintColumn(a,i,l,f,e.seed+91*n,t),stable01(e.seed,n,77)<e.dripChance&&drawPaintDrip(a+stableNoise(e.seed,n,21)*f,l-.05*e.h,e.h*(.35+1.4*stable01(e.seed,n,22)),e.seed+200*n)}for(let n=0;n<9;n++){const o=r+stableNoise(e.seed,n,301)*e.h*.45;refCtx.globalAlpha=e.alpha*t*.22,refCtx.lineWidth=e.h*(.025+.045*stable01(e.seed,n,302)),drawNoisyHorizontalStroke(e.x+stableNoise(e.seed,n,303)*e.w*.05,o,e.x+e.w+stableNoise(e.seed,n,304)*e.w*.05,o+stableNoise(e.seed,n,305)*e.h*.18,e.seed+400*n)}refCtx.restore()}function drawVerticalPaintColumn(e,t,r,n,o,a){refCtx.beginPath();for(let a=0;a<=12;a++){const s=t+(r-t)*(a/12),i=e-.5*n+stableNoise(o,a,31)*n*.75;0===a?refCtx.moveTo(i,s):refCtx.lineTo(i,s)}for(let a=12;a>=0;a--){const s=t+(r-t)*(a/12),i=e+.5*n+stableNoise(o,a,41)*n*.75;refCtx.lineTo(i,s)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalAlpha*=.35;const s=2+Math.floor(5*stable01(o,1,55));for(let a=0;a<s;a++){const s=e+stableNoise(o,a,61)*n*.45;refCtx.lineWidth=n*(.08+.18*stable01(o,a,62)),refCtx.beginPath(),refCtx.moveTo(s,t+stableNoise(o,a,63)*n),refCtx.lineTo(s+stableNoise(o,a,64)*n*.45,r-stable01(o,a,65)*n),refCtx.stroke()}refCtx.restore(),refCtx.save(),refCtx.globalCompositeOperation="destination-out";for(let s=0;s<22;s++){const i=stable01(o,s,71)<.5?-1:1,l=t+stable01(o,s,72)*(r-t),f=e+i*n*(.3+.45*stable01(o,s,73));refCtx.globalAlpha=a*(.04+.14*stable01(o,s,74)),refCtx.beginPath(),refCtx.ellipse(f,l,n*(.15+.55*stable01(o,s,75)),n*(.25+1.25*stable01(o,s,76)),stableNoise(o,s,77),0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawNoisyHorizontalStroke(e,t,r,n,o){refCtx.beginPath();for(let a=0;a<=18;a++){const s=a/18,i=e+(r-e)*s,l=t+(n-t)*s+5e3*stableNoise(o,a,91)*.006;0===a?refCtx.moveTo(i,l):refCtx.lineTo(i,l)}refCtx.stroke()}function drawPainterlyBlob(e,t,r,n,o,a){const s=[];for(let a=0;a<28;a++){const i=a/28*Math.PI*2,l=1+.12*stableNoise(o,a,31)+.06*stableNoise(o,a,32)+.06*Math.sin(2*i+o)+.04*Math.sin(5*i+.37*o);s.push({x:e+Math.cos(i)*r*.5*l,y:t+Math.sin(i)*n*.5*l})}refCtx.beginPath();for(let e=0;e<s.length;e++){const t=s[(e-1+s.length)%s.length],r=s[e],n=s[(e+1)%s.length],o={x:.5*(t.x+r.x),y:.5*(t.y+r.y)},a={x:.5*(r.x+n.x),y:.5*(r.y+n.y)};0===e&&refCtx.moveTo(o.x,o.y),refCtx.quadraticCurveTo(r.x,r.y,a.x,a.y)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalCompositeOperation="destination-out",refCtx.globalAlpha=.1*a;for(let s=0;s<160;s++){const i=stable01(o,s,41)*Math.PI*2,l=Math.sqrt(stable01(o,s,42)),f=Math.cos(i)*l,c=Math.sin(i)*l,d=e+f*r*.48,g=t+c*n*.48,u=Math.min(1,Math.sqrt(f*f+c*c)),h=Math.pow(u,2.2),x=r*(.004+.09*h*stable01(o,s,43)),m=n*(.003+.05*h*stable01(o,s,44));refCtx.globalAlpha=(.01+.18*h)*a,refCtx.beginPath(),refCtx.ellipse(d,g,x,m,stableNoise(o,s,45)*Math.PI,0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawPaintDrip(e,t,r,n){const o=.045*r;refCtx.beginPath(),refCtx.moveTo(e,t);for(let a=1;a<=5;a++){const s=a/5;refCtx.lineTo(e+stableNoise(n,a,51)*o,t+r*s)}refCtx.lineWidth=Math.max(1,4.8),refCtx.lineCap="round",refCtx.strokeStyle="rgb(0,0,255)",refCtx.stroke()}function drawNoisyLine(e,t,r,n,o,a){const s=.16*o;refCtx.beginPath(),refCtx.moveTo(e+stableNoise(e,t,a+1)*s,t+stableNoise(e,t,a+2)*s),refCtx.lineTo(r+stableNoise(r,n,a+3)*s,n+stableNoise(r,n,a+4)*s),refCtx.stroke()}function expandedWordBox(e,t=.15){const r=wordBox(e),n=4e3/e.grid.cols,o=5e3/e.grid.rows;return{x1:r.x1-n*t,y1:r.y1-o*t,x2:r.x2+n*t,y2:r.y2+o*t}}function wordBox(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.map(e=>e.x*t),o=e.cells.map(e=>e.y*r);return{x1:Math.min(...n),y1:Math.min(...o),x2:Math.max(...n)+t,y2:Math.max(...o)+r}}function stableNoise(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)-.5}function run(){if(renderLoopRunning=!1,isResizing||width<MIN_GL_SIZE||height<MIN_GL_SIZE||!refTexture||frameBuffers.length<2)return void requestRun();maybeFetchMoreWords();const e=state.placements[state.activePlacementIndex];if(!e)return renderScreen(),state.frame%30==0&&sampleVisibleCanvasHealth(),void(window.__RESIDUAL_NOTES_RENDERED__||(window.__RESIDUAL_NOTES_RENDERED__=!0,window.dispatchEvent(new Event("residual-notes:rendered"))));const t=settings.pauseFrames+settings.revealFrames,r=state.frame%t;let n=0,o=!1;if(r<settings.pauseFrames)clearReferenceToBlack();else{const t=r-settings.pauseFrames;n=Math.min(1,(t+1)*settings.revealSpeed/settings.revealFrames),o=t>=settings.revealFrames-1,drawPlacementReference(e,n)}updateReferenceTexture(),renderWash(e),renderScreen(),(o||r===t-1)&&state.activePlacementIndex++,state.frame++,requestRun()}function renderWash(e){const t=frameBufferId,r=1^frameBufferId;frameBufferId=r,useProgram(washProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,frameBuffers[r].buffer),assertFramebufferHealthy("renderWash")&&(gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[t].texture),gl.uniform1i(gl.getUniformLocation(washProgram,"previousFrame"),0),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.uniform1i(gl.getUniformLocation(washProgram,"referenceMask"),1),gl.uniform1f(gl.getUniformLocation(washProgram,"frame"),state.frame),gl.uniform1f(gl.getUniformLocation(washProgram,"dirx"),e.dirx),gl.uniform1f(gl.getUniformLocation(washProgram,"diry"),e.diry),gl.uniform1f(gl.getUniformLocation(washProgram,"washStrength"),e.washStrength??settings.washStrength),gl.uniform1f(gl.getUniformLocation(washProgram,"smudgeChance"),e.smudgeChance??settings.smudgeChance),gl.uniform1f(gl.getUniformLocation(washProgram,"textureAmount"),settings.textureAmount),gl.uniform4fv(gl.getUniformLocation(washProgram,"bgColor"),palette.bg),gl.uniform4fv(gl.getUniformLocation(washProgram,"inkColor"),e.color),gl.uniform4fv(gl.getUniformLocation(washProgram,"highlightColor"),"backgroundWash"===e.type?e.color:palette.highlight),gl.uniform2f(gl.getUniformLocation(washProgram,"resolution"),width,height),gl.uniform1f(gl.getUniformLocation(washProgram,"grainSeed"),GRAIN_SEED),gl.uniform1f(gl.getUniformLocation(washProgram,"baseGrainAmount"),settings.baseGrainAmount),gl.uniform1f(gl.getUniformLocation(washProgram,"baseMottleAmount"),settings.baseMottleAmount),gl.drawArrays(gl.TRIANGLE_STRIP,0,4))}function sampleVisibleCanvasHealth(){if(!gl||gl.isContextLost())return;const e=new Uint8Array(4);gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.readPixels(Math.floor(.5*width),Math.floor(.5*height),1,1,gl.RGBA,gl.UNSIGNED_BYTE,e);e[0]+e[1]+e[2]<8&&state.frame>20?blackFrameCount++:blackFrameCount=0,blackFrameCount>90&&queueHardRebuild("visible canvas stayed black"),state.activePlacementIndex!==lastProgressPlacementIndex&&(lastProgressPlacementIndex=state.activePlacementIndex,lastProgressFrame=state.frame);state.activePlacementIndex>0&&state.activePlacementIndex<state.placements.length&&state.frame-lastProgressFrame>240&&queueHardRebuild("render loop appears stuck")}function renderScreen(){useProgram(screenProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[frameBufferId].texture),gl.uniform1i(gl.getUniformLocation(screenProgram,"buffer"),0),gl.drawArrays(gl.TRIANGLE_STRIP,0,4)}function useProgram(e){gl.useProgram(e);const t=gl.getAttribLocation(e,"pos");gl.enableVertexAttribArray(t),gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer),gl.vertexAttribPointer(t,3,gl.FLOAT,!1,0,0)}function createProgram(e,t){const r=gl.createProgram(),n=createShader(`#version 300 es\n${e}`,gl.VERTEX_SHADER),o=createShader(`#version 300 es\nprecision highp float;\n${t}`,gl.FRAGMENT_SHADER);return gl.attachShader(r,n),gl.attachShader(r,o),gl.linkProgram(r),gl.getProgramParameter(r,gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(r)),r}function createShader(e,t){const r=gl.createShader(t);return gl.shaderSource(r,e),gl.compileShader(r),gl.getShaderParameter(r,gl.COMPILE_STATUS)||console.error(gl.getShaderInfoLog(r)),r}function createVertexBuffer(){const e=gl.createBuffer();return gl.bindBuffer(gl.ARRAY_BUFFER,e),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,1,0,-1,-1,0,1,1,0,1,-1,0]),gl.STATIC_DRAW),e}function createFrameBuffer(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createFramebuffer(),o=createTexture(e,t,r);gl.bindFramebuffer(gl.FRAMEBUFFER,n),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,o,0);const a=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return a!==gl.FRAMEBUFFER_COMPLETE&&console.warn("Framebuffer incomplete:",a,e,t),gl.bindFramebuffer(gl.FRAMEBUFFER,null),{buffer:n,texture:o}}function createTexture(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createTexture();return gl.bindTexture(gl.TEXTURE_2D,n),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),r&&r.width>0&&r.height>0?gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,r):gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,e,t,0,gl.RGBA,gl.UNSIGNED_BYTE,null),n}function updateReferenceTexture(){refTexture&&(!ref||ref.width<MIN_GL_SIZE||ref.height<MIN_GL_SIZE||(gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,gl.RGBA,gl.UNSIGNED_BYTE,ref)))}function rgb(e){return`rgb(${Math.floor(255*e[0])}, ${Math.floor(255*e[1])}, ${Math.floor(255*e[2])})`}function clamp255(e){return Math.max(0,Math.min(255,e))}function handleKey(e){"s"===e.key&&savePNG(),"m"===e.key&&(settings.showReference=!settings.showReference,ref.style.display=settings.showReference?"block":"none"),"c"===e.key&&setCustomRenderSize(),"p"===e.key.toLowerCase()&&setCustomRpcUrl()}function savePNG(){renderScreen();const e=document.createElement("a");e.download=`residual-notes-${HASH.slice(2,10)}-${width}x${height}.png`,e.href=view.toDataURL("image/png"),e.click()}function restart(){restartAfterResize()}init();
function parsePalette(e){const t=Object.fromEntries(e.split("|").map(e=>{const t=e.indexOf("=");return[e.slice(0,t),e.slice(t+1)]})),r=e=>e.trim().split(/\s+/).map(Number);return{bg:r(t.bg),grid:r(t.grid),highlight:r(t.highlight),inks:t.inks.split(";").map(e=>{const t=r(e);return{color:t.slice(0,4),weight:t[4]}})}}palette=parsePalette(palette);class Random{constructor(e){this.useA=!1,this.prngA=this.sfc32(e.slice(2,34)),this.prngB=this.sfc32(e.slice(34,66));for(let e=0;e<1e5;e++)this.prngA(),this.prngB()}sfc32(e){let t=parseInt(e.slice(0,8),16),r=parseInt(e.slice(8,16),16),n=parseInt(e.slice(16,24),16),o=parseInt(e.slice(24,32),16);return function(){t|=0,r|=0,n|=0,o|=0;let e=(t+r|0)+o|0;return o=o+1|0,t=r^r>>>9,r=n+(n<<3)|0,n=n<<21|n>>>11,n=n+e|0,(e>>>0)/4294967296}}g(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}range(e,t){return e+this.g()*(t-e)}int(e,t){return Math.floor(this.range(e,t+1))}pick(e){return e[Math.floor(this.g()*e.length)]}chance(e){return this.g()<e}}let R=new Random(HASH);const wordStream={fetching:!1,done:WORDS.length>=WORD_COUNT,failed:!1,nextOffset:WORDS.length};let customRpcUrl="";function getRpcUrls(){return customRpcUrl?[customRpcUrl,...RPC_URLS.filter(e=>e!==customRpcUrl)]:RPC_URLS}function setCustomRpcUrl(){const e=prompt("Custom RPC URL:",customRpcUrl||"https://...");if(!e)return;const t=e.trim();/^https?:\/\//i.test(t)?(customRpcUrl=t,wordStream.fetching=!1,wordStream.failed=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.nextOffset=WORDS.length,maybeFetchMoreWords(),requestRun(),console.log("Custom RPC set:",customRpcUrl)):alert("Please enter a valid http(s) RPC URL.")}const STRUCT_W=4e3,STRUCT_H=5e3,STRUCT_ASPECT=.8;let customRenderWidth=null,customRenderHeight=null,lastGoodWidth=1200,lastGoodHeight=1500,restartQueued=!1,blackFrameCount=0,lastProgressFrame=0;const MAX_BLACK_FRAMES=90,MAX_STUCK_FRAMES=240;let renderLoopRunning=!1;function requestRun(){renderLoopRunning||(renderLoopRunning=!0,requestAnimationFrame(run))}const GRAIN_SEED=parseInt(HASH.slice(2,10),16)/4294967295,settings={aspect:.8,washStrength:R.range(.1,.8),smudgeChance:R.range(.1,.24),textureAmount:R.range(.025,.08),pauseFrames:0,revealFrames:2,revealSpeed:1,backgroundWashChance:.01,backgroundWashMinWords:5,backgroundWashMaxWords:50,backgroundWashColor:palette.highlight,baseGrainAmount:.02,baseMottleAmount:8e-4,gridDefinitions:[...Array(5)].map((e,t)=>(co=t>0?Math.floor(R.range(70,130)):Math.floor(R.range(45,65)),ro=Math.floor(1.25*co),{name:t.toString(),cols:co,rows:ro,rotation:R.g()/25-.02,scale:1,offsetX:0,offsetY:0,letterScale:.8})),showReference:!1};function shuffled(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=R.int(0,e);[t[e],t[r]]=[t[r],t[e]]}return t}const view=document.getElementById("view"),ref=document.getElementById("ref"),refCtx=ref.getContext("2d",{alpha:!1});let gl,vertexBuffer,washProgram,screenProgram,refTexture,width=0,height=0,dpr=1,frameBuffers=[],frameBufferId=0,resizeQueued=!1,isResizing=!1;const MIN_GL_SIZE=2,state={frame:0,grids:[],placements:[],activePlacementIndex:0},vertexShader="\n in vec3 pos;\n out vec2 vUv;\n\n void main() {\n gl_Position = vec4(pos, 1.0);\n vUv = (pos.xy + 1.0) * 0.5;\n }\n",washShader="\n uniform sampler2D previousFrame;\n uniform sampler2D referenceMask;\n uniform float frame;\n uniform float dirx;\n uniform float diry;\n uniform float washStrength;\n uniform float smudgeChance;\n uniform float textureAmount;\n uniform vec4 bgColor;\n uniform vec4 inkColor;\n uniform vec4 highlightColor;\n\n uniform vec2 resolution;\n uniform float grainSeed;\n uniform float baseGrainAmount;\n uniform float baseMottleAmount;\n\n in vec2 vUv;\n out vec4 col;\n\n vec3 random3(vec3 c) {\n float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0)));\n vec3 r;\n r.z = fract(512.0 * j);\n j *= 0.125;\n r.x = fract(512.0 * j);\n j *= 0.125;\n r.y = fract(512.0 * j);\n return r - 0.5;\n }\n\n const float F3 = 0.3333333;\n const float G3 = 0.1666667;\n\n float simplex3(vec3 p) {\n vec3 s = floor(p + dot(p, vec3(F3)));\n vec3 x = p - s + dot(s, vec3(G3));\n vec3 e = step(vec3(0.0), x - x.yzx);\n vec3 i1 = e * (1.0 - e.zxy);\n vec3 i2 = 1.0 - e.zxy * (1.0 - e);\n vec3 x1 = x - i1 + G3;\n vec3 x2 = x - i2 + 2.0 * G3;\n vec3 x3 = x - 1.0 + 3.0 * G3;\n vec4 w, d;\n w.x = dot(x, x);\n w.y = dot(x1, x1);\n w.z = dot(x2, x2);\n w.w = dot(x3, x3);\n w = max(0.6 - w, 0.0);\n d.x = dot(random3(s), x);\n d.y = dot(random3(s + i1), x1);\n d.z = dot(random3(s + i2), x2);\n d.w = dot(random3(s + 1.0), x3);\n w *= w;\n w *= w;\n d *= w;\n return dot(d, vec4(52.0));\n }\n\n float hash12(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33 + grainSeed * 17.17);\n return fract((p3.x + p3.y) * p3.z);\n }\n\n void main() {\n vec4 ref = texture(referenceMask, vUv);\n vec4 prev = texture(previousFrame, vUv);\n\n float grainA = simplex3(vec3(vUv.x * 380.0 + frame * 0.011, vUv.y * 380.0 - frame * 0.013, 11.0));\n float grainB = simplex3(vec3(vUv.x * 7500.0, vUv.y * 7500.0, frame * 0.018));\n\n vec4 texturedBg = bgColor + vec4(vec3(grainB * textureAmount), 0.0);\n\n col = prev;\n\n // Only bake paper grain into the very first rendered frame.\n // After that it becomes part of previousFrame.\n if (frame < 0.5) {\n vec2 px = gl_FragCoord.xy;\n\n float fine = hash12(px) - 0.5;\n\n float fine2 = hash12(px * 0.5 + 91.7) - 0.5;\n float mottle = simplex3(vec3(\n vUv.x * 95.0 + grainSeed * 13.0,\n vUv.y * 95.0 - grainSeed * 9.0,\n 4.0\n ));\n\n float paper =\n fine * baseGrainAmount +\n fine2 * baseGrainAmount * 0.45 +\n mottle * baseMottleAmount;\n\n col.rgb += vec3(paper);\n }\n\n if (ref.g > 0.001) {\n float rnd = simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 50.0,\n grainSeed * 100000.0\n ));\n\n vec4 textureBG = mix(\n bgColor,\n vec4(\n bgColor.rgb - vec3(\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 500.0,\n (vUv.y - frame / 1000.0) * 500.0,\n grainSeed * 100000.0\n )) / 80.\n ),\n 1.0\n ),\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 5.0,\n (vUv.y - frame / 1000.0) / 4.0,\n grainSeed * 100000.0\n ))\n );\n\n // This is the core wash behavior from the second project:\n // replace/scratch toward textured background, instead of dragging black blocks.\n col = mix(\n textureBG,\n // vec4(1.,0.,0.,1.),\n // bgColor,\n prev,\n simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 5000.0,\n grainSeed * 100000.0\n ))*4.\n );\n\n // Directional smudge, but only sometimes.\n if (rnd < smudgeChance) {\n col = mix(\n texture(previousFrame, vec2(\n vUv.x + 0.003 * dirx,\n vUv.y + 0.003 * diry\n )),\n textureBG,\n rnd\n );\n }\n\n // Let the green mask control strength.\n prev = bgColor;\n col = mix(prev, col, ref.g * washStrength);\n }\n\n if (ref.b > 0.01) {\n float paintMask = ref.b;\n\n float roughA = simplex3(vec3(\n vUv.x * 900.0,\n vUv.y * 900.0,\n frame * 0.012\n ));\n\n float roughB = simplex3(vec3(\n vUv.x * 2200.0,\n vUv.y * 2200.0,\n 19.0\n ));\n\n float brokenMask = paintMask;\n brokenMask *= smoothstep(-0.45, 0.55, roughA);\n brokenMask *= 0.72 + roughB * 0.28;\n\n vec4 noisyPaint = highlightColor;\n noisyPaint.rgb += vec3(roughB * 0.10 + roughA * 0.035);\n\n vec2 dragUv = vUv + vec2(\n roughA * 0.0025,\n roughB * 0.0015\n );\n\n vec4 draggedSurface = texture(previousFrame, dragUv);\n\n vec4 paintOnSurface = mix(draggedSurface, noisyPaint, brokenMask);\n\n col = mix(col, paintOnSurface, brokenMask);\n }\n\n // Red = foreground ink. The incoming reference contains only the current word.\n if (ref.r > 0.01) {\n float smearNoise = simplex3(vec3(\n vUv.x * 900.0 + frame * 0.03,\n vUv.y * 220.0 - frame * 0.02,\n grainSeed * 9999.0\n ));\n\n float smearMask = ref.r * smoothstep(-0.35, 0.55, smearNoise);\n\n vec2 smearUv = vUv + vec2(\n -0.006 * dirx,\n 0.002 * diry\n );\n\n vec4 draggedInk = texture(previousFrame, smearUv);\n\n col = mix(col, draggedInk, smearMask * 0.65);\n\n vec4 brokenInk = inkColor;\n brokenInk.rgb += vec3(smearNoise * 0.16);\n\n col = mix(col, brokenInk, ref.r);\n }\n }\n",screenShader="\n uniform sampler2D buffer;\n in vec2 vUv;\n out vec4 col;\n\n void main() {\n col = texture(buffer, vec2(vUv.x, 1.0 - vUv.y));\n }\n";async function maybeFetchMoreWords(){if(!wordStream.fetching&&!wordStream.done&&!wordStream.failed)if(RPC_URLS.length){wordStream.fetching=!0;try{const e=await fetchNotesSlice(wordStream.nextOffset,FETCH_BATCH_SIZE);if(!e.length)return void(wordStream.done=!0);for(const t of e)WORDS.push(t),addWordPlacement(t);wordStream.nextOffset+=e.length,wordStream.nextOffset>=WORD_COUNT&&(wordStream.done=!0)}catch(e){console.warn("RPC word fetch failed:",e),wordStream.failed=!0}finally{wordStream.fetching=!1}}else wordStream.failed=!0}async function fetchNotesSlice(e,t){let r=null;for(const n of getRpcUrls())try{return await fetchNotesSliceFromRpc(n,TOKEN_ID,e,t)}catch(e){r=e,console.warn("RPC failed, trying next:",n,e)}throw r||new Error("No RPC worked")}const NOTES_SLICE_SELECTOR="0x91cf7995";async function fetchNotesSliceFromRpc(e,t,r,n){const o=NOTES_SLICE_SELECTOR+encodeUint256(t)+encodeUint256(r)+encodeUint256(n),a=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:CONTRACT_ADDRESS,data:o},"latest"]})}),s=await a.json();if(s.error)throw new Error(s.error.message||"RPC error");return s.result&&"0x"!==s.result?decodeStringArray(s.result):[]}function encodeUint256(e){return BigInt(e).toString(16).padStart(64,"0")}function decodeStringArray(e){e=e.replace(/^0x/,"");const t=t=>{const r=2*t;return BigInt("0x"+e.slice(r,r+64))},r=(t,r)=>{const n=2*t;return e.slice(n,n+2*r)},n=Number(t(0)),o=Number(t(n)),a=[];for(let e=0;e<o;e++){const o=n+32+Number(t(n+32+32*e)),s=r(o+32,Number(t(o)));a.push(hexToUtf8(s))}return a}function hexToUtf8(e){const t=[];for(let r=0;r<e.length;r+=2)t.push(parseInt(e.slice(r,r+2),16));return(new TextDecoder).decode(new Uint8Array(t)).replace(/\0/g,"").trim()}function varyColor(e,t=.01){return[Math.max(0,Math.min(1,e[0]+R.range(-t,t))),Math.max(0,Math.min(1,e[1]+R.range(-t,t))),Math.max(0,Math.min(1,e[2]+R.range(-t,t))),e[3]??1]}function init(){if(gl=view.getContext("webgl2",{alpha:!1,antialias:!1,preserveDrawingBuffer:!0}),!gl)return void(document.body.innerHTML="WebGL2 is required.");vertexBuffer=createVertexBuffer(),washProgram=createProgram(vertexShader,washShader),screenProgram=createProgram(vertexShader,screenShader),createGrids(),createWordPlacements(),window.addEventListener("resize",requestResize),window.addEventListener("keydown",handleKey),view.addEventListener("webglcontextlost",e=>{e.preventDefault(),console.warn("WebGL context lost")},!1),view.addEventListener("webglcontextrestored",()=>{console.warn("WebGL context restored, rebuilding"),hardRebuild()},!1);new ResizeObserver(()=>requestResize()).observe(document.body),resize(),requestRun()}function resetRandom(){R=new Random(HASH)}function requestResize(){resizeQueued||(resizeQueued=!0,requestAnimationFrame(()=>{resizeQueued=!1,resize()}))}function resetWordsToDefault(){WORDS.length=0,WORDS.push(...INITIAL_WORDS),wordStream.fetching=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.failed=!1,wordStream.nextOffset=WORDS.length}let lastRenderedPixelCount=0;function restartAfterResize(){resetWordsToDefault(),resetRandom(),state.frame=0,state.activePlacementIndex=0,createGrids(),createWordPlacements(),drawBaseReference(),refTexture&&gl.deleteTexture(refTexture),refTexture=createTexture(width,height,ref),resetFrameBuffers(),window.__RESIDUAL_NOTES_RENDERED__=!1,blackFrameCount=0,lastProgressFrame=0,lastRenderedPixelCount=width*height,requestRun()}let renderW=null,renderH=null;function resize(){const e=1*Math.floor(window.innerWidth||document.documentElement.clientWidth||0),t=1*Math.floor(window.innerHeight||document.documentElement.clientHeight||0);if(e<20||t<20)return void requestAnimationFrame(requestResize);let r,n;e/t>settings.aspect?(n=t,r=t*settings.aspect):(r=e,n=e/settings.aspect),dpr=Math.max(1,window.devicePixelRatio||1);const o=renderW??Math.max(20,Math.round(r*dpr)),a=renderH??Math.max(20,Math.round(n*dpr));if(!(o===width&&a===height)){if(width>0&&height>0&&!(o*a>width*height))return view.style.width=`${r}px`,void(view.style.height=`${n}px`);isResizing=!0,width=o,height=a,view.width=width,view.height=height,view.style.display="block",ref.width=width,ref.height=height,ref.style.display=settings.showReference?"block":"none",restartAfterResize(),isResizing=!1}}function setCustomRenderSize(){const e=prompt("Render width in pixels:",width);if(!e)return;const t=Math.max(100,Math.floor(Number(e)));Number.isFinite(t)&&(renderW=t,renderH=Math.round(renderW/settings.aspect),resize())}function useStructureSpace(){refCtx.setTransform(width/4e3,0,0,height/5e3,0,0)}function resetFrameBuffers(){for(const e of frameBuffers)gl.deleteFramebuffer(e.buffer),gl.deleteTexture(e.texture);frameBuffers=[createFrameBuffer(width,height,ref),createFrameBuffer(width,height,null)],frameBufferId=0}function queueHardRebuild(e="unknown"){restartQueued||(restartQueued=!0,console.warn("Queueing hard rebuild:",e),requestAnimationFrame(()=>{restartQueued=!1,hardRebuild(e)}))}function hardRebuild(e="unknown"){if(console.warn("Hard rebuild:",e),gl&&!gl.isContextLost())try{if(resize(),width<MIN_GL_SIZE||height<MIN_GL_SIZE)return;restartAfterResize(),blackFrameCount=0,lastProgressFrame=state.frame,window.__RESIDUAL_NOTES_RENDERED__=!1}catch(e){console.warn("Hard rebuild failed:",e)}}function assertFramebufferHealthy(e=""){const t=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return t===gl.FRAMEBUFFER_COMPLETE||(queueHardRebuild(`bad framebuffer ${e}: ${t}`),!1)}function drawBaseReference(){useStructureSpace(),refCtx.fillStyle=rgb(palette.bg),refCtx.fillRect(0,0,4e3,5e3),refCtx.fillStyle=rgb(palette.grid);const e=Math.floor(settings.gridDefinitions[0].cols),t=Math.floor(settings.gridDefinitions[0].rows);for(let t=2;t<e-2;t++)refCtx.fillRect(4e3/e*t,0,1,5e3);for(let e=2;e<t-2;e++)refCtx.fillRect(0,5e3/t*e,4e3,1)}function createGrids(){state.grids=settings.gridDefinitions.map((e,t)=>({id:t,name:e.name||`grid ${t}`,cols:Math.floor(e.cols),rows:Math.floor(e.rows),rotation:e.rotation||0,scale:e.scale||1,offsetX:e.offsetX||0,offsetY:e.offsetY||0,letterScale:e.letterScale||.8,occupied:null}))}function createWordPlacements(){state.placements=[];let e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords);for(const t of WORDS)addWordPlacement(t),e--,e<=0&&R.chance(settings.backgroundWashChance)&&(addBackgroundWashPlacement(),e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords))}function addWordPlacement(e){if(!e||!String(e).trim())return!1;const t=String(e).toUpperCase(),r=tryCreateWordPlacement(t)||tryCreateWordPlacement(t)||tryCreateWordPlacement(t);return!!r&&(state.placements.push(r),!0)}function addBackgroundWashPlacement(){state.placements.push({type:"backgroundWash",color:R.chance(.15)?palette.highlight:darkenedBgColor(.1),dirx:R.range(-1,1),diry:R.range(-1,1),washStrength:R.range(.03,.16),smudgeChance:R.range(.1,.32),x:4e3*R.range(-.08,.85),y:5e3*R.range(-.08,.85),w:4e3*R.range(.18,.55),h:5e3*R.range(.12,.38),seed:9999*R.g(),alpha:R.range(.02,.175),dripChance:R.range(.08,.28),force:1})}function tryCreateWordPlacement(e){for(let t=0;t<80;t++){const t=R.pick(state.grids),r=R.pick(["right","down","right","down","right","down","right","down","right","down","right","down","right","down","diagDown","diagUp"]),n=randomStartForTextBlock(t,e,r),o=textBlockCells(n.x,n.y,e,r,t);if(cellsFit(t,o))return{word:e,grid:t,direction:r,cells:o,color:varyColor(pickInkForWord(),.05),dirx:R.range(-2,2),diry:R.range(-2,2),washSeed:R.g(),washStrength:R.range(.05,.35),smudgeChance:R.range(.1,.32),washPad:R.range(.04,.2),washGaps:0,washStampCount:R.int(1,6),circle:R.chance(.065),underline:R.chance(.05),strikethrough:R.chance(.05),lettergrid:R.chance(.01),square:R.chance(.01),cross:R.chance(.01),plus:R.chance(.005),force:1}}return null}function randomStartForTextBlock(e,t,r){const n=Math.min(String(t).length,"right"===r?e.cols:e.rows),o=R.int(0,e.cols-1),a=R.int(0,e.rows-1);return"right"===r?{x:Math.max(0,Math.min(e.cols-1,o-Math.floor(n*R.range(.25,.65)))),y:Math.max(0,Math.min(e.rows-1,a-R.int(0,2)))}:"down"===r?{x:Math.max(0,Math.min(e.cols-1,o-R.int(0,2))),y:Math.max(0,Math.min(e.rows-1,a-Math.floor(n*R.range(.25,.65))))}:{x:o,y:a}}function textBlockCells(e,t,r,n,o){if("diagDown"===n||"diagUp"===n)return diagonalTextCells(e,t,r,n);const a=String(r).split(/\s+/),s=[],i="right"===n?Math.max(1,o.cols-e):Math.max(1,o.rows-t);let l=0,f=0;for(const r of a){if(f+(f>0?1:0)+r.length>i&&(l++,f=0),"right"===n&&t+l>=o.rows)break;if("down"===n&&e+l>=o.cols)break;f>0&&(addTextCharCell(s,e,t,n,f,l," "),f++);for(let a=0;a<r.length&&(f>=i&&(l++,f=0),!("right"===n&&t+l>=o.rows))&&!("down"===n&&e+l>=o.cols);a++)addTextCharCell(s,e,t,n,f,l,r[a]),f++}return s.map((e,t)=>({...e,index:t}))}function addTextCharCell(e,t,r,n,o,a,s){"right"===n&&e.push({x:t+o,y:r+a,char:s}),"down"===n&&e.push({x:t+a,y:r+o,char:s})}function diagonalTextCells(e,t,r,n){const o=[];for(let a=0;a<r.length;a++){let s=e+a,i="diagDown"===n?t+a:t-a;o.push({x:s,y:i,char:r[a],index:a})}return o}function cellsFit(e,t){return t.every(t=>t.x>=0&&t.y>=0&&t.x<e.cols&&t.y<e.rows)}function pickInkForWord(){return weightedPick(palette.inks).color}function weightedPick(e){const t=e.reduce((e,t)=>e+t.weight,0);let r=R.g()*t;for(const t of e)if(r-=t.weight,r<=0)return t;return e[e.length-1]}function clearReferenceToBlack(){refCtx.setTransform(1,0,0,1,0,0),refCtx.fillStyle="black",refCtx.fillRect(0,0,width,height)}function drawPlacementReference(e,t){if("backgroundWash"===e.type)return void drawBackgroundWashReference(e,t);clearReferenceToBlack(),refCtx.save(),useStructureSpace(),applyGridTransform(e.grid);const r=Math.ceil(e.cells.length*t),n=e.cells.slice(0,r);drawWashForCells(e.grid,n,e.washPad,e),drawWordCells(e.grid,n,e),e.underline&&t>.72&&drawUnderline(e),e.circle&&t>.84&&drawCircleAroundWord(e),e.strikethrough&&t>.65&&drawStrikethrough(e),e.lettergrid&&t>.7&&drawGridAroundLetters(e,n),e.square&&t>.82&&drawSquareAroundWord(e),e.cross&&t>.78&&drawCrossThroughWord(e),e.plus&&t>.78&&drawPlusThroughWord(e),refCtx.restore()}function applyGridTransform(e){refCtx.translate(2e3+4e3*e.offsetX,2500+5e3*e.offsetY),refCtx.rotate(e.rotation),refCtx.scale(e.scale,e.scale),refCtx.translate(-2e3,-2500)}function drawWashForCells(e,t,r,n){const o=4e3/e.cols,a=5e3/e.rows;refCtx.save(),refCtx.fillStyle="rgb(0,255,0)";for(const e of t){const t=n.washStampCount??1;for(let s=0;s<t;s++){const t=1e3*n.washSeed+97*s;if(stable01(e.x,e.y,e.index+t)<n.washGaps)continue;const i=stable01(e.x,e.y,101+t),l=stable01(e.x,e.y,202+t),f=stableNoise(e.x,e.y,303+t)*o*.22,c=stableNoise(e.x,e.y,404+t)*a*.22,d=o*(.04+.14*i),g=a*(.04+.14*l),u=o*r,h=a*r;refCtx.fillRect(e.x*o+d-u+f,e.y*a+g-h+c,o-2*d+2*u,a-2*g+2*h)}}refCtx.restore()}function stable01(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)}function drawWordCells(e,t,r){const n=4e3/e.cols,o=5e3/e.rows;refCtx.fillStyle="rgb(255,0,0)",refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(1,.09*Math.min(n,o)),refCtx.lineCap="round",refCtx.lineJoin="round";for(const e of t)drawLetterInCell(e.char,e.x*n,e.y*o,n,o,r,e.index)}function drawLetterInCell(e,t,r,n,o,a,s){if(" "===e)return;const i=Math.min(n,o)*RangedLetterScale(a,s),l=stableNoise(t,r,s+1)*n*.1,f=stableNoise(t,r,s+2)*o*.1,c=.05*stableNoise(t,r,s+3);refCtx.save(),refCtx.translate(t+.5*n+l,r+.54*o+f),refCtx.rotate(c),refCtx.font=`${i}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`,refCtx.textAlign="center",refCtx.textBaseline="middle",refCtx.fillText(e,0,0),refCtx.restore()}function RangedLetterScale(e,t){return e.grid.letterScale}function darkenedBgColor(e=.1){return[Math.max(0,palette.bg[0]-e),Math.max(0,palette.bg[1]-e),Math.max(0,palette.bg[2]-e),palette.bg[3]??1]}function drawUnderline(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round";const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.84*r,l=a.x*t+.85*t,f=a.y*r+.84*r,c=l-s,d=f-i,g=Math.max(1e-4,Math.sqrt(c*c+d*d)),u=-d/g,h=c/g,x=.28*Math.min(t,r);refCtx.beginPath(),refCtx.moveTo(s+u*x,i+h*x),refCtx.lineTo(l+u*x+stableNoise(l,f,9)*t*.25,f+h*x+stableNoise(s,i,10)*r*.25),refCtx.stroke(),refCtx.restore()}function drawStrikethrough(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.5*r,l=a.x*t+.85*t,f=a.y*r+.5*r;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round",refCtx.beginPath(),refCtx.moveTo(s+stableNoise(s,i,21)*t*.18,i+stableNoise(s,i,22)*r*.18),refCtx.lineTo(l+stableNoise(l,f,23)*t*.18,f+stableNoise(l,f,24)*r*.18),refCtx.stroke(),refCtx.restore()}function drawCircleAroundWord(e){const t=wordBox(e),r=.5*(t.x1+t.x2),n=.5*(t.y1+t.y2),o=.62*Math.abs(t.x2-t.x1)+4e3/e.grid.cols,a=.62*Math.abs(t.y2-t.y1)+5e3/e.grid.rows,s=.35*stableNoise(r,n,4),i=refCtx.lineWidth,l=refCtx.strokeStyle;refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineCap="round";const f=stableNoise(r,n,999),c=1+Math.floor(5*f);for(let e=0;e<c;e++){const t=1e3+137*e,l=stableNoise(r,n,t+1),f=stableNoise(r,n,t+2),c=stableNoise(r,n,t+3),d=stableNoise(r,n,t+4),g=stableNoise(r,n,t+5),u=.94+.18*g,h=.18+.95*stableNoise(r,n,t+6),x=.35+.65*d,m=4e3*l*.006,C=5e3*f*.006,w=o*u*(1+.04*c),b=a*u*(1-.04*c),R=g*Math.PI*2,p=stableNoise(r,n,t+7)<.45?.55+.35*stableNoise(r,n,t+8):1,v=R+2*Math.PI*p;refCtx.save(),refCtx.globalAlpha*=x,refCtx.lineWidth=i*h,refCtx.beginPath(),refCtx.ellipse(r+m,n+C,w,b,s+.22*d,R,v),refCtx.stroke(),refCtx.restore()}refCtx.lineWidth=i,refCtx.strokeStyle=l}function drawGridAroundLetters(e,t){const r=4e3/e.grid.cols,n=5e3/e.grid.rows,o=t.filter(e=>" "!==e.char);if(o.length){refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.5,.045*Math.min(r,n)),refCtx.lineCap="square",refCtx.lineJoin="miter";for(const t of o){const o=t.x*r,a=t.y*n,s=.025*Math.min(r,n),i=t.index+1e3*e.washSeed;refCtx.beginPath(),refCtx.rect(o+stableNoise(t.x,t.y,i+1)*s,a+stableNoise(t.x,t.y,i+2)*s,r+stableNoise(t.x,t.y,i+3)*s,n+stableNoise(t.x,t.y,i+4)*s),refCtx.stroke()}refCtx.restore()}}function drawSquareAroundWord(e){const t=expandedWordBox(e,.22),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round";const n=.12*r;refCtx.beginPath(),refCtx.moveTo(t.x1+stableNoise(t.x1,t.y1,301)*n,t.y1+stableNoise(t.x1,t.y1,302)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y1,303)*n,t.y1+stableNoise(t.x2,t.y1,304)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y2,305)*n,t.y2+stableNoise(t.x2,t.y2,306)*n),refCtx.lineTo(t.x1+stableNoise(t.x1,t.y2,307)*,&n,t.y2+stableNoise(t.x1,t.y2,308)*n),refCtx.closePath(),refCtx.stroke(),refCtx.restore()}function drawCrossThroughWord(e){const t=expandedWordBox(e,.12),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,t.y1,t.x2,t.y2,r,401),drawNoisyLine(t.x2,t.y1,t.x1,t.y2,r,411),refCtx.restore()}function drawPlusThroughWord(e){const t=expandedWordBox(e,.1),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows),n=.5*(t.x1+t.x2),o=.5*(t.y1+t.y2);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,o,t.x2,o,r,501),drawNoisyLine(n,t.y1,n,t.y2,r,511),refCtx.restore()}function drawBackgroundWashReference(e,t){clearReferenceToBlack(),refCtx.save(),useStructureSpace(),refCtx.globalAlpha=e.alpha*t,refCtx.fillStyle="rgb(0,0,255)",refCtx.strokeStyle="rgb(0,0,255)",refCtx.lineCap="round",refCtx.lineJoin="round";const r=e.y+.42*e.h;for(let n=0;n<44;n++){const o=n/Math.max(1,43),a=e.x+e.w*o+stableNoise(e.seed,n,11)*e.w*.025,s=Math.sin(o*Math.PI),i=r-e.h*(.25+.75*s+.45*stable01(e.seed,n,12)),l=r+e.h*(.15+.55*s+.75*stable01(e.seed,n,13)),f=e.w*(.018+.045*stable01(e.seed,n,14));drawVerticalPaintColumn(a,i,l,f,e.seed+91*n,t),stable01(e.seed,n,77)<e.dripChance&&drawPaintDrip(a+stableNoise(e.seed,n,21)*f,l-.05*e.h,e.h*(.35+1.4*stable01(e.seed,n,22)),e.seed+200*n)}for(let n=0;n<9;n++){const o=r+stableNoise(e.seed,n,301)*e.h*.45;refCtx.globalAlpha=e.alpha*t*.22,refCtx.lineWidth=e.h*(.025+.045*stable01(e.seed,n,302)),drawNoisyHorizontalStroke(e.x+stableNoise(e.seed,n,303)*e.w*.05,o,e.x+e.w+stableNoise(e.seed,n,304)*e.w*.05,o+stableNoise(e.seed,n,305)*e.h*.18,e.seed+400*n)}refCtx.restore()}function drawVerticalPaintColumn(e,t,r,n,o,a){refCtx.beginPath();for(let a=0;a<=12;a++){const s=t+(r-t)*(a/12),i=e-.5*n+stableNoise(o,a,31)*n*.75;0===a?refCtx.moveTo(i,s):refCtx.lineTo(i,s)}for(let a=12;a>=0;a--){const s=t+(r-t)*(a/12),i=e+.5*n+stableNoise(o,a,41)*n*.75;refCtx.lineTo(i,s)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalAlpha*=.35;const s=2+Math.floor(5*stable01(o,1,55));for(let a=0;a<s;a++){const s=e+stableNoise(o,a,61)*n*.45;refCtx.lineWidth=n*(.08+.18*stable01(o,a,62)),refCtx.beginPath(),refCtx.moveTo(s,t+stableNoise(o,a,63)*n),refCtx.lineTo(s+stableNoise(o,a,64)*n*.45,r-stable01(o,a,65)*n),refCtx.stroke()}refCtx.restore(),refCtx.save(),refCtx.globalCompositeOperation="destination-out";for(let s=0;s<22;s++){const i=stable01(o,s,71)<.5?-1:1,l=t+stable01(o,s,72)*(r-t),f=e+i*n*(.3+.45*stable01(o,s,73));refCtx.globalAlpha=a*(.04+.14*stable01(o,s,74)),refCtx.beginPath(),refCtx.ellipse(f,l,n*(.15+.55*stable01(o,s,75)),n*(.25+1.25*stable01(o,s,76)),stableNoise(o,s,77),0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawNoisyHorizontalStroke(e,t,r,n,o){refCtx.beginPath();for(let a=0;a<=18;a++){const s=a/18,i=e+(r-e)*s,l=t+(n-t)*s+5e3*stableNoise(o,a,91)*.006;0===a?refCtx.moveTo(i,l):refCtx.lineTo(i,l)}refCtx.stroke()}function drawPainterlyBlob(e,t,r,n,o,a){const s=[];for(let a=0;a<28;a++){const i=a/28*Math.PI*2,l=1+.12*stableNoise(o,a,31)+.06*stableNoise(o,a,32)+.06*Math.sin(2*i+o)+.04*Math.sin(5*i+.37*o);s.push({x:e+Math.cos(i)*r*.5*l,y:t+Math.sin(i)*n*.5*l})}refCtx.beginPath();for(let e=0;e<s.length;e++){const t=s[(e-1+s.length)%s.length],r=s[e],n=s[(e+1)%s.length],o={x:.5*(t.x+r.x),y:.5*(t.y+r.y)},a={x:.5*(r.x+n.x),y:.5*(r.y+n.y)};0===e&&refCtx.moveTo(o.x,o.y),refCtx.quadraticCurveTo(r.x,r.y,a.x,a.y)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalCompositeOperation="destination-out",refCtx.globalAlpha=.1*a;for(let s=0;s<160;s++){const i=stable01(o,s,41)*Math.PI*2,l=Math.sqrt(stable01(o,s,42)),f=Math.cos(i)*l,c=Math.sin(i)*l,d=e+f*r*.48,g=t+c*n*.48,u=Math.min(1,Math.sqrt(f*f+c*c)),h=Math.pow(u,2.2),x=r*(.004+.09*h*stable01(o,s,43)),m=n*(.003+.05*h*stable01(o,s,44));refCtx.globalAlpha=(.01+.18*h)*a,refCtx.beginPath(),refCtx.ellipse(d,g,x,m,stableNoise(o,s,45)*Math.PI,0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawPaintDrip(e,t,r,n){const o=.045*r;refCtx.beginPath(),refCtx.moveTo(e,t);for(let a=1;a<=5;a++){const s=a/5;refCtx.lineTo(e+stableNoise(n,a,51)*o,t+r*s)}refCtx.lineWidth=Math.max(1,4.8),refCtx.lineCap="round",refCtx.strokeStyle="rgb(0,0,255)",refCtx.stroke()}function drawNoisyLine(e,t,r,n,o,a){const s=.16*o;refCtx.beginPath(),refCtx.moveTo(e+stableNoise(e,t,a+1)*s,t+stableNoise(e,t,a+2)*s),refCtx.lineTo(r+stableNoise(r,n,a+3)*s,n+stableNoise(r,n,a+4)*s),refCtx.stroke()}function expandedWordBox(e,t=.15){const r=wordBox(e),n=4e3/e.grid.cols,o=5e3/e.grid.rows;return{x1:r.x1-n*t,y1:r.y1-o*t,x2:r.x2+n*t,y2:r.y2+o*t}}function wordBox(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.map(e=>e.x*t),o=e.cells.map(e=>e.y*r);return{x1:Math.min(...n),y1:Math.min(...o),x2:Math.max(...n)+t,y2:Math.max(...o)+r}}function stableNoise(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)-.5}function run(){if(renderLoopRunning=!1,isResizing||width<MIN_GL_SIZE||height<MIN_GL_SIZE||!refTexture||frameBuffers.length<2)return void requestRun();maybeFetchMoreWords();const e=state.placements[state.activePlacementIndex];if(!e)return renderScreen(),state.frame%30==0&&sampleVisibleCanvasHealth(),void(window.__RESIDUAL_NOTES_RENDERED__||(window.__RESIDUAL_NOTES_RENDERED__=!0,window.dispatchEvent(new Event("residual-notes:rendered"))));const t=settings.pauseFrames+settings.revealFrames,r=state.frame%t;let n=0,o=!1;if(r<settings.pauseFrames)clearReferenceToBlack();else{const t=r-settings.pauseFrames;n=Math.min(1,(t+1)*settings.revealSpeed/settings.revealFrames),o=t>=settings.revealFrames-1,drawPlacementReference(e,n)}updateReferenceTexture(),renderWash(e),renderScreen(),(o||r===t-1)&&state.activePlacementIndex++,state.frame++,requestRun()}function renderWash(e){const t=frameBufferId,r=1^frameBufferId;frameBufferId=r,useProgram(washProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,frameBuffers[r].buffer),assertFramebufferHealthy("renderWash")&&(gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[t].texture),gl.uniform1i(gl.getUniformLocation(washProgram,"previousFrame"),0),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.uniform1i(gl.getUniformLocation(washProgram,"referenceMask"),1),gl.uniform1f(gl.getUniformLocation(washProgram,"frame"),state.frame),gl.uniform1f(gl.getUniformLocation(washProgram,"dirx"),e.dirx),gl.uniform1f(gl.getUniformLocation(washProgram,"diry"),e.diry),gl.uniform1f(gl.getUniformLocation(washProgram,"washStrength"),e.washStrength??settings.washStrength),gl.uniform1f(gl.getUniformLocation(washProgram,"smudgeChance"),e.smudgeChance??settings.smudgeChance),gl.uniform1f(gl.getUniformLocation(washProgram,"textureAmount"),settings.textureAmount),gl.uniform4fv(gl.getUniformLocation(washProgram,"bgColor"),palette.bg),gl.uniform4fv(gl.getUniformLocation(washProgram,"inkColor"),e.color),gl.uniform4fv(gl.getUniformLocation(washProgram,"highlightColor"),"backgroundWash"===e.type?e.color:palette.highlight),gl.uniform2f(gl.getUniformLocation(washProgram,"resolution"),width,height),gl.uniform1f(gl.getUniformLocation(washProgram,"grainSeed"),GRAIN_SEED),gl.uniform1f(gl.getUniformLocation(washProgram,"baseGrainAmount"),settings.baseGrainAmount),gl.uniform1f(gl.getUniformLocation(washProgram,"baseMottleAmount"),settings.baseMottleAmount),gl.drawArrays(gl.TRIANGLE_STRIP,0,4))}function sampleVisibleCanvasHealth(){if(!gl||gl.isContextLost())return;const e=new Uint8Array(4);gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.readPixels(Math.floor(.5*width),Math.floor(.5*height),1,1,gl.RGBA,gl.UNSIGNED_BYTE,e);e[0]+e[1]+e[2]<8&&state.frame>20?blackFrameCount++:blackFrameCount=0,blackFrameCount>90&&queueHardRebuild("visible canvas stayed black"),state.activePlacementIndex>0&&state.frame-lastProgressFrame>240&&queueHardRebuild("render loop appears stuck"),lastProgressFrame=state.frame}function renderScreen(){useProgram(screenProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[frameBufferId].texture),gl.uniform1i(gl.getUniformLocation(screenProgram,"buffer"),0),gl.drawArrays(gl.TRIANGLE_STRIP,0,4)}function useProgram(e){gl.useProgram(e);const t=gl.getAttribLocation(e,"pos");gl.enableVertexAttribArray(t),gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer),gl.vertexAttribPointer(t,3,gl.FLOAT,!1,0,0)}function createProgram(e,t){const r=gl.createProgram(),n=createShader(`#version 300 es\n${e}`,gl.VERTEX_SHADER),o=createShader(`#version 300 es\nprecision highp float;\n${t}`,gl.FRAGMENT_SHADER);return gl.attachShader(r,n),gl.attachShader(r,o),gl.linkProgram(r),gl.getProgramParameter(r,gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(r)),r}function createShader(e,t){const r=gl.createShader(t);return gl.shaderSource(r,e),gl.compileShader(r),gl.getShaderParameter(r,gl.COMPILE_STATUS)||console.error(gl.getShaderInfoLog(r)),r}function createVertexBuffer(){const e=gl.createBuffer();return gl.bindBuffer(gl.ARRAY_BUFFER,e),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,1,0,-1,-1,0,1,1,0,1,-1,0]),gl.STATIC_DRAW),e}function createFrameBuffer(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createFramebuffer(),o=createTexture(e,t,r);gl.bindFramebuffer(gl.FRAMEBUFFER,n),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,o,0);const a=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return a!==gl.FRAMEBUFFER_COMPLETE&&console.warn("Framebuffer incomplete:",a,e,t),gl.bindFramebuffer(gl.FRAMEBUFFER,null),{buffer:n,texture:o}}function createTexture(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createTexture();return gl.bindTexture(gl.TEXTURE_2D,n),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),r&&r.width>0&&r.height>0?gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,r):gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,e,t,0,gl.RGBA,gl.UNSIGNED_BYTE,null),n}function updateReferenceTexture(){refTexture&&(!ref||ref.width<MIN_GL_SIZE||ref.height<MIN_GL_SIZE||(gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,gl.RGBA,gl.UNSIGNED_BYTE,ref)))}function rgb(e){return`rgb(${Math.floor(255*e[0])}, ${Math.floor(255*e[1])}, ${Math.floor(255*e[2])})`}function clamp255(e){return Math.max(0,Math.min(255,e))}function handleKey(e){"s"===e.key&&savePNG(),"m"===e.key&&(settings.showReference=!settings.showReference,ref.style.display=settings.showReference?"block":"none"),"c"===e.key&&setCustomRenderSize(),"p"===e.key.toLowerCase()&&setCustomRpcUrl()}function savePNG(){renderScreen();const e=document.createElement("a");e.download=`residual-notes-${HASH.slice(2,10)}-${width}x${height}.png`,e.href=view.toDataURL("image/png"),e.click()}function restart(){restartAfterResize()}init();
function parsePalette(e){const t=Object.fromEntries(e.split("|").map(e=>{const t=e.indexOf("=");return[e.slice(0,t),e.slice(t+1)]})),r=e=>e.trim().split(/\s+/).map(Number);return{bg:r(t.bg),grid:r(t.grid),highlight:r(t.highlight),inks:t.inks.split(";").map(e=>{const t=r(e);return{color:t.slice(0,4),weight:t[4]}})}}palette=parsePalette(palette);class Random{constructor(e){this.useA=!1,this.prngA=this.sfc32(e.slice(2,34)),this.prngB=this.sfc32(e.slice(34,66));for(let e=0;e<1e5;e++)this.prngA(),this.prngB()}sfc32(e){let t=parseInt(e.slice(0,8),16),r=parseInt(e.slice(8,16),16),n=parseInt(e.slice(16,24),16),o=parseInt(e.slice(24,32),16);return function(){t|=0,r|=0,n|=0,o|=0;let e=(t+r|0)+o|0;return o=o+1|0,t=r^r>>>9,r=n+(n<<3)|0,n=n<<21|n>>>11,n=n+e|0,(e>>>0)/4294967296}}g(){return this.useA=!this.useA,this.useA?this.prngA():this.prngB()}range(e,t){return e+this.g()*(t-e)}int(e,t){return Math.floor(this.range(e,t+1))}pick(e){return e[Math.floor(this.g()*e.length)]}chance(e){return this.g()<e}}let R=new Random(HASH);const wordStream={fetching:!1,done:WORDS.length>=WORD_COUNT,failed:!1,nextOffset:WORDS.length};let customRpcUrl="";function getRpcUrls(){return customRpcUrl?[customRpcUrl,...RPC_URLS.filter(e=>e!==customRpcUrl)]:RPC_URLS}function setCustomRpcUrl(){const e=prompt("Custom RPC URL:",customRpcUrl||"https://...");if(!e)return;const t=e.trim();/^https?:\/\//i.test(t)?(customRpcUrl=t,wordStream.fetching=!1,wordStream.failed=!1,wordStream.done=WORDS.length>=WORD_COUNT,wordStream.nextOffset=WORDS.length,maybeFetchMoreWords(),requestRun(),console.log("Custom RPC set:",customRpcUrl)):alert("Please enter a valid http(s) RPC URL.")}const STRUCT_W=4e3,STRUCT_H=5e3,STRUCT_ASPECT=.8;let customRenderWidth=null,customRenderHeight=null,lastGoodWidth=1200,lastGoodHeight=1500,restartQueued=!1,blackFrameCount=0,lastProgressFrame=0;const MAX_BLACK_FRAMES=90,MAX_STUCK_FRAMES=240;let renderLoopRunning=!1;function requestRun(){renderLoopRunning||(renderLoopRunning=!0,requestAnimationFrame(run))}const GRAIN_SEED=parseInt(HASH.slice(2,10),16)/4294967295,settings={aspect:.8,washStrength:R.range(.1,.8),smudgeChance:R.range(.1,.24),textureAmount:R.range(.025,.08),pauseFrames:0,revealFrames:2,revealSpeed:1,backgroundWashChance:.01,backgroundWashMinWords:5,backgroundWashMaxWords:50,backgroundWashColor:palette.highlight,baseGrainAmount:.02,baseMottleAmount:8e-4,gridDefinitions:[...Array(5)].map((e,t)=>(co=t>0?Math.floor(R.range(70,130)):Math.floor(R.range(45,65)),ro=Math.floor(1.25*co),{name:t.toString(),cols:co,rows:ro,rotation:R.g()/25-.02,scale:1,offsetX:0,offsetY:0,letterScale:.8})),showReference:!1};function shuffled(e){const t=[...e];for(let e=t.length-1;e>0;e--){const r=R.int(0,e);[t[e],t[r]]=[t[r],t[e]]}return t}const view=document.getElementById("view"),ref=document.getElementById("ref"),refCtx=ref.getContext("2d",{alpha:!1});let gl,vertexBuffer,washProgram,screenProgram,refTexture,width=0,height=0,dpr=1,frameBuffers=[],frameBufferId=0,resizeQueued=!1,isResizing=!1;const MIN_GL_SIZE=2,state={frame:0,grids:[],placements:[],activePlacementIndex:0},vertexShader="\n in vec3 pos;\n out vec2 vUv;\n\n void main() {\n gl_Position = vec4(pos, 1.0);\n vUv = (pos.xy + 1.0) * 0.5;\n }\n",washShader="\n uniform sampler2D previousFrame;\n uniform sampler2D referenceMask;\n uniform float frame;\n uniform float dirx;\n uniform float diry;\n uniform float washStrength;\n uniform float smudgeChance;\n uniform float textureAmount;\n uniform vec4 bgColor;\n uniform vec4 inkColor;\n uniform vec4 highlightColor;\n\n uniform vec2 resolution;\n uniform float grainSeed;\n uniform float baseGrainAmount;\n uniform float baseMottleAmount;\n\n in vec2 vUv;\n out vec4 col;\n\n vec3 random3(vec3 c) {\n float j = 4096.0 * sin(dot(c, vec3(17.0, 59.4, 15.0)));\n vec3 r;\n r.z = fract(512.0 * j);\n j *= 0.125;\n r.x = fract(512.0 * j);\n j *= 0.125;\n r.y = fract(512.0 * j);\n return r - 0.5;\n }\n\n const float F3 = 0.3333333;\n const float G3 = 0.1666667;\n\n float simplex3(vec3 p) {\n vec3 s = floor(p + dot(p, vec3(F3)));\n vec3 x = p - s + dot(s, vec3(G3));\n vec3 e = step(vec3(0.0), x - x.yzx);\n vec3 i1 = e * (1.0 - e.zxy);\n vec3 i2 = 1.0 - e.zxy * (1.0 - e);\n vec3 x1 = x - i1 + G3;\n vec3 x2 = x - i2 + 2.0 * G3;\n vec3 x3 = x - 1.0 + 3.0 * G3;\n vec4 w, d;\n w.x = dot(x, x);\n w.y = dot(x1, x1);\n w.z = dot(x2, x2);\n w.w = dot(x3, x3);\n w = max(0.6 - w, 0.0);\n d.x = dot(random3(s), x);\n d.y = dot(random3(s + i1), x1);\n d.z = dot(random3(s + i2), x2);\n d.w = dot(random3(s + 1.0), x3);\n w *= w;\n w *= w;\n d *= w;\n return dot(d, vec4(52.0));\n }\n\n float hash12(vec2 p) {\n vec3 p3 = fract(vec3(p.xyx) * 0.1031);\n p3 += dot(p3, p3.yzx + 33.33 + grainSeed * 17.17);\n return fract((p3.x + p3.y) * p3.z);\n }\n\n void main() {\n vec4 ref = texture(referenceMask, vUv);\n vec4 prev = texture(previousFrame, vUv);\n\n float grainA = simplex3(vec3(vUv.x * 380.0 + frame * 0.011, vUv.y * 380.0 - frame * 0.013, 11.0));\n float grainB = simplex3(vec3(vUv.x * 7500.0, vUv.y * 7500.0, frame * 0.018));\n\n vec4 texturedBg = bgColor + vec4(vec3(grainB * textureAmount), 0.0);\n\n col = prev;\n\n // Only bake paper grain into the very first rendered frame.\n // After that it becomes part of previousFrame.\n if (frame < 0.5) {\n vec2 px = gl_FragCoord.xy;\n\n float fine = hash12(px) - 0.5;\n\n float fine2 = hash12(px * 0.5 + 91.7) - 0.5;\n float mottle = simplex3(vec3(\n vUv.x * 95.0 + grainSeed * 13.0,\n vUv.y * 95.0 - grainSeed * 9.0,\n 4.0\n ));\n\n float paper =\n fine * baseGrainAmount +\n fine2 * baseGrainAmount * 0.45 +\n mottle * baseMottleAmount;\n\n col.rgb += vec3(paper);\n }\n\n if (ref.g > 0.001) {\n float rnd = simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 50.0,\n grainSeed * 100000.0\n ));\n\n vec4 textureBG = mix(\n bgColor,\n vec4(\n bgColor.rgb - vec3(\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 500.0,\n (vUv.y - frame / 1000.0) * 500.0,\n grainSeed * 100000.0\n )) / 80.\n ),\n 1.0\n ),\n simplex3(vec3(\n (vUv.x + frame / 1000.0) * 5.0,\n (vUv.y - frame / 1000.0) / 4.0,\n grainSeed * 100000.0\n ))\n );\n\n // This is the core wash behavior from the second project:\n // replace/scratch toward textured background, instead of dragging black blocks.\n col = mix(\n textureBG,\n // vec4(1.,0.,0.,1.),\n // bgColor,\n prev,\n simplex3(vec3(\n (vUv.x + frame / 100.0) * 5000.0,\n (vUv.y - frame / 100.0) * 5000.0,\n grainSeed * 100000.0\n ))*4.\n );\n\n // Directional smudge, but only sometimes.\n if (rnd < smudgeChance) {\n col = mix(\n texture(previousFrame, vec2(\n vUv.x + 0.003 * dirx,\n vUv.y + 0.003 * diry\n )),\n textureBG,\n rnd\n );\n }\n\n // Let the green mask control strength.\n prev = bgColor;\n col = mix(prev, col, ref.g * washStrength);\n }\n\n if (ref.b > 0.01) {\n float paintMask = ref.b;\n\n float roughA = simplex3(vec3(\n vUv.x * 900.0,\n vUv.y * 900.0,\n frame * 0.012\n ));\n\n float roughB = simplex3(vec3(\n vUv.x * 2200.0,\n vUv.y * 2200.0,\n 19.0\n ));\n\n float brokenMask = paintMask;\n brokenMask *= smoothstep(-0.45, 0.55, roughA);\n brokenMask *= 0.72 + roughB * 0.28;\n\n vec4 noisyPaint = highlightColor;\n noisyPaint.rgb += vec3(roughB * 0.10 + roughA * 0.035);\n\n vec2 dragUv = vUv + vec2(\n roughA * 0.0025,\n roughB * 0.0015\n );\n\n vec4 draggedSurface = texture(previousFrame, dragUv);\n\n vec4 paintOnSurface = mix(draggedSurface, noisyPaint, brokenMask);\n\n col = mix(col, paintOnSurface, brokenMask);\n }\n\n // Red = foreground ink. The incoming reference contains only the current word.\n if (ref.r > 0.01) {\n float smearNoise = simplex3(vec3(\n vUv.x * 900.0 + frame * 0.03,\n vUv.y * 220.0 - frame * 0.02,\n grainSeed * 9999.0\n ));\n\n float smearMask = ref.r * smoothstep(-0.35, 0.55, smearNoise);\n\n vec2 smearUv = vUv + vec2(\n -0.006 * dirx,\n 0.002 * diry\n );\n\n vec4 draggedInk = texture(previousFrame, smearUv);\n\n col = mix(col, draggedInk, smearMask * 0.65);\n\n vec4 brokenInk = inkColor;\n brokenInk.rgb += vec3(smearNoise * 0.16);\n\n col = mix(col, brokenInk, ref.r);\n }\n }\n",screenShader="\n uniform sampler2D buffer;\n in vec2 vUv;\n out vec4 col;\n\n void main() {\n col = texture(buffer, vec2(vUv.x, 1.0 - vUv.y));\n }\n";async function maybeFetchMoreWords(){if(!wordStream.fetching&&!wordStream.done&&!wordStream.failed)if(RPC_URLS.length){wordStream.fetching=!0;try{const e=await fetchNotesSlice(wordStream.nextOffset,FETCH_BATCH_SIZE);if(!e.length)return void(wordStream.done=!0);for(const t of e)WORDS.push(t),addWordPlacement(t);wordStream.nextOffset+=e.length,wordStream.nextOffset>=WORD_COUNT&&(wordStream.done=!0)}catch(e){console.warn("RPC word fetch failed:",e),wordStream.failed=!0}finally{wordStream.fetching=!1}}else wordStream.failed=!0}async function fetchNotesSlice(e,t){let r=null;for(const n of getRpcUrls())try{return await fetchNotesSliceFromRpc(n,TOKEN_ID,e,t)}catch(e){r=e,console.warn("RPC failed, trying next:",n,e)}throw r||new Error("No RPC worked")}const NOTES_SLICE_SELECTOR="0x91cf7995";async function fetchNotesSliceFromRpc(e,t,r,n){const o=NOTES_SLICE_SELECTOR+encodeUint256(t)+encodeUint256(r)+encodeUint256(n),a=await fetch(e,{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify({jsonrpc:"2.0",id:1,method:"eth_call",params:[{to:CONTRACT_ADDRESS,data:o},"latest"]})}),s=await a.json();if(s.error)throw new Error(s.error.message||"RPC error");return s.result&&"0x"!==s.result?decodeStringArray(s.result):[]}function encodeUint256(e){return BigInt(e).toString(16).padStart(64,"0")}function decodeStringArray(e){e=e.replace(/^0x/,"");const t=t=>{const r=2*t;return BigInt("0x"+e.slice(r,r+64))},r=(t,r)=>{const n=2*t;return e.slice(n,n+2*r)},n=Number(t(0)),o=Number(t(n)),a=[];for(let e=0;e<o;e++){const o=n+32+Number(t(n+32+32*e)),s=r(o+32,Number(t(o)));a.push(hexToUtf8(s))}return a}function hexToUtf8(e){const t=[];for(let r=0;r<e.length;r+=2)t.push(parseInt(e.slice(r,r+2),16));return(new TextDecoder).decode(new Uint8Array(t)).replace(/\0/g,"").trim()}function varyColor(e,t=.01){return[Math.max(0,Math.min(1,e[0]+R.range(-t,t))),Math.max(0,Math.min(1,e[1]+R.range(-t,t))),Math.max(0,Math.min(1,e[2]+R.range(-t,t))),e[3]??1]}function init(){if(gl=view.getContext("webgl2",{alpha:!1,antialias:!1,preserveDrawingBuffer:!0}),!gl)return void(document.body.innerHTML="WebGL2 is required.");vertexBuffer=createVertexBuffer(),washProgram=createProgram(vertexShader,washShader),screenProgram=createProgram(vertexShader,screenShader),createGrids(),createWordPlacements(),window.addEventListener("resize",requestResize),window.addEventListener("keydown",handleKey),view.addEventListener("webglcontextlost",e=>{e.preventDefault(),console.warn("WebGL context lost")},!1),view.addEventListener("webglcontextrestored",()=>{console.warn("WebGL context restored, rebuilding"),hardRebuild()},!1);new ResizeObserver(()=>requestResize()).observe(document.body),resize(),requestRun()}function resetRandom(){R=new Random(HASH)}function requestResize(){resizeQueued||(resizeQueued=!0,requestAnimationFrame(()=>{resizeQueued=!1,resize()}))}let lastRenderedPixelCount=0;function restartAfterResize(){resetRandom(),state.frame=0,state.activePlacementIndex=0,createGrids(),createWordPlacements(),drawBaseReference(),refTexture&&gl.deleteTexture(refTexture),refTexture=createTexture(width,height,ref),resetFrameBuffers(),window.__RESIDUAL_NOTES_RENDERED__=!1,blackFrameCount=0,lastProgressFrame=0,lastRenderedPixelCount=width*height,requestRun()}let renderW=null,renderH=null;function resize(){const e=1*Math.floor(window.innerWidth||document.documentElement.clientWidth||0),t=1*Math.floor(window.innerHeight||document.documentElement.clientHeight||0);if(e<20||t<20)return void requestAnimationFrame(requestResize);let r,n;e/t>settings.aspect?(n=t,r=t*settings.aspect):(r=e,n=e/settings.aspect),dpr=Math.max(1,window.devicePixelRatio||1);const o=renderW??Math.max(20,Math.round(r*dpr)),a=renderH??Math.max(20,Math.round(n*dpr));if(!(o===width&&a===height)){if(width>0&&height>0&&!(o*a>width*height))return view.style.width=`${r}px`,void(view.style.height=`${n}px`);isResizing=!0,width=o,height=a,view.width=width,view.height=height,view.style.display="block",ref.width=width,ref.height=height,ref.style.display=settings.showReference?"block":"none",restartAfterResize(),isResizing=!1}}function setCustomRenderSize(){const e=prompt("Render width in pixels:",width);if(!e)return;const t=Math.max(100,Math.floor(Number(e)));Number.isFinite(t)&&(renderW=t,renderH=Math.round(renderW/settings.aspect),resize(),restart())}function useStructureSpace(){refCtx.setTransform(width/4e3,0,0,height/5e3,0,0)}function resetFrameBuffers(){for(const e of frameBuffers)gl.deleteFramebuffer(e.buffer),gl.deleteTexture(e.texture);frameBuffers=[createFrameBuffer(width,height,ref),createFrameBuffer(width,height,null)],frameBufferId=0}function queueHardRebuild(e="unknown"){restartQueued||(restartQueued=!0,console.warn("Queueing hard rebuild:",e),requestAnimationFrame(()=>{restartQueued=!1,hardRebuild(e)}))}function hardRebuild(e="unknown"){if(console.warn("Hard rebuild:",e),gl&&!gl.isContextLost())try{if(resize(),width<MIN_GL_SIZE||height<MIN_GL_SIZE)return;restartAfterResize(),blackFrameCount=0,lastProgressFrame=state.frame,window.__RESIDUAL_NOTES_RENDERED__=!1}catch(e){console.warn("Hard rebuild failed:",e)}}function assertFramebufferHealthy(e=""){const t=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return t===gl.FRAMEBUFFER_COMPLETE||(queueHardRebuild(`bad framebuffer ${e}: ${t}`),!1)}function drawBaseReference(){useStructureSpace(),refCtx.fillStyle=rgb(palette.bg),refCtx.fillRect(0,0,4e3,5e3),refCtx.fillStyle=rgb(palette.grid);const e=Math.floor(settings.gridDefinitions[0].cols),t=Math.floor(settings.gridDefinitions[0].rows);for(let t=2;t<e-2;t++)refCtx.fillRect(4e3/e*t,0,1,5e3);for(let e=2;e<t-2;e++)refCtx.fillRect(0,5e3/t*e,4e3,1)}function createGrids(){state.grids=settings.gridDefinitions.map((e,t)=>({id:t,name:e.name||`grid ${t}`,cols:Math.floor(e.cols),rows:Math.floor(e.rows),rotation:e.rotation||0,scale:e.scale||1,offsetX:e.offsetX||0,offsetY:e.offsetY||0,letterScale:e.letterScale||.8,occupied:null}))}function createWordPlacements(){state.placements=[];let e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords);for(const t of WORDS)addWordPlacement(t),e--,e<=0&&R.chance(settings.backgroundWashChance)&&(addBackgroundWashPlacement(),e=R.int(settings.backgroundWashMinWords,settings.backgroundWashMaxWords))}function addWordPlacement(e){if(!e||!String(e).trim())return!1;const t=String(e).toUpperCase(),r=tryCreateWordPlacement(t)||tryCreateWordPlacement(t)||tryCreateWordPlacement(t);return!!r&&(state.placements.push(r),!0)}function addBackgroundWashPlacement(){state.placements.push({type:"backgroundWash",color:R.chance(.15)?palette.highlight:darkenedBgColor(.1),dirx:R.range(-1,1),diry:R.range(-1,1),washStrength:R.range(.03,.16),smudgeChance:R.range(.1,.32),x:4e3*R.range(-.08,.85),y:5e3*R.range(-.08,.85),w:4e3*R.range(.18,.55),h:5e3*R.range(.12,.38),seed:9999*R.g(),alpha:R.range(.02,.175),dripChance:R.range(.08,.28),force:1})}function tryCreateWordPlacement(e){for(let t=0;t<80;t++){const t=R.pick(state.grids),r=R.pick(["right","down","right","down","right","down","right","down","right","down","right","down","right","down","diagDown","diagUp"]),n=randomStartForTextBlock(t,e,r),o=textBlockCells(n.x,n.y,e,r,t);if(cellsFit(t,o))return{word:e,grid:t,direction:r,cells:o,color:varyColor(pickInkForWord(),.05),dirx:R.range(-2,2),diry:R.range(-2,2),washSeed:R.g(),washStrength:R.range(.05,.35),smudgeChance:R.range(.1,.32),washPad:R.range(.04,.2),washGaps:0,washStampCount:R.int(1,6),circle:R.chance(.065),underline:R.chance(.05),strikethrough:R.chance(.05),lettergrid:R.chance(.01),square:R.chance(.01),cross:R.chance(.01),plus:R.chance(.005),force:1}}return null}function randomStartForTextBlock(e,t,r){const n=Math.min(String(t).length,"right"===r?e.cols:e.rows),o=R.int(0,e.cols-1),a=R.int(0,e.rows-1);return"right"===r?{x:Math.max(0,Math.min(e.cols-1,o-Math.floor(n*R.range(.25,.65)))),y:Math.max(0,Math.min(e.rows-1,a-R.int(0,2)))}:"down"===r?{x:Math.max(0,Math.min(e.cols-1,o-R.int(0,2))),y:Math.max(0,Math.min(e.rows-1,a-Math.floor(n*R.range(.25,.65))))}:{x:o,y:a}}function textBlockCells(e,t,r,n,o){if("diagDown"===n||"diagUp"===n)return diagonalTextCells(e,t,r,n);const a=String(r).split(/\s+/),s=[],i="right"===n?Math.max(1,o.cols-e):Math.max(1,o.rows-t);let l=0,f=0;for(const r of a){if(f+(f>0?1:0)+r.length>i&&(l++,f=0),"right"===n&&t+l>=o.rows)break;if("down"===n&&e+l>=o.cols)break;f>0&&(addTextCharCell(s,e,t,n,f,l," "),f++);for(let a=0;a<r.length&&(f>=i&&(l++,f=0),!("right"===n&&t+l>=o.rows))&&!("down"===n&&e+l>=o.cols);a++)addTextCharCell(s,e,t,n,f,l,r[a]),f++}return s.map((e,t)=>({...e,index:t}))}function addTextCharCell(e,t,r,n,o,a,s){"right"===n&&e.push({x:t+o,y:r+a,char:s}),"down"===n&&e.push({x:t+a,y:r+o,char:s})}function diagonalTextCells(e,t,r,n){const o=[];for(let a=0;a<r.length;a++){let s=e+a,i="diagDown"===n?t+a:t-a;o.push({x:s,y:i,char:r[a],index:a})}return o}function cellsFit(e,t){return t.every(t=>t.x>=0&&t.y>=0&&t.x<e.cols&&t.y<e.rows)}function pickInkForWord(){return weightedPick(palette.inks).color}function weightedPick(e){const t=e.reduce((e,t)=>e+t.weight,0);let r=R.g()*t;for(const t of e)if(r-=t.weight,r<=0)return t;return e[e.length-1]}function clearReferenceToBlack(){refCtx.setTransform(1,0,0,1,0,0),refCtx.fillStyle="black",refCtx.fillRect(0,0,width,height)}function drawPlacementReference(e,t){if("backgroundWash"===e.type)return void drawBackgroundWashReference(e,t);clearReferenceToBlack(),refCtx.save(),useStructureSpace(),applyGridTransform(e.grid);const r=Math.ceil(e.cells.length*t),n=e.cells.slice(0,r);drawWashForCells(e.grid,n,e.washPad,e),drawWordCells(e.grid,n,e),e.underline&&t>.72&&drawUnderline(e),e.circle&&t>.84&&drawCircleAroundWord(e),e.strikethrough&&t>.65&&drawStrikethrough(e),e.lettergrid&&t>.7&&drawGridAroundLetters(e,n),e.square&&t>.82&&drawSquareAroundWord(e),e.cross&&t>.78&&drawCrossThroughWord(e),e.plus&&t>.78&&drawPlusThroughWord(e),refCtx.restore()}function applyGridTransform(e){refCtx.translate(2e3+4e3*e.offsetX,2500+5e3*e.offsetY),refCtx.rotate(e.rotation),refCtx.scale(e.scale,e.scale),refCtx.translate(-2e3,-2500)}function drawWashForCells(e,t,r,n){const o=4e3/e.cols,a=5e3/e.rows;refCtx.save(),refCtx.fillStyle="rgb(0,255,0)";for(const e of t){const t=n.washStampCount??1;for(let s=0;s<t;s++){const t=1e3*n.washSeed+97*s;if(stable01(e.x,e.y,e.index+t)<n.washGaps)continue;const i=stable01(e.x,e.y,101+t),l=stable01(e.x,e.y,202+t),f=stableNoise(e.x,e.y,303+t)*o*.22,c=stableNoise(e.x,e.y,404+t)*a*.22,d=o*(.04+.14*i),g=a*(.04+.14*l),u=o*r,h=a*r;refCtx.fillRect(e.x*o+d-u+f,e.y*a+g-h+c,o-2*d+2*u,a-2*g+2*h)}}refCtx.restore()}function stable01(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)}function drawWordCells(e,t,r){const n=4e3/e.cols,o=5e3/e.rows;refCtx.fillStyle="rgb(255,0,0)",refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(1,.09*Math.min(n,o)),refCtx.lineCap="round",refCtx.lineJoin="round";for(const e of t)drawLetterInCell(e.char,e.x*n,e.y*o,n,o,r,e.index)}function drawLetterInCell(e,t,r,n,o,a,s){if(" "===e)return;const i=Math.min(n,o)*RangedLetterScale(a,s),l=stableNoise(t,r,s+1)*n*.1,f=stableNoise(t,r,s+2)*o*.1,c=.05*stableNoise(t,r,s+3);refCtx.save(),refCtx.translate(t+.5*n+l,r+.54*o+f),refCtx.rotate(c),refCtx.font=`${i}px ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace`,refCtx.textAlign="center",refCtx.textBaseline="middle",refCtx.fillText(e,0,0),refCtx.restore()}function RangedLetterScale(e,t){return e.grid.letterScale}function darkenedBgColor(e=.1){return[Math.max(0,palette.bg[0]-e),Math.max(0,palette.bg[1]-e),Math.max(0,palette.bg[2]-e),palette.bg[3]??1]}function drawUnderline(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round";const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.84*r,l=a.x*t+.85*t,f=a.y*r+.84*r,c=l-s,d=f-i,g=Math.max(1e-4,Math.sqrt(c*c+d*d)),u=-d/g,h=c/g,x=.28*Math.min(t,r);refCtx.beginPath(),refCtx.moveTo(s+u*x,i+h*x),refCtx.lineTo(l+u*x+stableNoise(l,f,9)*t*.25,f+h*x+stableNoise(s,i,10)*r*.25),refCtx.stroke(),refCtx.restore()}function drawStrikethrough(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.filter(e=>" "!==e.char);if(!n.length)return;const o=n[0],a=n[n.length-1],s=o.x*t+.15*t,i=o.y*r+.5*r,l=a.x*t+.85*t,f=a.y*r+.5*r;refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*Math.min(t,r)),refCtx.lineCap="round",refCtx.lineJoin="round",refCtx.beginPath(),refCtx.moveTo(s+stableNoise(s,i,21)*t*.18,i+stableNoise(s,i,22)*r*.18),refCtx.lineTo(l+stableNoise(l,f,23)*t*.18,f+stableNoise(l,f,24)*r*.18),refCtx.stroke(),refCtx.restore()}function drawCircleAroundWord(e){const t=wordBox(e),r=.5*(t.x1+t.x2),n=.5*(t.y1+t.y2),o=.62*Math.abs(t.x2-t.x1)+4e3/e.grid.cols,a=.62*Math.abs(t.y2-t.y1)+5e3/e.grid.rows,s=.35*stableNoise(r,n,4),i=refCtx.lineWidth,l=refCtx.strokeStyle;refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineCap="round";const f=stableNoise(r,n,999),c=1+Math.floor(5*f);for(let e=0;e<c;e++){const t=1e3+137*e,l=stableNoise(r,n,t+1),f=stableNoise(r,n,t+2),c=stableNoise(r,n,t+3),d=stableNoise(r,n,t+4),g=stableNoise(r,n,t+5),u=.94+.18*g,h=.18+.95*stableNoise(r,n,t+6),x=.35+.65*d,m=4e3*l*.006,C=5e3*f*.006,b=o*u*(1+.04*c),w=a*u*(1-.04*c),p=g*Math.PI*2,R=stableNoise(r,n,t+7)<.45?.55+.35*stableNoise(r,n,t+8):1,v=p+2*Math.PI*R;refCtx.save(),refCtx.globalAlpha*=x,refCtx.lineWidth=i*h,refCtx.beginPath(),refCtx.ellipse(r+m,n+C,b,w,s+.22*d,p,v),refCtx.stroke(),refCtx.restore()}refCtx.lineWidth=i,refCtx.strokeStyle=l}function drawGridAroundLetters(e,t){const r=4e3/e.grid.cols,n=5e3/e.grid.rows,o=t.filter(e=>" "!==e.char);if(o.length){refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.5,.045*Math.min(r,n)),refCtx.lineCap="square",refCtx.lineJoin="miter";for(const t of o){const o=t.x*r,a=t.y*n,s=.025*Math.min(r,n),i=t.index+1e3*e.washSeed;refCtx.beginPath(),refCtx.rect(o+stableNoise(t.x,t.y,i+1)*s,a+stableNoise(t.x,t.y,i+2)*s,r+stableNoise(t.x,t.y,i+3)*s,n+stableNoise(t.x,t.y,i+4)*s),refCtx.stroke()}refCtx.restore()}}function drawSquareAroundWord(e){const t=expandedWordBox(e,.22),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round";const n=.12*r;refCtx.beginPath(),refCtx.moveTo(t.x1+stableNoise(t.x1,t.y1,301)*n,t.y1+stableNoise(t.x1,t.y1,302)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y1,303)*n,t.y1+stableNoise(t.x2,t.y1,304)*n),refCtx.lineTo(t.x2+stableNoise(t.x2,t.y2,305)*n,t.y2+stableNoise(t.x2,t.y2,306)*n),refCtx.lineTo(t.x1+stableNoise(t.x1,t.y2,307)*n,t.y2+stableNoise(t.x1,t.y2,308)*n),refCtx.closePath(),refCtx.stroke(),refCtx.restore()}function drawCrossThroughWord(e){const t=expandedWordBox(e,.12),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows);refCtx.sav+Xe(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,t.y1,t.x2,t.y2,r,401),drawNoisyLine(t.x2,t.y1,t.x1,t.y2,r,411),refCtx.restore()}function drawPlusThroughWord(e){const t=expandedWordBox(e,.1),r=Math.min(4e3/e.grid.cols,5e3/e.grid.rows),n=.5*(t.x1+t.x2),o=.5*(t.y1+t.y2);refCtx.save(),refCtx.strokeStyle="rgb(255,0,0)",refCtx.lineWidth=Math.max(.6,.08*r),refCtx.lineCap="round",refCtx.lineJoin="round",drawNoisyLine(t.x1,o,t.x2,o,r,501),drawNoisyLine(n,t.y1,n,t.y2,r,511),refCtx.restore()}function drawBackgroundWashReference(e,t){clearReferenceToBlack(),refCtx.save(),useStructureSpace(),refCtx.globalAlpha=e.alpha*t,refCtx.fillStyle="rgb(0,0,255)",refCtx.strokeStyle="rgb(0,0,255)",refCtx.lineCap="round",refCtx.lineJoin="round";const r=e.y+.42*e.h;for(let n=0;n<44;n++){const o=n/Math.max(1,43),a=e.x+e.w*o+stableNoise(e.seed,n,11)*e.w*.025,s=Math.sin(o*Math.PI),i=r-e.h*(.25+.75*s+.45*stable01(e.seed,n,12)),l=r+e.h*(.15+.55*s+.75*stable01(e.seed,n,13)),f=e.w*(.018+.045*stable01(e.seed,n,14));drawVerticalPaintColumn(a,i,l,f,e.seed+91*n,t),stable01(e.seed,n,77)<e.dripChance&&drawPaintDrip(a+stableNoise(e.seed,n,21)*f,l-.05*e.h,e.h*(.35+1.4*stable01(e.seed,n,22)),e.seed+200*n)}for(let n=0;n<9;n++){const o=r+stableNoise(e.seed,n,301)*e.h*.45;refCtx.globalAlpha=e.alpha*t*.22,refCtx.lineWidth=e.h*(.025+.045*stable01(e.seed,n,302)),drawNoisyHorizontalStroke(e.x+stableNoise(e.seed,n,303)*e.w*.05,o,e.x+e.w+stableNoise(e.seed,n,304)*e.w*.05,o+stableNoise(e.seed,n,305)*e.h*.18,e.seed+400*n)}refCtx.restore()}function drawVerticalPaintColumn(e,t,r,n,o,a){refCtx.beginPath();for(let a=0;a<=12;a++){const s=t+(r-t)*(a/12),i=e-.5*n+stableNoise(o,a,31)*n*.75;0===a?refCtx.moveTo(i,s):refCtx.lineTo(i,s)}for(let a=12;a>=0;a--){const s=t+(r-t)*(a/12),i=e+.5*n+stableNoise(o,a,41)*n*.75;refCtx.lineTo(i,s)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalAlpha*=.35;const s=2+Math.floor(5*stable01(o,1,55));for(let a=0;a<s;a++){const s=e+stableNoise(o,a,61)*n*.45;refCtx.lineWidth=n*(.08+.18*stable01(o,a,62)),refCtx.beginPath(),refCtx.moveTo(s,t+stableNoise(o,a,63)*n),refCtx.lineTo(s+stableNoise(o,a,64)*n*.45,r-stable01(o,a,65)*n),refCtx.stroke()}refCtx.restore(),refCtx.save(),refCtx.globalCompositeOperation="destination-out";for(let s=0;s<22;s++){const i=stable01(o,s,71)<.5?-1:1,l=t+stable01(o,s,72)*(r-t),f=e+i*n*(.3+.45*stable01(o,s,73));refCtx.globalAlpha=a*(.04+.14*stable01(o,s,74)),refCtx.beginPath(),refCtx.ellipse(f,l,n*(.15+.55*stable01(o,s,75)),n*(.25+1.25*stable01(o,s,76)),stableNoise(o,s,77),0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawNoisyHorizontalStroke(e,t,r,n,o){refCtx.beginPath();for(let a=0;a<=18;a++){const s=a/18,i=e+(r-e)*s,l=t+(n-t)*s+5e3*stableNoise(o,a,91)*.006;0===a?refCtx.moveTo(i,l):refCtx.lineTo(i,l)}refCtx.stroke()}function drawPainterlyBlob(e,t,r,n,o,a){const s=[];for(let a=0;a<28;a++){const i=a/28*Math.PI*2,l=1+.12*stableNoise(o,a,31)+.06*stableNoise(o,a,32)+.06*Math.sin(2*i+o)+.04*Math.sin(5*i+.37*o);s.push({x:e+Math.cos(i)*r*.5*l,y:t+Math.sin(i)*n*.5*l})}refCtx.beginPath();for(let e=0;e<s.length;e++){const t=s[(e-1+s.length)%s.length],r=s[e],n=s[(e+1)%s.length],o={x:.5*(t.x+r.x),y:.5*(t.y+r.y)},a={x:.5*(r.x+n.x),y:.5*(r.y+n.y)};0===e&&refCtx.moveTo(o.x,o.y),refCtx.quadraticCurveTo(r.x,r.y,a.x,a.y)}refCtx.closePath(),refCtx.fill(),refCtx.save(),refCtx.globalCompositeOperation="destination-out",refCtx.globalAlpha=.1*a;for(let s=0;s<160;s++){const i=stable01(o,s,41)*Math.PI*2,l=Math.sqrt(stable01(o,s,42)),f=Math.cos(i)*l,c=Math.sin(i)*l,d=e+f*r*.48,g=t+c*n*.48,u=Math.min(1,Math.sqrt(f*f+c*c)),h=Math.pow(u,2.2),x=r*(.004+.09*h*stable01(o,s,43)),m=n*(.003+.05*h*stable01(o,s,44));refCtx.globalAlpha=(.01+.18*h)*a,refCtx.beginPath(),refCtx.ellipse(d,g,x,m,stableNoise(o,s,45)*Math.PI,0,2*Math.PI),refCtx.fill()}refCtx.restore()}function drawPaintDrip(e,t,r,n){const o=.045*r;refCtx.beginPath(),refCtx.moveTo(e,t);for(let a=1;a<=5;a++){const s=a/5;refCtx.lineTo(e+stableNoise(n,a,51)*o,t+r*s)}refCtx.lineWidth=Math.max(1,4.8),refCtx.lineCap="round",refCtx.strokeStyle="rgb(0,0,255)",refCtx.stroke()}function drawNoisyLine(e,t,r,n,o,a){const s=.16*o;refCtx.beginPath(),refCtx.moveTo(e+stableNoise(e,t,a+1)*s,t+stableNoise(e,t,a+2)*s),refCtx.lineTo(r+stableNoise(r,n,a+3)*s,n+stableNoise(r,n,a+4)*s),refCtx.stroke()}function expandedWordBox(e,t=.15){const r=wordBox(e),n=4e3/e.grid.cols,o=5e3/e.grid.rows;return{x1:r.x1-n*t,y1:r.y1-o*t,x2:r.x2+n*t,y2:r.y2+o*t}}function wordBox(e){const t=4e3/e.grid.cols,r=5e3/e.grid.rows,n=e.cells.map(e=>e.x*t),o=e.cells.map(e=>e.y*r);return{x1:Math.min(...n),y1:Math.min(...o),x2:Math.max(...n)+t,y2:Math.max(...o)+r}}function stableNoise(e,t,r){const n=43758.5453*Math.sin(12.9898*e+78.233*t+37.719*r);return n-Math.floor(n)-.5}function run(){if(renderLoopRunning=!1,isResizing||width<MIN_GL_SIZE||height<MIN_GL_SIZE||!refTexture||frameBuffers.length<2)return void requestRun();maybeFetchMoreWords();const e=state.placements[state.activePlacementIndex];if(!e)return renderScreen(),state.frame%30==0&&sampleVisibleCanvasHealth(),void(window.__RESIDUAL_NOTES_RENDERED__||(window.__RESIDUAL_NOTES_RENDERED__=!0,window.dispatchEvent(new Event("residual-notes:rendered"))));const t=settings.pauseFrames+settings.revealFrames,r=state.frame%t;let n=0,o=!1;if(r<settings.pauseFrames)clearReferenceToBlack();else{const t=r-settings.pauseFrames;n=Math.min(1,(t+1)*settings.revealSpeed/settings.revealFrames),o=t>=settings.revealFrames-1,drawPlacementReference(e,n)}updateReferenceTexture(),renderWash(e),renderScreen(),(o||r===t-1)&&state.activePlacementIndex++,state.frame++,requestRun()}function renderWash(e){const t=frameBufferId,r=1^frameBufferId;frameBufferId=r,useProgram(washProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,frameBuffers[r].buffer),assertFramebufferHealthy("renderWash")&&(gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[t].texture),gl.uniform1i(gl.getUniformLocation(washProgram,"previousFrame"),0),gl.activeTexture(gl.TEXTURE1),gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.uniform1i(gl.getUniformLocation(washProgram,"referenceMask"),1),gl.uniform1f(gl.getUniformLocation(washProgram,"frame"),state.frame),gl.uniform1f(gl.getUniformLocation(washProgram,"dirx"),e.dirx),gl.uniform1f(gl.getUniformLocation(washProgram,"diry"),e.diry),gl.uniform1f(gl.getUniformLocation(washProgram,"washStrength"),e.washStrength??settings.washStrength),gl.uniform1f(gl.getUniformLocation(washProgram,"smudgeChance"),e.smudgeChance??settings.smudgeChance),gl.uniform1f(gl.getUniformLocation(washProgram,"textureAmount"),settings.textureAmount),gl.uniform4fv(gl.getUniformLocation(washProgram,"bgColor"),palette.bg),gl.uniform4fv(gl.getUniformLocation(washProgram,"inkColor"),e.color),gl.uniform4fv(gl.getUniformLocation(washProgram,"highlightColor"),"backgroundWash"===e.type?e.color:palette.highlight),gl.uniform2f(gl.getUniformLocation(washProgram,"resolution"),width,height),gl.uniform1f(gl.getUniformLocation(washProgram,"grainSeed"),GRAIN_SEED),gl.uniform1f(gl.getUniformLocation(washProgram,"baseGrainAmount"),settings.baseGrainAmount),gl.uniform1f(gl.getUniformLocation(washProgram,"baseMottleAmount"),settings.baseMottleAmount),gl.drawArrays(gl.TRIANGLE_STRIP,0,4))}function sampleVisibleCanvasHealth(){if(!gl||gl.isContextLost())return;const e=new Uint8Array(4);gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.readPixels(Math.floor(.5*width),Math.floor(.5*height),1,1,gl.RGBA,gl.UNSIGNED_BYTE,e);e[0]+e[1]+e[2]<8&&state.frame>20?blackFrameCount++:blackFrameCount=0,blackFrameCount>90&&queueHardRebuild("visible canvas stayed black"),state.activePlacementIndex>0&&state.frame-lastProgressFrame>240&&queueHardRebuild("render loop appears stuck"),lastProgressFrame=state.frame}function renderScreen(){useProgram(screenProgram),gl.bindFramebuffer(gl.FRAMEBUFFER,null),gl.viewport(0,0,width,height),gl.clearColor(0,0,0,1),gl.clear(gl.COLOR_BUFFER_BIT),gl.activeTexture(gl.TEXTURE0),gl.bindTexture(gl.TEXTURE_2D,frameBuffers[frameBufferId].texture),gl.uniform1i(gl.getUniformLocation(screenProgram,"buffer"),0),gl.drawArrays(gl.TRIANGLE_STRIP,0,4)}function useProgram(e){gl.useProgram(e);const t=gl.getAttribLocation(e,"pos");gl.enableVertexAttribArray(t),gl.bindBuffer(gl.ARRAY_BUFFER,vertexBuffer),gl.vertexAttribPointer(t,3,gl.FLOAT,!1,0,0)}function createProgram(e,t){const r=gl.createProgram(),n=createShader(`#version 300 es\n${e}`,gl.VERTEX_SHADER),o=createShader(`#version 300 es\nprecision highp float;\n${t}`,gl.FRAGMENT_SHADER);return gl.attachShader(r,n),gl.attachShader(r,o),gl.linkProgram(r),gl.getProgramParameter(r,gl.LINK_STATUS)||console.error(gl.getProgramInfoLog(r)),r}function createShader(e,t){const r=gl.createShader(t);return gl.shaderSource(r,e),gl.compileShader(r),gl.getShaderParameter(r,gl.COMPILE_STATUS)||console.error(gl.getShaderInfoLog(r)),r}function createVertexBuffer(){const e=gl.createBuffer();return gl.bindBuffer(gl.ARRAY_BUFFER,e),gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,1,0,-1,-1,0,1,1,0,1,-1,0]),gl.STATIC_DRAW),e}function createFrameBuffer(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createFramebuffer(),o=createTexture(e,t,r);gl.bindFramebuffer(gl.FRAMEBUFFER,n),gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,o,0);const a=gl.checkFramebufferStatus(gl.FRAMEBUFFER);return a!==gl.FRAMEBUFFER_COMPLETE&&console.warn("Framebuffer incomplete:",a,e,t),gl.bindFramebuffer(gl.FRAMEBUFFER,null),{buffer:n,texture:o}}function createTexture(e,t,r=null){e=Math.max(MIN_GL_SIZE,Math.floor(e)),t=Math.max(MIN_GL_SIZE,Math.floor(t));const n=gl.createTexture();return gl.bindTexture(gl.TEXTURE_2D,n),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),r&&r.width>0&&r.height>0?gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,r):gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,e,t,0,gl.RGBA,gl.UNSIGNED_BYTE,null),n}function updateReferenceTexture(){refTexture&&(!ref||ref.width<MIN_GL_SIZE||ref.height<MIN_GL_SIZE||(gl.bindTexture(gl.TEXTURE_2D,refTexture),gl.texSubImage2D(gl.TEXTURE_2D,0,0,0,gl.RGBA,gl.UNSIGNED_BYTE,ref)))}function rgb(e){return`rgb(${Math.floor(255*e[0])}, ${Math.floor(255*e[1])}, ${Math.floor(255*e[2])})`}function clamp255(e){return Math.max(0,Math.min(255,e))}function handleKey(e){"s"===e.key&&savePNG(),"m"===e.key&&(settings.showReference=!settings.showReference,ref.style.display=settings.showReference?"block":"none"),"c"===e.key&&setCustomRenderSize(),"p"===e.key.toLowerCase()&&setCustomRpcUrl()}function savePNG(){renderScreen();const e=document.createElement("a");e.download=`word-field-wash-${HASH.slice(2,10)}-${width}x${height}.png`,e.href=view.toDataURL("image/png"),e.click()}function restart(){restartAfterResize()}init();