oneshotlm
Compare all models on this prompt →

Aquarium breach · gpt-4.1-mini

Create a large glass aquarium whose side panel develops a visible crack and then bursts. The simulation must include: - Water escaping through the opening with flow strength based on water depth and decreasing as the tank drains - A curved water jet affected by gravity - A spreading puddle that collides with the room boundaries - Fish, rocks, plants, and a floating toy reacting differently according to density, buoyancy, drag, and current - Objects transitioning correctly from underwater motion to airborne motion and then to floor collisions - Fish attempting to swim against the current before being swept through the breach - Glass fragments with angular velocity, collisions, and water resistance - A visible waterline that lowers continuously rather than disappearing all at once Let the user drag the crack vertically before triggering the failure. A lower crack should initially produce a stronger jet than a higher crack. Give me 1 html file

⚠ no runnable output

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
user

Create a large glass aquarium whose side panel develops a visible crack and then bursts. The simulation must include: - Water escaping through the opening with flow strength based on water depth and decreasing as the tank drains - A curved water jet affected by gravity - A spreading puddle that collides with the room boundaries - Fish, rocks, plants, and a floating toy reacting differently according to density, buoyancy, drag, and current - Objects transitioning correctly from underwater motion to airborne motion and then to floor collisions - Fish attempting to swim against the current before being swept through the breach - Glass fragments with angular velocity, collisions, and water resistance - A visible waterline that lowers continuously rather than disappearing all at once Let the user drag the crack vertically before triggering the failure. A lower crack should initially produce a stronger jet than a higher crack. Give me 1 html file

assistant

```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Glass Aquarium Crack and Burst Simulation</title> <style> html, body { margin: 0; padding: 0; overflow: hidden; background: #181f2a; user-select: none; -webkit-user-select:none; -moz-user-select:none; -ms-user-select:none; height: 100vh; width: 100vw; } canvas { display: block; background: linear-gradient(to bottom, #004466 0%, #002233 90%); } #instructions { position: absolute; top: 12px; left: 50%; transform: translateX(-50%); font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #eef7ffcc; font-weight: 600; font-size: 14px; background: #00000088; padding: 6px 12px; border-radius: 6px; z-index: 10; pointer-events: none; max-width: 90vw; text-align: center; } </style> </head> <body> <div id="instructions"> Drag the crack vertically on the left side panel to position it. When ready, click or press Space/Enter to burst the glass.<br/> Watch water escape, fish struggle, objects float and fly, and glass fragments fly with physics! </div> <canvas id="c"></canvas> <script> (() => { // Constants for simulation const canvas = document.getElementById('c'); const ctx = canvas.getContext('2d'); let W, H; // Aquarium dimensions in px relative to canvas // Aquarium glass is a large rectangle, left side has crack // We'll place the aquarium roughly centered horizontally and vertically. // Coordinates: // Aquarium inside rectangle: left 150, top 100 width 600 height 400 // Water fills inside, initial waterline is at bottom, cracks on left side vertically draggable. // Bottom Y is 100+400=500 // Physical constants const GRAVITY = 0.5; // px/frame^2 downward for airborne and water jet const WATER_DENSITY = 1; const AIR_DENSITY = 0.0012; const WATER_VISCOSITY = 0.02; // drag in water const AIR_DRAG = 0.005; const GLASS_FRICTION = 0.12; const GLASS_RESTITUTION = 0.6; const OBJECT_RESTITUTION = 0.4; const MAX_WATER_DEPTH = 400; // Aquarium positioning and sizing const tank = { x: 150, y: 100, w: 600, h: 400, glassThickness: 16, }; // Crack properties const crack = { x: tank.x - tank.glassThickness + 3, minY: tank.y + 40, maxY: tank.y + tank.h - 40, y: tank.y + tank.h / 2, dragging: false, }; // Simulation state let waterHeight = tank.h; let burstInitiated = false; let burstTime = 0; // Arrays for moving entities const particles = []; const fishGroup = []; const glassFrags = []; const waterJets = []; const puddleDrops = []; // Objects inside aquarium: fish, rocks, plants, toy // We'll create small representation for each, physics attributes: // density, buoyancy factor, drag coefficient. // Fish try to swim upstream against current generated by the leak // Utility functions function lerp(a, b, t) { return a + (b - a)*t; } function clamp(x, a, b) { return Math.min(b, Math.max(a, x)); } function randRange(min,max) { return Math.random()*(max-min)+min; } function distSq(x1,y1,x2,y2){return (x1-x2)**2+(y1-y2)**2;} function dist(x1,y1,x2,y2){return Math.sqrt(distSq(x1,y1,x2,y2)); } // Vector utility class Vec { constructor(x,y) {this.x = x; this.y = y;} add(v){this.x+=v.x; this.y+=v.y;return this;} sub(v){this.x-=v.x; this.y-=v.y;return this;} mul(s){this.x*=s; this.y*=s;return this;} len() {return Math.sqrt(this.x*this.x+this.y*this.y);} norm() { let l = this.len(); if(l>0) this.mul(1/l); return this; } copy(){return new Vec(this.x,this.y);} } // Simulation objects // Base class for physics objects: position, velocity, acceleration class PhysObj { constructor(x,y,w,h,density,buoyancy,drag) { this.pos = new Vec(x,y); this.prevPos = this.pos.copy(); this.vel = new Vec(0,0); this.acc = new Vec(0,0); this.w = w; this.h = h; this.density = density; // relative density (water=1) this.buoyancy = buoyancy; // [0..1], fraction of force opposing gravity underwater this.drag = drag; // drag coeff in water this.inWater = true; // underwater state for drag & buoyancy this.onFloor = false; this.color = '#fff'; this.angle = 0; this.angularVel = 0; this.angularDrag = 0.1; this.mass = this.w*this.h*this.density*0.01; } applyForce(f) { this.acc.x += f.x / this.mass; this.acc.y += f.y / this.mass; } updatePhysics() { // Apply buoyancy and drag if in water if(this.inWater) { // gravity countered partly by buoyancy: buoyancy = percent of gravity force cancelled out let gravityForce = this.mass * GRAVITY * (1 - this.buoyancy); this.applyForce(new Vec(0, gravityForce)); // drag: -velocity * drag coeff this.applyForce(new Vec(-this.vel.x * this.drag * this.mass, -this.vel.y * this.drag * this.mass)); } else { // air drag much less this.applyForce(new Vec(-this.vel.x * AIR_DRAG * this.mass, -this.vel.y * AIR_DRAG * this.mass)); // plus gravity fully applied this.applyForce(new Vec(0, this.mass * GRAVITY)); } // Euler integration this.vel.x += this.acc.x; this.vel.y += this.acc.y; this.pos.x += this.vel.x; this.pos.y += this.vel.y; // Angular physics if any if(this.angularVel !== 0) { this.angularVel *= (1 - this.angularDrag); this.angle += this.angularVel; } // Reset acceleration for next frame this.acc.x = 0; this.acc.y = 0; } isUnderwater() { return (this.pos.y + this.h/2) < (tank.y + waterHeight); } clampToFloor() { // Floor y at tank.y + tank.h + puddle max spread height = H-20 let floorY = tank.y + tank.h + 20; if(this.pos.y + this.h/2 > floorY) { this.pos.y = floorY - this.h/2; this.vel.y *= -OBJECT_RESTITUTION; this.onFloor = true; // Dampen horizontal velocity due to floor friction this.vel.x *= 0.7; // Dampen angular velocity this.angularVel *= 0.7; if(Math.abs(this.vel.y) < 0.1) this.vel.y = 0; } else { this.onFloor = false; } } } // Fish class class Fish extends PhysObj { constructor(x,y) { let w = randRange(20, 30); let h = w*0.6; super(x,y,w,h,0.85,0.9,0.06); this.color = '#ffa05c'; this.direction = Math.random() < 0.5 ? 1 : -1; this.swimForce = 0.16; this.phase = Math.random()*Math.PI*2; this.phaseSpeed = randRange(0.07, 0.12); } update() { this.inWater = this.isUnderwater(); // Swim against current (water flow direction is down below crack on left) if(this.inWater) { // Apply a horizontal force opposing water current (which flows towards crack) let currentVelX = waterCurrentVelXAt(this.pos.y); let swimDir = -Math.sign(currentVelX) || this.direction; this.applyForce(new Vec(swimDir * this.swimForce, 0)); // Slow rotation / oscillate direction for liveliness this.phase += this.phaseSpeed; this.vel.y += Math.sin(this.phase)*0.06; } this.updatePhysics(); this.clampToFloor(); // Keep fish inside aquarium walls horizontally underwater if(this.inWater) { let leftLimit = tank.x + tank.glassThickness + 15; let rightLimit = tank.x + tank.w - 15; if(this.pos.x < leftLimit) { this.pos.x = leftLimit; this.vel.x = Math.abs(this.vel.x) * 0.9; } else if(this.pos.x > rightLimit) { this.pos.x = rightLimit; this.vel.x = -Math.abs(this.vel.x) * 0.9; } // Keep inside vertical waterline + tank bottom let waterSurfaceY = tank.y + waterHeight; if(this.pos.y - this.h/2 < tank.y + 10) { this.pos.y = tank.y + 10 + this.h/2; this.vel.y *= -0.3; } if(this.pos.y + this.h/2 > waterSurfaceY) { // Transition to airborne if past water surface this.inWater = false; } } } draw(ctx) { ctx.save(); ctx.translate(this.pos.x, this.pos.y); ctx.rotate(this.angle); // Body shape: ellipse + tail fin ctx.fillStyle = this.color; ctx.beginPath(); ctx.ellipse(0, 0, this.w/2, this.h/2, 0, 0, Math.PI*2); ctx.fill(); // Tail fin ctx.beginPath(); let tailDir = this.direction; ctx.moveTo(-this.w/2*tailDir, 0); ctx.lineTo(-this.w*tailDir, -this.h/2); ctx.lineTo(-this.w*tailDir, this.h/2); ctx.closePath(); ctx.fill(); // Eye ctx.fillStyle = '#222'; ctx.beginPath(); ctx.ellipse(this.w/6, -this.h/6, this.h/10, this.h/10, 0, 0, Math.PI * 2); ctx.fill(); ctx.restore(); } } // Rock class - dense, sink fast class Rock extends PhysObj { constructor(x,y) { const size = randRange(18, 35); super(x,y,size,size,2.7,0.15,0.05); this.color = '#665544'; this.shapeOffset = randRange(0,Math.PI*2); } update() { this.inWater = this.isUnderwater(); this.updatePhysics(); this.clampToFloor(); // Horizontal confinement inside tank let leftLimit = tank.x + tank.glassThickness + 10; let rightLimit = tank.x + tank.w - 10; if(this.inWater) { if(this.pos.x < leftLimit) { this.pos.x = leftLimit; this.vel.x *= -0.4; } else if(this.pos.x > rightLimit) { this.pos.x = rightLimit; this.vel.x *= -0.4; } } else { // On floor outside water (after flowing out) let floorXmin = 10; let floorXmax = W-10; if(this.pos.x < floorXmin) { this.pos.x = floorXmin; this.vel.x *= -0.5; } else if(this.pos.x > floorXmax) { this.pos.x = floorXmax; this.vel.x *= -0.5; } } } draw(ctx) { ctx.save(); ctx.translate(this.pos.x, this.pos.y); ctx.rotate(Math.sin((performance.now()*0.001)+this.shapeOffset)*0.05); ctx.fillStyle = this.color; ctx.beginPath(); // rock shape irregular circle let sides = 7; let angleStep = (Math.PI*2)/sides; ctx.moveTo(this.w/2 * Math.cos(0), this.h/2 * Math.sin(0)); for(let i=1; i<=sides; i++) { let r = this.w/2 * (0.8 + 0.4 * Math.sin(i*3 + this.shapeOffset)); ctx.lineTo(r * Math.cos(i*angleStep), r * Math.sin(i*angleStep)); } ctx.closePath(); ctx.fill(); ctx.restore(); } } // Plant class - light, mostly anchored, sways a bit with water current (stationary but animated) class Plant { constructor(x,y) { this.pos = new Vec(x,y); this.h = randRange(40, 60); this.color = '#17551e'; this.swayPhase = Math.random()*Math.PI*2; } update() { this.swayPhase += 0.04; } draw(ctx) { ctx.save(); ctx.translate(this.pos.x, this.pos.y); let sway = Math.sin(this.swayPhase)*4; ctx.strokeStyle = this.color; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(0, 0); ctx.bezierCurveTo(sway*0.3, -this.h*0.25, sway*0.8, -this.h*0.75, sway, -this.h); ctx.stroke(); ctx.restore(); } } // Floating toy - light, buoyant, reacts strongly to current and air, floating on surface initially class FloatingToy extends PhysObj { constructor(x,y) { super(x,y,35,25,0.5,1.0,0.07); this.color = '#e0aaff'; this.phase = Math.random()*Math.PI*2; this.phaseSpeed = 0.06; this.angle = 0; this.angularVel = 0; this.angularDrag = 0.15; } update() { this.inWater = this.isUnderwater(); // Floating toy bobs with waves & current if(this.inWater) { this.phase += this.phaseSpeed; // Slight sinusoidal force for buoyant bobbing this.applyForce(new Vec(Math.cos(this.phase)*0.02, Math.sin(this.phase)*0.06)); } this.updatePhysics(); this.clampToFloor(); // Keep toy inside aquarium horizontal bounds underwater if(this.inWater) { if(this.pos.x < tank.x + tank.glassThickness + 18) { this.pos.x = tank.x + tank.glassThickness + 18; this.vel.x = Math.abs(this.vel.x); } else if(this.pos.x > tank.x + tank.w - 18) { this.pos.x = tank.x + tank.w - 18; this.vel.x = -Math.abs(this.vel.x); } } } draw(ctx) { ctx.save(); ctx.translate(this.pos.x, this.pos.y); ctx.rotate(this.angle); ctx.fillStyle = this.color; ctx.beginPath(); // simple rounded rectangle with circular eyes for toy duck shape ctx.ellipse(0, 0, this.w/2, this.h/2, 0, 0, Math.PI*2); ctx.fill(); // eyes ctx.fillStyle = '#400040'; ctx.beginPath(); ctx.ellipse(this.w*0.15, -this.h*0.15, this.h*0.1, this.h*0.1, 0, 0, Math.PI*2); ctx.fill(); ctx.beginPath(); ctx.ellipse(this.w*0.15, this.h*0.05, this.h*0.1, this.h*0.1, 0, 0, Math.PI*2); ctx.fill(); ctx.restore(); } } // Glass fragments from burst - polygon fragments with velocity, angular velocity, drag and collisions class GlassFragment { constructor(x,y,w,h) { this.pos = new Vec(x,y); this.vel = new Vec(randRange(-4,4), randRange(-7,-2)); this.w = w; this.h = h; this.angle = randRange(0, Math.PI*2); this.angularVel = randRange(-0.15,0.15); this.mass = w*h*0.02; this.color = 'rgba(180,220,255,0.9)'; this.inAir = true; } update() { // Aerodynamic drag this.vel.x *= 0.95; this.vel.y += GRAVITY*0.6; this.pos.x += this.vel.x; this.pos.y += this.vel.y; this.angle += this.angularVel; // Bounce on floor let floorY = tank.y + tank.h + 20; if(this.pos.y + this.h/2 > floorY) { this.pos.y = floorY - this.h/2; this.vel.y *= -GLASS_RESTITUTION; // Angular velocity dampens on bounce this.angularVel *= 0.5; // Some friction on horizontal velocity this.vel.x *= 0.6; if(Math.abs(this.vel.y) < 0.3) this.vel.y = 0; } // Floor horizontal limits if(this.pos.x < 0) { this.pos.x = 0; this.vel.x = Math.abs(this.vel.x)*0.5; this.angularVel *= -0.7; } if(this.pos.x > W) { this.pos.x = W; this.vel.x = -Math.abs(this.vel.x)*0.5; this.angularVel *= -0.7; } } draw(ctx) { ctx.save(); ctx.translate(this.pos.x, this.pos.y); ctx.rotate(this.angle); ctx.fillStyle = this.color; ctx.strokeStyle = 'rgba(150,190,230,0.7)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.rect(-this.w/2, -this.h/2, this.w, this.h); ctx.fill(); ctx.stroke(); ctx.restore(); } } // Water jet particle - thrown water droplet leaving the crack, arcing down class WaterJetParticle { constructor(x,y,vx,vy) { this.pos = new Vec(x,y); this.vel = new Vec(vx, vy); this.radius = 3; this.alpha = 1; } update() { this.vel.y += GRAVITY*0.2; this.pos.x += this.vel.x; this.pos.y += this.vel.y; this.alpha -= 0.018; if(this.alpha < 0) this.alpha = 0; } draw(ctx) { ctx.save(); ctx.fillStyle = `rgba(90, 140, 230, ${this.alpha * 0.8})`; ctx.beginPath(); ctx.ellipse(this.pos.x, this.pos.y, this.radius*1.1, this.radius*0.6, 0, 0, Math.PI*2); ctx.fill(); ctx.restore(); } isDead() { return this.alpha <= 0 || this.pos.y > H; } } // Puddle water drop - small water blob spreading on floor, collides with floor edges class PuddleDrop { constructor(x,y) { this.pos = new Vec(x,y); this.vel = new Vec(randRange(-0.6,0.6), randRange(-0.1,0.1)); this.radius = randRange(12, 20); this.color = 'rgba(90, 140, 230, 0.4)'; this.life = 200; } update() { // Spread with a slight damped horizontal velocity, no vertical velocity (floor) this.vel.x *= 0.94; this.pos.x += this.vel.x; // Stay on puddle floor area (center tank below tank bottom) let floorY = tank.y + tank.h + 18; this.pos.y = floorY; // fixed y on floor // Collision with screen edges (room boundaries) if(this.pos.x - this.radius < 0) { this.pos.x = this.radius; this.vel.x *= -0.45; } else if(this.pos.x + this.radius > W) { this.pos.x = W - this.radius; this.vel.x *= -0.45; } this.life--; } draw(ctx) { ctx.save(); ctx.fillStyle = this.color; ctx.beginPath(); ctx.ellipse(this.pos.x, this.pos.y, this.radius, this.radius*0.3, 0, 0, Math.PI*2); ctx.fill(); ctx.restore(); } isDead() { return this.life <= 0; } } // Generate multiple fish, rocks, plants, one toy function populateTank() { fishGroup.length = 0; glassFrags.length = 0; waterJets.length = 0; puddleDrops.length = 0; // Fish for(let i=0; i<12; i++) { let fx = randRange(tank.x + tank.glassThickness + 30, tank.x + tank.w - 40); let fy = randRange(tank.y + 30, tank.y + tank.h - 30); fishGroup.push(new Fish(fx, fy)); } // Rocks for(let i=0; i<6; i++) { let rx = randRange(tank.x + tank.glassThickness + 30, tank.x + tank.w - 60); let ry = randRange(tank.y + tank.h - 80, tank.y + tank.h - 30); fishGroup.push(new Rock(rx, ry)); } // Plants plants.length = 0; for(let i=0; i<8; i++) { let px = randRange(tank.x + tank.glassThickness + 20, tank.x + tank.w - 20); let py = tank.y + tank.h - 12; plants.push(new Plant(px, py)); } // Toy toy = new FloatingToy(tank.x + tank.w / 2, tank.y + tank.h - 80); } // Array for plants and toy outside fishGroup for drawing order const plants = []; let toy; // Water current velocity at vertical position y inside aquarium // When burst: create horizontal water current near crack flowing left->right and downward // When no burst: no current function waterCurrentVelXAt(y) { if(!burstInitiated) return 0; // Stronger near crack y, decays upwards and downwards // Flow strength proportional to current water height and inverse y distance from crack let d = Math.abs(y - crack.y); // max range 150 px vertical (+/-75) if(d > 75) return 0; let strength = (waterHeight / MAX_WATER_DEPTH)*3; // The closer to crack.y the stronger strength *= (1 - (d/75))*2; return strength; } // Draw water surface as a colored blue polygon with waterline lowering continuously function drawWaterSurface(ctx) { ctx.save(); // Water rectangle inside aquarium, but clipped by current water height let surfaceY = tank.y + (tank.h - waterHeight); // Use multiple shades and transparency for nice water look const gradient = ctx.createLinearGradient(tank.x, surfaceY, tank.x, tank.y + tank.h); gradient.addColorStop(0, 'rgba(70,120,180,0.7)'); gradient.addColorStop(0.4, 'rgba(30,70,140,0.9)'); gradient.addColorStop(1, 'rgba(10,20,80,0.95)'); ctx.fillStyle = gradient; ctx.beginPath(); ctx.moveTo(tank.x + tank.glassThickness, surfaceY); ctx.lineTo(tank.x + tank.w - tank.glassThickness, surfaceY); ctx.lineTo(tank.x + tank.w - tank.glassThickness, tank.y + tank.h); ctx.lineTo(tank.x + tank.glassThickness, tank.y + tank.h); ctx.closePath(); ctx.fill(); // Waterline highlight ctx.strokeStyle = 'rgba(180,220,255,0.9)'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(tank.x + tank.glassThickness, surfaceY); ctx.lineTo(tank.x + tank.w - tank.glassThickness, surfaceY); ctx.stroke(); ctx.restore(); } // Draw tank glass with crack and cracks highlight function drawTank(ctx) { // Glass panels ctx.save(); // Outer glass walls (thick rectangles) ctx.fillStyle = 'rgba(180,220,250,0.2)'; // Left side thick panel ctx.fillRect(tank.x - tank.glassThickness, tank.y, tank.glassThickness, tank.h); // Bottom ctx.fillRect(tank.x, tank.y + tank.h, tank.w, tank.glassThickness); // Right side ctx.fillRect(tank.x + tank.w, tank.y, tank.glassThickness, tank.h); // Top ctx.fillRect(tank.x, tank.y - tank.glassThickness, tank.w, tank.glassThickness); // Glass edges: sharp lines ctx.strokeStyle = 'rgba(190, 230, 255, 0.7)'; ctx.lineWidth = 2; ctx.strokeRect(tank.x - tank.glassThickness, tank.y - tank.glassThickness, tank.w + 2*tank.glassThickness, tank.h + 2*tank.glassThickness); // Crack line on left panel, vertical draggable // Draw crack: jagged line with random noise in vertical range ~70px centered on crack.y let crackTop = clamp(crack.y - 35, tank.y + 10, tank.y + tank.h - 45); let crackBot = clamp(crack.y + 35, tank.y + 45, tank.y + tank.h - 10); const steps = 16; ctx.strokeStyle = burstInitiated ? 'rgba(255,90,90,0.8)' : 'rgba(255,165,165,0.8)'; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); for(let i=0; i<=steps; i++) { let t = i/steps; let y = lerp(crackTop, crackBot, t); // noise left-right jagged offset - connected zigzag let xOff = (i%2)*6 - 3; if(i === 0) ctx.moveTo(crack.x + xOff, y); else ctx.lineTo(crack.x + xOff, y); } ctx.stroke(); ctx.restore(); } // Water flow jet generation and simulation // Each frame, generate jets proportional to water height and crack vertical position function spawnWaterJets() { if(!burstInitiated) return; // The lower the crack, the stronger the jet because more water pressure // Pressure proportional to vertical water depth above crack.y let waterAboveCrack = clamp(waterHeight - (crack.y - tank.y), 0, MAX_WATER_DEPTH); if(waterAboveCrack <= 0) return; let pressure = (waterAboveCrack / MAX_WATER_DEPTH); if(Math.random() > pressure*0.27) return; // Emit several particles with velocities (flow strength) let baseX = crack.x + 4; let baseY = crack.y; let speedY = -12 * pressure; // initial upward speed, negative for upwards let speedX = 10 * pressure; // Randomize angular spread curve on jet: the jet curves downward by gravity // Base jet particle emitted with velocity + random variation let p = new WaterJetParticle(baseX, baseY, speedX + randRange(-1.2,1.2), speedY + randRange(-1,1)); waterJets.push(p); } // Update puddle drops from water surface hits on floor function updatePuddleDrops() { puddleDrops.forEach((d,i) => { d.update(); if(d.isDead()) puddleDrops.splice(i,1); }); } // Update water jets and create puddle drops on floor impact function updateWaterJets() { for(let i = waterJets.length - 1; i >= 0; i--) { const p = waterJets[i]; p.update(); if(p.isDead()) { waterJets.splice(i,1); // When jet dies near puddle floor area, spawn puddle drop if(p.pos.y > tank.y + tank.h + 15) { puddleDrops.push(new PuddleDrop(p.pos.x, p.pos.y)); } } } } // Burst the glass: setup glass fragments and start water draining function burstGlass() { if(burstInitiated) return; burstInitiated = true; burstTime = performance.now(); // Create glass fragments on left panel, break in pieces vertically around crack.y glassFrags.length = 0; let fragCount = 12; let fragW = tank.glassThickness - 4; let fragH = (tank.h / fragCount) * 0.9; let xFrag = tank.x - tank.glassThickness + 6; for(let i=0; i<fragCount; i++) { let yFrag = tank.y + (i * (tank.h / fragCount)) + fragH/2; // Only fragments near crack area have velocity let distToCrack = Math.abs(yFrag - crack.y); let baseVx = 0; let baseVy = 0; if(distToCrack < 80) { baseVx = randRange(4, 9); baseVy = randRange(-8, -3); } let frag = new GlassFragment(xFrag, yFrag, fragW, fragH); frag.vel.x = baseVx; frag.vel.y = baseVy; frag.angularVel = randRange(-0.3, 0.3); glassFrags.push(frag); } } // Update glass fragments function updateGlass() { glassFrags.forEach(f => f.update()); } // Update all fish, rocks, toy, plants, waterline water height draining function updateObjects() { // Water draining down over time when burst if(burstInitiated) { // Drain rate affected by crack height (lower crack drains faster) let crackDepth = clamp(tank.y + tank.h - crack.y, 0, tank.h); // Drain faster if crack closer to bottom, slower if near top let drainSpeed = 0.4 + (crackDepth / tank.h) * 0.6; waterHeight -= drainSpeed; if(waterHeight < 0) waterHeight = 0; } // Update fish and rocks fishGroup.forEach(f => f.update()); // Update plants (no physics, just sway) plants.forEach(p => p.update()); toy.update(); // Update glass fragments and water jets, puddles updateGlass(); updateWaterJets(); updatePuddleDrops(); // Spawn water jets near crack spawnWaterJets(); } // Handle waterline effect: water inside tank from bottom to waterHeight (stable until burst) // Animate waterline lowering smoothly in drawWaterSurface() // Draw whole scene function draw() { ctx.clearRect(0,0,W,H); // Draw floor area (room bottom, puddle) ctx.save(); // Floor ground extends full width ctx.fillStyle = '#241f1a'; ctx.fillRect(0, tank.y + tank.h + 20, W, H - (tank.y + tank.h + 20)); ctx.restore(); // Draw puddle water drops on floor puddleDrops.forEach(d => d.draw(ctx)); // Draw aquarium outer glass drawTank(ctx); // Draw water inside aquarium with dynamic waterline drawWaterSurface(ctx); // Draw water jets above crack and falling waterJets.forEach(wp => wp.draw(ctx)); // Draw underwater plants below waterline plants.forEach(p => p.draw(ctx)); // Draw rocks and fish and toy underwater and in air properly: // Draw fish underwater first, then toy, then things above water // Sort fish + rocks by pos.y to simulate layering let sortedAnimals = fishGroup.slice().sort((a,b) => a.pos.y - b.pos.y); sortedAnimals.forEach(f => f.draw(ctx)); toy.draw(ctx); // Draw glass fragments on top glassFrags.forEach(f => f.draw(ctx)); // Draw crack handle if not burst if(!burstInitiated) { ctx.save(); ctx.fillStyle = 'rgba(255,60,60,0.6)'; ctx.beginPath(); ctx.ellipse(crack.x, crack.y, 7, 20, 0, 0, Math.PI*2); ctx.fill(); ctx.strokeStyle = 'rgba(255, 160, 160, 0.9)'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(crack.x, crack.y - 20); ctx.lineTo(crack.x, crack.y + 20); ctx.stroke(); ctx.restore(); } // Draw reflections and highlights drawTankHighlights(); } // Add some subtle reflections on glass function drawTankHighlights() { ctx.save(); ctx.strokeStyle = "rgba(255,255,255,0.15)"; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(tank.x - tank.glassThickness + 2, tank.y); ctx.lineTo(tank.x - tank.glassThickness + 2, tank.y + tank.h); ctx.stroke(); // Top reflection curve ctx.beginPath(); let margin = 20; ctx.moveTo(tank.x + margin, tank.y - tank.glassThickness + 5); ctx.quadraticCurveTo(tank.x + tank.w/2, tank.y - tank.glassThickness - 15, tank.x + tank.w - margin, tank.y - tank.glassThickness + 5); ctx.stroke(); ctx.restore(); } // Handle crack dragging mouse/touch input function pointerDown(e) { e.preventDefault(); let px, py; if(e.touches) { px = e.touches[0].clientX; py = e.touches[0].clientY; } else { px = e.clientX; py = e.clientY; } let distToCrack = Math.abs(px - crack.x) + Math.abs(py - crack.y); if(distToCrack < 30 && !burstInitiated) { crack.dragging = true; } } function pointerMove(e) { if(!crack.dragging) return; e.preventDefault(); let py; if(e.touches) { py = e.touches[0].clientY; } else { py = e.clientY; } // Clamp crack y within tank limits crack.y = clamp(py, crack.minY, crack.maxY); } function pointerUp(e) { if(crack.dragging && !burstInitiated) { crack.dragging = false; } } // Keyboard to trigger burst: Space or Enter function keyDown(e) { if((e.code === 'Space' || e.code === 'Enter') && !burstInitiated) { burstGlass(); } } // On click also burst glass if not dragging crack function clickHandler(e) { if(!burstInitiated && !crack.dragging) { burstGlass(); } } // Resize canvas & reset function resize() { W = window.innerWidth; H = window.innerHeight; canvas.width = W; canvas.height = H; } // Main loop function loop() { updateObjects(); draw(); requestAnimationFrame(loop); } // Initialization window.addEventListener('resize', () => { resize(); populateTank(); }); canvas.addEventListener('mousedown', pointerDown); canvas.addEventListener('touchstart', pointerDown, {passive:false}); canvas.addEventListener('mousemove', pointerMove); canvas.addEventListener('touchmove', pointerMove, {passive:false}); canvas.addEventListener('mouseup', pointerUp); canvas.addEventListener('touchend', pointerUp); window.addEventListener('keydown', keyDown); window.addEventListener('click', clickHandler); resize(); populateTank(); loop(); })(); </script> </body> </html> ```

TAKE IT TO CODE

This run produced no artifact — reuse the prompt with a different model or your own pi-agent run.

RUN

modelgpt-4.1-mini
vendorOpenAI
harnesspi-agent
generated2026-08-02

METRICS

runtime127.6s
tokens in1.3k
tokens out8.5k
cached0
cost / run$0.0141