oneshotlm
Compare all models on this prompt →

Fireworks · seed-2.0-mini

Create a fireworks display on a full-screen canvas where clicks launch shells that explode into gravity-affected, fading particle bursts.

⛨ sandboxed Open in new tab ↗
transcript · 4 messages
user

Create a fireworks display on a full-screen canvas where clicks launch shells that explode into gravity-affected, fading particle bursts.

assistant
thinking
Got it, let's build this fireworks app. First, I need to make a single index.html file with all the code. Let's start by setting up the basic HTML structure, then the canvas full screen, CSS to remove margins, hide overflow, etc.

First, the HTML: a canvas element that covers the whole screen. Then the style tag to make the body have no margin, overflow hidden, background black probably, since fireworks look good on black.

Then the JavaScript part: we need particles and firework shells. Let's break it down:

1.  Firework class: each shell is launched from the bottom of the screen towards a target where the user clicked. When it reaches the target, it explodes into particles.
2.  Particle class: each particle has position, velocity, gravity, fade out over time, color.
3.  Handle click events: when the user clicks, create a new firework shell aimed at the click position.
4.  Animation loop: use requestAnimationFrame, clear the canvas each frame, update all fireworks and particles, draw them.
5.  Manage the arrays of active fireworks and particles, clean up old ones that are no longer visible/faded.

Wait, let's outline the steps:

First, the canvas setup: get the canvas element, set its width and height to window.innerWidth and window.innerHeight, handle resize events so it stays full screen.

Then, the Firework object: properties like x, y (start position, bottom middle?), targetX, targetY (where the click was), velocity, color, radius? Wait no, the shell is a small point that goes up. When it's close to the target, explode.

Wait, start position for the firework: usually, they launch from the bottom of the screen, random x or fixed? Wait no, the user clicks where they want the explosion, so the firework should launch from the bottom (y = canvas.height) at some x, maybe random or centered? No, better: launch from a random x along the bottom, or let's have the launch position be near the bottom, like y = canvas.height - 20, x = Math.random() * canvas.width? No, wait no, the user clicks the point where the explosion should happen, so the firework travels from the bottom of the screen to the click position. Oh right! So the firework's target is the click's (x,y), and it starts at a random x along the bottom edge, y = canvas.height.

Wait, no, maybe better: when the user clicks, set the explosion target at (event.clientX, event.clientY), then the firework starts at a random x between 0 and canvas.width, y = canvas.height, and travels towards the target. That makes sense.

Then, the particles: when the firework reaches the target, it splits into 100 particles, each with a random velocity direction, speed, gravity applied over time, fade out as their alpha decreases.

Let's think about the variables:

- An array fireworks[] to hold active firework shells.
- An array particles[] to hold active particles.
- Each firework has: startX, startY, targetX, targetY, vx, vy, color, exploded (boolean).
- Each particle has: x, y, vx, vy, life, maxLife, color, alpha.

Wait, gravity: let's set gravity to 0.1 or 0.2, so particles fall down over time.

For the animation loop: each frame, we clear the canvas (maybe with a semi-transparent black to create a trail effect? Or full clear each frame, but trails look nice if we use a rgba fill with low alpha). Wait, no, if we do ctx.fillStyle = 'rgba(0,0,0,0.1)' and fillRect each frame, that leaves trails, which is cool for fireworks.

Wait, let's code the color: each firework can have a random color, like hsl values, so hue between 0 and 360, saturation 100%, lightness 50% or 60%.

Let's outline the code structure:

First, the HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fireworks Display</title>
    <style>
        body { margin: 0; overflow: hidden; background: #000; }
        canvas { display: block; }
    </style>
</head>
<body>
    <canvas id="fireworksCanvas"></canvas>
    <script>
        // JS code here
    </script>
</body>
</html>

Then the JS:

First, get the canvas and context:

const canvas = document.getElementById('fireworksCanvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;

Then handle resize:

window.addEventListener('resize', () => {
    width = canvas.width = window.innerWidth;
    height = canvas.height = window.innerHeight;
});

Then the arrays:

let fireworks = [];
let particles = [];

Then the Firework class (or constructor function, since it's older JS, but class is fine):

class Firework {
    constructor(targetX, targetY) {
        // Start at random x along bottom edge
        this.startX = Math.random() * width;
        this.startY = height;
        this.targetX = targetX;
        this.targetY = targetY;
        // Calculate velocity towards target
        const dx = targetX - this.startX;
        const dy = targetY - this.startY;
        const distance = Math.sqrt(dx*dx + dy*dy);
        // Speed: higher for longer distances, so all fireworks take similar time
        this.speed = 2 + distance * 0.01;
        this.vx = dx / distance * this.speed;
        this.vy = dy / distance * this.speed;
        // Random color
        this.color = `hsl(${Math.random() * 360}, 100%, 60%)`;
        this.exploded = false;
    }

    update() {
        // Move the firework
        this.startX += this.vx;
        this.startY += this.vy;
        // Check if we've reached the target (or close enough)
        if (Math.abs(this.startX - this.targetX) < 5 && Math.abs(this.startY - this.targetY) < 5) {
            this.explode();
            this.exploded = true;
        }
    }

    explode() {
        // Create 150 particles
        const particleCount = 150;
        for (let i = 0; i < particleCount; i++) {
            particles.push(new Particle(this.startX, this.startY, this.color));
        }
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.startX, this.startY, 3, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
    }
}

Then the Particle class:

class Particle {
    constructor(x, y, color) {
        this.x = x;
        this.y = y;
        // Random velocity between -7 and 7 for both axes
        this.vx = (Math.random() - 0.5) * 10;
        this.vy = (Math.random() - 0.5) * 10;
        // Gravity
        this.gravity = 0.2;
        // Life time
        this.life = 0;
        this.maxLife = 100 + Math.random() * 50;
        this.color = color;
        this.alpha = 1;
    }

    update() {
        // Apply gravity
        this.vy += this.gravity;
        // Update position
        this.x += this.vx;
        this.y += this.vy;
        // Increase life, decrease alpha
        this.life++;
        this.alpha = 1 - (this.life / this.maxLife);
    }

    draw() {
        ctx.save();
        ctx.globalAlpha = this.alpha;
        ctx.beginPath();
        ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
        ctx.restore();
    }
}

Then the click handler:

window.addEventListener('click', (e) => {
    // Add a new firework aimed at the click position
    fireworks.push(new Firework(e.clientX, e.clientY));
});

Wait, also, maybe add touch support? But the user said clicks, but maybe optional, but let's stick to clicks first.

Then the animation loop:

function animate() {
    // Clear the canvas with a semi-transparent black to create trails
    ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
    ctx.fillRect(0, 0, width, height);

    // Update and draw fireworks
    for (let i = fireworks.length - 1; i >= 0; i--) {
        const firework = fireworks[i];
        firework.update();
        firework.draw();
        // Remove exploded fireworks
        if (firework.exploded) {
            fireworks.splice(i, 1);
        }
    }

    // Update and draw particles
    for (let i = particles.length - 1; i >= 0; i--) {
        const particle = particles[i];
        particle.update();
        particle.draw();
        // Remove dead particles
        if (particle.life >= particle.maxLife) {
            particles.splice(i, 1);
        }
    }

    requestAnimationFrame(animate);
}

// Start the animation loop
animate();

Wait, that's the basic code. Let's test for possible issues:

- When the user clicks, the firework starts at a random x along the bottom, goes to the click point. Correct.
- When it reaches the target, explodes into particles. Correct.
- Particles have gravity, fade out. Correct.
- The canvas resizes with window. Correct.
- The trail effect from the semi-transparent clear each frame. That's nice.

Wait, but what if the user clicks off-screen? No, because e.clientX and e.clientY are relative to the viewport, but the canvas is full screen, so that's fine.

Wait, let's adjust the particle size: maybe 1.5 instead of 2? Or keep it 2. Also, the firework's size: 3 is okay.

Another thing: the speed calculation. Right now, speed is 2 + distance *0.01, so longer distances go faster, which makes all fireworks take about the same time to reach the target, which is good. Because if a firework is going to a point far away, it needs to go faster to arrive at the same time as a close one. That makes sense.

Wait, let's check the distance calculation: dx is targetX - startX, dy is targetY - startY, distance is sqrt(dx² + dy²), then vx is dx/distance * speed, vy same. That's correct unit vector multiplied by speed, so the velocity is correct towards the target.

Yes.

Now, let's put all this together into a single index.html file. Let's make sure all the code is inside the script tag, no external resources.

Wait, also, let's add some default fireworks? Or just wait for clicks. The user said "clicks launch shells", so that's correct.

Wait, let's test: when you click anywhere, a firework launches from the bottom to that point, explodes into particles that fall and fade. Perfect.

Another thing: the background is black, which is perfect for fireworks. The body has no margin, overflow hidden, so no scrollbars.

Let's write the full file:

Wait, let's combine all the code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fireworks Display</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        body {
            overflow: hidden;
            background: #000;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="fireworksCanvas"></canvas>
    <script>
        const canvas = document.getElementById('fireworksCanvas');
        const ctx = canvas.getContext('2d');
        let width = canvas.width = window.innerWidth;
        let height = canvas.height = window.innerHeight;

        let fireworks = [];
        let particles = [];

        class Firework {
            constructor(targetX, targetY) {
                this.startX = Math.random() * width;
                this.startY = height;
                this.targetX = targetX;
                this.targetY = targetY;

                const dx = targetX - this.startX;
                const dy = targetY - this.startY;
                const distance = Math.sqrt(dx * dx + dy * dy);
                
                this.speed = 2 + distance * 0.01;
                this.vx = dx / distance * this.speed;
                this.vy = dy / distance * this.speed;

                this.color = `hsl(${Math.random() * 360}, 100%, 60%)`;
                this.exploded = false;
            }

            update() {
                this.startX += this.vx;
                this.startY += this.vy;

                if (Math.abs(this.startX - this.targetX) < 5 && Math.abs(this.startY - this.targetY) < 5) {
                    this.explode();
                    this.exploded = true;
                }
            }

            explode() {
                const particleCount = 150;
                for (let i = 0; i < particleCount; i++) {
                    particles.push(new Particle(this.startX, this.startY, this.color));
                }
            }

            draw() {
                ctx.beginPath();
                ctx.arc(this.startX, this.startY, 3, 0, Math.PI * 2);
                ctx.fillStyle = this.color;
                ctx.fill();
            }
        }

        class Particle {
            constructor(x, y, color) {
                this.x = x;
                this.y = y;
                this.vx = (Math.random() - 0.5) * 10;
                this.vy = (Math.random() - 0.5) * 10;
                this.gravity = 0.2;
                this.life = 0;
                this.maxLife = 100 + Math.random() * 50;
                this.color = color;
                this.alpha = 1;
            }

            update() {
                this.vy += this.gravity;
                this.x += this.vx;
                this.y += this.vy;
                this.life++;
                this.alpha = 1 - (this.life / this.maxLife);
            }

            draw() {
                ctx.save();
                ctx.globalAlpha = this.alpha;
                ctx.beginPath();
                ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
                ctx.fillStyle = this.color;
                ctx.fill();
                ctx.restore();
            }
        }

        window.addEventListener('click', (e) => {
            fireworks.push(new Firework(e.clientX, e.clientY));
        });

        window.addEventListener('resize', () => {
            width = canvas.width = window.innerWidth;
            height = canvas.height = window.innerHeight;
        });

        function animate() {
            ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
            ctx.fillRect(0, 0, width, height);

            for (let i = fireworks.length - 1; i >= 0; i--) {
                const firework = fireworks[i];
                firework.update();
                firework.draw();
                if (firework.exploded) {
                    fireworks.splice(i, 1);
                }
            }

            for (let i = particles.length - 1; i >= 0; i--) {
                const particle = particles[i];
                particle.update();
                particle.draw();
                if (particle.life >= particle.maxLife) {
                    particles.splice(i, 1);
                }
            }

            requestAnimationFrame(animate);
        }

        animate();
    </script>
</body>
</html>

That's the full code. Now, I need to write this to /tmp/ws/index.html. Let's use the write function.
write /tmp/ws/index.html (4423 bytes)
toolResult

Successfully wrote 4423 bytes to /tmp/ws/index.html

assistant
thinking
Great, the file was written successfully. Now let's verify that it works by checking if we can open it, but wait, the user just asked to build the single-page app and have it in index.html. Let's confirm that the file exists and has the correct content. Wait, but maybe just let the user know that the file is ready, and explain how it works: click anywhere on the screen to launch a firework shell that explodes into gravity-affected, fading particles. The app is fully self-contained, no external resources, runs sandboxed.

Perfect! I've created a fully self-contained fireworks display web app in `/tmp/ws/index.html`. ### How it works: 1. Click anywhere on the full-screen black canvas to launch a firework shell 2. The shell travels from the bottom of the screen to your click position, then explodes into 150 colorful particles 3. Particles are affected by gravity, fade out over time, and automatically clean themselves up when invisible 4. Semi-transparent trailing effects make the display look smoother 5. The canvas automatically resizes with your window The app uses no external resources, CDNs, or network requests — it runs completely sandboxed locally.

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

modelseed-2.0-mini
vendorByteDance · Seed
harnesspi-agent
generated2026-08-01

EVALUATION

3/5 partial

Click launches a shell trail, and wheel/random events trigger a firework burst; but keyboard inputs do nothing, no idle animation.

Vision grade · anthropic/claude-sonnet-5

MEASUREMENTS

Raw readings from the capture, not a verdict — the grade above is the only judgement. Percentages are how much of the screen changed; the measure under-reports sparse motion on a dark background.

idle motion0.0%
on click0.1%
on drag0.0%
on wheel4.9%
on enter + space0.0%
on arrow keys0.0%
on w/a/s/d0.0%
frame spread7.6 / 255
console errors0
js errors none

METRICS

runtime34.4s
tokens in8.7k
tokens out5.3k
cached1.6k
cost / run$0.0028