[object Object]

← back to Fireworks Sim

initial scaffold: particle fireworks simulator (rebuilt from Gemma3 12B arena attempt)

b5e9607dcb5e0b9468579fa9bf40f2f0e4d50131 · 2026-07-23 06:51:50 -0700 · Steve Abrams

Files touched

Diff

commit b5e9607dcb5e0b9468579fa9bf40f2f0e4d50131
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 23 06:51:50 2026 -0700

    initial scaffold: particle fireworks simulator (rebuilt from Gemma3 12B arena attempt)
---
 .gitignore |   8 ++++
 index.html | 155 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 163 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..1924158
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+node_modules/
+.env*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..1e02fa4
--- /dev/null
+++ b/index.html
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Particle Fireworks</title>
+<style>
+  html, body { margin: 0; height: 100%; overflow: hidden; background: #05060f; }
+  canvas { display: block; cursor: crosshair; }
+  #hud { position: fixed; top: 12px; left: 12px; color: #9aa4c7;
+         font: 13px/1.6 system-ui, sans-serif; user-select: none; }
+  #hud button { background: #1b2140; color: #cdd6ff; border: 1px solid #39406b;
+                border-radius: 6px; padding: 4px 12px; font: inherit; cursor: pointer; }
+  #hud button:hover { background: #262e58; }
+</style>
+</head>
+<body>
+<canvas id="sky"></canvas>
+<div id="hud">
+  <button id="autoBtn">auto-show: on</button>
+  <span>&nbsp;click anywhere to launch &middot; press A to toggle</span>
+</div>
+<script>
+'use strict';
+const canvas = document.getElementById('sky');
+const ctx = canvas.getContext('2d');
+let W, H;
+function resize() { W = canvas.width = innerWidth; H = canvas.height = innerHeight; }
+addEventListener('resize', resize);
+resize();
+
+const GRAVITY = 0.05, FRICTION = 0.985, TRAIL = 7;
+const rand = (a, b) => a + Math.random() * (b - a);
+const rockets = [], particles = [];
+
+class Rocket {
+  constructor(tx, ty) {
+    this.x = tx + rand(-25, 25);
+    this.y = H;
+    this.ty = Math.min(ty, H * 0.85);
+    // launch speed chosen so the rocket's apex lands at the target height
+    this.vy = -Math.sqrt(2 * GRAVITY * (H - this.ty)) - rand(0.2, 0.8);
+    this.vx = (tx - this.x) / (Math.abs(this.vy) / GRAVITY);
+    this.hue = rand(0, 360);
+    this.trail = [];
+  }
+  update() {
+    this.trail.push({ x: this.x, y: this.y });
+    if (this.trail.length > TRAIL) this.trail.shift();
+    this.x += this.vx;
+    this.y += this.vy;
+    this.vy += GRAVITY;
+    if (this.vy >= 0 || this.y <= this.ty) { explode(this.x, this.y, this.hue); return true; }
+    return false;
+  }
+  draw() {
+    const tail = this.trail[0] || this;
+    ctx.strokeStyle = `hsl(${this.hue} 100% 75%)`;
+    ctx.lineWidth = 2.5;
+    ctx.beginPath();
+    ctx.moveTo(tail.x, tail.y);
+    ctx.lineTo(this.x, this.y);
+    ctx.stroke();
+  }
+}
+
+class Particle {
+  constructor(x, y, hue) {
+    const angle = rand(0, Math.PI * 2);
+    // sqrt gives an even fill of the burst disc instead of a hollow ring
+    const speed = Math.sqrt(Math.random()) * rand(5, 9);
+    this.x = x; this.y = y;
+    this.vx = Math.cos(angle) * speed;
+    this.vy = Math.sin(angle) * speed;
+    this.hue = hue + rand(-18, 18);
+    this.life = 1;
+    this.decay = rand(0.008, 0.022);
+    this.trail = [];
+  }
+  update() {
+    this.trail.push({ x: this.x, y: this.y });
+    if (this.trail.length > TRAIL) this.trail.shift();
+    this.vx *= FRICTION;
+    this.vy = this.vy * FRICTION + GRAVITY;
+    this.x += this.vx;
+    this.y += this.vy;
+    this.life -= this.decay;
+    return this.life <= 0;
+  }
+  draw() {
+    if (this.life < 0.35 && Math.random() < 0.4) return; // twinkle as it dies
+    const tail = this.trail[0] || this;
+    ctx.strokeStyle = `hsla(${this.hue} 100% ${45 + this.life * 30}% / ${this.life})`;
+    ctx.lineWidth = 1 + this.life * 1.5;
+    ctx.beginPath();
+    ctx.moveTo(tail.x, tail.y);
+    ctx.lineTo(this.x, this.y);
+    ctx.stroke();
+  }
+}
+
+function explode(x, y, hue) {
+  const count = Math.floor(rand(70, 120));
+  const twoTone = Math.random() < 0.3 ? hue + 180 : hue;
+  for (let i = 0; i < count; i++) particles.push(new Particle(x, y, i % 2 ? hue : twoTone));
+  // brief flash at the burst point
+  const flash = ctx.createRadialGradient(x, y, 0, x, y, 60);
+  flash.addColorStop(0, `hsla(${hue} 100% 90% / 0.9)`);
+  flash.addColorStop(1, 'transparent');
+  ctx.fillStyle = flash;
+  ctx.fillRect(x - 60, y - 60, 120, 120);
+}
+
+function launch(tx, ty) { rockets.push(new Rocket(tx, ty)); }
+
+let autoOn = true, autoTimer = 0;
+function tick(now) {
+  // translucent fill (not clearRect) so everything leaves a fading trail
+  ctx.globalCompositeOperation = 'source-over';
+  ctx.fillStyle = 'rgba(5, 6, 15, 0.22)';
+  ctx.fillRect(0, 0, W, H);
+  ctx.globalCompositeOperation = 'lighter'; // additive blend = glow where sparks overlap
+
+  for (let i = rockets.length - 1; i >= 0; i--) {
+    if (rockets[i].update()) rockets.splice(i, 1);
+    else rockets[i].draw();
+  }
+  for (let i = particles.length - 1; i >= 0; i--) {
+    if (particles[i].update()) particles.splice(i, 1);
+    else particles[i].draw();
+  }
+
+  if (autoOn && now > autoTimer) {
+    launch(rand(W * 0.1, W * 0.9), rand(H * 0.1, H * 0.45));
+    autoTimer = now + rand(500, 1600);
+  }
+  requestAnimationFrame(tick);
+}
+requestAnimationFrame(tick);
+
+canvas.addEventListener('pointerdown', e => {
+  launch(e.clientX, e.clientY);
+  autoTimer = performance.now() + 4000; // give the user the sky for a few seconds
+});
+
+const autoBtn = document.getElementById('autoBtn');
+function setAuto(on) {
+  autoOn = on;
+  autoBtn.textContent = `auto-show: ${on ? 'on' : 'off'}`;
+}
+autoBtn.addEventListener('click', e => { e.stopPropagation(); setAuto(!autoOn); });
+addEventListener('keydown', e => { if (e.key.toLowerCase() === 'a') setAuto(!autoOn); });
+</script>
+</body>
+</html>

(oldest)  ·  back to Fireworks Sim  ·  verified via headless Playwright: no console errors, click-l ce4e672 →