NP@naman-parashar-s-team / sand-cardPublic
sand-card
A JS snippet on QuikRun, callable at quik.run/r/sand-card.
JS· node:20· 20 runs· 0 forks· updated 24m ago
sand-card.js
// sand-card — a premium "buried metal card" reveal.// WebGL2: shader sand (FBM dunes + golden glint), a persistent dig mask so the// reveal STAYS (real excavation, no spring-back), a GPU particle spray at the dig// front, and a bloom post-pass. The whole experience is the served HTML below.//// The bound import only selects ES-module mode in the sandbox (needed for the// default export); the page itself pulls in no libraries.import htm from "htm";void htm;const HTML = `<!doctype html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Buried — premium metal card</title><style>*{box-sizing:border-box}html,body{margin:0;height:100%}body{background:radial-gradient(120% 90% at 50% 20%, #0f0c07 0%, #030303 72%);color:#b9ad92;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:22px;overflow:hidden}.wrap{position:relative;width:600px;height:600px;border-radius:24px}canvas{display:block;width:600px;height:600px;border-radius:24px;cursor:crosshair;touch-action:none}.hint{font-size:11px;letter-spacing:.34em;text-transform:uppercase;color:#6a6048;opacity:.9}.hint b{color:#c9a24b;font-weight:600}</style></head><body><div class="wrap"><canvas id="gl"></canvas></div><div class="hint">drag to unearth · <b>double-click</b> to rebury</div><script>(function(){"use strict";var CSS = 600, DPR = Math.min(window.devicePixelRatio || 1, 2);var W = Math.round(CSS * DPR), H = Math.round(CSS * DPR);var canvas = document.getElementById("gl");canvas.width = W; canvas.height = H;var gl = canvas.getContext("webgl2", { alpha:false, antialias:false, premultipliedAlpha:false });if(!gl){ document.body.innerHTML = "<p style='color:#c9a24b;font-family:monospace'>WebGL2 unavailable</p>"; return; }var extF = gl.getExtension("EXT_color_buffer_float");/* ---------- premium metal credit card, drawn to a 2D texture ---------- */function makeCardTexture(){var s = 2, cw = CSS*s, ch = CSS*s;var cv = document.createElement("canvas"); cv.width = cw; cv.height = ch;var c = cv.getContext("2d");// scene backdrop (so bloom + edges sit on something)var bg = c.createRadialGradient(cw*0.5, ch*0.32, 0, cw*0.5, ch*0.5, cw*0.72);bg.addColorStop(0, "#0c0905"); bg.addColorStop(1, "#040302");c.fillStyle = bg; c.fillRect(0,0,cw,ch);// card geometry (landscape, ISO ~1.586:1)var cardW = 404*s, cardH = 255*s, x = (cw-cardW)/2, y = (ch-cardH)/2, r = 22*s;function rr(x,y,w,h,rad){ c.beginPath(); c.moveTo(x+rad,y); c.arcTo(x+w,y,x+w,y+h,rad); c.arcTo(x+w,y+h,x,y+h,rad); c.arcTo(x,y+h,x,y,rad); c.arcTo(x,y,x+w,y,rad); c.closePath(); }// drop shadow (card sits proud of the sand -> casts down)c.save(); c.shadowColor="rgba(0,0,0,0.8)"; c.shadowBlur=54*s; c.shadowOffsetY=30*s;rr(x,y,cardW,cardH,r); c.fillStyle="#000"; c.fill(); c.restore();// card thickness: a metal slab offset down-right peeks out as the 3D edgevar eg=c.createLinearGradient(x,y+cardH,x+cardW,y+cardH+16*s);eg.addColorStop(0,"#3a342a"); eg.addColorStop(0.5,"#14110c"); eg.addColorStop(1,"#050505");rr(x+3*s, y+9*s, cardW, cardH, r); c.fillStyle=eg; c.fill();c.save(); rr(x,y,cardW,cardH,r); c.clip();// base obsidian metalvar g = c.createLinearGradient(x,y,x+cardW,y+cardH);g.addColorStop(0,"#1a1712"); g.addColorStop(0.45,"#0c0b09"); g.addColorStop(0.55,"#0a0a0c"); g.addColorStop(1,"#161310");c.fillStyle=g; c.fillRect(x,y,cardW,cardH);// warm top sheenvar sh = c.createRadialGradient(x+cardW*0.3,y,0,x+cardW*0.3,y, cardH*1.4);sh.addColorStop(0,"rgba(201,162,75,0.10)"); sh.addColorStop(1,"rgba(0,0,0,0)");c.fillStyle=sh; c.fillRect(x,y,cardW,cardH);// brushed-metal microlinesc.save(); c.globalAlpha=0.05;for(var i=0;i<420;i++){ c.strokeStyle = i%2? "#c9a24b":"#000"; c.lineWidth=0.6*s;var yy=y+Math.random()*cardH; c.beginPath(); c.moveTo(x,yy); c.lineTo(x+cardW,yy+ (Math.random()-0.5)*2*s); c.stroke(); }c.restore();// guilloché engine-turned rosette (the premium security signature)c.save(); c.translate(x+cardW*0.5, y+cardH*0.5); c.globalAlpha=0.16; c.lineWidth=0.5*s;c.strokeStyle="#c9a24b";for(var ring=0; ring<5; ring++){var baseR = (58 + ring*17)*s, amp=(9+ring*1.4)*s, k=7+ring;c.beginPath();for(var a=0; a<=6.2832; a+=0.02){var rad = baseR + amp*Math.cos(k*a) + amp*0.4*Math.sin((k+3)*a);var px = Math.cos(a)*rad*1.5, py=Math.sin(a)*rad;if(a===0) c.moveTo(px,py); else c.lineTo(px,py);}c.closePath(); c.stroke();}c.restore();// EMV chipvar chx=x+40*s, chy=y+96*s, chw=58*s, chh=44*s;var cg=c.createLinearGradient(chx,chy,chx+chw,chy+chh);cg.addColorStop(0,"#f4e2a8"); cg.addColorStop(0.5,"#c9a24b"); cg.addColorStop(1,"#8a6a2a");rr(chx,chy,chw,chh,7*s); c.fillStyle=cg; c.fill();c.strokeStyle="rgba(60,40,10,0.6)"; c.lineWidth=1*s;// chip contactsc.beginPath();c.moveTo(chx+chw*0.33,chy); c.lineTo(chx+chw*0.33,chy+chh);c.moveTo(chx+chw*0.66,chy); c.lineTo(chx+chw*0.66,chy+chh);c.moveTo(chx,chy+chh*0.5); c.lineTo(chx+chw,chy+chh*0.5);c.moveTo(chx+chw*0.15,chy+chh*0.28); c.lineTo(chx+chw*0.85,chy+chh*0.28);c.moveTo(chx+chw*0.15,chy+chh*0.72); c.lineTo(chx+chw*0.85,chy+chh*0.72);c.stroke();// contactless glyphc.save(); c.translate(chx+chw+26*s, chy+chh*0.5); c.strokeStyle="#c9a24b"; c.lineWidth=2.4*s; c.lineCap="round";for(var w=0; w<3; w++){ c.beginPath(); c.arc(0,0,(8+w*7)*s, -0.7, 0.7); c.stroke(); }c.restore();// holographic patchvar hx=x+cardW-92*s, hy=y+92*s, hw=52*s, hh=40*s;var holo=c.createLinearGradient(hx,hy,hx+hw,hy+hh);holo.addColorStop(0,"rgba(120,220,200,0.5)"); holo.addColorStop(0.4,"rgba(220,180,120,0.5)");holo.addColorStop(0.7,"rgba(180,140,220,0.5)"); holo.addColorStop(1,"rgba(120,200,220,0.5)");rr(hx,hy,hw,hh,8*s); c.fillStyle=holo; c.fill();c.globalAlpha=0.5; c.strokeStyle="#efe3c0"; c.lineWidth=1*s; rr(hx,hy,hw,hh,8*s); c.stroke(); c.globalAlpha=1;// brand wordmarkc.textBaseline="alphabetic";c.fillStyle="#e7d6a6"; c.font= (26*s)+"px Georgia, 'Times New Roman', serif";c.save(); c.setTransform(1,0,0,1,0,0);c.fillStyle="#e7d6a6"; c.font= "700 "+(24*s)+"px Georgia, serif";c.fillText("AUREUS", x+40*s, y+56*s);c.font= (10*s)+"px ui-monospace, monospace"; c.fillStyle="#9c8a5e";c.fillText("METAL · PRIVATE", x+40*s, y+72*s);c.restore();// embossed helperfunction emboss(txt, ex, ey, font, spacing){c.font=font; c.textBaseline="alphabetic";var cx=ex;for(var i2=0;i2<txt.length;i2++){var ch2=txt[i2];c.fillStyle="rgba(0,0,0,0.55)"; c.fillText(ch2, cx+1.5*s, ey+1.8*s);var lg=c.createLinearGradient(cx,ey-18*s,cx,ey+4*s);lg.addColorStop(0,"#f6e7b6"); lg.addColorStop(1,"#b89451");c.fillStyle=lg; c.fillText(ch2, cx, ey);cx += c.measureText(ch2).width + spacing;}}// PANemboss("5412 7508 9021 1808", x+40*s, y+cardH-70*s, "600 "+(21.5*s)+"px ui-monospace, Menlo, monospace", 0.3*s);// labelsc.fillStyle="#7d7150"; c.font=(8.5*s)+"px ui-monospace, monospace";c.fillText("CARD HOLDER", x+40*s, y+cardH-44*s);c.fillText("VALID THRU", x+cardW-150*s, y+cardH-44*s);// name + date embossedemboss("R. MALHOTRA", x+40*s, y+cardH-24*s, "600 "+(15*s)+"px ui-monospace, monospace", 0.4*s);emboss("08/29", x+cardW-150*s, y+cardH-24*s, "600 "+(15*s)+"px ui-monospace, monospace", 0.4*s);// network mark (generic gold dual-disc)var nx=x+cardW-58*s, ny=y+cardH-40*s;c.globalAlpha=0.95;c.fillStyle="#d9a24b"; c.beginPath(); c.arc(nx,ny,16*s,0,6.2832); c.fill();c.fillStyle="#f0d488"; c.beginPath(); c.arc(nx+16*s,ny,16*s,0,6.2832); c.fill();c.globalCompositeOperation="overlay"; c.fillStyle="#8a6a2a";c.beginPath(); c.arc(nx+8*s,ny,16*s,0,6.2832); c.fill(); c.globalCompositeOperation="source-over"; c.globalAlpha=1;// beveled rim: light on top-left, dark on bottom-right => rounded metal edgec.lineWidth=1.8*s;c.strokeStyle="rgba(246,231,182,0.55)"; rr(x+1.4*s,y+1.4*s,cardW-2.8*s,cardH-2.8*s,r); c.stroke();var bevg=c.createLinearGradient(x,y,x+cardW,y+cardH);bevg.addColorStop(0,"rgba(255,244,210,0.0)"); bevg.addColorStop(0.55,"rgba(0,0,0,0.0)"); bevg.addColorStop(1,"rgba(0,0,0,0.55)");c.lineWidth=2.6*s; c.strokeStyle=bevg; rr(x+2.6*s,y+2.6*s,cardW-5.2*s,cardH-5.2*s,r); c.stroke();c.restore();var tex = gl.createTexture();gl.bindTexture(gl.TEXTURE_2D, tex);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);gl.texImage2D(gl.TEXTURE_2D,0,gl.RGBA,gl.RGBA,gl.UNSIGNED_BYTE,cv);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);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);gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);return tex;}/* ---------- GL helpers ---------- */function sh(type, src){ var s=gl.createShader(type); gl.shaderSource(s,src); gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s)+"\\n"+src); return s; }function prog(vs,fs){ var p=gl.createProgram(); gl.attachShader(p,sh(gl.VERTEX_SHADER,vs)); gl.attachShader(p,sh(gl.FRAGMENT_SHADER,fs));gl.linkProgram(p); if(!gl.getProgramParameter(p,gl.LINK_STATUS)) throw new Error(gl.getProgramInfoLog(p)); return p; }function fbo(w,h,float){ var t=gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D,t);var it = (float&&extF)? gl.RGBA16F: gl.RGBA, tp=(float&&extF)? gl.HALF_FLOAT: gl.UNSIGNED_BYTE;gl.texImage2D(gl.TEXTURE_2D,0,it,w,h,0,gl.RGBA,tp,null);gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.LINEAR);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);var f=gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER,f);gl.framebufferTexture2D(gl.FRAMEBUFFER,gl.COLOR_ATTACHMENT0,gl.TEXTURE_2D,t,0);gl.bindFramebuffer(gl.FRAMEBUFFER,null); return {f:f,t:t,w:w,h:h}; }var QVS = "#version 300 es\\nin vec2 p; out vec2 vUv; void main(){ vUv=p*0.5+0.5; gl_Position=vec4(p,0.,1.); }";var quad = gl.createVertexArray(); gl.bindVertexArray(quad);var qb=gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER,qb);gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1, 3,-1, -1,3]),gl.STATIC_DRAW);gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0,2,gl.FLOAT,false,0,0); gl.bindVertexArray(null);function drawQuad(){ gl.bindVertexArray(quad); gl.drawArrays(gl.TRIANGLES,0,3); gl.bindVertexArray(null); }/* ---------- noise (shared GLSL) ---------- */var NOISE = ["vec2 hash2(vec2 p){ p=vec2(dot(p,vec2(127.1,311.7)),dot(p,vec2(269.5,183.3))); return fract(sin(p)*43758.5453); }","float hash1(vec2 p){ return fract(sin(dot(p,vec2(127.1,311.7)))*43758.5453); }","float vnoise(vec2 p){ vec2 i=floor(p),f=fract(p); vec2 u=f*f*(3.-2.*f);"," float a=hash1(i),b=hash1(i+vec2(1,0)),c=hash1(i+vec2(0,1)),d=hash1(i+vec2(1,1));"," return mix(mix(a,b,u.x),mix(c,d,u.x),u.y); }","float fbm(vec2 p){ float v=0.,a=.5; for(int i=0;i<5;i++){ v+=a*vnoise(p); p*=2.03; a*=.5; } return v; }"].join("\\n");/* ---------- programs ---------- */var digProg = prog(QVS, "#version 300 es\\nprecision highp float;\\nin vec2 vUv; out vec4 o;\\n"+"uniform sampler2D uPrev; uniform vec2 uCur; uniform float uR; uniform float uStr; uniform float uAsp;\\n"+"void main(){ float prev=texture(uPrev,vUv).r;\\n"+" vec2 d=(vUv-uCur)*vec2(uAsp,1.0); float dist=length(d);\\n"+" float brush=smoothstep(uR,0.0,dist)*uStr;\\n"+" o=vec4(clamp(prev+brush,0.0,1.3),0.,0.,1.); }");var sceneProg = prog(QVS, "#version 300 es\\nprecision highp float;\\nin vec2 vUv; out vec4 o;\\n"+"uniform sampler2D uCard; uniform sampler2D uMask; uniform float uT; uniform float uAsp; uniform vec2 uPx; uniform vec2 uMouse;\\n"+ NOISE + "\\n"+"const vec3 GOLD_HOT=vec3(1.0,0.86,0.56);\\n"+// sand height: broad dunes + wind ripples + fine grain — this gives it FORM"float dune(vec2 uv){ vec2 sp=uv*vec2(uAsp,1.0);\\n"+" float d=fbm(sp*2.6);\\n"+" float rip=0.5+0.5*sin(sp.x*28.0 + sp.y*6.0 + fbm(sp*2.6)*5.0);\\n"+" float fine=vnoise(sp*115.0);\\n"+" return d*0.68 + rip*0.075 + fine*0.045; }\\n"+// effective surface = sand minus how much has been dug away (persistent)"float field(vec2 uv){ return dune(uv)*0.5 - texture(uMask,uv).r*0.95; }\\n"+"void main(){ vec2 uv=vUv;\\n"+// surface normal from the height field -> lights dunes, ripples AND the dig slopes" float hL=field(uv-vec2(uPx.x,0.0)), hR=field(uv+vec2(uPx.x,0.0));\\n"+" float hD=field(uv-vec2(0.0,uPx.y)), hU=field(uv+vec2(0.0,uPx.y));\\n"+" vec3 N=normalize(vec3((hL-hR)*185.0,(hD-hU)*185.0, 1.0));\\n"+" vec3 L=normalize(vec3(-0.40,0.52,0.66));\\n"+" float diff=clamp(dot(N,L),0.0,1.0);\\n"+" float dug=texture(uMask,uv).r;\\n"+" float remain=dune(uv)*0.5 - dug*0.95 + 0.30;\\n"+ // >0 sand present, <0 card" float sandMask=smoothstep(0.0,0.05,remain);\\n"+" vec3 shadow=vec3(0.14,0.100,0.038), lit=vec3(0.92,0.70,0.30);\\n"+" vec3 sand=mix(shadow,lit, 0.36 + diff*0.80);\\n"+" sand+=(vnoise(uv*940.0)-0.5)*0.04;\\n"+ // per-grain speckle// sparse specular sparkle only where a grain faces the light" vec3 Hh=normalize(L+vec3(0.0,0.0,1.0)); float spec=pow(clamp(dot(N,Hh),0.0,1.0),48.0);\\n"+" float fleck=step(0.93, hash1(floor(uv*vec2(660.0,660.0))));\\n"+" float tw=0.6+0.4*sin(uT*2.3 + floor(uv.x*660.0)*1.7);\\n"+" sand+=GOLD_HOT*spec*fleck*tw*1.1;\\n"+// card region (rounded-rect SDF) — used for parallax + metal sheen so it reads 3D" vec2 cc=uv-vec2(0.5); vec2 hf=vec2(0.338,0.213); float crn=0.05;\\n"+" vec2 qd=abs(cc)-hf+crn; float sd=length(max(qd,vec2(0.0)))+min(max(qd.x,qd.y),0.0)-crn;\\n"+" float cardArea=smoothstep(0.006,-0.006,sd);\\n"+// parallax: surface shifts with the cursor as if the slab tilts" vec2 cuv=uv + (uMouse-0.5)*0.013*cardArea;\\n"+" vec3 card=texture(uCard,cuv).rgb;\\n"+" float ao=smoothstep(0.13,0.0,remain)*smoothstep(-0.13,0.0,remain);\\n"+ // contact shadow at the sand wall" card*=mix(1.0,0.5,ao);\\n"+" float rev=1.0-sandMask;\\n"+// mouse-tilt specular sheen — a bright band sweeps the metal as you move" vec2 md=uMouse-0.5;\\n"+" float axis=dot(uv-0.5, normalize(vec2(0.82,-0.58)));\\n"+" float sheen=exp(-pow((axis + md.x*0.6 - md.y*0.34)*6.5,2.0));\\n"+" float brushed=0.72+0.28*sin(uv.y*520.0);\\n"+" card+=vec3(1.0,0.94,0.74)*sheen*brushed*cardArea*rev*0.6;\\n"+" vec3 col=mix(card,sand,sandMask);\\n"+" o=vec4(col,1.0); }");var brightProg = prog(QVS, "#version 300 es\\nprecision highp float;\\nin vec2 vUv; out vec4 o;\\n"+"uniform sampler2D uT0; uniform float uThresh;\\n"+"void main(){ vec3 c=texture(uT0,vUv).rgb; float l=dot(c,vec3(0.299,0.587,0.114));\\n"+" float k=max(l-uThresh,0.0)/max(l,0.0001); o=vec4(c*k*smoothstep(0.0,0.3,l),1.); }");var blurProg = prog(QVS, "#version 300 es\\nprecision highp float;\\nin vec2 vUv; out vec4 o;\\n"+"uniform sampler2D uT0; uniform vec2 uDir;\\n"+"void main(){ vec3 s=vec3(0.0); float w[5]=float[](0.2270,0.1946,0.1216,0.0540,0.0162);\\n"+" s+=texture(uT0,vUv).rgb*w[0];\\n"+" for(int i=1;i<5;i++){ s+=texture(uT0,vUv+uDir*float(i)).rgb*w[i]; s+=texture(uT0,vUv-uDir*float(i)).rgb*w[i]; }\\n"+" o=vec4(s,1.); }");var compProg = prog(QVS, "#version 300 es\\nprecision highp float;\\nin vec2 vUv; out vec4 o;\\n"+"uniform sampler2D uScene; uniform sampler2D uBloom; uniform float uBloomI;\\n"+ NOISE + "\\n"+"void main(){ vec3 c=texture(uScene,vUv).rgb; vec3 b=texture(uBloom,vUv).rgb;\\n"+" c+=b*uBloomI;\\n"+" float v=smoothstep(1.15,0.35,length(vUv-0.5)); c*=mix(0.72,1.0,v);\\n"+ // vignette" c+= (hash1(vUv*vec2(1920.0,1080.0))-0.5)*0.02;\\n"+ // dither" c=pow(c, vec3(0.95));\\n"+" o=vec4(c,1.); }");// particles (soft additive gold points)var PMAX=2400;var pPos=new Float32Array(PMAX*2), pVel=new Float32Array(PMAX*2), pLife=new Float32Array(PMAX);var pData=new Float32Array(PMAX*3); // x,y,life for buffervar pHead=0;var pBuf=gl.createBuffer();var pVao=gl.createVertexArray(); gl.bindVertexArray(pVao);gl.bindBuffer(gl.ARRAY_BUFFER,pBuf); gl.bufferData(gl.ARRAY_BUFFER,pData.byteLength,gl.DYNAMIC_DRAW);gl.enableVertexAttribArray(0); gl.vertexAttribPointer(0,3,gl.FLOAT,false,0,0); gl.bindVertexArray(null);var partProg = prog("#version 300 es\\nin vec3 p; out float vL; uniform float uDpr;\\n"+"void main(){ vL=p.z; gl_Position=vec4(p.xy*2.0-1.0,0.,1.); gl_PointSize=mix(1.0,3.6,p.z)*uDpr; }","#version 300 es\\nprecision highp float; in float vL; out vec4 o;\\n"+"void main(){ vec2 d=gl_PointCoord-0.5; float m=smoothstep(0.5,0.0,length(d));\\n"+" vec3 col=mix(vec3(0.42,0.30,0.13),vec3(0.95,0.78,0.45),vL);\\n"+" o=vec4(col*m*vL*0.7, m*vL*0.8); }");function spawn(ux,uy,vx,vy){for(var n=0;n<3;n++){ var i=pHead; pHead=(pHead+1)%PMAX;pPos[i*2]=ux; pPos[i*2+1]=uy;var ang=Math.random()*6.283, spd=0.0015+Math.random()*0.004;pVel[i*2]=Math.cos(ang)*spd + vx*0.35; pVel[i*2+1]=Math.sin(ang)*spd + vy*0.35 + 0.001;pLife[i]=0.7+Math.random()*0.3;}}function stepParticles(dt){for(var i=0;i<PMAX;i++){ if(pLife[i]<=0){ pData[i*3+2]=0; continue; }pVel[i*2+1]-=0.00006; // gravity (uv space, y up)pVel[i*2]*=0.96; pVel[i*2+1]*=0.96;pPos[i*2]+=pVel[i*2]; pPos[i*2+1]+=pVel[i*2+1];pLife[i]-=dt*1.1;pData[i*3]=pPos[i*2]; pData[i*3+1]=pPos[i*2+1]; pData[i*3+2]=Math.max(pLife[i],0);}gl.bindBuffer(gl.ARRAY_BUFFER,pBuf); gl.bufferSubData(gl.ARRAY_BUFFER,0,pData);}/* ---------- targets ---------- */var cardTex = makeCardTexture();var maskA=fbo(W,H,false), maskB=fbo(W,H,false);var scene=fbo(W,H,true);var bw=Math.round(W/2), bh=Math.round(H/2);var bloomA=fbo(bw,bh,true), bloomB=fbo(bw,bh,true);// clear masks to 0[maskA,maskB].forEach(function(m){ gl.bindFramebuffer(gl.FRAMEBUFFER,m.f); gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); });gl.bindFramebuffer(gl.FRAMEBUFFER,null);var ASP = W/H;var mouse={x:0.5,y:0.5,px:0.5,py:0.5,on:false,mv:0};function setM(e){ var b=canvas.getBoundingClientRect(); var t=e.touches?e.touches[0]:e;var nx=(t.clientX-b.left)/b.width, ny=1.0-(t.clientY-b.top)/b.height;mouse.px=mouse.x; mouse.py=mouse.y; mouse.x=nx; mouse.y=ny; mouse.on=true;mouse.mv=Math.min(1.0, Math.hypot(nx-mouse.px,ny-mouse.py)*22.0); }canvas.addEventListener("mousemove",setM);canvas.addEventListener("touchmove",function(e){setM(e);e.preventDefault();},{passive:false});canvas.addEventListener("mouseleave",function(){mouse.on=false;});canvas.addEventListener("dblclick",function(){ [maskA,maskB].forEach(function(m){ gl.bindFramebuffer(gl.FRAMEBUFFER,m.f); gl.clearColor(0,0,0,1); gl.clear(gl.COLOR_BUFFER_BIT); }); gl.bindFramebuffer(gl.FRAMEBUFFER,null); });function u(p,n){ return gl.getUniformLocation(p,n); }var last=performance.now();function frame(now){var dt=Math.min((now-last)/1000,0.05); last=now;// 1) dig into mask (persistent) — only when movingvar str = mouse.on ? (0.10 + mouse.mv*0.65) : 0.0;if(str>0.001){gl.bindFramebuffer(gl.FRAMEBUFFER,maskB.f); gl.viewport(0,0,W,H); gl.useProgram(digProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,maskA.t); gl.uniform1i(u(digProg,"uPrev"),0);gl.uniform2f(u(digProg,"uCur"),mouse.x,mouse.y); gl.uniform1f(u(digProg,"uR"),0.075);gl.uniform1f(u(digProg,"uStr"),str*0.5); gl.uniform1f(u(digProg,"uAsp"),ASP);drawQuad();var tmp=maskA; maskA=maskB; maskB=tmp;// spawn particles along the digspawn(mouse.x,mouse.y,(mouse.x-mouse.px),(mouse.y-mouse.py));}mouse.mv*=0.6;stepParticles(dt);// 2) scenegl.bindFramebuffer(gl.FRAMEBUFFER,scene.f); gl.viewport(0,0,W,H); gl.useProgram(sceneProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,cardTex); gl.uniform1i(u(sceneProg,"uCard"),0);gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,maskA.t); gl.uniform1i(u(sceneProg,"uMask"),1);gl.uniform1f(u(sceneProg,"uT"),now/1000); gl.uniform1f(u(sceneProg,"uAsp"),ASP);gl.uniform2f(u(sceneProg,"uPx"),2.2/W,2.2/H);gl.uniform2f(u(sceneProg,"uMouse"),mouse.x,mouse.y);drawQuad();// 2b) particles additively into scenegl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA,gl.ONE);gl.useProgram(partProg); gl.uniform1f(u(partProg,"uDpr"),DPR);gl.bindVertexArray(pVao); gl.drawArrays(gl.POINTS,0,PMAX); gl.bindVertexArray(null);gl.disable(gl.BLEND);// 3) bloom bright-pass -> half resgl.bindFramebuffer(gl.FRAMEBUFFER,bloomA.f); gl.viewport(0,0,bw,bh); gl.useProgram(brightProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,scene.t); gl.uniform1i(u(brightProg,"uT0"),0);gl.uniform1f(u(brightProg,"uThresh"),0.82); drawQuad();// blur iterationsvar src=bloomA,dst=bloomB;for(var it=0; it<3; it++){gl.bindFramebuffer(gl.FRAMEBUFFER,dst.f); gl.viewport(0,0,bw,bh); gl.useProgram(blurProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,src.t); gl.uniform1i(u(blurProg,"uT0"),0);gl.uniform2f(u(blurProg,"uDir"),1.4/bw,0.0); drawQuad();var t1=src;src=dst;dst=t1;gl.bindFramebuffer(gl.FRAMEBUFFER,dst.f); gl.viewport(0,0,bw,bh); gl.useProgram(blurProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,src.t); gl.uniform1i(u(blurProg,"uT0"),0);gl.uniform2f(u(blurProg,"uDir"),0.0,1.4/bh); drawQuad();var t2=src;src=dst;dst=t2;}// 4) composite to screengl.bindFramebuffer(gl.FRAMEBUFFER,null); gl.viewport(0,0,W,H); gl.useProgram(compProg);gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D,scene.t); gl.uniform1i(u(compProg,"uScene"),0);gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D,src.t); gl.uniform1i(u(compProg,"uBloom"),1);gl.uniform1f(u(compProg,"uBloomI"),0.5); drawQuad();window.__ready=true;requestAnimationFrame(frame);}requestAnimationFrame(frame);})();</script></body></html>`;export default async function handler() {return new Response(HTML, {status: 200,headers: { "content-type": "text/html; charset=utf-8" },});}
Fork this and make it yours
Describe a change in plain English and QuikRun deploys it to your own URL.
Built on QuikRun by @naman-parashar-s-team · plain English in, live URL out