oneshotlm
Compare all models on this prompt →

Mandelbrot explorer · seed-2.0-lite

Render the Mandelbrot set with a smooth color gradient on a full-screen canvas, with click-and-drag to pan and mouse-wheel to zoom.

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

Render the Mandelbrot set with a smooth color gradient on a full-screen canvas, with click-and-drag to pan and mouse-wheel to zoom.

assistant
thinking
Got it, let's create a self-contained index.html that renders the Mandelbrot set with pan/zoom and smooth colors. First, I need to structure the HTML with a canvas that takes the full screen. Then add CSS to make it fullscreen, no margins. Then the JavaScript to handle the rendering, mouse events for drag and scroll.

First, the HTML structure: a canvas element that's fixed to cover the whole viewport. The CSS will set body and html to 100% width/height, no overflow, margin 0. The canvas is width and height 100vw and 100vh.

Then the Mandelbrot rendering logic. I need to track the current view bounds: center x, center y, zoom level. The default view is usually -0.5, 0, with a range that covers the whole set, so from x: -2.5 to 1, y: -1 to 1, which is width 3.5, so scale that based on zoom.

For smooth coloring: the standard method is to use the iteration count plus a smoothing term to avoid banding. Then map that to an HSL color gradient, maybe cycle through hues to get a smooth rainbow, or use a custom gradient. Convert HSL to RGB to set the pixel data on the canvas.

Mouse events: mousedown to start dragging, mousemove to update the pan when dragging, mouseup to stop. Wheel event to zoom in/out, scaling the zoom factor, and zoom towards the mouse position so it's centered where the user scrolled. That's important, when you zoom, you want to zoom into the point the mouse is over, not the center of the screen.

Wait, let's outline the steps:

1. Set up the canvas to fill the screen, handle window resize to reset the canvas size and rerender.
2. Initialize view parameters: centerX = -0.5, centerY = 0, scale = 1/200 (so the initial width is 3.5, which is 700 pixels wide, which fits most screens). Max iterations, maybe 100, adjust that for detail.
3. Create a function to convert screen coordinates to complex plane coordinates.
4. The Mandelbrot calculation: for each pixel, compute c = x + yi, iterate z = z² + c until |z| > 2, count iterations. If it doesn't escape, it's part of the set, color it black. For escaping points, add the smoothing term: log2(log2(|z|)) to get a smooth value, then map that to a color.
5. Implement the smooth color gradient: use HSL, where hue is based on the smooth iteration count, saturation 1, lightness 0.5, which gives a nice rainbow. Or adjust to have a better gradient.
6. Handle mouse events: when user clicks and drags, update the center based on the delta in screen coordinates, converted to the complex plane's units. For wheel events, adjust the scale (multiply by 0.9 for zoom in, 1.1 for zoom out), and adjust the center so that the mouse position stays under the cursor, which is the correct zoom behavior.
7. Every time the view changes, rerender the Mandelbrot set. To make it efficient, but since it's a single-threaded JS, even on large screens it's manageable for a demo. Maybe add a debounce? No, for simplicity, just rerender on every change, it's fast enough for the purposes here.

Wait, let's write the code step by step. First the HTML:

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

Then in the script: get the canvas, context. Set canvas width and height to window.innerWidth and innerHeight. Add a resize event listener to update that.

Then the view variables:
let centerX = -0.5;
let centerY = 0;
let scale = 1 / 200; // Initial scale, so 1 unit = 200 pixels, so initial width is 3.5 (from -2.5 to 1) which is 3.5 * 200 = 700px, which is good.
const maxIterations = 150;

Then a flag for dragging, last mouse position:
let isDragging = false;
let lastMouseX, lastMouseY;

Then the render function: function render() { get the canvas width and height, create image data, loop over every pixel, compute the complex coordinates, run the mandelbrot calculation, set the pixel color.

Wait, the Mandelbrot calculation for smooth coloring: the standard formula is:

if (iter < maxIterations) {
    // add smoothing
    const logZn = Math.log(zx*zx + zy*zy) / 2;
    const nu = Math.log(logZn / Math.log(2)) / Math.log(2);
    iter = iter + 1 - nu;
}

Then use that iter value to map to a hue. For example, hue = (iter / maxIterations * 360) % 360, so it cycles. Then convert HSL to RGB, because canvas ImageData uses RGB. There's a standard HSL to RGB function.

Wait, write an hslToRgb function that takes h, s, l and returns [r, g, b]. That's easy.

Then for mouse events: canvas.addEventListener('mousedown', (e) => { isDragging = true; lastMouseX = e.clientX; lastMouseY = e.clientY; }); canvas.addEventListener('mousemove', (e) => { if (isDragging) { const dx = e.clientX - lastMouseX; const dy = e.clientY - lastMouseY; // convert dx/dy from pixels to complex plane units centerX -= dx * scale; centerY -= dy * scale; lastMouseX = e.clientX; lastMouseY = e.clientY; render(); } }); canvas.addEventListener('mouseup', () => { isDragging = false; }); canvas.addEventListener('mouseleave', () => { isDragging = false; });

Then the wheel event: canvas.addEventListener('wheel', (e) => { e.preventDefault(); // get mouse position in canvas const mouseX = e.clientX; const mouseY = e.clientY; // convert to complex plane coordinates before zoom const worldX = (mouseX - canvas.width/2) * scale + centerX; const worldY = (mouseY - canvas.height/2) * scale + centerY; // adjust scale if (e.deltaY < 0) { // zoom in scale *= 0.9; } else { // zoom out scale *= 1.1; } // adjust center so that the mouse point stays in the same place const newWorldX = (mouseX - canvas.width/2) * scale + centerX; const newWorldY = (mouseY - canvas.height/2) * scale + centerY; centerX += worldX - newWorldX; centerY += worldY - newWorldY; render(); });

That's the correct way to zoom towards the mouse, which is what users expect.

Then the render function: first get the width and height of the canvas. const width = canvas.width; const height = canvas.height; const imageData = ctx.createImageData(width, height); const data = imageData.data; Then loop over each y from 0 to height, each x from 0 to width. Compute the c x and y: const px = (x - width/2) * scale + centerX; const py = (y - height/2) * scale + centerY; Then iterate zx=0, zy=0, iter=0. While zx*zx + zy*zy < 4 and iter < maxIterations: const temp = zx*zx - zy*zy + px; zy = 2*zx*zy + py; zx = temp; iter++; Then compute the color. If iter is maxIterations, set to black. Else compute the smooth iter, then hsl to rgb. Then set the data at position (y*width + x)*4 + 0 = r, +1 =g, +2 =b, +3=255.

Wait, the HSL to RGB function: function hslToRgb(h, s, l) { h /= 360; let r, g, b; if (s === 0) { r = g = b = l; } else { const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -=1; if (t < 1/6) return p + (q-p)*6*t; if (t < 1/2) return q; if (t < 2/3) return p + (q-p)*(2/3 - t)*6; return p; }; const q = l < 0.5 ? l*(1+s) : l + s - l*s; const p = 2*l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r*255), Math.round(g*255), Math.round(b*255)]; } That's the standard implementation.

Then, when the smooth iteration is calculated, set h = iter * 10 % 360, to get a nice spread of colors, not too compressed. s=1, l=0.5, so bright colors. The set itself is black, which is good.

Putting it all together, test that it works. Also add a window resize event: window.addEventListener('resize', () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; render(); }); So when you resize the window, it rerenders.

Wait, initial call to render() after setting up everything. That's all. Let's put all this into the index.html file, write it as a single file, no external resources. Let's check that all parts are there: full screen canvas, pan with drag, zoom with mouse wheel, smooth coloring. Yep. Let's write that file.
write /tmp/ws/index.html (5922 bytes)
toolResult

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

assistant
thinking
The file is created successfully. Let's just confirm that all requirements are met: it's a single self-contained index.html, uses only inline CSS and JS, no external resources, fullscreen canvas, click-and-drag to pan, mouse wheel to zoom, smooth color gradient on the Mandelbrot set. Yep, that's all there. The code includes smooth coloring to avoid banding, zoom follows the mouse position which is the expected behavior, panning works with drag, resizes with the window, the set is rendered correctly. The end result is complete.

The Mandelbrot set explorer is complete, saved as `/tmp/ws/index.html`. It features: - Full-screen canvas rendering - Click-and-drag to pan across the fractal - Mouse-wheel zoom that centers on your cursor position - Smooth color gradient to avoid iteration banding - Automatic window resize handling - All code self-contained in a single file with no external dependencies Open the file in a modern browser to explore the Mandelbrot set!

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

modelseed-2.0-lite
vendorByteDance · Seed
harnesspi-agent
generated2026-08-01

EVALUATION

4/5 working

Mandelbrot set with smooth color gradient; drag pans and wheel zooms correctly, click/keys have no effect as expected.

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 drag74.0%
on wheel7.5%
on enter + space0.0%
on arrow keys0.0%
on w/a/s/d0.0%
frame spread37.9 / 255
console errors0
js errors none

METRICS

runtime55.3s
tokens in7.5k
tokens out4.2k
cached1.3k
cost / run$0.0100