live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Kaleidoscope</title>
<style>
:root {
--bg-color: #060010;
--hue-1: #f0abfc;
--hue-2: #22d3ee;
--hue-3: #fbbf24;
}
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; width: 100vw; height: 100vh; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c');
const ctx = cv.getContext('2d');
const off = document.createElement('canvas');
const octx = off.getContext('2d');
const css = getComputedStyle(document.documentElement);
function hex(h) {
h = (h || '').trim().replace('#', '');
if (h.length === 3) h = h.split('').map(c => c + c).join('');
const n = parseInt(h, 16); return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
const c1 = hex(css.getPropertyValue('--hue-1') || '#f0abfc');
const c2 = hex(css.getPropertyValue('--hue-2') || '#22d3ee');
const c3 = hex(css.getPropertyValue('--hue-3') || '#fbbf24');
const SCALE = 4;
const SEG = 8; // mirror segments
let lw, lh, img, buf, cx, cy;
function build() {
lw = off.width = Math.ceil(innerWidth / SCALE);
lh = off.height = Math.ceil(innerHeight / SCALE);
cv.width = innerWidth; cv.height = innerHeight;
img = octx.createImageData(lw, lh); buf = img.data;
cx = lw / 2; cy = lh / 2;
ctx.imageSmoothingEnabled = true;
}
addEventListener('resize', build);
build();
const seg = Math.PI * 2 / SEG;
function step(t) {
for (let y = 0; y < lh; y++) {
for (let x = 0; x < lw; x++) {
const dx = x - cx, dy = y - cy;
const r = Math.sqrt(dx * dx + dy * dy);
let a = Math.atan2(dy, dx) + t * 0.25;
// fold angle into a mirrored wedge -> kaleidoscope symmetry
a = a % seg; if (a < 0) a += seg;
if (a > seg / 2) a = seg - a;
// procedural mandala pattern in (a, r)
const v1 = Math.sin(r * 0.09 - t * 2) ;
const v2 = Math.sin(a * 9 + t * 1.5);
const v3 = Math.sin(r * 0.04 + a * 6 - t);
const m1 = (v1 * v2 * 0.5 + 0.5);
const m2 = (v3 * 0.5 + 0.5);
let r0 = c1[0] * m1 + c2[0] * (1 - m1);
let g0 = c1[1] * m1 + c2[1] * (1 - m1);
let b0 = c1[2] * m1 + c2[2] * (1 - m1);
r0 = r0 * (1 - m2) + c3[0] * m2;
g0 = g0 * (1 - m2) + c3[1] * m2;
b0 = b0 * (1 - m2) + c3[2] * m2;
const bright = 0.35 + 0.65 * Math.max(0, v1 * v2);
const i = (y * lw + x) * 4;
buf[i] = r0 * bright; buf[i + 1] = g0 * bright; buf[i + 2] = b0 * bright; buf[i + 3] = 255;
}
}
octx.putImageData(img, 0, 0);
ctx.drawImage(off, 0, 0, cv.width, cv.height);
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
step(1.0);
} else {
let t = 0;
(function loop() { t += 0.02; step(t); requestAnimationFrame(loop); })();
}
</script>
</body>
</html> About this animation
A symmetric mandala of mirrored light that endlessly rotates and morphs, radial petals and rings blooming from a bright core.
Related