// Julia distance-isolines background. // Renderer: WebGL (default) -> Canvas2D -> flat dark backdrop. // // The 512x512 distance field is precomputed (live per-pixel iteration is too slow). // It ships as `dist-strips.bin`: full-resolution horizontal bands, each WebP-compressed, // concatenated in CENTER-OUT order. We stream the file and upload each band to its texture // sub-rectangle as its bytes arrive. // // The DISPLAY FOLLOWS THE STREAM: instead of drawing the whole screen over a half-loaded // texture (which looked like a "glow"), the shader only reveals the field rows that have // actually streamed in, blooming outward from the center over a flat dark backdrop. The // revealed extent eases smoothly, so it reads as an intentional reveal, not jank. // Palette: BSOD-blue, smooth cosine bands, 1024-entry LUT (Canvas2D path only). var FIELD_URL = '/routes/root/dist-strips.bin?v=1'; // streamed WebP bands (bump ?v on regen) var DIST_URL = '/routes/root/dist.png'; // monolithic source (Canvas2D fallback) var DARK_HEX = '#060a10'; // backdrop / unloaded tone // The old palette cycle advanced quickly enough to look shimmery on some displays. // Keep the camera drift slow, cap redraws below display refresh, and pause real work // while the tab is hidden. The field still streams at full speed; only presentation // frames are rate-limited. var MOTION_SPEED = 0.045; var COLOR_CYCLE_SPEED = 4.2; var WEBGL_TARGET_FPS = 30; var CANVAS_TARGET_FPS = 24; var DITHER_STRENGTH = 0.45; function _prefersReducedMotion() { return window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; } function initFractal(canvasId) { console.log('[fractal-gl] initFractal:', canvasId); var canvas = document.getElementById(canvasId); if (!canvas) { console.warn('[fractal-gl] canvas not found:', canvasId); return; } // Flat dark backdrop: clean start state, and the tone the reveal blooms over. document.body.style.background = DARK_HEX; var gl = null; try { gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl'); } catch (e) { console.warn('[fractal-gl] WebGL exception:', e); } if (gl) { console.log('[fractal-gl] using WebGL path'); initWebGL(canvas, gl); } else { console.warn('[fractal-gl] WebGL unavailable; using Canvas2D fallback'); initCanvas2D(canvas); } } // ---------------------------------------------------------------- // Shared streamed-field loader // Emits each band as a decoded ImageBitmap the moment its bytes land. // cb = { onHeader(header), onStrip(bitmap, y, h), onError(err) } // File: u32(LE) headerLen | JSON header | concatenated band WebPs. // JSON: { fullW, fullH, bands:[{y,h,off,len}] } in stream (center-out) order. // ---------------------------------------------------------------- function streamFieldBands(cb) { var TD = new TextDecoder(); function emitBand(bytes, b) { // bytes is an independent copy, safe to keep past the next read. // Disable color management so the grayscale field bytes survive intact. createImageBitmap(new Blob([bytes], { type: 'image/webp' }), { colorSpaceConversion: 'none', premultiplyAlpha: 'none' }) .then(function (bmp) { try { cb.onStrip(bmp, b.y, b.h); } catch (e) { console.warn('[fractal-gl] band upload failed', e); } }) .catch(function (e) { console.warn('[fractal-gl] band decode failed', e); }); } function emitAll(buf) { var hlen = new DataView(buf.buffer, buf.byteOffset, 4).getUint32(0, true); var header = JSON.parse(TD.decode(buf.subarray(4, 4 + hlen))); var ps = 4 + hlen; cb.onHeader(header); header.bands.forEach(function (b) { emitBand(buf.subarray(ps + b.off, ps + b.off + b.len).slice(), b); }); } fetch(FIELD_URL).then(function (res) { if (!res.ok) throw new Error('HTTP ' + res.status); if (!res.body || !res.body.getReader) { return res.arrayBuffer().then(function (ab) { emitAll(new Uint8Array(ab)); }); } var reader = res.body.getReader(); var chunks = []; var total = 0; var header = null; var ps = 0; var next = 0; function assemble() { var out = new Uint8Array(total); var o = 0; for (var i = 0; i < chunks.length; i++) { out.set(chunks[i], o); o += chunks[i].length; } return out; } function step() { return reader.read().then(function (r) { if (r.value) { chunks.push(r.value); total += r.value.length; } var buf = assemble(); if (!header && total >= 4) { var hlen = new DataView(buf.buffer, 0, 4).getUint32(0, true); if (total >= 4 + hlen) { header = JSON.parse(TD.decode(buf.subarray(4, 4 + hlen))); ps = 4 + hlen; cb.onHeader(header); } } if (header) { while (next < header.bands.length) { var b = header.bands[next]; var end = ps + b.off + b.len; if (total < end) break; emitBand(buf.subarray(ps + b.off, end).slice(), b); next++; } } if (r.done) return; return step(); }); } return step(); }).catch(function (e) { console.error('[fractal-gl] field stream failed', e); if (cb.onError) cb.onError(e); }); } function _hslToRgb(h, s, l) { var a = s * Math.min(l, 1 - l); function f(n) { var k = (n + h * 12) % 12; return Math.round((l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1))) * 255); } return [f(0), f(8), f(4)]; } // 1024-entry palette: 4x finer than the iteration-count space (0..255), // which kills visible quantization banding in the band peaks/troughs. var PALETTE_SIZE = 1024; function _buildPalette32() { var p32 = new Uint32Array(PALETTE_SIZE); for (var i = 0; i < PALETTE_SIZE; i++) { var f = i / 4; // logical index in [0, 256) var band = 0.5 + 0.5 * Math.cos(f * 2 * Math.PI / 56); var h = 0.665 + 0.015 * Math.sin(f * Math.PI / 128); var s = 0.80; var l = 0.18 + 0.28 * band; var rgb = _hslToRgb(h, s, l); p32[i] = (255 << 24) | (rgb[2] << 16) | (rgb[1] << 8) | rgb[0]; } return p32; } // ---------------------------------------------------------------- // WebGL path (default) — streams bands in, reveals only what has loaded // ---------------------------------------------------------------- var FIELD_VERT_GLSL = [ 'attribute vec2 a_pos;', 'void main() { gl_Position = vec4(a_pos, 0.0, 1.0); }' ].join('\n'); // Same field-sampling math as before, plus a feathered reveal mask gated on the // loaded field-row extent [u_loadV0, u_loadV1]. Unloaded rows show the dark backdrop. var FIELD_FRAG_GLSL = [ 'precision highp float;', 'uniform float u_time;', 'uniform vec2 u_resolution;', 'uniform sampler2D u_dist;', 'uniform float u_loadV0;', 'uniform float u_loadV1;', 'const vec3 DARK = vec3(0.024, 0.039, 0.063);', 'vec3 hslToRgb(float h, float s, float l) {', ' float a = s * min(l, 1.0 - l);', ' vec3 k = mod(vec3(0.0, 8.0, 4.0) + h * 12.0, 12.0);', ' vec3 v = max(min(min(k - 3.0, 9.0 - k), vec3(1.0)), vec3(-1.0));', ' return vec3(l) - a * v;', '}', 'vec3 calmBand(float idx) {', ' float f = mod(idx, 256.0);', ' float band = 0.5 + 0.5 * cos(f * 6.28318530 / 56.0);', ' float h = 0.665 + 0.015 * sin(f * 3.14159265 / 128.0);', ' float s = 0.80;', ' float l = 0.18 + 0.28 * band;', ' return hslToRgb(h, s, l);', '}', 'void main() {', ' vec2 uv = gl_FragCoord.xy / u_resolution;', ' float aspect = u_resolution.x / u_resolution.y;', ' float t = u_time * 0.045;', ' float maxH = 1.0 / max(aspect, 1.0);', ' float zoomFrac = 0.65 + 0.15 * sin(t * 0.4);', ' float halfH = maxH * zoomFrac * 0.5;', ' float halfW = halfH * aspect;', ' float fx = 0.5 - halfW;', ' float fy = 0.5 - halfH;', ' float cx = 0.5 + fx * 0.5 * cos(t * 0.55);', ' float cy = 0.5 + fy * 0.5 * sin(t * 0.45);', ' float sx = cx + (uv.x - 0.5) * 2.0 * halfW;', ' float sy = cy + (uv.y - 0.5) * 2.0 * halfH;', ' float fv = 1.0 - sy;', // field v-coordinate (texture uploaded natural orientation) ' // Reveal only loaded field rows; nothing loaded yet => all dark.', ' if (u_loadV1 - u_loadV0 < 0.001) { gl_FragColor = vec4(DARK, 1.0); return; }', ' float E = 0.03;', ' float m = smoothstep(u_loadV0 - E, u_loadV0 + E, fv) *', ' (1.0 - smoothstep(u_loadV1 - E, u_loadV1 + E, fv));', ' float v = texture2D(u_dist, vec2(sx, fv)).r * 255.0;', ' float ditherMix = clamp((v - 180.0) / 60.0, 0.0, 1.0);', ' float noise = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453) - 0.5;', ' v += noise * 0.45 * ditherMix;', ' float offset = u_time * 4.2;', ' vec3 color = calmBand(v - offset);', ' gl_FragColor = vec4(mix(DARK, color, m), 1.0);', '}' ].join('\n'); function initWebGL(canvas, gl) { function compile(type, src) { var s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { console.error('[fractal-gl] shader compile error:', gl.getShaderInfoLog(s)); return null; } return s; } var vs = compile(gl.VERTEX_SHADER, FIELD_VERT_GLSL); var fs = compile(gl.FRAGMENT_SHADER, FIELD_FRAG_GLSL); if (!vs || !fs) return initCanvas2D(canvas); var prog = gl.createProgram(); gl.attachShader(prog, vs); gl.attachShader(prog, fs); gl.linkProgram(prog); if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) { console.error('[fractal-gl] link error:', gl.getProgramInfoLog(prog)); return initCanvas2D(canvas); } gl.useProgram(prog); var buf = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, buf); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW); var aPos = gl.getAttribLocation(prog, 'a_pos'); gl.enableVertexAttribArray(aPos); gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0); var uTime = gl.getUniformLocation(prog, 'u_time'); var uRes = gl.getUniformLocation(prog, 'u_resolution'); var uDist = gl.getUniformLocation(prog, 'u_dist'); var uV0 = gl.getUniformLocation(prog, 'u_loadV0'); var uV1 = gl.getUniformLocation(prog, 'u_loadV1'); var tex = gl.createTexture(); gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255])); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); gl.uniform1i(uDist, 0); // Drawing the band bitmap to a 2D canvas yields a universally-supported // TexImageSource (works on WebGL1 and WebGL2 alike). var scratch = document.createElement('canvas'); var sctx = scratch.getContext('2d'); // ---- reveal state: which field rows have streamed in ---- var totalH = 1; var spatial = []; // bands sorted top->bottom: {y, h} var loadedSlots = []; // parallel booleans var centerSlot = 0, lastSlot = 0; var targetV0 = 0.5, targetV1 = 0.5; // loaded extent (v-fraction), grows from center var dispV0 = 0.5, dispV1 = 0.5; // eased toward target for a smooth bloom // Bloom only on the first page of a tab session; on later navigations the field // is cached, so snap straight to it instead of replaying the ~1s intro. var revealed = false; try { revealed = sessionStorage.getItem('fractalRevealed') === '1'; } catch (e) {} streamFieldBands({ onHeader: function (header) { var w = header.fullW, h = header.fullH; totalH = h; spatial = header.bands.slice().sort(function (a, b) { return a.y - b.y; }); loadedSlots = spatial.map(function () { return false; }); lastSlot = spatial.length - 1; centerSlot = 0; for (var i = 0; i < spatial.length; i++) { if (h / 2 >= spatial[i].y && h / 2 < spatial[i].y + spatial[i].h) { centerSlot = i; break; } } // Allocate the full texture (black; masked until rows load). var fill = new Uint8Array(w * h * 4); for (var k = 3; k < fill.length; k += 4) fill[k] = 255; gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, w, h, 0, gl.RGBA, gl.UNSIGNED_BYTE, fill); }, onStrip: function (bitmap, y, h) { scratch.width = bitmap.width; scratch.height = bitmap.height; sctx.drawImage(bitmap, 0, 0); gl.bindTexture(gl.TEXTURE_2D, tex); gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, y, gl.RGBA, gl.UNSIGNED_BYTE, scratch); var s = -1; for (var i = 0; i < spatial.length; i++) { if (spatial[i].y === y) { s = i; break; } } if (s < 0) return; loadedSlots[s] = true; // Grow the contiguous loaded run around the center (guards against a late // middle band briefly revealing an unloaded gap). if (loadedSlots[centerSlot]) { var lo = centerSlot; while (lo - 1 >= 0 && loadedSlots[lo - 1]) lo--; var hi = centerSlot; while (hi + 1 < spatial.length && loadedSlots[hi + 1]) hi++; // Push the extent past the field edge once the outermost band is in, so the // feather never darkens the true field edge in the steady state. targetV0 = (lo === 0) ? -1.0 : spatial[lo].y / totalH; targetV1 = (hi === lastSlot) ? 2.0 : (spatial[hi].y + spatial[hi].h) / totalH; if (lo === 0 && hi === lastSlot) { try { sessionStorage.setItem('fractalRevealed', '1'); } catch (e) {} } } } }); function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; gl.viewport(0, 0, canvas.width, canvas.height); } resize(); window.addEventListener('resize', resize); var startTime = performance.now(); var reducedMotion = _prefersReducedMotion(); var targetFrameMs = 1000 / WEBGL_TARGET_FPS; var lastFrameTime = 0; function frame(now) { requestAnimationFrame(frame); if (document.hidden) return; if (now - lastFrameTime < targetFrameMs) return; lastFrameTime = now; var t = reducedMotion ? 0 : (now - startTime) * 0.001; // Ease the displayed extent toward the loaded extent for a smooth bloom. // On a repeat visit (cached field) snap instead of replaying the intro. var ease = revealed ? 1.0 : 0.12; dispV0 += (targetV0 - dispV0) * ease; dispV1 += (targetV1 - dispV1) * ease; gl.uniform1f(uTime, t); gl.uniform2f(uRes, canvas.width, canvas.height); gl.uniform1f(uV0, dispV0); gl.uniform1f(uV1, dispV1); gl.drawArrays(gl.TRIANGLES, 0, 3); } requestAnimationFrame(frame); console.log('[fractal-gl] WebGL loop started'); } // ---------------------------------------------------------------- // Canvas2D fallback (rare; ancient / no-GPU browsers) // - 1024-entry palette (4x finer than the byte-quantized iteration value) // - bilinear sample of source buffer (no nearest-neighbor jaggies) // - Uint32 writes (one packed write per pixel instead of four byte writes) // Loads the monolithic dist.png and renders once decoded (fades in). // ---------------------------------------------------------------- function initCanvas2D(canvas) { var ctx2d = canvas.getContext('2d'); if (!ctx2d) { console.error('[fractal-gl] Canvas2D also unavailable; body backdrop remains'); return; } var palette32 = _buildPalette32(); var img = new Image(); img.onerror = function (e) { console.error('[fractal-gl] dist.png load failed (canvas2d)', e); }; img.onload = function () { var SW = img.width, SH = img.height; console.log('[fractal-gl] PNG decoded (canvas2d):', SW, 'x', SH); var off = document.createElement('canvas'); off.width = SW; off.height = SH; var octx = off.getContext('2d'); octx.drawImage(img, 0, 0); var data = octx.getImageData(0, 0, SW, SH).data; var gray = new Uint8Array(SW * SH); for (var i = 0, j = 0; i < gray.length; i++, j += 4) gray[i] = data[j]; // Fade the canvas in once the first frame is ready. canvas.style.transition = 'opacity 400ms ease'; canvas.style.opacity = '0'; // Higher internal resolution = sharper. Capped to keep frame budget reasonable. function targetSize() { var h = Math.min(800, Math.max(360, Math.floor(window.innerHeight * 0.7))); var aspect = window.innerWidth / window.innerHeight; return { w: Math.max(2, Math.floor(h * aspect)), h: h }; } function resize() { var s = targetSize(); canvas.width = s.w; canvas.height = s.h; } resize(); window.addEventListener('resize', resize); var imgOut = ctx2d.createImageData(canvas.width, canvas.height); var out32 = new Uint32Array(imgOut.data.buffer); var lastW = canvas.width, lastH = canvas.height; var startTime = performance.now(); var reducedMotion = _prefersReducedMotion(); var targetFrameMs = 1000 / CANVAS_TARGET_FPS; var lastFrameTime = 0; var faded = false; // 4x4 Bayer matrix, recentered to ±0.6 source units. Only applied where // v is in the outer-smooth band (ramp 180→240) so inner detail stays crisp. var BAYER = new Float32Array([0,8,2,10, 12,4,14,6, 3,11,1,9, 15,7,13,5]); for (var bi = 0; bi < 16; bi++) BAYER[bi] = (BAYER[bi] - 7.5) * 0.6 / 7.5; function frame(now) { requestAnimationFrame(frame); if (document.hidden) return; if (now - lastFrameTime < targetFrameMs) return; lastFrameTime = now; var W = canvas.width, H = canvas.height; if (W !== lastW || H !== lastH) { imgOut = ctx2d.createImageData(W, H); out32 = new Uint32Array(imgOut.data.buffer); lastW = W; lastH = H; } var elapsed = reducedMotion ? 0 : (now - startTime) * 0.001; var t = elapsed * MOTION_SPEED; var aspect = W / H; var maxH = 1.0 / Math.max(aspect, 1.0); var zoomFrac = 0.65 + 0.15 * Math.sin(t * 0.4); var halfH = maxH * zoomFrac * 0.5; var halfW = halfH * aspect; var fxRem = 0.5 - halfW, fyRem = 0.5 - halfH; var cx = 0.5 + fxRem * 0.5 * Math.cos(t * 0.55); var cy = 0.5 + fyRem * 0.5 * Math.sin(t * 0.45); var x0 = (cx - halfW) * SW; var y0 = (cy - halfH) * SH; var dxs = (2 * halfW) / W * SW; var dys = (2 * halfH) / H * SH; // offset in 1024-entry space (4x granularity), deliberately slow to avoid shimmer. var offsetHD = (elapsed * COLOR_CYCLE_SPEED * 4) | 0; var SW1 = SW - 1, SH1 = SH - 1; var idx = 0; var sy = y0; for (var y = 0; y < H; y++) { var sy0i = sy | 0; if (sy0i < 0) sy0i = 0; else if (sy0i > SH1) sy0i = SH1; var sy1i = sy0i + 1; if (sy1i > SH1) sy1i = SH1; var fy = sy - (sy | 0); var ify = 1 - fy; var row0 = sy0i * SW; var row1 = sy1i * SW; var sx = x0; for (var x = 0; x < W; x++) { var sx0i = sx | 0; if (sx0i < 0) sx0i = 0; else if (sx0i > SW1) sx0i = SW1; var sx1i = sx0i + 1; if (sx1i > SW1) sx1i = SW1; var fx = sx - (sx | 0); var ifx = 1 - fx; var a = gray[row0 + sx0i]; var b = gray[row0 + sx1i]; var c = gray[row1 + sx0i]; var d = gray[row1 + sx1i]; var v = (a * ifx + b * fx) * ify + (c * ifx + d * fx) * fy; // Selective dither: only the outer smooth region (v 180→240 ramps in). if (v > 180) { var mix = v >= 240 ? 1 : (v - 180) / 60; v += BAYER[(y & 3) * 4 + (x & 3)] * DITHER_STRENGTH * mix; } // Multiply by 4 to index into 1024-entry palette, then wrap. out32[idx++] = palette32[(((v * 4) | 0) - offsetHD) & 1023]; sx += dxs; } sy += dys; } ctx2d.putImageData(imgOut, 0, 0); if (!faded) { faded = true; canvas.style.opacity = '1'; } } requestAnimationFrame(frame); console.log('[fractal-gl] Canvas2D loop started; internal', canvas.width, 'x', canvas.height); }; img.src = DIST_URL; } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', function () { initFractal('fractal-canvas'); }); } else { initFractal('fractal-canvas'); }