const c = document.createElement('canvas');
document.body.appendChild(c);
c.style.width = '100%'; c.style.height = '100%';
const ctx = c.getContext('2d');
const cs = getComputedStyle(document.documentElement);
const rgb = (cs.getPropertyValue('--accent').trim() || '#5b5bf7').match(/[0-9a-f]{2}/gi).map((x) => parseInt(x, 16));
let w, h;
function resize() {
const dpr = Math.min(devicePixelRatio || 1, 2);
w = innerWidth; h = innerHeight;
c.width = w * dpr; c.height = h * dpr;
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.fillStyle = '#0d0d12'; ctx.fillRect(0, 0, w, h);
buildAndReset();
}
const A = -1.4, B = 1.6, CPARAM = 1.0, D = 0.7;
let traj = [], scaleX = 1, scaleY = 1, offX = 0, offY = 0, drawn = 0;
function buildAndReset() {
let x = 0.1, y = 0.1;
const N = 42000;
traj = new Float32Array(N * 2);
let minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity;
for (let i = 0; i < N; i++) {
const nx = Math.sin(A * y) + CPARAM * Math.cos(A * x);
const ny = Math.sin(B * x) + D * Math.cos(B * y);
x = nx; y = ny;
traj[i * 2] = x; traj[i * 2 + 1] = y;
if (x < minX) minX = x; if (x > maxX) maxX = x; if (y < minY) minY = y; if (y > maxY) maxY = y;
}
scaleX = (w * 0.86) / (maxX - minX); scaleY = (h * 0.86) / (maxY - minY);
const s = Math.min(scaleX, scaleY);
offX = w / 2 - ((minX + maxX) / 2) * s; offY = h / 2 - ((minY + maxY) / 2) * s;
scaleX = scaleY = s;
drawn = 0;
ctx.fillStyle = '#0d0d12'; ctx.fillRect(0, 0, w, h);
}
addEventListener('resize', resize);
resize();
function drawBatch() {
ctx.fillStyle = 'rgba(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ',0.05)';
const total = traj.length / 2, batch = 900;
const end = Math.min(total, drawn + batch);
for (let i = drawn; i < end; i++) {
ctx.fillRect(traj[i * 2] * scaleX + offX, traj[i * 2 + 1] * scaleY + offY, 1.3, 1.3);
}
drawn = end;
}
for (let i = 0; i < 30; i++) drawBatch(); // 스크린샷 시점에 상당 부분이 이미 드러나도록
(function loop() {
if (drawn >= traj.length / 2) { if (Math.random() < 0.004) buildAndReset(); }
else drawBatch();
requestAnimationFrame(loop);
})();