Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion sass/_base.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ html {
font-size: $font-size;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background: $bg;
}

body {
font-family: $font-sans;
line-height: $line-height;
color: $text;
background: $bg;
background: transparent;
min-height: 100vh;
}

Expand Down
29 changes: 29 additions & 0 deletions static/background.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Background dispatcher — picks a random renderer on each page load
(function () {
'use strict';

var backgrounds = [
'mandelbrot.js',
'bg-julia.js',
'bg-reaction-diffusion.js',
'bg-flowfield.js',
'bg-lorenz.js',
'bg-waves.js',
'bg-contours.js',
'bg-life.js',
'bg-lissajous.js',
'bg-sierpinski.js',
'bg-domain.js',
];

var pick = backgrounds[Math.floor(Math.random() * backgrounds.length)];

// Resolve path relative to this script's location
var scripts = document.getElementsByTagName('script');
var thisScript = scripts[scripts.length - 1];
var basePath = thisScript.src.substring(0, thisScript.src.lastIndexOf('/') + 1);

var script = document.createElement('script');
script.src = basePath + pick;
document.body.appendChild(script);
})();
119 changes: 119 additions & 0 deletions static/bg-contours.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Perlin noise contour lines — slowly morphing topographic map
(function () {
'use strict';

const canvas = document.getElementById('fractal-bg');
if (!canvas) return;
const ctx = canvas.getContext('2d');

const W = 360;
const H = 240;
canvas.width = W;
canvas.height = H;

const imgData = ctx.createImageData(W, H);
const buf = imgData.data;

// Permutation table
const PERM = new Uint8Array(512);
for (let i = 0; i < 256; i++) PERM[i] = i;
for (let i = 255; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
const tmp = PERM[i]; PERM[i] = PERM[j]; PERM[j] = tmp;
}
for (let i = 0; i < 256; i++) PERM[256 + i] = PERM[i];

function fade(t) { return t * t * t * (t * (t * 6 - 15) + 10); }
function lerp(a, b, t) { return a + (b - a) * t; }

function grad(hash, x, y) {
const h = hash & 3;
const u = h < 2 ? x : y;
const v = h < 2 ? y : x;
return ((h & 1) ? -u : u) + ((h & 2) ? -v : v);
}

function noise2d(x, y) {
const xi = Math.floor(x) & 255;
const yi = Math.floor(y) & 255;
const xf = x - Math.floor(x);
const yf = y - Math.floor(y);
const u = fade(xf);
const v = fade(yf);
const aa = PERM[PERM[xi] + yi];
const ab = PERM[PERM[xi] + yi + 1];
const ba = PERM[PERM[xi + 1] + yi];
const bb = PERM[PERM[xi + 1] + yi + 1];
return lerp(
lerp(grad(aa, xf, yf), grad(ba, xf - 1, yf), u),
lerp(grad(ab, xf, yf - 1), grad(bb, xf - 1, yf - 1), u),
v
);
}

function fbm(x, y, octaves) {
let val = 0, amp = 1, freq = 1, total = 0;
for (let i = 0; i < octaves; i++) {
val += noise2d(x * freq, y * freq) * amp;
total += amp;
amp *= 0.5;
freq *= 2;
}
return val / total;
}

let time = 0;
let animId;
let lastFrame = 0;
const frameInterval = 80; // Slower — contours don't need fast updates
const numContours = 12;

function render(timestamp) {
animId = requestAnimationFrame(render);
if (timestamp - lastFrame < frameInterval) return;
lastFrame = timestamp;

const scale = 0.012;

for (let py = 0; py < H; py++) {
for (let px = 0; px < W; px++) {
const n = fbm(px * scale + time, py * scale, 4);
// Normalize to 0-1
const v = (n + 1) * 0.5;

// Create contour lines: sharp brightness at specific iso-values
const contourVal = v * numContours;
const frac = contourVal - Math.floor(contourVal);
// Thin contour line when frac is near 0 or 1
const edge = Math.min(frac, 1 - frac);
const line = edge < 0.06 ? 1.0 - edge / 0.06 : 0;

// Base fill between contours
const fill = v * 0.15;
const brightness = fill + line * 0.7;

const idx = (py * W + px) * 4;
buf[idx] = (10 + brightness * 98) | 0;
buf[idx + 1] = (13 + brightness * 127) | 0;
buf[idx + 2] = (20 + brightness * 235) | 0;
buf[idx + 3] = 255;
}
}

ctx.putImageData(imgData, 0, 0);
time += 0.008;
}

function onVisibility() {
if (document.hidden) { cancelAnimationFrame(animId); }
else { animId = requestAnimationFrame(render); }
}

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
render(0);
cancelAnimationFrame(animId);
} else {
document.addEventListener('visibilitychange', onVisibility);
animId = requestAnimationFrame(render);
}
})();
101 changes: 101 additions & 0 deletions static/bg-domain.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Domain coloring — complex function visualization with slowly morphing parameters
(function () {
'use strict';

const canvas = document.getElementById('fractal-bg');
if (!canvas) return;
const ctx = canvas.getContext('2d');

const W = 360;
const H = 240;
canvas.width = W;
canvas.height = H;

const imgData = ctx.createImageData(W, H);
const buf = imgData.data;

let time = 0;
let animId;
let lastFrame = 0;
const frameInterval = 50;

// Complex arithmetic helpers
function cmul(ar, ai, br, bi) { return [ar * br - ai * bi, ar * bi + ai * br]; }
function cabs(r, i) { return Math.sqrt(r * r + i * i); }
function carg(r, i) { return Math.atan2(i, r); }

function render(timestamp) {
animId = requestAnimationFrame(render);
if (timestamp - lastFrame < frameInterval) return;
lastFrame = timestamp;

const scale = 4;
const aspect = W / H;

// Morphing parameter for the complex function
const pr = Math.sin(time * 0.3) * 0.8;
const pi = Math.cos(time * 0.2) * 0.8;

for (let py = 0; py < H; py++) {
for (let px = 0; px < W; px++) {
// Map pixel to complex plane
let zr = (px / W - 0.5) * scale * aspect;
let zi = (py / H - 0.5) * scale;

// f(z) = z^3 + p*z + 1 (morphing cubic)
const z2 = cmul(zr, zi, zr, zi);
const z3 = cmul(z2[0], z2[1], zr, zi);
const pz = cmul(pr, pi, zr, zi);
const wr = z3[0] + pz[0] + 1;
const wi = z3[1] + pz[1];

const mag = cabs(wr, wi);
const arg = carg(wr, wi);

// Map argument (angle) to hue, magnitude to brightness
// Use site palette colors based on angle
const t = (arg / (Math.PI * 2) + 1) % 1;
const brightness = 1 - 1 / (1 + mag * 0.3);

// Contour lines on magnitude
const logMag = Math.log(mag + 1);
const contour = Math.abs(logMag - Math.round(logMag)) < 0.08 ? 0.3 : 0;

let r, g, b;
if (t < 0.33) {
const s = t / 0.33;
r = 10 + s * 98; g = 13 + s * 127; b = 20 + s * 235;
} else if (t < 0.66) {
const s = (t - 0.33) / 0.33;
r = 108 - s * 74; g = 140 + s * 71; b = 255 - s * 17;
} else {
const s = (t - 0.66) / 0.34;
r = 34 + s * 158; g = 211 - s * 79; b = 238 + s * 14;
}

const v = brightness + contour;
const idx = (py * W + px) * 4;
buf[idx] = Math.min(255, (r * v) | 0);
buf[idx + 1] = Math.min(255, (g * v) | 0);
buf[idx + 2] = Math.min(255, (b * v) | 0);
buf[idx + 3] = 255;
}
}

ctx.putImageData(imgData, 0, 0);
time += 0.008;
}

function onVisibility() {
if (document.hidden) { cancelAnimationFrame(animId); }
else { animId = requestAnimationFrame(render); }
}

if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
render(0);
cancelAnimationFrame(animId);
} else {
document.addEventListener('visibilitychange', onVisibility);
animId = requestAnimationFrame(render);
}
})();
Loading
Loading