oneshotlm
Compare all models on this prompt →

Fireworks · qwen3-235b-a22b

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 that displays fireworks on a full-screen canvas. When the user clicks, shells launch and explode into particles that are affected by gravity and fade out. Let me think about how to approach this.

First, the HTML structure. The user wants everything in one index.html file, so I'll use a canvas element that takes up the full screen. Then, include CSS in a style tag to set the canvas size to 100% of the viewport and maybe a dark background. JavaScript will handle the animation and physics.

For the JavaScript part, I'll need to set up the canvas context, create firework shells that launch when the user clicks, and then have them explode into particles. Each particle should have gravity applied, so they slow down and fall. Also, fading can be done by reducing the alpha value over time.

Let me outline the steps:

1. Create the canvas element in HTML with full-screen size.
2. CSS: body margin/padding reset, canvas covering 100vw and 100vh.
3. JavaScript:
   - Setup canvas and context.
   - Handle click events to launch shells.
   - Update and draw each frame.
   - Create particles when a shell explodes.
   - Apply gravity and fading to particles.

I'll need to define objects for the firework shells and particles. The shell will have a position, velocity, and a method to update and draw itself. When it reaches the peak, it explodes into particles.

For the particles, each will have position, velocity, alpha, and size. They'll be affected by gravity, so their velocity in the y-direction increases downward. Each frame, update their position, decrease their alpha, and remove them when alpha is below a threshold.

The animation loop will be a function that clears the canvas, updates all particles, and redraws them. Also, handle the shell's movement until it explodes.

Let me think about the physics: gravity can be a constant added to the y velocity each frame. For fading, each particle's alpha decreases by a certain amount each frame.

Potential issues: performance with many particles. But since it's a simple example, using requestAnimationFrame should handle it.

Now, putting it all together. The code structure:

- HTML: canvas element with id "canvas".
- CSS: full-screen canvas.
- JS:
  - Get canvas context.
  - Listen for click events to create new shells.
  - Animate function with requestAnimationFrame.
  - Arrays to keep track of shells and particles.
  - Update and draw functions for each object.

Testing: when a shell is launched, it moves upward, then explodes into particles that spread out and fall.

Let me draft the code. The shell starts at the click position, moves upward, then explodes. Particles are created with random velocities, and each has a decreasing alpha.

I'll need to calculate the explosion position. Maybe when the shell's y velocity reaches a certain point, it explodes.

Wait, actually, shells should go up, then explode. So the shell has a position (x, y), velocity (vy upwards), and when it reaches a certain height or when its velocity becomes negative (starts to fall), it explodes into particles.

But maybe to make it explode at a certain height, or maybe when the velocity makes it start to go up, but then it's time to explode. Hmm, perhaps when the shell's velocity in the y direction becomes negative (it starts to fall), that's when it explodes.

Alternatively, set a random explosion height. Let me think. For simplicity, when the shell's velocity in the y direction changes from positive to negative (apex), explode.

So the shell's update function checks if its velocity.y is negative, then trigger explosion.

Now, for the particles, create a bunch of particles at the shell's position when it explodes. Each particle has a random direction and speed, and then is affected by gravity.

The particles' draw function will use a circle with a color that fades as alpha decreases.

Putting this into code. Let me write the JavaScript:

- Define a Shell class with x, y, vy, and exploded state.
- In the update, add gravity to vy, update y position. If vy becomes negative and not exploded, create particles and set exploded to true.
- Particles are objects with x, y, vx, vy, alpha, size.
- Each frame, update particles: apply gravity to vy, update x and y, decrease alpha.
- Draw particles as circles with current alpha.

Also, handle the canvas resizing in case the window size changes, but maybe the user wants full-screen, so set the canvas size on window resize.

Wait, the user said full-screen canvas. So in CSS, set canvas to width and height 100%, and maybe use object-fit: cover? Or just set the canvas dimensions to window.innerWidth and window.innerHeight. But for simplicity, using CSS to make the canvas full-screen.

Now, code:

HTML:

<!DOCTYPE html>
<html>
<head>
    <title>Fireworks</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// JavaScript code here
</script>
</body>
</html>

In the script:

Get the canvas and context. Set up variables:

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

But with CSS, maybe the canvas size is set by the CSS, but for high DPI, it's better to set the canvas's width and height to devicePixelRatio * window.innerWidth, etc. But for simplicity, maybe just use window.innerWidth and innerHeight.

Wait, but if the canvas is styled with 100vw and 100vh, then the actual pixel dimensions depend on the device pixel ratio. To make it high DPI, perhaps set the canvas's width and height to window.innerWidth * devicePixelRatio, etc., and then scale the context. But that might complicate things. For this example, maybe just use the CSS approach and set the canvas's width and height to the window's innerWidth and innerHeight.

Alternatively, use the CSS to make the canvas fill the viewport, and set the canvas's width and height to the same as the CSS. But in practice, the CSS might stretch the canvas if the aspect ratios differ. Hmm, perhaps better to set the canvas dimensions to match the window's innerWidth and innerHeight.

So:

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

But in the CSS, the canvas will be 100% of the viewport, which matches the actual pixel dimensions if we set the canvas's width and height accordingly. So that's okay.

Now, the firework shells and particles. Let's have an array for shells and particles.

let shells = [];
let particles = [];

On click:

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

Wait, but the shell should start from the bottom of the canvas, maybe. Or from the click position? The user said "clicks launch shells". So maybe the shell starts at the click position and goes upward.

But usually, fireworks are launched from a point, maybe the bottom. But the user might mean that clicking anywhere on the screen launches a shell from that point upwards. Let's go with that.

So when the user clicks, create a new shell at the click position.

The Shell class:

class Shell {
    constructor(x, y) {
        this.x = x;
        this.y = y;
        this.vy = -5; // initial upward velocity
        this.exploded = false;
        this.color = 'white';
    }

    update() {
        this.y += this.vy;
        this.vy += 0.05; // gravity pulling down (small value)
        if (this.vy >= 0 && !this.exploded) {
            this.explode();
            this.exploded = true;
        }
    }

    explode() {
        // Create particles
        const count = 50;
        for (let i = 0; i < count; i++) {
            const angle = Math.random() * 2 * Math.PI;
            const speed = Math.random() * 3;
            const vx = Math.cos(angle) * speed;
            const vy = Math.sin(angle) * speed;
            particles.push(new Particle(this.x, this.y, vx, vy, this.color));
        }
    }

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

Wait, but the Shell's color could be random. Maybe each shell has a random color for the explosion.

Alternatively, the particles can have varying colors, but for simplicity, maybe the shell's color is used.

Now, the Particle class:

class Particle {
    constructor(x, y, vx, vy, color) {
        this.x = x;
        this.y = y;
        this.vx = vx;
        this.vy = vy;
        this.alpha = 1;
        this.size = 3;
        this.color = color;
    }

    update() {
        this.x += this.vx;
        this.y += this.vy;
        this.vy += 0.05; // gravity
        this.alpha -= 0.01; // fade
    }

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

But the color could be randomized. Maybe each particle has a different color, but for simplicity, the shell's color is passed to the particles.

Wait, the Shell's explode method creates particles with its color. But if the shell's color is white, all particles are white. Maybe to make it more colorful, each shell can have a random color.

Modify the Shell constructor:

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

That way, each shell has a random color, and the particles will have that color.

Now, the animation loop:

function animate() {
    requestAnimationFrame(animate);
    ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'; // semi-transparent black to create trailing effect
    ctx.fillRect(0, 0, width, height);

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

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

animate();

Wait, but the ctx.fillStyle is set to a semi-transparent black each frame, which allows the trails to fade out. That's good for the particles' trailing effect.

But in the code above, the ctx.fillStyle is set to 'rgba(0,0,0,0.2)' and then fillRect is called. That's correct.

Putting all together.

But the Shell's vy is increased by 0.05 each frame. The value might need adjustment. Let's test with these values.

Potential issues:

- The particles might move too fast or too slow. Adjust speed and gravity values.

Also, the initial vy of the shell is -5 (upwards), and gravity adds 0.05 each frame. So the shell will slow down, reach a peak when vy becomes 0, then start falling.

In the Shell's update, when vy >=0 and not exploded, explode.

But once exploded, the shell is removed from the array.

In the animate loop, after drawing, shells that have exploded are spliced out.

Similarly, particles are removed when their alpha is below 0.

Now, testing this code. But since I can't actually test it here, I need to make sure the code is correct.

Another thing: when creating the particles, the initial velocity is random in direction and speed. Each particle has a random angle and speed.

But in the code above, the speed is Math.random() * 3. So particles will have varying speeds.

Now, putting all this into the index.html file.

The complete code would be:

<!DOCTYPE html>
<html>
<head>
    <title>Fireworks</title>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            overflow: hidden;
        }
        canvas {
            display: block;
            width: 100vw;
            height: 100vh;
            background: #000;
        }
    </style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    let width = canvas.width = window.innerWidth;
    let height = canvas.height = window.innerHeight;

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

    class Shell {
        constructor(x, y) {
            this.x = x;
            this.y = y;
            this.vy = -5;
            this.exploded = false;
            this.color = `hsl(${Math.random() * 360}, 100%, 50%)`;
        }

        update() {
            this.y += this.vy;
            this.vy += 0.05;
            if (this.vy >= 0 && !this.exploded) {
                this.explode();
                this.exploded = true;
            }
        }

        explode() {
            const count = 50;
            for (let i = 0; i < count; i++) {
                const angle = Math.random() * 2 * Math.PI;
                const speed = Math.random() * 3;
                const vx = Math.cos(angle) * speed;
                const vy = Math.sin(angle) * speed;
                particles.push(new Particle(this.x, this.y, vx, vy, this.color));
            }
        }

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

    class Particle {
        constructor(x, y, vx, vy, color) {
            this.x = x;
            this.y = y;
            this.vx = vx;
            this.vy = vy;
            this.alpha = 1;
            this.size = 3;
            this.color = color;
        }

        update() {
            this.x += this.vx;
            this.y += this.vy;
            this.vy += 0.05;
            this.alpha -= 0.01;
        }

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

    let shells = [];
    let particles = [];

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

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

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

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

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

Wait, but in the code, the canvas width and height are set to window.innerWidth and innerHeight, but the CSS sets the canvas to 100vw and 100vh. This might cause issues with scaling on high-DPI displays. However, for the purpose of this exercise, it's acceptable as the user requested a basic implementation.

Another thing: the gravity value (0.05) for the shell and particles might need tweaking. If the shell explodes too quickly or the particles fall too fast, adjust the values. But this is part of the code that can be fine-tuned, but since the user didn't specify exact behavior, the code as written should work.

Testing this code, clicking on the canvas should create a shell that moves upward, then explodes into particles that spread out and fall with gravity, fading out.

I think this covers the requirements. Now, I need to write this into the index.html file using the write function.
write /tmp/ws/index.html (3529 bytes)
toolResult

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

assistant
thinking
Okay, I've successfully written the index.html file with the fireworks display code. The user wanted a self-contained single-page app with CSS and JS inline, which I've achieved. The code includes a full-screen canvas that responds to clicks by launching shells that explode into particles affected by gravity and fading out.

I should confirm that the file is created and maybe suggest opening it in a browser to test. Since the user might want to see it in action, mentioning that they can open the file in a browser to view the fireworks effect would be helpful. Also, ensuring that there are no external dependencies as required, since everything is inline and no CDNs are used.

I think that's all. The task is completed as per the user's request.

The fireworks display has been created successfully! 🎆 To use: 1. Open `/tmp/ws/index.html` in a browser 2. Click anywhere on the canvas to launch a firework shell 3. Watch as shells explode into colorful particle bursts affected by gravity 4. Particles will fade out naturally as they fall Features: - Full-screen responsive canvas - Randomized color explosions - Gravity physics and particle fading - No external dependencies or network requests - Self-contained in a single HTML file You can test it by opening the file in any modern browser. The effect should work offline with no additional setup required.

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
vendorQwen
harnesspi-agent
generated2026-08-01

EVALUATION

3/5 partial

Click launches a firework that explodes into fading particle bursts; drag and keyboard inputs show no effect.

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

METRICS

runtime75.2s
tokens in7.8k
tokens out5.1k
cached0
cost / run$0.0129