.wrap{position:relative;width:100%;height:100%}
#cv{position:absolute;inset:0;width:100%;height:100%;image-rendering:pixelated}
.panel{position:absolute;bottom:10px;left:10px;right:10px;z-index:2;padding:7px 10px;border-radius:10px;
background:rgba(13,13,18,0.55);backdrop-filter:blur(6px);border:1px solid rgba(255,255,255,0.15);
font-size:11px;font-weight:600;color:#f1f0ec}
const cv = document.getElementById('cv'), ctx = cv.getContext('2d');
const panel = document.getElementById('panel');
const SW = 48, SH = 30;
const small = document.createElement('canvas'); small.width = SW; small.height = SH;
const sctx = small.getContext('2d');
function fit() { const dpr = Math.min(devicePixelRatio || 1, 2); cv.width = innerWidth * dpr; cv.height = innerHeight * dpr; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.imageSmoothingEnabled = false; }
addEventListener('resize', fit); fit();
function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
function paintTarget() {
const r = mulberry32(31);
const g = sctx.createLinearGradient(0, 0, 0, SH);
g.addColorStop(0, 'hsl(280 45% 60%)'); g.addColorStop(1, 'hsl(320 40% 30%)');
sctx.fillStyle = g; sctx.fillRect(0, 0, SW, SH);
sctx.beginPath(); sctx.arc(SW * 0.3, SH * 0.22, SH * 0.12, 0, Math.PI * 2); sctx.fillStyle = 'hsl(50 90% 75%)'; sctx.fill();
for (let l = 0; l < 2; l++) {
sctx.beginPath(); sctx.moveTo(0, SH);
for (let i = 0; i <= 6; i++) { const x = SW * i / 6; const y = SH * (0.55 + l * 0.16) - r() * SH * 0.12; sctx.lineTo(x, y); }
sctx.lineTo(SW, SH); sctx.closePath();
sctx.fillStyle = 'hsl(300 30% ' + (16 + l * 12) + '%)'; sctx.fill();
}
}
function noiseCanvas(seed) {
const r = mulberry32(seed);
const tmp = document.createElement('canvas'); tmp.width = SW; tmp.height = SH;
const tctx = tmp.getContext('2d');
const id = tctx.createImageData(SW, SH);
for (let i = 0; i < id.data.length; i += 4) { const v = Math.floor(r() * 255); id.data[i] = v; id.data[i + 1] = v; id.data[i + 2] = v; id.data[i + 3] = 255; }
tctx.putImageData(id, 0, 0);
return tmp;
}
function drawFrame(t) {
paintTarget();
if (t > 0.01) { sctx.globalAlpha = t; sctx.drawImage(noiseCanvas(500 + Math.floor(t * 40)), 0, 0); sctx.globalAlpha = 1; }
ctx.clearRect(0, 0, innerWidth, innerHeight);
ctx.drawImage(small, 0, 0, innerWidth, innerHeight);
}
let t = 0.95, dir = -1, label = '역방향(생성): 노이즈에서 이미지를 복원합니다';
function loop() {
drawFrame(t); panel.textContent = label;
t += dir * 0.012;
if (t <= 0) { t = 0; dir = 1; label = '정방향(학습): 이미지에 노이즈를 더합니다'; }
if (t >= 1) { t = 1; dir = -1; label = '역방향(생성): 노이즈에서 이미지를 복원합니다'; }
setTimeout(loop, 30);
}
loop();