← Fractal Explorer results

Fractal Explorer

lfm2 24b a2b · basic

Initial view of Fractal Explorer

Browser check: Artifact runtime error. This is a technical check, not a quality review.

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

Imported historical artifact. Its exact original instructions and lineage are not known.

Generation prompt · exact text unavailable

See the current prompt for reference. It may differ from the instructions used to generate this artifact.

Inspect original source 15,447 bytes · SHA-256 4e5f2ac174ae
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Interactive Fractal Explorer</title>
<style>
  :root {--bg:#0f0f12;--fwd:#e0e0ff;--accent:#ff6b6b;--accent2:#ffd93d;--accent3:#ffc107;--text:#e0e0ff;--accent4:#ff8c00;}
  * {box-sizing:border-box;}
  body,html {margin:0;padding:0;height:100%;width:100%;font-family:sans-serif;overflow:hidden;}
  #canvas {display:block;width:100vw;height:100vh;image-rendering:pixelated;}
  #hud {position:fixed;bottom:0;left:0;right:0;background:rgba(0,0,0,0.7);color:#fff;padding:8px 12px;font-size:12px;z-index:10;display:flex;align-items:center;gap:8px;}
  #hud span {margin-left:8px;}
  .palette-swatches {display:flex;flex-wrap:wrap;gap:4px;margin-top:4px;}
  .palette-swatch {width:16px;height:16px;border-radius:2px;cursor:pointer;box-shadow:0 1px 3px rgba(0,0,0,.2);}
  #controls {position:fixed;top:12px;left:12px;background:#111;padding:10px 16px;border-radius:8px;display:flex;flex-direction:column;gap:6px;}
  .control-group {display:flex;align-items:center;flex-wrap:wrap;gap:6px;min-width:180px;}
  label {font-size:11px;color:#e0e0ff;}
  input[type=range] {width:120px;}
  #zoom-btn,#reset-btn,#j-btn {background:#222;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;font-weight:bold;}
  #zoom-btn:hover,#reset-btn:hover,#j-btn:hover {background:#444;}
  #zoom-slider {width:120px;margin-left:6px;}
  #back-btn {background:#222;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;}
  #palette-select {font-size:13px;color:#fff;background:#111;padding:2px 6px;border-radius:4px;border:none;cursor:pointer;}
  .active {background:#222;color:#fff;}
  #view-info {position:absolute;bottom:12px;left:12px;font-size:11px;color:#bbb;z-index:10;}
  #debug-panel {position:fixed;top:0;right:0;padding:8px 12px;background:rgba(0,0,0,.7);color:#fff;font-family:monospace;font-size:11px;display:none;z-index:20;}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="hud">
  <span>Zoom: ×<span id="zoom">1e0</span></span>
  <span>Center: (x,y)</span>
  <span>Iterations: <span id="iter">0</span></span>
  <span>FPS: <span id="fps">0</span></span>
</div>
<div id="controls">
  <div class="control-group">
    <label>Palette</label>
    <div class="palette-swatches" id="palette-swatches"></div>
  </div>
  <div class="control-group">
    <label>Julia c (click or J)</label>
    <input type="text" id="j-c" placeholder="c real, c imag" style="width:60px;font-size:11px;">
    <button id="j-btn">J</button>
  </div>
  <div class="control-group">
    <label>Iterations</label>
    <input type="range" id="iter-slider" min="1" max="200" value="100" style="width:120px;">
    <span id="iter-label">100</span>
  </div>
  <div class="control-group">
    <label>Max Iterations</label>
    <input type="range" id="max-iter-slider" min="50" max="300" value="200" style="width:120px;">
    <span id="max-iter-label">200</span>
  </div>
  <button id="zoom-btn" class="active">Zoom+</button>
  <button id="reset-btn">Reset</button>
  <div id="debug-panel">
    <span>FPS: <span id="fps-debug">0</span></span>
    <span>Iterations(0,0):<span id="iter-debug">0</span></span>
    <span>View: <span id="view-info">1x @ 1e0</span></span>
  </div>
</div>
<div id="view-info" style="position:absolute;bottom:12px;left:12px;font-size:11px;color:#bbb;z-index:10;"></div>

<script>
/* ==== CONFIG ====
 * All constants are here for quick tuning and documented behavior
 * Iteration count scales with zoom (log scale) to keep detail at high magnifications
 * Max iterations is capped to avoid infinite loops while allowing deep zooms
 * Smooth coloring uses fractional escape count: n + 1 - log2(log|z|)
 */
const CONFIG = {
  CANVAS_WIDTH: window.innerWidth,
  CANVAS_HEIGHT: window.innerHeight,
  MAX_ITER_BASE: 200, // base iterations at base zoom
  MAX_ITER_SCALE: 2.5, // iterates faster as zoom increases
  MIN_ITER: 1,
  MAX_ITER_MAX: 3000, // cap to avoid precision loss
  ZOOM_SPEED: 0.85, // smooth zoom factor per wheel step
  PAN_SPEED: 0.3, // px per step when dragging pan
  PROGRESSIVE_PASSES: 2, // 1/4 then full resolution
  FPS_LIMIT: 60,
  DEBUG_FPS: true,
  FRAME_TIME_MS: 1000 / CONFIG.FPS_LIMIT,
  MAX_ZOOM: 1e13,
  CURSOR_RADIUS: 8,
  HISTORY_DEPTH: 10,
  INTERNAL_PIXELS: 256, // coarsest pass resolution
  ANIMATION_SMOOTH: true,
  FRACTAL_COLORS: {
    fire: [0xff0000,0xff7f00,0xffff00,0xffd700],
    ocean: [0x001a33,0x0066cc,0x0099ff,0x66ddff],
    monochrome: [0x222222,0x444444,0x666666,0x88aaaa],
    psychedelic: [0xff00ff,0x00ffff,0xffff00,0xff7f00]
  }
};

/* ==== STATE ====
 * All mutable state lives here; updated via UI and math functions
 */
const state = {
  center: {x: -0.5, y: 0}, // Mandelbrot c-reference
  scale: 1,
  zoom: 1,
  maxIter: CONFIG.MAX_ITER_BASE,
  iterations: 100,
  palette: 'fire',
  jc: null, // Julia c for preview
  history: [],
  activeBox: null, // {x1,y1,x2,y2}
  isDraggingBox: false,
  lastFps: 0,
  lastTime: 0,
  animationId: null,
  lastRenderTime: 0,
  frameCount: 0
};

/* ==== CANVAS SETUP ====
 * Precompute pixel buffer and color palette lookup tables for speed
 */
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
canvas.width = CONFIG.CANVAS_WIDTH;
canvas.height = CONFIG.CANVAS_HEIGHT;

// Color palettes as lookup arrays (linear interpolation supported)
const PALETTES = CONFIG.FRACTAL_COLORS;
function paletteColor(iter, maxIter) {
  if (iter >= maxIter || iter <= 0) return [0,0,0];
  // smooth fractional iteration
  const n = iter - 1;
  const logz = Math.log(Math.abs(cj));
  const log2logz = Math.log(Math.logz);
  const t = n + 1 - Math.log2(log2logz);
  // clamp to palette range
  const idx = Math.min(Math.floor(t * (palette.length-1)), palette.length-1);
  return palette[idx];
}
// Precompute palette lookup for fast coloring
const PALETTE_LOOKUP = {};
for (let i=0;i<4;i++) {
  const p = CONFIG.FRACTAL_COLORS[Object.keys(CONFIG.FRACTAL_COLORS)[i]];
  for (let j=0;j<p.length;j++) {
    PALETTE_LOOKUP[p[j]] = [j,p[j]];
  }
}

/* ==== RENDERING CORE ====
 * All pixel work is done via ImageData and direct buffer writes
 */
function renderFrame() {
  const w = canvas.width, h = canvas.height;
  const imgData = ctx.createImageData(w, h);
  const data = imgData.data;

  // Coarse pass if requested (progressive refinement)
  const coarseRes = Math.max(1, Math.floor(CONFIG.PROGRESSIVE_PASSES * state.zoom / CONFIG.MAX_ZOOM));
  const stepX = w / coarseRes;
  const stepY = h / coarseRes;

  // For each pixel in coarse grid
  for (let y = 0; y < coarseRes; ++y) {
    for (let x = 0; x < coarseRes; ++x) {
      // Map to complex plane
      const cx = state.center.x + (x - .5) * state.scale / (coarseRes/2);
      const cy = state.center.y + (y - .5) * state.scale / (coarseRes/2);

      // Mandelbrot iteration with smooth coloring
      let zx = 0, zy = 0;
      let iter = 0;
      let escaped = false;
      const maxIter = Math.min(CONFIG.MAX_ITER_MAX, Math.floor(state.maxIter * state.zoom * CONFIG.ZOOM_SPEED * state.zoom));
      while (iter < maxIter && !escaped) {
        const zx2 = zx*zx - zy*zy + cx;
        const zy2 = 2*zx*zy + cy;
        zx = zx2; zy = zy2;
        iter++;
        if (zx*zx + zy*zy > 4) {
          escaped = true;
        }
      }

      // Color
      const col = iter < maxIter ? paletteColor(iter, maxIter) : [0,0,0];
      const idx = (y*w + x)*4;
      data[idx] = col[0];
      data[idx+1] = col[1];
      data[idx+2] = col[2];
      data[idx+3] = 255;
    }
  }

  // Optional: draw selection overlay
  if (state.activeBox) {
    const [x1,y1,x2,y2] = state.activeBox;
    ctx.save();
    ctx.globalAlpha = 0.4;
    ctx.strokeStyle = state.isDraggingBox ? '#ff8c00' : '#ff6b6b';
    ctx.lineWidth = 2;
    ctx.setLineDash([4,3]);
    ctx.beginPath();
    ctx.rect(x1, y1, x2-x1, y2-y1);
    ctx.stroke();
    ctx.setLineDash([]);
    ctx.restore();
  }

  // Copy buffer to canvas
  ctx.putImageData(imgData, 0, 0);

  // Update HUD
  updateHud();
}

/* ==== INTERACTION HANDLERS ====
 * Mouse events for pan/zoom/drag selection
 */
canvas.addEventListener('mousedown', e => {
  if (e.target !== canvas) return;
  state.isDraggingBox = true;
  state.activeBox = [e.offsetX, e.offsetY, e.offsetX, e.offsetY];
  updateHud();
});
canvas.addEventListener('mousemove', e => {
  if (!state.isDraggingBox) return;
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left, y = e.clientY - rect.top;
  state.activeBox[2] = x; state.activeBox[3] = y;
  updateHud();
});
canvas.addEventListener('mouseup', e => {
  if (!state.isDraggingBox) return;
  state.isDraggingBox = false;
  const rect = canvas.getBoundingClientRect();
  const x1 = Math.min(state.activeBox[0], state.activeBox[2]);
  const y1 = Math.min(state.activeBox[1], state.activeBox[3]);
  const x2 = Math.max(state.activeBox[0], state.activeBox[2]);
  const y2 = Math.max(state.activeBox[1], state.activeBox[3]);
  // Center on box center
  const cx = (x1 + x2) / 2, cy = (y1 + y2) / 2;
  state.center = {
    x: cx - (state.scale / 2),
    y: cy - (state.scale / 2)
  };
  state.zoom = Math.max(1, Math.min(state.zoom*1.2, CONFIG.MAX_ZOOM));
  state.history.push({center: {x:cx, y:cy}, scale: state.zoom, zoom: state.zoom, palette: state.palette, jc: state.jc});
  if (state.history.length > state.HISTORY_DEPTH) state.history.shift();
  updateHud();
});
canvas.addEventListener('wheel', e => {
  e.preventDefault();
  const rect = canvas.getBoundingClientRect();
  const x = e.clientX - rect.left, y = e.clientY - rect.top;
  // Center on mouse
  const cx = state.center.x + (x - rect.width/2);
  const cy = state.center.y + (y - rect.height/2);
  const zoomFactor = e.deltaY < 0 ? CONFIG.ZOOM_SPEED : -CONFIG.ZOOM_SPEED;
  // Clamp scale
  let newScale = state.zoom * Math.exp(zoomFactor * 0.1);
  newScale = Math.max(CONFIG.MIN_ZOOM, Math.min(newScale, CONFIG.MAX_ZOOM));
  // Update view
  state.center = {x:cx, y:cy};
  state.zoom = newScale;
  state.history.push({center: {x:cx, y:cy}, scale: newScale, zoom: newScale, palette: state.palette, jc: state.jc});
  if (state.history.length > state.HISTORY_DEPTH) state.history.shift();
  updateHud();
}, {passive:false});
document.getElementById('reset-btn').addEventListener('click', () => {
  state.center = {x:-0.5, y:0};
  state.scale = 1;
  state.zoom = 1;
  state.maxIter = CONFIG.MAX_ITER_BASE;
  state.palette = 'fire';
  state.jc = null;
  state.history = [];
  updateHud();
});
document.getElementById('j-btn').addEventListener('click', () => {
  const cStr = document.getElementById('j-c').value.trim();
  if (cStr) {
    try {
      const [cr, ci] = cStr.split(',').map(Number);
      state.jc = {real: cr, imag: ci};
    } catch(e) {}
  }
});
document.getElementById('iter-slider').addEventListener('input', e => {
  state.iterations = +e.target.value;
  updateHud();
});
document.getElementById('max-iter-slider').addEventListener('input', e => {
  state.maxIter = +e.target.value;
  updateHud();
});

/* ==== PROGRESSIVE REFINEMENT ====
 * Draw low-res first, then refine to full resolution
 */
function progressiveRender() {
  const w = canvas.width, h = canvas.height;
  let progress = 0, total = CONFIG.PROGRESSIVE_PASSES;
  function renderStep() {
    if (progress >= total) return;
    const step = Math.ceil(CONFIG.PROGRESSIVE_PASSES * state.zoom / CONFIG.MAX_ZOOM);
    const coarseRes = Math.max(1, Math.floor(step/2));
    // Draw low-res pass
    renderFrameLowRes(coarseRes);
    progress += 1;
    requestAnimationFrame(renderStep);
  }
  function renderFrameLowRes(res) {
    const imgData = ctx.createImageData(w, h);
    const data = imgData.data;
    for (let y = 0; y < res; ++y) {
      for (let x = 0; x < res; ++x) {
        // Same math as above but at lower resolution
        const cx = state.center.x + (x - .5) * state.scale / (res/2);
        const cy = state.center.y + (y - .5) * state.zoom / (res/2);
        let zx = 0, zy = 0, iter = 0;
        while (iter < state.maxIter && !(zx*zx+zy*zy > 4)) {
          const zx2 = zx*zx - zy*zy + cx;
          const zy2 = 2*zx*zy + cy;
          zx = zx2; zy = zy2;
          iter++;
        }
        const col = iter < state.maxIter ? paletteColor(iter, state.maxIter) : [0,0,0];
        const idx = (y*w + x)*4;
        data[idx] = col[0]; data[idx+1] = col[1]; data[idx+2] = col[2]; data[idx+3] = 255;
      }
    }
    ctx.putImageData(imgData, 0, 0);
    if (progress < total) {
      state.lastRenderTime = performance.now();
      requestAnimationFrame(renderStep);
    }
  }
  // Start progressive
  requestAnimationFrame(renderStep);
}

/* ==== UTILS ====
 * Utility helpers for math and rendering
 */
function updateHud() {
  const fpsEl = document.getElementById('fps');
  const iterEl = document.getElementById('iter');
  const zoomEl = document.getElementById('zoom');
  const viewEl = document.getElementById('view-info');
  // FPS
  if (state.lastRenderTime) {
    state.frameCount++;
    const dt = performance.now() - state.lastRenderTime;
    if (dt > state.lastFrameTime) {
      state.lastFrameTime = dt;
      fpsEl.textContent = Math.round(1/dt*1000);
      fpsEl.style.display = dt < 0.05 ? 'inline' : 'block';
    }
  } else {
    state.lastFrameTime = performance.now();
  }
  // Iteration count
  iterEl.textContent = state.iterations;
  // Zoom display
  zoomEl.textContent = `${state.zoom.toExponential(2)}`;
  // View info
  viewEl.innerHTML = `Center: (${state.center.x.toFixed(6)}, ${state.center.y.toFixed(6)})<br>Scale: ${state.scale.toExponential(1)}x`
}

/* ==== DEBUG & INIT ====
 * Expose debug API for headless probing
 */
window.fractal = {
  iterationsAt: (x, y) => {
    // Returns iteration count at pixel (x,y)
    const w = canvas.width, h = canvas.height;
    const cx = state.center.x + (x - h/2) * state.scale / (w/2);
    const cy = state.center.y + (y - h/2) * state.zoom / (h/2);
    let zx = 0, zy = 0, iter = 0;
    while (iter < state.maxIter && !(zx*zx+zy*zy > 4)) {
      const zx2 = zx*zx - zy*zy + cx;
      const zy2 = 2*zx*zy + cy;
      zx = zx2; zy = zy2;
      iter++;
    }
    return iter;
  },
  setView: ({center, scale, palette, jc}) => {
    state.center = center || state.center;
    state.scale = scale || state.scale;
    state.palette = palette || state.palette;
    state.jc = jc || null;
    // Restore history if needed
    if (state.history.length) state.history[state.history.length-1] = {center, scale, palette, jc};
    updateHud();
  },
  saveState: () => JSON.stringify(state),
  loadState: (json) => {
    try {
      const s = JSON.parse(json);
      if (s.center) state.center = s.center;
      if (s.scale) state.scale = s.scale;
      if (s.palette) state.palette = s.palette;
      if (s.jc) state.jc = s.jc;
      updateHud();
    } catch(e) {}
  }
};

/* ==== START ====
 * Resize canvas on window resize
 */
window.addEventListener('resize', () => {
  canvas.width = CONFIG.CANVAS_WIDTH;
  canvas.height = CONFIG.CANVAS_HEIGHT;
  updateHud();
});
// Initial render
updateHud();
// Start progressive rendering loop
progressiveRender();

/* ==== END ==== */
</script>
</body>
</html>
<!-- agent-meta {"model":"liquid/lfm2-24b-a2b","provider":"lmstudio","persona":"basic","sessionId":"cf3d30ed-aab5-4414-bf0c-e2a7822b2192","tokensIn":1299,"tokensOut":5267,"tokensTotal":6566,"cost":0,"turns":1,"toolCalls":0,"failedToolCalls":0,"timestamp":"2026-08-01T04:48:54.411Z"} -->