← Fractal Explorer results

Fractal Explorer

Qwen 3.6 27b · typescript

Initial view of Fractal Explorer

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 55,732 bytes · SHA-256 295e23fd1ed8
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fractal Explorer — Mandelbrot &amp; Julia Sets</title>
<style>
/* ── css: layout ── */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { width: 100%; height: 100%; overflow: hidden; background: #000; font-family: 'Segoe UI', system-ui, sans-serif; }
#fractalCanvas { display: block; width: 100%; height: 100%; cursor: crosshair; }
#zoom-box { position: absolute; border: 2px solid rgba(255,255,255,0.8); border-style: dashed; pointer-events: none; display: none; }
#hud { position: absolute; top: 0; left: 0; right: 0; bottom: 0; pointer-events: none; transition: opacity 0.5s; }
#hud.hidden { opacity: 0; }
#hud > * { pointer-events: auto; }
#hud-top { display: flex; justify-content: space-between; padding: 12px; }
#info-panel { background: rgba(10,10,20,0.75); backdrop-filter: blur(8px); border-radius: 10px; padding: 10px 14px; color: #ccc; font-size: 13px; line-height: 1.6; border: 1px solid rgba(255,255,255,0.1); }
#info-panel div { white-space: nowrap; }
#controls-panel { display: flex; gap: 6px; }
#controls-panel button { background: rgba(10,10,20,0.75); backdrop-filter: blur(8px); color: #ddd; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; padding: 8px 14px; font-size: 16px; cursor: pointer; transition: background 0.2s; }
#controls-panel button:hover { background: rgba(40,40,80,0.85); }
#hud-bottom { position: absolute; bottom: 12px; left: 12px; right: 12px; display: flex; align-items: center; gap: 16px; }
#palette-bar { display: flex; gap: 6px; }
.palette-swatch { width: 28px; height: 28px; border-radius: 50%; cursor: pointer; border: 2px solid transparent; transition: border-color 0.2s, transform 0.15s; }
.palette-swatch.active { border-color: #fff; transform: scale(1.15); }
#slider-group { display: flex; gap: 16px; align-items: center; color: #ccc; font-size: 12px; background: rgba(10,10,20,0.75); backdrop-filter: blur(8px); padding: 6px 12px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.1); }
#slider-group input[type="range"] { width: 100px; accent-color: #6af; }
#julia-preview { position: absolute; top: 12px; right: 12px; width: 260px; height: 260px; border-radius: 10px; overflow: hidden; border: 2px solid rgba(255,255,255,0.2); box-shadow: 0 4px 20px rgba(0,0,0,0.6); }
#juliaCanvas { width: 100%; height: 100%; display: block; }
#bookmark-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.6); display: flex; align-items: center; justify-content: center; z-index: 100; }
#bookmark-inner { background: rgba(15,15,30,0.95); backdrop-filter: blur(10px); border-radius: 14px; padding: 24px; min-width: 300px; color: #ddd; border: 1px solid rgba(255,255,255,0.15); }
#bookmark-inner h3 { margin-bottom: 12px; }
#bookmark-list { max-height: 300px; overflow-y: auto; margin-bottom: 12px; }
.bookmark-item { padding: 8px 12px; margin: 4px 0; background: rgba(255,255,255,0.05); border-radius: 6px; cursor: pointer; font-size: 13px; display: flex; justify-content: space-between; align-items: center; }
.bookmark-item:hover { background: rgba(255,255,255,0.12); }
.bookmark-item .del { color: #f66; cursor: pointer; font-size: 16px; }
#btn-close-bookmarks { background: rgba(60,60,100,0.8); color: #ddd; border: 1px solid rgba(255,255,255,0.15); border-radius: 8px; padding: 8px 16px; cursor: pointer; }
</style>
</head>
<body>
<canvas id="fractalCanvas"></canvas>
<div id="hud">
  <div id="hud-top">
    <div id="info-panel">
      <div id="zoom-level">×1.0</div>
      <div id="center-coords">Re: 0.0, Im: 0.0</div>
      <div id="iter-info">Iterations: 100</div>
      <div id="fps-display">FPS: --</div>
    </div>
    <div id="controls-panel">
      <button id="btn-reset" title="Reset View">⟲</button>
      <button id="btn-back" title="Zoom Back">←</button>
      <button id="btn-julia" title="Julia Mode (J)">J</button>
      <button id="btn-bookmark" title="Save Bookmark">★</button>
    </div>
  </div>
  <div id="hud-bottom">
    <div id="palette-bar"></div>
    <div id="slider-group">
      <label>Iterations: <input type="range" id="iter-slider" min="50" max="5000" value="200"></label>
      <label>Color Offset: <input type="range" id="color-slider" min="0" max="1000" value="0"></label>
    </div>
  </div>
</div>
<div id="zoom-box"></div>
<div id="bookmark-modal" style="display:none">
  <div id="bookmark-inner">
    <h3>Bookmarks</h3>
    <div id="bookmark-list"></div>
    <button id="btn-close-bookmarks">Close</button>
  </div>
</div>
<div id="julia-preview" style="display:none">
  <canvas id="juliaCanvas"></canvas>
</div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./config":"src/config.ts","./state":"src/state.ts","./math":"src/math.ts","./renderer":"src/renderer.ts","./input":"src/input.ts","./ui":"src/ui.ts"},"src/state.ts":{"./config":"src/config.ts"},"src/math.ts":{"./config":"src/config.ts","./state":"src/state.ts"},"src/color.ts":{"./config":"src/config.ts"},"src/renderer.ts":{"./config":"src/config.ts","./state":"src/state.ts","./math":"src/math.ts","./color":"src/color.ts","./workerCode":"src/workerCode.ts"},"src/input.ts":{"./config":"src/config.ts","./state":"src/state.ts","./math":"src/math.ts"},"src/ui.ts":{"./config":"src/config.ts","./state":"src/state.ts"}};
function __require(id) {
  if (__cache[id]) return __cache[id].exports;
  var module = __cache[id] = { exports: {} };
  var factory = __mods[id];
  if (!factory) throw new Error("Module not found: " + id);
  factory(module.exports, function (spec) {
    var target = (__map[id] && __map[id][spec]) || spec;
    return __require(target);
  }, module);
  return module.exports;
}

// ── module: src/main.ts ──
__mods["src/main.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
// Entry point: wire up all systems
const config_1 = require("./config");
const state_1 = require("./state");
const math_1 = require("./math");
const renderer_1 = require("./renderer");
const input_1 = require("./input");
const ui_1 = require("./ui");
// Julia preview canvas
let juliaCanvas;
let juliaCtx;
let juliaImageData;
let juliaPixelBuffer;
function main() {
    const canvas = document.getElementById('fractalCanvas');
    const zoomBox = document.getElementById('zoom-box');
    // Try to load state from URL hash
    if (location.hash) {
        (0, state_1.hashToState)(location.hash);
    }
    // Init systems
    (0, renderer_1.initRenderer)(canvas);
    (0, ui_1.initUI)();
    (0, input_1.initInput)(canvas, zoomBox, () => { });
    // Init Julia preview
    juliaCanvas = document.getElementById('juliaCanvas');
    juliaCtx = juliaCanvas.getContext('2d', { alpha: false });
    juliaCanvas.width = config_1.CONFIG.juliaPreviewSize;
    juliaCanvas.height = config_1.CONFIG.juliaPreviewSize;
    juliaImageData = juliaCtx.createImageData(config_1.CONFIG.juliaPreviewSize, config_1.CONFIG.juliaPreviewSize);
    juliaPixelBuffer = juliaImageData.data;
    // Wire up callbacks
    (0, input_1.setOnRender)(() => {
        if ((0, renderer_1.isRendering)())
            (0, renderer_1.cancelRender)();
        // Auto-update maxIter based on zoom depth
        state_1.viewState.maxIter = (0, state_1.calcMaxIter)();
        (0, ui_1.updateHUD)();
        (0, renderer_1.startRender)();
        (0, input_1.resetHudTimer)();
    });
    (0, ui_1.setOnRecolor)(() => {
        (0, renderer_1.recolor)();
        (0, input_1.resetHudTimer)();
    });
    (0, ui_1.setOnReset)(() => {
        const ds = (0, state_1.defaultState)();
        Object.assign(state_1.viewState, ds);
        (0, ui_1.updateHUD)();
        (0, renderer_1.startRender)();
    });
    (0, ui_1.setOnBack)(() => {
        if ((0, state_1.popZoomHistory)()) {
            (0, ui_1.updateHUD)();
            (0, renderer_1.startRender)();
        }
    });
    (0, ui_1.setOnToggleJulia)(() => {
        if (state_1.viewState.setMode === 'mandelbrot') {
            state_1.viewState.setMode = 'julia';
            (0, ui_1.showJuliaPreview)(false);
        }
        else {
            state_1.viewState.setMode = 'mandelbrot';
            (0, ui_1.showJuliaPreview)(true);
        }
        (0, ui_1.updateJuliaButton)();
        (0, ui_1.updateHUD)();
        (0, renderer_1.startRender)();
    });
    (0, input_1.setOnJuliaCChanged)(() => {
        // Render Julia preview with new c-value
        renderJuliaPreview();
    });
    (0, input_1.setOnHover)((x, y) => {
        // Live Julia preview on hover in Mandelbrot mode
        if (state_1.viewState.setMode === 'mandelbrot' && !isDragging()) {
            const rect = canvas.getBoundingClientRect();
            const mx = x - rect.left;
            const my = y - rect.top;
            const c = screenToComplexFromInput(mx, my, canvas.clientWidth, canvas.clientHeight);
            state_1.viewState.juliaCRe = c.re;
            state_1.viewState.juliaCIm = c.im;
            renderJuliaPreview();
        }
    });
    // Show HUD initially
    (0, input_1.showHud)();
    // Show Julia preview by default
    (0, ui_1.showJuliaPreview)(true);
    (0, ui_1.updateJuliaButton)();
    // Initial Julia preview render
    renderJuliaPreview();
    // Initial render
    (0, renderer_1.startRender)();
    // Expose debug interface
    window.fractal = {
        iterationsAt: (re, im) => (0, math_1.iterationsAt)(re, im),
        setView: (params) => {
            if (params.re !== undefined)
                state_1.viewState.centerRe = params.re;
            if (params.im !== undefined)
                state_1.viewState.centerIm = params.im;
            if (params.scale !== undefined)
                state_1.viewState.scale = params.scale;
            state_1.viewState.maxIter = (0, state_1.calcMaxIter)();
            (0, ui_1.updateHUD)();
            (0, renderer_1.startRender)();
        },
        getState: () => ({ ...state_1.viewState }),
        magnification: () => {
            return state_1.viewState.scale / 150;
        },
    };
    // Handle resize
    window.addEventListener('resize', () => {
        if ((0, renderer_1.isRendering)())
            (0, renderer_1.cancelRender)();
        const newCanvas = document.getElementById('fractalCanvas');
        (0, renderer_1.initRenderer)(newCanvas);
        (0, renderer_1.startRender)();
    });
    // Auto-animate color offset if configured
    if (config_1.CONFIG.colorAnimSpeed > 0) {
        setInterval(() => {
            state_1.viewState.colorOffset = (state_1.viewState.colorOffset + config_1.CONFIG.colorAnimSpeed) % 1000;
            (0, renderer_1.recolor)();
        }, 50);
    }
    // FPS update loop
    setInterval(() => {
        const fps = (0, renderer_1.updateFps)();
        (0, ui_1.updateFps)(fps);
    }, 1000);
}
// Track dragging state for hover preview
let dragging = false;
let dragStartX = 0, dragStartY = 0;
const origCanvas = document.getElementById('fractalCanvas');
origCanvas.addEventListener('mousedown', (e) => {
    dragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;
});
origCanvas.addEventListener('mouseup', (e) => {
    const dx = e.clientX - dragStartX;
    const dy = e.clientY - dragStartY;
    if (Math.sqrt(dx * dx + dy * dy) < 5) {
        dragging = false;
    }
    else {
        dragging = false;
    }
});
origCanvas.addEventListener('mouseleave', () => { dragging = false; });
function isDragging() { return dragging; }
// Screen to complex (duplicated here since input.ts uses the one from math.ts)
function screenToComplexFromInput(sx, sy, canvasW, canvasH) {
    const halfW = canvasW / (2 * state_1.viewState.scale);
    const halfH = canvasH / (2 * state_1.viewState.scale);
    const re = state_1.viewState.centerRe + (sx - canvasW / 2) / state_1.viewState.scale;
    const im = state_1.viewState.centerIm - (sy - canvasH / 2) / state_1.viewState.scale;
    return { re, im };
}
// Julia preview rendering (fast, no workers needed for small canvas)
function renderJuliaPreview() {
    const size = config_1.CONFIG.juliaPreviewSize;
    const maxIter = config_1.CONFIG.juliaPreviewIter;
    const scale = size / 3; // view range of -1.5 to 1.5
    const paletteIndex = state_1.viewState.palette;
    const colorOffset = state_1.viewState.colorOffset;
    // Build small LUT
    const lutSize = 512;
    const lut = buildSmallLUT(paletteIndex, colorOffset, lutSize);
    const halfW = size / (2 * scale);
    const halfH = size / (2 * scale);
    const dRe = (2 * halfW) / size;
    const dIm = (2 * halfH) / size;
    for (let row = 0; row < size; row++) {
        const im = halfH - row * dIm;
        let re = -halfW;
        for (let col = 0; col < size; col++) {
            const smoothIter = juliaIterFast(re, im, state_1.viewState.juliaCRe, state_1.viewState.juliaCIm, maxIter);
            const pidx = (row * size + col) * 4;
            if (smoothIter >= maxIter) {
                juliaPixelBuffer[pidx] = 0;
                juliaPixelBuffer[pidx + 1] = 0;
                juliaPixelBuffer[pidx + 2] = 0;
                juliaPixelBuffer[pidx + 3] = 255;
            }
            else {
                const normIter = (smoothIter % maxIter) / maxIter;
                const lutIdx = Math.floor(normIter * lutSize) % lutSize;
                const lutOff = lutIdx * 4;
                juliaPixelBuffer[pidx] = lut[lutOff];
                juliaPixelBuffer[pidx + 1] = lut[lutOff + 1];
                juliaPixelBuffer[pidx + 2] = lut[lutOff + 2];
                juliaPixelBuffer[pidx + 3] = 255;
            }
            re += dRe;
        }
    }
    juliaCtx.putImageData(juliaImageData, 0, 0);
}
function juliaIterFast(zr, zi, cr, ci, maxIter) {
    let zr2 = zr * zr;
    let zi2 = zi * zi;
    for (let i = 0; i < maxIter; i++) {
        zi = 2 * zr * zi + ci;
        zr = zr2 - zi2 + cr;
        zr2 = zr * zr;
        zi2 = zi * zi;
        if (zr2 + zi2 > 4) {
            const logZn = Math.log(zr2 + zi2) * 0.5;
            return i + 1 - Math.log(logZn) / Math.log(2);
        }
    }
    return maxIter;
}
function buildSmallLUT(paletteIndex, colorOffset, size) {
    const palette = config_1.PALETTES[paletteIndex % config_1.PALETTES.length];
    const stops = palette[1];
    const lut = new Uint8Array(size * 4);
    const offset = colorOffset / 1000;
    for (let i = 0; i < size; i++) {
        const t = ((i / size) + offset) % 1.0;
        const [r, g, b] = interpolateStops(stops, t);
        const idx = i * 4;
        lut[idx] = r;
        lut[idx + 1] = g;
        lut[idx + 2] = b;
        lut[idx + 3] = 255;
    }
    return lut;
}
function interpolateStops(stops, t) {
    for (let i = 0; i < stops.length - 1; i++) {
        if (t >= stops[i][0] && t <= stops[i + 1][0]) {
            const t0 = stops[i][0];
            const t1 = stops[i + 1][0];
            const range = t1 - t0;
            const frac = range > 0 ? (t - t0) / range : 0;
            const r = Math.round(stops[i][1] + frac * (stops[i + 1][1] - stops[i][1]));
            const g = Math.round(stops[i][2] + frac * (stops[i + 1][2] - stops[i][2]));
            const b = Math.round(stops[i][3] + frac * (stops[i + 1][3] - stops[i][3]));
            return [r, g, b];
        }
    }
    const last = stops[stops.length - 1];
    return [last[1], last[2], last[3]];
}
main();
};

// ── module: src/config.ts ──
__mods["src/config.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PALETTES = exports.CONFIG = void 0;
// Tunable constants for the fractal explorer
exports.CONFIG = {
    // Iteration settings
    maxIterBase: 150,
    maxIterScale: 80, // added per log2(magnification)
    maxIterCap: 15000,
    // Zoom settings
    zoomFactor: 0.5, // how much to zoom on wheel (fraction of current view)
    wheelZoomSpeed: 1.15, // multiplier per wheel tick
    maxMagnification: 1e14, // double-precision limit
    zoomBoxMinSize: 5, // minimum drag box size in px
    // Progressive rendering
    progressivePasses: 4, // start at 1/16 res, end at full
    tileSize: 256, // worker tile size (larger = fewer tiles, faster startup)
    // Julia preview
    juliaPreviewSize: 260,
    juliaPreviewIter: 200,
    // HUD
    hudHideDelay: 0, // ms before HUD auto-hides (0 = never hide)
    // Worker count
    workerCount: 0, // 0 = auto (navigator.hardwareConcurrency or 4)
    // Color offset animation
    colorAnimSpeed: 0, // 0 = off, >0 = auto-animate color offset
};
exports.PALETTES = [
    ["Fire", [
            [0.0, 0, 0, 0],
            [0.15, 128, 0, 0],
            [0.3, 255, 80, 0],
            [0.5, 255, 200, 0],
            [0.7, 255, 255, 100],
            [0.85, 200, 255, 255],
            [1.0, 255, 255, 255],
        ]],
    ["Ocean", [
            [0.0, 0, 0, 0],
            [0.1, 0, 10, 60],
            [0.25, 0, 40, 120],
            [0.4, 0, 100, 180],
            [0.55, 0, 180, 220],
            [0.7, 80, 220, 255],
            [0.85, 180, 240, 255],
            [1.0, 255, 255, 255],
        ]],
    ["Monochrome", [
            [0.0, 0, 0, 0],
            [0.2, 30, 30, 30],
            [0.4, 80, 80, 80],
            [0.6, 150, 150, 150],
            [0.8, 210, 210, 210],
            [1.0, 255, 255, 255],
        ]],
    ["Psychedelic", [
            [0.0, 128, 0, 128],
            [0.167, 128, 128, 0],
            [0.333, 0, 128, 128],
            [0.5, 0, 128, 0],
            [0.667, 128, 0, 128],
            [0.833, 128, 128, 0],
            [1.0, 128, 0, 128],
        ]],
    ["Electric", [
            [0.0, 0, 0, 0],
            [0.1, 20, 0, 80],
            [0.25, 60, 0, 200],
            [0.4, 120, 0, 255],
            [0.55, 180, 100, 255],
            [0.7, 220, 200, 255],
            [0.85, 255, 255, 220],
            [1.0, 255, 255, 255],
        ]],
];
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.zoomHistory = exports.viewState = exports.defaultState = void 0;
exports.pushZoomHistory = pushZoomHistory;
exports.popZoomHistory = popZoomHistory;
exports.magnification = magnification;
exports.calcMaxIter = calcMaxIter;
exports.loadBookmarks = loadBookmarks;
exports.saveBookmarks = saveBookmarks;
exports.addBookmark = addBookmark;
exports.restoreBookmark = restoreBookmark;
exports.deleteBookmark = deleteBookmark;
exports.stateToHash = stateToHash;
exports.hashToState = hashToState;
// View state: center, scale, palette, iterations, set type, Julia c-value
const config_1 = require("./config");
const defaultState = () => ({
    centerRe: -0.5,
    centerIm: 0,
    scale: 150,
    palette: 0,
    maxIter: config_1.CONFIG.maxIterBase,
    setMode: 'mandelbrot',
    juliaCRe: -0.7269,
    juliaCIm: 0.1889,
    colorOffset: 0,
});
exports.defaultState = defaultState;
exports.viewState = (0, exports.defaultState)();
// Zoom history stack for back navigation
exports.zoomHistory = [];
function pushZoomHistory() {
    exports.zoomHistory.push({
        re: exports.viewState.centerRe,
        im: exports.viewState.centerIm,
        scale: exports.viewState.scale,
    });
    if (exports.zoomHistory.length > 100)
        exports.zoomHistory.shift();
}
function popZoomHistory() {
    if (exports.zoomHistory.length === 0)
        return false;
    const prev = exports.zoomHistory.pop();
    exports.viewState.centerRe = prev.re;
    exports.viewState.centerIm = prev.im;
    exports.viewState.scale = prev.scale;
    return true;
}
function magnification() {
    return exports.viewState.scale / 150;
}
// Auto-calculate max iterations based on zoom depth
function calcMaxIter() {
    const mag = magnification();
    const logMag = Math.log2(Math.max(mag, 1));
    return Math.min(config_1.CONFIG.maxIterBase + Math.floor(config_1.CONFIG.maxIterScale * Math.log2(Math.max(logMag, 1))), config_1.CONFIG.maxIterCap);
}
// Bookmarks
function loadBookmarks() {
    try {
        const raw = localStorage.getItem('fractal-bookmarks');
        return raw ? JSON.parse(raw) : [];
    }
    catch {
        return [];
    }
}
function saveBookmarks(bookmarks) {
    localStorage.setItem('fractal-bookmarks', JSON.stringify(bookmarks));
}
function addBookmark(name) {
    const bookmarks = loadBookmarks();
    bookmarks.push({ name, time: Date.now(), state: { ...exports.viewState } });
    saveBookmarks(bookmarks);
}
function restoreBookmark(index) {
    const bookmarks = loadBookmarks();
    if (index < 0 || index >= bookmarks.length)
        return false;
    exports.viewState = { ...bookmarks[index].state };
    return true;
}
function deleteBookmark(index) {
    const bookmarks = loadBookmarks();
    bookmarks.splice(index, 1);
    saveBookmarks(bookmarks);
}
// URL hash state
function stateToHash() {
    const s = exports.viewState;
    return `#${btoa(JSON.stringify({
        re: s.centerRe, im: s.centerIm, sc: s.scale,
        p: s.palette, mi: s.maxIter, sm: s.setMode,
        jcr: s.juliaCRe, jci: s.juliaCIm, co: s.colorOffset
    }))}`;
}
function hashToState(hash) {
    try {
        const decoded = atob(hash.replace('#', ''));
        const obj = JSON.parse(decoded);
        exports.viewState.centerRe = obj.re ?? exports.viewState.centerRe;
        exports.viewState.centerIm = obj.im ?? exports.viewState.centerIm;
        exports.viewState.scale = obj.sc ?? exports.viewState.scale;
        exports.viewState.palette = obj.p ?? exports.viewState.palette;
        exports.viewState.maxIter = obj.mi ?? exports.viewState.maxIter;
        exports.viewState.setMode = obj.sm ?? exports.viewState.setMode;
        exports.viewState.juliaCRe = obj.jcr ?? exports.viewState.juliaCRe;
        exports.viewState.juliaCIm = obj.jci ?? exports.viewState.juliaCIm;
        exports.viewState.colorOffset = obj.co ?? exports.viewState.colorOffset;
        return true;
    }
    catch {
        return false;
    }
}
};

// ── module: src/math.ts ──
__mods["src/math.ts"] = function (exports, require, module) {
"use strict";
// Fractal iteration math — allocation-free core loop
Object.defineProperty(exports, "__esModule", { value: true });
exports.mandelbrotIter = mandelbrotIter;
exports.juliaIter = juliaIter;
exports.computeRegion = computeRegion;
exports.iterationsAt = iterationsAt;
exports.iterationsAtWith = iterationsAtWith;
exports.screenToComplex = screenToComplex;
const state_1 = require("./state");
// Compute the smooth iteration count for a single point
// Returns the iteration count (integer) and smooth value
// For Mandelbrot: z = 0, c = (re, im)
// For Julia: z = (re, im), c = (juliaCRe, juliaCIm)
function mandelbrotIter(re, im, maxIter) {
    let zr = 0, zi = 0;
    let zr2 = 0, zi2 = 0;
    for (let i = 0; i < maxIter; i++) {
        // z = z*z + c  =>  (zr + izi)^2 + c = (zr^2 - zi^2 + cr) + i(2*zr*zi + ci)
        zi = 2 * zr * zi + im;
        zr = zr2 - zi2 + re;
        zr2 = zr * zr;
        zi2 = zi * zi;
        if (zr2 + zi2 > 4) {
            // Smooth iteration count: n + 1 - log2(log|z|)
            const logZn = Math.log(zr2 + zi2) * 0.5;
            return i + 1 - Math.log(logZn) / Math.log(2);
        }
    }
    return maxIter; // interior point
}
// Julia set iteration
function juliaIter(zr, zi, cr, ci, maxIter) {
    let zr2 = zr * zr;
    let zi2 = zi * zi;
    for (let i = 0; i < maxIter; i++) {
        zi = 2 * zr * zi + ci;
        zr = zr2 - zi2 + cr;
        zr2 = zr * zr;
        zi2 = zi * zi;
        if (zr2 + zi2 > 4) {
            const logZn = Math.log(zr2 + zi2) * 0.5;
            return i + 1 - Math.log(logZn) / Math.log(2);
        }
    }
    return maxIter;
}
// Compute iterations for a rectangular region
// result is Float64Array of smooth iteration counts
function computeRegion(result, offsetX, offsetY, width, height, centerRe, centerIm, scale, maxIter, setMode, juliaCRe, juliaCIm) {
    const pixelsPerUnit = scale;
    const halfW = width / (2 * pixelsPerUnit);
    const halfH = height / (2 * pixelsPerUnit);
    const reStart = centerRe - halfW + (offsetX / pixelsPerUnit);
    const imStart = centerIm + halfH - (offsetY / pixelsPerUnit); // y goes up in complex plane
    const dRe = 1 / pixelsPerUnit;
    const dIm = -1 / pixelsPerUnit;
    let idx = 0;
    for (let row = 0; row < height; row++) {
        const im = imStart + row * dIm;
        let re = reStart;
        for (let col = 0; col < width; col++) {
            if (setMode === 'mandelbrot') {
                result[idx] = mandelbrotIter(re, im, maxIter);
            }
            else {
                result[idx] = juliaIter(re, im, juliaCRe, juliaCIm, maxIter);
            }
            re += dRe;
            idx++;
        }
    }
}
// Debug: compute iterations at a specific point
function iterationsAt(re, im) {
    const maxIter = (0, state_1.calcMaxIter)();
    return mandelbrotIter(re, im, maxIter);
}
// Debug: compute iterations at a specific point with explicit maxIter
function iterationsAtWith(re, im, maxIter) {
    return mandelbrotIter(re, im, maxIter);
}
// Convert screen coords to complex plane coords
function screenToComplex(sx, sy, canvasW, canvasH) {
    const halfW = canvasW / (2 * state_1.viewState.scale);
    const halfH = canvasH / (2 * state_1.viewState.scale);
    const re = state_1.viewState.centerRe + (sx - canvasW / 2) / state_1.viewState.scale;
    const im = state_1.viewState.centerIm - (sy - canvasH / 2) / state_1.viewState.scale;
    return { re, im };
}
};

// ── module: src/color.ts ──
__mods["src/color.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPaletteLUT = getPaletteLUT;
exports.colorizeRegion = colorizeRegion;
exports.colorizeFull = colorizeFull;
exports.invalidateLUTCache = invalidateLUTCache;
// Color palette interpolation and pixel coloring
const config_1 = require("./config");
// Cached LUT — only rebuild when palette or offset changes
let cachedLUT = null;
let cachedPaletteIdx = -1;
let cachedOffset = -1;
// Pre-compute a LUT (look-up table) for a palette
// Returns Uint8Array of size 2048*4 (RGBA) for smooth interpolation
function getPaletteLUT(paletteIndex, colorOffset) {
    if (cachedLUT && cachedPaletteIdx === paletteIndex && cachedOffset === colorOffset) {
        return cachedLUT;
    }
    return buildPaletteLUT(paletteIndex, colorOffset);
}
function buildPaletteLUT(paletteIndex, colorOffset) {
    const idx = ((paletteIndex % config_1.PALETTES.length) + config_1.PALETTES.length) % config_1.PALETTES.length;
    const palette = config_1.PALETTES[idx];
    const stops = palette[1];
    if (!stops || stops.length === 0) {
        // Fallback: simple grayscale
        const lutSize = 2048;
        const lut = new Uint8Array(lutSize * 4);
        for (let i = 0; i < lutSize; i++) {
            const v = Math.round((i / lutSize) * 255);
            const o = i * 4;
            lut[o] = v;
            lut[o + 1] = v;
            lut[o + 2] = v;
            lut[o + 3] = 255;
        }
        return lut;
    }
    const lutSize = 2048;
    const lut = new Uint8Array(lutSize * 4);
    const offset = colorOffset / 1000;
    for (let i = 0; i < lutSize; i++) {
        const t = ((i / lutSize) + offset) % 1.0;
        const [r, g, b] = interpolateStops(stops, t);
        const o = i * 4;
        lut[o] = r;
        lut[o + 1] = g;
        lut[o + 2] = b;
        lut[o + 3] = 255;
    }
    cachedLUT = lut;
    cachedPaletteIdx = paletteIndex;
    cachedOffset = colorOffset;
    return lut;
}
// Bilinear interpolation between palette stops
function interpolateStops(stops, t) {
    if (!stops || stops.length < 2) {
        return [128, 128, 128];
    }
    // Clamp t to [0, 1]
    t = Math.max(0, Math.min(1, t));
    for (let i = 0; i < stops.length - 1; i++) {
        const s0 = stops[i];
        const s1 = stops[i + 1];
        if (!s0 || !s1)
            continue;
        if (t >= s0[0] && t <= s1[0]) {
            const range = s1[0] - s0[0];
            const frac = range > 0 ? (t - s0[0]) / range : 0;
            const r = Math.round(s0[1] + frac * (s1[1] - s0[1]));
            const g = Math.round(s0[2] + frac * (s1[2] - s0[2]));
            const b = Math.round(s0[3] + frac * (s1[3] - s0[3]));
            return [r, g, b];
        }
    }
    // Fallback: use last stop
    const last = stops[stops.length - 1];
    if (last)
        return [last[1], last[2], last[3]];
    return [128, 128, 128];
}
// Colorize a region of pixel data from iteration counts
function colorizeRegion(pixels, iterData, paletteLUT, width, height, offsetX, offsetY, maxIter) {
    const halfLut = paletteLUT.length / 4;
    for (let row = 0; row < height; row++) {
        for (let col = 0; col < width; col++) {
            const iterIdx = row * width + col;
            const smoothIter = iterData[iterIdx];
            const pixelX = offsetX + col;
            const pixelY = offsetY + row;
            const pixelIdx = (pixelY * width + pixelX) * 4;
            if (smoothIter >= maxIter) {
                pixels[pixelIdx] = 0;
                pixels[pixelIdx + 1] = 0;
                pixels[pixelIdx + 2] = 0;
                pixels[pixelIdx + 3] = 255;
            }
            else {
                const normIter = ((smoothIter % maxIter) + maxIter) / maxIter;
                const lutIdx = Math.floor(normIter * halfLut) % halfLut;
                const lutOff = lutIdx * 4;
                pixels[pixelIdx] = paletteLUT[lutOff];
                pixels[pixelIdx + 1] = paletteLUT[lutOff + 1];
                pixels[pixelIdx + 2] = paletteLUT[lutOff + 2];
                pixels[pixelIdx + 3] = 255;
            }
        }
    }
}
function colorizeFull(pixels, iterData, paletteLUT, width, height, maxIter) {
    colorizeRegion(pixels, iterData, paletteLUT, width, height, 0, 0, maxIter);
}
// Invalidate LUT cache (call when palette or offset changes)
function invalidateLUTCache() {
    cachedLUT = null;
    cachedPaletteIdx = -1;
    cachedOffset = -1;
}
};

// ── module: src/workerCode.ts ──
__mods["src/workerCode.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.workerSource = void 0;
// Web Worker code for fractal computation (inline via Blob URL)
exports.workerSource = `
function mandelbrotIter(re, im, maxIter) {
  var zr = 0, zi = 0, zr2 = 0, zi2 = 0;
  for (var i = 0; i < maxIter; i++) {
    zi = 2 * zr * zi + im;
    zr = zr2 - zi2 + re;
    zr2 = zr * zr;
    zi2 = zi * zi;
    if (zr2 + zi2 > 4) {
      var logZn = Math.log(zr2 + zi2) * 0.5;
      return i + 1 - Math.log(logZn) / Math.log(2);
    }
  }
  return maxIter;
}

function juliaIter(zr, zi, cr, ci, maxIter) {
  var zr2 = zr * zr, zi2 = zi * zi;
  for (var i = 0; i < maxIter; i++) {
    zi = 2 * zr * zi + ci;
    zr = zr2 - zi2 + cr;
    zr2 = zr * zr;
    zi2 = zi * zi;
    if (zr2 + zi2 > 4) {
      var logZn = Math.log(zr2 + zi2) * 0.5;
      return i + 1 - Math.log(logZn) / Math.log(2);
    }
  }
  return maxIter;
}

self.onmessage = function(e) {
  var msg = e.data;
  if (msg.type === 'compute') {
    var { tileX, tileY, tileW, tileH, canvasW, canvasH, centerRe, centerIm, scale, maxIter, setMode, juliaCRe, juliaCIm } = msg;
    var result = new Float64Array(tileW * tileH);
    var pixelsPerUnit = scale;
    // Compute the complex plane region for this tile
    // The canvas maps: left edge = centerRe - canvasW/(2*scale), right edge = centerRe + canvasW/(2*scale)
    // top edge = centerIm + canvasH/(2*scale), bottom edge = centerIm - canvasH/(2*scale)
    var reStart = centerRe - canvasW / (2 * pixelsPerUnit) + tileX / pixelsPerUnit;
    var imStart = centerIm + canvasH / (2 * pixelsPerUnit) - tileY / pixelsPerUnit;
    var dRe = 1 / pixelsPerUnit;
    var dIm = -1 / pixelsPerUnit;
    var idx = 0;
    for (var row = 0; row < tileH; row++) {
      var im = imStart + row * dIm;
      var re = reStart;
      for (var col = 0; col < tileW; col++) {
        if (setMode === 'mandelbrot') {
          result[idx] = mandelbrotIter(re, im, maxIter);
        } else {
          result[idx] = juliaIter(re, im, juliaCRe, juliaCIm, maxIter);
        }
        re += dRe;
        idx++;
      }
    }
    self.postMessage({ type: 'tileResult', token: msg.token, tileX, tileY, tileW, tileH, result }, [result.buffer]);
  }
};
`;
};

// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initRenderer = initRenderer;
exports.startRender = startRender;
exports.cancelRender = cancelRender;
exports.recolor = recolor;
exports.fastRender = fastRender;
exports.updateFps = updateFps;
exports.isRendering = isRendering;
exports.destroy = destroy;
// Renderer: manages canvas, workers, tile-based rendering with progressive display
const config_1 = require("./config");
const state_1 = require("./state");
const math_1 = require("./math");
const color_1 = require("./color");
const workerCode_1 = require("./workerCode");
let canvas;
let ctx;
let imageData;
let pixelBuffer;
let iterBuffer;
// Worker management
let workers = [];
let renderToken = 0;
let pendingTiles = 0;
let renderComplete = true;
let renderCallback;
// FPS tracking
let frameCount = 0;
let lastFpsTime = performance.now();
let currentFps = 0;
let tileQueue = [];
let freeWorkers = [];
function initRenderer(canvasEl) {
    canvas = canvasEl;
    ctx = canvas.getContext('2d', { alpha: false });
    resizeCanvas();
    setupWorkers();
}
function resizeCanvas() {
    const dpr = Math.min(window.devicePixelRatio || 1, 2);
    const w = Math.floor(window.innerWidth * dpr);
    const h = Math.floor(window.innerHeight * dpr);
    canvas.width = w;
    canvas.height = h;
    canvas.style.width = window.innerWidth + 'px';
    canvas.style.height = window.innerHeight + 'px';
    imageData = ctx.createImageData(canvas.width, canvas.height);
    pixelBuffer = imageData.data;
    iterBuffer = new Float64Array(canvas.width * canvas.height);
}
function setupWorkers() {
    workers.forEach(w => w.terminate());
    workers = [];
    freeWorkers = [];
    const count = Math.max(2, config_1.CONFIG.workerCount || (navigator.hardwareConcurrency || 4));
    const blob = new Blob([workerCode_1.workerSource], { type: 'application/javascript' });
    const url = URL.createObjectURL(blob);
    for (let i = 0; i < count; i++) {
        const w = new Worker(url);
        w.onmessage = handleWorkerMessage;
        workers.push(w);
        freeWorkers.push(i);
    }
    URL.revokeObjectURL(url);
}
function handleWorkerMessage(e) {
    const msg = e.data;
    if (msg.type !== 'tileResult')
        return;
    if (msg.token !== renderToken)
        return;
    colorizeTile(msg.tileX, msg.tileY, msg.tileW, msg.tileH, msg.result);
    blitToCanvas();
    pendingTiles--;
    dispatchNextTile();
    if (pendingTiles === 0) {
        renderComplete = true;
        renderCallback?.();
        renderCallback = undefined;
    }
}
function colorizeTile(tx, ty, tw, th, result) {
    const paletteLUT = (0, color_1.getPaletteLUT)(state_1.viewState.palette, state_1.viewState.colorOffset);
    const maxIter = state_1.viewState.maxIter;
    const halfLut = paletteLUT.length / 4;
    const cw = canvas.width;
    for (let row = 0; row < th; row++) {
        const py = ty + row;
        for (let col = 0; col < tw; col++) {
            const smoothIter = result[row * tw + col];
            const px = tx + col;
            const pidx = (py * cw + px) * 4;
            if (smoothIter >= maxIter) {
                pixelBuffer[pidx] = 0;
                pixelBuffer[pidx + 1] = 0;
                pixelBuffer[pidx + 2] = 0;
                pixelBuffer[pidx + 3] = 255;
            }
            else {
                const normIter = ((smoothIter % maxIter) + maxIter) / maxIter;
                const lutOff = (Math.floor(normIter * halfLut) % halfLut) * 4;
                pixelBuffer[pidx] = paletteLUT[lutOff];
                pixelBuffer[pidx + 1] = paletteLUT[lutOff + 1];
                pixelBuffer[pidx + 2] = paletteLUT[lutOff + 2];
                pixelBuffer[pidx + 3] = 255;
            }
        }
    }
}
function dispatchNextTile() {
    if (freeWorkers.length === 0 || tileQueue.length === 0)
        return;
    const workerIdx = freeWorkers.pop();
    const task = tileQueue.shift();
    workers[workerIdx].postMessage({
        type: 'compute', token: renderToken,
        tileX: task.tx, tileY: task.ty, tileW: task.tw, tileH: task.th,
        canvasW: canvas.width, canvasH: canvas.height,
        centerRe: state_1.viewState.centerRe, centerIm: state_1.viewState.centerIm,
        scale: state_1.viewState.scale, maxIter: state_1.viewState.maxIter,
        setMode: state_1.viewState.setMode,
        juliaCRe: state_1.viewState.juliaCRe, juliaCIm: state_1.viewState.juliaCIm,
    });
}
function buildTileQueue() {
    const queue = [];
    const tileSize = config_1.CONFIG.tileSize;
    for (let ty = 0; ty < canvas.height; ty += tileSize) {
        for (let tx = 0; tx < canvas.width; tx += tileSize) {
            queue.push({
                tx, ty,
                tw: Math.min(tileSize, canvas.width - tx),
                th: Math.min(tileSize, canvas.height - ty),
            });
        }
    }
    // Progressive: render center tiles first for faster visual feedback
    const cx = canvas.width / 2;
    const cy = canvas.height / 2;
    queue.sort((a, b) => {
        const da = Math.abs(a.tx + a.tw / 2 - cx) + Math.abs(a.ty + a.th / 2 - cy);
        const db = Math.abs(b.tx + b.tw / 2 - cx) + Math.abs(b.ty + b.th / 2 - cy);
        return da - db;
    });
    return queue;
}
function blitToCanvas() {
    ctx.putImageData(imageData, 0, 0);
}
function startRender(onComplete) {
    cancelRender();
    renderToken++;
    renderComplete = false;
    for (let i = 0; i < pixelBuffer.length; i += 4) {
        pixelBuffer[i] = 8;
        pixelBuffer[i + 1] = 8;
        pixelBuffer[i + 2] = 12;
        pixelBuffer[i + 3] = 255;
    }
    blitToCanvas();
    renderCallback = onComplete;
    tileQueue = buildTileQueue();
    pendingTiles = tileQueue.length;
    while (freeWorkers.length > 0 && tileQueue.length > 0)
        dispatchNextTile();
}
function cancelRender() {
    renderToken++;
    renderComplete = true;
    pendingTiles = 0;
    tileQueue.length = 0;
    freeWorkers.length = 0;
    workers.forEach((_, i) => freeWorkers.push(i));
    renderCallback = undefined;
}
function recolor() {
    const paletteLUT = (0, color_1.getPaletteLUT)(state_1.viewState.palette, state_1.viewState.colorOffset);
    (0, color_1.colorizeFull)(pixelBuffer, iterBuffer, paletteLUT, canvas.width, canvas.height, state_1.viewState.maxIter);
    blitToCanvas();
}
function fastRender() {
    const maxIter = state_1.viewState.maxIter;
    (0, math_1.computeRegion)(iterBuffer, 0, 0, canvas.width, canvas.height, state_1.viewState.centerRe, state_1.viewState.centerIm, state_1.viewState.scale, maxIter, state_1.viewState.setMode, state_1.viewState.juliaCRe, state_1.viewState.juliaCIm);
    const paletteLUT = (0, color_1.getPaletteLUT)(state_1.viewState.palette, state_1.viewState.colorOffset);
    (0, color_1.colorizeFull)(pixelBuffer, iterBuffer, paletteLUT, canvas.width, canvas.height, maxIter);
    blitToCanvas();
}
function updateFps() {
    frameCount++;
    const now = performance.now();
    if (now - lastFpsTime >= 1000) {
        currentFps = Math.round(frameCount * 1000 / (now - lastFpsTime));
        frameCount = 0;
        lastFpsTime = now;
    }
    return currentFps;
}
function isRendering() { return !renderComplete; }
function destroy() { cancelRender(); workers.forEach(w => w.terminate()); workers = []; }
};

// ── module: src/input.ts ──
__mods["src/input.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initInput = initInput;
exports.showHud = showHud;
exports.setOnRender = setOnRender;
exports.setOnJuliaCChanged = setOnJuliaCChanged;
exports.setOnHover = setOnHover;
exports.setHudVisible = setHudVisible;
exports.resetHudTimer = resetHudTimer;
// Input handling: mouse, keyboard, wheel, drag-to-zoom
const config_1 = require("./config");
const state_1 = require("./state");
const math_1 = require("./math");
let canvas;
let zoomBox;
// Mouse state
let isDragging = false;
let dragStartX = 0, dragStartY = 0;
let dragMode = 'none';
let hasDragged = false;
// Pan state
let panStartX = 0, panStartY = 0;
let panStartRe = 0, panStartIm = 0;
function initInput(canvasEl, zoomBoxEl, onRender) {
    canvas = canvasEl;
    zoomBox = zoomBoxEl;
    canvas.addEventListener('mousedown', onMouseDown);
    canvas.addEventListener('mousemove', onMouseMove);
    canvas.addEventListener('mouseup', onMouseUp);
    canvas.addEventListener('wheel', onWheel, { passive: false });
    canvas.addEventListener('contextmenu', e => e.preventDefault());
    window.addEventListener('keydown', onKeyDown);
}
function onMouseDown(e) {
    if (e.button !== 0)
        return; // only left click
    isDragging = true;
    dragStartX = e.clientX;
    dragStartY = e.clientY;
    hasDragged = false;
    dragMode = 'zoom';
    zoomBox.style.left = e.clientX + 'px';
    zoomBox.style.top = e.clientY + 'px';
    zoomBox.style.width = '0px';
    zoomBox.style.height = '0px';
    zoomBox.style.display = 'block';
}
function onMouseMove(e) {
    if (!isDragging) {
        // Update crosshair / julia preview on hover
        onHover?.(e.clientX, e.clientY);
        return;
    }
    const dx = e.clientX - dragStartX;
    const dy = e.clientY - dragStartY;
    const dist = Math.sqrt(dx * dx + dy * dy);
    if (dist > 5) {
        hasDragged = true;
    }
    if (dragMode === 'zoom') {
        // Show zoom box
        const x = Math.min(dragStartX, e.clientX);
        const y = Math.min(dragStartY, e.clientY);
        const w = Math.abs(dx);
        const h = Math.abs(dy);
        zoomBox.style.left = x + 'px';
        zoomBox.style.top = y + 'px';
        zoomBox.style.width = w + 'px';
        zoomBox.style.height = h + 'px';
    }
}
function onMouseUp(e) {
    if (!isDragging)
        return;
    isDragging = false;
    zoomBox.style.display = 'none';
    const dx = e.clientX - dragStartX;
    const dy = e.clientY - dragStartY;
    const dist = Math.sqrt(dx * dx + dy * dy);
    if (dragMode === 'zoom' && dist >= config_1.CONFIG.zoomBoxMinSize) {
        // Zoom into the rectangle
        (0, state_1.pushZoomHistory)();
        const x1 = Math.min(dragStartX, e.clientX);
        const y1 = Math.min(dragStartY, e.clientY);
        const x2 = Math.max(dragStartX, e.clientX);
        const y2 = Math.max(dragStartY, e.clientY);
        const c1 = (0, math_1.screenToComplex)(x1, y1, canvas.clientWidth, canvas.clientHeight);
        const c2 = (0, math_1.screenToComplex)(x2, y2, canvas.clientWidth, canvas.clientHeight);
        state_1.viewState.centerRe = (c1.re + c2.re) / 2;
        state_1.viewState.centerIm = (c1.im + c2.im) / 2;
        // New scale: fit the rectangle
        const pixelW = x2 - x1;
        const pixelH = y2 - y1;
        const complexW = Math.abs(c2.re - c1.re);
        const complexH = Math.abs(c2.im - c1.im);
        // Use the smaller dimension to ensure the rectangle fills the view
        const newScale = Math.min(pixelW / complexW, pixelH / complexH);
        state_1.viewState.scale = newScale;
        checkMaxMagnification();
        onRender?.();
    }
    else if (dist < config_1.CONFIG.zoomBoxMinSize) {
        // Click without drag — pick Julia c-value in Mandelbrot mode
        if (state_1.viewState.setMode === 'mandelbrot') {
            const c = (0, math_1.screenToComplex)(e.clientX, e.clientY, canvas.clientWidth, canvas.clientHeight);
            state_1.viewState.juliaCRe = c.re;
            state_1.viewState.juliaCIm = c.im;
            onJuliaCChanged?.();
        }
    }
}
function onWheel(e) {
    e.preventDefault();
    (0, state_1.pushZoomHistory)();
    const rect = canvas.getBoundingClientRect();
    const mx = e.clientX - rect.left;
    const my = e.clientY - rect.top;
    // Zoom toward cursor
    const complexPoint = (0, math_1.screenToComplex)(mx, my, canvas.clientWidth, canvas.clientHeight);
    const factor = e.deltaY > 0 ? 1 / config_1.CONFIG.wheelZoomSpeed : config_1.CONFIG.wheelZoomSpeed;
    state_1.viewState.scale *= factor;
    // Adjust center so the cursor point stays fixed
    const newComplexPoint = (0, math_1.screenToComplex)(mx, my, canvas.clientWidth, canvas.clientHeight);
    state_1.viewState.centerRe += complexPoint.re - newComplexPoint.re;
    state_1.viewState.centerIm += complexPoint.im - newComplexPoint.im;
    checkMaxMagnification();
    onRender?.();
}
function onKeyDown(e) {
    const panAmount = 0.05 / state_1.viewState.scale;
    const zoomIn = 1.5;
    const zoomOut = 1 / 1.5;
    switch (e.key) {
        case 'ArrowLeft':
            state_1.viewState.centerRe -= panAmount;
            onRender?.();
            e.preventDefault();
            break;
        case 'ArrowRight':
            state_1.viewState.centerRe += panAmount;
            onRender?.();
            e.preventDefault();
            break;
        case 'ArrowUp':
            state_1.viewState.centerIm += panAmount;
            onRender?.();
            e.preventDefault();
            break;
        case 'ArrowDown':
            state_1.viewState.centerIm -= panAmount;
            onRender?.();
            e.preventDefault();
            break;
        case '+':
        case '=':
            (0, state_1.pushZoomHistory)();
            state_1.viewState.scale *= zoomIn;
            checkMaxMagnification();
            onRender?.();
            e.preventDefault();
            break;
        case '-':
        case '_':
            (0, state_1.pushZoomHistory)();
            state_1.viewState.scale *= zoomOut;
            onRender?.();
            e.preventDefault();
            break;
        case 'j':
        case 'J':
            toggleJuliaMode();
            e.preventDefault();
            break;
        case 'Escape':
            hideHud = false;
            showHud();
            e.preventDefault();
            break;
    }
}
function checkMaxMagnification() {
    const mag = (0, state_1.magnification)();
    if (mag > config_1.CONFIG.maxMagnification) {
        state_1.viewState.scale = 150 * config_1.CONFIG.maxMagnification;
    }
}
// Julia mode toggle
function toggleJuliaMode() {
    if (state_1.viewState.setMode === 'mandelbrot') {
        state_1.viewState.setMode = 'julia';
        onRender?.();
    }
    else {
        state_1.viewState.setMode = 'mandelbrot';
        onRender?.();
    }
}
// HUD visibility
let hideHud = false;
let hudHideTimer = null;
function showHud() {
    const hud = document.getElementById('hud');
    if (hud)
        hud.classList.remove('hidden');
    resetHudHideTimer();
}
function resetHudHideTimer() {
    if (hudHideTimer)
        clearTimeout(hudHideTimer);
    if (config_1.CONFIG.hudHideDelay > 0) {
        hudHideTimer = window.setTimeout(() => {
            const hud = document.getElementById('hud');
            if (hud)
                hud.classList.add('hidden');
        }, config_1.CONFIG.hudHideDelay);
    }
    else {
        const hud = document.getElementById('hud');
        if (hud)
            hud.classList.remove('hidden');
    }
}
// Callbacks
let onRender;
let onJuliaCChanged;
let onHover;
function setOnRender(cb) { onRender = cb; }
function setOnJuliaCChanged(cb) { onJuliaCChanged = cb; }
function setOnHover(cb) { onHover = cb; }
function setHudVisible(visible) {
    const hud = document.getElementById('hud');
    if (hud) {
        if (visible)
            hud.classList.remove('hidden');
        else
            hud.classList.add('hidden');
    }
}
function resetHudTimer() { resetHudHideTimer(); }
};

// ── module: src/ui.ts ──
__mods["src/ui.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initUI = initUI;
exports.updateHUD = updateHUD;
exports.updateFps = updateFps;
exports.showJuliaPreview = showJuliaPreview;
exports.updateJuliaButton = updateJuliaButton;
exports.setOnRender = setOnRender;
exports.setOnRecolor = setOnRecolor;
exports.setOnReset = setOnReset;
exports.setOnBack = setOnBack;
exports.setOnToggleJulia = setOnToggleJulia;
// UI: HUD updates, palette selector, bookmarks
const config_1 = require("./config");
const state_1 = require("./state");
// DOM references
let zoomLevelEl;
let centerCoordsEl;
let iterInfoEl;
let fpsDisplayEl;
let iterSlider;
let colorSlider;
let paletteBar;
let bookmarkModal;
let bookmarkList;
let juliaPreview;
function initUI() {
    zoomLevelEl = document.getElementById('zoom-level');
    centerCoordsEl = document.getElementById('center-coords');
    iterInfoEl = document.getElementById('iter-info');
    fpsDisplayEl = document.getElementById('fps-display');
    iterSlider = document.getElementById('iter-slider');
    colorSlider = document.getElementById('color-slider');
    paletteBar = document.getElementById('palette-bar');
    bookmarkModal = document.getElementById('bookmark-modal');
    bookmarkList = document.getElementById('bookmark-list');
    juliaPreview = document.getElementById('julia-preview');
    // Build palette swatches
    buildPaletteSwatches();
    // Slider events
    iterSlider.addEventListener('input', () => {
        state_1.viewState.maxIter = parseInt(iterSlider.value);
        onRender?.();
    });
    colorSlider.addEventListener('input', () => {
        state_1.viewState.colorOffset = parseInt(colorSlider.value);
        onRecolor?.();
    });
    // Button events
    document.getElementById('btn-reset').addEventListener('click', () => {
        onReset?.();
    });
    document.getElementById('btn-back').addEventListener('click', () => {
        onBack?.();
    });
    document.getElementById('btn-julia').addEventListener('click', () => {
        onToggleJulia?.();
    });
    document.getElementById('btn-bookmark').addEventListener('click', () => {
        showBookmarkDialog();
    });
    document.getElementById('btn-close-bookmarks').addEventListener('click', () => {
        bookmarkModal.style.display = 'none';
    });
    // Initial UI update
    updateHUD();
}
function buildPaletteSwatches() {
    paletteBar.innerHTML = '';
    config_1.PALETTES.forEach((pal, i) => {
        const swatch = document.createElement('div');
        swatch.className = 'palette-swatch' + (i === state_1.viewState.palette ? ' active' : '');
        swatch.title = pal[0];
        // Create a gradient background from the palette stops
        const stops = pal[1];
        const colors = stops.map(s => `rgb(${s[1]},${s[2]},${s[3]})`).join(',');
        swatch.style.background = `linear-gradient(135deg, ${colors})`;
        swatch.addEventListener('click', () => {
            state_1.viewState.palette = i;
            document.querySelectorAll('.palette-swatch').forEach(s => s.classList.remove('active'));
            swatch.classList.add('active');
            onRender?.();
        });
        paletteBar.appendChild(swatch);
    });
}
function updateHUD() {
    const mag = (0, state_1.magnification)();
    zoomLevelEl.textContent = '×' + formatNumber(mag);
    centerCoordsEl.textContent = `Re: ${state_1.viewState.centerRe.toPrecision(10)}, Im: ${state_1.viewState.centerIm.toPrecision(10)}`;
    const autoIter = (0, state_1.calcMaxIter)();
    iterInfoEl.textContent = `Iterations: ${state_1.viewState.maxIter} (auto: ${autoIter})`;
    iterSlider.max = Math.max(50, autoIter * 2).toString();
    if (state_1.viewState.maxIter !== parseInt(iterSlider.value)) {
        iterSlider.value = state_1.viewState.maxIter.toString();
    }
    colorSlider.value = state_1.viewState.colorOffset.toString();
}
function updateFps(fps) {
    fpsDisplayEl.textContent = `FPS: ${fps}`;
}
function formatNumber(n) {
    if (n >= 1e15)
        return n.toExponential(2);
    if (n >= 1e10)
        return n.toExponential(2);
    if (n >= 1e6)
        return (n / 1e6).toFixed(1) + 'M';
    if (n >= 1e3)
        return (n / 1e3).toFixed(1) + 'K';
    return n.toFixed(1);
}
// Bookmark dialog
function showBookmarkDialog() {
    const bookmarks = (0, state_1.loadBookmarks)();
    bookmarkList.innerHTML = '';
    if (bookmarks.length === 0) {
        bookmarkList.innerHTML = '<p style="color:#888;padding:8px">No bookmarks saved</p>';
    }
    else {
        bookmarks.forEach((bm, i) => {
            const item = document.createElement('div');
            item.className = 'bookmark-item';
            const date = new Date(bm.time);
            const timeStr = date.toLocaleTimeString();
            item.innerHTML = `
        <span>${bm.name} <small style="color:#888">(${timeStr})</small></span>
        <span class="del" data-index="${i}">✕</span>
      `;
            item.addEventListener('click', (e) => {
                if (e.target.classList.contains('del')) {
                    const idx = parseInt(e.target.dataset.index);
                    (0, state_1.deleteBookmark)(idx);
                    showBookmarkDialog();
                }
                else {
                    (0, state_1.restoreBookmark)(i);
                    bookmarkModal.style.display = 'none';
                    onRender?.();
                }
            });
            bookmarkList.appendChild(item);
        });
    }
    // Add "Save current view" button
    const saveBtn = document.createElement('button');
    saveBtn.textContent = 'Save Current View';
    saveBtn.style.cssText = 'background:rgba(40,120,200,0.6);color:#ddd;border:1px solid rgba(255,255,255,0.2);border-radius:8px;padding:8px 16px;cursor:pointer;margin-top:8px;width:100%';
    saveBtn.addEventListener('click', () => {
        const name = prompt('Bookmark name:', `View ${(0, state_1.loadBookmarks)().length + 1}`);
        if (name) {
            (0, state_1.addBookmark)(name);
            showBookmarkDialog();
        }
    });
    bookmarkList.appendChild(saveBtn);
    bookmarkModal.style.display = 'flex';
}
// Julia preview visibility
function showJuliaPreview(show) {
    if (juliaPreview) {
        juliaPreview.style.display = show ? 'block' : 'none';
    }
}
// Update Julia mode button state
function updateJuliaButton() {
    const btn = document.getElementById('btn-julia');
    if (btn) {
        btn.style.background = state_1.viewState.setMode === 'julia'
            ? 'rgba(100,60,180,0.8)'
            : 'rgba(10,10,20,0.75)';
        btn.textContent = state_1.viewState.setMode === 'julia' ? 'M' : 'J';
    }
}
// Callbacks
let onRender;
let onRecolor;
let onReset;
let onBack;
let onToggleJulia;
function setOnRender(cb) { onRender = cb; }
function setOnRecolor(cb) { onRecolor = cb; }
function setOnReset(cb) { onReset = cb; }
function setOnBack(cb) { onBack = cb; }
function setOnToggleJulia(cb) { onToggleJulia = cb; }
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"qwen/qwen3.6-27b","provider":"lmstudio","persona":"typescript","sessionId":"de523cf0-26c7-440c-a3f2-f2904ed6a0f4","tokensIn":2403260,"tokensOut":42270,"tokensTotal":2445530,"cost":0,"turns":63,"toolCalls":63,"failedToolCalls":1,"timestamp":"2026-08-01T06:53:54.873Z"} -->