Fractal Explorer
Gemma 4 12b · typescript

Browser check: Artifact runtime error. This is a technical check, not a quality review.
Reviews & generation details
Reviews
No human review yet. Unreviewed does not mean unsuccessful.
Generation context
Imported historical artifact. Its exact original instructions and lineage are not known.
Generation prompt · exact text unavailable
See the current prompt for reference. It may differ from the instructions used to generate this artifact.
Inspect original source 17,332 bytes · SHA-256 43e276c026a8
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fractal Explorer</title>
<style>
/* ── css: theme ── */
body {
margin: 0;
padding: 0;
background: #000;
color: #fff;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
overflow: hidden;
}
#fractal-canvas {
display: block;
width: 100vw;
height: 100vh;
cursor: crosshair;
}
#hud {
position: fixed;
top: 20px;
left: 20px;
padding: 15px;
background: rgba(20, 20, 20, 0.8);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 12px;
z-index: 100;
transition: opacity 0.3s ease;
}
#hud.hidden {
opacity: 0;
pointer-events: none;
}
.hud-stats, .hud-controls, .hud-history {
margin-bottom: 10px;
font-size: 12px;
}
.hud-stats span {
margin-right: 15px;
}
button, select {
background: rgba(255, 255, 255, 0.1);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
padding: 4px 8px;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background: rgba(255, 255, 255, 0.2);
}
#zoom-box {
position: absolute;
border: 2px solid #fff;
background: rgba(255, 255, 255, 0.1);
pointer-events: none;
display: none;
z-index: 101;
}
#zoom-box::before { /* Inverted border effect */
content: '';
position: absolute;
top: -2px; left: -2px; right: -2px; bottom: -2px;
border: 2px solid #000;
pointer-events: none;
}
#fractal-canvas {
image-rendering: pixelated; /* optional, depends on zoom */
}
</style>
</head>
<body>
<div id="hud" class="hidden">
<div class="hud-stats">
<span>Mag: <span id="mag-val">1.0x</span></span>
<span>Iter: <span id="iter-val">256</span></span>
<span>FPS: <span id="fps-val">60</span></span>
</div>
<div class="hud-controls">
<button id="btn-reset">Reset</button>
<select id="palette-select"></select>
<input type="range" id="iter-slider" min="64" max="5000" value="256">
</div>
<div class="hud-history">
<button id="btn-back">←</button>
<button id="btn-bookmark">💾</button>
</div>
</div>
<div id="zoom-box"></div>
<canvas id="fractal-canvas"></canvas>
<script>
(function () {
'use strict';
var __mods = {};
var __cache = {};
var __map = {"src/main.ts":{"./renderer":"src/renderer.ts","./math":"src/math.ts"},"src/renderer.ts":{"./math":"src/math.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 renderer_1 = require("./renderer");
const math_1 = require("./math");
class App {
constructor() {
this.history = [];
this.isDragging = false;
this.dragStart = null;
this.renderer = new renderer_1.Renderer('fractal-canvas');
this.zoomBox = document.getElementById('zoom-box');
this.initUI();
this.setupEvents();
this.saveToHistory();
this.loadBookmark();
}
initUI() {
const paletteSelect = document.getElementById('palette-select');
Object.keys(math_1.CONFIG.PALETTES).forEach(key => {
const opt = document.createElement('option');
opt.value = key;
opt.textContent = key.charAt(0).toUpperCase() + key.slice(1);
paletteSelect.appendChild(opt);
});
paletteSelect.onchange = (e) => {
const target = e.target;
this.renderer.setPalette(target.value);
};
const iterSlider = document.getElementById('iter-slider');
if (iterSlider) {
iterSlider.oninput = () => { };
}
document.getElementById('btn-reset').onclick = () => {
this.renderer.setView(-0.5, 0, 1);
this.saveToHistory();
};
document.getElementById('btn-back').onclick = () => {
if (this.history.length > 1) {
this.history.pop(); // pop current
const prev = this.history.pop() || null;
if (prev) {
this.renderer.setView(prev.re, prev.im, prev.zoom);
this.saveToHistory();
}
}
};
document.getElementById('btn-bookmark').onclick = () => {
const state = {
re: this.renderer.currentView.re,
im: this.renderer.currentView.im,
zoom: this.renderer.currentView.zoom,
palette: this.renderer.paletteName,
type: this.renderer.currentType
};
localStorage.setItem('fractal_bookmark', JSON.stringify(state));
alert("Bookmark saved!");
};
}
loadBookmark() {
const data = localStorage.getItem('fractal_bookmark');
if (data) {
try {
const state = JSON.parse(data);
this.renderer.setView(state.re, state.im, state.zoom);
this.renderer.setPalette(state.palette);
this.renderer.setType(state.type);
}
catch (e) {
console.error("Failed to load bookmark", e);
}
}
}
setupEvents() {
const canvas = this.renderer.canvas;
// Wheel Zoom
canvas.onwheel = (e) => {
e.preventDefault();
const zoomFactor = e.deltaY > 0 ? 1 / math_1.CONFIG.ZOOM_SPEED : math_1.CONFIG.ZOOM_SPEED;
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width;
const y = (e.clientY - rect.top) / rect.height;
const currentZoom = this.renderer.currentView.zoom || 1;
const range = 4 / Math.sqrt(currentZoom);
const curRe = this.renderer.currentView.re + (x - 0.5) * range;
const curIm = this.renderer.currentView.im + (y - 0.5) * range;
const newZoom = currentZoom * zoomFactor;
const newRange = 4 / Math.sqrt(newZoom);
const targetRe = curRe - (x - 0.5) * newRange;
const targetIm = curIm - (y - 0.5) * newRange;
this.renderer.setView(targetRe, targetIm, newZoom);
this.updateHUD();
};
// Drag to pan and Selection Box
canvas.onmousedown = (e) => {
if (e.button === 0) { // Left click
this.isDragging = true;
this.dragStart = { x: e.clientX, y: e.clientY };
this.zoomBox.style.display = 'block';
}
};
window.onmousemove = (e) => {
if (!this.isDragging || !this.dragStart)
return;
const x = e.clientX;
const y = e.clientY;
const boxWidth = Math.abs(x - this.dragStart.x);
const boxHeight = Math.abs(y - this.dragStart.y);
this.zoomBox.style.left = (Math.min(x, this.dragStart.x) + 'px');
this.zoomBox.style.top = (Math.min(y, this.dragStart.y) + 'px');
this.zoomBox.style.width = boxWidth + 'px';
this.zoomBox.style.height = boxHeight + 'px';
// Julia Preview logic:
if (this.renderer.currentType === 'mandelbrot') {
const rect = canvas.getBoundingClientRect();
const range = 4 / Math.sqrt(this.renderer.currentView.zoom);
const re = this.renderer.currentView.re + ((x - rect.left) / rect.width - 0.5) * range;
const im = this.renderer.currentView.im + ((y - rect.top) / rect.height - 0.5) * range;
}
};
window.onmouseup = (e) => {
if (!this.isDragging || !this.dragStart)
return;
this.isDragging = false;
this.dragStart = null;
this.zoomBox.style.display = 'none';
const x1 = Math.min(e.clientX, this.dragStart.x);
const y1 = Math.min(e.clientY, this.dragStart.y);
const x2 = Math.max(e.clientX, this.dragStart.x);
const y2 = Math.max(e.clientY, this.dragStart.y);
const rect = canvas.getBoundingClientRect();
const range = 4 / Math.sqrt(this.renderer.currentView.zoom || 1);
const re1 = this.renderer.currentView.re + ((x1 - rect.left) / rect.width - 0.5) * range;
const im1 = this.renderer.currentView.im + ((y1 - rect.top) / rect.height - 0.5) * range;
const re2 = this.renderer.currentView.re + ((x2 - rect.left) / rect.width - 0.5) * range;
const im2 = this.renderer.currentView.im + ((y2 - rect.top) / rect.height - 0.5) * range;
if (x1 !== x2 && y1 !== y2) {
const spanRe = Math.abs(re2 - re1);
const spanIm = Math.abs(im2 - im1);
const maxSpan = Math.max(spanRe, spanIm);
const newZoom = 16 / (maxSpan * maxSpan);
const centerRe = (re1 + re2) / 2;
const centerIm = (im1 + im2) / 2;
this.renderer.setView(centerRe, centerIm, newZoom);
this.saveToHistory();
}
};
// Keyboard navigation
window.onkeydown = (e) => {
if (e.key === 'j') {
const currentType = this.renderer.currentType;
this.renderer.setType(currentType === 'mandelbrot' ? 'julia' : 'mandelbrot');
}
};
// HUD visibility logic
canvas.onmouseenter = () => document.getElementById('hud').classList.remove('hidden');
canvas.onmouseleave = () => {
const hud = document.getElementById('hud');
setTimeout(() => { if (!this.isDragging)
hud.classList.add('hidden'); }, 2000);
};
// Update stats
setInterval(() => this.updateHUD(), 500);
}
updateHUD() {
const view = this.renderer.currentView;
const magStr = view.zoom ? `×${view.zoom.toExponential(2)}` : '×1.0';
document.getElementById('mag-val').textContent = magStr;
const iterCount = Math.floor(math_1.CONFIG.MAX_ITER_BASE + (Math.log2(view.zoom || 1) * math_1.CONFIG.ITERATION_SCALE));
document.getElementById('iter-val').textContent = iterCount.toString();
}
saveToHistory() {
const view = this.renderer.currentView;
this.history.push({ ...view });
if (this.history.length > 50)
this.history.shift();
}
}
new App();
};
// ── module: src/math.ts ──
__mods["src/math.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.fractal = exports.FractalMath = exports.CONFIG = void 0;
/**
* CONFIGURATION & CONSTANTS
*/
exports.CONFIG = {
MAX_ITER_BASE: 256,
ITERATION_SCALE: 1.5, // How much max iterations increase per log2(zoom)
ZOOM_SPEED: 1.05,
SMOOTH_COLORING_THRESHOLD: 4.0, // Escape radius for smooth coloring math
PALETTES: {
fire: [[0, 0, 20], [60, 30, 0], [255, 100, 0], [255, 200, 0], [255, 255, 255]],
ocean: [[0, 0, 40], [0, 100, 200], [0, 255, 255], [200, 255, 255], [255, 255, 255]],
monochrome: [[0, 0, 0], [50, 50, 50], [150, 150, 150], [200, 200, 200], [255, 255, 255]],
psychedelic: [[0, 255, 255], [255, 0, 255], [255, 255, 0], [0, 255, 0], [255, 0, 0]]
}
};
/**
* MATH ENGINE
*/
class FractalMath {
static getIteration(re, im, maxIter, type, juliaC) {
let zr = 0;
let zi = 0;
let cr = type === 'mandelbrot' ? re : (juliaC?.re ?? 0);
let ci = type === 'mandelbrot' ? im : (juliaC?.im ?? 0);
// For Julia, the starting point is z_0 = c_initial? No, standard Julia: z_{n+1} = z_n^2 + c.
// In this explorer, we usually set z_0 = initial coord and c = constant.
// Wait, typical Julia: z_0 is anything (usually 0), c is fixed.
// Let's stick to standard: Mandelbrot -> z_0=0, c=pos; Julia -> z_0=pos, c=fixed.
if (type === 'julia') {
zr = re;
zi = im;
}
else {
zr = 0;
zi = 0;
}
let i = 0;
while (i < maxIter && (zr * zr + zi * zi) < 16.0) {
const nextZr = zr * zr - zi * zi + cr;
zi = 2 * zr * zi + ci;
zr = nextZr;
i++;
}
if (i === maxIter)
return { iter: maxIter, smooth: maxIter };
// Smooth coloring formula: n + 1 - log(log|z|) / log(2)
const magnitude = Math.sqrt(zr * zr + zi * zi);
const smooth = i + 1 - Math.log(Math.log(magnitude)) / Math.log(2);
return { iter: i, smooth };
}
static getIterationAt(re, im, maxIter, type, juliaC) {
return this.getIteration(re, im, maxIter, type, juliaC).iter;
}
}
exports.FractalMath = FractalMath;
/**
* GLOBAL EXPOSURE FOR PROBES
*/
exports.fractal = {
iterationsAt: (re, im) => FractalMath.getIterationAt(re, im, exports.CONFIG.MAX_ITER_BASE, 'mandelbrot'),
setView: (view) => console.log("View set:", view)
};
};
// ── module: src/renderer.ts ──
__mods["src/renderer.ts"] = function (exports, require, module) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Renderer = void 0;
const math_1 = require("./math");
class Renderer {
constructor(canvasId) {
this.imageData = null;
this.currentView = { re: -0.5, im: 0, zoom: 1 };
this.currentType = 'mandelbrot';
this.juliaC = { re: -0.7, im: 0.27015 };
this.paletteName = 'fire';
this.canvas = document.getElementById(canvasId);
this.ctx = this.canvas.getContext('2d', { alpha: false });
this.resize();
window.addEventListener('resize', () => this.resize());
}
resize() {
this.width = this.canvas.width = window.innerWidth;
this.height = this.canvas.height = window.innerHeight;
this.render();
}
setView(re, im, zoom) {
this.currentView = { re, im, zoom };
this.render();
}
setType(type) {
this.currentType = type;
this.render();
}
setJuliaC(re, im) {
this.juliaC = { re, im };
this.render();
}
setPalette(name) {
this.paletteName = name;
this.render();
}
get_maxIter() {
const zoomLog2 = Math.log2(this.currentView.zoom || 1);
return Math.floor(math_1.CONFIG.MAX_ITER_BASE + (zoomLog2 * math_1.CONFIG.ITERATION_SCALE));
}
getColor(smooth, maxIter) {
const palette = math_1.CONFIG.PALETTES[this.paletteName];
const scaledNorm = Math.max(0, Math.min(1, (smooth - 2) / (maxIter - 2)));
const segmentCount = palette.length - 1;
const i = Math.floor(scaledNorm * segmentCount);
const fraction = (scaledNorm * segmentCount) - i;
if (i >= segmentCount)
return palette[segmentCount];
const p1 = palette[i];
const p2 = palette[i + 1];
return [
Math.round(p1[0] + (p2[0] - p1[0]) * fraction),
Math.round(p1[1] + (p2[1] - p1[1]) * fraction),
Math.round(p1[2] + (p2[2] - p1[2]) * fraction)
];
}
render() {
const maxIter = this.get_maxIter();
const imgData = new ImageData(this.width, this.height);
const data = imgData.data;
const range = 4 / Math.sqrt(this.currentView.zoom);
const reMin = this.currentView.re - range / 2;
const imMin = this.currentView.im - range / 2;
for (let y = 0; y < this.height; y++) {
const im = imMin + (y / this.height) * range;
for (let x = 0; x < this.width; x++) {
const re = reMin + (x / this.width) * range;
const { iter, smooth } = math_1.FractalMath.getIteration(re, im, maxIter, this.currentType, this.juliaC);
let r = 0, g = 0, b = 0;
if (iter < maxIter) {
const [pr, pg, pb] = this.getColor(smooth, maxIter);
r = pr;
g = pg;
b = pb;
}
const idx = (y * this.width + x) * 4;
data[idx] = r;
data[idx + 1] = g;
data[idx + 2] = b;
data[idx + 3] = 255;
}
}
this.ctx.putImageData(imgData, 0, 0);
}
}
exports.Renderer = Renderer;
};
// ── entry ──
__require("src/main.ts");
})();
</script>
</body>
</html>
<!-- agent-meta {"model":"google/gemma-4-12b","provider":"lmstudio","persona":"typescript","sessionId":"958c107a-29bb-4b4d-8179-dda38ad60e53","tokensIn":2290516,"tokensOut":54569,"tokensTotal":2345085,"cost":0,"turns":63,"toolCalls":62,"failedToolCalls":22,"timestamp":"2026-08-02T04:39:56.867Z"} -->