Fireworks · deepseek-r1-0528
Create a fireworks display on a full-screen canvas where clicks launch shells that explode into gravity-affected, fading particle bursts.
This one-shot run produced no runnable artifact.
agent produced no index.html
The transcript below shows what the model did — read it to see why the one shot fell short.
transcript · 2 messages
Create a fireworks display on a full-screen canvas where clicks launch shells that explode into gravity-affected, fading particle bursts.
thinking
We are creating a single HTML file (index.html) that contains:
- A full-screen canvas for the fireworks display.
- JavaScript to handle:
* Resizing the canvas to full-screen.
* Drawing fireworks and particles.
* Handling mouse clicks to launch fireworks.
* Animation loop.
- All CSS and JS inline.
Features:
1. When the user clicks, a firework shell is launched from the bottom center to the clicked position.
2. When the shell reaches the target, it explodes into multiple particles (with random colors and velocities).
3. Particles are affected by gravity and fade over time.
Steps:
- We'll create an HTML5 canvas that covers the entire viewport.
- We'll set up an animation loop using requestAnimationFrame.
- We'll have two arrays: one for the shells (in flight) and one for the particles (explosions).
- On click, we create a shell that moves upward (from the bottom center) to the click point.
- The shell will have a starting position (bottom center) and target (click point).
- The shell moves with a constant velocity until it reaches the target, then it explodes (removed from shells and particles added).
- Each explosion creates a set of particles that:
* Have random velocities (in different directions)
* Are affected by gravity (acceleration downward)
* Fade out over time (reduce opacity or diminish in size and then remove when done)
Implementation details:
Classes (or object constructors):
Shell:
- start: {x, y} (bottom center initially)
- target: {x, y}
- current: {x, y} (current position, starts at start)
- color: random or chosen
- speed: constant speed per frame (we can calculate the velocity vector from start to target and set magnitude)
We can calculate:
dx = target.x - start.x
dy = target.y - start.y
distance = Math.sqrt(dx*dx+dy*dy)
velocity = (dx/distance)*speed, (dy/distance)*speed
Update:
current.x += velocity.x
current.y += velocity.y
Check if distance from current to target is very small (or if it has passed the target? we can check by moving and then if it overshoots, set to target and explode)
Particle:
- position: {x, y} (start at explosion point)
- velocity: {x, y} (random in all directions, scaled by a random factor)
- color: could be random or inherited from shell
- friction: to slow down particles (e.g., multiply velocity by 0.95 each frame)
- gravity: adds downward acceleration (e.g., adding to velocity.y each frame)
- opacity: start at 1 and decrease to 0
- size: also might decrease?
Update:
velocity.y += gravity
velocity.x *= friction
velocity.y *= friction
position.x += velocity.x
position.y += velocity.y
opacity -= rate
Then when opacity is <=0 or position goes off screen, we mark for removal.
We'll also handle resizing the window.
Let's design:
HTML:
<!DOCTYPE html>
<html>
<head>
<title>Fireworks</title>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
// All Javascript here
</script>
</body>
</html>
JavaScript:
We'll set up:
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Shells and particles arrays
let shells = [];
let particles = [];
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
// Mouse click event: add a new shell
canvas.addEventListener('click', function(e) {
// Launch from bottom center (x: canvas.width/2, y: canvas.height) to (e.clientX, e.clientY)
createShell(canvas.width/2, canvas.height, e.clientX, e.clientY);
});
function createShell(startX, startY, targetX, targetY) {
// Create shell object and push to shells
}
function explode(x, y, color) {
// Create multiple particles (maybe 50-200) and push to particles array
for (let i = 0; i < 100; i++) {
let angle = Math.random() * Math.PI * 2;
let speed = Math.random() * 5 + 1; // random speed between 1 and 6
let velocityX = Math.cos(angle) * speed;
let velocityY = Math.sin(angle) * speed;
particles.push(new Particle(x, y, velocityX, velocityY, color));
}
}
function Particle(x, y, vx, vy, color) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.color = color;
this.opacity = 1;
this.gravity = 0.1;
this.friction = 0.98;
this.fadeRate = 0.015;
}
Particle.prototype.update = function() {
this.vx *= this.friction;
this.vy *= this.friction;
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.opacity -= this.fadeRate;
return this.opacity > 0; // if still visible, keep; else remove
};
// Similarly for Shell
function Shell(startX, startY, targetX, targetY) {
this.startX = startX;
this.startY = startY;
this.targetX = targetX;
this.targetY = targetY;
this.x = startX;
this.y = startY;
this.color = `hsl(${Math.random()*360}, 100%, 50%)`;
// Calculate velocity vector
let dx = targetX - startX;
let dy = targetY - startY;
let distance = Math.sqrt(dx*dx+dy*dy);
this.speed = 8; // pixels per frame
this.vx = (dx/distance) * this.speed;
this.vy = (dy/distance) * this.speed;
this.arrived = false;
}
Shell.prototype.update = function() {
if (!this.arrived) {
this.x += this.vx;
this.y += this.vy;
// Calculate distance to target
let dx = this.targetX - this.x;
let dy = this.targetY - this.y;
if (Math.sqrt(dx*dx+dy*dy) < 5) { // if very close
this.arrived = true;
explode(this.x, this.y, this.color);
return false; // remove this shell
}
}
return !this.arrived; // if not arrived yet, keep; if arrived (exploded) then remove
};
// The animation loop
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update shells
for (let i = shells.length - 1; i >= 0; i--) {
let shell = shells[i];
let keep = shell.update();
if (!keep) {
shells.splice(i, 1);
} else {
// Draw the shell (as a circle)
ctx.beginPath();
ctx.arc(shell.x, shell.y, 2, 0, Math.PI * 2);
ctx.fillStyle = shell.color;
ctx.fill();
}
}
// Update particles
for (let i = particles.length - 1; i >= 0; i--) {
let p = particles[i];
let keep = p.update();
if (!keep) {
particles.splice(i, 1);
} else {
ctx.beginPath();
ctx.arc(p.x, p.y, 2 * p.opacity, 0, Math.PI * 2); // size reduces with opacity?
ctx.fillStyle = `rgba(${parseInt(p.color.substring(4, p.color.length-1).split(',').map(c => parseInt(c))) || '0,0,0'},${p.opacity})`;
// Actually, we stored color as hsl string, so we cannot directly use rgba. We can adjust the drawing style.
// Instead, we can store color as HSL string and then convert to rgba with opacity? Or we could store HSLA?
// Alternatively, we can change to use RGB for particles? Or we can just adjust the drawing.
// Let's change our approach: store color as separate RGB? Or we can use `hsla` by string.
// Since we used HSL for the shell, we can do:
// `hsla(${hue}, 100%, 50%, ${p.opacity})`
// But the particle color was set from the shell's HSL string. How can we extract hue?
// We can store the hue separately? Or we can design the particle to be the same hue?
// I think we should change the Shell and Particle to use HSL with an alpha.
// Actually, we did not store the HSL components separately. We have `this.color` as a string.
// Since we are generating the shell color with:
// `hsl(${Math.random()*360}, 100%, 50%)`
// Then we can do for particles:
// ctx.fillStyle = `hsla(${hue}, 100%, 50%, ${p.opacity})`
// How to get the hue? We could store the hue in the Particle? Or we can parse the shell color string?
// Alternative: we change the Shell to store the hue, and then pass that to particles.
// Instead, we can store the color as an object {h, s, l}? We don't want to complicate.
// Since this is a simple example, we can generate the particle color from the shell's HSL string in a better way?
// Actually, the shell color string is: `hsl(${h}, 100%, 50%)`
// So we can extract the hue by using the string?
// We can do:
// let hue = shell.color.match(/hsl\((\d+)/)[1];
// Then in the particle we can use `hsla(${hue}, 100%, 50%, ${opacity})`
// But in the Particle we don't have the shell anymore. So when creating the particle, we should set the color as `hsla(...)` with the alpha?
// Or we can store the color string without the opacity and then change the fillStyle each time.
// We'll adjust the particle to store the base color (without opacity) and then use for drawing with current opacity.
// Actually, we can change the Shell to store the base HSL and pass that to particles?
// Alternatively, we can store the HSL in the shell as a number and then pass it.
// Let's refactor:
// In the Shell, store the hue:
// this.hue = Math.random()*360;
// this.color = `hsl(${this.hue}, 100%, 50%)`;
// Then when creating particles, pass the hue.
// So for the particle, we will store the hue and then use:
// `hsla(${this.hue}, 100%, 50%, ${this.opacity})`
// Let's change both the Shell and Particle to store hue.
// We'll update the code accordingly.
// Since we are generating the shell with a random hue, we can pass the hue to particles.
// Let me adjust:
// In createShell:
// We are setting shell.color = `hsl(...)` and also we don't have the stored hue? So we'll add a property for hue.
// Instead, we can have in the Shell constructor:
// this.hue = Math.random()*360;
// this.color = `hsl(${this.hue}, 100%, 50%)`;
// Then when exploding, pass the hue.
// And in Particle:
// this.hue = hue;
// Then when drawing the particle:
// ctx.fillStyle = `hsla(${this.hue}, 100%, 50%, ${this.opacity})`;
// Given we have time, let's assume we did that.
// How about we don't change the shell and particle for now? We'll use a simple solution: pass the shell's hue to the particle.
// Since we are not showing the code until now, we'll adjust the initial design.
// We are creating the Particle function below. Let me update:
// function Particle(x, y, vx, vy, hue) {
// ... and then use the hue to set the color when drawing.
// In explode, we pass the shell's hue.
// So in the shell, we change to store the hue.
// Let's go back and adjust.
// Due to time, I'll update the code below accordingly.
// Actually, I already described the change above. We'll set:
// In Shell: this.hue = Math.random()*360;
// In explode: pass the hue instead of the color string.
// Then in Particle, we use this.hue.
// So we change the particle drawing to:
ctx.fillStyle = `hsla(${p.hue}, 100%, 70%, ${p.opacity})`;
// Let me use 70% lightness to make it a bit brighter.
ctx.fill();
}
}
requestAnimationFrame(animate);
}
// Start animation
animate();
Let's code accordingly.
We are going to write the entire code in one index.html file.
Note: We are using ES5 for simplicity (function constructors) to avoid needing classes for compatibility.
We'll also adjust the shell update: if the shell hasn't arrived, we update and draw. If it arrives, we remove it and create particles.
Let's write the HTML, CSS, and JS accordingly.
Since we are writing in one file, we can put the whole JavaScript inside the script tag.
We'll write:
<!DOCTYPE html>
<html>
<head>
<title>Fireworks</title>
<style>
body { margin: 0; overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script>
(function() {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let shells = [];
let particles = [];
// Shell constructor
function Shell(startX, startY, targetX, targetY) {
this.startX = startX;
this.startY = startY;
this.targetX = targetX;
this.targetY = targetY;
this.x = startX;
this.y = startY;
this.hue = Math.random() * 360;
this.color = `hsl(${this.hue}, 100%, 50%)`;
let dx = targetX - startX;
let dy = targetY - startY;
let distance = Math.sqrt(dx*dx + dy*dy);
this.speed = 8;
this.vx = (dx / distance) * this.speed;
this.vy = (dy / distance) * this.speed;
this.arrived = false;
}
Shell.prototype.update = function() {
if (this.arrived) {
return false;
}
this.x += this.vx;
this.y += this.vy;
// Check if we reached the target (within a threshold)
let dx = this.targetX - this.x;
let dy = this.targetY - this.y;
if (Math.sqrt(dx*dx + dy*dy) < 5) {
this.arrived = true;
explode(this.x, this.y, this.hue);
return false;
}
return true;
};
// Particle constructor
function Particle(x, y, vx, vy, hue) {
this.x = x;
this.y = y;
this.vx = vx;
this.vy = vy;
this.hue = hue;
this.opacity = 1;
this.gravity = 0.1;
this.friction = 0.98;
this.fadeRate = 0.015;
}
Particle.prototype.update = function() {
this.vx *= this.friction;
this.vy *= this.friction;
this.vy += this.gravity;
this.x += this.vx;
this.y += this.vy;
this.opacity -= this.fadeRate;
return this.opacity > 0;
};
function explode(x, y, hue) {
const count = 100;
for (let i = 0; i < count; i++) {
let angle = Math.random() * Math.PI * 2;
let speed = Math.random() * 5 + 1;
let vx = Math.cos(angle) * speed;
let vy = Math.sin(angle) * speed;
particles.push(new Particle(x, y, vx, vy, hue));
}
}
function createShell(startX, startY, targetX, targetY) {
shells.push(new Shell(startX, startY, targetX, targetY));
}
function init() {
window.addEventListener('resize', resize);
canvas.addEventListener('click', handleClick);
resize();
animate();
}
function resize() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
function handleClick(e) {
createShell(canvas.width / 2, canvas.height, e.clientX, e.clientY);
}
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Update and draw shells
for (let i = shells.length - 1; i >= 0; i--) {
if (!shells[i].update()) { // update returns false when shell should be removed
shells.splice(i, 1);
} else {
drawShell(shells[i]);
}
}
// Update and draw particles
for (let i = particles.length - 1; i >= 0; i--) {
if (!particles[i].update()) {
particles.splice(i, 1);
} else {
drawParticle(particles[i]);
}
}
requestAnimationFrame(animate);
}
function drawShell(shell) {
ctx.beginPath();
ctx.arc(shell.x, shell.y, 2, 0, Math.PI * 2);
ctx.fillStyle = shell.color;
ctx.fill();
}
function drawParticle(particle) {
ctx.beginPath();
ctx.arc(particle.x, particle.y, 2, 0, Math.PI * 2);
ctx.fillStyle = `hsla(${particle.hue}, 100%, 70%, ${particle.opacity})`;
ctx.fill();
}
init();
})();
</script>
</body>
</html>
We are wrapping in an IIFE to avoid polluting global scope.
We have:
- The canvas is set to full screen (via resize on start and on window resize).
- Click handler creates a new shell from bottom center to the click point.
- Shells move towards target and explode when close.
- Particles are created and move with random directions, gravity, friction, and fade.
Let's write it.
Note: This is a self-contained index.html.
We'll write this to the current directory as index.html.
We are not using any external resources.
We are in the current directory: /tmp/ws
We'll use the `write` tool to create index.html.
The content is long, so we must write it as a string.
Let me generate the string for the entire HTML file.
I'll create a fireworks display with gravity-affected particles in a self-contained HTML file. Here's the implementation: json { "name": "write", "arguments": { "path": "index.html", "content": "<!DOCTYPE html>\n<html>\n<head>\n <title>Fireworks Display</title>\n <style>\n body, html {\n margin: 0;\n padding: 0;\n overflow: hidden;\n background: #000;\n width: 100%;\n height: 100%;\n }\n canvas {\n display: block;\n background: #000;\n cursor: pointer;\n }\n </style>\n</head>\n<body>\n <canvas id=\"fireworksCanvas\"></canvas>\n\n <script>\n (function() {\n const canvas = document.getElementById('fireworksCanvas');\n const ctx = canvas.getContext('2d');\n \n // Set canvas to full window size\n function resizeCanvas() {\n canvas.width = window.innerWidth;\n canvas.height = window.innerHeight;\n }\n window.addEventListener('resize', resizeCanvas);\n resizeCanvas();\n \n // Firework system\n const fireworks = [];\n const particles = [];\n \n class Firework {\n constructor(startX, startY, targetX, targetY) {\n this.x = startX;\n this.y = startY;\n this.startY = startY;\n this.targetX = targetX;\n this.targetY = targetY;\n this.velocity = {\n x: (targetX - startX) * 0.007,\n y: (targetY - startY) * 0.007\n };\n this.gravity = 0.05;\n this.friction = 0.99;\n this.hue = Math.random() * 360;\n this.radius = 3;\n this.brightness = 1;\n this.alpha = 1;\n this.shimmer = Math.random() * 0.1;\n }\n \n update() {\n this.velocity.x *= this.friction;\n this.velocity.y *= this.friction;\n this.velocity.y += this.gravity;\n \n this.x += this.velocity.x;\n this.y += this.velocity.y;\n \n // Fade as it approaches target\n const distance = Math.hypot(\n this.targetX - this.x,\n this.targetY - this.y\n );\n this.alpha = Math.min(distance / 50, 1);\n \n // Oscillating brightness effect\n this.brightness = 0.8 + Math.sin(Date.now() * this.shimmer) * 0.2;\n \n // Explode when close to target or moving up but velocity changing\n const velocityUp = this.velocity.y < 0;\n const nearTarget = distance < 10;\n const maxHeight = this.y > this.startY;\n \n if (nearTarget || (velocityUp && maxHeight)) {\n this.explode();\n return false;\n }\n \n return true;\n }\n \n draw() {\n ctx.beginPath();\n ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${this.hue}, 100%, ${this.brightness * 50}%, ${this.alpha})`;\n ctx.fill();\n }\n \n explode() {\n const particleCount = Math.floor(Math.random() * 50) + 50;\n \n for (let i = 0; i < particleCount; i++) {\n const angle = Math.random() * Math.PI * 2;\n const speed = Math.random() * 2 + 1;\n const vx = Math.cos(angle) * speed;\n const vy = Math.sin(angle) * speed;\n \n particles.push(new Particle(\n this.x,\n this.y,\n this.hue,\n vx, \n vy\n ));\n }\n }\n }\n \n class Particle {\n constructor(x, y, hue, vx, vy) {\n this.x = x;\n this.y = y;\n this.velocity = {\n x: vx + (Math.random() - 0.5) * 2,\n y: vy + (Math.random() - 0.5) * 2\n };\n this.gravity = 0.1;\n this.friction = 0.95;\n this.radius = Math.random() * 2 + 1;\n this.hue = (hue + Math.random() * 30 - 15) % 360;\n this.saturation = 80 + Math.random() * 20;\n this.brightness = 70 + Math.random() * 30;\n this.alpha = 1;\n this.decay = Math.random() * 0.01 + 0.005;\n this.shimmer = Math.random() * 0.1;\n }\n \n update() {\n this.velocity.x *= this.friction;\n this.velocity.y *= this.friction;\n this.velocity.y += this.gravity;\n \n this.x += this.velocity.x;\n this.y += this.velocity.y;\n \n this.alpha -= this.decay;\n this.saturation = Math.max(this.saturation - 0.2, 0);\n \n // Oscillating brightness effect\n this.brightness = 60 + Math.sin(Date.now() * this.shimmer) * 40;\n \n return this.alpha > 0 && \n this.y < canvas.height &&\n this.radius > 0.1;\n }\n \n draw() {\n ctx.beginPath();\n ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);\n ctx.fillStyle = `hsla(${this.hue}, ${this.saturation}%, ${this.brightness}%, ${this.alpha})`;\n ctx.fill();\n }\n }\n \n // Launch fireworks with mouse clicks\n canvas.addEventListener('click', e => {\n fireworks.push(new Firework(\n canvas.width / 2,\n canvas.height,\n e.clientX,\n e.clientY\n ));\n });\n \n // Animation loop\n function animate() {\n // Fade out previous frame\n ctx.fillStyle = 'rgba(0, 0, 0, 0.2)';\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n \n // Update and draw fireworks\n for (let i = fireworks.length - 1; i >= 0; i--) {\n if (fireworks[i].update()) {\n fireworks[i].draw();\n } else {\n fireworks.splice(i, 1);\n }\n }\n \n // Update and draw particles\n for (let i = particles.length - 1; i >= 0; i--) {\n if (particles[i].update()) {\n particles[i].draw();\n } else {\n particles.splice(i, 1);\n }\n }\n \n requestAnimationFrame(animate);\n }\n \n // Start animation\n animate();\n \n // Create welcome fireworks\n setTimeout(() => {\n for (let i = 0; i < 3; i++) {\n setTimeout(() => {\n fireworks.push(new Firework(\n canvas.width / 2,\n canvas.height,\n Math.random() * canvas.width,\n canvas.height * (0.3 + Math.random() * 0.3)\n ));\n }, i * 500);\n }\n }, 1000);\n })();\n </script>\n</body>\n</html>" } }
TAKE IT TO CODE
This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.