Fireworks · mercury-3
Output generated Cost / run $0.0206 Runtime 101.0s
Prompt for this run
Build an interactive fireworks display on a full-screen canvas. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Clicking the sky launches a visible shell upward from the bottom of the scene toward the clicked location. The shell then explodes into a radial burst of colored particles. - Burst particles fall under gravity, fade over time, and are removed after their lifetime. Support several overlapping shells and bursts. - Launch one demonstration shell automatically on initial load so the effect is visible before interaction; subsequent shells respond to clicks. - Provide Clear to remove active shells, particles, and trails. Keep controls usable after resizing. - Use procedural drawing only. Choose the visual design, palette, and trails freely while keeping the launch-to-burst sequence visible. Verify: Trigger a shell at a chosen location and sample its ascent, burst, and early particle fall. Check that the click affects the launch target and that particles visibly spread and fade. Use Clear and confirm the scene is emptied.
TAKE IT TO CODE
A single self-contained HTML file — copy it and open in any browser, or reuse the prompt with your own pi-agent run.
RUN
METRICS
Transcript · 76 messages
Build an interactive fireworks display on a full-screen canvas. Use HTML, CSS, and JavaScript as needed. Write it to `index.html` in the current working directory using your file tools. Inline your own CSS and JavaScript. Do not use external assets, CDNs, or network requests. No third-party libraries are provided. Required behavior: - Clicking the sky launches a visible shell upward from the bottom of the scene toward the clicked location. The shell then explodes into a radial burst of colored particles. - Burst particles fall under gravity, fade over time, and are removed after their lifetime. Support several overlapping shells and bursts. - Launch one demonstration shell automatically on initial load so the effect is visible before interaction; subsequent shells respond to clicks. - Provide Clear to remove active shells, particles, and trails. Keep controls usable after resizing. - Use procedural drawing only. Choose the visual design, palette, and trails freely while keeping the launch-to-burst sequence visible. Verify: Trigger a shell at a chosen location and sample its ascent, burst, and early particle fall. Check that the click affects the launch target and that particles visibly spread and fade. Use Clear and confirm the scene is emptied.
{"command":"cd /tmp/job-cU3VYd && ls -la && pwd"}total 12 drwx------ 3 node node 4096 Oct 23 09:54 . drwxrwxrwt 8 root root 4096 Oct 23 09:54 .. drwxr-xr-x 3 node node 4096 Oct 23 09:54 .home /tmp/job-cU3VYd
/tmp/job-cU3VYd/index.html (9303 bytes)Successfully wrote to /tmp/job-cU3VYd/index.html
{"action":"reload"}{"console":[],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"command":"sleep 2"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (678,363) · flight 0.55s","info: [fireworks] burst at (678,368) · particles = 110"],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"action":"click","x":300,"y":200}{"console":["info: [fireworks] shell launched → target (678,363) · flight 0.55s","info: [fireworks] burst at (678,368) · particles = 110","info: [fireworks] shell launched → target (300,200) · flight 0.75s"],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"command":"sleep 1"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (678,363) · flight 0.55s","info: [fireworks] burst at (678,368) · particles = 110","info: [fireworks] shell launched → target (300,200) · flight 0.75s","info: [fireworks] burst at (300,205) · particles = 91"],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"path":"/tmp/job-cU3VYd/index.html","edits":[{"oldText":"// ---------------- main loop ----------------\nlet last = performance.now();\nfunSuccessfully replaced 1 block(s) in /tmp/job-cU3VYd/index.html.
{"action":"reload"}{"console":[],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"command":"sleep 1.2"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (526,231) · flight 0.72s","info: [fireworks] burst at (526,244) · particles = 149"],"dom":"Clear\nClick anywhere to launch fireworks","images":"model does not support images"}
{"path":"/tmp/job-cU3VYd/index.html","edits":[{"oldText":" #hint { color:rgba(224,230,255,0.72); font-size:13px; text-shadow:0 1px 8px rgbaSuccessfully replaced 3 block(s) in /tmp/job-cU3VYd/index.html.
{"action":"reload"}{"console":[],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 1.6"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (762,377) · flight 0.54s","info: [fireworks] burst at (762,382) · particles = 80"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"action":"click","x":500,"y":250}{"console":["info: [fireworks] shell launched → target (762,377) · flight 0.54s","info: [fireworks] burst at (762,382) · particles = 80","info: [fireworks] shell launched → target (500,250) · flight 0.69s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"path":"/tmp/job-cU3VYd/index.html","edits":[{"oldText":"const shells = [];\nconst particles = [];\nconst rings = [];","newText":"const sheSuccessfully replaced 3 block(s) in /tmp/job-cU3VYd/index.html.
{"action":"reload"}{"console":[],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 4.2"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"action":"click","x":350,"y":280}{"action":"click","x":950,"y":430}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 1.3"}(no output)
{"action":"click","x":600,"y":600}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 0.7"}(no output)
{"action":"click","x":600,"y":38}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 0.8"}(no output)
{"action":"click","x":495,"y":38}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s","info: [fireworks] burst at (600,52) · particles = 101","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] cleared (shells=0, particles=0, rings=0)"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"action":"click","x":700,"y":350}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s","info: [fireworks] burst at (600,52) · particles = 101","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] cleared (shells=0, particles=0, rings=0)","info: [fireworks] shell launched → target (700,350) · flight 0.57s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 0.85"}(no output)
{"action":"click","x":495,"y":38}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s","info: [fireworks] burst at (600,52) · particles = 101","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] cleared (shells=0, particles=0, rings=0)","info: [fireworks] shell launched → target (700,350) · flight 0.57s","info: [fireworks] burst at (700,355) · particles = 121","info: [fireworks] cleared (shells=0, particles=46, rings=0)"],"dom":"Clear\nClick anywhere to launch fireworks\n0/54/0","images":"model does not support images"}
{"action":"click","x":250,"y":210}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s","info: [fireworks] burst at (600,52) · particles = 101","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] cleared (shells=0, particles=0, rings=0)","info: [fireworks] shell launched → target (700,350) · flight 0.57s","info: [fireworks] burst at (700,355) · particles = 121","info: [fireworks] cleared (shells=0, particles=46, rings=0)","info: [fireworks] shell launched → target (250,210) · flight 0.74s"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"sleep 3"}(no output)
{"action":"inspect"}{"console":["info: [fireworks] shell launched → target (462,206) · flight 0.75s","info: [fireworks] burst at (462,217) · particles = 88","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (350,280) · flight 0.66s","info: [fireworks] shell launched → target (950,430) · flight 0.50s","info: [fireworks] burst at (950,443) · particles = 114","info: [fireworks] burst at (350,287) · particles = 104","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,600) · flight 0.50s","info: [fireworks] burst at (600,613) · particles = 137","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] shell launched → target (600,38) · flight 0.95s","info: [fireworks] burst at (600,52) · particles = 101","info: [fireworks] scene idle — all shells/particles/trails expired","info: [fireworks] cleared (shells=0, particles=0, rings=0)","info: [fireworks] shell launched → target (700,350) · flight 0.57s","info: [fireworks] burst at (700,355) · particles = 121","info: [fireworks] cleared (shells=0, particles=46, rings=0)","info: [fireworks] shell launched → target (250,210) · flight 0.74s","info: [fireworks] burst at (250,218) · particles = 95","info: [fireworks] scene idle — all shells/particles/trails expired"],"dom":"Clear\nClick anywhere to launch fireworks\n0/0/0","images":"model does not support images"}
{"command":"cd /tmp/job-cU3VYd && ls -la && node -e \"const s=require('fs').readFileSync('index.html','utf8'); console.log('bytes:', s.lengttotal 24 drwx------ 3 node node 4096 Oct 23 09:54 . drwxrwxrwt 10 root root 4096 Oct 23 09:55 .. drwxr-xr-x 4 node node 4096 Oct 23 09:54 .home -rw-r--r-- 1 node node 10146 Oct 23 09:55 index.html bytes: 10137 script tags: 1 external src: false external links: false
{"path":"/tmp/job-cU3VYd/index.html"}<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fireworks</title> <style> :root { --ui-font: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; } html, body { margin:0; height:100%; background:#060712; overflow:hidden; font-family:var(--ui-font); } #sky { position:fixed; inset:0; display:block; cursor:crosshair; touch-action:none; } #ui { position:fixed; top:16px; left:0; right:0; display:flex; justify-content:center; align-items:center; gap:12px; z-index:10; pointer-events:none; } #ui button { pointer-events:auto; padding:10px 22px; font-size:14px; font-weight:600; letter-spacing:.02em; color:#e8ecff; background:rgba(16,18,40,0.62); border:1px solid rgba(140,160,255,0.35); border-radius:999px; backdrop-filter:blur(6px); -webkit-backdrop-filter:blur(6px); cursor:pointer; transition:background .15s, border-color .15s, transform .08s; box-shadow:0 2px 12px rgba(0,0,0,0.5); } #ui button:hover { background:rgba(56,64,140,0.72); border-color:rgba(170,190,255,0.6); } #ui button:active { transform:scale(0.96); } #hint { color:rgba(224,230,255,0.72); font-size:13px; text-shadow:0 1px 8px rgba(0,0,0,0.9); letter-spacing:.03em; user-select:none; } #hud { position:fixed; left:14px; top:14px; font-size:11px; font-variant-numeric:tabular-nums; letter-spacing:.08em; color:rgba(160,170,210,0.4); user-select:none; pointer-events:none; z-index:10; text-shadow:0 1px 4px rgba(0,0,0,0.9); } @media (max-width:640px){ #hint { display:none; } } </style> </head> <body> <canvas id="sky"></canvas> <div id="ui"> <button id="clearBtn" type="button">Clear</button> <span id="hint">Click anywhere to launch fireworks</span> </div> <span id="hud">0/0/0</span> <script> (() => { "use strict"; // ---------------- elements ---------------- const canvas = document.getElementById('sky'); const ctx = canvas.getContext('2d'); let W = 0, H = 0, DPR = 1; // ---------------- physics constants ---------------- const G = 430; // px/s² gravity (shells and particles) const PARTICLE_MAX = 540; // max radial burst speed (px/s) const FADE = 0.20; // per-frame trail fade amount // ---------------- resize ---------------- function resize() { DPR = Math.min(window.devicePixelRatio || 1, 2); W = window.innerWidth; H = window.innerHeight; canvas.width = Math.round(W * DPR); canvas.height = Math.round(H * DPR); canvas.style.width = W + 'px'; canvas.style.height = H + 'px'; ctx.setTransform(DPR, 0, 0, DPR, 0, 0); ctx.fillStyle = '#060712'; ctx.fillRect(0, 0, W, H); makeStars(); } window.addEventListener('resize', resize); // ---------------- stars ---------------- let stars = []; function makeStars() { stars = []; const n = Math.round(W * H / 9000); for (let i = 0; i < n; i++) { stars.push({ x: Math.random() * W, y: Math.random() * H * 0.92, r: 0.4 + Math.random() * 1.2, base: 0.25 + Math.random() * 0.6, freq: 0.5 + Math.random() * 2.2, ph: Math.random() * Math.PI * 2 }); } } function drawStars(t) { ctx.save(); ctx.fillStyle = '#cdd7ff'; for (const s of stars) { const a = s.base * (0.75 + 0.25 * Math.sin(t * s.freq + s.ph)); ctx.globalAlpha = a; ctx.fillRect(s.x, s.y, s.r, s.r); } ctx.restore(); } // ---------------- palettes + glow sprites ---------------- const PALETTES = { gold: ['#fff8dc', '#ffd86b', '#ffa94d', '#ff7b33'], crimson: ['#ffe9e9', '#ff6b6b', '#ef3b4c', '#a4122c'], azure: ['#eaf6ff', '#8ad4ff', '#3fa5ff', '#185fce'], emerald: ['#e9ffe9', '#83f0a0', '#2fcb72', '#0d7d4a'], violet: ['#f3ebff', '#cb9bff', '#8f5bf2', '#5a22bd'], rose: ['#ffebf3', '#ff9ecd', '#f15bb5', '#ae2278'], cyan: ['#e6ffff', '#7df0f0', '#23c4cd', '#0b7480'] }; const PAL_KEYS = Object.keys(PALETTES); function randPalette() { return PAL_KEYS[(Math.random() * PAL_KEYS.length) | 0]; } const spriteCache = new Map(); function glowSprite(color) { let cv = spriteCache.get(color); if (!cv) { cv = document.createElement('canvas'); cv.width = cv.height = 64; const g = cv.getContext('2d'); const grd = g.createRadialGradient(32, 32, 0, 32, 32, 32); grd.addColorStop(0, '#ffffff'); grd.addColorStop(0.22, color); grd.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = grd; g.fillRect(0, 0, 64, 64); spriteCache.set(color, cv); } return cv; } function drawGlow(x, y, diam, color, alpha) { if (alpha <= 0.003) return; ctx.globalAlpha = alpha; ctx.drawImage(glowSprite(color), x - diam / 2, y - diam / 2, diam, diam); } // ---------------- state ---------------- const shells = []; const particles = []; const rings = []; let prevTotal = 0; // ---------------- shell launch ---------------- function launchShell(tx, ty) { const lx = Math.max(24, Math.min(W - 24, tx)); const ly = H + 18; const dx = tx - lx, dy = ty - ly; const dist = Math.hypot(dx, dy); const T = Math.max(0.5, Math.min(0.95, dist / 820)); const vx = dx / T; const vy = dy / T - 0.5 * G * T; // gravity-compensated: arrives at target at t=T shells.push({ x: lx, y: ly, vx, vy, t: 0, T, tx, ty, pal: randPalette(), trail: [], dead: false }); console.info('[fireworks] shell launched → target (' + Math.round(tx) + ',' + Math.round(ty) + ') · flight ' + T.toFixed(2) + 's'); } // ---------------- burst ---------------- function explode(x, y, pal) { const colors = PALETTES[pal] || PALETTES.gold; const n = 80 + ((Math.random() * 70) | 0); for (let i = 0; i < n; i++) { const ang = Math.random() * Math.PI * 2; const spd = (0.22 + Math.pow(Math.random(), 0.75) * 0.78) * PARTICLE_MAX; particles.push({ x, y, vx: Math.cos(ang) * spd, vy: Math.sin(ang) * spd, age: 0, life: 0.9 + Math.random() * 1.4, color: colors[(Math.random() * colors.length) | 0], size: 1.4 + Math.random() * 2.6, dead: false }); } rings.push({ x, y, age: 0, life: 0.55, dead: false }); if (rings.length > 24) rings.shift(); console.info('[fireworks] burst at (' + Math.round(x) + ',' + Math.round(y) + ') · particles = ' + n); } // ---------------- stepping ---------------- function stepShell(sh, dt) { sh.t += dt; sh.vy += G * dt; sh.x += sh.vx * dt; sh.y += sh.vy * dt; sh.trail.push({ x: sh.x, y: sh.y }); if (sh.trail.length > 16) sh.trail.shift(); const arrived = sh.t >= sh.T || Math.hypot(sh.x - sh.tx, sh.y - sh.ty) < 14; if (arrived || sh.y > H + 80) { explode(sh.x, sh.y, sh.pal); sh.dead = true; } } function stepParticle(p, dt) { p.age += dt; if (p.age >= p.life) { p.dead = true; return; } const drag = Math.exp(-0.28 * dt); p.vx *= drag; p.vy = p.vy * drag + G * dt; p.x += p.vx * dt; p.y += p.vy * dt; } function easeOutCubic(x) { return 1 - Math.pow(1 - x, 3); } // ---------------- main loop ---------------- let last = performance.now(); let frameCount = 0; function frame(now) { const dt = Math.min((now - last) / 1000, 1 / 30); last = now; if ((frameCount++ & 15) === 0) { const hud = document.getElementById('hud'); if (hud) hud.textContent = shells.length + '/' + particles.length + '/' + rings.length; } // fade previous frame → persistent trails ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = 'rgba(6,7,18,' + FADE + ')'; ctx.fillRect(0, 0, W, H); drawStars(now / 1000); // physics for (const s of shells) stepShell(s, dt); for (const p of particles) stepParticle(p, dt); for (const r of rings) { r.age += dt; if (r.age >= r.life) r.dead = true; } // remove expired for (let i = shells.length - 1; i >= 0; i--) if (shells[i].dead) shells.splice(i, 1); for (let i = particles.length - 1; i >= 0; i--) if (particles[i].dead) particles.splice(i, 1); for (let i = rings.length - 1; i >= 0; i--) if (rings[i].dead) rings.splice(i, 1); const total = shells.length + particles.length + rings.length; if (prevTotal > 0 && total === 0) console.info('[fireworks] scene idle — all shells/particles/trails expired'); prevTotal = total; // draw additive glow ctx.globalCompositeOperation = 'lighter'; for (const s of shells) { if (s.trail.length >= 2) { ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.strokeStyle = 'rgba(255,220,160,0.28)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(s.trail[0].x, s.trail[0].y); for (let i = 1; i < s.trail.length; i++) ctx.lineTo(s.trail[i].x, s.trail[i].y); ctx.stroke(); } drawGlow(s.x, s.y, 26, '#ffd9a0', 0.95); drawGlow(s.x, s.y, 10, '#ffffff', 1); } for (const p of particles) { const t = p.age / p.life; const alpha = Math.pow(1 - t, 1.2); const diam = p.size * (1 - 0.55 * t) * 7; drawGlow(p.x, p.y, diam, p.color, alpha); } for (const r of rings) { const t = r.age / r.life; const rad = 14 + easeOutCubic(t) * 120; const a = (1 - t) * 0.8; ctx.strokeStyle = 'rgba(255,235,205,' + a.toFixed(3) + ')'; ctx.lineWidth = 2.6 * (1 - t); ctx.beginPath(); ctx.arc(r.x, r.y, rad, 0, Math.PI * 2); ctx.stroke(); } ctx.globalCompositeOperation = 'source-over'; requestAnimationFrame(frame); } // ---------------- input ---------------- canvas.addEventListener('pointerdown', (e) => { launchShell(e.clientX, e.clientY); }); document.getElementById('clearBtn').addEventListener('click', () => { const pS = shells.length, pP = particles.length, pR = rings.length; shells.length = 0; particles.length = 0; rings.length = 0; prevTotal = 0; ctx.globalCompositeOperation = 'source-over'; ctx.fillStyle = '#060712'; ctx.fillRect(0, 0, W, H); drawStars(performance.now() / 1000); console.info('[fireworks] cleared (shells=' + pS + ', particles=' + pP + ', rings=' + pR + ')'); }); // ---------------- init ---------------- resize(); requestAnimationFrame(frame); // demonstration shell on load setTimeout(() => { launchShell(W * 0.5 + (Math.random() - 0.5) * W * 0.3, H * (0.25 + Math.random() * 0.25)); }, 350); })(); </script> </body> </html>
Done. Final artifact: `/tmp/job-cU3VYd/index.html` (single self-contained file: inline `<style>` + `<script>`, no external assets/CDNs/network). **What I built** - Full-screen canvas (`position:fixed`), procedural drawing with cached radial-glow sprites, additive `lighter` compositing, and per-frame translucent fade for trails. - **Shells:** launch from the bottom toward the clicked point with gravity-compensated velocity (arrives exactly at target), leaving a comet trail; explode on arrival. - **Bursts:** 80–150 radial particles from a random 7-color palette; gravity, mild drag, cross over 0.9–2.3 s, removed after lifetime; plus an expanding shockwave ring. - **Demo** shell auto-fires ~350 ms after load; clicks launch subsequent shells. **Clear** button empties shells/particles/rings and flushes trails with an opaque repaint. - Subtle HUD (`shells/particles/rings`) in the corner; controls are `position:fixed` so they stay usable after resizing. **What I verified (via browser harness)** - **Click target respected:** clicked (300,200), (700,350), (950,430), (250,210) → each shell launched at exactly those coordinates; bursts landed within ~5–13 px of target. - **Ascent → burst → fall/removal:** logs showed launch → burst (with particle count) → "scene idle — all shells/particles/trails expired"; HUD decayed back to `0/0/0`. - **Overlapping effects:** two rapid clicks produced two concurrent shells/bursts. - **Clear:** pressed while 46 particles were mid-fall → `cleared (shells=0, particles=46, rings=0)`; post-clear click still launched normally. No consol errors at any point. - **Self-contained:** no external src/link references. **Remaining unverified** - Actual pixel rendering (this model can't view screenshots), so visual polish is confirmed only indirectly via logs/HUD counts. - A live `resize` event couldn't be simulated in the harness; the resize path was verified by code review (re-measures W/H/DPR, resets transform, regenerates stars; UI is fixed-position).