Create a volcano eruption simulation using canvas. Single HTML file. Sliders (power, wind, particle count), pressure meter, lava particles using gravity physics, slope collision, cooling color, ash particles, FPS counter, particle count counter. Design: - Canvas size probably 900x500, responsive. - Volcano: mountain shape with a crater in the middle. Two slopes from the edges to the crater rim. Simple polygon: ground at y = H, peak (crater) at (W/2, topY). Crater opening between peakX-30 and peakX+30. - Pressure builds over time; when it reaches max, erupt: spawn N lava particles with random velocities mostly upward; reset pressure. - Lava particle: x, y, vx, vy, life, temperature. Gravity g = 0.15. Collision with slopes: defined as line segments of the triangle. When it lands, it flows downhill: along the slope, with velocity influenced by wind? Gravity along the slope, keep flowing along the slope surface. Settle once velocity is low. Cooling: lifetime-based color: yellow → orange → red → dark grey. Settle duration, then fade. - Slope function: height at x: left slope is from (0, H) to (craterLeftX, craterTopY)... actually simpler: volcano defined as piecewise: left slope from (0,H) to (cx - craterHalfW, topY), right slope from (cx+craterHalfW, topY) to (W,H). Crater between them at topY: particles spawned inside crater fall out and land. Use slope surface: surfaceY(x). height at x: left slope from (0,H) to (cx-craterHalfW, topY), right slope from (cx+craterHalfW, topY) to (W,H). Crater between is at topY: particles spawned inside crater exit crater and land. Use surfaceY(x): if x between crater edges return crater bottom (topY + some depth? maybe make crater floor slightly lower). If x < craterL: H - (H - topY) * (x - 0)/(craterL - 0)? slope from ground on left to peak at left edge of crater. Actually the slope from ground to peak... left slope from (0,H) to (craterL, topY). So surfaceY = H - (H - topY)*(x)/(craterL). Outside craterL..craterR it's H, but topY inside crater. Lava landed: if particle crosses surfaceY, snap to it, velocity along slope proportional to tan. On slopes there's gravity along slope. Slope: left slope angle — surface decreases with x... slope s = (topY - H)/(craterL - 0), negative (higher as x increases). Particle on left slope flows downhill to left (decreasing x). On right slope to right. So tangential acceleration = g * sin(theta)* direction toward base. Compute per side. Simplify: when on surface, vx += slopeAccel*dir, position y = surfaceY(x). Friction. If |velocity| tiny, settle: vy component stops — set landed=true, keep sliding with friction, then when speed < eps stop, becoming "rock", fade after coolTime. Ash: spawn from crater during eruption (also small ambient?), rise, get random walk, drift with wind, expand, fade. Wind slider: -1 to 1 (arbitrary units). Power slider: eruption strength. Particle amount slider: density multiplier for eruption density and ash. Pressure meter: bar on screen, drawn in canvas or HTML. HTML panel: draw in top corner on canvas. Simpler: canvas draw. FPS: compute per second. Cap particles at some max like 4000 for performance. Use simple arrays. Colors: glow via composite "lighter" for hot lava. Avoid per-particle shadowBlur (slow). Precompute? Draw radial gradient sprite once offscreen and drawImage — fast. Make sprite of white circle gradient, colorize with globalAlpha and... colorization cost. Approach: draw lava as circles with fillStyle chosen from palette buckets via fillRect — decent. For glow, draw a bigger circle of low alpha on top. To keep fast, precompute offscreen canvases: e.g., 6 sprites (different colors) of radial gradient, and drawImage with scale. Colors: interpolate along cooling palette but sprite per color bucket (e.g., 8 buckets). Sprites generated at load time. Structure: Lava particle holds temperature t (0..1 decreasing over lifetime). Color bucket = floor(t*7). Draw sprite accordingly with "lighter". Settle rocks drawn normal dark fill, no lighter — maybe fade. Simpler approach: keep all lava particles drawn with "lighter" additive. Settle rocks fade out, dark rock color is barely visible with additive. Hmm additive of dark red is fine. Use two passes: hot particles (t>0.15) additive, cooled ones drawn normally as dark rock. Good. Ash: grey circle drawn with globalAlpha, normal composite, drawn after lava? Ash behind (first)? Ash rises from crater, drawn before lava — drawn after? Draw ash below (first) then lava on top. Ash rises up so overlap minimal. Draw ash then lava. Also draw ash on top of volcano shape. Ash behind lava. Volcano drawing: sky gradient background, then ash cloud, then volcano cone (polygon) drawn on top of landed lava? Landed lava should be on top of cone. Draw order: sky, ash, volcano, lava (rock then hot with additive), UI. Wait, ash should drift across sky — draw after volcano is behind volcano. Fine: ash behind cone looks fine (smoke behind cone visible above crater). Good. Pressure: builds at rate, eruption occurs when pressure==1: burst spawn, plus continuous spawn during short eruption duration for realism: eruptionTimer, rate depends on particle amount. After timer ends, pressure resets to 0 and starts building again. Wind affects ash: vx += wind*0.02; affects lava slightly? In flight only affect ash (realistic). Counter: text at top-left: "Particles: N FPS: F". Sliders: HTML input range below canvas in a controls div, dark styling. Canvas size: fixed 960x540, scale by CSS. Code specifics: ```js const W=960,H=540; groundY = H (bottom). volcano: cx=W/2, craterHalf=34, topY=140; baseY=H (ground). Left slope from (0,H) to (cx-craterHalf, topY)? then slopes reach ground at edges. Might add a second smaller peak. Simple cone with wide base is fine: baseY = H+? Ground level for lava settling: groundY=430 with ground rectangle below. Actually side view: ground line at y=430, below it is earth-brown ground fill. Volcano cone from groundY to topY. surfaceY(x): if x < craterL: if x<=0 return groundY (but slope reaches at x=0) — cone base spans entire width. surfaceY = groundY + (topY - groundY) * (x - 0)/(craterL - 0) → at x=0 groundY, at craterL topY. Clamp. if x within crater: return craterFloorY = topY+14. if x > craterR: mirror. Beyond cone base (x out of [0,W]) still fine. ``` Lava physics: while in flight, vy += G; vx += wind*0.005 (tiny air drag on lava too, small). If y >= surfaceY(x): landed. If inside crater (x within craterX range) settle there. Compute slope s (surfaceY derivative): left slope sL = (topY-groundY)/craterL (negative). Tangential gravity: on slope, acceleration along x = G * s * something... For surface y = a + m*x, gravity along slope acceleration = g*m/sqrt(1+m²)*... direction: particle accelerates in direction of downhill = if m<0 downhill means decreasing x. Use ax = -G*m / (1+m²)?? For 1D sliding with slope y = m x, acceleration along x = g·sin θ·cos θ direction downhill = -g·m/(1+m²)... check: downhill = decreasing y. If m negative (left slope: y decreases as x increases? left slope: as x increases toward peak y decreases (upward)). Downhill side: toward x=0. So acceleration should be negative. With m negative, -g·m/(1+m²): -g·m is positive → wrong. Use ax = g·m/(1+m²): negative → leftward. Hmm derivation: acceleration along slope: ax = -g·m/(1+m²)? Test: m=0 flat → 0 good. Steep negative m: should accelerate left (negative). ax = g·m/(1+m²) → negative ✓. For positive m (right side slope rises as x increases then... right slope: from peak (craterR, topY) to (W, groundY): m = (groundY-topY)/(W-craterR) positive) downhill = increasing x → positive ax ✓ with ax = g·m/(1+m²). Wait but left slope in y-down coords: y = groundY at x=0, y=topY at craterL: m = (topY-groundY)/craterL <0 since topY=1: erupting=true, eruptTimer = 90 + power*... frames; } while erupting: spawn per frame = particleAmount scaled count; if timer done: erupting=false; pressure=0; ``` Eruption spawn: from crater: x = cx + rand*craterHalf, y = topY+10; vx = (rand-0.5)*2 * spread*powerFactor... vx = (Math.random()-0.5)*4, vy = -(6 + Math.random()*6)*power. Power slider 0.3..2. Cap: if lava array length > MAX (like 6000) remove oldest. FPS: frame counter, update every 500ms. Draw UI: pressure meter: bar at top-left, vertical or horizontal. Draw horizontal bar labeled with gradient. Let me also add slight glow in crater before eruption when pressure high. Volcano shape draw: path polygon (0,groundY)→(craterL,topY)→(craterR,topY)→(W,groundY) plus below to bottom corners, filled with mountain gradient. Ground rect below groundY. Crater glow: when erupting or high pressure, draw radial at crater. Also small background mountains optional; skip, keep sky gradient + maybe sun? Keep sky gradient simple + ground. Lava draw: hot ones additive pass. Cooled rock (settled, dark): draw dark grey sprite normal. Manage: each lava p has settled flag, temp. Bucket: temp>0.05 → hot sprite (bucket = clamp(floor(temp*8))). Else rock. Lava draw size: r = p.size (2..5) → drawImage(sprite, x-s,y-s,2s,2s) where sprite is soft circle; hot core brighter. Ash draw sprite grey. Now HTML/CSS: dark page, centered canvas, slider row with labels showing values. Let me write code. Performance: with "lighter" drawImage for up to ~2000 particles fine. Also prevent ash buildup from eating memory: cap ash at 800. Let me write it. Counter text at top-left: "Particles: N FPS: F". Let me code now. surfaceY(x) function: ```js const groundY=430, topY=140, craterHalf=34, cx=W/2; const craterL=cx-craterHalf, craterR=cx+craterHalf, craterFloor=topY+16; const mLeft=(topY-groundY)/craterL; // per px const mRight=(groundY-topY)/(W-craterR); function surfaceY(x){ if(x<=0)return groundY; // slope extends to edge; if x<0 offscreen if(x= surfaceY(p.x): landed = true. y=surfaceY(x). Keep tangential: keep vx but constrain. Also during flight don't check collision below ground bottom. Ash spawn from crater: x = cx ± craterHalf*rand, y=topY+4. Wind slider label shows left/right. Update pressure meter drawing: rectangle with border, fill proportion, glow when near full. Draw text. Main loop via requestAnimationFrame; fixed-ish dt per frame (assume 60). Could use dt scale = min(2, delta/16.67). Simpler: assume 60fps. FPS compute: frames++, every 500ms fps = frames*1000/elapsed. Also "at least 1000 active particles" needs to hold: with particle amount slider to max, eruption spawns like 40/frame for 150 frames → 6000 → cap MAX 5000. Performance: 5000 drawImages might be heavy but with sprite draws okay. Set MAX_LAVA=4000. Also ambient smoke from crater between eruptions: light seeping to show pressure: spawn rate proportional to pressure^3 * small. Nice. Write final HTML. Color palette hot (t near 1 → white/yellow; low t → dark red): palette[i] i=0..7 from cool to hot: 0: (60,20,10) ... build: t=1 bright yellow (255,240,150). Sprite: radial gradient, center color bright, edge transparent-ish. Define palette from t=0..1 (8 buckets): bucketColors = [ [70,15,8],[120,25,10],[170,40,12],[200,60,15],[220,90,20],[240,140,30],[255,190,70],[255,240,140] ] Sprite creation: radial gradient, center color rgba(r,g,b,1) → mid rgba*0.6 → edge transparent. Size 40px. Rock sprite: dark grey soft blob. Ash sprite: grey soft blob, size 64. Draw ash with globalAlpha = p.alpha, drawImage scaled by r. UI text and sliders. Slider ids: power (0.3–2 step .05 default 1), wind (-1–1 default 0.15), amount (0.2–2 default 1). Value displays. Erupt spawn count per frame: base = Math.round(14*amount) plus power influence on velocity. Ash spawn during eruption: ~4*amount per frame; ambient: pressure>0.5 → pressure^3*2 per frame. OK let me code it. Also draw stars? Daytime, skip. Edge case: particle below canvas if past W+50 etc. remove. Removal: settled rocks: after settled, fade: p.alpha -= 0.002. When alpha<=0 remove. Actually keep rocks visible for a while: alpha starts at 1 after settling, fade after cooldownTimer. Simplify: temperature keeps dropping even in flight; when landed cooling doubles (contact). temp decreases: flight coolRate 0.0009 + landed 0.0025. temp <= 0.02 → rock fade: alpha decreases 0.0015 → remove at <=0. Hot draw condition temp > 0.12, else rock. Settled condition: landed && |vx|<0.03 → settled=true, vx=0. Slide on landed while not settled: vx += g*m/(1+m²)*2 (2x more dramatic), friction 0.99, then y = surfaceY(x). Lava in crater with m=0: vx *= 0.9 → quickly settles. Good. Let me code it. Also add subtle background hills for aesthetics: 2 dark triangles. Optional; add cheaply. Let me generate final HTML.