oneshotlm
Compare all models on this prompt →

Fireworks · qwen3-235b-a22b-thinking-2507

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
Okay, I need to create a single-page web app with a fireworks display. The user wants it all in one index.html file, no external resources. Let me think about how to structure this.

First, the HTML structure. It should have a canvas element that takes up the full screen. So, the body will have a canvas with style to cover the entire viewport. Maybe set position: fixed, top: 0, left: 0, width: 100%, height: 100%.

Next, the CSS. Inside a <style> tag. I'll reset margins and padding for body and html to 0. Make the canvas cover the whole screen. Maybe set overflow: hidden to prevent scrollbars.

Then the JavaScript. The script needs to handle the canvas context, handle click events to launch fireworks, simulate the explosion with particles affected by gravity, and make them fade out.

Let me outline the JS steps:

1. Get the canvas and context.
2. Set up event listener for clicks on the canvas. When clicked, create a firework shell that moves upwards.
3. When the shell reaches a certain height (or after a delay), it explodes into particles.
4. Each particle has velocity, gravity effect, and fades over time.

For the firework shell: when clicked, a shell is created at the click position. It moves upward with some initial velocity, slowing down due to gravity (negative acceleration). When it reaches the peak (velocity <=0), it explodes.

Explosion: create multiple particles radiating out. Each particle has a random direction, speed, and color. Apply gravity to each particle (downward acceleration), and reduce their opacity over time until they fade out.

Need to manage all these particles in an array, updating and rendering each frame.

Also, the animation loop using requestAnimationFrame to keep the display smooth.

Let me think about the code structure.

Variables:

- canvas, ctx
- fireworks array (for shells)
- particles array

Functions:

- createFirework(x, y): creates a new shell at the click position, moving up.
- updateFireworks(): moves shells, checks for explosion.
- createExplosion(x, y): when shell explodes, generate particles.
- updateParticles(): move particles, apply gravity, fade, remove when done.
- draw(): clear canvas, draw fireworks and particles.
- animate(): the main loop calling update and draw.

Handling clicks: event listener on canvas, calls createFirework with click coordinates.

For the particles, each has properties like x, y, velocity (vx, vy), gravity, color, alpha, size.

In the explosion, particles are given random velocities in different directions.

Gravity will increase vy (downward) each frame.

Alpha decreases over time until 0, then particle is removed.

I need to make sure the canvas resizes properly if the window is resized, but the user said full-screen, so maybe set canvas to window.innerWidth and window.innerHeight on resize.

Wait, but the problem says no external resources, so the code should handle resizing. Maybe add a resize event listener to adjust canvas size.

But the user might not require that, but to be safe, include it.

Wait, the problem says "full-screen canvas", so the canvas should cover the entire viewport. So in CSS, set canvas to position: fixed, top: 0, left: 0, width: 100vw, height: 100vh.

But maybe better to set via JS to match window dimensions, and handle resize.

Wait, in the CSS, using 100% might not work if the body doesn't have 100% height. So maybe set html, body { height: 100%; margin: 0; } and canvas { width: 100%; height: 100%; }.

But perhaps using absolute positioning with top/left 0 and width/height 100%.

Let me draft the HTML structure.

<!DOCTYPE html>
<html>
<head>
    <title>Fireworks</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }
        canvas {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="fireworks"></canvas>
    <script>
        // JS code here
    </script>
</body>
</html>

That should set the canvas to full screen.

Now the JS part.

Initialize canvas:

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

Add resize event:

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

Firework shell object:

class Firework {
    constructor(sx, sy, tx, ty) {
        this.sx = sx; // start x
        this.sy = sy;
        this.tx = tx; // target x (where it explodes)
        this.ty = ty;
        this.x = sx;
        this.y = sy;
        this.speed = 5; // initial speed
        this.acceleration = 0.05; // maybe gravity slowing it down?
        this.velocity = 3; // initial velocity upwards
        this.friction = 0.95;
        this.brightness = random(50, 70);
        this.radius = 2;
        this.done = false;
    }

    update() {
        // move towards target
        // maybe use velocity and gravity
        // when velocity <= 0, explode
        this.velocity -= 0.05; // gravity effect
        this.y -= this.velocity;
        this.x += (this.tx - this.x) * 0.01; // slight drift?

        if (this.velocity <= 0.1) {
            this.done = true;
        }
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = `hsl(${random(0, 360)}, 100%, 50%)`;
        ctx.fill();
    }
}

Wait, maybe simpler: the firework shell moves upward with decreasing velocity until it stops, then explodes.

Particles:

class Particle {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.vx = random(-2, 2);
        this.vy = random(-2, 2);
        this.gravity = 0.05;
        this.life = 100; // lifespan
        this.decay = random(0.95, 0.99);
        this.color = `hsl(${random(0, 360)}, 100%, 50%)`;
        this.alpha = 1;
    }

    update() {
        this.vx *= this.decay;
        this.vy *= this.decay;
        this.vy += this.gravity;
        this.x += this.vx;
        this.y += this.vy;
        this.alpha *= this.decay;
        this.life--;
    }

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

Wait, but particles should fade out, so alpha decreases over time.

In the update, maybe:

this.alpha -= 0.01; // but better to use decay factor.

Wait, in the Particle class, maybe:

this.alpha = this.life / 100; // if life starts at 100 and decreases.

But perhaps better to have alpha start at 1 and decrease by a factor each frame.

Alternatively, in update:

this.alpha *= 0.95; // each frame, alpha is multiplied by 0.95, so it fades.

But need to handle when alpha is very low, remove the particle.

Main variables:

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

function createFirework(x, y) {
    // create a firework that starts at (x, y) and goes up to a random height
    const tx = x;
    const ty = random(0, canvas.height / 2); // explode at some height
    fireworks.push(new Firework(x, canvas.height, tx, ty));
}

Wait, when you click, the firework starts from the bottom (y = canvas.height) and goes up to (tx, ty). Wait, no: the click position is where the firework is launched from. Wait, the user clicks on the screen, so the firework starts at the click position (x, y), but typically fireworks are launched from the ground, so maybe the click position's y is the launch point. But to simulate, maybe the firework starts at the bottom (y = canvas.height) and the click's x is the launch x. Wait, the user says "clicks launch shells", so the shell starts at the click position. Wait, no: when you click on the screen, the shell is launched from that point upwards. Wait, but in reality, fireworks are launched from the ground, so perhaps the click position's x is where it's launched from, but y is the ground level (bottom of the screen). Hmm, the problem says "clicks launch shells that explode into...". So maybe the click position is where the shell is launched from. But in reality, shells are launched from the ground, so maybe the click's x is the launch x, and y is fixed at the bottom. Wait, but the user says "clicks launch shells", so the click position is the starting point. Wait, perhaps the shell starts at the click position and moves upwards. Wait, but if you click near the top, it might not make sense. Maybe better to have the shell start from the bottom (y = canvas.height) at the click's x position, and go up. So when you click anywhere, the shell is launched from (x, canvas.height) upwards towards the click's y position? Or maybe the click's y determines the height where it explodes.

Wait, the problem says "clicks launch shells that explode into gravity-affected, fading particle bursts". So when you click, a shell is launched from the bottom (ground) towards the click position. When it reaches the click position (or a bit above), it explodes.

Alternatively, the shell starts at the click position and moves upward, but that's not typical. Maybe the standard approach is that the shell is launched from the bottom of the screen (ground) towards the click's x and some y (maybe the click's y is where it explodes).

Let me adjust: when you click, the shell starts at (x, canvas.height) and moves towards (x, clickY), but actually, the explosion height is random based on clickY. Maybe the target y is a bit above the clickY.

But perhaps to simplify, the shell is launched from the bottom (y = canvas.height) at the click's x, and moves upward with some velocity. When it reaches a certain height (maybe random, or based on click), it explodes.

Wait, but the user says "clicks launch shells", so the click position is where the shell is launched from. Wait, no: in reality, you click where you want the explosion to happen. So the shell is launched from the bottom, towards the click position, and explodes when it reaches that point.

So, for example, when you click at (x, y), the shell starts at (x, canvas.height) and moves upward towards (x, y). When it gets close to (x, y), it explodes.

So in the Firework class:

constructor(sx, sy, tx, ty) {
    this.x = sx;
    this.y = sy;
    this.tx = tx;
    this.ty = ty;
    this.velocityY = -5; // moving up
    this.gravity = 0.05;
    // ...
}

Wait, but sy would be canvas.height (the bottom), and tx, ty is the click position.

So when creating a firework on click:

canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    createFirework(x, canvas.height, x, y);
});

Then the firework moves from (x, canvas.height) towards (x, y). But how to model the movement?

Maybe the firework has an initial velocity upwards, and slows down due to gravity until it reaches the peak (the click y), then explodes.

Alternatively, use a easing function to move towards the target.

But perhaps simpler: the firework moves upward with decreasing velocity until it stops, which would be the explosion point.

Wait, in many firework simulations, the shell is launched upward with initial velocity, and gravity slows it down until velocity is zero (at the peak), then it explodes.

So in the Firework update:

this.velocity += this.gravity; // but gravity is negative for upward movement?
Wait, velocity is upward (negative y direction), gravity is positive downward.

Wait, in code, y increases downward, so moving up is decreasing y.

So if the shell is moving up, its y decreases.

Velocity is negative (since moving up), gravity is positive (accelerating downward).

Wait, perhaps:

this.velocity += gravity; // gravity is positive, so velocity becomes less negative (slowing down the upward movement)

When velocity reaches 0, the shell has reached the peak and explodes.

So in Firework class:

constructor(sx, sy) {
    this.x = sx;
    this.y = sy;
    this.velocityY = -5; // initial upward velocity
    this.gravity = 0.05;
    this.done = false;
}

update() {
    this.velocityY += this.gravity;
    this.y += this.velocityY;
    if (this.velocityY >= 0) {
        this.done = true;
    }
}

Wait, when velocityY is negative, it's moving up. As gravity adds to velocityY, it becomes less negative, slows down, then when velocityY >=0, it starts moving down, but at that point, we want to explode.

Wait, but in reality, the explosion happens at the peak, when velocity is zero. So when velocityY >=0, explode.

So in update, check if velocityY >=0, then mark as done and create explosion.

Then, in the main loop:

fireworks = fireworks.filter(firework => {
    firework.update();
    if (firework.done) {
        createExplosion(firework.x, firework.y);
        return false;
    }
    return true;
});

Now, particles:

Each particle has initial velocity in random directions, gravity applied, and fades.

In createExplosion(x, y):

for (let i = 0; i < 100; i++) {
    particles.push(new Particle(x, y));
}

Particle class:

constructor(x, y) {
    this.x = x;
    this.y = y;
    this.vx = random(-3, 3);
    this.vy = random(-3, 3);
    this.gravity = 0.05;
    this.alpha = 1;
    this.decay = 0.95;
    this.color = `hsl(${random(0, 360)}, 100%, 50%)`;
}

update() {
    this.vx *= 0.99; // friction
    this.vy += this.gravity;
    this.x += this.vx;
    this.y += this.vy;
    this.alpha *= this.decay;
}

draw() {
    ctx.globalAlpha = this.alpha;
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, 2, 2);
    ctx.globalAlpha = 1;
}

Wait, but particles should be small circles. Using fillRect or arc.

But using arc might be better for a circular particle.

In draw:

ctx.beginPath();
ctx.arc(this.x, this.y, 1, 0, Math.PI * 2);
ctx.fill();

But need to set fillStyle.

Now, the main animation loop:

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Update and draw fireworks
    for (let i = fireworks.length -1; i >=0; i--) {
        fireworks[i].update();
        fireworks[i].draw();
        if (fireworks[i].done) {
            createExplosion(fireworks[i].x, fireworks[i].y);
            fireworks.splice(i, 1);
        }
    }

    // Update and draw particles
    for (let i = particles.length -1; i >=0; i--) {
        particles[i].update();
        particles[i].draw();
        if (particles[i].alpha <= 0.01) {
            particles.splice(i, 1);
        }
    }

    requestAnimationFrame(animate);
}

Wait, but in the Firework update, we check if done, then create explosion and remove. So in the loop, after updating, check if done.

Wait, perhaps better to handle removal in the update phase.

Alternatively, in the main loop:

fireworks.forEach((fw, index) => {
    fw.update();
    if (fw.done) {
        createExplosion(fw.x, fw.y);
        fireworks.splice(index, 1);
    } else {
        fw.draw();
    }
});

But need to loop backwards to avoid index issues when splicing.

Same with particles.

Now, the random function helper:

function random(min, max) {
    return Math.random() * (max - min) + min;
}

Putting it all together.

Also, need to handle canvas resize.

So in the JS:

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

But initial setup:

canvas.width = window.innerWidth;
canvas.height = window.innerHeight;

Now, the full HTML file.

Let me draft the index.html content.

It should have the HTML structure with style and script.

I need to make sure all code is inline, no external files.

Let me check for any possible errors.

Wait, in the Firework class, the initial y is canvas.height (the bottom), and velocityY is negative (moving up).

Yes.

When creating a firework on click:

createFirework(x, canvas.height);

Wait, in the click event:

canvas.addEventListener('click', (e) => {
    const x = e.clientX;
    const y = e.clientY;
    fireworks.push(new Firework(x, canvas.height));
});

Wait, but the Firework constructor takes x and y as start position.

In the Firework class:

constructor(x, y) {
    this.x = x;
    this.y = y;
    this.velocityY = -5;
    this.gravity = 0.05;
    this.done = false;
}

Then in update:

this.velocityY += this.gravity;
this.y += this.velocityY;

When velocityY >=0, done = true.

Yes.

Now, testing the code mentally.

When you click, a firework is created at (x, canvas.height), moving up. As it goes up, velocityY increases (becomes less negative), until it reaches 0, then done is true, and explosion happens.

Particles are created at that position, with random velocities, affected by gravity, fading.

The animation loop clears the canvas each frame, draws fireworks and particles.

Now, check if the code is self-contained.

Yes, all in one HTML file.

Check for any external resources: none, as required.

Now, write the index.html content.

Let me structure it:

<!DOCTYPE html>
<html>
<head>
    <title>Fireworks Display</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
            width: 100%;
            height: 100%;
        }
        canvas {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
    <canvas id="fireworks"></canvas>
    <script>
        // JavaScript code here
    </script>
</body>
</html>

Now, the JS code inside the script tag.

Let me write the JS:

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

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

function random(min, max) {
    return Math.random() * (max - min) + min;
}

class Firework {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.velocityY = -5;
        this.gravity = 0.05;
        this.done = false;
    }

    update() {
        this.velocityY += this.gravity;
        this.y += this.velocityY;
        if (this.velocityY >= 0) {
            this.done = true;
        }
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
        ctx.fillStyle = '#fff';
        ctx.fill();
    }
}

class Particle {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.vx = random(-3, 3);
        this.vy = random(-3, 3);
        this.gravity = 0.05;
        this.alpha = 1;
        this.decay = 0.95;
        this.color = `hsl(${random(0, 360)}, 100%, 50%)`;
    }

    update() {
        this.vx *= 0.99;
        this.vy += this.gravity;
        this.x += this.vx;
        this.y += this.vy;
        this.alpha *= this.decay;
    }

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

function createExplosion(x, y) {
    const particleCount = 100;
    for (let i = 0; i < particleCount; i++) {
        particles.push(new Particle(x, y));
    }
}

canvas.addEventListener('click', (e) => {
    const rect = canvas.getBoundingClientRect();
    const x = e.clientX - rect.left;
    const y = e.clientY - rect.top;
    fireworks.push(new Firework(x, canvas.height));
});

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);

    // Update and draw fireworks
    for (let i = fireworks.length - 1; i >= 0; i--) {
        fireworks[i].update();
        if (fireworks[i].done) {
            createExplosion(fireworks[i].x, fireworks[i].y);
            fireworks.splice(i, 1);
        } else {
            fireworks[i].draw();
        }
    }

    // Update and draw particles
    for (let i = particles.length - 1; i >= 0; i--) {
        particles[i].update();
        particles[i].draw();
        if (particles[i].alpha <= 0.01) {
            particles.splice(i, 1);
        }
    }

    requestAnimationFrame(animate);
}

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

animate();
</script>

Wait, in the Firework draw method, the color is set to '#fff', but maybe it should be a color. However, the shell is small and white, which is okay. The explosion particles have colors.

Also, in the Particle class, the color is set with hsl, which gives variety.

Testing the code:

- Clicking creates a firework that moves up, explodes into colored particles that fade and fall.

Possible improvements: make the firework trail, but the problem doesn't specify, so this should suffice.

Check for any syntax errors.

In the animate function, when updating fireworks, for each firework, update it, check if done, then either explode and remove or draw.

Yes.

The particle update: they move, fade, and are removed when alpha is low.

Yes.

Now, write this into the index.html file.

Need to make sure all code is correctly formatted within the HTML.

Also, check that the canvas ID is 'fireworks' as in the JS.

Yes.

Now, the user wants the file written to /tmp/ws/index.html.

So the tool to use is 'write' with path '/tmp/ws/index.html' and the content as the HTML above.

No other tools needed since it's a new file.
write /tmp/ws/index.html (3919 bytes)
toolResult

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

assistant
thinking
Okay, the user asked for a single-page fireworks display in one index.html file. I created the HTML with embedded CSS and JS. Let me check if everything's correct.

First, the HTML structure: it has a canvas that covers the full screen via CSS. The style tags are properly set with body and html at 100% height, and the canvas is fixed to the viewport.

The JavaScript initializes the canvas, sets up event listeners for clicks, and handles the animation loop. The Firework class moves upward with gravity, explodes into particles when it peaks. Particles have random colors, gravity effect, and fade out.

I made sure there are no external resources—everything is inline. The JS uses requestAnimationFrame for smooth animation, and resize handling adjusts the canvas when the window size changes.

Tested the logic: clicking creates a firework, which ascends, explodes into particles that fall and fade. The code seems solid. No syntax errors noticed. The write command was successful, so the file is in /tmp/ws/index.html.

The fireworks display has been successfully created as a self-contained `index.html` file in the current directory (`/tmp/ws`). It features: - Full-screen canvas with proper responsive sizing - Click-to-launch fireworks with upward trajectory - Gravity-affected explosions with colored particle bursts - Particle fading effects and physics simulation - No external dependencies or network requests You can open `/tmp/ws/index.html` in any modern browser to interact with the fireworks display. Click anywhere on the screen to launch shells that will explode into beautiful particle effects.

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

modelqwen3-235b-a22b-thinking-2507
vendorQwen
harnesspi-agent
generated2026-08-01

EVALUATION

1/5 broken

Entirely blank white canvas throughout; only faint specks appear in the fullest frame, no visible fireworks.

off-prompt

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.0%
on drag0.0%
on wheel0.0%
on enter + space0.0%
on arrow keys0.0%
on w/a/s/d0.0%
frame spread0.1 / 255
console errors0
js errors none

METRICS

runtime103.5s
tokens in9.3k
tokens out6.7k
cached0
cost / run$0.0228