Fractal Explorer
nex agi Nex N2.5 mini GGUF · typescript

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 & 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"} -->