live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Truchet Tiles</title>
<style>
:root { --bg-color: #0a0a12; --line-color: #f5c542; --line-2: #ef476f; }
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c'), ctx = cv.getContext('2d');
const css = getComputedStyle(document.documentElement);
const bg = (css.getPropertyValue('--bg-color') || '#0a0a12').trim();
const l1 = (css.getPropertyValue('--line-color') || '#f5c542').trim();
const l2 = (css.getPropertyValue('--line-2') || '#ef476f').trim();
const T = 56; let w, h, cols, rows;
function build() { w = cv.width = innerWidth; h = cv.height = innerHeight; cols = Math.ceil(w / T) + 1; rows = Math.ceil(h / T) + 1; }
addEventListener('resize', build); build();
// smooth noise for evolving tile orientation
function hash(x, y) { let n = Math.sin(x * 127.1 + y * 311.7) * 43758.5453; return n - Math.floor(n); }
function step(t) {
ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
ctx.lineWidth = 5; ctx.lineCap = 'round';
for (let j = 0; j < rows; j++) {
for (let i = 0; i < cols; i++) {
const x = i * T, y = j * T, r = T / 2;
const flip = Math.sin(hash(i, j) * 6.28 + t * 0.6) > 0;
ctx.strokeStyle = (i + j) % 2 ? l1 : l2;
ctx.beginPath();
if (flip) {
ctx.arc(x, y, r, 0, Math.PI / 2);
ctx.arc(x + T, y + T, r, Math.PI, Math.PI * 1.5);
} else {
ctx.arc(x + T, y, r, Math.PI / 2, Math.PI);
ctx.arc(x, y + T, r, Math.PI * 1.5, Math.PI * 2);
}
ctx.stroke();
}
}
}
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
Interlocking arcs tile into an ever-shifting woven maze, connections forming and reforming across the grid in two-tone neon.
Related