{
  "choices": [
    {
      "finish_reason": "stop",
      "index": 0,
      "message": {
        "role": "assistant",
        "content": "```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Volcano Eruption Physics</title>\n<style>\n  html,body{margin:0;padding:0;background:#000;overflow:hidden;font-family:Consolas,monospace;}\n  canvas{display:block;}\n  #panel{position:fixed;top:10px;left:10px;background:rgba(10,10,20,.65);border:1px solid #555;\n         border-radius:8px;padding:10px 14px;color:#eee;z-index:10;width:230px;backdrop-filter:blur(3px);}\n  #panel label{display:block;font-size:12px;margin-top:8px;color:#fca;}\n  #panel label span{float:right;color:#fff;}\n  #panel input[type=range]{width:100%;accent-color:#f60;}\n  #counter{position:fixed;top:10px;right:10px;background:rgba(10,10,20,.65);border:1px solid #555;\n           border-radius:8px;padding:8px 12px;color:#8f8;font-size:13px;z-index:10;line-height:1.5;}\n  #meterWrap{position:fixed;right:30px;bottom:14px;width:26px;height:180px;background:rgba(10,10,20,.6);\n             border:1px solid #666;border-radius:6px;z-index:10;}\n  #meterFill{position:absolute;left:2px;right:2px;bottom:2px;height:0%;border-radius:4px;\n             background:linear-gradient(to top,#a30,#fa0,#ff6);transition:height .1s;}\n  #meterLabel{position:fixed;right:14px;bottom:200px;color:#fca;font-size:11px;z-index:10;width:58px;text-align:center;}\n  h3{margin:0;font-size:14px;color:#ff8;}\n</style>\n</head>\n<body>\n<canvas id=\"cv\"></canvas>\n\n<div id=\"panel\">\n  <h3>\ud83c\udf0b Volcano</h3>\n  <label>Eruption power <span id=\"vPower\">1.0</span>\n    <input id=\"sPower\" type=\"range\" min=\"0.2\" max=\"2\" step=\"0.05\" value=\"1\"></label>\n  <label>Wind (L\u2190 \u2192R) <span id=\"vWind\">0</span>\n    <input id=\"sWind\" type=\"range\" min=\"-100\" max=\"100\" step=\"1\" value=\"0\"></label>\n  <label>Particle amount <span id=\"vAmt\">1.0</span>\n    <input id=\"sAmt\" type=\"range\" min=\"0.2\" max=\"4\" step=\"0.1\" value=\"1\"></label>\n</div>\n\n<div id=\"counter\">particles: 0<br>fps: 0</div>\n<div id=\"meterWrap\"><div id=\"meterFill\"></div></div>\n<div id=\"meterLabel\">PRESSURE</div>\n\n<script>\n(function(){\n\"use strict\";\nvar cv=document.getElementById('cv'),ctx=cv.getContext('2d');\nvar W=0,Hh=0;\n\n// ---------- controls ----------\nvar sP=document.getElementById('sPower'),sW=document.getElementById('sWind'),sA=document.getElementById('sAmt');\nvar vP=document.getElementById('vPower'),vW=document.getElementById('vWind'),vA=document.getElementById('vAmt');\nvar power=1,wind=0,amount=1;\nfunction readControls(){\n  power=+sP.value; wind=(+sW.value)/100; amount=+sA.value;\n  vP.textContent=power.toFixed(1); vW.textContent=sW.value; vA.textContent=amount.toFixed(1);\n}\nsP.addEventListener('input',readControls);\nsW.addEventListener('input',readControls);\nsA.addEventListener('input',readControls);\nreadControls();\n\n// ---------- terrain height field ----------\nvar terrain=null,GROUND=0,PEAKY=0,CX=0,CRA=0,DEPTH=0;\nfunction buildTerrain(){\n  terrain=new Float32Array(W+2);\n  GROUND=Hh*0.80;\n  PEAKY=Hh*0.28;\n  CX=W*0.46;\n  CRA=Math.max(22,W*0.030);\n  DEPTH=Math.max(14,Hh*0.035);\n  var baseW=W*0.34;\n  for(var x=0;x<=W+1;x++){\n    var d=Math.abs(x-CX),y;\n    if(d<=CRA){\n      var t=d/CRA;\n      y=PEAKY+DEPTH*(1-t*t);\n    }else if(d<=baseW){\n      var t2=(d-CRA)/(baseW-CRA);\n      var t2c=t2*t2*(3-2*t2);\n      y=PEAKY+DEPTH+t2c*(GROUND-(PEAKY+DEPTH));\n    }else{\n      y=GROUND-Math.max(0,8-(d-baseW)*0.15);\n      y=GROUND;\n    }\n    terrain[x]=y;\n  }\n}\nfunction tY(x){\n  var i=x|0;\n  if(i<0)i=0; if(i>W)i=W;\n  return terrain[i];\n}\nfunction slopeAt(x){\n  var i=x|0; if(i<1)i=1; if(i>W-2)i=W-2;\n  return (terrain[i+2]-terrain[i-2])*0.25;\n}\n\n// ---------- resize ----------\nfunction resize(){\n  W=window.innerWidth; Hh=window.innerHeight;\n  cv.width=W; cv.height=Hh;\n  buildTerrain();\n}\nwindow.addEventListener('resize',resize);\nresize();\n\n// ---------- particles ----------\nvar lava=[],ash=[];\nvar MAX_LAVA=9000;\nvar GRAV=620;\n\nfunction spawnLava(n,pow){\n  for(var i=0;i<n;i++){\n    var spread=CRA*0.9;\n    var a={\n      x:CX+(Math.random()*2-1)*spread,\n      y:PEAKY+DEPTH*0.4,\n      vx:(Math.random()*2-1)*260*pow,\n      vy:-(280+Math.random()*520)*pow,\n      r:2.2+Math.random()*4.5,\n      heat:1,\n      settled:false,\n      va:0\n    };\n    // convert angle velocity model: store along-slope speed when settled\n    a.vx*=0.6+Math.random()*0.6;\n    a.vy*=0.6+Math.random()*0.6;\n    lava.push(a);\n  }\n  if(lava.length>MAX_LAVA){ lava.splice(0,lava.length-MAX_LAVA); }\n}\n\nfunction spawnAsh(n,rush){\n  for(var i=0;i<n;i++){\n    ash.push({\n      x:CX+(Math.random()*2-1)*CRA*0.8,\n      y:PEAKY+DEPTH*0.3,\n      vx:(Math.random()*2-1)*40,\n      vy:-(50+Math.random()*110)*(rush?1.4:1),\n      r:8+Math.random()*16,\n      a:0.35+Math.random()*0.3,\n      life:1,\n      wob:Math.random()*Math.PI*2\n    });\n  }\n  if(ash.length>3500){ ash.splice(0,ash.length-3500); }\n}\n\n// ---------- pressure / eruption ----------\nvar pressure=0;\nvar eruptTimer=0;\nvar nextWave=0;\nvar wavesLeft=0;\nvar buildRate=0.11;\n\nfunction triggerEruption(){\n  eruptTimer=1.3+Math.random()*0.5;\n  wavesLeft=14;\n  nextWave=0;\n  spawnAsh(Math.round(55*amount),true);\n}\n\nfunction updateEruption(dt){\n  // ambient smoke from crater, more with higher pressure\n  if(Math.random()<pressure*1.4*dt*amount*30/60){\n    spawnAsh(Math.round(1+amount),false);\n  }\n  if(eruptTimer>0){\n    eruptTimer-=dt;\n    nextWave-=dt;\n    if(nextWave<=0&&wavesLeft>0){\n      wavesLeft--;\n      nextWave=0.09+Math.random()*0.08;\n      spawnLava(Math.round((70+Math.random()*50)*amount*power*0.9+30*amount),power);\n      spawnAsh(Math.round(4*amount),true);\n    }\n    if(eruptTimer<=0){ wavesLeft=0; }\n  }else{\n    pressure+=dt*buildRate*(0.6+power*0.5);\n    if(pressure>=1){\n      pressure=0;\n      triggerEruption();\n    }\n  }\n  var fill=document.getElementById('meterFill');\n  var p=eruptTimer>0?1:pressure;\n  fill.style.height=(p*100)+'%';\n}\n\n// ---------- lava physics ----------\nfunction lerpColor(c1,c2,t){\n  return [c1[0]+(c2[0]-c1[0])*t|0,c1[1]+(c2[1]-c1[1])*t|0,c1[2]+(c2[2]-c1[2])*t|0];\n}\nvar STOPS=[\n  [1.0,[255,255,190]],\n  [0.75,[255,190,60]],\n  [0.55,[255,110,20]],\n  [0.35,[200,45,5]],\n  [0.18,[90,22,8]],\n  [0.06,[45,15,10]],\n  [0.0,[30,12,10]]\n];\nfunction heatColor(h){\n  for(var i=0;i<STOPS.length-1;i++){\n    var a=STOPS[i],b=STOPS[i+1];\n    if(h>=b[0]&&h<=a[0]){\n      var t=(h-b[0])/(a[0]-b[0]);\n      var c=lerpColor(b[1],a[1],t);\n      return c;\n    }\n  }\n  return STOPS[STOPS.length-1][1];\n}\n\nfunction updateLava(dt){\n  var windAcc=wind*140;\n  var i;\n  for(i=lava.length-1;i>=0;i--){\n    var p=lava[i];\n    if(p.settled){\n      // slide downhill along terrain\n      var s=slopeAt(p.x);\n      var dir=-Math.sign(s)|| (Math.random()<0.5?-1:1);\n      p.va+= -GRAV*s*0.55*dt;   // gravity along slope (approx)\n      p.va*=1-1.4*dt;           // friction\n      if(Math.abs(p.va)<2&&Math.abs(s)<0.12)p.va=0;\n      p.va+= (Math.random()-0.5)*2*dt;\n      p.x+=p.va*dt;\n      p.heat-=dt*0.135;          // cools faster on ground\n    }else{\n      p.vy+=GRAV*dt;\n      p.vx+=windAcc*dt;\n      p.vy+=windAcc*0.15*dt;\n      p.x+=p.vx*dt;\n      p.y+=p.vy*dt;\n      p.heat-=dt*0.09;\n      var gY=tY(p.x);\n      if(p.y>=gY-p.r*0.3){\n        // land\n        p.y=gY-p.r*0.4;\n        var s0=slopeAt(p.x);\n        p.settled=true;\n        p.va=p.vx*0.7+p.vy*0.15*(s0); // tangential-ish transfer\n        // splash loss of heat on impact\n        p.heat-=0.04;\n      }\n    }\n    // remove\n    if(p.x<-30||p.x>W+30||p.y>Hh+40||p.heat<=0){\n      lava.splice(i,1);\n      continue;\n    }\n  }\n}\n\nfunction drawLava(){\n  ctx.globalCompositeOperation='lighter';\n  for(var i=0;i<lava.length;i++){\n    var p=lava[i];\n    var c=heatColor(p.heat);\n    var alpha=p.heat>0.12?1:Math.max(0,p.heat/0.12);\n    var hot=p.heat>0.45;\n    var rad=p.r*(hot?1.4:1);\n    ctx.fillStyle='rgba('+c[0]+','+c[1]+','+c[2]+','+(alpha*0.9)+')';\n    ctx.beginPath();\n    ctx.arc(p.x,p.y,rad,0,6.283);\n    ctx.fill();\n    if(hot){\n      ctx.fillStyle='rgba('+c[0]+','+c[1]+','+c[2]+','+(alpha*0.18)+')';\n      ctx.beginPath();\n      ctx.arc(p.x,p.y,rad*2.4,0,6.283);\n      ctx.fill();\n    }\n  }\n  ctx.globalCompositeOperation='source-over';\n}\n\n// ---------- ash physics ----------\nfunction updateAsh(dt){\n  var wf=wind*90;\n  for(var i=ash.length-1;i>=0;i--){\n    var a=ash[i];\n    a.vy+=(-8-Math.random()*10)*dt*0.2; // buoyancy jitter\n    a.vy=Math.max(a.vy,-150);\n    a.vx+=wf*dt+(Math.random()-0.5)*30*dt;\n    a.vx*=1-0.6*dt;\n    a.wob+=dt*2;\n    a.x+=(a.vx+Math.sin(a.wob)*8)*dt;\n    a.y+=a.vy*dt;\n    a.r+= (6+a.r*0.25)*dt;\n    a.life-=dt*0.10;\n    if(a.life<=0||a.y<-60||a.x<-80||a.x>W+80){\n      ash.splice(i,1);\n    }\n  }\n}\nfunction drawAsh(){\n  for(var i=0;i<ash.length;i++){\n    var a=ash[i];\n    var al=Math.max(0,Math.min(1,a.life))*a.a;\n    var shade=60+(a.life*40)|0;\n    ctx.fillStyle='rgba('+shade+','+shade+','+shade+','+al+')';\n    ctx.beginPath();\n    ctx.arc(a.x,a.y,a.r,0,6.283);\n    ctx.fill();\n  }\n}\n\n// ---------- crater glow ----------\nfunction drawCraterGlow(){\n  var glow=eruptTimer>0?1:Math.max(0,(pressure-0.45))*1.8;\n  if(glow<=0.01)return;\n  var g=ctx.createRadialGradient(CX,PEAKY+DEPTH*0.4,2,CX,PEAKY+DEPTH*0.4,CRA*2.2+eruptTimer*60);\n  var pulse=0.6+0.4*Math.sin(Date.now()*0.006);\n  var inten=Math.min(1,glow)*pulse;\n  g.addColorStop(0,'rgba(255,150,30,'+(0.5*inten)+')');\n  g.addColorStop(0.4,'rgba(255,70,10,'+(0.25*inten)+')');\n  g.addColorStop(1,'rgba(255,60,0,0)');\n  ctx.fillStyle=g;\n  ctx.fillRect(CX-CRA*3,PEAKY+DEPTH-200,CRA*6,230);\n  if(eruptTimer>0){\n    ctx.fillStyle='rgba(255,'+(140+60*pressure|0)+',30,'+(0.75*pulse)+')';\n    ctx.beginPath();\n    ctx.ellipse(CX,PEAKY+DEPTH*0.55,CRA*0.95,DEPTH*0.75,0,0,6.283);\n    ctx.fill();\n  }\n}\n\n// ---------- scene ----------\nfunction drawScene(){\n  // sky\n  var sky=ctx.createLinearGradient(0,0,0,Hh);\n  sky.addColorStop(0,'#0b1020');\n  sky.addColorStop(0.6,'#20193a');\n  sky.addColorStop(1,'#3a2438');\n  ctx.fillStyle=sky;\n  ctx.fillRect(0,0,W,Hh);\n  // distant moon\n  ctx.fillStyle='rgba(230,235,255,0.85)';\n  ctx.beginPath();ctx.arc(W*0.82,Hh*0.16,26,0,6.283);ctx.fill();\n  ctx.fillStyle='rgba(230,235,255,0.18)';\n  ctx.beginPath();ctx.arc(W*0.82,Hh*0.16,40,0,6.283);ctx.fill();\n\n  // volcano body\n  ctx.beginPath();\n  ctx.moveTo(0,Hh);\n  for(var x=0;x<=W;x+=2){ ctx.lineTo(x,tY(x)); }\n  ctx.lineTo(W,Hh);\n  ctx.closePath();\n  var rock=ctx.createLinearGradient(0,PEAKY,0,Hh);\n  rock.addColorStop(0,'#4a3336');\n  rock.addColorStop(0.5,'#2e2126');\n  rock.addColorStop(1,'#1a1418');\n  ctx.fillStyle=rock;\n  ctx.fill();\n\n  // subtle rim highlights\n  ctx.strokeStyle='rgba(255,120,60,0.15)';\n  ctx.lineWidth=2;\n  ctx.beginPath();\n  ctx.moveTo(CX-CRA,PEAKY);\n  ctx.lineTo(CX-CRA*3,GROUND+10);\n  ctx.moveTo(CX+CRA,PEAKY);\n  ctx.lineTo(CX+CRA*3,GROUND+10);\n  ctx.stroke();\n}\n\n// ---------- main loop ----------\nvar last=performance.now();\nvar frames=0,fpsTime=0,fpsVal=0;\nvar elCount=document.getElementById('counter');\n\nfunction frame(now){\n  var dt=Math.min(0.033,(now-last)/1000);\n  last=now;\n  frames++; fpsTime+=dt;\n  if(fpsTime>=1){ fpsVal=frames/fpsTime; frames=0; fpsTime=0; }\n\n  ctx.clearRect(0,0,W,Hh);\n  updateEruption(dt);\n  updateLava(dt);\n  updateAsh(dt);\n\n  drawScene();\n  drawCraterGlow();\n  drawAsh();\n  drawLava();\n\n  // info after draw (over everything)\n  ctx.fillStyle='rgba(0,0,0,0)';\n\n  elCount.innerHTML='particles: '+(lava.length+ash.length)+\n    '<br>&nbsp;&nbsp;lava: '+lava.length+\n    '<br>&nbsp;&nbsp;ash: '+ash.length+\n    '<br>fps: '+fpsVal.toFixed(0);\n\n  requestAnimationFrame(frame);\n}\nrequestAnimationFrame(frame);\n})();\n</script>\n</body>\n</html>\n\n</body>\n</html>\n```",
        "reasoning_content": "Let's design a solid self-contained implementation.\n\nKey elements:\n- Canvas full-screen, side view. Ground line, volcano cone as a polygon with a crater (V-shaped notch at the summit). Volcano shape: symmetric cone with a crater (V-shaped notch) at the top.\n- Pressure meter (bar) builds up over time; when full \u2192 eruption burst: emits N particles with velocities scaled by \"eruption power\"; pressure then resets and rebuilds.\n- Lava particles: gravity, collide with terrain height field, settle/slide downhill (friction along slope). Cooling: color interpolation from yellow \u2192 orange \u2192 red \u2192 dark gray, then fade out and disappear.\n- Ash/smoke: rises, drifts with wind, expands, fades.\n- Sliders: power, wind (strength + direction), particle amount. Wind slider is negative to positive? Direction is a separate control? \"Strength and direction of the wind\" \u2014 one slider -100..100 for strength+direction, or two controls; the requirement says three sliders: eruption power, wind strength and direction (maybe two sliders?), particle amount. Hmm, \"three sliders ... eruption power, wind strength and direction, particle amount\". Ambiguous \u2014 make wind a slider -100..100 (negative = left, positive = right) to cover strength+direction, keeping a total of three sliders. Or direction as a separate slider. Simpler: wind slider -100..100.\n\n- Particle count: cap with a maximum pool. Need to be able to keep 1000+ active. Use arrays.\n\nTerrain: height field function groundY(x) \u2014 volcano triangle plus flat ground. Cone defined as: center cx, base at ground level gy, peak at crater. Crater: plateau with a dip. Piecewise: slope rises from the left base to the left rim of the crater, crater dip, right rim descends to the right base, beyond that flat ground.\n\nSlope collision handling: particle position vs height at height(x). If below, snap to surface, velocity: decompose into slope tangent, apply friction, cancel normal component. Slope computed from height(x+1)-height(x). On steep slopes, slide downhill. Eventually stop or fade.\n\nCooling: particles have life; color keyed to temperature = remaining heat. When settled, cool faster.\n\nRendering: draw sky gradient, maybe also draw a glow of the crater while pressure is high. Draw volcano filled as a dark polygon. Particles as circles with glow (shadowBlur may be slow with 1000 particles; use radial? Too slow). Performance: drawing filled arcs of circles for 1000 particles with globalCompositeOperation 'lighter' for glow is fine. Avoid shadowBlur.\n\nPrecompute terrain height array per x pixel (store in array) to speed collision detection. Slope from array.\n\nFPS: compute smoothed fps.\n\nPressure: builds up at some rate; triggers eruption burst when full: spawn burstCount = amount * power particles, maybe multiple pulses over ~1 second. Then pressure resets to 0.\n\nAlso allow small \"trickle\": when pressure is high, glow at the crater.\n\nSettled lava: particles permanently reduce terrain? Could darken the terrain and add glow \u2014 too complex. Instead, settled particles slide a bit then become \"rock\" and fade out after a while. Simpler: settled lava stays for a while (temperature low \u2192 darkens), fades out after ~6 seconds, disappears to keep counts manageable. But user wants 1000+ active \u2014 cap at max ~6000, spawn heavily.\n\nWind slider affects ash drift, and also slightly affects airborne lava.\n\nParticle amount slider: scales the spawn count of eruption and ash.\n\nUI: sliders in a fixed panel at the top. Counter top-left: particle count and FPS. Pressure meter on the right side or top.\n\nCode sketch:\n\nlet canvas, ctx; resize handler; terrain heights array H[] length W.\n\nbuildTerrain(): for x: base ground at H0 = h*0.85. Volcano centered at cx=w*0.5? Put the volcano at 45% of the width. Cone: leftBaseX..peakLeft..crater..peakRight..rightBase. Use: halfW = w*0.32; peakX=w*0.45? Simple: cx=w*0.5, craterHalf=40 (scales), peakY = groundY - coneH, crater depth ~18. Height function: for x in the range cx-halfBase .. cx-craterHalf: interpolate from ground to peak...\n\nActually compute:\n- g = groundBase (y = canvas.height*0.78)\n- volcano height vh = canvas.height*0.4\n- craterW = w*0.05\n- baseW = w*0.30\nSlope: |dx| > craterW/2? For |x-cx| in [craterW/2, baseW]: t = (|x-cx|-crater/2)/(baseW - crater/2) to map y, slopeY = peakY + t*(g-peakY)?? That's a slope from peak to base... y goes from peakY to g: y = peakY + t*(g - peakY). Inside craterW: dip to crater depth: dip at center: craterY = peakY + craterDepth*(1-(|x| /(crater/2))^2)? Dip at center: y = peakY + depth*(1 - (d/ (crater/2))^2)? Actually, crater floor is above the peak: y = peakY + craterFloorOffset... Crater = depression: at edges (rim) y=peakY, at center lower, so y increases: y = peakY + craterDepth*(1 - (d/(craterHalf))^2)... At d=craterHalf, y=peakY (rim) \u2713, at center d=0, peakY+depth (bottom of crater) \u2713.\n\nSave H array per x (float). Clamp.\n\nPhysics loop dt (clamp at 0.033). For each lava particle:\n If !settled: vy += gravity*dt; vx += wind*dt*0.15; vy += wind*dt*0.05. Update position. Check surface: sy = terrainY(x). If y >= sy: settle: y = sy + small value; compute slope s = (H[x+1]-H[x-1]); velocity decomposition: tangential direction... Simplify: compute slope; vx = slope * speedFactor? Slide model: keep speed along surface: along = vx + vy*slope? Hmm. Simpler: compute slope; gAlong = gravity * sin(theta) where sin theta = slope/sqrt(1+slope\u00b2). Direction downhill: -sign(slope). v_along += gAlong*downDir*dt*dt? Units: acceleration a = gravity*slope/(1+slope\u00b2)... Apply: dir = slope>0? -1:1 (x increases as y goes down, so downhill is the opposite sign of slope... if y increases going downhill (screen coords), downhill x direction = -sign(slope)). vAlong += -gravity*slope*? Let's just do this: vAlong += -gravity*slope/(1+slope*slope)*dt; friction: vAlong *= 0.98. And vx = vAlong, vy = vAlong*slope. Good enough. If slope is small, vAlong\u21920, set \"resting\" state. When resting, friction higher.\n\nTemperature: temp starts at 1, decreases: temp -= coolRate*dt (faster when settled? Slower when in the air? Actually hot when launched). life = temp. Color: interpolate from bright yellow (high) to dark rock (low), alpha fades near the end. Remove when temp<=0 or off-screen.\n\nOn settle, add a small bounce? Convert: if hit at high speed, small chance to splash? Keep simple: stick and slide.\n\nAsh particles: spawn from crater at pressure or continuously during eruption. Rise vy negative, vx = wind*..., expand radius, fade alpha. Remove when alpha<=0.\n\nPressure: rate pressure += dt * 0.15 (scaled). If >=1: erupting: eruption burst over 0.8 seconds spawning ~ amount*power * multiple waves in multiple frames. Use an eruption state with remaining time.\n\nCrater glow: draw radial gradient at the crater during high pressure or during eruption.\n\nDraw order: sky gradient, maybe sun, volcano polygon via H (fill path along H, filled dark brown), lava glow inside crater when pressure is high, ash, particles with lighter compositing, UI meter.\n\nCounter: active = lava.length + ash.length.\n\nPerformance: fine.\n\nLet's write it.\n\nDetails: H array recomputed on resize; particles use W (canvas width) at time of launch.\n\nWind slider: -100..100, wind = value/100 * 60 px/s\u00b2 for ash, *20 for lava.\n\nPower slider: 0.2..2 multiplier on velocity and burst size.\n\nAmount slider: 0.3..2 for particle count per eruption; base burst maybe amount*1800? Need 1000+: at power 1, amount 1: burst ~1200? \"Must stay smooth with at least 1000 active\" \u2014 allow amount slider max ~4000 in one eruption. Cap total lava around 8000; remove the oldest when over.\n\nSpawn: position at crater center, vx spread, vy upward power*(450..900)*power, vx spread \u00b1350*power.\n\nAsh: continuous smoke from crater at rate proportional to pressure (pre-eruption smoke) and heavy during eruption.\n\nFPS counter: count frames per second, update text once per second.\n\nWrite it all out cleanly. Meter bar on the left vertical: shows pressure, plus label. Panel with sliders: HTML inputs with labels showing values.\n\nColors: lava color via temp: t>0.66: yellowish white; interpolate between stops: define function lavaColor(temp):\n stops: temp 1: rgb(255,255,180) bright; 0.7: rgb(255,160,40); 0.45: rgb(220,70,10); 0.25: rgb(90,25,5) dark red; then fade to rgb(40,15,10) fading. Use lerp between stops.\n\nAlpha: 1 until temp<0.15 then fade? Actually when it becomes rock, don't fade \u2014 stay as rock and remove when temp<=0: cool to rock color with min temp -0.? Give two properties: heat (1\u21920 over lifetime) and age. Simpler: when heat reaches 0.12 (rock), then start fading out? Give settled lava a longer life, in-air shorter life. Keep it: heat decreases faster when settled. Remove when heat<=0. Color: heat>0.5 hot glow color; heat<0.3 darkens to rock gray. Alpha = min(1, heat/0.3)? So it fades at the end. Good.\n\nSmaller radius when settled? No, glow radius larger when hot.\n\nCrater glow: when pressure>0.5: draw a pulsing radial gradient at crater center, intensity based on pressure.\n\nAsh color gray, rising vy -60..-140, expand radius, alpha max 0.5.\n\nEruption trigger: eruptionTimer set; during eruption, spawn waves: every ~0.07 seconds spawn a batch. Pressure resets to 0, rebuild rate maybe slower.\n\nPre-eruption: ash rate scales with pressure (rumbling smoke).\n\nWrite HTML with styling: panel at top, semi-transparent.\n\nEdge cases: particles off-screen (x<0 or >w, y>h) removed. Cap arrays.\n\nCode:\n\nconst S={}; init on load; use requestAnimationFrame loop with dt.\n\nLet's write the final HTML. Roughly 250 lines. Ensure self-contained.\n\nTerrain draw: ctx.beginPath moveTo(0,H[0]) ... lineTo every ~2 px, close along the bottom edge. Dark gradient fill. Maybe add snow/ash tinting.\n\nSliders panel fixed top-left; counter top-right; pressure meter maybe bottom-left horizontal. Fine: vertical meter at bottom-right.\n\nAdd crater lava pool glow during eruption: bright fill at crater center.\n\nWrite the code carefully.\n\nWind direction: value/100 * 80 for lava acceleration, *60 with noise for ash.\n\nAlso add a bit of initial turbulence to the ash.\n\nCounter updates FPS: fps = frames count over one second.\n\nOK, writing.\n"
      }
    }
  ],
  "created": 1787803965,
  "model": "~/.cache/huggingface/hub/models--unsloth--Qwen3.8-Flash-Next-GGUF/snapshots/8bdc666649440e9bdc97e16f3f75782c98478ff5/UD-IQ1_M/Qwen3.8-Flash-Next-UD-IQ1_M-00001-of-00003.gguf",
  "system_fingerprint": "b10667-213df585b",
  "object": "chat.completion",
  "usage": {
    "completion_tokens": 7456,
    "prompt_tokens": 236,
    "total_tokens": 7692,
    "prompt_tokens_details": {
      "cached_tokens": 0
    }
  },
  "id": "chatcmpl-M7FLNEx4DnV6mvwa5P7VfPnwcwCv9nz0",
  "timings": {
    "cache_n": 0,
    "prompt_n": 236,
    "prompt_ms": 1571.385,
    "prompt_per_token_ms": 6.658411016949152,
    "prompt_per_second": 150.18598242951282,
    "predicted_n": 7456,
    "predicted_ms": 318490.882,
    "predicted_per_token_ms": 42.72178162307176,
    "predicted_per_second": 23.407263508410267
  }
}