Aquarium breach · ling-2.6-flash
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
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 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
```html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Aquarium Burst Simulation</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; display: flex; justify-content: center; align-items: center; height: 100vh; font-family: Arial, sans-serif; } canvas { display: block; cursor: crosshair; } #ui { position: absolute; top: 10px; left: 10px; color: white; font-size: 12px; pointer-events: none; z-index: 10; } </style> </head> <body> <canvas id="canvas"></canvas> <div id="ui"> Drag crack position vertically • Click to burst<br> Crack: <span id="crackPos">50%</span> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); let W, H; function resize() { W = canvas.width = window.innerWidth; H = canvas.height = window.innerHeight; } resize(); window.addEventListener('resize', resize); // ===== ROOM ===== const room = { x: 0, y: 0, w: W * 0.8, h: H * 0.85 }; room.x = (W - room.w) / 2; room.y = (H - room.h) * 0.05; // ===== AQUARIUM ===== const tank = { x: room.x + room.w * 0.15, y: room.y + room.h * 0.03, w: room.w * 0.35, h: room.h * 0.75, glassThickness: 8 }; // Water level (0 to 1) let waterLevel = 0.95; let targetWaterLevel = 0.95; // Crack state let crack = { x: tank.x + tank.w * 0.5, y: tank.y + tank.h * 0.5, dragging: false, width: 3, height: 20, open: false, burstTime: 0 }; function screenToCrackY(mouseY) { const cy = tank.y + tank.h * 0.1; const ch = tank.h * 0.7; let y = mouseY; if (y < cy) y = cy; if (y > cy + ch) y = cy + ch; return y; } // ===== PARTICLE SYSTEM ===== let particles = []; let debris = []; let waterSplashes = []; let bubbles = []; // ===== FISH ===== let fish = []; const NUM_FISH = 12; const fishColors = [ '#ff6b6b','#4ecdc4','#ffe66d','#ff9f1c','#6bcb77', '#448ee4','#c77dff','#ff9e00','#ff0055','#00d2d3' ]; function createFish() { fish = []; for (let i = 0; i < NUM_FISH; i++) { fish.push({ x: tank.x + tank.w * 0.2 + Math.random() * tank.w * 0.3, y: tank.y + tank.h * (0.2 + Math.random() * 0.5), size: 8 + Math.random() * 10, speed: 15 + Math.random() * 25, color: fishColors[i], angle: Math.random() * Math.PI * 2, swimAngle: (Math.random() - 0.5) * 0.5, alive: true, type: Math.random() > 0.5 ? 'small' : 'large', vx: 0, vy: 0, breathTimer: Math.random() * 100, tailOffset: Math.random() * Math.PI * 2 }); } } createFish(); // ===== ROCKS ===== let rocks = []; function createRocks() { rocks = []; for (let i = 0; i < 8; i++) { const r = 15 + Math.random() * 25; rocks.push({ x: tank.x + tank.glassThickness + r + Math.random() * (tank.w - tank.glassThickness*2 - r*2), y: tank.y + tank.h * (0.3 + Math.random() * 0.55), r: r, color: `rgb(${120+Math.random()*60},${120+Math.random()*60},${130+Math.random()*40})` }); } } createRocks(); // ===== PLANTS ===== let plants = []; function createPlants() { plants = []; for (let i = 0; i < 6; i++) { plants.push({ x: tank.x + tank.glassThickness + 10 + Math.random() * (tank.w - tank.glassThickness*2 - 20), y: tank.y + tank.h * 0.5 + Math.random() * tank.h * 0.35, segments: 4 + Math.floor(Math.random() * 4), sway: Math.random() * Math.PI * 2, color: `hsl(${100+Math.random()*40},${60+Math.random()*30}%,${20+Math.random()*20}%)`, leaves: [] }); } } createPlants(); // ===== FLOATING TOY ===== let toy = { x: tank.x + tank.w * 0.5, y: tank.y + tank.h * 0.65, w: 24, h: 16, buoyancy: 0.3, drag: 0.98, submerged: true, type: 'duck', // 'duck', 'boat', 'rubber_ducky' angle: 0, vAngle: 0 }; // ===== GLASS DEBRIS ===== function createDebrisPiece(baseX, baseY, width, height) { return { x: baseX, y: baseY, w: width, h: height, vx: (Math.random() - 0.5) * 8, vy: (Math.random() - 0.5) * 8, rotation: Math.random() * Math.PI * 2, rotSpeed: (Math.random() - 0.5) * 0.2, friction: 0.995, gravity: 0.15, waterResistance: 0.98, alpha: 1, type: 'glass' }; } // ===== SPLASHES ===== function spawnSplash(x, y, force) { for (let i = 0; i < force * 3; i++) { const angle = Math.random() * Math.PI * 2; const speed = 2 + Math.random() * force * 1.5; waterSplashes.push({ x, y, vx: Math.cos(angle) * speed * (0.3 + Math.random() * 0.7), vy: Math.sin(angle) * speed * (0.3 + Math.random() * 0.7) - 2, life: 1, decay: 0.015 + Math.random() * 0.02, size: 1 + Math.random() * 3, type: Math.random() > 0.5 ? 'drop' : 'bubble' }); } } // ===== WATER FLOW PARTICLES ===== let waterFlow = []; function spawnWaterFlow(x, y, strength) { for (let i = 0; i < strength * 0.8; i++) { const angle = Math.PI + Math.PI * 0.6 * (Math.random() - 0.5); const speed = 2 + Math.random() * strength * 1.5; waterFlow.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, life: 1, decay: 0.008 + Math.random() * 0.015, size: 1 + Math.random() * 2, trail: [] }); } } // ===== PHYSICS GLOBALS ===== const gravity = 0.18; const waterDrag = 0.96; const airDrag = 0.995; // ===== MAIN LOOP ===== let burstTime = 0; let burstActive = false; let burstStartTime = 0; let crackTargetHeight = tank.h * 0.65; function update(dt) { // Water level smoothing waterLevel += (targetWaterLevel - waterLevel) * 0.02; // Crack follow if (crack.dragging && crack.open) { const targetY = screenToCrackY(pointerY); crack.y += (targetY - crack.y) * 0.2; } if (!burstActive && !crack.dragging) { // Animate crack breathing crack.height = 20 + Math.sin(Date.now() * 0.003) * 3; } // ===== UPDATE PARTICLES ===== for (let i = particles.length - 1; i >= 0; i--) { const p = particles[i]; p.x += p.vx; p.y += p.vy; p.vy += gravity; p.vx *= waterDrag; p.vy *= waterDrag; // Check if moved into air if (p.y < tank.y + tank.h * waterLevel) { // Underwater p.vy *= 0.96; } else { // Air p.vy += gravity * 0.5; p.vx *= airDrag; } // Life p.life -= p.decay; if (p.life <= 0) { particles.splice(i, 1); } } // ===== WATER FLOW ===== if (burstActive) { const depth = waterLevel * tank.h; const breachY = crack.y; const breachHeight = crack.height; const breachTopY = breachY - breachHeight / 2; // Flow strength based on water depth above breach, decreasing as tank drains const effectiveBreach = Math.min(breachTopY, tank.y + tank.h * waterLevel - 5); const pressure = Math.max(0, (effectiveBreach - tank.y) / tank.h); const openingFactor = 1 - (tank.h - tank.h * waterLevel) / (tank.h * 0.5); const strength = Math.max(0.2, pressure * 8 * (1 - openingFactor * 0.7)); for (let y = breachTopY; y < tank.y + tank.h * waterLevel; y += 4) { const dy = y - breachTopY; if (dy < 0 || dy > breachHeight) continue; const localStrength = strength * (1 - dy / breachHeight); if (localStrength > 0.05 && Math.random() < localStrength * 0.6) { const x = crack.x + (Math.random() - 0.5) * crack.width; const angle = Math.PI * 0.9 + (Math.random() - 0.5) * 0.5; // curved forward spawnWaterFlow(x, y, localStrength); } } // Jet curve physics for (let i = waterFlow.length - 1; i >= 0; i--) { const p = waterFlow[i]; p.x += p.vx; p.y += p.vy; p.vy += gravity * 0.5; p.vx *= airDrag; p.vy *= 0.99; // slight viscosity p.life -= p.decay; // Splash when hitting bottom or boundary if (p.y > H - 10 || p.x < 0 || p.x > W) { for (let s = 0; s < 3; s++) { const a = Math.PI + Math.PI * 0.4 * (Math.random() - 0.5); waterSplashes.push({ x: p.x, y: p.y, vx: Math.cos(a) * (1 + Math.random() * 3), vy: Math.sin(a) * (1 + Math.random() * 3) - 2, life: 1, decay: 0.02, size: 1 + Math.random() * 2 }); } waterFlow.splice(i, 1); } if (p.life <= 0) waterFlow.splice(i, 1); } // Water level drop const leakRate = 0.008 + strength * 0.004; targetWaterLevel = Math.max(0.05, targetWaterLevel - leakRate * dt * 0.06); // Spawn debris pieces if (Math.random() < 0.3) { const side = Math.random() > 0.5 ? 1 : -1; const dw = 8 + Math.random() * 15; const dh = 8 + Math.random() * 15; const piece = createDebrisPiece( crack.x + side * crack.width * 0.5 + (Math.random() - 0.5) * crack.width, crack.y - crack.height / 2 + Math.random() * crack.height, dw, dh ); piece.vx += side * (5 + Math.random() * 8); debris.push(piece); } // Spawn glass fragments around breach if (Math.random() < 0.5) { const angle = Math.random() * Math.PI * 2; const speed = 3 + Math.random() * 8; debris.push(createDebrisPiece(crack.x, crack.y, 6 + Math.random() * 8, 6 + Math.random() * 8)); debris[debris.length - 1].vx = Math.cos(angle) * speed; debris[debris.length - 1].vy = Math.sin(angle) * speed - 2; debris[debris.length - 1].rotSpeed = (Math.random() - 0.5) * 0.3; } } // ===== DEBRIS ===== for (let i = debris.length - 1; i >= 0; i--) { const d = debris[i]; const inWater = d.y < tank.y + tank.h * waterLevel; if (inWater) { d.vx *= 0.98; d.vy += gravity * 0.3; d.vy *= 0.985; d.vx += (Math.random() - 0.5) * 0.3; d.rotation += d.rotSpeed * 0.5; } else { d.vy += gravity * 0.3; d.vx *= airDrag; d.rotation += d.rotSpeed; } d.x += d.vx; d.y += d.vy; // Bounds if (d.x < room.x - 50 || d.x > W + 50 || d.y > H + 50) { d.alpha -= 0.02; } if (d.alpha <= 0) { debris.splice(i, 1); } } // ===== SPLASHES ===== for (let i = waterSplashes.length - 1; i >= 0; i--) { const s = waterSplashes[i]; s.x += s.vx; s.y += s.vy; s.vy += gravity * 0.3; s.vx *= airDrag; s.life -= s.decay; if (s.life <= 0 || s.y > H + 20) waterSplashes.splice(i, 1); } // ===== FISH ===== const currentCurrentX = burstActive ? -30 - Math.sin(Date.now() * 0.003) * 10 : -25; for (let i = 0; i < fish.length; i++) { const f = fish[i]; if (!f.alive) continue; const isUnderwater = f.y < tank.y + tank.h * waterLevel; const waterDepth = Math.max(0, (tank.y + tank.h * waterLevel - f.y) / tank.h); // Current pushes fish horizontally over time f.vx += currentCurrentX * 0.1; if (isUnderwater) { // Underwater physics f.vx *= 0.95; f.vy += gravity * 0.4; f.vy *= 0.97; // Bubble release when going up fast if (f.vy < -2 && Math.random() < 0.1) { bubbles.push({ x: f.x + (f.size / 2) * (Math.random() > 0.5 ? 1 : -1), y: f.y - f.size, size: 2 + Math.random() * 3, riseSpeed: 1 + Math.random() * 2, life: 1 }); } // Try to swim against current f.breathTimer++; if (f.breathTimer > 80 + Math.random() * 60) { f.breathTimer = 0; f.swimAngle = -0.3 - Math.random() * 0.4; // attempt against flow } f.angle += f.swimAngle; f.vx += Math.cos(f.angle) * 1.5; f.vy += Math.sin(f.angle) * 1.5; f.swimAngle *= 0.95; } else { // Airborne behavior f.vy += gravity * 0.5; f.vx *= airDrag; f.angle += f.vAngle; f.vAngle *= 0.9; // Hit floor if (f.y + f.size/2 > H - 15) { f.y = H - 15 - f.size/2; f.vy = -f.vy * 0.3; f.vx *= 0.5; f.angle *= 0.5; } } f.x += f.vx; f.y += f.vy; } // ===== BUBBLES ===== for (let i = bubbles.length - 1; i >= 0; i--) { const b = bubbles[i]; b.y -= b.riseSpeed; b.life -= 0.02; if (b.life <= 0 || b.y < tank.y) bubbles.splice(i, 1); } // ===== DRAW ===== ctx.clearRect(0, 0, W, H); // Background const bgGrad = ctx.createRadialGradient(W/2, H/2, 0, W/2, H/2, Math.max(W, H)); bgGrad.addColorStop(0, '#0a0a2e'); bgGrad.addColorStop(1, '#000000'); ctx.fillStyle = bgGrad; ctx.fillRect(0, 0, W, H); // Room ctx.fillStyle = '#2a2a3a'; ctx.fillRect(room.x, room.y, room.w, room.h); // Room interior ctx.fillStyle = '#1a1a28'; ctx.fillRect(room.x + 2, room.y + 2, room.w - 4, room.h - 4); // Floor ctx.fillStyle = '#253344'; ctx.fillRect(room.x, room.y + room.h - 15, room.w, 15); // Water base (subtle gradient) const waterGrad = ctx.createLinearGradient(tank.x, tank.y + tank.h * waterLevel, tank.x, tank.y + tank.h); waterGrad.addColorStop(0, 'rgba(20,80,120,0.6)'); waterGrad.addColorStop(1, 'rgba(10,40,70,0.3)'); ctx.fillStyle = waterGrad; ctx.fillRect(tank.x, tank.y + tank.h * waterLevel, tank.w, tank.y + tank.h - (tank.y + tank.h * waterLevel)); // ===== DRAW TANK ===== // Glass body ctx.lineWidth = tank.glassThickness; ctx.strokeStyle = 'rgba(200,230,255,0.3)'; ctx.lineCap = 'butt'; ctx.beginPath(); ctx.moveTo(tank.x, tank.y); ctx.lineTo(tank.x, tank.y + tank.h); ctx.stroke(); ctx.beginPath(); ctx.moveTo(tank.x + tank.w, tank.y); ctx.lineTo(tank.x + tank.w, tank.y + tank.h); ctx.stroke(); // Top ctx.fillStyle = 'rgba(180,200,220,0.4)'; ctx.fillRect(tank.x - 2, tank.y - tank.glassThickness, tank.w + 4, tank.glassThickness + 2); // ===== DRAW CRACK ===== if (burstActive) { // Burst opening - darkened glass const bx = crack.x - crack.width / 2; const by = crack.y - crack.height / 2; const bw = crack.width; const bh = crack.height; ctx.fillStyle = 'rgba(40,40,50,0.9)'; ctx.fillRect(bx - 2, by - 2, bw + 4, bh + 4); // Inner shard pieces ctx.fillStyle = 'rgba(60,60,80,0.8)'; // Top shard ctx.beginPath(); ctx.moveTo(bx, by); ctx.lineTo(bx + bw, by); ctx.lineTo(bx + bw * 0.4, by + 4); ctx.closePath(); ctx.fill(); // Bottom shard ctx.beginPath(); ctx.moveTo(bx, by + bh); ctx.lineTo(bx + bw, by + bh); ctx.lineTo(bx + bw * 0.6, by + bh - 4); ctx.closePath(); ctx.fill(); // Crack lines ctx.strokeStyle = 'rgba(100,100,120,0.7)'; ctx.lineWidth = 1; for (let i = 0; i < 5; i++) { const ly = by + bh * (i / 4); ctx.beginPath(); ctx.moveTo(bx + bw * 0.2, ly); ctx.lineTo(bx + bw * 0.3 + Math.sin(i * 2) * 3, ly + 2); ctx.stroke(); } } else { // Draw visible crack const crackY = crack.y - crack.height / 2; ctx.fillStyle = 'rgba(120,140,160,0.5)'; ctx.fillRect(crack.x - crack.width / 2 - 1, crackY, crack.width + 2, crack.height); // Crack texture ctx.strokeStyle = 'rgba(180,200,220,0.6)'; ctx.lineWidth = 1.5; for (let i = 0; i < 6; i++) { const py = crackY + (crack.height / 6) * i + Math.sin(i * 3) * 2; ctx.beginPath(); ctx.moveTo(crack.x - 1, py); ctx.lineTo(crack.x + 1 + Math.sin(i) * 2, py + 1.5); ctx.stroke(); } } // ===== DRAW GLASS FRAGMENTS ===== for (const d of debris) { ctx.save(); ctx.translate(d.x, d.y); ctx.rotate(d.rotation); ctx.globalAlpha = Math.max(0, d.alpha); ctx.fillStyle = 'rgba(180,200,220,0.5)'; ctx.strokeStyle = 'rgba(220,230,240,0.4)'; ctx.lineWidth = 0.5; const tw = Math.max(1, d.w * 0.3); const th = Math.max(1, d.h * 0.3); ctx.beginPath(); ctx.moveTo(-d.w/2, -d.h/2); ctx.lineTo(d.w/2, -d.h/2); ctx.lineTo(d.w/2 - tw, -d.h/2 + th); ctx.lineTo(-d.w/2 + tw, -d.h/2 + th); ctx.closePath(); ctx.fill(); ctx.stroke(); ctx.restore(); } // ===== DRAW WATER SPLASHES ===== for (const s of waterSplashes) { ctx.globalAlpha = s.life; ctx.fillStyle = s.type === 'drop' ? 'rgba(150,220,255,0.7)' : 'rgba(200,240,255,0.5)'; ctx.beginPath(); ctx.arc(s.x, s.y, s.size * s.life, 0, Math.PI * 2); ctx.fill(); } ctx.globalAlpha = 1; // ===== DRAW FLOW ===== for (const p of waterFlow) { ctx.globalAlpha = p.life * 0.6; ctx.fillStyle = 'rgba(150,210,255,0.6)'; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); // Trail p.trail.push({ x: p.x, y: p.y }); if (p.trail.length > 6) p.trail.shift(); ctx.strokeStyle = 'rgba(150,210,255,0.3)'; ctx.lineWidth = p.size * 0.5; ctx.beginPath(); ctx.moveTo(p.trail[0].x, p.trail[0].y); for (let t = 1; t < p.trail.length; t++) { ctx.lineTo(p.trail[t].x, p.trail[t].y); } ctx.stroke(); } ctx.globalAlpha = 1; // ===== DRAW ROCKS ===== for (const r of rocks) { ctx.fillStyle = r.color; ctx.beginPath(); ctx.ellipse(r.x, r.y, r.r, r.r * 0.6, 0.3, 0, Math.PI * 2); ctx.fill(); // Highlight ctx.fillStyle = 'rgba(255,255,255,0.2)'; ctx.beginPath(); ctx.ellipse(r.x - r.r * 0.2, r.y - r.r * 0.3, r.r * 0.3, r.r * 0.18, -0.3, 0, Math.PI * 2); ctx.fill(); } // ===== DRAW PLANTS ===== for (const pl of plants) { pl.sway += 0.02; ctx.save(); ctx.translate(pl.x, pl.y); for (let s = 0; s < pl.segments; s++) { const sy = (s / pl.segments) * pl.r * 2; const sx = Math.sin(pl.sway + s * 0.8) * 6; ctx.beginPath(); ctx.moveTo(0, 0); ctx.quadraticCurveTo(sx, -pl.r * 0.5, sx * 2, -sy); ctx.strokeStyle = pl.color; ctx.lineWidth = 3 - s * 0.4; ctx.stroke(); } ctx.restore(); } // ===== DRAW TOY ===== if (toy.submerged) { // Underwater ctx.save(); ctx.translate(toy.x, toy.y); ctx.rotate(toy.angle); ctx.fillStyle = '#ff6b6b'; ctx.beginPath(); ctx.ellipse(0, 0, toy.w/2, toy.h/2, 0, 0, Math.PI * 2); ctx.fill(); // Eyes ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(5, -3, 2, 0, Math.PI * 2); ctx.arc(-5, -3, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(5.5, -3, 1, 0, Math.PI * 2); ctx.arc(-4.5, -3, 1, 0, Math.PI * 2); ctx.fill(); // Bubble ctx.globalAlpha = 0.6; ctx.fillStyle = 'rgba(200,240,255,0.5)'; ctx.beginPath(); ctx.arc(0, -toy.h, 3, 0, Math.PI * 2); ctx.fill(); ctx.globalAlpha = 1; ctx.restore(); } else { // Airborne toy ctx.save(); ctx.translate(toy.x, toy.y); ctx.rotate(toy.angle); ctx.fillStyle = '#ff9f1c'; ctx.beginPath(); ctx.ellipse(0, 0, toy.w/2, toy.h/2, 0, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#000'; ctx.font = '10px Arial'; ctx.textAlign = 'center'; ctx.fillText('!', 0, 4); ctx.restore(); } // Update toy if (toy.submerged) { const waterTop = tank.y + tank.h * waterLevel; if (toy.y + toy.h/2 > waterTop) { toy.submerged = false; toy.y = waterTop - toy.h/2; toy.vy = -8; for (let i = 0; i < 8; i++) { waterSplashes.push({ x: toy.x + (Math.random() - 0.5) * toy.w, y: waterTop, vx: (Math.random() - 0.5) * 4, vy: -2 - Math.random() * 3, life: 1, decay: 0.025, size: 2 + Math.random() * 2 }); } } } else { toy.vy += gravity * 0.5; toy.vy *= 0.95; toy.x += toy.vx; toy.y += toy.vy; toy.angle += toy.vAngle; toy.vAngle *= 0.9; // Floor bounce if (toy.y + toy.h/2 > H - 15) { toy.y = H - 15 - toy.h/2; toy.vy = -Math.abs(toy.vy) * 0.3; toy.vx *= 0.8; toy.vAngle *= 0.5; } // Wall bounce if (toy.x - toy.w/2 < room.x) { toy.x = room.x + toy.w/2; toy.vx = Math.abs(toy.vx); } if (toy.x + toy.w/2 > room.x + room.w) { toy.x = room.x + room.w - toy.w/2; toy.vx = -Math.abs(toy.vx); } } // Update bubbles for (const b of bubbles) { b.x += (Math.random() - 0.5) * 2; b.y -= b.riseSpeed; b.life -= 0.015; } // Draw room label ctx.globalAlpha = 0.5; ctx.fillStyle = 'white'; ctx.font = '12px Arial'; ctx.textAlign = 'center'; ctx.fillText('Room', room.x + room.w/2, room.y - 5); ctx.globalAlpha = 1; } // ===== INPUT ===== let pointerX = 0, pointerY = 0; let isDragging = false; canvas.addEventListener('mousedown', (e) => { const rect = canvas.getBoundingClientRect(); const mx = (e.clientX - rect.left) * (canvas.width / rect.width); const my = (e.clientY - rect.top) * (canvas.height / rect.height); // Check if clicking crack const dx = mx - crack.x; const dy = my - (crack.y - crack.height/2); if (Math.abs(dx) < crack.width + 5 && Math.abs(dy) < crack.height + 10) { crack.dragging = true; isDragging = true; updatePointer(mx, my); return; } // Burst burst(mx, my); }); canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); pointerX = (e.clientX - rect.left) * (canvas.width / rect.width); pointerY = (e.clientY - rect.top) * (canvas.height / rect.height); if (crack.dragging) { crack.y = screenToCrackY(pointerY); } }); canvas.addEventListener('mouseup', () => { crack.dragging = false; isDragging = false; }); canvas.addEventListener('touchstart', (e) => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; const mx = (touch.clientX - rect.left) * (canvas.width / rect.width); const my = (touch.clientY - rect.top) * (canvas.height / rect.height); const dx = mx - crack.x; const dy = my - (crack.y - crack.height/2); if (Math.abs(dx) < crack.width + 5 && Math.abs(dy) < crack.height + 10) { crack.dragging = true; isDragging = true; } else { burst(mx, my); } updatePointer(mx, my); }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const touch = e.touches[0]; pointerX = (touch.clientX - rect.left) * (canvas.width / rect.width); pointerY = (touch.clientY - rect.top) * (canvas.height / rect.height); if (crack.dragging) { crack.y = screenToCrackY(pointerY); } }); canvas.addEventListener('touchend', () => { crack.dragging = false; isDragging = false; }); function updatePointer(x, y) { pointerX = x; pointerY = y; } function burst(mx, my) { if (burstActive) return; burstActive = true; burstStartTime = Date.now(); // Check if click is on crack to open it, or anywhere to burst const dx = mx - crack.x; const dy = my - crack.y; const distToCrack = Math.sqrt(dx*dx + dy*dy); if (distToCrack < 60) { // Open crack mode crack.open = true; crack.targetHeight = Math.max(15, Math.min(60, distToCrack * 1.5)); // Set crack position crack.y = Math.max(tank.y + 20, Math.min(tank.y + tank.h - 20, my)); } else { // Full burst from current crack position crack.open = true; crack.targetHeight = 60; } } // Animation loop let lastTime = 0; function animate(time) { const dt = Math.min(time - lastTime, 30); lastTime = time; update(dt); draw(); requestAnimationFrame(animate); } // Start animation requestAnimationFrame(animate); </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.