Create a single HTML file with canvas, volcano, pressure meter, eruption with particles, gravity, terrain collision (side view of a cone shape), particles that slide down and come to rest, cooling color, drifting ash cloud, three sliders (power, wind, particle amount), counters for particle count and FPS. Volcano terrain: define a height function y = groundY - cone(x). Use this for collision detection: when a particle's y goes below the terrain height, it lands, slides with friction, and comes to rest. For performance: use a particle pool, simple canvas circles, avoid gradients per particle. Use additive blending (globalCompositeOperation 'lighter') for lava glow. Keep the count modest. Target 1000+ particles. Let's write the code. Canvas 960x540 or responsive. Keep it fixed. Volcano: cone centered at x=W/2, base from cx-280 to cx+280, peak near top y=180, crater: flat top between cx-40 and cx+40 with a dip. terrainY(x): for |x-cx| <= 280, height = (1 - |x-cx|/280) * coneHeight, crater dip: if |x-cx|<45 then subtract the dip. Otherwise ground at y = H-40. Particles: - Lava: spawn at crater top, vx random * power, vy upward * power. Gravity. When y >= terrainY(x), set to rest: y = terrainY(x), compute slope, apply downhill slide: velocity along the slope, friction. After a few seconds cooling stops them (state=settled). Cooling: color goes from yellow (255,220,80) to orange to dark. Life ~6-10 seconds. Disappear. - Ash: spawn at crater, rise (small buoyancy) and drift with wind, expand, fade alpha. Emission during eruption: while eruptionT>0, spawn lava at a rate proportional to power and the particle amount slider. Ash spawns continuously during eruption, and a little ambient ash. Pressure: increases over time (rate might depend on sliders? keep it simple, fixed). When it reaches 1, erupt for eruption duration ~2.5s, then reset pressure to 0. Sliders: power (1-10), wind (-5..5), particles (200-3000, spawn multiplier). FPS: computed with smoothing. Rendering: draw sky gradient (precomputed), volcano shape as filled path (precomputed offscreen? or draw every frame is fine). Draw volcano once to offscreen canvas and blit it. Lava glow: draw with 'lighter' compositing, circle with shadowBlur? shadowBlur is slow. Instead draw 2 circles: outer semi-transparent orange, inner bright color. Use hsl via precomputed color table by life fraction: index into a 32-entry array of [r,g,b]. Slide: on terrain, slope = (terrainY(x+2)-terrainY(x-2))/4; acceleration along slope; vx += slope-ish... Simpler: treat as ball rolling down: vx += (sign of slope)*g*0.3*... actually downhill direction: if terrainY increases with x (y down is positive), the slope direction going down is toward increasing y, i.e. sign of (dy/dx). So vx += sign(dydx)*g*0.25*dt; friction vx*=0.95; update x, snap y to terrain. Once speed is low and life is short, stop it. Also cap max particles. Let's write the file. Color ramp: t = 1 - age/life (1 new → 0 old). t>0.6: bright yellow-orange: r=255, g= 120+135*t... let me just do this: - t in [0.7,1]: color (255, 230, 100)→(255,160,40) - Simplify with array lookup: ramp = for i 0..31: t = i/31; if t > 0.75: mix yellow(255,240,140) and orange(255,140,20) by (1-t)/0.25 else if t > 0.4: mix orange(255,140,20) and red(180,50,20) else mix red and dark rock (50,35,35), and alpha fades in the last 20%. Compute a 32-entry table. Ash: gray particles, size grows, alpha = life-based * 0.4. Wind slider -5..5 affects ash vx and lava slightly. Draw eruption flash at crater during eruption. Write code carefully. Particle cap: pool arrays (typed arrays for performance?) Use object arrays with fixed capacity and a free list — fine for a few thousand. Actually use a plain object array with swap-remove. Emission rate per frame: during eruption, spawnRate = power * particlesSlider factor. particle amount slider scales both spawn rate and max count. Let me write: ```js const W=960,H=540; const cx=W/2, baseW=300, peakY=150, groundY=H-50, craterHalf=42; function terrainY(x){ if(xcx+baseW) return groundY; const d=Math.abs(x-cx)/baseW; let y=groundY-(groundY-peakY)*(1-d); const dc=Math.abs(x-cx); if(dcp.life){remove;continue;} if(p.state===0){ // flying p.vy+=G*dt; p.vx+=wind*0.05*dt; p.x+=p.vx*dt; p.y+=p.vy*dt; if(p.y>=terrainY(p.x)){ p.y=terrainY(p.x); p.state=1; p.vx*=0.5; } } else { // on ground const s=terrainSlope(p.x); p.vx += s*G*0.35*dt * Math.sign? ``` slope = terrainY(x+1)-terrainY(x-1) / 2 (positive means downhill to the right). Acceleration vx += s * G * 0.3 * dt... units: s is dimensionless (dy/dx in px), G is px/s². If s>0 the downhill direction is +x, so a_x = G * s / sqrt(1+s²) ≈ G*s. Use p.vx += G*s*0.4*dt; friction p.vx *= Math.pow(0.5, dt*2)? Use p.vx -= p.vx*1.5*dt. Then p.x+=p.vx*dt; p.y=terrainY(p.x). If |p.vx|<4 and p.age> p.life*0.7, mark as settled (no movement) to save CPU. Simplify state 2 = settled. Ash update: vy decreases toward riseSpeed (buoyancy) — give initial vy upward, apply light gravity or negative buoyancy: p.vy += (p.targetVy - p.vy)*dt; targetVy = -20 (rises). vx += (wind*30 - p.vx)*dt*0.5. Size grows. Alpha fades with life. Eruption logic: ``` pressure += dt * rate; if pressure>=1 { erupting=true; eruptT=eruptDur; pressure=0; } if(erupting){ eruptT-=dt; spawn lava... if eruptT<=0 erupting=false; } ``` Eruption duration might scale with power: 1.5 + power*0.3. Lava spawn during eruption: count per frame = (base 6 + power*2) * amountFactor. Random angle: mostly upward, spread. speed = (300+Math.random()*250)*power/5. G = 600 px/s². Also add slight ambient lava glow in crater while pressure>0.5? Add glow intensity with pressure. UI: div with sliders and values, overlay. Counters: "Particles: N | FPS: 60". Ash also spawns during eruption, a small amount of ambient ash always. Particle cap: maxCount = particlesSlider (e.g. 1000-4000). Performance: avoid shadowBlur; use two-arc technique for glow, or single arc with 'lighter'. ~2000 arcs per frame with two circles each = 4000 arcs — canvas can handle at 60fps on most machines. Use small glow radius. Alternative: draw lava to a low-res offscreen and blur... Keep it simple. Let's write the full HTML. Color ramp table: ```js function mix(a,b,t){return [a[0]+(b[0]-a[0])*t, ...];} const C1=[255,244,180],C2=[255,150,40],C3=[190,55,25],C4=[45,32,32]; ramp=new Array(32); for(let i=0;i<32;i++){const t=i/31; let c; if(t>0.7)c=mix(C2,C1,(t-0.7)/0.3); else if(t>0.35)c=mix(C3,C2,(t-0.35)/0.35); else c=mix(C4,C3,t/0.35); ramp[i]=`rgb(${c[0]|0},${c[1]|0},${c[2]|0})`;} ``` Also alpha: fade in last 25%: if t<0.25 then alpha = t/0.25, else 1. But with 'lighter' compositing, per-particle alpha requires globalAlpha — fine. Actually the dark rock in a 'lighter' context just adds a bit — good, fades. Draw: for lava, pass 1 with 'lighter': glow circle radius*2, low-alpha color, then main circle. For each particle maybe: ctx.globalAlpha = a*0.25; arc r*2.2; then globalAlpha=a; arc r. FPS: ema. Counter text update every ~250ms. Sliders: - Power: 1–10, default 5 - Wind: -5–5, default 1 - Amount: 300–3000, default 1400 Particle amount affects spawn rate: amountFactor = amount/1400; max particles = amount. Let me write it. Also make canvas scale to window? Fixed 960x540, style max-width 100%. Fine. Volcano offscreen texture: gradient fill of dark brown/gray rock, plus some random specks, dark crater interior, ground with a gradient. Eruption flash: while erupting, draw radial gradient at crater with intensity varying sin/noise. Ambient: pressure glow at crater proportional to pressure. Now let me compose the HTML.