← 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)} &nbsp;·&nbsp;
            max ${r.pre_max.toFixed(1)} → ${r.post_max.toFixed(1)} &nbsp;·&nbsp;
            <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>