.wrap{position:absolute;inset:0;padding:clamp(8px,3vmin,20px);display:flex;flex-direction:column;gap:6px}
.stage{position:relative;flex:1;min-height:0;display:flex;gap:clamp(6px,2vmin,12px)}
.pane{position:relative;flex:1;min-height:0}
.lbl{position:absolute;left:8px;bottom:8px;z-index:2;font:800 clamp(9px,2.2vmin,11px)/1 ui-monospace,monospace;color:#fff;text-shadow:0 1px 3px rgba(0,0,0,.6)}
canvas{position:absolute;inset:0;width:100%;height:100%;object-fit:contain;border-radius:10px;border:1px solid var(--line);background:#0a0a0a}
.hud{display:flex;justify-content:center;font:700 clamp(9px,2.2vmin,11px)/1 ui-monospace,monospace;color:var(--muted)}
var W = 300, H = 180;
var lc = document.getElementById('lc'), lx = lc.getContext('2d');
var rc = document.getElementById('rc'), rx = rc.getContext('2d');
function paintPhoto(cx, w, h) {
var g = cx.createLinearGradient(0, 0, 0, h * 0.62);
g.addColorStop(0, '#5b86c9'); g.addColorStop(0.55, '#bcd6ea'); g.addColorStop(1, '#f5e0ad');
cx.fillStyle = g; cx.fillRect(0, 0, w, h);
var sx = w * 0.74, sy = h * 0.22, sr = h * 0.11;
var sg = cx.createRadialGradient(sx, sy, 0, sx, sy, sr * 3.2);
sg.addColorStop(0, 'rgba(255,247,214,0.9)'); sg.addColorStop(1, 'rgba(255,247,214,0)');
cx.fillStyle = sg; cx.fillRect(0, 0, w, h * 0.62);
cx.fillStyle = '#fff8df'; cx.beginPath(); cx.arc(sx, sy, sr, 0, 7); cx.fill();
cx.fillStyle = '#6f9152';
cx.beginPath(); cx.moveTo(0, h * 0.64);
for (var x = 0; x <= w; x += 14) cx.lineTo(x, h * 0.64 - Math.sin(x * 0.014) * h * 0.05 - Math.sin(x * 0.032 + 1.2) * h * 0.02);
cx.lineTo(w, h); cx.lineTo(0, h); cx.closePath(); cx.fill();
cx.fillStyle = '#405f38';
cx.beginPath(); cx.moveTo(0, h * 0.79);
for (var x2 = 0; x2 <= w; x2 += 14) cx.lineTo(x2, h * 0.79 - Math.sin(x2 * 0.022 + 0.6) * h * 0.035);
cx.lineTo(w, h); cx.lineTo(0, h); cx.closePath(); cx.fill();
var tx = w * 0.2, ty = h * 0.8;
cx.strokeStyle = '#3a2a1c'; cx.lineWidth = Math.max(1.5, w * 0.008);
cx.beginPath(); cx.moveTo(tx, ty); cx.lineTo(tx - w * 0.01, ty - h * 0.13); cx.stroke();
cx.fillStyle = '#2e4a28';
for (var i = 0; i < 5; i++) { cx.beginPath(); cx.arc(tx + (i - 2) * w * 0.013, ty - h * 0.15 - (i % 2) * h * 0.018, w * 0.028, 0, 7); cx.fill(); }
cx.fillStyle = '#1e1e1c';
cx.beginPath(); cx.arc(w * 0.56, h * 0.745, h * 0.018, 0, 7); cx.fill();
cx.fillRect(w * 0.555, h * 0.76, w * 0.01, h * 0.06);
var img = cx.getImageData(0, 0, w, h), d = img.data;
for (var p = 0; p < d.length; p += 4) { var n = (Math.random() - 0.5) * 10; d[p] += n; d[p + 1] += n; d[p + 2] += n; }
cx.putImageData(img, 0, 0);
}
paintPhoto(lx, W, H);
var base = lx.getImageData(0, 0, W, H);
function boxBlur(src, w, h, r) {
var tmp = new Float32Array(src.length), out = new Float32Array(src.length);
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var sr = 0, sg = 0, sb = 0, n = 0;
for (var k = -r; k <= r; k++) { var xx = x + k; if (xx < 0 || xx >= w) continue; var idx = (y * w + xx) * 4; sr += src[idx]; sg += src[idx + 1]; sb += src[idx + 2]; n++; }
var o = (y * w + x) * 4; tmp[o] = sr / n; tmp[o + 1] = sg / n; tmp[o + 2] = sb / n;
}
}
for (var x2 = 0; x2 < w; x2++) {
for (var y2 = 0; y2 < h; y2++) {
var sr2 = 0, sg2 = 0, sb2 = 0, n2 = 0;
for (var k2 = -r; k2 <= r; k2++) { var yy = y2 + k2; if (yy < 0 || yy >= h) continue; var idx2 = (yy * w + x2) * 4; sr2 += tmp[idx2]; sg2 += tmp[idx2 + 1]; sb2 += tmp[idx2 + 2]; n2++; }
var o2 = (y2 * w + x2) * 4; out[o2] = sr2 / n2; out[o2 + 1] = sg2 / n2; out[o2 + 2] = sb2 / n2; out[o2 + 3] = 255;
}
}
return out;
}
function edgeAwareBlur(src, w, h, r, sigma) {
var out = new Float32Array(src.length);
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var ci = (y * w + x) * 4;
var cl = (src[ci] + src[ci + 1] + src[ci + 2]) / 3;
var sr = 0, sg = 0, sb = 0, sw = 0;
for (var dy = -r; dy <= r; dy++) {
for (var dx = -r; dx <= r; dx++) {
var xx = x + dx, yy = y + dy;
if (xx < 0 || xx >= w || yy < 0 || yy >= h) continue;
var idx = (yy * w + xx) * 4;
var nl = (src[idx] + src[idx + 1] + src[idx + 2]) / 3;
var diff = nl - cl;
var wgt = Math.exp(-(diff * diff) / (2 * sigma * sigma));
sr += src[idx] * wgt; sg += src[idx + 1] * wgt; sb += src[idx + 2] * wgt; sw += wgt;
}
}
out[ci] = sr / sw; out[ci + 1] = sg / sw; out[ci + 2] = sb / sw; out[ci + 3] = 255;
}
}
return out;
}
function addNoiseArr(src) {
var out = new Uint8ClampedArray(src.length);
for (var i = 0; i < src.length; i += 4) {
var n = (Math.random() - 0.5) * 46;
out[i] = src[i] + n; out[i + 1] = src[i + 1] + n; out[i + 2] = src[i + 2] + n; out[i + 3] = 255;
}
return out;
}
function recompute() {
var noisy = addNoiseArr(base.data);
var plain = boxBlur(noisy, W, H, 2);
var edge = edgeAwareBlur(noisy, W, H, 2, 24);
var lImg = lx.createImageData(W, H);
for (var i = 0; i < plain.length; i++) lImg.data[i] = plain[i];
var rImg = rx.createImageData(W, H);
for (var j = 0; j < edge.length; j++) rImg.data[j] = edge[j];
lx.putImageData(lImg, 0, 0);
rx.putImageData(rImg, 0, 0);
}
recompute();
var frameCount = 0;
function frame() {
frameCount++;
if (frameCount % 50 === 0) recompute();
requestAnimationFrame(frame);
}
frame();