← back to Wallco Ai
public/admin/mid-heal-preview.html
216 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Mid-Seam-Heal Preview — wallco.ai admin</title>
<style>
:root {
--ink:#1f1808; --line:#d8d0c0; --gold:#c9a14b;
--bg:#fbf8f1; --card:#fff; --faint:#7a6e5a;
--green:#1f6a2c; --red:#b3261e; --orange:#c0660b;
--sans:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;
--serif:'Playfair Display','Didot',Georgia,serif;
}
* { box-sizing:border-box; }
body { margin:0; background:var(--bg); color:var(--ink); font:14px var(--sans); }
header {
padding:14px 24px; border-bottom:1px solid var(--line); background:#fff;
display:flex; gap:16px; align-items:baseline; position:sticky; top:0; z-index:5;
}
header h1 { font:400 22px/1.2 var(--serif); margin:0; }
header .meta { color:var(--faint); font-size:12px; }
header .summary { margin-left:auto; font:600 12px ui-monospace,Menlo,monospace; color:var(--ink); }
header .summary .pass { color:var(--green); }
header .summary .warn { color:var(--orange); }
header .summary .fail { color:var(--red); }
main { max-width:1400px; margin:0 auto; padding:18px; }
.controls {
display:flex; gap:14px; align-items:center; padding:11px 14px;
background:#fff; border:1px solid var(--line); border-radius:8px;
margin-bottom:14px; font-size:12px;
}
.controls label { font:600 10.5px var(--sans); letter-spacing:.16em;
text-transform:uppercase; color:var(--faint); }
.controls .toggle { display:flex; align-items:center; gap:6px; cursor:pointer; }
.controls .toggle input { margin:0; }
.pair {
background:#fff; border:1px solid var(--line); border-radius:8px;
margin-bottom:18px; padding:14px;
}
.pair-header {
display:flex; justify-content:space-between; align-items:baseline;
margin-bottom:10px;
}
.pair-id {
font:700 14px ui-monospace,Menlo,monospace; color:var(--ink);
}
.pair-stats {
font:600 12px ui-monospace,Menlo,monospace; color:var(--faint);
}
.pair-stats .v-before { color:var(--red); }
.pair-stats .v-after.pass { color:var(--green); }
.pair-stats .v-after.warn { color:var(--orange); }
.pair-stats .v-after.fail { color:var(--red); }
.duo {
display:grid; grid-template-columns:1fr 1fr; gap:12px;
}
.duo .panel { position:relative; }
.duo .panel img {
width:100%; aspect-ratio:1; object-fit:cover;
border:1px solid var(--line); border-radius:6px;
background:#f3eee2;
}
.duo .panel.show-bands img {
/* highlight the heal band */
}
.duo .panel.show-bands::after {
content:''; position:absolute; inset:0;
background:
linear-gradient(transparent calc(50% - 12px), rgba(255,52,52,.18) calc(50% - 12px), rgba(255,52,52,.18) calc(50% + 12px), transparent calc(50% + 12px)),
linear-gradient(90deg, transparent calc(50% - 12px), rgba(255,52,52,.18) calc(50% - 12px), rgba(255,52,52,.18) calc(50% + 12px), transparent calc(50% + 12px));
border-radius:6px;
pointer-events:none;
}
.duo .panel-label {
position:absolute; top:8px; left:8px;
background:rgba(0,0,0,.7); color:#fff;
font:700 9px var(--sans); letter-spacing:.12em; text-transform:uppercase;
padding:3px 7px; border-radius:3px;
}
.duo .panel-de {
position:absolute; bottom:8px; right:8px;
background:rgba(0,0,0,.65); color:#fff;
font:700 10px ui-monospace,Menlo,monospace;
padding:3px 7px; border-radius:3px;
}
.duo .panel.before .panel-de { background:rgba(179,38,30,.85); }
.duo .panel.after .panel-de { background:rgba(31,106,44,.85); }
.verdict-pill {
display:inline-block;
font:700 9.5px var(--sans); letter-spacing:.14em; text-transform:uppercase;
padding:3px 8px; border-radius:3px;
color:#fff;
}
.verdict-pill.PASS { background:var(--green); }
.verdict-pill.WARN { background:var(--orange); }
.verdict-pill.FAIL { background:var(--red); }
.loading { text-align:center; padding:40px; color:var(--faint); }
.decision {
background:var(--ink); color:#fff;
padding:16px 24px; border-radius:8px;
margin-top:20px; text-align:center;
}
.decision h3 { margin:0 0 8px; font:400 18px var(--serif); }
.decision p { margin:0 0 12px; color:#dcd0bb; font-size:13px; }
.decision .btn {
background:var(--gold); color:#fff; border:0; padding:10px 22px;
border-radius:4px; cursor:pointer; margin:0 6px;
font:600 11.5px var(--sans); letter-spacing:.12em; text-transform:uppercase;
}
.decision .btn.no { background:#555; }
</style>
</head>
<body>
<header>
<h1>Mid-Seam-Heal Preview</h1>
<span class="meta">20-sample · BAND=12px · SIGMA=4.0 · before vs after</span>
<div class="summary" id="summary">loading…</div>
</header>
<main>
<div class="controls">
<label class="toggle">
<input type="checkbox" id="show-bands">
Highlight heal bands (red overlay at H/2 and V/2)
</label>
<span style="margin-left:auto; color:var(--faint); font-size:11px;">
The red bands show the 24px region (12px each side of the midline) where
Gaussian blur σ=4 was applied. Everything outside the bands is untouched.
</span>
</div>
<div id="pairs"><div class="loading">loading sample…</div></div>
<div class="decision">
<h3>Eyeball check — does the blur band look acceptable?</h3>
<p>If yes, scale to all 7,333 mids-only FAILs for $0 (new spoon_all_designs rows,
round-1 originals preserved as __preseam.png backup).</p>
<p style="font-size:11.5px; color:var(--gold)">
This page is read-only. Tell Claude in chat to scale or to retune
(e.g. "tighter band" / "less blur" / "different sigma").
</p>
</div>
</main>
<script>
fetch('/api/admin/mid-heal-preview/sample')
.then(r => r.json())
.then(data => {
if (!data.ok) {
document.getElementById('pairs').innerHTML =
`<div class="loading">error: ${data.error || 'unknown'}</div>`;
return;
}
const rows = data.items || [];
const passN = rows.filter(r => r.post_v === 'PASS').length;
const warnN = rows.filter(r => r.post_v === 'WARN').length;
const failN = rows.filter(r => r.post_v === 'FAIL').length;
document.getElementById('summary').innerHTML =
`n=${rows.length} ` +
`<span class="fail">${rows.length} FAIL</span> → ` +
`<span class="pass">${passN} PASS</span> + ` +
`<span class="warn">${warnN} WARN</span> + ` +
`<span class="fail">${failN} still FAIL</span>`;
document.getElementById('pairs').innerHTML = rows.map(r => `
<div class="pair">
<div class="pair-header">
<span class="pair-id">#${r.id}</span>
<span class="pair-stats">
mids ${r.pre_mids.toFixed(1)} → ${r.post_mids.toFixed(1)} ·
max ${r.pre_max.toFixed(1)} → ${r.post_max.toFixed(1)} ·
<span class="verdict-pill ${r.pre_v}">${r.pre_v}</span>
→
<span class="verdict-pill ${r.post_v}">${r.post_v}</span>
</span>
</div>
<div class="duo">
<div class="panel before">
<span class="panel-label">Before (source)</span>
<img src="${r.before_url}" loading="lazy" alt="before #${r.id}">
<span class="panel-de">ΔE ${r.pre_max.toFixed(1)}</span>
</div>
<div class="panel after">
<span class="panel-label">After (heal)</span>
<img src="${r.after_url}" loading="lazy" alt="after #${r.id}">
<span class="panel-de">ΔE ${r.post_max.toFixed(1)}</span>
</div>
</div>
</div>
`).join('');
const cb = document.getElementById('show-bands');
function applyBands() {
document.querySelectorAll('.duo .panel').forEach(p => {
p.classList.toggle('show-bands', cb.checked);
});
}
cb.addEventListener('change', applyBands);
})
.catch(err => {
document.getElementById('pairs').innerHTML =
`<div class="loading">fetch failed: ${err.message}</div>`;
});
</script>
<script src="/admin/admin-modal-kit.js" defer></script>
</body>
</html>