oneshotlm
Compare all models on this prompt →

Double pendulum · deepseek-v3.2

Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.

⛨ sandboxed Open in new tab ↗
transcript · 10 messages
user

Simulate a chaotic double pendulum on a canvas using the correct equations of motion, drawing the arms and a colorful fading trail of the tip.

assistant
thinking
I'll create a single-page web app with a chaotic double pendulum simulation in a canvas. It will include the physics equations, colorful fading trail, and controls. Let's start by checking the current directory and then write the index.html.
bash {"command":"pwd && ls -la"}
toolResult

/workspace total 4 drwxrwxrwx 2 node node 64 Jul 22 17:15 . drwxr-xr-x 1 root root 4096 Jul 22 17:15 ..

assistant
thinking
Now write the index.html file with all CSS and JS inline. I need to implement the double pendulum equations of motion (Lagrangian). We'll simulate using Runge-Kutta integration. We'll draw the pendulum arms, pivot, and a trail that fades out. We'll add controls for resetting, changing colors, and maybe parameters.

Let's create a comprehensive file.
write index.html (27419 bytes)
toolResult

Successfully wrote 27419 bytes to index.html

assistant
thinking
Now let's verify the file exists and maybe open it in a browser to test quickly (though we can't open a browser, we can check with a headless browser using node?). But we can at least ensure the file is valid HTML and has no obvious syntax errors.

Let's read a portion of the file to confirm
read {"path":"index.html"}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chaotic Double Pendulum Simulation</title> <style> * { margin: 0; padding:她们 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #e6e6e6; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; overflow-x: hidden; } header { text-align: center; margin-bottom:要约 1.5rem; width: 100%; max-width: 1000px; } h1 { font-size: 2.8rem; margin-bottom: 0.5rem; background: linear-gradient(90deg, #00dbde 0%, #fc00ff 100%); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 0 15px rgba(0, 219, 222, 0.3); } .subtitle { font-size: 1.1rem; opacity: 0.8; margin-bottom:1.5rem; } .container { display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; width: 100%; max-width: 1200px; } .canvas-container { border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); border: 2px solid #333366; position: relative; background: #0c0c1a; flex: 0 0 auto; } #canvas { display: block; cursor: crosshair; } .controls { background: rgba(30, 30, 60, 0.9); border-radius: 12px; padding: 25px; flex: 1; min-width: 300px; max-width: 400px; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4); border: 1px solid #444488; } .controls h2 { font-size: 1.5rem; margin-bottom: 1.5rem; color: #a0a0ff; border-bottom: 2px solid #444488; padding-bottom: 0.5rem; } .control-group { margin-bottom: 1.5rem; } .control-group label { display: block; margin-bottom: 0.5rem; color: #b0b0ff; font-weight: 600; } .slider-container { display: flex; align-items: center; gap: 15px; } .slider-container input[type="range"] { flex: 1; height: 8px; -webkit-appearance: none; background: linear-gradient(90deg, #333366, #6666cc); border-radius: 4px; outline: none; } .slider-container input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%; background: #00dbde; cursor: pointer; box-shadow: 0 0 10px #00dbde; } .slider-container span { min-width: 50px; text-align: center; font-weight: bold; color: #00dbde; } .checkbox-container { display: flex; align-items: center; gap: 10px; margin-bottom: 0.8rem; } .checkbox-container input[type="checkbox"] { width: 18px; height: 18px; accent-color: #00dbde; } .button-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 1.5rem; } button { padding: 12px 20px; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; flex: 1; min-width: 120px; background: linear-gradient(90deg, #333366, #444488); color: #e6e6e6; font-size: 1rem; letter-spacing: 0.5px; box-shadow: 0 4px馈 8px rgba(0, 0, 0, 0.3); } button:hover { transform: translateY(-3px); box-shadow: 0 8px 15px rgba(0, 0, 0, 0.5); } button:active { transform: translateY(0); } .primary { background: linear-gradient(90deg, #00dbde, #0099ff); color: white; } .danger { background: linear-gradient(90deg, #ff416c, #ff4b2b); color: white; } .trail-color-picker { display: flex; gap: 10px; margin-top: 10px; } .color-option { width:座谈会 30px; height: 30px; border-radius: 50%; cursor: pointer; border: 3px solid transparent; transition: transform 0.2s ease; } .color-option:hover { transform: scale(1.1); } .color-option.selected { border-color: white; box-shadow: 0 0 10px white; } .info { background: rgba(20, 20, 40, 0.9); border-radius: 12px; padding: 20px; margin-top: 30px; width: 100%; max-width: 1000px; border: 1px solid #333366; } .info h2 { color: #a0a0ff; margin-bottom: 1rem; font-size: 1.5rem; } .info p { margin-bottom: 1rem; line-height: 1.6; } .info ul { padding-left: 1.5rem; margin-bottom: 1rem; } .info li { margin-bottom: 0.5rem; } footer { margin-top: 30px; text-align: center; font-size: 0.9rem; opacity: 0.7; padding: 20px; width: 100%; } @media (max-width: 900px) { .container { flex-direction: column; align-items: center; } .controls { max-width: 100%; } } </style> </head> <body> <header> <h1>Chaotic Double Pendulum</h1> <p class="subtitle">A simulation of deterministic chaos using Lagrangian mechanics. Tiny changes in initial conditions lead to vastly different trajectories.</p> </header> <div class="container"> <div class="canvas-container"> <canvas id="canvas" width="700" height="700"></canvas> </div> <div class="controls"> <h2>Controls</h2> <div class="control-group"> <label for="angle1">Initial Angle 1 (θ₁)</label> <div class="slider-container"> <input type="range" id="angle1" min="-180" max="180" value="-90" step="1"> <span id="angle1Value">-90°</span> </div> </div> <div class="control-group"> <label for="angle2">Initial Angle 2 (θ₂)</label> <div class="slider-container"> <input type="range" id="angle2" min="-180" max="180" value="180" step="1"> <span id="angle2Value">180°</span> </div> </div> <div class="control-group"> <label for="length1">Length 1 (m)</label> <div class="slider-container"> <input type="range" id="length1" min="50" max="300" value="150" step="1"> <span id="length1Value">150</span> </div> </div> <div class="control-group"> <label for="length2">Length 2 (m)</label> <div class="slider-container"> <input type="range" id="length2" min="50" max="300" value="150" step="1"> <span id="length2Value">150</span> </div> </div> <div class="control-group"> <label for="mass1">Mass 1 (kg)</label> <div class="slider-container"> <input type="range" id="mass1" min="1" max="50" value="20" step="1"> <span id="mass1Value">20</span> </div> </div> <div class="control-group"> <label for="mass2">Mass 2 (kg)</label> <div class="slider-container"> <input type="range" id="mass2" min="1" max="50" value="20" step="1"> <span id="mass2Value">20</span> </div> </div> <div class="control-group"> <label for="gravity">Gravity (m/s²)</label> <div class="slider-container"> <input type="range" id="gravity" min="1" max="30" value="9.81" step="0.1"> <span id="gravityValue">9.81</span> </div> </div> <div class="control-group"> <label>Trail Options</label> <div class="checkbox-container"> <input type="checkbox" id="showTrail" checked> <label for="showTrail">Show colorful fading trail</label> </div> <div class="checkbox-container"> <input type="checkbox" id="fadeTrail" checked> <label for="fadeTrail">Fade trail over time</label> </div> </div> <div class="control-group"> <label>Trail Color Scheme</label> <div class="trail-color-picker"> <div class="color-option selected" data-color="rainbow" style="background: linear-gradient(90deg, red, orange, yellow, green, blue, indigo, violet);"></div> <div class="color-option" data-color="heat" style="background: linear-gradient(90deg, yellow, orange, red);"></div> <div class="color-option" data-color="ocean" style="background: linear-gradient(90deg, #00dbde, #0099ff, #0044ff);"></div> <div class="color-option" data-color="neon" style="background: linear-gradient(90deg, #00ff87, #00fff7, #ff00ff);"></div> <div class="color-option" data-color="mono" style="background: #ffffff;"></div> </div> </div> <div class="button-row"> <button id="reset" class="primary">Reset Pendulum</button> <button id="randomize" class="primary">Randomize</button> <button id="pause" class="primary">Pause / Resume</button> <button id="clearTrail" class="danger">Clear Trail</button> </div> </div> </div> <div class="info"> <h2>About the Double Pendulum</h2> <p>The double pendulum is a classic example of a chaotic system. Despite being governed by deterministic equations of motion, its behavior is highly sensitive to initial conditions—a hallmark of chaos.</p> <p>This simulation uses Lagrangian mechanics to derive the equations of motion and solves them numerically using a 4th-order Runge-Kutta integration scheme.</p> <ul> <li><strong>θ₁, θ₂:</strong> Angles of the two arms relative to vertical</li> <li><strong>Length 1, Length 2:</strong> Rod lengths (in pixels, but labeled as meters)</li> <li><strong>Mass 1, Mass 2:</strong> Bob masses (affect momentum)</li> <li><strong>Gravity:</strong> Acceleration due to gravity</li> </ul> <p>The colorful trail behind the second bob shows its recent path, with colors indicating speed (blue = slow, red = fast) or selected color scheme.</p> <p>Try changing parameters slightly and observe how the motion diverges dramatically over time!</p> </div> <footer> <p>Simulation built with JavaScript & Canvas • Physics equations derived from Lagrangian mechanics • No external libraries used</p> </footer> <script> // Canvas setup const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const width = canvas.width; const height = canvas.height; const centerX = width / 2; const centerY = height / 2; const scale = 1; // pixels per meter // Pendulum state let state = { theta1: -Math.PI / 2, // angle of first arm theta2: Math.PI, // angle of second arm omega1: 0, // angular velocity 1 omega2: 0, // angular velocity 2 }; // Parameters let params = { L1: 150, // length 1 (pixels) L2: 150, // length 2 m1: 20, // mass 1 m2: 20, // mass 2 g: 9.81, // gravity }; // Simulation control let paused = false; let lastTime = 0; let trail = []; const TRAIL_MAX = 2000; let trailColorScheme = 'rainbow'; // UI elements const angle1Slider = document.getElementById('angle1'); const angle2Slider = document.getElementById('angle2'); const length1Slider = document.getElementById('length1'); const length2Slider = document.getElementById('length2'); const mass1Slider = document.getElementById('mass1'); const mass2Slider = document.getElementById('mass2'); const gravitySlider = document.getElementById('gravity'); const showTrailCheck = document.getElementById('showTrail'); const fadeTrailCheck = document.getElementById('fadeTrail'); const resetBtn = document.getElementById('reset'); const randomizeBtn = document.getElementById('randomize'); const pauseBtn = document.getElementById('pause'); const clearTrailBtn = document.getElementById('clearTrail'); const colorOptions = document.querySelectorAll('.color-option'); // Update display values angle1Slider.addEventListener('input', () => { document.getElementById('angle1Value').textContent = angle1Slider.value + '°'; if (!paused) resetPendulum(); }); angle2Slider.addEventListener('input', () => { document.getElementById('angle2Value').textContent = angle2Slider.value + '°'; if (!paused) resetPendulum(); }); length1Slider.addEventListener('input', () => { document.getElementById('length1Value').textContent = length1Slider.value; params.L1 = parseInt(length1Slider.value); }); length2Slider.addEventListener('input', () => { document.getElementById('length2Value').textContent = length2Slider.value; params.L2 = parseInt(length2Slider.value); }); mass1Slider.addEventListener('input', () => { document.getElementById('mass1Value').textContent = mass1Slider.value; params.m1 = parseInt(mass1Slider.value); }); mass2Slider.addEventListener('input', () => { document.getElementById('mass2Value').textContent = mass2Slider.value; params.m2 = parseInt(mass2Slider.value); }); gravitySlider.addEventListener('input', () => { document.getElementById('gravityValue').textContent = gravitySlider.value; params.g = parseFloat(gravitySlider.value); }); resetBtn.addEventListener('click', resetPendulum); pauseBtn.addEventListener('click', () => { paused = !paused; pauseBtn.textContent = paused ? 'Resume' : 'Pause'; if (!paused) { lastTime = performance.now(); requestAnimationFrame(animate); } }); randomizeBtn.addEventListener('click', randomizeParams); clearTrailBtn.addEventListener('click', () => { trail = []; }); colorOptions.forEach(opt => { opt.addEventListener('click', () => { colorOptions.forEach(o => o.classList.remove('selected')); opt.classList.add('selected'); trailColorScheme = opt.dataset.color; }); }); function resetPendulum() { state.theta1 = parseInt(angle1Slider.value) * Math.PI / 180; state.theta2 = parseInt(angle2Slider.value) * Math.PI / 180; state.omega1 = 0; state.omega2 = 0; trail = []; } function randomizeParams() { angle1Slider.value = Math.floor(Math.random() * 361 - 180); angle2Slider.value = Math.floor(Math.random() * 361 - 180); length1Slider.value = Math.floor(Math.random() * 251 + 50); length2Slider.value = Math.floor(Math.random() * 251 + 50); mass1Slider.value = Math.floor(Math.random() * 50 + 1); mass2Slider.value = Math.floor(Math.random() * 50 + 1); gravitySlider.value = (Math.random() * 29 + 1).toFixed(1); document.getElementById('angle1Value').textContent = angle1Slider.value + '°'; document.getElementById('angle2Value').textContent = angle2Slider.value + '°'; document.getElementById('length1Value').textContent = length1Slider.value; document.getElementById('length2Value').textContent = length2Slider.value; document.getElementById('mass1Value').textContent = mass1Slider.value; document.getElementById('mass2Value').textContent = mass2Slider.value; document.getElementById('gravityValue').textContent = gravitySlider.value; params.L1 = parseInt(length1Slider.value); params.L2 = parseInt(length2Slider.value); params.m1 = parseInt(mass1Slider.value); params.m2 = parseInt(mass2Slider.value); params.g = parseFloat(gravitySlider.value); resetPendulum(); } // Equations of motion for double pendulum (derived from Lagrangian) function derivatives(stateVec, t) { const { theta1, theta2, omega1, omega2 } = stateVec; const { L1, L2, m1, m2, g } = params; // Denominators for angular accelerations const delta = theta2 - theta1; const sinDelta = Math.sin(delta); const cosDelta = Math.cos(delta); const den1 = (m1 + m2) * L1 - m2 * L1 * cosDelta * cosDelta; const den2 = (L2 / L1) * den1; // Angular acceleration for theta1 const num1 = -g * (m1 + m2) * Math.sin(theta1) - m2 * g * Math.sin(theta1 - 2 * theta2) - 2 * sinDelta * m2 * (omega2 * omega2 * L2 + omega1 * omega1 * L1 * cosDelta); const alpha1 = num1 / (L1 * den1); // Angular acceleration for theta2 const num2 = 2 * sinDelta * (omega1 * omega1 * L1 * (m1 + m2) + g * (m1 + m2) * Math.cos(theta1) + omega2 * omega2 * L2 * m2 * cosDelta); const alpha2 = num2 / (L2 * den2); return [omega1, omega2, alpha1, alpha2]; } // 4th order Runge-Kutta integration function rk4(dt) { const s = [state.theta1, state.theta2, state.omega1, state.omega2]; const k1 = derivatives(s, 0); const k2 = derivatives(s.map((v, i) => v + 0.5 * dt * k1[i]), dt * 0.5); const k3 = derivatives(s.map((v, i) => v + 0.5 * dt * k2[i]), dt * 0.5); const k4 = derivatives(s.map((v, i) => v + dt * k3[i]), dt); for (let i = 0; i < 4; i++) { s[i] += dt / 6 * (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i]); } state.theta1 = s[0]; state.theta2 = s[1]; state.omega1 = s[2]; state.omega2 = s[3]; } // Compute pendulum positions function getPositions() { const x1 = centerX + params.L1 * Math.sin(state.theta1); const y1 = centerY + params.L1 * Math.cos(state.theta1); const x2 = x1 + params.L2 * Math.sin(state.theta2); const y2 = y1 + params.L2 * Math.cos(state.theta2); return { x0: centerX, y0: centerY, x1, y1, x2, y2 }; } // Draw pendulum function drawPendulum(pos) { // Clear canvas with a subtle gradient background const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, width * 0.8); gradient.addColorStop(0, '#0a0a1a'); gradient.addColorStop(1, '#151530'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, width, height); // Draw pivot ctx.beginPath(); ctx.arc(pos.x0, pos.y0,坐落 10, 0, Math.PI * 2); ctx.fillStyle = '#ffffff'; ctx.fill(); ctx.beginPath(); ctx.arc(pos.x0, pos.y0, 15, 0, Math.PI * 2); ctx.strokeStyle = '#8888ff'; ctx.lineWidth = 3; ctx.stroke(); // Draw arms ctx.beginPath(); ctx.moveTo(pos.x0, pos.y0); ctx.lineTo(pos.x1, pos.y1); ctx.lineWidth = 6; ctx.strokeStyle = '#aaaaaa'; ctx.stroke(); ctx.beginPath(); ctx.moveTo(pos.x1, pos.y1); ctx.lineTo(pos.x2, pos.y2); ctx.lineWidth = 6; ctx.strokeStyle = '#aaaaaa'; ctx.stroke(); // Draw bobs const r1 = Math.max(10, Math.sqrt(params.m1) * 2); const r2 = Math.max(10, Math.sqrt(params.m2) * 2); const gradient1 = ctx.createRadialGradient(pos.x1, pos.y1, 0, pos.x1, pos.y1, r1); gradient1.addColorStop(0, '#00dbde'); gradient1.addColorStop(1, '#0077aa'); ctx.beginPath(); ctx.arc(pos.x1, pos.y1, r1, 0, Math.PI * 2); ctx.fillStyle = gradient1; ctx.fill(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.stroke(); const gradient2 = ctx.createRadialGradient(pos.x2, pos.y2, 0, pos.x2, pos.y2, r2); gradient2.addColorStop(0, '#ff416c'); gradient2.addColorStop(1, '#cc0044'); ctx.beginPath(); ctx.arc(pos.x2, pos.y2, r2, 0, Math.PI * 2); ctx.fillStyle = gradient2; ctx.fill(); ctx.strokeStyle = '#ffffff'; ctx.lineWidth = 2; ctx.stroke(); } // Draw trail function drawTrail() { if (!showTrailCheck.checked || trail.length < 2) return; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; const fade = fadeTrailCheck.checked; const total = trail.length; for (let i = 1; i < trail.length; i++) { const p0 = trail[i - 1]; const p1 = trail[i]; // Determine color based on scheme and position let color; const t = i / total; const speed = p1.speed || 0; switch (trailColorScheme) { case 'rainbow': const hue = (t * 360 + (Date.now() * 0.001) * 20) % 360; color = `hsl(${hue}, 100%, 70%)`; break; case 'heat': const red = Math.min(255, Math.floor(255 * t)); const green = Math.min(255, Math.floor(128 * (1 - t))); const blue = Math.min(255, Math.floor(64 * (1 - t))); color = `rgb(${red}, ${green}, ${blue})`; break; case 'ocean': const blueShade = Math.floor(100 + 155 * t); const greenShade = Math.floor(200 - 100 * t); color = `rgb(0, ${greenShade}, ${blueShade})`; break; case 'neon': const phase = (t * 2 * Math.PI + Date.now() * 0.002) % (2 * Math.PI); const r = Math.floor(127 + 128 * Math.sin(phase)); const g = Math.floor(127 + 128 * Math.sin(phase + 2)); const b = Math.floor(127 + 128 * Math.sin(phase + 4)); color = `rgb(${r}, ${g}, ${b})`; break; case 'mono': default: const alpha = fade ? (0.1 + 0.9 * (i / total)) : 1; color = `rgba(255, 255, 255, ${alpha})`; break; } // Fade out older segments let alpha = 1; if (fade) { alpha = 0.05 + 0.95 * (i / total); if (trailColorScheme !== 'mono') { // For colored schemes, we can't directly set alpha, so adjust rgba if (color.startsWith('rgb(')) { color = color.replace(')', `, ${alpha})`).replace('rgb', 'rgba'); } else if (color.startsWith('hsl')) { color = color.replace(')', `, ${alpha})`).replace('hsl', 'hsla'); } } else { // Already handled above } } ctx.strokeStyle = color; ctx.beginPath(); ctx.moveTo(p0.x, p0.y); ctx.lineTo(p1.x, p1.y); ctx.stroke(); } } // Animation loop function animate(currentTime) { if (!lastTime) lastTime = currentTime; const dt = Math.min(0.02, (currentTime - lastTime) / 1000); // cap at 20ms lastTime = currentTime; if (!paused) { // Step physics rk4(dt); // Get positions const pos = getPositions(); // Add to trail const speed = Math.sqrt(state.omega1 * state.omega1 + state.omega2 * state.omega2); trail.push({ x: pos.x2, y: pos.y2, speed }); if (trail.length > TRAIL_MAX) { trail.shift(); } } // Draw const pos = getPositions(); drawPendulum(pos); drawTrail(); // Continue animation if (!paused) { requestAnimationFrame(animate); } } // Initialize UI function init() { resetPendulum(); randomizeParams(); requestAnimationFrame(animate); } // Start init(); </script> </body> </html>

assistant
thinking
I notice some stray characters in CSS: "她们", "要约", "坐落", "馈". Those are probably copy-paste artifacts. Let's fix them quickly by editing the CSS. We'll replace those lines. First, locate them:

In the CSS, there are lines like:
- `padding:她们 0;`
- `margin-bottom:要约 1.5rem;`
- `padding-bottom: 0.5rem;` (maybe there's other stray)
- `box-shadow: 0 4px馈 8px`
- `ctx.arc(pos.x0, pos.y0,坐落 10,` in JS (should be `ctx.arc(pos.x0, pos.y0, 10,`)

Let's make a single edit for CSS and JS. We'll replace the entire style block with corrected CSS, and also fix the JS arc line.

But note: the edit's oldText must exactly match what's in the file. We'll extract the style block first to see its exact content. Let's read the file from start to end to see the full style block. Use bash to get lines? Let's read the whole file with offset/limit to get the style block region. Let's read from line 1 to maybe 200.
read {"path":"index.html","offset":1,"limit":200}
toolResult

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Chaotic Double Pendulum Simulation</title> <style> * { margin: 0; padding:她们 0; box-sizing: border-box; } body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); color: #e6e6e6; min-height: 100vh; display: flex; flex-direction: column; align-items: center; padding: 20px; overflow-x: hidden; } header { text-align: center; margin-bottom:要约 1.5rem; width: 100%; max-width: 1000px; } h1 { font-size: 2.8rem; margin-bottom: 0.5rem; background: linear-gradient(90deg, #00dbde 0%, #fc00ff 100%); -webkit-background-clip: text; background-clip: text; color: transparent; text-shadow: 0 0 15px rgba(0, 219, 222, 0.3); } .subtitle { font-size: 1.1rem; opacity: 0.8; margin-bottom:1.5rem; } .container { display: flex; flex-wrap: wrap; justify-content: center; gap: 30px; width: 100%; max-width: 1200px; } .canvas-container { border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6); border: 2px solid #333366; position: relative; background: #0c0c1a; flex: 0 0 auto; } #canvas { display: block; cursor: crosshair; } .controls { background: rgba(30, 30, 60, 0.9); border-radius: 12px; padding: 25px; flex: 1; min-width: 300px; max-width: 400px; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.4); border: 1px solid #444488; } .controls h2 { font-size: 1.5rem; margin-bottom: 1.5rem; color: #a0a0ff; border-bottom: 2px solid #444488; padding-bottom: 0.5rem; } .control-group { margin-bottom: 1.5rem; } .control-group label { display: block; margin-bottom: 0.5rem; color: #b0b0ff; font-weight: 600; } .slider-container { display: flex; align-items: center; gap: 15px; } .slider-container input[type="range"] { flex: 1; height: 8px; -webkit-appearance: none; background: linear-gradient(90deg, #333366, #6666cc); border-radius: 4px; outline: none; } .slider-container input[type="range"]::-webkit-slider-thumb { -webkit-appearance: none; width: 22px; height: 22px; border-radius: 50%; background: #00dbde; cursor: pointer; box-shadow: 0 0 10px #00dbde; } .slider-container span { min-width: 50px; text-align: center; font-weight: bold; color: #00dbde; } .checkbox-container { display: flex; align-items: center; gap: 10px; margin-bottom: 0.8rem; } .checkbox-container input[type="checkbox"] { width: 18px; height: 18px; accent-color: #00dbde; } .button-row { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 1.5rem; } button { padding: 12px 20px; border: none; border-radius: 8px; font-weight: bold; cursor: pointer; transition: all 0.3s ease; flex: 1; min-width: 120px; background: linear-gradient(90deg, #333366, #444488); color: #e6e6e6; font-size: 1rem; letter-spacing: 0.5px; box-shadow: 0 4px馈 8px rgba(0, 0, 0, 0.3); } button:hover { transform: translateY(-3px); box-shadow: 0 8px 15px rgba(0, 0, 0, 0.5); } button:active { transform: translateY(0); } .primary { background: linear-gradient(90deg, #00dbde, #0099ff); color: white; } .danger { background: linear-gradient(90deg, #ff416c, #ff4b2b); color: white; } .trail-color-picker { display: flex; gap: 10px; margin-top: 10px; } .color-option { width:座谈会 30px; height: 30px; border-radius: 50%; cursor: pointer; [552 more lines in file. Use offset=201 to continue.]

assistant

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

modeldeepseek-v3.2
vendorDeepSeek
harnesspi-agent
generated2026-07-22

EVALUATION

1/5 broken

Canvas remains completely blank throughout; no pendulum, arms, or trail ever render due to a JS syntax error.

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 enter + space35.5%
on arrow keys0.0%
on w/a/s/d0.0%
frame spread28.8 / 255
console errors0
js errors
  • missing ) after argument list

METRICS

runtime145.1s
tokens in25k
tokens out7.4k
cached2.1k
cost / run$0.0083