memoscan
← all memos

Memo 0xd71b265d…4951fe on Ethereum

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>THE PURRMINATORS | Phase 2 Living Geometry Melt</title> <style> html,body{margin:0;height:100%;display:flex;justify-content:center;align-items:center;background:#222} #box{position:relative;width:400px;height:400px;border:2px solid #666;overflow:hidden;background:#000} #phase1,#melt{position:absolute;top:0;left:0;width:400px;height:400px;border:none} #melt{z-index:2;display:none;pointer-events:none;image-rendering:pixelated} #panel{position:absolute;top:6px;left:6px;z-index:3;display:flex;gap:6px} button{font-size:13px;background:rgba(0,0,0,.55);color:#fff;border:1px solid #888;border-radius:4px;padding:4px 10px;cursor:pointer} button:disabled{opacity:.4;cursor:default} </style> <script src="https://cdn.jsdelivr.net/npm/web3/dist/web3.min.js"></script> </head> <body> <div id="box"> <iframe id="phase1"></iframe> <canvas id="melt"></canvas> <div id="panel"> <button id="meltBtn">Melt</button> <button id="reformBtn" disabled>Re-form</button> </div> </div> <script> // ── CONSTANTS & GLOBALS ───────────────────────────────────────────── const TX_PHASE1 = "0x9f1e8063c9277ddd50651472ba8a3e7fe5ea7e8df2f8722b4a0f5646492b55de"; const SIZE = 400, DPR = devicePixelRatio || 1; let web3, phase1Frame, melt, ctx; let particles = [], mode = "none", active = false; let lastTS = 0, tGlobal = 0; // ── UTILS ───────────────────────────────────────────────────────────── const rnd = (hi=1, lo=0) => Math.random()*(hi-lo) + lo; const colouredFamilies = ["red","orange","yellow","green","cyan","blue","magenta"]; const geometryPool = ["circle","fig8","triangle","squircle","spiral","ellipse","star","square","hexagon"]; const geomState = {}; // pick random geometry for each colour family function mutateAllGeometries(){ colouredFamilies.forEach(fam => { const type = geometryPool[Math.floor(Math.random()*geometryPool.length)]; switch(type){ case "circle": geomState[fam]={type,R:rnd(80,120),speed:rnd(0.3,0.1),phase:rnd(0,2*Math.PI)};break; case "fig8": geomState[fam]={type,R:rnd(60,90),speed:rnd(0.4,0.15),phase:rnd(0,2*Math.PI)};break; case "triangle": geomState[fam]={type,R:rnd(70,100),rotSpeed:rnd(0.5,0.2),phase:rnd(0,2*Math.PI)};break; case "squircle": geomState[fam]={type,R:rnd(60,100),rot:rnd(0,2*Math.PI)};break; case "spiral": geomState[fam]={type,a:rnd(4,2),b:rnd(3,1),rotSpeed:rnd(0.4,0.15)};break; case "ellipse": geomState[fam]={type,rx:rnd(60,100),ry:rnd(40,70),tilt:rnd(0,2*Math.PI),speed:rnd(0.4,0.15)};break; case "star": geomState[fam]={type,R:rnd(70,100),r:rnd(25,35),rotSpeed:rnd(0.6,0.2)};break; case "square": geomState[fam]={type,S:rnd(120,160),speed:rnd(0.35,0.15),rot:rnd(0,2*Math.PI)};break; case "hexagon": geomState[fam]={type,R:rnd(85,115),speed:rnd(0.35,0.15),rot:rnd(0,2*Math.PI)};break; } }); } // compute target geometry point for a fam + time + index function geomTarget(fam,t,idx){ const s=geomState[fam]; if(!s) return [200,200]; switch(s.type){ case "circle":{ const ang=t*s.speed+s.phase+idx*0.0008; return [200+Math.cos(ang)*s.R,200+Math.sin(ang)*s.R]; } case "fig8":{ const ang=t*s.speed+s.phase+idx*0.001; return [200+s.R*Math.sin(ang),200+s.R*Math.sin(ang)*Math.cos(ang)]; } case "triangle":{ const ang=(idx%3)*2*Math.PI/3 + s.phase + t*s.rotSpeed; return [200+Math.cos(ang)*s.R,200+Math.sin(ang)*s.R]; } case "squircle":{ const ang=t*0.8+s.rot,n=4; const x=s.R*Math.sign(Math.cos(ang))*Math.pow(Math.abs(Math.cos(ang)),2/n); const y=s.R*Math.sign(Math.sin(ang))*Math.pow(Math.abs(Math.sin(ang)),2/n); return [200+x,200+y]; } case "spiral":{ const ang=t*s.rotSpeed+idx*0.002; const r=s.a*Math.exp(s.b*ang*0.05); return [200+r*Math.cos(ang),200+r*Math.sin(ang)]; } case "ellipse":{ const ang=t*s.speed+idx*0.001; const x=s.rx*Math.cos(ang), y=s.ry*Math.sin(ang); const c=Math.cos(s.tilt),si=Math.sin(s.tilt); return [200+x*c-y*si,200+x*si+y*c]; } case "star":{ const k=5, ang=(idx%k)*2*Math.PI/k + t*s.rotSpeed; const r=((idx%k)%2===0)?s.R:s.r; return [200+Math.cos(ang)*r,200+Math.sin(ang)*r]; } case "square":{ const per=(t*s.speed+idx*0.0008)%4,half=s.S/2,rot=s.rot; let lx,ly; if(per<1){lx=-half+per*s.S;ly=-half} else if(per<2){lx=half;ly=-half+(per-1)*s.S} else if(per<3){lx=half-(per-2)*s.S;ly=half} else{lx=-half;ly=half-(per-3)*s.S} const c=Math.cos(rot),si=Math.sin(rot); return [200+lx*c-ly*si,200+lx*si+ly*c]; } case "hexagon":{ const per=(t*s.speed+idx*0.0008)%6,ang=per*Math.PI/3; const v1=[Math.cos(ang)*s.R,Math.sin(ang)*s.R], v2=[Math.cos(ang+Math.PI/3)*s.R,Math.sin(ang+Math.PI/3)*s.R], f=per%1; const lx=v1[0]*(1-f)+v2[0]*f, ly=v1[1]*(1-f)+v2[1]*f; const c=Math.cos(s.rot),si=Math.sin(s.rot); return [200+lx*c-ly*si,200+lx*si+ly*c]; } } return [200,200]; } // fetch on-chain HTML async function fetchHtml(tx){ try { const t = await web3.eth.getTransaction(tx); if(!t||!t.input) return null; const hex = t.input.startsWith("0x")?t.input.slice(2):t.input; const bytes = web3.utils.hexToBytes(hex); const txt = new TextDecoder().decode(new Uint8Array(bytes)); const m = txt.match(/data:text\/html;base64,([A-Za-z0-9+/=]+)/); return m? atob(m[1]) : null; } catch { return null; } } // simple hue-based classification function rgbaToFamily(r,g,b){ const M=Math.max(r,g,b)/255, m=Math.min(r,g,b)/255, d=M-m, L=(M+m)/2; if(d<0.01) return L<0.15?"black":L>0.85?"white":"grey"; let H; if(M===r/255) H=((g-b)/d)%6; else if(M===g/255) H=(b-r)/d+2; else H=(r-g)/d+4; H*=60; if(H<0) H+=360; if(H<20||H>=340) return "red"; if(H<45) return "orange"; if(H<70) return "yellow"; if(H<150) return "green"; if(H<200) return "cyan"; if(H<260) return "blue"; return "magenta"; } // ── MELT / REFORM ───────────────────────────────────────────────────── let geomTimer; function startMelt(){ if(active) return; const src = phase1Frame.contentDocument?.getElementById("mainCanvas"); if(!src){ alert("Phase-1 canvas not found"); return; } const data = src.getContext("2d").getImageData(0,0,SIZE,SIZE).data; particles = []; for(let y=0;y<SIZE;y++){ for(let x=0;x<SIZE;x++){ const i=(y*SIZE+x)*4; if(data[i+3]<10) continue; const r=data[i],g=data[i+1],b=data[i+2],a=data[i+3]/255; particles.push({ x,y, baseX:x, baseY:y, vx:(Math.random()-0.5)*40, vy:(Math.random()-0.5)*40, rgb:[r,g,b], alpha:a, fam:rgbaToFamily(r,g,b), zPhase:Math.random()*2*Math.PI, size:1, noiseMag:rnd(6,12), noiseSpeed:rnd(1.5,0.8), noisePhase:rnd(0,2*Math.PI) }); } } phase1Frame.style.visibility="hidden"; melt.style.display="block"; meltBtn.disabled = true; reformBtn.disabled = false; active=true; mode="melt"; tGlobal=0; mutateAllGeometries(); geomTimer=setInterval(mutateAllGeometries,4000); } function startReform(){ if(mode!=="melt")return; mode="reform"; clearInterval(geomTimer); particles.forEach(p=>{ p.vx+=(p.baseX-p.x)*2.2; p.vy+=(p.baseY-p.y)*2.2; }); } // ── INIT ───────────────────────────────────────────────────────────── window.addEventListener("load",async()=>{ web3=new Web3(new Web3.providers.HttpProvider( "https://mainnet.infura.io/v3/330c1ac15fc7483b8d9b6f229938d843" )); phase1Frame=document.getElementById("phase1"); phase1Frame.srcdoc=await fetchHtml(TX_PHASE1) ||"<h2 style='color:#fff;text-align:center;'>phase-1 not found</h2>"; melt=document.getElementById("melt"); melt.width = SIZE*DPR; melt.height = SIZE*DPR; melt.style.width = SIZE+"px"; melt.style.height = SIZE+"px"; ctx=melt.getContext("2d"); ctx.scale(DPR,DPR); ctx.imageSmoothingEnabled=false; document.getElementById("meltBtn") .onclick=startMelt; document.getElementById("reformBtn").onclick=startReform; requestAnimationFrame(loop); }); // ── PHYSICS & RENDER ───────────────────────────────────────────────── function physics(dt){ const B={attract:0.22,jitter:60,damping:0.85,depthFreq:4,depthAmp:4, dustJitter:55,dustAttract:0.18,wakePush:0.7}; let cX=0,cY=0,vX=0,vY=0,nC=0; particles.forEach(p=>{ if(colouredFamilies.includes(p.fam)){ cX+=p.x; cY+=p.y; vX+=p.vx; vY+=p.vy; nC++; } }); if(nC){cX/=nC;cY/=nC;vX/=nC;vY/=nC;} let settled=0; particles.forEach((p,i)=>{ if(mode==="melt"){ if(colouredFamilies.includes(p.fam)){ const [gx,gy]=geomTarget(p.fam,tGlobal,i); const nX=Math.sin(p.noisePhase+tGlobal*p.noiseSpeed)*p.noiseMag; const nY=Math.cos(p.noisePhase+tGlobal*p.noiseSpeed)*p.noiseMag; const dx=gx+nX-p.x, dy=gy+nY-p.y; p.vx+=dx*B.attract; p.vy+=dy*B.attract; p.vx+=(Math.random()-0.5)*B.jitter*dt; p.vy+=(Math.random()-0.5)*B.jitter*dt; p.zPhase+=dt*B.depthFreq; p.size=1+B.depthAmp*Math.sin(p.zPhase); } else { p.vx+=(Math.random()-0.5)*B.dustJitter*dt; p.vy+=(Math.random()-0.5)*B.dustJitter*dt; if(nC){ p.vx+=(cX-p.x)*B.dustAttract*dt; p.vy+=(cY-p.y)*B.dustAttract*dt; p.vx+=vX*B.wakePush*dt; p.vy+=vY*B.wakePush*dt; } p.zPhase+=dt*B.depthFreq*0.6; p.size=1+0.5*B.depthAmp*Math.sin(p.zPhase); } } else { const dx=p.baseX-p.x, dy=p.baseY-p.y; p.vx+=dx*0.25; p.vy+=dy*0.25; p.size+=(1-p.size)*6*dt; if(Math.abs(dx)+Math.abs(dy)<0.3) settled++; } p.x+=p.vx*dt; p.y+=p.vy*dt; p.vx*=B.damping; p.vy*=B.damping; if(p.x<0){p.x=0;p.vx*=-0.6;} if(p.x>SIZE-1){p.x=SIZE-1;p.vx*=-0.6;} if(p.y<0){p.y=0;p.vy*=-0.6;} if(p.y>SIZE-1){p.y=SIZE-1;p.vy*=-0.4;} }); if(mode==="reform" && settled===particles.length){ active=false; mode="none"; melt.style.display="none"; phase1Frame.style.visibility="visible"; meltBtn.disabled = false; reformBtn.disabled = true; } } function render(){ if(!active){ ctx.clearRect(0,0,SIZE,SIZE); return; } ctx.clearRect(0,0,SIZE,SIZE); particles.forEach(p=>{ // coloured families pulse their hue if(colouredFamilies.includes(p.fam)){ const hueMap={red:0,orange:30,yellow:60,green:120,cyan:180,blue:240,magenta:300}; const H = (hueMap[p.fam] + tGlobal*30) % 360; ctx.fillStyle = `hsla(${H},100%,50%,${p.alpha})`; } else { const [r,g,b] = p.rgb; ctx.fillStyle = `rgba(${r},${g},${b},${p.alpha})`; } const s = Math.max(1,Math.round(p.size)); ctx.fillRect(Math.round(p.x-s/2),Math.round(p.y-s/2),s,s); }); } function loop(ts){ requestAnimationFrame(loop); if(!lastTS) lastTS=ts; let dt=(ts-lastTS)/1000; if(dt>0.05) dt=0.05; lastTS=ts; tGlobal+=dt; if(active) physics(dt); render(); } </script> </body> </html>