### PARTIAL OUTPUT (time limit hit; the model was still generating) ### === thinking so far === Let me plan this volcano simulation carefully. It's a physics/rendering piece, and I want it to feel like a living scene, not a generic canvas demo. **Core architecture:** - Single canvas, full screen, devicePixelRatio-aware. - Fixed-timestep physics (accumulator) decoupled from render for stability, or simpler: dt-clamped variable timestep. With 1000+ particles, I'll use typed-object pools (arrays of objects, reused) to avoid GC churn. - Particle systems: 1. Lava particles — ballistic arc with gravity, collide with terrain (heightmap), then slide downhill along slope with friction, cooling over lifetime. Rendered with additive-ish glow: draw hot particles with radial gradient sprites (pre-rendered offscreen canvases at a few colors for speed — actually per-particle gradients are slow; I'll pre-render ~5 color-stage sprites onto small offscreen canvases and use `drawImage` with globalAlpha + `globalCompositeOperation = 'lighter'` for hot ones). drawImage of small sprites is very fast, easily 1000+ particles. 2. Ash/smoke particles — rise from crater, buoyancy, drift with wind, expand and fade. Rendered as soft dark/gray sprites with normal compositing. 3. Embers/sparks maybe as part of lava (same pool, smaller size). **Terrain:** generate a heightmap for the volcano cone via a profile function: flat ground, a slope rising to a crater with a dip in the middle (classic truncated cone with funnel). Add some noise for natural look. Store as array `terrain[y at x]`. Collision: particle checks `py >= terrain[ipx]` → landing. Sliding downhill: when landed, move along the heightmap gradient: `slope = (terrain[x+1]-terrain[x-1])/2`, particle x-velocity = sign * slideSpeed scaled by slope steepness, friction decays it, and stick when slope flattens. Also particles can roll slightly. Settled lava particles darken and eventually vanish — so terrain doesn't accumulate (requirement says "settle downhill... then disappearing" via cooling — good, keeps count bounded). **Pressure system:** pressure 0→1 over time (rate influenced by nothing or slight randomness with "tremor" wobble for drama). When pressure >= 1 → eruption: continuous emission for a duration scaled by power slider; pressure drains during eruption; then resets to 0 and rebuilds. Add rumbling: screen shake, crater glow intensifies as pressure rises. I'll make the pressure meter a nice vertical gauge on the side with tick marks, styled like an instrument — not a gradient banner. A canvas-drawn or DOM meter? DOM with a fill bar is fine; I'll do a custom-drawn needle gauge... Actually a vertical thermometer-style bar with ticks drawn on the canvas or a separate small canvas. I'll do DOM: a slim vertical track with a fill and tick marks, plus an SVG-ish needle — keep it simple: vertical bar + ticks via repeating-linear-gradient... hmm, I avoid decorative gradients but a functional meter fill is fine as solid color with ticks. Solid crimson fill on dark track, hairline ticks. Good. **Wind:** slider from -N to +N; affects ash strongly, lava slightly, and flag/ambient (maybe drifting ember sparks, subtle sky dust). Visualize wind with faint streaks in the sky — drifting thin dashes whose speed mirrors the wind value. That's a nice alive touch and communicates wind direction at a glance. **Sliders:** three custom-styled range inputs: Eruption Power, Wind, Particle Amount (max concurrent spawn multiplier / pool cap). Live-adjustable. **HUD:** top-left panel: particle count + FPS, updated every ~500ms. Monospace-ish distinctive font — I'll pull a characterful font from Google Fonts CDN (allowed: external CDN references are fine). Something like "Space Grotesk" or a mono like "IBM Plex Mono" for readouts. I'll use IBM Plex Mono for data and maybe "Bricolage Grotesque" or "Instrument Serif" for the title. Keep it restrained: dark UI panels with hairline borders, amber/crimson accent matching lava. **Visual direction:** I want a painterly dusk scene rather than flat cartoon: deep twilight sky (this is a case where a sky gradient is legitimate — a natural atmospheric gradient, not decoration), the volcano as a dark silhouette with a glowing crater, stars faint, layered mountains behind for depth. Lava glow illuminating the cone when erupting (radial glow behind crater). Colors: near-black indigo sky up top → burnt orange dusk at horizon. Lava palette: #fff3b0 → #ffb347 → #ff5e2b → #c93007 → #4a1d14 → fade. Wait — I should be careful about my own stance on gradients: a sky gradient here is a natural rendering of atmosphere in a canvas scene, which is genuinely representational, not a decorative UI gradient. That's fine and I'll own it. UI chrome stays flat, precise, hairline borders. **Performance plan:** - Pre-render sprite atlas: small canvases per color stage (e.g., 6 stages for lava glow, each a radial gradient blob), plus smoke puffs (2–3 gray soft blobs). drawImage with scaling = fast. - Hot lava drawn with `lighter` composite; draw all hot ones in one batch (set composite once, loop), then reset for smoke. - Pool cap = slider-controlled (500–3000), reuse dead particles. - Physics: simple Euler, per-particle O(1). Terrain lookup via index. Avoid Math.random in hot loop where possible — it's fine actually. - FPS: rolling average via frame delta. **Physics details:** Terrain generation: ``` groundY baseline = H*0.82 volcano: centerX = W*0.5, baseHalfWidth = W*0.34, peak height = H*0.42 above ground → peakY = groundY - H*0.40 crater half width = W*0.055, crater depth dip = peakY + 26 heightAt(x): if |x-cx| > baseHalf: groundY + small noise hills else: t = |x-cx|/baseHalf; y = lerp(peakY, groundY, smoothstep-ish t^1.4) crater: if |x-cx| < craterW: dip down via parabola add ridged noise: y += noise for texture (small, e.g. ±6px with a few sine octaves) — but keep crater area usable. ``` Also render the terrain fill each frame? Terrain is static — render once to an offscreen canvas (with texture: dark rock silhouette, some streaks, glow near crater redrawn dynamically? Crater glow pulses — draw glow dynamically on main canvas each frame, terrain silhouette cached offscreen. But lava glow lighting the slope changes... I can draw an additive radial glow at crater each frame over the cached terrain — cheap and looks great.) Background: stars + distant ridge lines — also cached into the background offscreen (sky gradient + stars + far mountains). Redraw only when resized. Eruption sequence: - pressure builds: `pressure += buildRate * dt` with slight nonlinearity + tremor jitter near max. - When pressure >= 1: state = ERUPTING, eruptionTimer = duration = 2.5 + power*4 s. During eruption, pressure drains to 0 across the duration. - Emission rate: `power * 400` particles/sec (scaled by particle amount slider as pool cap) from crater mouth with spread: vx = (rand-0.5)*power*140 + wind*0.3, vy = -(220 + rand*power*380). Also continuous smoke emission scaled by activity, plus after-eruption lingering smoke. - Also small always-on fumarole: occasional wisps even between eruptions for aliveness (few smoke particles trickling). Lava particle update: ``` if airborne: vy += G*dt; vx += wind*windFactor*dt (light drag); x+=vx*dt; y+=vy*dt if y >= terrain[floor(x)] : land → mode=slide, compute slope slide: slope = (terrain[x+1]-terrain[x-1])/2; slideV += slope*slideAccel*dt (slope sign: positive slope means going down to right? terrain y increases downward, so terrain[x+1]>terrain[x-1] means downhill to the right → slideV += that * k) slideV *= friction (e.g., pow per dt) x += slideV*dt; y = terrain[x] - small embed also cool faster on ground; if slideV tiny and slope tiny → settle (mode=still), just cool. still: y = terrain[x]; cool. cooling: heat -= dt * coolRate (air cool slower? actually flight cools less; contact cools faster? I'll do: flight cools slowly, on ground cools faster). heat 1→0. Color index = heat stages. When heat<=0 → despawn (release to pool). Bounce: if landing vy > threshold, small bounce chance? Keep simple: slight bounce if speed high (vy = -vy*0.25) adds life. Do a bounce when impact vy > 180: reflect with damping, spawn 1-2 spark fragments if power high? Fragment spawning risks pool blowout — cap: only if pool has room and count small. I'll include modest splatter: on high-impact landing, spawn 0–2 small sparks with low heat cost. Guard with pool capacity. ``` Smoke particle: ``` vy rises with buoyancy: vy approaches -riseSpeed; vx approaches wind*windScale + turbulence sin(time*f + seed) size grows, alpha fades; life ~4–8s; color dark gray → light gray. Rendered normal composite under? Over lava but below glow? Order: bg → terrain → settled+all lava (lighter for hot) → smoke on top with alpha → foreground? Smoke above lava looks right for plume. ``` Rendering lava: heat 1.0→0 mapped to sprite stages: - 1.0–0.85: white-hot core sprite - 0.85–0.65: yellow - 0.65–0.45: orange - 0.45–0.28: red - 0.28–0.12: dark red (still additive but low alpha) - <0.12: dark rock — draw with normal composite small dark dot, or just skip drawing and let a "rock" stage draw as dark crumb with normal alpha fading. Simplest: stages 0..5 array of sprites; last stage drawn normal-composite. To avoid composite switching per particle, batch: iterate once for hot (lighter), and collect rock-stage indices in a second pass. Or just set composite per particle — bad. Two loops over the array with an if on heat: loop1 lighter-draws heat>0.12, loop2 normal-draws the rest (few). Fine. Sprite pre-render: ``` makeSprite(color stops, size=32): radial gradient center white-ish → color → transparent. Stages: s0: #fff8e5 core → #ffd24d → rgba(255,140,20,0) s1: #ffe27a → #ff9a1f → transparent s2: #ff9a3c → #ff5416 → transparent s3: #ff5e3a → rgba(180,30,10,0) s4: rgba(255,77,20,..) dim smoke: gray radial soft. ``` Particles also flicker: scale = base * (0.85+0.3*rand) cached per particle, plus time flicker cheap: use particle's seed with sin(time*freq+phase) — a sin per particle is okay-ish; cheaper: precomputed flicker array. I'll just use `(Math.sin(t*10+p.seed) *0.15+0.95)` — 1000 sins/frame is fine. Wind visualization: array of ~40 wind streak particles in sky: x drifts with wind*40 + slight per-particle speed, wrap around, draw as short line with low alpha, alpha scales with |wind|. Nice. Screen shake during eruption: shakeAmp tied to eruption intensity, applied as translate before drawing world (not UI since UI is DOM). Crater glow: pressure-driven: glowIntensity = max(pressure, eruptionBurst). Draw radial glow (additive) at crater, plus rim light on cone edges? Rim light is complex; the additive glow over terrain cache gives the impression. Also inner crater lava pool visible: draw bright line/ellipse at crater mouth whose brightness pulses with pressure. Pressure meter (DOM): vertical gauge on right side; fill bar height = pressure; ticks; label "PRESSURE" vertical text; percentage readout; warning color when >0.75 (crimson pulse). Build with plain divs + CSS. Add a "tremor" class that jitters slightly at high pressure. Eruption status line: "DORMANT → building" / "ERUPTING" indicator with small blinking dot. Controls panel bottom-left: three sliders, custom styled (accent-color or full custom webkit styling — I'll style thumb and track with CSS for the aesthetic). Values shown live. Particle amount slider changes pool cap live: cap 300–3000 (default 1600). When cap lowered, excess particles just die out naturally (don't force-kill, or kill oldest when spawning — spawn guard uses active count). Counter top-left: PARTICLES n / FPS n, updated 4×/s. Also maybe show wind direction arrow. Keep it. Signature moment: the eruption itself — pressure needle trembling, ground shake, then a fountain of glowing lava arcing with additive bloom, sparks raining onto slopes and creeping downhill while the ash plume leans with the wind. Also: lava landing creates brief glow spots on slope (the sliding particles themselves do this). Add sound? No audio requested; skip (autoplay issues anyway). Maybe a subtle "boom" via WebAudio on user interaction... skip, keep focused. Title overlay: small, elegant: "VULCAN / stratovolcano simulator" — restrained type, top center-left. Use "Instrument Serif" italic for the wordmark + IBM Plex Mono for data. Nice authored feel. Let me also handle resize: rebuild caches, recompute terrain; particles keep going (terrain array replaced — positions may mismatch; acceptable; or scale x proportionally — just rebuild). DPR: cap at 1.5 for perf on hidpi? Cap 2 but with heavy particles maybe 1.5. I'll cap DPR at 1.75. Now the terrain noise: I'll write a small value-noise (sum of sines with irrational freqs) for ground undulation far from volcano, and slight ruggedness on cone flanks (but ensure crater zone smooth). Terrain render (cached): fill silhouette dark (#17120f-ish), then add: - slope texture: random short strokes slightly lighter/darker along the surface. - a few faint ridge lines from crater downward (darker strokes) for structure. - ground foreground slightly different dark with sparse dead-tree silhouettes? Might add tiny distant trees for scale — small triangle-ish pine silhouettes on the flanks far from crater, very dark. Adds authored charm. A handful of pines near base on both sides. Also a thin mist band at ground horizon in background cache. Background cache: sky vertical gradient (deep #0b0e1d top → #1a1626 → dusk band #3d1f1c near horizon? Let me pick: top #070a18, mid #141328, low #35201e, horizon #54291a — subtle). Stars (random dots, alpha varies, few twinkle? static fine, or a handful drawn per-frame twinkle — keep static + a couple of brighter ones). Distant mountain silhouettes (two layered ridges, bluish dark #12142a / #0d0f1f) behind the volcano. Moon? A small pale disc high on the side with slight halo — tasteful, adds composition. Yes, small moon upper-left (or opposite the prevailing light). I'll place moon at ~ (0.18W, 0.2H). Foreground: maybe a dark foreground ground strip in front (slightly below camera) — the terrain ground already handles it. Ash render: smoke sprites drawn with `globalAlpha`, normal composite, colored dark → they'll darken sky plume. During night scene, ash plume should be dark charcoal with lit underside? Simpler: soft gray-brown puffs alpha ~0.18 each, hundreds overlap → convincing plume. Also at night a plume lit from below by crater glow: draw two sprite types — dark ash sprite and an ember-lit variant near crater mouth (first ~0.5s of life use warm-lit sprite tinted orange, additive). Nice detail: young smoke particles get a warm-lit version that fades. I'll do: smoke has `age`; if age < 0.8, additionally draw small warm sprite with alpha (1-age/0.8)*0.3 using lighter — only for young ones, cheap. Emission scheduling: use fractional accumulators: ``` eruptEmitAcc += eruptRate*dt; while(acc>1){spawnLava(); acc--} smokeAcc += smokeRate*dt; ... ``` Pool: arrays of objects with `active` flag + free list (stack of indices). activeCount tracked. Spawning pops from free stack; killing pushes back. Simple. Also lava that flies off-screen sides: kill when x<−50 or x>W+50 or y>H+50 (below ground can't happen since terrain covers full width? Terrain covers full width — ground at bottom everywhere, so landing catches everything). Wind can push lava past edges — kill if outside. Sliding physics tuning: slope from heightmap in px/px; typical slope ~ (groundY-peakY)/(halfWidth) ≈ 0.4H/0.34W... e.g., H=800: rise 320 over run 400 → slope ~0.8 near mid. slideAccel = slope*900*dt, friction: v *= exp(-1.6*dt) roughly `v *= 1/(1+1.8*dt)`. Terminal-ish speed fine. Settle when |v|<8 and |slope|<0.12 → still. While sliding heat -= 0.25*dt (≈4s visible creep). Still: heat -= 0.35*dt. Air: heat -= 0.06*dt. Plus lifetime cap 20s hard. Bounce: on landing if vy>140 && heat>0.5: vy=-vy*0.22, vx*=0.6, stay airborne; else land. Maybe spawn spark on hard impact: if vy>260 and pool room: spawn 1–2 with random small v. Keep. G (gravity) ~ 340 px/s² scaled with H? Scale physics by canvas height: G = H*0.45? For H≈900 → 400. I'll compute from H so it feels consistent: G = Math.max(280, H*0.5). Velocities similarly scale by H/900 factor. Simpler: define `S = H/900` scale factor used in spawn velocities and gravity. Do that. FPS measurement: ema of 1/dt; update DOM every 300ms. Particle count: count active each frame (we track activeCount already via pool ops) — display it. Pressure build rate: full in ~9–14s: `pressure += dt*(0.085 + 0.02*sin(t*0.7))`; add tremor near >0.7 (screen micro-shake). Eruption duration D = 2.2 + power*3.3; pressure decreases dt/D... but pressure should reset and rebuild — during eruption drain to 0. Also eruption intensity envelope: `env = sin(pi * progress)`? Nicer: intensity = smooth pulse: start strong, decay: `env = Math.pow(1-progress, 0.7)` with initial ramp 0.15s. Use env for emission rate and shake. Power slider (0.2–2, default 1): scales launch speed, emission rate, duration. Wind slider (-10..10, default 2): windAbs/10 → windX = v*10 px/s for ash... tune: ash vx target = wind*18*S. Lava air drag toward wind: vx += (wind*8*S - vx)*0.12*dt... simpler vx += wind*6*S*dt. Wind streaks speed = wind*30. Smoke emission: base trickle rate 3/s; during eruption 30*power*env /s; post-eruption decay over few seconds. Track `activity` variable (0..1) that jumps to 1 at eruption start and decays: activity = max(env, activity - dt*0.25). smokeRate = 4 + 60*activity*power... plus crater pool glow follows activity too. Also lava fountain should spread: vx = (rand-.5)*(140+power*160)*S with slight bias outward both sides; vy = -(300 + rand*260*power)*S... need arcs to reach slopes not just straight up — crater dip means particles rise and fall back into crater? Funnel: particles spawn slightly above crater mouth at varying x within crater width; those near edges may hit inner crater walls and slide into the crater — actually that's realistic (lava lake). Collision handles it: they land on crater floor and slide down-slope... crater interior slopes down toward center — sliding logic follows terrain gradient so they'd pool at crater bottom. That could trap all lava! Crater floor is below rim; particles landing inside will settle in the crater and die there with a glow — actually a glowing lava lake in the crater is gorgeous and realistic. But too many trapped makes few land on slopes. Launch velocities mostly exceed rim height, and spawn positions + outward vx bias will carry most over the rim. I'll bias vx slightly by sign of x offset from center. Also make crater shallow-ish. Ash rises from crater: spawn at crater mouth, initial vy = -(40+..)*S, buoyant rise slows... ash should rise above rim and billow upward — since crater mouth is at dip, ash must climb out of funnel: give strong initial rise vy=-(120+rand*80) and buoyancy: vy += (-50*S - vy)*0.8*dt (approach terminal rise). Turbulence: vx += (wind*windAsh + sin(t*1.3+seed*7)*14 - vx)*0.9*dt. Size grows: r += growth*dt. Alpha: fade in first 0.3s, fade out last 30% of life. Life 5–9s. Smoke sprite: radial gradient rgba(70,66,72,0.5)→transparent, plus lighter variant for young lit ones rgba(255,140,60,...). Composite batching plan per frame: 1. drawImage(bgCache) 2. wind streaks (light strokes) 3. drawImage(terrainCache) 4. crater glow: additive radial (pre-rendered glow sprite scaled) with alpha = f(activity, pressure), flicker. 5. moon halo already in bg. 6. Lava hot: `lighter`, loop draw. 7. Lava rock-stage: normal, loop. 8. Smoke: normal, loop (smoke above lava — plume occludes fountain partially, good). 9. Foreground vignette? subtle bottom vignette in bg cache top layer? A slight vignette adds mood — draw in bg cache corners. Keep subtle. Shake: ctx.translate(shakeX, shakeY) around steps 1–8; shake = tremor + eruption * power * random. DOM structure: - canvas#scene - header .brand: title + subtitle - .readout (top-left below brand or top-right): FPS, particles, status - .gauge (right side): pressure bar + % - .controls (bottom-left): sliders with labels + value spans - small hint text bottom-right maybe "drag sliders live". Fonts: Google Fonts link (IBM Plex Mono 400/600 + Instrument Serif italic). Accent color: lava amber #ffb15e / crimson #e8452c. Panel: rgba(10,10,14,0.55) blur backdrop, 1px border rgba(255,255,255,0.08). Slider styling: custom track (2px line) + round thumb with amber fill using accent-color? Custom webkit: I'll write -webkit-slider-thumb styles, plus Firefox ::-moz-range-thumb. Keep concise. Gauge: container height ~ 200px, 10px wide track, fill div height %. Ticks: 5 hairlines via absolutely positioned divs or background repeating-linear-gradient (functional). Percent label under, plus "PRESSURE" rotated. Pulse class when >0.8. Status text: DORMANT / TREMOR / ERUPTING with dot color. Let me now write the code carefully. I'll aim for clean, well-commented, ~600 lines. Key constants scaled on resize: ``` S = H/900 G = 430*S ``` Terrain function detail: ``` const cx = W*0.5; const halfW = Math.min(W*0.36, 520); const groundY = H*0.84; const peakH = Math.min(H*0.48, 430); const peakY = groundY - peakH; const craterHalf = Math.max(26, halfW*0.14); function heightAt(x){ let y; const d = Math.abs(x-cx); if(d >= halfW){ y = groundY; } else { const t = d/halfW; // concave cone flank: y = peakY + (groundY-peakY)*Math.pow(t,1.5)? pow(t,1.5) at t small rises steeply from peak... At t=0 (center) y=peakY. Slope near peak infinite for pow<1... pow(t,1.5): derivative 1.5 t^0.5 → 0 at peak (rounded peak) and steep near base? d/dt at t=1 =1.5 → steeper at base. Real cones are steeper near top. Use y = peakY + span * (t^0.8)? derivative of t^0.8 at 0 → infinite... For a volcano: straightish flanks slightly concave. Use t^1.15 maybe: at peak slope 0 (rounded), base slope >1. Hmm real stratovolcano: flanks ~30°, slightly concave (steeper up top). t^0.85 gives steeper near peak (since for small t, t^0.85 > t, meaning y further from peak... wait y = peakY + span*t^p; at small t with p<1, y rises quickly → steep near peak. Yes p<1 → concave, steep near peak. p=0.7. y = peakY + (groundY-peakY)*Math.pow(t,0.72); } // crater funnel if(d < craterHalf){ const ct = 1 - d/craterHalf; // 1 at center y += ct*ct * Math.min(34, peakH*0.12) * (something) // dip // funnel shape: smooth } // rim smoothness: blend is fine since crater dip is 0 at d=craterHalf. // noise: const n = noiseVal(x); // reduce noise near crater for clean mouth: const mask = Math.min(1, Math.max(0,(d - craterHalf) / (halfW*0.25))); y += n * mask; return y; } noiseVal(x) = sin(x*0.011)*4 + sin(x*0.023+1.7)*3 + sin(x*0.005)*6 — plus ground rolling far from volcano: if d>halfW add rolling hills sin(x*0.004)*10*sin(...)? Keep: n applies everywhere scaled. ``` Ground should be roughly flat for particles to rest; small ±8px noise ok. Also gentle randomness per load: phase offsets from Math.random at init so each load differs slightly. But heightAt must be deterministic during a session — store phases. Build terrain array: `terr = new Float32Array(W+2)` for x from -1..W. Index Math.round(x) clamped. Terrain cache rendering: path from (0,terr[0]) along points → down to bottom corners → fill. Then texture strokes: iterate x step 3: draw short vertical-ish streaks below surface with rgba lighten/darken random. Plus grass? No — dark volcanic sand: sparse lighter speckles. Plus pines: place ~14 pines at random x where slope gentle and d>halfW*0.75 or on far ground; draw simple triangles stacked, very dark #0a0806. Also draw faint warm reflection strip at crater interior: a soft glow at crater mouth in terrain cache? Glow is dynamic; skip in cache. Crater lava pool: dynamic — at crater floor, draw ellipse of bright lava with pulsing alpha tied to activity/pressure: fillStyle gradient? Just draw glow sprite at crater mouth center (cx, craterFloorY) with 'lighter', scale pulsing. This covers pool visual. Ember sparks from fountain: handled via same lava pool (they're just small heat=1 particles). Fine. Numbers: default cap 1600; eruption rate ~ power*420 /s while env high → bursts of ~800 live. Smoke cap separate ~ 900 (its own pool, count separately? Counter shows total). Particle amount slider scales BOTH caps: lavaCap = amount, smokeCap = amount*0.6. Default amount 1600 → smoke 960. OK. Pool implementation: ``` function makePool(cap, factory){ arr = []; free = stack indices 0..cap-1; active=0 } spawn: if(free.length) idx=free.pop() else steal? When full, optionally reuse oldest: for lava, if full, skip (or steal a settled particle). Stealing settled rocks keeps fountains alive — I'll steal: maintain a ring? Simpler: if free empty, don't spawn (sliders cap it; user sees count plateau). Acceptable and honest. kill(i): arr[i].active=false; free.push(i); activeCount-- ``` Iterating all cap entries each frame with active check: cap up to 3000+1800, two loops — fine. FPS with 4800 iterations + draws: fine on canvas. Draw lava: size varies: r = 3+heat*4 * S ... air particles slightly larger with glow. drawImage(sprite, x-r, y-r, 2r, 2r). alpha = clamp(heat*1.6, 0.15, 1) for hot; rock stage alpha = heat/0.12 * 0.8. Sprite stage selection: heat thresholds: >0.8 s0, >0.62 s1, >0.45 s2, >0.3 s3, >0.14 s4, else rock (normal composite dark dot). Rock drawing: fillStyle set once outside loop, per particle alpha — use globalAlpha per particle + fillRect small (cheaper than sprite): fillRect(x-1.5,y-1.5,3,3) with rgba(30,20,16, a). Batch: set fillStyle once; loop set globalAlpha & fillRect. Wait: dark rocks against dark terrain invisible — fine, they fade out anyway; visible mainly against sky. Good. Ash drawing: globalAlpha per particle, drawImage smoke sprite scaled to r. Dark plume at night should read against sky — use mid-gray rgba(120,110,115) tint sprite with low alpha, layered → plume reads. Also warm-lit young ones. Wind streaks: 50 particles {x,y,len,spd,depth}; speed = wind*36*(0.5+depth); x+=speed*dt; wrap; alpha = min(0.25, |wind|*0.028)*depth; draw as 1px lines with slight downward slope? Keep horizontal thin lines rgba(255,255,255,α). Only when |wind|>0.4. Now UI details & copy (English, since request is English): Brand: "STRATOVOLCANO" small caps mono + big italic serif "Mt. Kessler"? Make up a name: "Vulcania" ... I'll title it "OPEN-VENT VOLCANO — live simulation" hmm. Signature: name the volcano "Monte Aria". Title: italic serif "Monte Aria" + mono sub "STRATOVOLCANO · LIVE SIMULATION". Nice. Readout panel (top-right): - FPS: 60 - PARTICLES: 1247 - STATUS: ● DORMANT / TREMOR / ERUPTING - WIND: → 3.2 (direction arrow via character? no emoji — use a small inline SVG arrow or text like "E 3.2 m/s"). I'll show "WIND: ←/→ value" using CSS triangle? Simplest: text "E" / "W" based on sign, mono. Fine: `WIND E 4.0`. Gauge panel (right, below readout or left side?): Place gauge bottom-right vertical, controls bottom-left. Readout top-right. Brand top-left. Mobile: panels small enough; add media query stacking. Keep basic responsiveness. Controls: sliders with label row (name + value). - POWER: 0.2–2.0 step .05, default 1.0, shown "1.00×" - WIND: −10–10 step .5 default 2.5, shown "→ 2.5" - PARTICLES: 300–3000 step 50 default 1600 shown "1600" Implement slider input events updating state; wind change affects immediately. Pointer events on canvas? Not required. Maybe click crater to force... not required; skip to keep focus. Actually a small delight: clicking the canvas adds a "pressure surge"? Might confuse. Skip. Let me write the eruption state machine: ``` state: 'build' | 'erupt' pressure 0..1 eruptT (elapsed), eruptDur update: if build: pressure += dt*rate; if pressure>=1: startErupt() startErupt: eruptDur = 2.2+power*3.6; eruptT=0; state='erupt'; activity=1; shakeBurst if erupt: eruptT+=dt; prog=eruptT/eruptDur; env = prog<0.08? prog/0.08 : Math.pow(1-(prog-0.08)/0.92, 0.85); pressure = 1-prog (clamp); emission uses env; if prog>=1: state='build'; pressure=0; activity lingers activity = max(env, activity - dt*0.3) ``` Tremor: tremor = state==='erupt'? env : Math.max(0, pressure-0.72)/0.28 * 0.5; shakeAmp = tremor*4*S (+ initial burst). Emission rates: ``` lavaRate = (60 + 380*power) * env * S? rate in particles/s: up to ~440 at power 1. With eruptDur ~5.8s at power1 → 2500 spawned over eruption but pool 1600 with lifetimes... lifetimes: flight ~2.5s, slide ~4-6s → many still alive near end; pool full → skips. Fine, cap works. spawnLava: x = cx + (rand-.5)*craterHalf*1.2; y = craterMouthY - 4; ang: base up, spread: vx = (rand()-.5)*(90+240*power)*S + sign(x-cx)*30*S; vy = -(280 + rand()*(240+320*power))*S... at power 2: vy up to -(280+1800*?) let's compute: 240+320*2=880; vy up to -(280+880)*S... too much? S~1: vy ∈ -(280..1160)? At power 1: -(280..840)*S. With G=430S: apex = vy²/2G ≈ 840²/860 ≈ 820px — off screen top. Hmm H=900, crater at ~H*0.36≈324 from top... apex 820 above → y=-496 offscreen. Too strong. Reintroduce: max vy should send particles near top of screen: available rise ≈ 300px (crater y 324 → apex ~40). vy_max = sqrt(2*G*300) ≈ sqrt(2*430*300) ≈ 508. So vy = -(180 + rand()*(180+150*power))*S → power1: 180..510; power2: 180..660 (some exit top briefly — dramatic, fine, they come back down; particles above screen: don't kill them, keep updating, they fall back. Only kill x out of range. Good, leave y uncapped.) vx spread: (rand-.5)*(120+180*power)*S + outward bias 25S. Wind adds during flight. r (size): 2+rand*2.6 (*S for glow radius factor) heat=1; mode air; seed rand*6.28; slideV=0. ``` Smoke spawn: x = cx+(rand-.5)*craterHalf; y = mouthY-6; life = 4+rand*4; r0=(8+rand*14)*S; growth (6+rand*10)*S; vy=-(50+rand*70)*S; buoy target -(60+20*rand)*S... during high activity stronger: vy0 = -(60+rand*90)*S*(0.5+activity). vx initial = wind*10S. Smoke update: ``` age+=dt; if age>life kill. turb = sin(t*1.1+seed)* 18*S + sin(t*2.3+seed*3)*9*S vx += (wind*14*S*windExp? + turb - vx)*0.7*dt vy += (targetRise - vy)*0.8*dt where targetRise = -(30+40)*S * (1 - 0.5*age/life)?? buoyancy decays with age as cloud cools: targetRise = -(20+50*(1-age/life))*S r = r0 + growth*age... plus slight ease. alpha envelope: a = sin-ish: fadeIn = min(1, age/0.4); fadeOut = 1 - max(0,(age-life*0.65)/(life*0.35)); alpha = 0.16*fadeIn*fadeOut*? maybe up to 0.2. young glow: if age<0.9: warm overlay alpha (1-age/0.9)*0.35*activity-ish (just age-based). ``` Crater glow draw: ``` glow = 0.15 + pressure*0.5 + activity*0.9 (+flicker sin(t*13)*0.08) draw glowSprite (warm radial) at (cx, mouthY): width = craterHalf*6*..., alpha=glow*0.5, 'lighter' Also a vertical flare during eruption: second glow stretched tall (drawImage with big height, small width) alpha=activity*0.5. ``` Mouth Y: craterFloorY = heightAt(cx). Compute from terrain array: terr[round(cx)]. Lava landing inside crater: slide toward center then settle & die glowing → pool glow region gets extra brightness implicitly from particles. Kill conditions lava: heat<=0 or x<−60||x>W+60. Also y> H+80 safety. Slide code: ``` const xi = clamp(Math.round(p.x),1,W-1); const s = (terr[xi+1]-terr[xi-1])*0.5; // positive → down to right (screen y down) p.slideV += s*1400*S*dt; p.slideV *= Math.max(0, 1-2.2*dt); p.slideV = clamp(p.slideV, -260*S, 260*S); p.x += p.slideV*dt; p.y = terr[clamp(round(p.x))]-1; if(Math.abs(p.slideV)<9*S && Math.abs(s)<0.14){ p.mode=2 (still) } heat -= (0.16 + Math.abs(p.slideV)*0.0009)*dt*? Let's: sliding cools at 0.14/s + movement-dependent small; still cools 0.3/s → still lasts ~3.3s after settling. Also hard lifetime 16s. ``` Hmm heat from 1: air cools 0.05/s (flight ~2.5s → 0.87 remaining — lands bright orange ✓). Slide: land heat ~0.85; slide cooling 0.14/s → ~4s sliding bright→red ✓. Still 0.3/s → fades to rock. Total life ~10s. With rate 440/s and ~9s avg life → 3900 needed but cap 1600 → skips spawn late in eruption; visually eruption tapers because pool fills — acceptable, actually natural (fountain weakens as plateau fills). Slightly reduce rates: lavaRate = 40+320*power → power1: 360/s. OK. Actually, sliding particles going downhill off the cone onto flat ground spread out — nice: lava flows reach ground level. slope near base ~ derivative of pow(0.72) at t→1: span/halfW*0.72 → with span 380, halfW 420: 0.65 → decent. On flat ground s≈noise small → they settle. Bounce logic on landing: ``` if(p.vy > 150*S && p.heat>0.4 && rand()<0.5){ p.vy*=-0.2; p.vx*=0.5; p.y = terr-2; stays air } else { mode=1(slide); p.slideV = p.vx*0.4; if(|vy|>240S && poolHasRoom) spawn 1-2 sparks } ``` Sparks: spawn with vx=±(20..120)S, vy=-(40..160)S, heat=0.9, small r. Guard: only if activeCount < cap*0.9 (avoid cascade). Also while airborne: mild wind coupling: p.vx += (wind*6*S - 0)*dt*?? simple: p.vx += wind*7*S*dt. And slight drag: vx*=1-0.05*dt? skip drag. Frame loop: ``` function frame(ts){ dt = clamp((ts-last)/1000, 0, 0.033); last=ts; t+=dt; update(dt); render(); raf } ``` Use requestAnimationFrame; physics per-frame with dt (variable but clamped — fine for this). FPS: fps = fps*0.92 + (1/dtRaw)*0.08 using raw dt before clamp. Update DOM readouts every 0.25s: fps rounded, activeCount (lava.active + smoke.active), wind text, status text+class, gauge fill height + percent, gauge pulse class when pressure>0.8. Wind text: `wind>=0? '→ E':'← W'` plus value. Using arrows "→" is a text glyph, fine (not emoji). Gauge markup: ```
PRESSURE
0%
``` Vertical: track 14px wide, 180 tall, border hairline, fill bottom-anchored (position absolute bottom, height %). Fill color: amber → crimson when >0.8 (class swap, solid colors). Ticks: repeating-linear-gradient on a separate ticks layer — functional scale marks. OK. Status dot: span with class erupt/tremor/dormant → color crimson/amber/gray-green. Controls markup: ```
ERUPTION POWER1.00×
... wind, particles
``` CSS custom range: ``` input[type=range]{ -webkit-appearance:none; background:transparent; width:200px; height:18px } ::-webkit-slider-runnable-track{ height:2px; background:rgba(255,255,255,.18) } ::-webkit-slider-thumb{ -webkit-appearance:none; width:12px;height:12px;border-radius:50%;background:#ffb15e;margin-top:-5px; border:1px solid #0008; box-shadow:0 0 0 2px rgba(255,177,94,.15)} ::-moz-range-track{...} ::-moz-range-thumb{...} ``` Also filled portion of track? Extra JS to set background gradient per value — I'll skip; thumb is clear enough. Actually a subtle filled track improves scannability: set track background via JS as linear-gradient two-stop (functional indicator). I'll do it: `el.style.background = linear-gradient(90deg, #ffb15e p%, rgba(...) p%)` applied to the input with appearance none and track transparent... For webkit, styling the input element background directly with track transparent works. I'll implement small helper updateSliderFill. Panels: position absolute; pointer-events auto; canvas full-viewport fixed. Tremor shake also nudges the DOM panels slightly? CSS class with transform jitter via JS: apply translate to a #ui wrapper: small (1-2px) at high tremor — adds physicality. Cheap: set style.transform on body panels container each frame from shake values. Do it — UI shaking with the ground is a great touch. Moon: disc radius 26 with halo in bg cache at (W*0.16, H*0.18), color #d8d4c8 alpha, few craters darker dots. Subtle. Stars: 140 dots random above horizon, alpha 0.2–0.9 by random, size 1px, few 2px. Avoid drawing stars behind volcano? They're behind — fine since terrain drawn after. Distant ridges: two polylines with noise, colors #131530 and #0e1024, spanning width at y ~ groundY-160 and groundY-90 — wait volcano base at groundY; distant ridges should be higher horizon: ridge tops around groundY - 140. Draw ridge1 (farther, lighter #1a1c38) then ridge2 (#111327). These sit behind volcano. Then foreground terrain (near-black #14100c) drawn over. Hmm layering: sky → stars → moon → ridges → [bg cache done] → per-frame: bg, streaks, terrain cache, glow, lava, smoke, shake. Mist band: after ridges, translucent horizontal band rgba(180,140,160,0.05)? Skip or very subtle gradient band above ground — I'll add a faint horizon haze rect gradient in bg. Terrain fill color: #120d09. Slope texture: for i in steps: random strokes rgba(255,255,255,0.02–0.04) and rgba(0,0,0,0.3) short lines tangent to slope. Also crater interior: slight warm tint static? dynamic glow covers it. Also add a faint rim highlight on the crater lip facing the glow? The dynamic additive glow at crater will bleed onto rim via sprite — good enough. Pines: only on outer ground (d > halfW*1.05), heights 8–18px: triangle pairs + trunk, color #0a0705. ~18 pines random x, y = heightAt(x). Draw into terrain cache. Now let's also ensure "at least 1000 active particles smooth": use additive drawImage of 32px sprites scaled ~10px — 1600 draws + 900 smoke: fine. One perf nuance: `ctx.drawImage` with per-call globalAlpha is fine. Write DPR handling: canvas.width = W*dpr; ctx.setTransform(dpr,0,0,dpr,0,0). All logic in CSS pixels. Caches at same dpr — create offscreen canvas w=W*dpr etc., draw with setTransform, then drawImage(cache,0,0,W,H) with main ctx (which has dpr transform) — drawImage(cache, 0,0, W, H) will scale down correctly? cache pixel size = W*dpr; drawing at W,H under dpr transform → device size W*dpr — 1:1. ✓. Resize: debounce-lite (rebuild on resize event directly). Let me write noise for terrain: ``` const ph1=rand()*6.28, ph2..., ph3... function terrNoise(x){ return Math.sin(x*0.010+ph1)*5 + Math.sin(x*0.027+ph2)*2.6 + Math.sin(x*0.0043+ph3)*7; } ``` Ground flatness: multiply noise by mask that's smaller on ground? Ground noise ±10 fine for sliding (settles anywhere slope small — noise slope up to 10*0.027≈0.3? derivative: 5*0.010+2.6*0.027+7*0.0043 ≈ 0.05+0.07+0.03=0.15 max — gentle ✓). Cone texture noise mask uses (d-craterHalf)/(halfW*0.3) clamp 0..1 as said. Crater dip: depth = Math.min(40, peakH*0.14): with peakH 400 → 40. dip = smooth: `dip * (0.5+0.5*Math.cos(ct*Math.PI))`? Let ct = 1-d/craterHalf ∈0..1; want dip 0 at rim, max at center: dip * ct^1.6? Use ct*ct*(3-2ct*?) — simple: dipVal = depth * ct*ct*(3-2*ct)?? that's smoothstep on ct — max depth*1 at center ✓ zero at rim ✓ smooth ✓. Mouth Y for spawns: terr[cx] gives crater floor; spawn slightly above: craterFloorY - 6. But particles spawned at center-bottom must clear rim (rimY = peakY). Rise needed = depth + margin ≈ 50px → fine with vy≥180. Wait: particles spawn inside funnel below rim; those with low vy (180) rise 180²/860 ≈ 37px < depth 40 → fall back into crater, glow there briefly — realistic spatter, small fraction. Good. But ensure not TOO many trapped: vy base 180+ with rand*(180+150power): ~15% below rise-50 threshold... they die in crater — fine, adds lava lake glow. Ash spawns also at floor — must rise 40+ to escape; vy0 -(60..150)*S with buoyancy target negative → escapes ✓. Glow position: at crater floor center. Let me now also think about "wind affects eruption plume lean": ash handled. Lava arcs: wind 10 → vx += 70*S*dt over 2s flight → ~+100px drift — visible ✓. Streaks show wind always. Edge case: wind extreme negative pushes lava left beyond x<−60 → killed while still hot — maybe wrap? Just kill; also spawn bias unaffected. Fine. Counter should show lava+smoke total. Now write it all. I'll be careful with code quality and comments. Also the panel shake: I'll wrap all UI in #ui div and translate by shake each frame (only when >0.3px to avoid layout thrash — setting transform is cheap, do always). Let me draft the full file mentally, then output. Structure: ```html Monte Aria — Stratovolcano Simulation

Monte Aria

STRATOVOLCANO · LIVE SIMULATION

STATUSDORMANT
PARTICLES0
FPS--
WIND→ 2.5
PRESSURE0%
3 sliders
``` Gauge: vertical bar 12×170. Fill from bottom. CSS palette: - panel bg: rgba(8,9,14,.58); border:1px solid rgba(255,255,255,.09); border-radius 10px; backdrop-filter blur(6px). - text: #e8e4dc-ish; keys: rgba(232,228,220,.45) 10px letter-spacing 0.12em. - accent amber #ffb15e; crimson #ff4b2e. - Title: Instrument Serif italic 30px #f2ead9; subtitle mono 10px letterspaced. Gauge fill classes: default #e8a24a; .hot → #ff4b2e with pulse animation. Status dot: .dormant #6f8f6a? Actually dormant gray #7a8577; tremor #ffb15e blinking; erupt #ff4b2e blinking fast. Now the JS. Let me write it fully. ```js 'use strict'; const cv = document.getElementById('scene'); const ctx = cv.getContext('2d'); let W=0,H=0,DPR=1,S=1,G=400; // ---- state ---- const state = { power: 1, wind: 2.5, amount: 1600, pressure: 0, mode:'build', eruptT:0, eruptDur:1, env:0, activity:0, t:0 }; // terrain let terr=null, cx=0, halfW=0, groundY=0, peakY=0, craterHalf=0, mouthY=0; const rnd = Math.random; let nph=[0,0,0]; function heightAt(x){...} ``` heightAt: ```js function heightAt(x){ const d = Math.abs(x-cx); let y; if(d>=halfW){ y = groundY; } else { const tt = d/halfW; y = peakY + (groundY-peakY)*Math.pow(tt,0.72); } if(d=halfW) y=groundY + noise*mask(=1) → rolling ±14. ok. buildTerrain(): fill Float32Array(W+2) with heightAt(x-1)... simpler: terr[i] = heightAt(i) for i in 0..W. Access guarded by clamped idx. Sprites: ```js function makeSprite(sz, stops){ const c=document.createElement('canvas'); c.width=c.height=sz; const g=c.getContext('2d'); const gr=g.createRadialGradient(sz/2,sz/2,0,sz/2,sz/2,sz/2); for(const [o,col] of stops) gr.addColorStop(o,col); g.fillStyle=gr; g.fillRect(0,0,sz,sz); return c; } const SPR = [ makeSprite(48, [[0,'rgba(255,250,230,1)'],[0.25,'rgba(255,214,110,0.95)'],[0.6,'rgba(255,140,40,0.45)'],[1,'rgba(255,120,30,0)']]), //white-hot [[0,'rgba(255,225,150,1)'],[0.3,'rgba(255,160,60,0.9)'],[1,'rgba(255,110,30,0)']], [[0,'rgba(255,170,80,0.95)'],[0.35,'rgba(255,90,30,0.75)'],[1,'rgba(230,60,20,0)']], [[0,'rgba(255,110,60,0.9)'],[0.4,'rgba(210,50,20,0.55)'],[1,'rgba(160,30,10,0)']], [[0,'rgba(200,60,30,0.7)'],[0.5,'rgba(120,25,12,0.35)'],[1,'rgba(90,18,8,0)']], ]; const SPR_SMOKE = makeSprite(64, [[0,'rgba(96,92,100,0.42)'],[0.5,'rgba(70,66,76,0.22)'],[1,'rgba(60,56,66,0)']]); const SPR_WARM = makeSprite(48, [[0,'rgba(255,190,110,0.5)'],[0.5,'rgba(255,120,50,0.22)'],[1,'rgba(255,90,40,0)']]); const SPR_GLOW = makeSprite(64, [[0,'rgba(255,180,90,0.85)'],[0.4,'rgba(255,110,40,0.35)'],[1,'rgba(255,80,30,0)']]); ``` Pools: ```js function makePool(cap){ return { arr: new Array(cap).fill(null).map(()=>({on:false})), free: ..., n:0, cap } } ``` Simplify: preallocate objects with fields. free = Array of idx desc. spawn pushes fields. Lava fields: x,y,vx,vy,heat,r,mode(0 air,1 slide,2 still),seed,age Smoke fields: x,y,vx,vy,r,r0,gr,life,age,seed spawnLava(spark): ```js function spawnLava(x,y,vx,vy,heat,r){ if(lava.free.length===0) return; const i=lava.free.pop(); const p=lava.arr[i]; ... set; lava.n++; } ``` Kill: function killLava(i){ p.on=false; lava.free.push(i); lava.n--; } — careful: free array could get duplicates if kill called twice; guard with p.on check at call sites. I'll write update loop: if(!p.on) continue; and kill only sets on=false and pushes. Spawn checks free.length. Kill called once per particle per frame at most (in its own update). Safe. Emission: ```js let lavaAcc=0, smokeAcc=0; function emit(dt){ const P=state; const act = P.activity; // lava during eruption if(P.mode==='erupt'){ lavaAcc += (50+330*P.power)*P.env*dt; while(lavaAcc>=1){ lavaAcc--; spawnFountain(); } } smokeAcc += (3 + 70*act*Math.min(P.power,1.6))*dt; while(smokeAcc>=1){ smokeAcc--; spawnSmoke(); } } ``` spawnFountain: ```js const x0 = cx + (rnd()-0.5)*craterHalf*1.3; const y0 = mouthY - 4 - rnd()*6; const dir = Math.sign(x0-cx)||1; const vx = (rnd()-0.5)*(120+190*power)*S + dir*(20+rnd()*40)*S; const vy = -(170 + rnd()*(170+160*power))*S; spawnLava(x0,y0,vx,vy, 1, (2.1+rnd()*2.6)*S) ``` Hmm at power=2, vy up to -(170+500)= -670*S; apex 670²/860≈522 above mouth — mouth ~324 → apex y≈-198: exits top ~200px. Dramatic, ok. Most at power1: vy -170..-500 → apex 34..291 → within screen mostly ✓. Sparks on impact similar smaller. Update lava: ```js function stepLava(dt){ const wind = state.wind; for(let i=0;i=ty){ if(p.vy>150*S && p.heat>0.35 && rnd()<0.45){ p.vy=-p.vy*0.18; p.vx*=0.55; p.y=ty-2; } else { p.mode=1; p.slideV = p.vx*0.35; p.y=ty-1; if(p.vy>230*S && lava.nW+60){ kill; continue } p.heat -= 0.05*dt; } else if(p.mode===1){ const xi=clampX(Math.round(p.x)); const s = terr[Math.min(xi+1,W)]-terr[Math.max(xi-1,0)]; s/=2; p.slideV += s*1500*S*dt; p.slideV *= Math.max(0,1-2.4*dt); p.slideV = clamp(p.slideV,-240*S,240*S); p.x += p.slideV*dt; p.y = terr[clampX(Math.round(p.x))]-1; if(Math.abs(p.slideV)<10*S && Math.abs(s)<0.15) p.mode=2; p.heat -= (0.13 + Math.abs(p.slideV)*0.0004)*dt; } else { p.heat -= 0.30*dt; } p.age += dt; if(p.heat<=0 || p.age>18) kill; } } ``` Note: slideV should decay to 0 on flat: friction handles. Also mode1 on flat ground with noise slope: settles ✓. Smoke step as designed. Crater mouth glow pool: also draw a few persistent "lava lake" flicker: covered by glow sprite. Render: ```js function render(){ ctx.clearRect? just drawImage bg full cover. // shake const sh = shakeAmp; const sx=(rnd()-0.5)*2*sh, sy=(rnd()-0.5)*2*sh*0.6; ctx.save(); ctx.translate(sx,sy); ctx.drawImage(bg,0,0,W,H); drawWindStreaks(); ctx.drawImage(tr,0,0,W,H); // glow const glowA = 0.25*state.pressure + state.activity*0.85; if(glowA>0.02){ ctx.globalCompositeOperation='lighter'; const fl = 0.85+0.15*Math.sin(state.t*11)+0.06*Math.sin(state.t*29); const gw = craterHalf*(5+state.activity*3)*fl, gh = gw*0.55; ctx.globalAlpha = Math.min(0.9, glowA*0.75)*fl... clamp ctx.drawImage(SPR_GLOW, cx-gw/2, mouthY-gh/2, gw, gh); // vertical flare ctx.globalAlpha = Math.min(0.8, state.activity*0.55); const fw = craterHalf*2.2, fh = craterHalf*(7+state.activity*5); ctx.drawImage(SPR_GLOW, cx-fw/2, mouthY-fh, fw, fh); ctx.globalCompositeOperation='source-over'; ctx.globalAlpha=1; } // lava hot ctx.globalCompositeOperation='lighter'; for(...lava){ if(!on)continue; if(p.heat>0.13){ pick sprite by heat; a=..., r=p.r*(0.9+0.35*heat)+flicker; draw } } ctx.globalCompositeOperation='source-over'; // rock-stage ctx.fillStyle='#241610'; for(...){ if(on && heat<=0.13){ ctx.globalAlpha=Math.max(0,p.heat/0.13)*0.9; fillRect(p.x-1.6,p.y-1.6,3.2,3.2) } } ga=1 // smoke for smoke: draw SPR_SMOKE alpha env; young warm overlay lighter — batch: do normal loop; for young ones set lighter temporarily? Switching composites per particle costly if many young; young fraction small (0.9s of ~6s life → ~15%) — acceptable, or collect young into mini array during loop then draw after with lighter. I'll collect indices in a preallocated scratch array. ctx.restore(); } ``` Hmm smoke above lava: yes smoke drawn after. But hot lava fountain should be visible through plume partially — additive lava drawn before smoke means smoke covers; with smoke alpha ~0.15 each and overlapping, fountain still visible early. Fine — also realistic. Actually better order: smoke behind lava? Real fountain: ash plume surrounds; lava glows through. I'll draw smoke first, then lava additive on top — additive glow bleeding through smoke looks like lit cloud. Yes: smoke, then lava, then rocks? Rocks under smoke fine. Order: terrain → glow → smoke → lava(hot additive) → rocks. Rocks are dark, on terrain, fine under or over smoke. Keep rocks with smoke stage. Wind streaks: ```js const streaks=[]; init 46: {x:rnd()*W, y:rnd()*groundY*0.75, l:20+rnd()*40, d:0.4+rnd()*0.6} update+draw: sp = state.wind*38*d; x+=sp*dt; wrap by l; alpha=clamp(|wind|*0.03,0,0.3)*d*... draw line horizontal slight tilt: moveTo(x,y) lineTo(x+l*Math.sign(sp||1), y+2) ``` Line width 1, strokeStyle rgba(255,255,255,a). Batch in one path with same alpha? alpha varies — set per stroke; 46 strokes fine. Or group: single beginPath per alpha tier — overkill; 46 strokes/frame ok. Shake calc: ```js tremor = state.mode==='erupt' ? state.env : Math.max(0,(state.pressure-0.7)/0.3)*0.45; shakeAmp = (tremor*4.2*S + (burst? extra)) — burst: at eruption start set burst=1 decaying *= (1-3dt) → shakeAmp += burst*6*S. ``` Apply to UI: ui.style.transform = translate(sx*0.6, sy*0.6) rounded 2 decimals. Only when |amp|>0.2 else reset ''. Avoid string churn: build only when shaking. Pressure update: ```js if(mode==='build'){ pressure += dt*(0.085+0.03*Math.sin(t*0.6)); if(pressure>=1){ pressure=1; mode='erupt'; eruptT=0; eruptDur=2.3+power*3.8; burst=1; } }else{ eruptT+=dt; const prog=eruptT/eruptDur; env = prog<0.06 ? prog/0.06 : Math.pow(1-(prog-0.06)/0.94,0.9); pressure = Math.max(0,1-prog); if(prog>=1){ mode='build'; pressure=0; env=0; } } activity = Math.max(env, activity - dt*0.28); ``` DOM updates every 0.25s accumulate timer. Gauge fill height percent, class hot if>0.8. Wind text: `(wind>=0?'→ ':'← ')+Math.abs(wind).toFixed(1)` — arrows are fine typographically. Status text & dot class. Slider fill helper: ```js function fillSlider(el){ const min=+el.min,max=+el.max,v=+el.value; const p=(v-min)/(max-min)*100; el.style.background = `linear-gradient(90deg, rgba(255,177,94,.85) ${p}%, rgba(255,255,255,.14) ${p}%)`; } ``` With appearance:none and height set, background applies to whole input; track invisible? If I style track transparent and input height ~14px, background shows as block — set input height 3px? Thumb bigger than input gets clipped? -webkit thumb can overflow if input has no overflow hidden... Actually with appearance:none on input, thumb is positioned within; common pattern: input height 14px, background gradient applied to a wrapper. Simpler: apply gradient to the track pseudo — can't from JS easily. Alternative: put background gradient on input with height 3px? Then thumb 12px overflows visually but webkit renders it fine (input doesn't clip by default? It does clip? No — form elements don't clip overflow generally with appearance none... Actually they don't clip). Risky cross-browser. Alternative: wrap each slider in .slider div with background track and let input be transparent on top: ```
.sl{position:relative;height:16px} .sl::before{content:'';position:absolute;left:0;right:0;top:7px;height:3px;background:var(--filltrack...)} ``` But JS gradient needs to be on ::before — can't. Use the wrapper div with inline background set by JS on the wrapper itself, and input transparent on top with appearance:none, track transparent, thumb visible. Input covers wrapper; wrapper shows filled track. Structure: `.sl { position:relative; height:14px; border-radius:2px; background: rgba(255,255,255,.14); }` with JS: sl.style.background = linear-gradient. input absolute inset 0, appearance none, background transparent, thumb visible. Track transparent. Works in webkit & firefox. Thumb style both vendors. Init order: resize() → build sprites → pools → bg caches → loop. BG cache build: ```js function buildBG(){ bg = document.createElement('canvas'); bg.width=W*DPR; bg.height=H*DPR; const g=bg.getContext('2d'); g.setTransform(DPR,0,0,DPR,0,0); // sky const sky=g.createLinearGradient(0,0,0,H); sky.addColorStop(0,'#05070f'); addColorStop(0.45,'#0d1024'); addColorStop(0.75,'#2a1a22'); addColorStop(1,'#57301f'); fillRect all. // stars for 150: x,y0? `rgba(190,150,120,${a*0.5})` : `rgba(0,0,0,${-a})`; slope tangent: tx = 1, ty=(terr[x+1]-terr[x-1]) /2... draw short line along tangent downward-ish: line from (x, y+2) to (x + dx*len, y+2 + dy*len) where (dx,dy) normalized slope dir... simpler: vertical-ish streaks: lineTo(x+ (rnd()-0.5)*3, y+2+len) } // crater inner shading dark gradient inside funnel: fill ellipse at mouth with rgba(0,0,0,0.45)? // pines let placed=0; while(placed<16){ x=rnd()*W; d=|x-cx|; if(d>halfW*1.02 && d i<0?0:i>W?W:i. Smoke spawn: ```js function spawnSmoke(){ if(smoke.free.length===0) return; ... const x0=cx+(rnd()-0.5)*craterHalf*1.2, y0=mouthY-4; const act=state.activity; const vy0 = -(40+rnd()*(60+70*act))*S; vx0 = state.wind*6*S + (rnd()-0.5)*20*S; life = 4.5+rnd()*4; r0=(7+rnd()*12)*S; gr=(5+rnd()*9)*S*(0.6+0.4*act? just rand); seed=rnd()*10; } ``` step: ```js p.age+=dt; if(p.age>p.life) kill const k = 1-p.age/p.life; const rise = -(24+46*k)*S; p.vy += (rise-p.vy)*0.9*dt; const turb = Math.sin(t*1.2+p.seed)*16*S + Math.sin(t*2.7+p.seed*2.1)*8*S; p.vx += (state.wind*15*S + turb - p.vx)*0.8*dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.r = p.r0 + p.gr*p.age; ``` draw: ```js const lifeFrac = p.age/p.life; a = Math.min(1, p.age/0.5) * (1-Math.max(0,(lifeFrac-0.6)/0.4)); ctx.globalAlpha = a*0.55; drawImage(SPR_SMOKE, x-r,y-r, 2r,2r) if(p.age<1.0){ young list push } → after loop: lighter, alpha=(1-age)*0.4*... draw SPR_WARM at r*0.7 ``` Total smoke alpha stacking: 0.55*0.42 core alpha ≈ 0.23 per puff — plume builds nicely. Counter: display lava.n + smoke.n. Pool sizes: lavaCap = amount (300..3000), smokeCap = round(amount*0.55). Changing amount: rebuild pools? Resizing pools live: allocate max pool once (3600 lava, 2000 smoke) but cap spawns by current limit: spawn checks lava.n < lavaLimit. free list holds all indices of max pool. active n tracked. Kill still works. So pool init: cap = 3600; free = [0..3599]; limit = state.amount. spawn: if(lava.n >= lavaLimit || !free.length) return. Smoke limit = Math.round(amount*0.55). Update limits when slider moves. Particles counter shows both n's. Perf: iterating 3600+2000 array slots per frame with early continue — fine. FPS: track ema: fpsE = fpsE*0.9 + (1/rawDt)*0.1; rawDt unclamped but guard div0. One more visual: during eruption, tint? skip. Signature extra: slow "breathing" of crater glow with pressure (pressure*glow) ✓; tremor sound? skip. Alright — also make sure lava particles that settle inside crater glow collectively — they will. Let me also add keyboard? no. Write final code now. Careful about details: - `terr` length W+1, index round clamp. - On resize mid-eruption particles may be inside new terrain → they'll land/slide, ok. UI shake: apply to #ui and also maybe canvas draws — canvas translate already. UI transform: `uiEl.style.transform = amp>0.15? \`translate(${(sx*0.5).toFixed(1)}px, ${(sy*0.5).toFixed(1)}px)\` : ''`. Gauge ticks: CSS repeating-linear-gradient on .gauge::after with hairlines: functional scale. ``` .gauge::after{content:'';position:absolute;inset:0;background:repeating-linear-gradient(to top, transparent 0 calc(20% - 1px), rgba(255,255,255,.25) calc(20% - 1px) 20%);pointer-events:none} ``` Hmm repeating with percentage: use background-size trick: linear-gradient lines at 0,25,50,75,100 via multiple backgrounds? Simpler: 4 absolutely positioned 1px divs? Use background-image: linear-gradient(rgba(255,255,255,.22) 1px, transparent 1px); background-size: 100% 25%; — gives lines every 25%. ✓ position at bottom lines... close enough: lines at top of each 25% band. Fine. Font loading: ``` ``` Responsive: controls width 240px; on small screens scale panels down; brand font smaller. Let me finalize copy: - Title: "Monte Aria" — subtitle "STRATOVOLCANO · LIVE SIMULATION" - Controls labels: "ERUPTION POWER", "WIND", "PARTICLE BUDGET"? "PARTICLES" fine. - Footer hint: none or tiny "eruptions recur automatically — tune the dials while she blows." Put small line under controls: mono 9px, muted. Nice authored touch. Status values: DORMANT / TREMOR / ERUPTING — tremor when pressure>0.7 && build. Gauge label vertical: writing-mode vertical-rl text? Place label under gauge horizontally "PRESSURE" + value. Layout: gaugeWrap flex row: bar + column(label rotated? simple horizontal small text). Keep simple: column right of bar: "PRESS- URE"? Just rotate with writing-mode: vertical-rl; letter-spacing .18em; color muted. Good. Also add a tiny triangle marker on gauge? skip. Check self-consistency on the "no external libraries" requirement: only fonts CDN — allowed (not a library). Everything inline otherwise. The user said no external libraries — fonts via CSS are fine, but to be safe I could inline system fallbacks if font fails. Keep the link; degrade gracefully with fallback stacks. Edge: DPR changes on zoom — recalc in resize. Now write the whole thing. I'll write ~700 lines total. Also remember explanation after code, and no `#` heading start, English. One more check — draw order for lava hot loop sprite selection: ```js let spr; if(h>0.8) spr=SPR[0]; else if(h>0.62) spr=SPR[1]; else if(h>0.45) spr=SPR[2]; else if(h>0.3) spr=SPR[3]; else spr=SPR[4]; alpha = Math.min(1, 0.25+h*0.9); r = p.r*(1.6+h*1.2) * (0.9+0.18*Math.sin(t*9+p.seed*7)) — include glow halo scale: air particles get bigger halo: if mode===0 r*=1.25 ``` Flicker via sin per particle: 1600 sins ok. Rocks fill: color '#2a1a12' slightly warm dark. Also on very first load, give pressure a head start (0.25) so first eruption comes in ~8s not 12. pressure=0.3. Let me also double check the slide gradient sign: screen y grows downward. terr[xi+1] > terr[xi-1] means surface lower (further down) on right → downhill is to the right → particle should slide right (positive slideV). s>0 → slideV += s*k → positive ✓. For crater interior: center is deepest → left side of crater: terr decreases toward center → terr[xi+1] W+l → x=-l; if x< -l-? handle sign: if(x> W+ s.l) x = -s.l; if(x < -s.l-60) x = W+s.l... just: x = ((x % (W+120)) + W+120) % (W+120) - 60. Use modulo wrap. len along +sign direction: draw from x to x + s.l*dir where dir=wind>=0?1:-1? Streak should trail: draw line from x to x - dir*s.l. Fine. Also alpha = Math.min(0.28, Math.abs(state.wind)*0.024)*s.d; skip if <0.01. DOM ids: fps, pCount, statusDot(status container), windTxt, gFill, gVal, gauge el, sliders. Status: ```js const st = mode==='erupt' ? 'ERUPTING' : (pressure>0.7?'TREMOR':'DORMANT'); ``` OK, also the "activity" for smoke even after eruption decays ✓ giving after-eruption plume. Ash during eruption color darker? Fine as is. Let me write CSS carefully: ```css :root{ --amber:#ffb15e; --ember:#ff4b2e; --ink:#e9e4d8; --mut:rgba(233,228,216,.42); --panel:rgba(9,10,15,.6); --line:rgba(255,255,255,.09) } *{margin:0;padding:0;box-sizing:border-box} html,body{height:100%;overflow:hidden;background:#05070f} canvas#scene{position:fixed;inset:0;width:100%;height:100%;display:block} #ui{position:fixed;inset:0;pointer-events:none;font-family:'IBM Plex Mono',ui-monospace,monospace;color:var(--ink);z-index:2} .panel{position:absolute;pointer-events:auto;background:var(--panel);border:1px solid var(--line);border-radius:10px;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);padding:14px 16px} .brand{top:20px;left:20px;border:none;background:none;backdrop-filter:none;padding:0} .brand h1{font-family:'Instrument Serif',serif;font-style:italic;font-weight:400;font-size:34px;letter-spacing:.01em;color:#f4ecdc;line-height:1} .brand p{font-size:9.5px;letter-spacing:.32em;color:var(--mut);margin-top:7px} .brand .live{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--ember);margin-right:7px;vertical-align:1px; animation: blink 1.6s infinite? } ``` Hmm a "LIVE" red dot next to subtitle — small, functional (live sim indicator). Keep as part of subtitle: `

STRATOVOLCANO — LIVE SIMULATION

`. Readout top-right: ```css .readout{top:20px;right:20px;min-width:190px} .row{display:flex;justify-content:space-between;gap:18px;font-size:11px;line-height:1.9} .k{color:var(--mut);letter-spacing:.14em;font-size:9.5px;padding-top:1px} .v{font-weight:600} .dot{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;background:#6b7564;vertical-align:0} .dot.tremor{background:var(--amber);animation:blink .5s infinite alternate? use 0.6s} .dot.erupt{background:var(--ember);animation:blink .3s infinite} @keyframes blink{from{opacity:1}to{opacity:.25}} ``` Gauge bottom-right: ```css .gaugeWrap{right:20px;bottom:20px;display:flex;gap:10px;align-items:flex-end;padding:14px} .gauge{position:relative;width:13px;height:168px;border:1px solid var(--line);border-radius:6px;background:rgba(255,255,255,.05);overflow:hidden} .gauge::after{content:'';position:absolute;inset:0;background:linear-gradient(rgba(255,255,255,.16) 1px,transparent 1px);background-size:100% 25%;pointer-events:none} #gFill{position:absolute;left:0;right:0;bottom:0;height:0%;background:var(--amber);transition:height .18s linear, background .3s} #gFill.hot{background:var(--ember);animation:blink .5s infinite} .gaugeSide{display:flex;flex-direction:column;justify-content:space-between;height:168px;align-items:center} .gLabel{writing-mode:vertical-rl;transform:rotate(180deg);font-size:9px;letter-spacing:.3em;color:var(--mut)} #gVal{font-size:11px;font-weight:600;color:var(--amber)} ``` Hmm gaugeSide: label rotated + value. order: value at bottom near bar bottom. fine. Controls bottom-left: ```css .controls{left:20px;bottom:20px;width:250px;display:flex;flex-direction:column;gap:13px} .ctl-head{display:flex;justify-content:space-between;font-size:9.5px;letter-spacing:.18em;color:var(--mut);margin-bottom:7px} .ctl-head b{color:var(--ink);font-weight:600;letter-spacing:0} .sl{position:relative;height:14px;border-radius:7px;background:rgba(255,255,255,.13)} .sl input{position:absolute;inset:0;width:100%;height:100%;-webkit-appearance:none;appearance:none;background:transparent;cursor:ew-resize;outline:none} .sl input::-webkit-slider-thumb{-webkit-appearance:none;width:13px;height:13px;border-radius:50%;background:#f6e8d4;border:2px solid #1a120c;box-shadow:0 0 0 1px rgba(255,177,94,.5)} .sl input::-moz-range-thumb{width:11px;height:11px;border-radius:50%;background:#f6e8d4;border:2px solid #1a120c} .hint{font-size:9px;color:var(--mut);letter-spacing:.08em;margin-top:2px;line-height:1.5} ``` Media query: max-width 720px → scale panels: brand h1 26px; controls width 200px; gauge height 120; readout smaller. Basic. Sliders config: - power: min 0.2 max 2 step 0.05 value 1 → display (v).toFixed(2)+'×' - wind: min -10 max 10 step 0.5 value 2.5 → display (v>=0?'→ ':'← ')+abs.toFixed(1) - amount: min 300 max 3000 step 50 value 1600 → display v JS wiring: on input → update state, fillSlider, label. fillSlider sets el.parentNode.style.background = gradient. For wind, fill from center? Nice touch: for wind show fill from middle to thumb: compute p; if v>=0: gradient left 50% → transparent to 50%, amber 50%→p%. Else p%→50%. Implement: ```js function paintTrack(el){ const min=+el.min, max=+el.max, v=+el.value; const p = (v-min)/(max-min)*100; const a='rgba(255,177,94,.8)', b='rgba(255,255,255,.12)'; if(el.dataset.mid){ // bidirectional const mid=50; const g = v>=mid? `linear-gradient(90deg, ${b} 0 ${mid}%, ${a} ${mid}% ${p}%, ${b} ${p}% 100%)` : `linear-gradient(90deg, ${b} 0 ${p}%, ${a} ${p}% ${mid}%, ${b} ${mid}% 100%)`; el.parentNode.style.background=g; } else { el.parentNode.style.background=`linear-gradient(90deg, ${a} 0 ${p}%, ${b} ${p}% 100%)`; } } ``` Now write full JS in order: 1. helpers (clamp, rnd) 2. canvas & resize 3. terrain gen 4. sprites 5. pools 6. bg/tr caches 7. emission & updates 8. render 9. loop 10. UI wiring 11. init Potential bug areas: drawImage of sprite canvases before defined — order matters. Also ensure ctx.imageSmoothingEnabled default true (good for sprites). Let me now also double-check eruption pacing: build rate 0.085–0.115/s → 8.7–11.8s from 0. From 0.3 head start: ~6-8s. Good. EruptDur power1: 6.1s; power2: 9.9s; power 0.2: 3.06s. lavaRate power1 env1: 380/s * ~5s avg env → ~1400 spawned; pool 1600 with overlap — good, most spawn. Now smoke rate: 3 + 70*act → up to 73/s during eruption; life ~6s → ~440 smoke. Cap smoke = 880 at default. ok. Counter could show "1 247" formatting with thin space? Just toLocaleString? Use plain number with space thousands: n.toLocaleString('en'). fine. Also I want the wind slider to have visible immediate effect on plume lean — 15*S factor: wind 10 → 150 px/s target — strong lean ✓. Title flair: In brand panel, also show current vent temperature? gimmick — skip. Let me now write out everything concretely, watching for correctness. Also remember: no emoji anywhere; arrows ←/→ are punctuation, fine. For FPS stability: rawDt for fps calc: dtRaw. Write code: ```js const cv=document.getElementById('scene'), ctx=cv.getContext('2d'); const ui=document.getElementById('ui'); const $=id=>document.getElementById(id); const clamp=(v,a,b)=>vb?b:v; const rnd=Math.random; let W=innerWidth,H=innerHeight,DPR=1,S=1,G=420; let terr=null, cx=0, halfW=0, groundY=0, peakY=0, craterHalf=0, mouthY=0, mouthDepth=40; let bg=null, tr=null; const nph=[rnd()*6.28,rnd()*6.28,rnd()*6.28]; function terrNoise(x){ return Math.sin(x*0.0103+nph[0])*5.2 + Math.sin(x*0.0271+nph[1])*2.4 + Math.sin(x*0.0047+nph[2])*7.5; } function heightAt(x){ const d=Math.abs(x-cx); let y; if(d>=halfW) y=groundY; else y=peakY+(groundY-peakY)*Math.pow(d/halfW,0.72); if(dclamp(Math.round(i),0,W); function buildTerrain(){ cx=W*0.5; halfW=Math.min(W*0.37,540); groundY=H*0.845; const peakH=Math.min(H*0.5,470); peakY=groundY-peakH; mouthDepth=Math.min(46,peakH*0.16); craterHalf=Math.max(24,halfW*0.15); terr=new Float32Array(W+1); for(let i=0;i<=W;i++) terr[i]=heightAt(i); mouthY=terr[Math.round(cx)]; } ``` Wait: for ground beyond cone, mask = clamp((d-craterHalf)/(halfW*0.28)) = 1 → ground gets noise ±15 around groundY ✓; but between crater rim and mask zone, noise fades in — ok. Cone base: at d slightly < halfW, y≈groundY (t=1) ✓ smooth junction. peakH vs H*0.5: crater mouth y = groundY - peakH + mouthDepth ≈ H*0.845-470+46 → for H=900: 761-470+46=337. ✓ Sprites — write makeSprite. Pools: ```js function makePool(max){ const arr=new Array(max); const free=new Array(max); for(let i=0;i=lava.limit||!lava.free.length)return; const i=lava.free.pop(); const p=lava.arr[i]; p.on=true; p.x=x;p.y=y;p.vx=vx;p.vy=vy;p.heat=heat;p.r=r;p.mode=0;p.slideV=0;p.age=0;p.seed=rnd()*6.28; lava.n++; ``` spawnSmoke similar: life, r0, gr, seed. state object as planned. Emission & spawnFountain & spawnSpark & spawnSmoke as designed. stepLava(dt), stepSmoke(dt) as designed with kills inline: Within loop use index i for kill. stepLava details — careful kill-and-continue: ```js function stepLava(dt){ const wind=state.wind, t=state.t; for(let i=0;i=ty){ if(p.vy>150*S&&p.heat>0.35&&rnd()<0.45){ p.vy=-p.vy*0.18; p.vx*=0.55; p.y=ty-2; } else{ p.mode=1; p.slideV=p.vx*0.35; p.y=ty-1; p.vy=0; if(p.vy>... // removed since vy=0 now; do spatter check before zeroing: } } ``` Reorder: capture impact speed first: ```js if(p.y>=ty новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых новых === answer so far ===