← Fractal Explorer results

Fractal Explorer

Gemma 4 12b · typescript

Initial view of Fractal Explorer

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

Reviews & generation details

Reviews

No human review yet. Unreviewed does not mean unsuccessful.

Generation context

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

Generation prompt · exact text unavailable

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

Inspect original source 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"} -->