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 ACCENT = cs.getPropertyValue('--accent').trim() || '#5b5bf7';
// 타일 소켓: [N,E,S,W] 1=연결됨 0=끊김
const TILES = [[0, 0, 0, 0], [0, 1, 0, 1], [1, 0, 1, 0], [0, 1, 1, 0], [0, 0, 1, 1], [1, 0, 0, 1], [1, 1, 0, 0], [1, 1, 1, 1]];
let w, h, cols, rows, SIZE = 42, cellsGrid = [], order = [], revealed = 0, genStart = 0;
function collapse() {
const n = cols * rows;
cellsGrid = new Array(n).fill(-1);
order = [...Array(n).keys()];
for (let i = order.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [order[i], order[j]] = [order[j], order[i]]; }
for (const idx of order) {
const x = idx % cols, y = (idx / cols) | 0;
let candidates = TILES.map((_, ti) => ti);
const nb = [[x, y - 1, 2, 0], [x + 1, y, 3, 1], [x, y + 1, 0, 2], [x - 1, y, 1, 3]];
for (const [nx, ny, mySide, theirSide] of nb) {
if (nx < 0 || ny < 0 || nx >= cols || ny >= rows) continue;
const other = cellsGrid[ny * cols + nx];
if (other < 0) continue;
candidates = candidates.filter((ti) => TILES[ti][mySide] === TILES[other][theirSide]);
}
cellsGrid[idx] = candidates.length ? candidates[Math.floor(Math.random() * candidates.length)] : 0;
}
revealed = 0;
genStart = 0;
}
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);
cols = Math.max(4, Math.floor(w / SIZE)); rows = Math.max(3, Math.floor(h / SIZE));
collapse();
}
addEventListener('resize', resize);
resize();
function drawTile(cx, cy, sockets) {
ctx.strokeStyle = ACCENT; ctx.lineWidth = 5; ctx.lineCap = 'round';
const half = SIZE / 2;
const dirs = [[0, -half], [half, 0], [0, half], [-half, 0]];
for (let i = 0; i < 4; i++) if (sockets[i]) { ctx.beginPath(); ctx.moveTo(cx, cy); ctx.lineTo(cx + dirs[i][0], cy + dirs[i][1]); ctx.stroke(); }
ctx.fillStyle = ACCENT; ctx.beginPath(); ctx.arc(cx, cy, 3, 0, 7); ctx.fill();
}
function draw() {
ctx.fillStyle = '#0d0d12'; ctx.fillRect(0, 0, w, h);
for (let i = 0; i < revealed; i++) {
const idx = order[i], x = idx % cols, y = (idx / cols) | 0;
drawTile(x * SIZE + SIZE / 2, y * SIZE + SIZE / 2, TILES[cellsGrid[idx]]);
}
}
// 칸 수와 무관하게 항상 같은 시간 안에 다 드러나도록 경과 시간 비율로 reveal 한다
const REVEAL_MS = 900, HOLD_MS = 2200;
draw();
(function loop(t) {
if (!genStart) genStart = t;
const elapsed = t - genStart;
revealed = Math.min(order.length, Math.floor(order.length * Math.min(1, elapsed / REVEAL_MS)));
if (elapsed > REVEAL_MS + HOLD_MS) collapse();
draw();
requestAnimationFrame(loop);
})(0);