One commited on
Commit
8e60635
·
verified ·
1 Parent(s): c3fea4f

Upload index.html

Browse files
Files changed (1) hide show
  1. index.html +55 -0
index.html CHANGED
@@ -139,6 +139,7 @@ const ABS_DUR=1.6,BUD_DUR=2.6; // merge/split transit
139
  function step(dt){
140
  const midY=(LIQ_TOP+LIQ_BOT)/2;
141
  for(const b of blobs){
 
142
  if(b.childOf)continue; // absorbed child is driven below
143
  const t=(b.y-LIQ_TOP)/(LIQ_BOT-LIQ_TOP);
144
  const er=b.r*WAXR; // world radius at current wax amount
@@ -203,6 +204,7 @@ function step(dt){
203
  u.child.r=u.r2*lerp(0.15,1,k);
204
  if(u.t>=1){ // free now - keep them from re-fusing mid-drift
205
  u.child.budding=false;u.child.partner=b;b.partner=u.child;b.bud=null;
 
206
  }
207
  }
208
 
@@ -210,6 +212,10 @@ function step(dt){
210
  for(let i=0;i<blobs.length;i++)for(let j=i+1;j<blobs.length;j++){
211
  const a=blobs[i],c=blobs[j];
212
  if(a.abs||a.childOf||c.abs||c.childOf||a.bud||c.bud||a.budding||c.budding)continue;
 
 
 
 
213
  if((a.partner&&a.partner===c)||(c.partner&&c.partner===a))continue;
214
  const dx=c.x-a.x,dy=c.y-a.y,d=Math.hypot(dx,dy)||0.001,rs=a.r+c.r;
215
  if(d<rs*0.32){
@@ -256,6 +262,18 @@ function step(dt){
256
  const child={x:nx,y:ny,vx:b.vx+Math.cos(ang)*(10+Math.random()*8),vy:-5-Math.random()*6,r:r2*0.15,temp:Math.min(1,b.temp+0.05),seed:Math.random()*1000,budding:true};
257
  blobs.push(child);
258
  b.bud={child,t:0,dur:0.9,rFrom:b.r,rTo:Math.max(18,Math.cbrt(b.r**3-r2**3)),r2};
 
 
 
 
 
 
 
 
 
 
 
 
259
  }
260
  }
261
  }
@@ -437,6 +455,42 @@ function drawSlider(){
437
  ctx.fillStyle=g;ctx.beginPath();ctx.arc(kx,SL.y,r+1.5,0,Math.PI*2);ctx.fill();
438
  ctx.restore();
439
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
440
  function drawLiquid(){
441
  ctx.save();ctx.clip(liquidClip);
442
  const g=ctx.createLinearGradient(0,LIQ_TOP,0,LIQ_BOT);
@@ -552,6 +606,7 @@ function frame(now){
552
  drawGlass();
553
  drawBase();
554
  drawSlider();
 
555
  canvas.style.cursor=(slDrag||slHover)?'ew-resize':(ledHover?'pointer':'default');
556
  }catch(err){
557
  const d=document.createElement('div');
 
139
  function step(dt){
140
  const midY=(LIQ_TOP+LIQ_BOT)/2;
141
  for(const b of blobs){
142
+ if(b.splitAge!==undefined)b.splitAge+=dt;
143
  if(b.childOf)continue; // absorbed child is driven below
144
  const t=(b.y-LIQ_TOP)/(LIQ_BOT-LIQ_TOP);
145
  const er=b.r*WAXR; // world radius at current wax amount
 
204
  u.child.r=u.r2*lerp(0.15,1,k);
205
  if(u.t>=1){ // free now - keep them from re-fusing mid-drift
206
  u.child.budding=false;u.child.partner=b;b.partner=u.child;b.bud=null;
207
+ u.child.splitAge=0; // start the grace period timer
208
  }
209
  }
210
 
 
212
  for(let i=0;i<blobs.length;i++)for(let j=i+1;j<blobs.length;j++){
213
  const a=blobs[i],c=blobs[j];
214
  if(a.abs||a.childOf||c.abs||c.childOf||a.bud||c.bud||a.budding||c.budding)continue;
215
+ // protect newly split-off blobs from immediate absorption (3s grace period)
216
+ if((a.splitAge!==undefined&&a.splitAge<3)||(c.splitAge!==undefined&&c.splitAge<3))continue;
217
+ // don't absorb very small blobs (they're still establishing themselves)
218
+ if(a.r<12||c.r<12)continue;
219
  if((a.partner&&a.partner===c)||(c.partner&&c.partner===a))continue;
220
  const dx=c.x-a.x,dy=c.y-a.y,d=Math.hypot(dx,dy)||0.001,rs=a.r+c.r;
221
  if(d<rs*0.32){
 
262
  const child={x:nx,y:ny,vx:b.vx+Math.cos(ang)*(10+Math.random()*8),vy:-5-Math.random()*6,r:r2*0.15,temp:Math.min(1,b.temp+0.05),seed:Math.random()*1000,budding:true};
263
  blobs.push(child);
264
  b.bud={child,t:0,dur:0.9,rFrom:b.r,rTo:Math.max(18,Math.cbrt(b.r**3-r2**3)),r2};
265
+ // burst of carbonated bubbles at the boil location
266
+ const count=4+Math.floor(Math.random()*5); // 4-8 bubbles for a bigger burst
267
+ for(let i=0;i<count;i++){
268
+ blobBubs.push({
269
+ x:nx+(Math.random()-0.5)*6,
270
+ y:ny+(Math.random()-0.5)*6,
271
+ vy:-(20+Math.random()*18),
272
+ r:0.7+Math.random()*1.8,
273
+ ph:Math.random()*7,
274
+ wob:2+Math.random()*2
275
+ });
276
+ }
277
  }
278
  }
279
  }
 
455
  ctx.fillStyle=g;ctx.beginPath();ctx.arc(kx,SL.y,r+1.5,0,Math.PI*2);ctx.fill();
456
  ctx.restore();
457
  }
458
+
459
+ // film grain overlay - subtle animated noise for that analog feel (larger grains)
460
+ function drawGrain(){
461
+ const cw=canvas.width,ch=canvas.height;
462
+ const imgData=ctx.getImageData(0,0,cw,ch);
463
+ const data=imgData.data;
464
+ const grainSize=3*DPR; // scale grain size with DPR
465
+ // precompute blob positions in canvas coordinates for wax detection
466
+ const ox=CX-MAX_HW-pad,oy=LIQ_TOP-pad;
467
+ const baseIntensity=2.25; // very subtle grain intensity (reduced 25% more)
468
+ const waxMultiplier=2.25; // 125% stronger where wax is (another 50% on top)
469
+ for(let y=0;y<ch;y+=grainSize){
470
+ for(let x=0;x<cw;x+=grainSize){
471
+ // check if this grain cell overlaps any blob (wax mask)
472
+ let onWax=false;
473
+ const bufX=CX-MAX_HW-pad,bufY=LIQ_TOP-pad; // where wax buffer is drawn
474
+ const wx=(x/DPR)-bufX,wy=(y/DPR)-bufY; // convert to world coords
475
+ for(const b of blobs){
476
+ const bx=b.x-ox,by=b.y-oy;
477
+ const rr=b.r*WAXR*1.12;
478
+ if(Math.hypot(wx-bx,wy-by)<rr){onWax=true;break;}
479
+ }
480
+ const intensity=onWax?baseIntensity*waxMultiplier:baseIntensity;
481
+ const n=(Math.random()-0.5)*intensity;
482
+ for(let gy=0;gy<grainSize&&y+gy<ch;gy++){
483
+ for(let gx=0;gx<grainSize&&x+gx<cw;gx++){
484
+ const idx=((Math.floor(y+gy)*cw+Math.floor(x+gx))*4);
485
+ data[idx]=Math.max(0,Math.min(255,data[idx]+n)); // R
486
+ data[idx+1]=Math.max(0,Math.min(255,data[idx+1]+n)); // G
487
+ data[idx+2]=Math.max(0,Math.min(255,data[idx+2]+n)); // B
488
+ }
489
+ }
490
+ }
491
+ }
492
+ ctx.putImageData(imgData,0,0);
493
+ }
494
  function drawLiquid(){
495
  ctx.save();ctx.clip(liquidClip);
496
  const g=ctx.createLinearGradient(0,LIQ_TOP,0,LIQ_BOT);
 
606
  drawGlass();
607
  drawBase();
608
  drawSlider();
609
+ drawGrain();
610
  canvas.style.cursor=(slDrag||slHover)?'ew-resize':(ledHover?'pointer':'default');
611
  }catch(err){
612
  const d=document.createElement('div');