← Fractal Explorer results

Fractal Explorer

nex agi Nex N2.5 mini GGUF · 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 51,566 bytes · SHA-256 ae457811cbcb
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Abyssal Fractals — Mandelbrot &amp; Julia Explorer</title>
<style>
/* ── css: layout ── */
:root {
  --ink: #eef4f7;
  --muted: #91a4b3;
  --line: rgba(188, 215, 225, 0.2);
  --glass: rgba(8, 17, 24, 0.7);
  --glass-strong: rgba(6, 13, 20, 0.86);
  --accent: #f0a45d;
  --accent-2: #73d7d2;
  --danger: #ff6b7a;
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; height: 100%; overflow: hidden; background: #03080c; color: var(--ink); font-family: "Avenir Next", "Segoe UI", system-ui, sans-serif; }
#app { position: fixed; inset: 0; min-width: 320px; min-height: 260px; overflow: hidden; background: radial-gradient(circle at 50% 45%, #142c3a 0%, #061119 54%, #020508 100%); }
#fractalCanvas { position: absolute; inset: 0; width: 100%; height: 100%; display: block; cursor: crosshair; touch-action: none; }
.glass { background: linear-gradient(145deg, rgba(18, 31, 42, 0.78), rgba(7, 15, 22, 0.7)); border: 1px solid var(--line); box-shadow: 0 16px 44px rgba(0, 0, 0, 0.24), inset 0 1px rgba(255,255,255,.05); backdrop-filter: blur(18px); -webkit-backdrop-filter: blur(18px); }
#hud { position: absolute; z-index: 5; top: 18px; left: 18px; right: 18px; min-height: 58px; padding: 13px 17px 12px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-radius: 13px; transition: opacity .45s ease, transform .45s ease; }
.brand { display: flex; align-items: center; gap: 12px; min-width: 225px; }
.brandMark { width: 34px; height: 34px; display: grid; place-items: center; color: #16242b; background: linear-gradient(135deg, var(--accent), #f5d99a); border-radius: 10px; font-family: Georgia, serif; font-size: 24px; font-weight: 700; box-shadow: 0 5px 18px rgba(240,164,93,.22); }
.brand strong { display: block; letter-spacing: 2.3px; font-size: 12px; line-height: 1.1; font-weight: 700; }
.brand small { display: block; margin-top: 4px; color: var(--muted); letter-spacing: 1.1px; font-size: 9px; text-transform: uppercase; }
.hudStats { display: flex; gap: 7px; flex: 1; justify-content: flex-end; }
.stat { min-width: 76px; padding: 3px 9px 5px; border-left: 1px solid rgba(192, 218, 224, .14); }
.stat label { display: block; color: #748b99; font-size: 8px; letter-spacing: 1.8px; font-weight: 700; }
.stat output { display: block; margin-top: 2px; color: #e8f2f5; font: 600 11px/1.2 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
aside { position: absolute; z-index: 6; top: 96px; bottom: 20px; width: 220px; padding: 18px 16px; display: flex; flex-direction: column; gap: 18px; border-radius: 13px; overflow-y: auto; scrollbar-width: thin; scrollbar-color: rgba(202,227,226,.25) transparent; }
#leftPanel { left: 18px; } #rightPanel { right: 18px; }
.panelTitle { color: #8ba0aa; font-size: 9px; font-weight: 800; letter-spacing: 2.2px; }
#paletteSwatches { display: grid; grid-template-columns: repeat(2, 1fr); gap: 7px; }
.swatch { position: relative; height: 31px; padding: 0; border: 1px solid rgba(224,239,242,.15); border-radius: 7px; cursor: pointer; overflow: hidden; background: linear-gradient(100deg, var(--swatch)); box-shadow: inset 0 0 0 1px rgba(255,255,255,.05); transition: transform .18s ease, border-color .18s ease, box-shadow .18s ease; }
.swatch:hover { transform: translateY(-2px); border-color: rgba(255,255,255,.42); }
.swatch.active { border-color: rgba(255,255,255,.76); box-shadow: 0 0 0 1px rgba(255,255,255,.34), 0 5px 14px rgba(0,0,0,.2); }
.swatch span { position: absolute; left: 6px; bottom: 4px; color: rgba(255,255,255,.9); font-size: 8px; letter-spacing: 1.1px; font-weight: 800; text-transform: uppercase; text-shadow: 0 1px 3px #000; }
.rangeGroup { display: flex; flex-direction: column; gap: 8px; }
.rangeLabel { display: flex; justify-content: space-between; align-items: baseline; color: #8ba0aa; font-size: 9px; letter-spacing: 1.4px; font-weight: 800; }
.rangeLabel output { color: #d9e9eb; font: 600 11px ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: 0; }
input[type="range"] { width: 100%; height: 3px; margin: 9px 0; appearance: none; border-radius: 9px; outline: none; background: linear-gradient(90deg, var(--accent-2), #243e4c); cursor: pointer; }
input[type="range"]::-webkit-slider-thumb { appearance: none; width: 12px; height: 12px; border-radius: 50%; border: 2px solid #e8f6f3; background: #37535d; box-shadow: 0 0 0 3px rgba(115,215,210,.12); }
.rangeHint { color: #60747f; font-size: 9px; line-height: 1.35; }
.modeButtons { display: grid; grid-template-columns: 1fr 1fr; border: 1px solid rgba(190,217,223,.17); border-radius: 8px; overflow: hidden; }
.modeButton, .controlButtonRow button, #saveBookmark { height: 32px; border: 0; border-right: 1px solid rgba(190,217,223,.17); color: #a7bdc5; background: rgba(12,25,34,.46); font-size: 9px; font-weight: 800; letter-spacing: 1.35px; cursor: pointer; transition: background .2s, color .2s, transform .2s; }
.modeButton:last-child, .controlButtonRow button:last-child { border-right: 0; }
.modeButton:hover, .controlButtonRow button:hover, #saveBookmark:hover { color: #fff; background: rgba(48,77,85,.65); transform: translateY(-1px); }
.modeButton.active { color: #0e1c23; background: linear-gradient(135deg, var(--accent-2), #d7efc6); }
.juliaInfo { padding: 11px 11px 12px; border: 1px solid rgba(194,218,223,.13); border-radius: 8px; background: rgba(5,12,17,.35); }
.juliaInfo label { display: block; color: #657c87; font-size: 8px; letter-spacing: 1.8px; font-weight: 800; }
.juliaInfo output { display: block; margin-top: 4px; color: #dcebef; font: 600 10px ui-monospace, SFMono-Regular, Menlo, monospace; }
.controlButtonRow { display: grid; grid-template-columns: 1fr 1fr; gap: 7px; }
.controlButtonRow button { height: 31px; border: 1px solid rgba(190,217,223,.17); border-radius: 7px; }
.bookmarkTitle { margin-top: 4px; }
.bookmarkForm { display: flex; gap: 6px; }
#bookmarkName { min-width: 0; width: 100%; height: 29px; padding: 0 8px; border: 1px solid rgba(190,217,223,.16); border-radius: 6px; color: #dcebef; background: rgba(4,12,17,.54); outline: none; font-size: 11px; }
#bookmarkName:focus { border-color: rgba(115,215,210,.65); box-shadow: 0 0 0 2px rgba(115,215,210,.1); }
#saveBookmark { height: 29px; padding: 0 9px; color: #eef8f5; background: rgba(115,215,210,.18); border: 1px solid rgba(115,215,210,.27); }
.bookmarkList { display: flex; flex-direction: column; gap: 5px; min-height: 0; }
.bookmarkItem { display: flex; align-items: center; gap: 7px; min-height: 27px; padding: 4px 7px; border: 1px solid rgba(194,218,223,.11); border-radius: 6px; background: rgba(5,12,17,.28); }
.bookmarkItem button:first-child { flex: 1; min-width: 0; overflow: hidden; color: #bdd0d5; background: transparent; border: 0; text-align: left; text-overflow: ellipsis; white-space: nowrap; font-size: 10px; cursor: pointer; }
.bookmarkItem button:first-child:hover { color: #fff; }
.bookmarkItem button:last-child { width: 19px; height: 19px; padding: 0; border: 0; border-radius: 4px; color: #84979f; background: transparent; cursor: pointer; font-size: 14px; line-height: 1; }
.bookmarkItem button:last-child:hover { color: var(--danger); background: rgba(255,107,122,.1); }
.emptyList { color: #596d77; font-size: 10px; padding: 3px 0; }
.helpText { margin-top: auto; color: #627781; font: 9px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; letter-spacing: .2px; }
#zoomBox { position: absolute; z-index: 8; width: 0; height: 0; border: 1px solid rgba(245,235,197,.95); outline: 1px solid rgba(115,215,210,.65); outline-offset: 3px; pointer-events: none; background: rgba(220,239,236,.08); box-shadow: 0 0 20px rgba(115,215,210,.25); }
#zoomBox::before, #zoomBox::after { content: ""; position: absolute; width: 8px; height: 8px; border-color: rgba(245,235,197,.95); border-style: solid; }
#zoomBox::before { left: -5px; top: -5px; border-width: 1px 0 0 1px; } #zoomBox::after { right: -5px; bottom: -5px; border-width: 0 1px 1px 0; }
#reticle { position: absolute; z-index: 7; width: 21px; height: 21px; pointer-events: none; transform: translate(-50%,-50%); }
#reticle span { position: absolute; background: rgba(231,248,245,.9); box-shadow: 0 0 6px rgba(115,215,210,.8); }
#reticle span:first-child { left: 10px; top: 0; width: 1px; height: 21px; } #reticle span:last-child { left: 0; top: 10px; width: 21px; height: 1px; }
#toast { position: absolute; z-index: 10; left: 50%; bottom: 25px; transform: translate(-50%, 10px); padding: 9px 14px; border-radius: 7px; color: #e8f7f3; background: rgba(7,18,24,.88); border: 1px solid rgba(115,215,210,.3); font-size: 10px; letter-spacing: 1px; opacity: 0; transition: opacity .25s ease, transform .25s ease; pointer-events: none; }
#toast.show { opacity: 1; transform: translate(-50%, 0); }
#loadingVeil { position: absolute; z-index: 4; left: 50%; top: 50%; width: 170px; padding: 15px; border-radius: 10px; text-align: center; transform: translate(-50%, -50%); opacity: 0; pointer-events: none; transition: opacity .2s ease; } #loadingVeil.show { opacity: 1; }
.veilMark { color: var(--accent); font-family: Georgia, serif; font-size: 25px; } #loadingVeil strong { display: block; margin-top: 5px; color: #e0edef; font-size: 9px; letter-spacing: 2px; } #loadingVeil small { display: block; margin-top: 5px; color: #879da6; font-size: 9px; }
#hud.idle, aside.idle { opacity: 0; transform: translateY(5px); } #hud.idle { pointer-events: none; } aside.idle { pointer-events: none; }
@media (max-width: 760px) { aside { width: 186px; top: 82px; bottom: 12px; padding: 13px 12px; gap: 13px; } #hud { top: 10px; left: 10px; right: 10px; padding: 10px 11px; min-height: 49px; } .brand { min-width: 155px; } .brandMark { width: 28px; height: 28px; font-size: 20px; } .brand small { display: none; } .hudStats { gap: 3px; } .stat { min-width: 55px; padding-left: 6px; } .stat label { font-size: 7px; } .stat output { font-size: 9px; } .helpText { display: none; } }
@media (max-width: 480px) { aside { width: 154px; } #rightPanel { right: 8px; } #leftPanel { left: 8px; } .stat { display: none; } .hudStats { justify-content: flex-end; } .hudStats .stat:first-child { display: block; } .brand strong { font-size: 10px; } .controlButtonRow { gap: 4px; } }
</style>
</head>
<body>
<canvas id="fractalCanvas" aria-label="Fractal rendering canvas"></canvas>
<div id="zoomBox" hidden=""></div>
<div id="reticle" hidden=""><span></span><span></span></div>
<header id="hud" class="glass">
  <div class="brand"><div class="brandMark">∿</div><div><strong>ABYSSAL<br>FRACTALS</strong><small>Mandelbrot · Julia Explorer</small></div></div>
  <div class="hudStats">
    <div class="stat"><label>DEPTH</label><output id="depthReadout">×1.00</output></div>
    <div class="stat"><label>CENTER</label><output id="centerReadout">−0.6055, 0.2701</output></div>
    <div class="stat"><label>ITER</label><output id="iterReadout">160</output></div>
    <div class="stat"><label>FPS</label><output id="fpsReadout">—</output></div>
  </div>
</header>
<aside id="leftPanel" class="glass">
  <div class="panelTitle">PALETTE</div>
  <div id="paletteSwatches"></div>
  <div class="rangeGroup"><div class="rangeLabel"><label for="offsetRange">COLOR DRIFT</label><output id="offsetValue">0.00</output></div><input id="offsetRange" type="range" min="-100" max="100" value="0" step="0.01"></div>
  <div class="rangeGroup"><div class="rangeLabel"><label for="iterRange">ITERATION LIMIT</label><output id="iterValue">auto</output></div><input id="iterRange" type="range" min="32" max="12000" step="1" value="160"><div class="rangeHint">move to override auto depth</div></div>
</aside>
<aside id="rightPanel" class="glass">
  <div class="panelTitle">SET TYPE</div>
  <div class="modeButtons"><button id="mandelButton" class="modeButton active" type="button">MANDELBROT</button><button id="juliaButton" class="modeButton" type="button">JULIA</button></div>
  <div id="juliaInfo" class="juliaInfo"><label>C VALUE</label><output id="juliaReadout">−0.7450, 0.1130</output></div>
  <div class="controlButtonRow"><button id="backButton" type="button" title="Previous view (B)">← BACK</button><button id="resetButton" type="button" title="Reset view (R)">⟲ RESET</button></div>
  <div class="panelTitle bookmarkTitle">BOOKMARKS</div>
  <div id="bookmarkForm" class="bookmarkForm"><input id="bookmarkName" type="text" placeholder="Name this view" maxlength="32" aria-label="Bookmark name"><button id="saveBookmark" type="button">SAVE</button></div>
  <div id="bookmarkList" class="bookmarkList"></div>
  <div class="helpText">DRAG select zoom · WHEEL zoom · DRAG pan<br>J live preview · ARROWS pan · + / − zoom · B back</div>
</aside>
<div id="toast" role="status" aria-live="polite"></div>
<div id="loadingVeil" class="glass"><div class="veilMark">∿</div><strong>COMPUTING PIXELS</strong><small id="renderStatus">coarse pass ready</small></div>
<div id="fractalCanvas" aria-label="Fractal rendering canvas" role="application" style="position: fixed; inset: 0; width: 100%; height: 100%; display: block; cursor: crosshair; touch-action: none;"></div>
<div id="zoomBox" hidden=""></div>
<div id="reticle" hidden=""><span></span><span></span></div>
<header id="hud" class="glass">
  <div class="brand"><div class="brandMark">∿</div><div><strong>ABYSSAL<br>FRACTALS</strong><small>Mandelbrot · Julia Explorer</small></div></div>
  <div class="hudStats">
    <div class="stat"><label>DEPTH</label><output id="depthReadout">×1.00</output></div>
    <div class="stat"><label>CENTER</label><output id="centerReadout">−0.6055, 0.2701</output></div>
    <div class="stat"><label>ITER</label><output id="iterReadout">160</output></div>
    <div class="stat"><label>FPS</label><output id="fpsReadout">—</output></div>
  </div>
</header>
<aside id="leftPanel" class="glass">
  <div class="panelTitle">PALETTE</div>
  <div id="paletteSwatches"></div>
  <div class="rangeGroup"><div class="rangeLabel"><label for="offsetRange">COLOR DRIFT</label><output id="offsetValue">0.00</output></div><input id="offsetRange" type="range" min="-100" max="100" value="0" step="0.01"></div>
  <div class="rangeGroup"><div class="rangeLabel"><label for="iterRange">ITERATION LIMIT</label><output id="iterValue">auto</output></div><input id="iterRange" type="range" min="32" max="12000" step="1" value="160"><div class="rangeHint">move to override auto depth</div></div>
</aside>
<aside id="rightPanel" class="glass">
  <div class="panelTitle">SET TYPE</div>
  <div class="modeButtons"><button id="mandelButton" class="modeButton active" type="button">MANDELBROT</button><button id="juliaButton" class="modeButton" type="button">JULIA</button></div>
  <div id="juliaInfo" class="juliaInfo"><label>C VALUE</label><output id="juliaReadout">−0.7450, 0.1130</output></div>
  <div class="controlButtonRow"><button id="backButton" type="button" title="Previous view (B)">← BACK</button><button id="resetButton" type="button" title="Reset view (R)">⟲ RESET</button></div>
  <div class="panelTitle bookmarkTitle">BOOKMARKS</div>
  <div id="bookmarkForm" class="bookmarkForm"><input id="bookmarkName" type="text" placeholder="Name this view" maxlength="32" aria-label="Bookmark name"><button id="saveBookmark" type="button">SAVE</button></div>
  <div id="bookmarkList" class="bookmarkList"></div>
  <div class="helpText">DRAG select zoom · WHEEL zoom · DRAG pan<br>J live preview · ARROWS pan · + / − zoom · B back</div>
</aside>
<div id="toast" role="status" aria-live="polite"></div>
<div id="loadingVeil" class="glass"><div class="veilMark">∿</div><strong>COMPUTING PIXELS</strong><small id="renderStatus">coarse pass ready</small></div>
<div id="fractalCanvasMarker" hidden=""></div>
<div id="fractalCanvasMarker" hidden=""></div>
<div id="fractalCanvasMarker" hidden=""></div>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./config":"src/config.ts","./core":"src/core.ts","./render":"src/render.ts","./state":"src/state.ts"},"src/state.ts":{"./config":"src/config.ts"},"src/core.ts":{"./config":"src/config.ts","./state":"src/state.ts"},"src/color.ts":{"./config":"src/config.ts"},"src/render.ts":{"./state":"src/state.ts","./config":"src/config.ts","./core":"src/core.ts","./color":"src/color.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 });
const config_1 = require("./config");
const core_1 = require("./core");
const render_1 = require("./render");
const state_1 = require("./state");
// DOM references
const app = document.querySelector('#app');
const canvas = document.querySelector('#fractalCanvas');
const zoomBox = document.querySelector('#zoomBox');
const reticle = document.querySelector('#reticle');
const hud = document.querySelector('#hud');
const leftPanel = document.querySelector('#leftPanel');
const rightPanel = document.querySelector('#rightPanel');
const depthReadout = document.querySelector('#depthReadout');
const centerReadout = document.querySelector('#centerReadout');
const iterReadout = document.querySelector('#iterReadout');
const fpsReadout = document.querySelector('#fpsReadout');
const offsetRange = document.querySelector('#offsetRange');
const offsetValue = document.querySelector('#offsetValue');
const iterRange = document.querySelector('#iterRange');
const iterValue = document.querySelector('#iterValue');
const mandelButton = document.querySelector('#mandelButton');
const juliaButton = document.querySelector('#juliaButton');
const juliaReadout = document.querySelector('#juliaReadout');
const backButton = document.querySelector('#backButton');
const resetButton = document.querySelector('#resetButton');
const bookmarkName = document.querySelector('#bookmarkName');
const saveBookmark = document.querySelector('#saveBookmark');
const bookmarkList = document.querySelector('#bookmarkList');
const toast = document.querySelector('#toast');
const loadingVeil = document.querySelector('#loadingVeil');
const renderStatus = document.querySelector('#renderStatus');
let toastTimer = 0;
let interaction = {
    active: false,
    selecting: false,
    panning: false,
    panStart: null,
    selection: { x0: 0, y0: 0, x1: 0, y1: 0 },
    hover: { x: 0, y: 0, re: 0, im: 0 },
};
const state = loadHashState() ?? (0, state_1.defaultState)();
// Helpers
function $(id) { return document.getElementById(id); }
function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); }
function formatNumber(value) { return new Intl.NumberFormat('en', { maximumFractionDigits: 4 }).format(value); }
function formatMagnitude(value) {
    const exponent = Math.floor(Math.log10(Math.abs(value)));
    return `×${(value / 10 ** exponent).toFixed(2)}e${exponent}`;
}
function formatCenter(value) { return `${value < 0 ? '−' : '+'}${Math.abs(value).toFixed(4)}`; }
function showToast(message) {
    toast.textContent = message;
    toast.classList.add('show');
    window.clearTimeout(toastTimer);
    toastTimer = window.setTimeout(() => toast.classList.remove('show'), 2600);
}
function pushHistory(view) {
    state.history.push((0, state_1.cloneState)(state).view);
    if (state.history.length > 40)
        state.history.shift();
}
function iterationLimitFor(current) {
    if (current.iterationOverride !== null)
        return clamp(current.iterationOverride, 32, config_1.MAX_ITERATIONS);
    const magnification = 3.2 / Math.max(current.view.scale, Number.EPSILON);
    return Math.max(32, Math.min(config_1.MAX_ITERATIONS, Math.round(160 + 18 * Math.log2(Math.max(1, magnification)))));
}
function setView(view, record = true) {
    const normalized = { re: view.re, im: view.im, scale: (0, state_1.clampScale)(view.scale) };
    if (record && (normalized.re !== state.view.re || normalized.im !== state.view.im || normalized.scale !== state.view.scale))
        pushHistory(state.view);
    state.view = normalized;
    renderer.cancel();
    renderer.schedule(state, null);
    updateHUD();
    writeHash();
}
function updateHUD() {
    const magnitude = 3.2 / state.view.scale;
    depthReadout.textContent = `×${formatMagnitude(magnitude)}`;
    centerReadout.textContent = `${formatCenter(state.view.re)}, ${formatCenter(state.view.im)}`;
    iterReadout.textContent = String(state.iterationOverride === null ? iterationLimitFor(state) : state.iterationOverride);
    juliaReadout.textContent = `${formatCenter(state.juliaC.re)}, ${formatCenter(state.juliaC.im)}`;
}
// Renderer + initial paint
const renderer = new render_1.Renderer(canvas, (progress) => {
    renderStatus.textContent = progress.quality < 0.6 ? `coarse pass · ${Math.round(progress.percent)}%` : progress.quality < 1 ? `refining · ${Math.round(progress.percent)}%` : 'full resolution · complete';
    loadingVeil.classList.toggle('show', progress.percent < 100);
    hud.classList.remove('idle');
    leftPanel.classList.remove('idle');
    rightPanel.classList.remove('idle');
    window.setTimeout(() => { if (!interaction.active) {
        hud.classList.add('idle');
        leftPanel.classList.add('idle');
        rightPanel.classList.add('idle');
    } }, 3200);
});
function resizeCanvas() {
    renderer.resize(canvas.clientWidth, canvas.clientHeight);
    renderer.schedule(state, null);
    updateHUD();
}
function scheduleRender() {
    renderer.cancel();
    renderer.schedule(state, null);
    updateHUD();
}
function worldFromPixels(px, py) {
    const rect = canvas.getBoundingClientRect();
    return {
        re: state.view.re - state.view.scale * 0.5 + (px / rect.width) * state.view.scale,
        im: state.view.im + state.view.scale * 0.5 - (py / rect.height) * state.view.scale,
    };
}
function pointFromClient(clientX, clientY) {
    const rect = canvas.getBoundingClientRect();
    return { x: clamp(clientX - rect.left, 0, rect.width), y: clamp(clientY - rect.top, 0, rect.height) };
}
function pointerFromClient(clientX, clientY) {
    const rect = canvas.getBoundingClientRect();
    const px = clamp(clientX - rect.left, 0, rect.width);
    const py = clamp(clientY - rect.top, 0, rect.height);
    return worldFromPixels(px, py);
}
// Navigation / interaction
function setMode(mode) {
    state.mode = mode;
    mandelButton.classList.toggle('active', mode === 'mandelbrot');
    juliaButton.classList.toggle('active', mode === 'julia');
    if (mode === 'julia') {
        reticle.hidden = false;
        showToast('Julia mode · click the canvas to set c');
    }
    else {
        reticle.hidden = true;
        showToast('Mandelbrot mode · drag a rectangle to zoom');
    }
    scheduleRender();
}
function beginSelection(x, y) {
    interaction.selecting = true;
    interaction.selection = { x0: x, y0: y, x1: x, y1: y };
    renderSelectionBox();
}
function renderSelectionBox() {
    const s = interaction.selection;
    const left = Math.min(s.x0, s.x1);
    const top = Math.min(s.y0, s.y1);
    zoomBox.hidden = Math.abs(s.x1 - s.x0) < 3 || Math.abs(s.y1 - s.y0) < 3;
    zoomBox.style.left = `${left}px`;
    zoomBox.style.top = `${top}px`;
    zoomBox.style.width = `${Math.abs(s.x1 - s.x0)}px`;
    zoomBox.style.height = `${Math.abs(s.y1 - s.y0)}px`;
}
function finishSelection(x, y) {
    if (!interaction.selecting)
        return;
    interaction.selecting = false;
    const current = pointFromClient(x, y);
    if (Math.abs(current.x - interaction.selection.x0) < 4 || Math.abs(current.y - interaction.selection.y0) < 4)
        return;
    const rect = canvas.getBoundingClientRect();
    const sx0 = Math.min(interaction.selection.x0, current.x) / rect.width;
    const sy0 = Math.min(interaction.selection.y0, current.y) / rect.height;
    const sx1 = Math.max(interaction.selection.x0, current.x) / rect.width;
    const sy1 = Math.max(interaction.selection.y0, current.y) / rect.height;
    const before = state.view;
    const centerRe = before.re - before.scale * 0.5 + ((sx0 + sx1) / 2) * before.scale;
    const centerIm = before.im + before.scale * 0.5 - ((sy0 + sy1) / 2) * before.scale;
    const scale = before.scale * Math.max(sx1 - sx0, sy1 - sy0);
    setView({ re: centerRe, im: centerIm, scale });
    zoomBox.hidden = true;
    showToast(`Zoomed ${formatMagnitude(scale / before.scale)}×`);
}
function panBy(dx, dy) {
    const rect = canvas.getBoundingClientRect();
    setView({ re: state.view.re + (dx / rect.width) * state.view.scale, im: state.view.im - (dy / rect.height) * state.view.scale, scale: state.view.scale }, false);
}
function zoomAt(clientX, clientY, factor) {
    const before = pointerFromClient(clientX, clientY);
    const oldScale = state.view.scale;
    const newScale = (0, state_1.clampScale)(oldScale * factor);
    const ratio = newScale / oldScale;
    setView({ re: before.re - (before.re - state.view.re) * ratio, im: before.im - (before.im - state.view.im) * ratio, scale: newScale });
    showToast(`Depth ${formatMagnitude(3.2 / newScale)}`);
}
function updatePointerHover(x, y) {
    const point = pointFromClient(x, y);
    const world = worldFromPixels(point.x, point.y);
    interaction.hover = { x: point.x, y: point.y, re: world.re, im: world.im };
    reticle.style.left = `${point.x}px`;
    reticle.style.top = `${point.y}px`;
    reticle.hidden = false;
    reticle.title = state.mode === 'mandelbrot' ? `hover c = ${formatCenter(world.re)}, ${formatCenter(world.im)}` : 'Julia c';
}
function bindEvents() {
    canvas.addEventListener('contextmenu', (event) => event.preventDefault());
    canvas.addEventListener('pointerdown', (event) => {
        if (![0, 1, 2].includes(event.button))
            return;
        interaction.active = true;
        const point = pointFromClient(event.clientX, event.clientY);
        if (event.button === 2 || event.button === 1 || event.shiftKey) {
            interaction.panning = true;
            interaction.panStart = { x: event.clientX, y: event.clientY, view: { ...state.view } };
        }
        else {
            beginSelection(point.x, point.y);
        }
        hud.classList.remove('idle');
        leftPanel.classList.remove('idle');
        rightPanel.classList.remove('idle');
    });
    canvas.addEventListener('pointermove', (event) => {
        updatePointerHover(event.clientX, event.clientY);
        if (interaction.panning && interaction.panStart)
            panBy(event.clientX - interaction.panStart.x, event.clientY - interaction.panStart.y);
        else if (interaction.selecting) {
            const point = pointFromClient(event.clientX, event.clientY);
            interaction.selection.x1 = point.x;
            interaction.selection.y1 = point.y;
            renderSelectionBox();
        }
    });
    canvas.addEventListener('pointerup', (event) => {
        if (interaction.panning) {
            interaction.panning = false;
            interaction.panStart = null;
        }
        if (interaction.selecting)
            finishSelection(event.clientX, event.clientY);
        interaction.active = false;
    });
    canvas.addEventListener('pointercancel', () => { interaction.selecting = false; interaction.panning = false; zoomBox.hidden = true; });
    canvas.addEventListener('pointerleave', () => { if (!interaction.active)
        reticle.hidden = true; });
    canvas.addEventListener('wheel', (event) => { event.preventDefault(); zoomAt(event.clientX, event.clientY, Math.exp(-event.deltaY * 0.0014)); }, { passive: false });
    window.addEventListener('resize', resizeCanvas);
    mandelButton.addEventListener('click', () => setMode('mandelbrot'));
    juliaButton.addEventListener('click', () => setMode('julia'));
    backButton.addEventListener('click', () => { const previous = state.history.pop(); if (previous)
        setView(previous, false); });
    resetButton.addEventListener('click', () => {
        state.history.length = 0;
        state.view = { re: -0.6055, im: 0.2701, scale: 3.2 };
        state.mode = 'mandelbrot';
        state.juliaC = { re: -0.745, im: 0.113 };
        mandelButton.classList.add('active');
        juliaButton.classList.remove('active');
        reticle.hidden = true;
        scheduleRender();
        showToast('Reset to canonical Mandelbrot view');
    });
    offsetRange.addEventListener('input', () => { state.colorOffset = Number(offsetRange.value) / 100; offsetValue.textContent = Number(offsetRange.value).toFixed(2); scheduleRender(); });
    iterRange.addEventListener('input', () => { state.iterationOverride = Number(iterRange.value); iterValue.textContent = Number(iterRange.value) === 160 ? 'auto' : String(Number(iterRange.value)); scheduleRender(); });
    window.addEventListener('keydown', (event) => {
        const tag = event.target?.tagName;
        if (tag === 'INPUT' || tag === 'TEXTAREA')
            return;
        if (event.key === 'j' || event.key === 'J')
            setMode(state.mode === 'mandelbrot' ? 'julia' : 'mandelbrot');
        else if (event.key === 'b' || event.key === 'B')
            backButton.click();
        else if (event.key === 'r' || event.key === 'R')
            resetButton.click();
        else if (event.key === '+' || event.key === '=')
            zoomAt(window.innerWidth / 2, window.innerHeight / 2, 1.18);
        else if (event.key === '-' || event.key === '_')
            zoomAt(window.innerWidth / 2, window.innerHeight / 2, 1 / 1.18);
        else if (event.key === 'ArrowLeft')
            panBy(45, 0);
        else if (event.key === 'ArrowRight')
            panBy(-45, 0);
        else if (event.key === 'ArrowUp')
            panBy(0, 45);
        else if (event.key === 'ArrowDown')
            panBy(0, -45);
    });
    buildPaletteChips();
    bindBookmarks();
}
// Palette chips
function buildPaletteChips() {
    const container = $('paletteSwatches');
    const names = ['fire', 'ocean', 'monochrome', 'psychedelic'];
    const gradients = {
        fire: 'linear-gradient(120deg,#ff6b35,#f0a45d,#fff1a8)',
        ocean: 'linear-gradient(120deg,#061d32,#2a9db4,#dcfffb)',
        monochrome: 'linear-gradient(120deg,#0b0e11,#7f8990,#f5f5f0)',
        psychedelic: 'linear-gradient(120deg,#4c1470,#ef4764,#6ad774,#226a9f,#060810)',
    };
    for (const name of names) {
        const button = document.createElement('button');
        button.type = 'button';
        button.className = `swatch${state.palette === name ? ' active' : ''}`;
        button.dataset.palette = name;
        button.style.setProperty('--swatch', gradients[name]);
        button.setAttribute('aria-label', `Use ${name} palette`);
        const label = document.createElement('span');
        label.textContent = name;
        button.appendChild(label);
        button.addEventListener('click', () => {
            state.palette = name;
            [...container.children].forEach((child) => child.classList.toggle('active', child.dataset.palette === name));
            scheduleRender();
        });
        container.appendChild(button);
    }
}
function readBookmarks() {
    try {
        return JSON.parse(window.localStorage.getItem(config_1.BOOKMARK_KEY) ?? '[]');
    }
    catch {
        return [];
    }
}
function writeBookmarks(items) { window.localStorage.setItem(config_1.BOOKMARK_KEY, JSON.stringify(items)); }
function bindBookmarks() {
    saveBookmark.addEventListener('click', () => {
        const name = bookmarkName.value.trim() || `View ${state.history.length + 1}`;
        const items = readBookmarks();
        items.unshift({ id: `${Date.now()}-${Math.random().toString(16).slice(2)}`, name, createdAt: Date.now(), ...(0, state_1.cloneState)(state) });
        writeBookmarks(items.slice(0, 30));
        bookmarkName.value = '';
        renderBookmarkList();
        showToast(`Saved “${name}”`);
    });
    renderBookmarkList();
}
function renderBookmarkList() {
    bookmarkList.innerHTML = '';
    const items = readBookmarks();
    if (!items.length) {
        const empty = document.createElement('div');
        empty.className = 'emptyList';
        empty.textContent = 'No bookmarks yet · save the current view';
        bookmarkList.appendChild(empty);
        return;
    }
    for (const item of items) {
        const row = document.createElement('div');
        row.className = 'bookmarkItem';
        const label = document.createElement('button');
        label.type = 'button';
        label.textContent = item.name;
        label.title = `${item.view.scale} · ${item.palette}`;
        label.addEventListener('click', () => {
            Object.assign(state, (0, state_1.cloneState)(item));
            state.history = [];
            offsetRange.value = String(Math.round(state.colorOffset * 100));
            offsetValue.textContent = state.colorOffset.toFixed(2);
            iterRange.value = String(state.iterationOverride ?? 160);
            iterValue.textContent = state.iterationOverride === null ? 'auto' : String(state.iterationOverride);
            [...$('paletteSwatches').children].forEach((child) => child.classList.toggle('active', child.dataset.palette === state.palette));
            mandelButton.classList.toggle('active', state.mode === 'mandelbrot');
            juliaButton.classList.toggle('active', state.mode === 'julia');
            scheduleRender();
            showToast(`Restored “${item.name}”`);
        });
        const remove = document.createElement('button');
        remove.type = 'button';
        remove.textContent = '×';
        remove.setAttribute('aria-label', `Remove ${item.name}`);
        remove.addEventListener('click', () => { writeBookmarks(readBookmarks().filter((bookmark) => bookmark.id !== item.id)); renderBookmarkList(); });
        row.append(label, remove);
        bookmarkList.appendChild(row);
    }
}
// Shareable state
function writeHash() {
    const params = new URLSearchParams();
    params.set('v', config_1.URL_HASH_VERSION);
    params.set('re', state.view.re.toFixed(9));
    params.set('im', state.view.im.toFixed(9));
    params.set('scale', state.view.scale.toExponential(6));
    params.set('mode', state.mode);
    params.set('c', `${state.juliaC.re},${state.juliaC.im}`);
    params.set('palette', state.palette);
    params.set('iter', state.iterationOverride === null ? 'auto' : String(state.iterationOverride));
    window.history.replaceState(null, '', `#${params.toString()}`);
}
function loadHashState() {
    const hash = window.location.hash.slice(1);
    if (!hash.startsWith(`v=${config_1.URL_HASH_VERSION}&`))
        return null;
    try {
        const params = new URLSearchParams(hash);
        const view = { re: Number(params.get('re')), im: Number(params.get('im')), scale: (0, state_1.clampScale)(Number(params.get('scale'))) };
        if (![view.re, view.im, view.scale].every(Number.isFinite))
            return null;
        const [cre, cim] = (params.get('c') ?? '-0.745,0.113').split(',').map(Number);
        const candidate = (0, state_1.defaultState)();
        candidate.view = view;
        candidate.mode = params.get('mode') === 'julia' ? 'julia' : 'mandelbrot';
        candidate.juliaC = { re: cre, im: cim };
        candidate.palette = ['fire', 'ocean', 'monochrome', 'psychedelic'].includes(params.get('palette')) ? params.get('palette') : 'fire';
        candidate.iterationOverride = params.get('iter') === 'auto' ? null : Math.max(32, Math.min(config_1.MAX_ITERATIONS, Number(params.get('iter'))));
        return candidate;
    }
    catch {
        return null;
    }
}
function bindDebugApi() {
    const api = {
        iterationsAt: (re, im) => {
            const value = (0, core_1.iterateComplex)(re, im, { re, im }, config_1.MAX_ITERATIONS);
            return value === 0 ? config_1.MAX_ITERATIONS : value;
        },
        get state() { return (0, state_1.cloneState)(state); },
        setView: (view) => { setView({ re: view.re, im: view.im, scale: (0, state_1.clampScale)(view.scale) }, false); },
        refresh: () => scheduleRender(),
    };
    window.fractal = api;
}
bindEvents();
resizeCanvas();
updateHUD();
bindDebugApi();
};

// ── module: src/config.ts ──
__mods["src/config.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.URL_HASH_VERSION = exports.BOOKMARK_KEY = exports.PALETTES = exports.PALETTE_NAMES = exports.COARSE_ITERATION_FRACTION = exports.PROGRESSIVE_QUALITIES = exports.DEFAULT_ITERATIONS = exports.MAX_MANUAL_ITERATIONS = exports.MIN_MANUAL_ITERATIONS = exports.MAX_ITERATIONS = exports.MAX_MAGNIFICATION = exports.MIN_SCALE = exports.INITIAL_SCALE = void 0;
// Tunable constants live here so the visual and numerical behavior is easy to tune.
exports.INITIAL_SCALE = 3.2;
exports.MIN_SCALE = exports.INITIAL_SCALE / 10000000000000;
exports.MAX_MAGNIFICATION = 10000000000000;
exports.MAX_ITERATIONS = 12000;
exports.MIN_MANUAL_ITERATIONS = 32;
exports.MAX_MANUAL_ITERATIONS = 12000;
exports.DEFAULT_ITERATIONS = 160;
exports.PROGRESSIVE_QUALITIES = [0.25, 0.55, 1];
exports.COARSE_ITERATION_FRACTION = 0.42;
exports.PALETTE_NAMES = ['fire', 'ocean', 'monochrome', 'psychedelic'];
exports.PALETTES = {
    fire: { label: 'Fire', stops: [[0, 0, 0], [20, 3, 8], [92, 9, 21], [205, 52, 30], [255, 125, 25], [255, 214, 78], [255, 248, 196]] },
    ocean: { label: 'Ocean', stops: [[0, 0, 0], [3, 25, 48], [4, 86, 124], [23, 144, 175], [77, 203, 200], [205, 250, 244], [255, 255, 240]] },
    monochrome: { label: 'Ivory', stops: [[0, 0, 0], [16, 16, 19], [55, 56, 63], [120, 121, 126], [196, 197, 199], [245, 245, 240], [255, 255, 255]] },
    psychedelic: { label: 'Neon', stops: [[0, 0, 0], [38, 12, 59], [100, 13, 115], [182, 31, 112], [235, 68, 91], [255, 133, 77], [253, 204, 78], [117, 207, 116], [42, 157, 143], [34, 91, 150], [99, 35, 125], [178, 24, 100], [0, 0, 0]] },
};
exports.BOOKMARK_KEY = 'abyssal-fractals-bookmarks-v1';
exports.URL_HASH_VERSION = 'af1';
};

// ── module: src/state.ts ──
__mods["src/state.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.defaultState = defaultState;
exports.cloneViewState = cloneViewState;
exports.clampScale = clampScale;
exports.cloneState = cloneState;
const config_1 = require("./config");
function defaultState() {
    return {
        view: { re: -0.6055, im: 0.2701, scale: 3.2 },
        mode: 'mandelbrot',
        juliaC: { re: -0.745, im: 0.113 },
        palette: 'fire',
        colorOffset: 0,
        iterationOverride: null,
        history: [],
    };
}
function cloneViewState(view) {
    return { re: view.re, im: view.im, scale: view.scale };
}
function clampScale(scale) {
    return Math.min(config_1.INITIAL_SCALE, Math.max(config_1.MIN_SCALE, scale));
}
function cloneState(state) {
    return {
        view: cloneViewState(state.view),
        mode: state.mode,
        juliaC: { ...state.juliaC },
        palette: state.palette,
        colorOffset: state.colorOffset,
        iterationOverride: state.iterationOverride,
        history: state.history.map(cloneViewState),
    };
}
};

// ── module: src/core.ts ──
__mods["src/core.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.iterateComplex = iterateComplex;
exports.activeComplex = activeComplex;
exports.iterationLimit = iterationLimit;
const config_1 = require("./config");
const LOG_BASE_2 = Math.LN2;
// Allocation-free escape-time loop. Positive return is a smooth iteration count;
// zero means the point remained inside the set through the supplied cap.
function iterateComplex(re, im, c, maxIterations) {
    let zr = 0;
    let zi = 0;
    let n = 0;
    while (n < maxIterations) {
        const zr2 = zr * zr;
        const zi2 = zi * zi;
        const magnitude2 = zr2 + zi2;
        if (magnitude2 > 4) {
            const logMagnitude = 0.5 * Math.log(magnitude2);
            return n + 1 - logMagnitude / LOG_BASE_2;
        }
        zi = 2 * zr * zi + c.im;
        zr = zr2 - zi2 + c.re;
        n += 1;
    }
    return 0;
}
function activeComplex(stateMode, stateJuliaC, re, im) {
    return stateMode === 'julia' ? stateJuliaC : { re, im };
}
function iterationLimit(state, viewScale) {
    if (state.iterationOverride !== null)
        return Math.max(1, Math.min(config_1.MAX_ITERATIONS, Math.round(state.iterationOverride)));
    const magnification = 3.2 / Math.max(state && viewScale, Number.EPSILON);
    return Math.max(32, Math.min(config_1.MAX_ITERATIONS, Math.round(160 + 18 * Math.log2(Math.max(1, magnification)))));
}
};

// ── module: src/color.ts ──
__mods["src/color.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.samplePalette = samplePalette;
exports.smoothPhase = smoothPhase;
exports.paletteFor = paletteFor;
const config_1 = require("./config");
function clamp01(value) {
    return Math.max(0, Math.min(1, value));
}
// The phase is intentionally non-integer so neighboring pixels cross palette
// stops continuously; no iteration recompute is needed for offset changes.
function samplePalette(phase, palette, target, index) {
    const stops = config_1.PALETTES[palette].stops;
    const count = stops.length;
    const normalized = clamp01(phase) * (count - 1);
    const leftIndex = Math.min(count - 2, Math.floor(normalized));
    const rightIndex = leftIndex + 1;
    const amount = normalized - leftIndex;
    const left = stops[leftIndex];
    const right = stops[rightIndex];
    target[index] = Math.round(left[0] + (right[0] - left[0]) * amount);
    target[index + 1] = Math.round(left[1] + (right[1] - left[1]) * amount);
    target[index + 2] = Math.round(left[2] + (right[2] - left[2]) * amount);
    target[index + 3] = 255;
}
function smoothPhase(smoothIteration, offset) {
    if (smoothIteration <= 0)
        return 0;
    return (smoothIteration * 0.035 + offset) % 1;
}
function paletteFor(name) {
    return name;
}
};

// ── module: src/render.ts ──
__mods["src/render.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = void 0;
const config_1 = require("./config");
const core_1 = require("./core");
const color_1 = require("./color");
/**
 * Allocation-free progressive renderer.
 * Escape iterations are written into a reusable typed array and only the
 * resulting ImageData is blitted. A new interaction invalidates the current
 * request, so stale tiles never appear after a pan or zoom.
 */
class Renderer {
    constructor(canvas, onProgress = null) {
        this.imageData = null;
        this.iterationBuffer = null;
        this.queue = [];
        this.current = null;
        this.progressCallback = null;
        this.passIndex = 0;
        this.rowCursor = 0;
        this.processedRows = 0;
        this.nextId = 1;
        let target = null;
        if (canvas instanceof HTMLCanvasElement)
            target = canvas;
        else {
            const replacement = document.createElement('canvas');
            replacement.id = canvas.id;
            replacement.setAttribute('aria-label', canvas.getAttribute('aria-label') ?? 'Fractal rendering canvas');
            canvas.replaceWith(replacement);
            target = replacement;
        }
        this.canvas = target;
        const ctx = this.canvas.getContext('2d', { alpha: false });
        if (!ctx)
            throw new Error('Canvas 2D context unavailable');
        this.ctx = ctx;
        this.progressCallback = onProgress;
    }
    resize(width, height) {
        const dpr = Math.min(1.25, window.devicePixelRatio || 1);
        const targetWidth = Math.max(1, Math.round(width * dpr));
        const targetHeight = Math.max(1, Math.round(height * dpr));
        if (this.canvas.width === targetWidth && this.canvas.height === targetHeight)
            return;
        this.cancel();
        this.canvas.width = targetWidth;
        this.canvas.height = targetHeight;
        this.imageData = this.ctx.createImageData(targetWidth, targetHeight);
        this.iterationBuffer = new Uint32Array(targetWidth * targetHeight);
        this.passIndex = 0;
        this.rowCursor = 0;
        this.processedRows = 0;
    }
    schedule(state, onProgress) {
        this.progressCallback = onProgress;
        const request = {
            id: this.nextId++,
            cancelled: false,
            width: this.canvas.width,
            height: this.canvas.height,
            view: { ...state.view },
            mode: state.mode,
            juliaC: { ...state.juliaC },
            palette: state.palette,
            offset: state.colorOffset,
            iterations: this.iterationLimit(state),
        };
        this.queue.push(request);
        this.startNext();
    }
    cancel() {
        if (this.current)
            this.current.cancelled = true;
        this.current = null;
        this.passIndex = 0;
        this.rowCursor = 0;
        this.processedRows = 0;
        this.queue.length = 0;
    }
    recolor() {
        const request = this.current;
        const iterations = this.iterationBuffer;
        const data = this.imageData?.data;
        if (!request || !iterations || !data)
            return;
        for (let index = 0; index < iterations.length; index++) {
            const smooth = iterations[index];
            const p = index * 4;
            if (smooth <= 0) {
                data[p] = 0;
                data[p + 1] = 0;
                data[p + 2] = 0;
                data[p + 3] = 255;
            }
            else {
                (0, color_1.samplePalette)((smooth * 0.035 + request.offset) % 1, request.palette, data, p);
            }
        }
        if (!this.imageData)
            return;
        this.ctx.putImageData(this.imageData, 0, 0);
    }
    iterationLimit(state) {
        if (state.iterationOverride !== null)
            return Math.max(32, Math.min(12000, Math.round(state.iterationOverride)));
        const magnification = 3.2 / Math.max(state.view.scale, Number.EPSILON);
        return Math.max(32, Math.min(12000, Math.round(160 + 18 * Math.log2(Math.max(1, magnification)))));
    }
    startNext() {
        if (this.current || !this.queue.length)
            return;
        this.current = this.queue.shift();
        this.passIndex = 0;
        this.rowCursor = 0;
        this.processedRows = 0;
        this.processChunk();
    }
    processChunk() {
        const request = this.current;
        if (!request || request.cancelled || this.queue.some((queued) => queued.id === request.id))
            return;
        const pass = this.passIndex;
        const quality = config_1.PROGRESSIVE_QUALITIES[pass];
        if (!quality) {
            this.finishPass();
            return;
        }
        const step = pass === 0 ? 4 : pass === 1 ? 2 : 1;
        const rowsPerChunk = pass === 0 ? 40 : pass === 1 ? 24 : 14;
        const endRow = Math.min(request.height, this.rowCursor + rowsPerChunk);
        let rowsWritten = 0;
        const top = request.view.im + request.view.scale * 0.5;
        const left = request.view.re - request.view.scale * 0.5;
        const ratioX = request.view.scale / request.width;
        const ratioY = request.view.scale / request.height;
        const leftEdge = left - ratioX * 0.5;
        const topEdge = top + ratioY * 0.5;
        const iterations = this.iterationBuffer;
        const data = this.imageData?.data;
        if (!iterations || !data) {
            this.finishPass();
            return;
        }
        for (let y = this.rowCursor; y < endRow; y += step) {
            const cIm = topEdge - Math.min(request.height - 1, y + step - 1) * ratioY;
            for (let x = 0; x < request.width; x += step) {
                const cRe = leftEdge + Math.min(request.width - 1, x + step - 1) * ratioX;
                const zc = (0, core_1.activeComplex)(request.mode, request.juliaC, cRe, cIm);
                const smooth = (0, core_1.iterateComplex)(cRe, cIm, zc, request.iterations);
                iterations[y * request.width + x] = smooth;
                const p0 = (y * request.width + x) * 4;
                if (smooth <= 0) {
                    for (let oy = 0; y + oy < endRow && oy < step; oy++) {
                        const py = y + oy;
                        for (let ox = 0; x + ox < request.width && ox < step; ox++) {
                            const p = (py * request.width + x + ox) * 4;
                            data[p] = 0;
                            data[p + 1] = 0;
                            data[p + 2] = 0;
                            data[p + 3] = 255;
                        }
                    }
                }
                else {
                    const phase = (smooth * 0.035 + request.offset) % 1;
                    for (let oy = 0; y + oy < endRow && oy < step; oy++) {
                        const py = y + oy;
                        for (let ox = 0; x + ox < request.width && ox < step; ox++) {
                            const p = (py * request.width + x + ox) * 4;
                            (0, color_1.samplePalette)(phase, request.palette, data, p);
                        }
                    }
                }
            }
            rowsWritten += step;
        }
        this.rowCursor = endRow;
        this.processedRows += rowsWritten;
        const percent = Math.min(99, (this.processedRows / request.height) * 100);
        this.progressCallback?.({ quality, percent, cancelRequested: false });
        if (this.rowCursor < request.height)
            window.requestAnimationFrame(() => this.processChunk());
        else
            this.finishPass();
    }
    finishPass() {
        const request = this.current;
        if (!request || request.cancelled)
            return;
        if (!this.imageData)
            return;
        if (!this.imageData)
            return;
        this.ctx.putImageData(this.imageData, 0, 0);
        if (this.passIndex + 1 < config_1.PROGRESSIVE_QUALITIES.length) {
            this.passIndex += 1;
            this.rowCursor = 0;
            this.processedRows = 0;
            window.requestAnimationFrame(() => this.processChunk());
            return;
        }
        this.progressCallback?.({ quality: 1, percent: 100, cancelRequested: false });
        this.current = null;
        this.passIndex = 0;
        this.rowCursor = 0;
        this.processedRows = 0;
        this.startNext();
    }
}
exports.Renderer = Renderer;
};

// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"bartowski/nex-agi_Nex-N2.5-mini-GGUF","provider":"brigid","persona":"typescript","sessionId":"95c8934d-eed0-406d-a826-398e85172110","tokensIn":4631597,"tokensOut":69699,"tokensTotal":4701296,"cost":0,"turns":109,"toolCalls":111,"failedToolCalls":32,"timestamp":"2026-09-14T15:43:47.996Z"} -->