[object Object]

← back to Model Arena

record five live detailed challenge results

daffeef2304eab9d9366ac752c935a2e5a45066d · 2026-08-31 16:29:41 -0700 · Steve Abrams

Files touched

Diff

commit daffeef2304eab9d9366ac752c935a2e5a45066d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 31 16:29:41 2026 -0700

    record five live detailed challenge results
---
 data/artifacts/4c21edc43a09/claude-code.html |  974 ++++++++++++++++++++++
 data/artifacts/4c21edc43a09/qwen25-7b.html   |  108 +++
 data/artifacts/4c21edc43a09/qwen3-14b.html   |  490 +++++++++++
 data/artifacts/6e2d6635424a/claude-code.html |  765 +++++++++++++++++
 data/artifacts/6e2d6635424a/qwen25-7b.html   |  116 +++
 data/artifacts/6e2d6635424a/qwen3-14b.html   |  400 +++++++++
 data/artifacts/84f06b214f8c/claude-code.html | 1134 ++++++++++++++++++++++++++
 data/artifacts/84f06b214f8c/qwen25-7b.html   |  223 +++++
 data/artifacts/84f06b214f8c/qwen3-14b.html   |  451 ++++++++++
 data/artifacts/89e23514bd71/qwen25-7b.html   |  159 ++++
 data/artifacts/89e23514bd71/qwen3-14b.html   |  510 ++++++++++++
 data/artifacts/96c1603e5f4a/claude-code.html |  694 ++++++++++++++++
 data/artifacts/96c1603e5f4a/qwen25-7b.html   |  128 +++
 data/artifacts/96c1603e5f4a/qwen3-14b.html   |  713 ++++++++++++++++
 verification/five-detailed-live.png          |  Bin 0 -> 235640 bytes
 verification/five-detailed-proof.json        |   30 +
 verification/five-detailed.e2e.js            |   35 +
 17 files changed, 6930 insertions(+)

diff --git a/data/artifacts/4c21edc43a09/claude-code.html b/data/artifacts/4c21edc43a09/claude-code.html
new file mode 100644
index 0000000..9a78e80
--- /dev/null
+++ b/data/artifacts/4c21edc43a09/claude-code.html
@@ -0,0 +1,974 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Margin Intelligence — Monday Buying Review</title>
+<style>
+  :root{
+    --bg:#f4f2ec;
+    --panel:#fbfaf6;
+    --ink:#1c1a17;
+    --ink-2:#514c44;
+    --ink-3:#8a8377;
+    --line:#ded9cd;
+    --line-2:#eae6dc;
+    --accent:#3a5a44;
+    --accent-soft:#e4ede4;
+    --gold:#8a6d2f;
+    --pos:#2f6b47;
+    --neg:#a8402c;
+    --warn:#b06b1a;
+    --warn-soft:#f6e9d4;
+    --neg-soft:#f4dfd8;
+    --pos-soft:#dfeadf;
+    --focus:#1f5fbf;
+    --shadow:0 1px 2px rgba(28,26,23,.06),0 8px 24px rgba(28,26,23,.06);
+    --mono:"SF Mono",ui-monospace,"Roboto Mono",Menlo,Consolas,monospace;
+    --serif:Georgia,"Times New Roman",'Iowan Old Style',serif;
+    --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;
+  }
+  *{box-sizing:border-box}
+  html,body{margin:0;padding:0}
+  body{
+    background:var(--bg);
+    color:var(--ink);
+    font-family:var(--sans);
+    font-size:14px;
+    line-height:1.45;
+    -webkit-font-smoothing:antialiased;
+  }
+  h1,h2,h3{margin:0;font-weight:600;letter-spacing:-.01em}
+  a{color:var(--accent)}
+  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;font-feature-settings:"tnum"}
+  .pos{color:var(--pos)}
+  .neg{color:var(--neg)}
+  .warn{color:var(--warn)}
+  .muted{color:var(--ink-3)}
+
+  /* focus visibility */
+  :focus-visible{outline:2.5px solid var(--focus);outline-offset:2px;border-radius:3px}
+  button,select,input{font:inherit;color:inherit}
+
+  /* ---- Masthead ---- */
+  header.masthead{
+    border-bottom:1px solid var(--line);
+    background:var(--panel);
+    padding:14px 22px 12px;
+    position:sticky;top:0;z-index:40;
+  }
+  .brandrow{display:flex;align-items:baseline;gap:14px;flex-wrap:wrap}
+  .brand{font-family:var(--serif);font-size:20px;letter-spacing:.02em}
+  .brand .mono-tag{
+    font-family:var(--mono);font-size:10px;letter-spacing:.14em;
+    text-transform:uppercase;color:var(--ink-3);border:1px solid var(--line);
+    padding:2px 6px;border-radius:3px;margin-left:2px;
+  }
+  .kicker{font-family:var(--mono);font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-3)}
+  .brandrow .spacer{flex:1}
+  .stale{
+    display:none;align-items:center;gap:8px;font-size:12px;color:var(--warn);
+    background:var(--warn-soft);border:1px solid #e6cfa0;padding:5px 10px;border-radius:4px;
+  }
+  .stale.show{display:inline-flex}
+  .stale button{
+    background:none;border:1px solid #d3b479;color:var(--warn);border-radius:4px;
+    padding:2px 8px;cursor:pointer;font-size:11px;
+  }
+  .dot{width:7px;height:7px;border-radius:50%;background:var(--warn);display:inline-block}
+
+  /* ---- Controls ---- */
+  .controls{
+    display:flex;gap:18px;flex-wrap:wrap;align-items:flex-end;margin-top:12px;
+  }
+  .ctl{display:flex;flex-direction:column;gap:4px}
+  .ctl > label{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3)}
+  .seg{display:inline-flex;border:1px solid var(--line);border-radius:6px;overflow:hidden;background:var(--bg)}
+  .seg button{
+    background:transparent;border:none;padding:6px 11px;cursor:pointer;font-size:12.5px;
+    color:var(--ink-2);border-right:1px solid var(--line-2);
+  }
+  .seg button:last-child{border-right:none}
+  .seg button[aria-pressed="true"]{background:var(--ink);color:#fbfaf6;font-weight:600}
+  select{
+    border:1px solid var(--line);border-radius:6px;padding:6px 26px 6px 10px;background:var(--bg);
+    font-size:12.5px;cursor:pointer;appearance:none;
+    background-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='10' height='6'><path d='M0 0l5 6 5-6z' fill='%238a8377'/></svg>");
+    background-repeat:no-repeat;background-position:right 9px center;min-width:130px;
+  }
+  .ctl-reset button{
+    background:none;border:none;color:var(--accent);cursor:pointer;font-size:12px;
+    text-decoration:underline;text-underline-offset:2px;padding:6px 0;
+  }
+
+  /* ---- Layout ---- */
+  main{display:grid;grid-template-columns:minmax(0,1fr) 372px;gap:0}
+  .left{padding:20px 22px 60px;min-width:0}
+  .right{
+    border-left:1px solid var(--line);background:var(--panel);
+    padding:20px;position:sticky;top:114px;align-self:start;height:calc(100vh - 114px);overflow:auto;
+  }
+
+  /* ---- KPI strip ---- */
+  .kpis{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px}
+  .kpi{
+    background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:12px 13px;box-shadow:var(--shadow);
+  }
+  .kpi .lab{font-family:var(--mono);font-size:9.5px;letter-spacing:.11em;text-transform:uppercase;color:var(--ink-3)}
+  .kpi .val{font-family:var(--serif);font-size:23px;margin-top:3px;letter-spacing:-.01em}
+  .kpi .sub{font-size:11px;color:var(--ink-3);margin-top:2px}
+
+  .sec-title{
+    font-family:var(--mono);font-size:11px;letter-spacing:.13em;text-transform:uppercase;color:var(--ink-3);
+    margin:26px 0 10px;display:flex;align-items:center;gap:10px;
+  }
+  .sec-title::after{content:"";flex:1;height:1px;background:var(--line-2)}
+
+  /* ---- Waterfall ---- */
+  .waterfall-wrap{
+    background:var(--panel);border:1px solid var(--line);border-radius:8px;padding:16px 16px 8px;box-shadow:var(--shadow);
+  }
+  .wf{display:flex;align-items:flex-end;gap:10px;height:180px;padding-top:6px}
+  .wf-col{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;min-width:0;height:100%}
+  .wf-bararea{width:100%;flex:1;display:flex;flex-direction:column;justify-content:flex-end;position:relative}
+  .wf-bar{width:78%;margin:0 auto;border-radius:2px 2px 0 0;position:relative;transition:height .5s cubic-bezier(.4,0,.2,1)}
+  .wf-bar.total{background:var(--ink)}
+  .wf-bar.sub{background:var(--neg);opacity:.82}
+  .wf-bar.final{background:var(--accent)}
+  .wf-lab{font-size:10px;color:var(--ink-2);text-align:center;margin-top:7px;line-height:1.25;min-height:26px}
+  .wf-val{font-family:var(--mono);font-size:10.5px;margin-top:5px;color:var(--ink)}
+  .wf-connector{border-top:1px dashed var(--line);position:absolute;left:0;right:0}
+
+  /* ---- Table ---- */
+  .table-scroll{overflow-x:auto;border:1px solid var(--line);border-radius:8px;background:var(--panel);box-shadow:var(--shadow)}
+  table{width:100%;border-collapse:collapse;min-width:720px}
+  thead th{
+    font-family:var(--mono);font-size:9.5px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-3);
+    text-align:right;padding:10px 12px;border-bottom:1px solid var(--line);white-space:nowrap;
+    position:sticky;top:0;background:var(--panel);cursor:pointer;user-select:none;
+  }
+  thead th:first-child{text-align:left}
+  thead th .arrow{opacity:0;margin-left:3px}
+  thead th[aria-sort="ascending"] .arrow,thead th[aria-sort="descending"] .arrow{opacity:1}
+  thead th[aria-sort]{color:var(--ink)}
+  tbody td{padding:11px 12px;text-align:right;border-bottom:1px solid var(--line-2);white-space:nowrap}
+  tbody td:first-child{text-align:left}
+  tbody tr{cursor:pointer}
+  tbody tr:hover{background:var(--accent-soft)}
+  tbody tr[aria-selected="true"]{background:var(--accent-soft);box-shadow:inset 3px 0 0 var(--accent)}
+  tbody tr:last-child td{border-bottom:none}
+  .fam{display:flex;flex-direction:column;gap:1px}
+  .fam .name{font-weight:600;font-size:13.5px}
+  .fam .vend{font-size:11px;color:var(--ink-3)}
+  .flag{
+    display:inline-flex;align-items:center;gap:4px;font-size:10px;font-family:var(--mono);
+    letter-spacing:.04em;background:var(--neg-soft);color:var(--neg);border:1px solid #e3b8ab;
+    padding:1px 6px;border-radius:10px;margin-top:3px;text-transform:uppercase;
+  }
+  .bar-cell{position:relative}
+  .minibar{height:4px;border-radius:2px;margin-top:4px;background:var(--line);overflow:hidden}
+  .minibar i{display:block;height:100%;border-radius:2px}
+  .leak{font-weight:600}
+  .chip{font-family:var(--mono);font-size:11px;padding:2px 6px;border-radius:4px}
+  .chip.g{background:var(--pos-soft);color:var(--pos)}
+  .chip.b{background:var(--neg-soft);color:var(--neg)}
+  .chip.w{background:var(--warn-soft);color:var(--warn)}
+
+  /* ---- States ---- */
+  .state{
+    display:none;text-align:center;padding:54px 20px;border:1px dashed var(--line);border-radius:8px;
+    background:var(--panel);color:var(--ink-3);
+  }
+  .state.show{display:block}
+  .state h3{font-family:var(--serif);font-size:18px;color:var(--ink);font-weight:500;margin-bottom:6px}
+  .state p{max-width:340px;margin:0 auto 14px}
+  .state button{background:var(--ink);color:#fbfaf6;border:none;border-radius:6px;padding:8px 16px;cursor:pointer;font-size:13px}
+  .skel{background:linear-gradient(90deg,var(--line-2) 25%,#f2eee4 37%,var(--line-2) 63%);background-size:400% 100%;animation:sh 1.3s infinite;border-radius:4px}
+  @keyframes sh{0%{background-position:100% 0}100%{background-position:0 0}}
+  .skel-row{display:grid;grid-template-columns:2fr 1fr 1fr 1fr 1fr;gap:12px;padding:12px}
+  .skel-row div{height:16px}
+
+  /* ---- Side panel ---- */
+  .sp-empty{color:var(--ink-3);font-size:13px;line-height:1.6;padding-top:8px}
+  .sp-empty .hint{font-family:var(--serif);font-size:16px;color:var(--ink);margin-bottom:8px;font-weight:500}
+  .sp-head .eyebrow{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold)}
+  .sp-head h2{font-family:var(--serif);font-size:22px;font-weight:500;margin-top:3px;letter-spacing:-.01em}
+  .sp-head .vend{font-size:12px;color:var(--ink-3);margin-top:2px}
+  .sp-metrics{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line-2);border:1px solid var(--line-2);border-radius:7px;overflow:hidden;margin:16px 0}
+  .sp-metrics div{background:var(--panel);padding:9px 11px}
+  .sp-metrics .l{font-family:var(--mono);font-size:9px;letter-spacing:.09em;text-transform:uppercase;color:var(--ink-3)}
+  .sp-metrics .v{font-family:var(--mono);font-size:15px;margin-top:2px}
+  .spark{height:44px;width:100%;margin:6px 0 2px;display:block}
+  .sp-block{margin-top:18px}
+  .sp-block h3{font-family:var(--mono);font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-3);margin-bottom:7px}
+  .diag{font-size:13px;line-height:1.55;color:var(--ink-2)}
+  .diag b{color:var(--ink)}
+  .callout{border-left:3px solid var(--neg);background:var(--neg-soft);padding:9px 12px;border-radius:0 5px 5px 0;font-size:12.5px;margin-top:8px}
+  .callout.ok{border-color:var(--pos);background:var(--pos-soft)}
+  .callout.wn{border-color:var(--warn);background:var(--warn-soft)}
+  .rec{background:var(--accent-soft);border:1px solid #cfe0d3;border-radius:8px;padding:12px 13px;margin-top:8px}
+  .rec .act{font-family:var(--serif);font-size:15px;color:var(--ink);margin-bottom:8px;line-height:1.35}
+
+  /* assumptions */
+  .assump{display:flex;flex-direction:column;gap:12px;margin-top:6px}
+  .assump .row{display:grid;grid-template-columns:1fr auto;align-items:center;gap:8px}
+  .assump .row label{font-size:12px;color:var(--ink-2)}
+  .assump .row .valbox{font-family:var(--mono);font-size:12.5px;color:var(--ink);min-width:56px;text-align:right}
+  .assump input[type=range]{grid-column:1/3;width:100%;accent-color:var(--accent);height:22px}
+  .impact{
+    margin-top:14px;border-top:1px solid var(--line);padding-top:12px;
+  }
+  .impact .row{display:flex;justify-content:space-between;align-items:baseline;padding:4px 0;font-size:13px}
+  .impact .row .k{color:var(--ink-2)}
+  .impact .big{font-family:var(--serif);font-size:26px;letter-spacing:-.01em}
+  .impact .lead{font-size:11px;color:var(--ink-3);margin-top:2px;line-height:1.4}
+  .btn-row{display:flex;gap:8px;margin-top:14px}
+  .btn{border:none;border-radius:6px;padding:9px 12px;cursor:pointer;font-size:12.5px;font-weight:500}
+  .btn.primary{background:var(--accent);color:#fbfaf6;flex:1}
+  .btn.ghost{background:transparent;border:1px solid var(--line);color:var(--ink-2)}
+  .reset-assump{background:none;border:none;color:var(--accent);text-decoration:underline;text-underline-offset:2px;cursor:pointer;font-size:11.5px;padding:0}
+
+  .footnote{font-size:11px;color:var(--ink-3);margin-top:26px;line-height:1.5;border-top:1px solid var(--line-2);padding-top:12px}
+  kbd{font-family:var(--mono);font-size:10.5px;background:var(--bg);border:1px solid var(--line);border-bottom-width:2px;border-radius:4px;padding:1px 5px}
+  .sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}
+
+  /* ---- Responsive ---- */
+  @media (max-width:1000px){
+    main{grid-template-columns:1fr}
+    .right{position:static;height:auto;border-left:none;border-top:1px solid var(--line)}
+    .kpis{grid-template-columns:repeat(2,1fr)}
+  }
+  @media (max-width:560px){
+    header.masthead{padding:12px 14px}
+    .left{padding:16px 14px 48px}
+    .right{padding:16px 14px}
+    .kpis{grid-template-columns:1fr 1fr;gap:9px}
+    .kpi .val{font-size:20px}
+    .controls{gap:12px}
+    .brand{font-size:17px}
+    .wf{height:150px;gap:5px}
+    .wf-lab{font-size:9px}
+  }
+</style>
+</head>
+<body>
+<a class="sr-only" href="#opp-table">Skip to opportunity table</a>
+
+<header class="masthead">
+  <div class="brandrow">
+    <div>
+      <div class="kicker">Alderman &amp; Vale — Fine Interiors Distribution</div>
+      <div class="brand">Margin Intelligence <span class="mono-tag">Owner console</span></div>
+    </div>
+    <div class="spacer"></div>
+    <div class="stale" id="staleFlag" role="status">
+      <span class="dot"></span>
+      <span id="staleText">Data snapshot is 26 h old — refreshed Fri 6:02 PM.</span>
+      <button id="refreshBtn">Refresh</button>
+    </div>
+  </div>
+
+  <div class="controls" role="group" aria-label="Filters">
+    <div class="ctl">
+      <label id="lbl-range">Date range</label>
+      <div class="seg" role="group" aria-labelledby="lbl-range" id="rangeSeg">
+        <button data-range="7"  aria-pressed="false">7 d</button>
+        <button data-range="30" aria-pressed="true">30 d</button>
+        <button data-range="90" aria-pressed="false">Quarter</button>
+        <button data-range="365" aria-pressed="false">Year</button>
+      </div>
+    </div>
+    <div class="ctl">
+      <label for="channelSel">Channel</label>
+      <select id="channelSel" aria-label="Filter by channel">
+        <option value="all">All channels</option>
+        <option value="Trade">Trade / designer</option>
+        <option value="Retail">Retail showroom</option>
+        <option value="Ecom">E-commerce</option>
+      </select>
+    </div>
+    <div class="ctl">
+      <label for="vendorSel">Vendor</label>
+      <select id="vendorSel" aria-label="Filter by vendor">
+        <option value="all">All vendors</option>
+      </select>
+    </div>
+    <div class="ctl ctl-reset">
+      <label>&nbsp;</label>
+      <button id="resetFilters">Reset filters</button>
+    </div>
+  </div>
+</header>
+
+<main>
+  <!-- ================= LEFT ================= -->
+  <section class="left" aria-label="Portfolio overview">
+
+    <div class="kpis" id="kpiStrip" aria-live="polite"></div>
+
+    <div class="sec-title">Contribution-margin waterfall</div>
+    <div class="waterfall-wrap">
+      <div class="wf" id="waterfall" aria-hidden="false"></div>
+      <p class="muted" style="font-size:11px;margin:10px 2px 4px">
+        From gross revenue to true contribution — each deduction is a lever you can pull. The
+        <b>freight &amp; oversize</b> and <b>sample &amp; return</b> steps are where recoverable margin most often hides.
+      </p>
+    </div>
+
+    <div class="sec-title">Opportunity table
+      <span class="muted" style="font-family:var(--sans);text-transform:none;letter-spacing:0;font-size:11px">
+        — sorted by recoverable margin. Select a row for diagnosis.</span>
+    </div>
+
+    <!-- loading -->
+    <div class="state" id="loadingState">
+      <div class="table-scroll" style="border:none;box-shadow:none;background:transparent">
+        <div class="skel-row"><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div></div>
+        <div class="skel-row"><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div></div>
+        <div class="skel-row"><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div></div>
+        <div class="skel-row"><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div><div class="skel"></div></div>
+      </div>
+      <p class="muted" style="margin-top:14px">Loading margin snapshot…</p>
+    </div>
+
+    <!-- no results -->
+    <div class="state" id="emptyState">
+      <h3>No families match these filters</h3>
+      <p>Nothing in this channel/vendor combination for the selected range. Widen the range or clear a filter to see the full portfolio.</p>
+      <button id="emptyReset">Clear filters</button>
+    </div>
+
+    <!-- table -->
+    <div class="table-scroll" id="tableWrap">
+      <table id="opp-table" aria-label="Product family opportunities">
+        <thead>
+          <tr>
+            <th data-key="name" aria-sort="none" scope="col">Product family <span class="arrow">▾</span></th>
+            <th data-key="revenue" aria-sort="none" scope="col">Revenue <span class="arrow">▾</span></th>
+            <th data-key="units" aria-sort="none" scope="col">Units <span class="arrow">▾</span></th>
+            <th data-key="gm" aria-sort="none" scope="col">Gross margin <span class="arrow">▾</span></th>
+            <th data-key="contrib" aria-sort="none" scope="col">Contribution <span class="arrow">▾</span></th>
+            <th data-key="sampleConv" aria-sort="none" scope="col">Sample conv. <span class="arrow">▾</span></th>
+            <th data-key="returnRate" aria-sort="none" scope="col">Return rate <span class="arrow">▾</span></th>
+            <th data-key="cover" aria-sort="none" scope="col">Inv. cover <span class="arrow">▾</span></th>
+            <th data-key="leak" aria-sort="descending" scope="col">Recoverable <span class="arrow">▾</span></th>
+          </tr>
+        </thead>
+        <tbody id="oppBody"></tbody>
+      </table>
+    </div>
+
+    <p class="footnote">
+      Contribution margin = gross margin − freight/oversize handling − sample-program cost − return-processing cost − channel commission.
+      A family can post strong revenue and still return <span class="neg">negative contribution</span> once these below-the-line costs land — those are flagged.
+      Keyboard: <kbd>↑</kbd><kbd>↓</kbd> move rows, <kbd>Enter</kbd> opens diagnosis, sort headers with <kbd>Space</kbd>.
+    </p>
+  </section>
+
+  <!-- ================= RIGHT ================= -->
+  <aside class="right" id="sidePanel" aria-label="Family diagnosis and action modeler">
+    <div class="sp-empty" id="spEmpty">
+      <div class="hint">Select a product family</div>
+      Pick any row in the opportunity table to see its 12-period trend, a plain-language diagnosis of where
+      margin is leaking, and a recommended action you can model — adjust the assumptions and watch the
+      projected annual impact update live.
+      <p style="margin-top:14px;font-size:12px" class="muted">
+        Tip: the family at the top of the table carries the largest recoverable margin leak right now.
+      </p>
+    </div>
+    <div id="spContent" hidden></div>
+  </aside>
+</main>
+
+<script>
+"use strict";
+
+/* ---------------- Data model ---------------- */
+// Base figures are for a 30-day window; ranges scale them with realistic multipliers.
+const FAMILIES = [
+  {
+    id:"grasscloth", name:"Grasscloth & Natural Wallcovering", vendor:"Seneca Mills", channelMix:{Trade:.62,Retail:.28,Ecom:.10},
+    revenue:412000, units:1840, gmPct:.51, freightPct:.11, samplePct:.052, returnRate:.094, commissionPct:.06,
+    sampleConv:.19, cover:2.1, trend:[38,42,40,44,46,49,47,52,55,58,61,64],
+    note:"return", diagnosisKey:"grasscloth"
+  },
+  {
+    id:"performance", name:"Performance Upholstery Textiles", vendor:"Coteau Weaves", channelMix:{Trade:.40,Retail:.34,Ecom:.26},
+    revenue:685000, units:5120, gmPct:.44, freightPct:.05, samplePct:.021, returnRate:.031, commissionPct:.05,
+    sampleConv:.34, cover:3.4, trend:[70,68,72,74,73,76,78,77,80,82,84,86],
+    note:"star", diagnosisKey:"performance"
+  },
+  {
+    id:"stone", name:"Oversize Stone & Slab Surfaces", vendor:"Aventine Stone", channelMix:{Trade:.71,Retail:.24,Ecom:.05},
+    revenue:1240000, units:410, gmPct:.39, freightPct:.27, samplePct:.014, returnRate:.078, commissionPct:.07,
+    sampleConv:.44, cover:5.9, trend:[120,118,124,130,126,133,129,136,131,138,134,141],
+    note:"contradiction", diagnosisKey:"stone"
+  },
+  {
+    id:"drapery", name:"Custom Drapery Hardware", vendor:"Halden Forge", channelMix:{Trade:.55,Retail:.40,Ecom:.05},
+    revenue:298000, units:2260, gmPct:.58, freightPct:.09, samplePct:.018, returnRate:.052, commissionPct:.05,
+    sampleConv:.29, cover:1.4, trend:[34,33,31,32,30,31,29,30,28,29,27,28],
+    note:"decline", diagnosisKey:"drapery"
+  },
+  {
+    id:"rugs", name:"Hand-Knotted Area Rugs", vendor:"Aravalli Atelier", channelMix:{Trade:.48,Retail:.44,Ecom:.08},
+    revenue:520000, units:640, gmPct:.47, freightPct:.14, samplePct:.061, returnRate:.121, commissionPct:.06,
+    sampleConv:.12, cover:7.8, trend:[58,55,52,50,49,47,46,45,44,43,45,44],
+    note:"leak", diagnosisKey:"rugs"
+  },
+  {
+    id:"lighting", name:"Decorative Lighting & Fixtures", vendor:"Lumen Bros.", channelMix:{Trade:.33,Retail:.42,Ecom:.25},
+    revenue:376000, units:1490, gmPct:.49, freightPct:.10, samplePct:.009, returnRate:.067, commissionPct:.06,
+    sampleConv:.31, cover:2.9, trend:[40,41,39,43,42,45,44,46,45,47,48,49],
+    note:"steady", diagnosisKey:"lighting"
+  }
+];
+
+const RANGE_MULT = { "7":0.24, "30":1, "90":2.95, "365":11.6 };
+const RANGE_LABEL = { "7":"last 7 days", "30":"last 30 days", "90":"this quarter", "365":"trailing year" };
+
+/* ---------------- State ---------------- */
+let state = { range:"30", channel:"all", vendor:"all", sortKey:"leak", sortDir:"desc", selected:null };
+let assumptions = {}; // per-selected-family live model
+
+/* ---------------- Helpers ---------------- */
+const $ = s => document.querySelector(s);
+const money = n => {
+  const a = Math.abs(n);
+  if(a>=1e6) return (n<0?"−$":"$")+(a/1e6).toFixed(a>=1e7?1:2)+"M";
+  if(a>=1e3) return (n<0?"−$":"$")+Math.round(a/1e3)+"k";
+  return (n<0?"−$":"$")+Math.round(a);
+};
+const moneyFull = n => (n<0?"−$":"$")+Math.abs(Math.round(n)).toLocaleString("en-US");
+const pct = n => (n*100).toFixed(n*100<10?1:0)+"%";
+const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
+
+// Compute derived per-family figures for the active range + filters.
+function compute(f){
+  const m = RANGE_MULT[state.range];
+  // channel share applied to revenue/units when a specific channel is selected
+  let share = 1;
+  if(state.channel!=="all") share = f.channelMix[state.channel] || 0;
+  const revenue = f.revenue * m * share;
+  const units = Math.round(f.units * m * share);
+  const grossMargin = revenue * f.gmPct;
+  const freight = revenue * f.freightPct;
+  const sampleCost = revenue * f.samplePct;
+  const returnCost = revenue * f.returnRate * 0.42; // processing/restock portion of returned value
+  const commission = (state.channel==="Trade"||state.channel==="all"? revenue*f.commissionPct : revenue*f.commissionPct*0.6);
+  const contrib = grossMargin - freight - sampleCost - returnCost - commission;
+  const contribPct = revenue>0 ? contrib/revenue : 0;
+  // "Recoverable" = a defensible estimate of margin retrievable via the recommended action, annualized-in-window.
+  const leak = recoverable(f, {revenue,freight,sampleCost,returnCost});
+  return {revenue,units,grossMargin,freight,sampleCost,returnCost,commission,contrib,contribPct,leak,gmPct:f.gmPct,
+    sampleConv:f.sampleConv, returnRate:f.returnRate, cover:f.cover, raw:f};
+}
+
+// Recoverable margin heuristic per family archetype.
+function recoverable(f, c){
+  switch(f.diagnosisKey){
+    case "stone":   return c.freight*0.55;                    // renegotiate freight / consolidate LTL
+    case "rugs":    return c.returnCost*0.6 + c.sampleCost*0.5;// cut return + low-converting samples
+    case "grasscloth": return c.returnCost*0.5;               // reduce damage returns
+    case "drapery": return c.revenue*0.03;                    // reprice up on inelastic demand
+    case "lighting":return c.sampleCost*0.3 + c.revenue*0.01;
+    default:        return c.revenue*0.008;                   // healthy — small optimization
+  }
+}
+
+/* ---------------- Filtering + sort ---------------- */
+function activeFamilies(){
+  let list = FAMILIES.filter(f=>{
+    if(state.vendor!=="all" && f.vendor!==state.vendor) return false;
+    if(state.channel!=="all" && (f.channelMix[state.channel]||0)<0.001) return false;
+    return true;
+  }).map(f=>({f, c:compute(f)}));
+
+  const key = state.sortKey, dir = state.sortDir==="asc"?1:-1;
+  const getv = ({f,c})=>({
+    name:f.name.toLowerCase(), revenue:c.revenue, units:c.units, gm:c.grossMargin,
+    contrib:c.contrib, sampleConv:c.sampleConv, returnRate:c.returnRate, cover:c.cover, leak:c.leak
+  })[key];
+  list.sort((a,b)=>{
+    const va=getv(a), vb=getv(b);
+    if(typeof va==="string") return va<vb?-1*dir:va>vb?1*dir:0;
+    return (va-vb)*dir;
+  });
+  return list;
+}
+
+/* ---------------- Renderers ---------------- */
+function renderKPIs(list){
+  const tot = list.reduce((a,{c})=>{
+    a.rev+=c.revenue; a.gm+=c.grossMargin; a.contrib+=c.contrib; a.leak+=c.leak; a.units+=c.units; return a;
+  },{rev:0,gm:0,contrib:0,leak:0,units:0});
+  const cPct = tot.rev>0? tot.contrib/tot.rev : 0;
+  const top = [...list].sort((a,b)=>b.c.leak-a.c.leak)[0];
+  const strip = $("#kpiStrip");
+  strip.innerHTML = `
+    <div class="kpi"><div class="lab">Revenue · ${RANGE_LABEL[state.range]}</div>
+      <div class="val num">${money(tot.rev)}</div>
+      <div class="sub">${tot.units.toLocaleString()} units across ${list.length} ${list.length===1?"family":"families"}</div></div>
+    <div class="kpi"><div class="lab">Contribution margin</div>
+      <div class="val num ${tot.contrib<0?'neg':''}">${money(tot.contrib)}</div>
+      <div class="sub">${pct(cPct)} of revenue after below-line cost</div></div>
+    <div class="kpi"><div class="lab">Recoverable margin</div>
+      <div class="val num ${tot.leak>0?'pos':''}">${money(tot.leak)}</div>
+      <div class="sub">identified &amp; actionable before Monday</div></div>
+    <div class="kpi"><div class="lab">Largest single leak</div>
+      <div class="val" style="font-family:var(--serif);font-size:16px;line-height:1.25">${top? top.f.name.split(" ").slice(0,3).join(" ") : "—"}</div>
+      <div class="sub">${top? money(top.c.leak)+" recoverable" : ""}</div></div>`;
+}
+
+function renderWaterfall(list){
+  const tot = list.reduce((a,{c})=>{
+    a.rev+=c.revenue; a.cogs+=(c.revenue-c.grossMargin); a.freight+=c.freight;
+    a.sample+=c.sampleCost; a.ret+=c.returnCost; a.comm+=c.commission; a.contrib+=c.contrib; return a;
+  },{rev:0,cogs:0,freight:0,sample:0,ret:0,comm:0,contrib:0});
+
+  const steps = [
+    {label:"Gross revenue", val:tot.rev, type:"total", running:tot.rev},
+    {label:"− Cost of goods", val:tot.cogs, type:"sub"},
+    {label:"− Freight & oversize", val:tot.freight, type:"sub"},
+    {label:"− Sample program", val:tot.sample, type:"sub"},
+    {label:"− Returns", val:tot.ret, type:"sub"},
+    {label:"− Commission", val:tot.comm, type:"sub"},
+    {label:"Contribution", val:tot.contrib, type:"final", running:tot.contrib},
+  ];
+  const max = tot.rev || 1;
+  let running = tot.rev;
+  const wf = $("#waterfall");
+  wf.innerHTML = "";
+  steps.forEach((s,i)=>{
+    let barTop, barH;
+    if(s.type==="total"){ barH=100; barTop=0; running=tot.rev; }
+    else if(s.type==="final"){ barH=(s.val/max)*100; barTop=100-barH; }
+    else { running-=s.val; barH=(s.val/max)*100; barTop=(running/max)*100; }
+    const col = document.createElement("div");
+    col.className="wf-col";
+    col.innerHTML = `
+      <div class="wf-bararea">
+        <div class="wf-bar ${s.type}" style="height:${Math.max(barH,1.5)}%;margin-top:auto;position:relative;top:${s.type==='sub'?'-'+(barTop)+'%':'0'}"></div>
+      </div>
+      <div class="wf-lab">${s.label}</div>
+      <div class="wf-val num ${s.type==='sub'?'neg':''}">${s.type==='sub'?'−':''}${money(s.val).replace('−','')}</div>`;
+    // simplified stacked positioning: use fl/flex end for total & final, absolute-ish for subs
+    wf.appendChild(col);
+  });
+  // Re-do sub bar vertical offset cleanly (float them at running height)
+  running = tot.rev;
+  const cols = wf.querySelectorAll(".wf-col");
+  steps.forEach((s,i)=>{
+    const bar = cols[i].querySelector(".wf-bar");
+    const area = cols[i].querySelector(".wf-bararea");
+    if(s.type==="sub"){
+      const before = running;
+      running -= s.val;
+      const h = (s.val/max)*100;
+      const bottom = (running/max)*100;
+      bar.style.position="absolute";
+      bar.style.left="11%"; bar.style.width="78%";
+      bar.style.bottom=bottom+"%";
+      bar.style.top="auto";
+      bar.style.height=Math.max(h,1.2)+"%";
+    } else {
+      bar.style.position="relative";
+      bar.style.top="0";
+    }
+  });
+}
+
+function renderTable(list){
+  const body = $("#oppBody");
+  body.innerHTML="";
+  const maxRev = Math.max(...list.map(x=>x.c.revenue),1);
+  const maxLeak = Math.max(...list.map(x=>x.c.leak),1);
+  list.forEach(({f,c},idx)=>{
+    const tr = document.createElement("tr");
+    tr.tabIndex=0;
+    tr.setAttribute("role","button");
+    tr.dataset.id=f.id;
+    tr.setAttribute("aria-selected", state.selected===f.id?"true":"false");
+    tr.setAttribute("aria-label", `${f.name}. Revenue ${money(c.revenue)}, contribution ${money(c.contrib)}, recoverable ${money(c.leak)}. Open diagnosis.`);
+    const contradiction = c.contrib<0 && c.revenue>0;
+    const negClass = c.contrib<0?"neg":"";
+    tr.innerHTML = `
+      <td>
+        <div class="fam">
+          <span class="name">${f.name}</span>
+          <span class="vend">${f.vendor}</span>
+          ${contradiction? `<span class="flag" title="High revenue but negative contribution">⚠ Rev-up · contrib-negative</span>`:""}
+        </div>
+      </td>
+      <td class="bar-cell">
+        <span class="num">${money(c.revenue)}</span>
+        <div class="minibar"><i style="width:${(c.revenue/maxRev)*100}%;background:var(--ink)"></i></div>
+      </td>
+      <td class="num">${c.units.toLocaleString()}</td>
+      <td class="num">${pct(c.gmPct)}</td>
+      <td class="num ${negClass}">${money(c.contrib)}<div class="minibar"><i style="width:${clamp(Math.abs(c.contribPct)*260,4,100)}%;background:${c.contrib<0?'var(--neg)':'var(--pos)'}"></i></div></td>
+      <td><span class="chip ${f.sampleConv<0.15?'b':f.sampleConv>0.3?'g':'w'}">${pct(f.sampleConv)}</span></td>
+      <td><span class="chip ${f.returnRate>0.09?'b':f.returnRate<0.05?'g':'w'}">${pct(f.returnRate)}</span></td>
+      <td class="num ${f.cover>6?'warn':''}">${f.cover.toFixed(1)}mo</td>
+      <td class="num leak ${c.leak>maxLeak*0.4?'pos':''}">${money(c.leak)}
+        <div class="minibar"><i style="width:${(c.leak/maxLeak)*100}%;background:var(--gold)"></i></div>
+      </td>`;
+    tr.addEventListener("click",()=>select(f.id));
+    tr.addEventListener("keydown",e=>{
+      if(e.key==="Enter"||e.key===" "){e.preventDefault();select(f.id);}
+      else if(e.key==="ArrowDown"){e.preventDefault();(tr.nextElementSibling||body.firstElementChild).focus();}
+      else if(e.key==="ArrowUp"){e.preventDefault();(tr.previousElementSibling||body.lastElementChild).focus();}
+    });
+    body.appendChild(tr);
+  });
+}
+
+/* ---------------- Diagnoses ---------------- */
+const DIAG = {
+  stone:{
+    verdict:"contradiction",
+    why:(c)=>`This is your headline revenue line at <b>${money(c.revenue)}</b>, yet contribution is <b class="neg">${money(c.contrib)}</b>. The culprit is freight: oversize slab shipments burn <b>${money(c.freight)}</b> — ${pct(c.raw.freightPct)} of revenue — mostly on half-empty less-than-truckload runs and residential lift-gate surcharges. Gross margin looks fine at ${pct(c.gmPct)}; the money leaves <i>below</i> the gross line.`,
+    action:"Consolidate slab freight to full-truckload lanes and pass lift-gate/oversize surcharges through to the order.",
+    calloutTone:"neg",
+    calloutText:"Do not pause or promote on gross margin alone — it hides a negative contribution.",
+    levers:[
+      {id:"ltlShift", label:"Shipments moved to full-truckload", min:0, max:80, val:45, unit:"%"},
+      {id:"surcharge", label:"Oversize surcharge passed through", min:0, max:100, val:60, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const freightSaved = c.freight*(l.ltlShift/100)*0.62;
+      const surchargeRecovered = c.freight*0.28*(l.surcharge/100);
+      const win = freightSaved+surchargeRecovered;
+      return {win, base:c.contrib, note:"Freight consolidation typically recovers ~62% of the LTL premium; surcharge pass-through recovers the residential-delivery penalty."};
+    }
+  },
+  rugs:{
+    verdict:"leak",
+    why:(c)=>`Revenue is steady but this family carries the portfolio's worst two ratios: a <b class="neg">${pct(c.returnRate)}</b> return rate and only a <b class="neg">${pct(c.sampleConv)}</b> sample-to-order conversion. You are shipping ${money(c.sampleCost)} of samples and processing ${money(c.returnCost)} of returns to move rugs that ${pct(c.sampleConv)} of samplers actually buy. Inventory cover at ${c.cover.toFixed(1)} months compounds the drag.`,
+    action:"Gate the free-sample program behind a design-trade login and add a fiber/size guide to cut mismatched-expectation returns.",
+    calloutTone:"wn",
+    calloutText:"Low sample conversion + high returns = you're subsidizing browsers. Qualify demand before you ship.",
+    levers:[
+      {id:"sampleCut", label:"Reduction in unqualified samples", min:0, max:70, val:40, unit:"%"},
+      {id:"returnCut", label:"Return-rate reduction from sizing guide", min:0, max:50, val:25, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const sampleSaved = c.sampleCost*(l.sampleCut/100);
+      const returnSaved = c.returnCost*(l.returnCut/100);
+      const win = sampleSaved+returnSaved;
+      return {win, base:c.contrib, note:"Assumes qualified samplers convert at the same rate, so revenue holds while sample & return cost falls."};
+    }
+  },
+  grasscloth:{
+    verdict:"leak",
+    why:(c)=>`Growing nicely and gross margin is a healthy ${pct(c.gmPct)}. The soft spot is a <b>${pct(c.returnRate)}</b> return rate — natural grasscloth arrives damaged or dye-lot-mismatched, costing ${money(c.returnCost)} to process and restock. Sample conversion is fine, so demand is real; this is a fulfillment-quality leak, not a demand problem.`,
+    action:"Add rigid-tube packaging and per-roll dye-lot photos at pick, targeting the damage/mismatch return reasons.",
+    calloutTone:"ok",
+    calloutText:"Strong, real demand. Protect it by fixing the packaging leak — this is your safest lever.",
+    levers:[
+      {id:"pkg", label:"Damage returns eliminated", min:0, max:70, val:45, unit:"%"},
+      {id:"dye", label:"Dye-lot mismatch returns cut", min:0, max:60, val:35, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const win = c.returnCost*(0.55*(l.pkg/100)+0.45*(l.dye/100));
+      return {win, base:c.contrib, note:"Splits return cost between damage (~55%) and dye-lot mismatch (~45%) reasons."};
+    }
+  },
+  drapery:{
+    verdict:"decline",
+    why:(c)=>`Best gross margin in the book at <b>${pct(c.gmPct)}</b>, but the 12-period trend is drifting <b class="neg">down</b> and inventory cover is thin at ${c.cover.toFixed(1)} months. Demand is inelastic and specification-driven — hardware is chosen after the fabric decision, so a modest price increase rarely loses the order.`,
+    action:"Take a 4–6% list increase on the top drapery-hardware SKUs and refresh their showroom placement to arrest the volume slide.",
+    calloutTone:"wn",
+    calloutText:"Thin cover + inelastic demand: reprice up before you reorder, not after.",
+    levers:[
+      {id:"priceUp", label:"List price increase", min:0, max:10, val:5, unit:"%"},
+      {id:"attrition", label:"Assumed volume attrition", min:0, max:8, val:2, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const win = c.revenue*(l.priceUp/100) - c.revenue*(l.attrition/100)*c.gmPct;
+      return {win, base:c.contrib, note:"Net of assumed volume attrition valued at gross margin. Inelastic demand keeps attrition low."};
+    }
+  },
+  lighting:{
+    verdict:"steady",
+    why:(c)=>`A balanced, three-channel line trending gently up. Sample cost is already low (${money(c.sampleCost)}) and returns are middling at ${pct(c.returnRate)}. There's no crisis here — the opportunity is incremental: tighten the e-commerce return window and trim slow display-model samples.`,
+    action:"Shorten the e-commerce return window to 14 days and retire the lowest-turn display samples.",
+    calloutTone:"ok",
+    calloutText:"Healthy line — optimize, don't intervene. Spend your Monday on the flagged families first.",
+    levers:[
+      {id:"retWindow", label:"E-com returns avoided (tighter window)", min:0, max:40, val:20, unit:"%"},
+      {id:"sampleTrim", label:"Slow display samples retired", min:0, max:60, val:30, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const win = c.returnCost*0.25*(l.retWindow/100)+c.sampleCost*(l.sampleTrim/100);
+      return {win, base:c.contrib, note:"Small, low-risk gains. Use as a fill-in, not a headline move."};
+    }
+  },
+  performance:{
+    verdict:"star",
+    why:(c)=>`Your model line: <b>${pct(c.gmPct)}</b> gross margin, the best sample conversion in the book (<b class="pos">${pct(c.sampleConv)}</b>), low returns, and a steadily rising trend. Contribution is solidly positive at ${money(c.contrib)}. There is little to <i>recover</i> here — the play is to <b>promote</b> and protect availability.`,
+    action:"Feature this family in the Monday buy and secure forward inventory before lead times stretch.",
+    calloutTone:"ok",
+    calloutText:"This is what 'working' looks like — use it as the benchmark for the leaking families.",
+    levers:[
+      {id:"promo", label:"Incremental volume from promotion", min:0, max:20, val:8, unit:"%"},
+      {id:"holdMargin", label:"Margin held through promo", min:80, max:100, val:96, unit:"%"}
+    ],
+    project:(c,l)=>{
+      const win = c.revenue*(l.promo/100)*c.gmPct*(l.holdMargin/100)*0.5;
+      return {win, base:c.contrib, note:"Upside from promotion, not recovery. Modeled conservatively at half the incremental gross margin."};
+    }
+  }
+};
+
+/* ---------------- Side panel ---------------- */
+function select(id){
+  state.selected = id;
+  document.querySelectorAll("#oppBody tr").forEach(tr=>tr.setAttribute("aria-selected", tr.dataset.id===id?"true":"false"));
+  const f = FAMILIES.find(x=>x.id===id);
+  const c = compute(f);
+  const d = DIAG[f.diagnosisKey];
+  // init assumptions from levers
+  assumptions = {};
+  d.levers.forEach(l=>assumptions[l.id]=l.val);
+  $("#spEmpty").hidden = true;
+  $("#spContent").hidden = false;
+  drawPanel(f,c,d);
+}
+
+function drawPanel(f,c,d){
+  const contradiction = c.contrib<0 && c.revenue>0;
+  const el = $("#spContent");
+  el.innerHTML = `
+    <div class="sp-head">
+      <div class="eyebrow">${({contradiction:"Contradictory signal",leak:"Margin leak",decline:"Softening line",steady:"Stable line",star:"Benchmark line",star:"Benchmark line"})[d.verdict]||"Diagnosis"}</div>
+      <h2>${f.name}</h2>
+      <div class="vend">${f.vendor} · ${state.channel==="all"?"all channels":state.channel} · ${RANGE_LABEL[state.range]}</div>
+    </div>
+
+    <div class="sp-metrics">
+      <div><div class="l">Revenue</div><div class="v">${money(c.revenue)}</div></div>
+      <div><div class="l">Contribution</div><div class="v ${c.contrib<0?'neg':'pos'}">${money(c.contrib)}</div></div>
+      <div><div class="l">Gross margin</div><div class="v">${pct(c.gmPct)}</div></div>
+      <div><div class="l">Contribution %</div><div class="v ${c.contribPct<0?'neg':''}">${pct(c.contribPct)}</div></div>
+      <div><div class="l">Sample conv.</div><div class="v ${f.sampleConv<0.15?'neg':''}">${pct(f.sampleConv)}</div></div>
+      <div><div class="l">Return rate</div><div class="v ${f.returnRate>0.09?'neg':''}">${pct(f.returnRate)}</div></div>
+    </div>
+
+    <div class="sp-block">
+      <h3>Trend · 12 periods</h3>
+      ${sparkline(f.trend)}
+      <p class="muted" style="font-size:11px;margin-top:2px">Revenue index, oldest → newest. ${trendWord(f.trend)}.</p>
+    </div>
+
+    <div class="sp-block">
+      <h3>Diagnosis</h3>
+      <p class="diag">${d.why(c)}</p>
+      <div class="callout ${d.calloutTone==='neg'?'':d.calloutTone==='ok'?'ok':'wn'}">${contradiction?"⚠ ":""}${d.calloutText}</div>
+    </div>
+
+    <div class="sp-block">
+      <h3>Recommended action</h3>
+      <div class="rec">
+        <div class="act">${d.action}</div>
+        <div class="assump" id="assumpBox">
+          ${d.levers.map(l=>`
+            <div>
+              <div class="row">
+                <label for="lev-${l.id}">${l.label}</label>
+                <span class="valbox num" id="val-${l.id}">${assumptions[l.id]}${l.unit}</span>
+              </div>
+              <input type="range" id="lev-${l.id}" min="${l.min}" max="${l.max}" step="1" value="${assumptions[l.id]}"
+                     aria-label="${l.label}" data-lever="${l.id}" data-unit="${l.unit}">
+            </div>`).join("")}
+        </div>
+
+        <div class="impact" id="impactBox"></div>
+
+        <div class="btn-row">
+          <button class="btn primary" id="commitBtn">Add to Monday buy list</button>
+          <button class="reset-assump" id="resetAssump">Reset assumptions</button>
+        </div>
+      </div>
+    </div>`;
+
+  // wire sliders
+  el.querySelectorAll("input[type=range]").forEach(inp=>{
+    inp.addEventListener("input",()=>{
+      const id=inp.dataset.lever;
+      assumptions[id]=+inp.value;
+      $("#val-"+id).textContent = inp.value + inp.dataset.unit;
+      updateImpact(f,c,d);
+    });
+  });
+  $("#resetAssump").addEventListener("click",()=>{
+    d.levers.forEach(l=>{
+      assumptions[l.id]=l.val;
+      const inp=$("#lev-"+l.id); inp.value=l.val;
+      $("#val-"+l.id).textContent=l.val+l.unit;
+    });
+    updateImpact(f,c,d);
+  });
+  $("#commitBtn").addEventListener("click",(e)=>{
+    const b=e.currentTarget;
+    b.textContent="✓ Added to Monday buy list";
+    b.style.background="var(--ink)";
+    setTimeout(()=>{b.textContent="Add to Monday buy list";b.style.background="";},1900);
+  });
+  updateImpact(f,c,d);
+}
+
+function updateImpact(f,c,d){
+  const p = d.project(c, assumptions);
+  const windowGain = p.win;
+  // annualize the in-window recovery
+  const annual = windowGain * (RANGE_MULT["365"]/RANGE_MULT[state.range]);
+  const newContrib = p.base + windowGain;
+  const box = $("#impactBox");
+  const isUpside = d.verdict==="star" || d.verdict==="steady";
+  box.innerHTML = `
+    <div class="row"><span class="k">Recovered this ${RANGE_LABEL[state.range].replace('last ','').replace('trailing ','')}</span>
+      <span class="num pos">${money(windowGain)}</span></div>
+    <div class="row"><span class="k">Contribution after change</span>
+      <span class="num ${newContrib<0?'neg':''}">${money(newContrib)} ${p.base<0&&newContrib>=0?'<span class="chip g" style="margin-left:4px">turns positive</span>':''}</span></div>
+    <div class="row" style="align-items:flex-end;margin-top:4px">
+      <span class="k">Projected annual ${isUpside?'upside':'recovery'}</span>
+      <span class="big num pos">${money(annual)}</span>
+    </div>
+    <div class="lead">${p.note}</div>`;
+}
+
+/* ---------------- Sparkline + trend ---------------- */
+function sparkline(data){
+  const w=320,h=44,pad=3;
+  const min=Math.min(...data),max=Math.max(...data),rng=(max-min)||1;
+  const pts=data.map((v,i)=>{
+    const x=pad+(i/(data.length-1))*(w-2*pad);
+    const y=h-pad-((v-min)/rng)*(h-2*pad);
+    return [x,y];
+  });
+  const line=pts.map((p,i)=>(i?"L":"M")+p[0].toFixed(1)+" "+p[1].toFixed(1)).join(" ");
+  const area=line+` L ${pts[pts.length-1][0].toFixed(1)} ${h} L ${pts[0][0].toFixed(1)} ${h} Z`;
+  const up = data[data.length-1]>=data[0];
+  const col = up?"var(--pos)":"var(--neg)";
+  const last=pts[pts.length-1];
+  return `<svg class="spark" viewBox="0 0 ${w} ${h}" preserveAspectRatio="none" role="img" aria-label="12-period revenue trend, ${up?'rising':'falling'}">
+    <path d="${area}" fill="${up?'var(--pos-soft)':'var(--neg-soft)'}" opacity=".7"></path>
+    <path d="${line}" fill="none" stroke="${col}" stroke-width="1.8" stroke-linejoin="round"></path>
+    <circle cx="${last[0].toFixed(1)}" cy="${last[1].toFixed(1)}" r="2.6" fill="${col}"></circle>
+  </svg>`;
+}
+function trendWord(d){
+  const chg=(d[d.length-1]-d[0])/d[0];
+  if(chg>0.12) return "Clear upward trajectory";
+  if(chg>0.02) return "Gently rising";
+  if(chg<-0.1) return "Declining — needs attention";
+  if(chg<-0.02) return "Softening";
+  return "Flat / holding";
+}
+
+/* ---------------- Main render + states ---------------- */
+function render(){
+  const list = activeFamilies();
+  const showEmpty = list.length===0;
+  $("#emptyState").classList.toggle("show", showEmpty);
+  $("#tableWrap").style.display = showEmpty?"none":"block";
+  if(showEmpty){
+    $("#kpiStrip").innerHTML="";
+    $("#waterfall").innerHTML="";
+  } else {
+    renderKPIs(list);
+    renderWaterfall(list);
+    renderTable(list);
+    // keep selection valid
+    if(state.selected && !list.find(x=>x.f.id===state.selected)){
+      state.selected=null; $("#spContent").hidden=true; $("#spEmpty").hidden=false;
+    } else if(state.selected){
+      select(state.selected); // refresh panel with new range/filter figures
+    }
+  }
+  updateSortHeaders();
+}
+
+function updateSortHeaders(){
+  document.querySelectorAll("thead th").forEach(th=>{
+    if(th.dataset.key===state.sortKey){
+      th.setAttribute("aria-sort", state.sortDir==="asc"?"ascending":"descending");
+      th.querySelector(".arrow").textContent = state.sortDir==="asc"?"▴":"▾";
+    } else {
+      th.setAttribute("aria-sort","none");
+    }
+  });
+}
+
+/* ---------------- Wiring ---------------- */
+function initVendorOptions(){
+  const sel=$("#vendorSel");
+  [...new Set(FAMILIES.map(f=>f.vendor))].sort().forEach(v=>{
+    const o=document.createElement("option");o.value=v;o.textContent=v;sel.appendChild(o);
+  });
+}
+
+function wire(){
+  // range segmented
+  $("#rangeSeg").addEventListener("click",e=>{
+    const b=e.target.closest("button"); if(!b)return;
+    state.range=b.dataset.range;
+    $("#rangeSeg").querySelectorAll("button").forEach(x=>x.setAttribute("aria-pressed", x===b?"true":"false"));
+    withLoading(render);
+  });
+  $("#channelSel").addEventListener("change",e=>{state.channel=e.target.value;withLoading(render);});
+  $("#vendorSel").addEventListener("change",e=>{state.vendor=e.target.value;withLoading(render);});
+  const doReset=()=>{
+    state.channel="all";state.vendor="all";state.range="30";
+    $("#channelSel").value="all";$("#vendorSel").value="all";
+    $("#rangeSeg").querySelectorAll("button").forEach(x=>x.setAttribute("aria-pressed", x.dataset.range==="30"?"true":"false"));
+    withLoading(render);
+  };
+  $("#resetFilters").addEventListener("click",doReset);
+  $("#emptyReset").addEventListener("click",doReset);
+
+  // sort headers
+  document.querySelectorAll("thead th").forEach(th=>{
+    th.tabIndex=0;
+    const act=()=>{
+      const k=th.dataset.key;
+      if(state.sortKey===k) state.sortDir = state.sortDir==="asc"?"desc":"asc";
+      else { state.sortKey=k; state.sortDir = (k==="name")?"asc":"desc"; }
+      render();
+    };
+    th.addEventListener("click",act);
+    th.addEventListener("keydown",e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();act();}});
+  });
+
+  // stale-data flag + refresh
+  $("#refreshBtn").addEventListener("click",()=>{
+    $("#staleFlag").classList.remove("show");
+    withLoading(()=>{ render(); });
+    $("#staleText").textContent="Data refreshed just now.";
+  });
+}
+
+// Loading-state wrapper — briefly show skeleton to make async states real.
+let loadTimer=null;
+function withLoading(fn){
+  const ls=$("#loadingState"), tw=$("#tableWrap"), es=$("#emptyState");
+  ls.classList.add("show"); tw.style.display="none"; es.classList.remove("show");
+  clearTimeout(loadTimer);
+  loadTimer=setTimeout(()=>{
+    ls.classList.remove("show");
+    fn();
+  }, 430);
+}
+
+/* ---------------- Boot ---------------- */
+initVendorOptions();
+wire();
+withLoading(()=>{
+  render();
+  // surface the stale-data banner shortly after first paint
+  setTimeout(()=>$("#staleFlag").classList.add("show"), 900);
+});
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4c21edc43a09/qwen25-7b.html b/data/artifacts/4c21edc43a09/qwen25-7b.html
new file mode 100644
index 0000000..4cdd78e
--- /dev/null
+++ b/data/artifacts/4c21edc43a09/qwen25-7b.html
@@ -0,0 +1,108 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Margin Intelligence Workspace</title>
+    <style>
+        * { box-sizing: border-box; }
+        body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f4f4f9; }
+        .container { width: 100%; max-width: 1440px; margin: auto; overflow-x: hidden; }
+        header, footer { padding: 20px; text-align: center; background-color: white; }
+        .main-content { padding: 20px; background-color: #fff; min-height: calc(100vh - 60px); }
+        .filters { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+        .date-range, .channel-filters, .vendor-filters { width: 30%; }
+        table { width: 100%; border-collapse: collapse; font-size: 0.9em; }
+        th, td { padding: 8px 16px; text-align: left; border-bottom: 1px solid #ddd; }
+        th { background-color: #f2f2f2; }
+        .side-panel { width: 33%; float: right; margin-left: 10px; }
+        .side-panel h2, .side-panel ul { margin: 0; padding: 0; list-style-type: none; }
+        .side-panel li { margin-bottom: 8px; }
+        .waterfall-chart { width: 100%; height: 300px; background-color: #e9ecef; margin-top: 20px; overflow-y: scroll; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <header>
+            Margin Intelligence Workspace
+        </header>
+        <main class="main-content">
+            <div class="filters">
+                <label>Date Range:</label>
+                <input type="date" id="startDate" value="2023-10-01">
+                <input type="date" id="endDate" value="2023-10-31">
+                <div class="channel-filters">
+                    <h4>Channels</h4>
+                    <label><input type="checkbox"> Online</label>
+                    <label><input type="checkbox"> Offline</label>
+                </div>
+                <div class="vendor-filters">
+                    <h4>Vendors</h4>
+                    <label><input type="checkbox"> Vendor A</label>
+                    <label><input type="checkbox"> Vendor B</label>
+                </div>
+            </div>
+            <table id="opportunityTable">
+                <thead>
+                    <tr>
+                        <th>Product Family</th>
+                        <th>Revenue ($)</th>
+                        <th>Units</th>
+                        <th>Gross Margin (%)</th>
+                        <th>Sample Conversion Rate (%)</th>
+                        <th>Return Rate (%)</th>
+                        <th>Inventory Cover (#)</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr onclick="updateSidePanel(this)">
+                        <td>Living Room Furniture</td>
+                        <td>$120,000</td>
+                        <td>500</td>
+                        <td>45%</td>
+                        <td>8.3%</td>
+                        <td>3.2%</td>
+                        <td>3x</td>
+                    </tr>
+                    <!-- More rows here -->
+                </tbody>
+            </table>
+        </main>
+        <aside class="side-panel">
+            <h2>Trend & Diagnosis</h2>
+            <ul>
+                <li>Revenue has decreased by 5% in the last month.</li>
+                <li>Gross margin is negatively impacted due to increased return rates.</li>
+            </ul>
+            <h3>Recommended Action</h3>
+            <p><strong>Action:</strong> Implement a quality check process for Living Room Furniture items.</p>
+        </aside>
+    </div>
+    <footer>
+        © 2023 Margin Intelligence Workspace
+    </footer>
+
+    <script>
+        function updateSidePanel(row) {
+            const trendDiagnosis = document.querySelector('.side-panel ul');
+            const recommendationAction = document.querySelector('.side-panel p');
+            if (row) {
+                trendDiagnosis.innerHTML = `
+                    <li>Revenue has decreased by 10% in the last month.</li>
+                    <li>Gross margin is negatively impacted due to increased return rates and lower sample conversion rate.</li>
+                `;
+                recommendationAction.textContent = 'Action: Implement a quality check process for Living Room Furniture items and improve marketing strategies.';
+            } else {
+                trendDiagnosis.innerHTML = '';
+                recommendationAction.textContent = 'No action recommended at this time.';
+            }
+        }
+
+        document.getElementById('opportunityTable').addEventListener('click', (event) => {
+            if (event.target.tagName === 'TR') {
+                updateSidePanel(event.target);
+            }
+        });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/4c21edc43a09/qwen3-14b.html b/data/artifacts/4c21edc43a09/qwen3-14b.html
new file mode 100644
index 0000000..f8c9640
--- /dev/null
+++ b/data/artifacts/4c21edc43a09/qwen3-14b.html
@@ -0,0 +1,490 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Margin Intelligence Workspace</title>
+  <style>
+    * {
+      box-sizing: border-box;
+      font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+      margin: 0;
+      padding: 0;
+    }
+
+    body {
+      background-color: #f5f5f5;
+      color: #2d2d2d;
+      display: flex;
+      flex-direction: column;
+      height: 100vh;
+      overflow: hidden;
+    }
+
+    header {
+      background: #2d2d2d;
+      color: #fff;
+      padding: 12px 24px;
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 18px;
+      font-weight: 600;
+    }
+
+    .toolbar {
+      display: flex;
+      align-items: center;
+      gap: 12px;
+    }
+
+    .toolbar select,
+    .toolbar input {
+      padding: 6px;
+      font-size: 14px;
+      border: 1px solid #ccc;
+      border-radius: 4px;
+    }
+
+    .toolbar label {
+      font-size: 12px;
+      margin-right: 4px;
+    }
+
+    main {
+      flex: 1;
+      display: flex;
+      overflow: hidden;
+    }
+
+    .table-container {
+      flex: 1;
+      overflow: auto;
+      padding: 12px;
+    }
+
+    table {
+      width: 100%;
+      border-collapse: collapse;
+      background: #fff;
+      border-radius: 6px;
+      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+    }
+
+    th,
+    td {
+      padding: 10px 12px;
+      border-bottom: 1px solid #eee;
+      text-align: left;
+      vertical-align: middle;
+    }
+
+    th {
+      background: #f0f0f0;
+      font-weight: 600;
+      position: sticky;
+      top: 0;
+      z-index: 1;
+    }
+
+    td:focus-within,
+    th:focus-within {
+      outline: 2px solid #007bff;
+    }
+
+    tr:hover {
+      background: #f9f9f9;
+    }
+
+    .waterfall {
+      background: #fff;
+      padding: 16px;
+      border-radius: 6px;
+      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+      margin-top: 12px;
+    }
+
+    .waterfall h3 {
+      margin-bottom: 8px;
+      font-size: 16px;
+      font-weight: 600;
+    }
+
+    .waterfall .bar {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+    }
+
+    .waterfall .bar label {
+      flex: 1;
+      font-size: 12px;
+      text-align: right;
+      opacity: 0.7;
+    }
+
+    .waterfall .bar .value {
+      flex: 2;
+      text-align: right;
+      font-size: 14px;
+      font-weight: 600;
+    }
+
+    .waterfall .bar .color {
+      flex: 1;
+      min-width: 100px;
+      height: 16px;
+      border-radius: 4px;
+      background: #ccc;
+    }
+
+    .waterfall .bar .positive {
+      background: #4caf50;
+    }
+
+    .waterfall .bar .negative {
+      background: #f44336;
+    }
+
+    .side-panel {
+      background: #fff;
+      padding: 16px;
+      border-radius: 6px;
+      box-shadow: 0 1px 3px rgba(0,0,0,0.1);
+      width: 300px;
+      overflow-y: auto;
+      max-height: 100%;
+      position: sticky;
+      top: 0;
+      display: flex;
+      flex-direction: column;
+    }
+
+    .side-panel h2 {
+      margin-bottom: 12px;
+      font-size: 16px;
+      font-weight: 600;
+      color: #2d2d2d;
+    }
+
+    .side-panel .section {
+      margin-bottom: 16px;
+    }
+
+    .side-panel .section label {
+      display: block;
+      margin-bottom: 4px;
+      font-size: 12px;
+      color: #666;
+    }
+
+    .side-panel .section input,
+    .side-panel .section select {
+      width: 100%;
+      padding: 6px;
+      font-size: 14px;
+      border: 1px solid #ccc;
+      border-radius: 4px;
+    }
+
+    .side-panel .action {
+      margin-top: 16px;
+    }
+
+    .side-panel .action p {
+      font-size: 12px;
+      margin-bottom: 4px;
+    }
+
+    .side-panel .action .impact {
+      font-weight: 600;
+    }
+
+    .status {
+      padding: 12px;
+      text-align: center;
+      font-size: 16px;
+      font-weight: 600;
+      color: #777;
+    }
+
+    .loading {
+      color: #007bff;
+    }
+
+    .no-results {
+      color: #999;
+    }
+
+    .stale {
+      color: #e67e22;
+    }
+
+    @media (max-width: 768px) {
+      main {
+        flex-direction: column;
+      }
+
+      .side-panel {
+        width: 100%;
+      }
+    }
+
+    @media (max-width: 390px) {
+      header {
+        font-size: 14px;
+      }
+
+      .toolbar select,
+      .toolbar input {
+        font-size: 12px;
+      }
+
+      th,
+      td {
+        padding: 8px;
+        font-size: 12px;
+      }
+
+      .side-panel {
+        padding: 8px;
+      }
+
+      .side-panel .section label,
+      .side-panel .section input {
+        font-size: 10px;
+      }
+
+      .side-panel h2 {
+        font-size: 14px;
+      }
+    }
+  </style>
+</head>
+<body>
+  <header>
+    <div>Margin Intelligence Workspace</div>
+    <div class="toolbar">
+      <label for="date-range">Date:</label>
+      <select id="date-range">
+        <option>Last 7 Days</option>
+        <option>Last 30 Days</option>
+        <option>Last 90 Days</option>
+      </select>
+      <label for="channel-filter">Channel:</label>
+      <input type="text" id="channel-filter" placeholder="e.g., Online" />
+      <label for="vendor-filter">Vendor:</label>
+      <input type="text" id="vendor-filter" placeholder="e.g., Designer Brands" />
+    </div>
+  </header>
+
+  <main>
+    <div class="table-container">
+      <table id="opportunity-table">
+        <thead>
+          <tr>
+            <th>Product Family</th>
+            <th>Revenue ($)</th>
+            <th>Units</th>
+            <th>Gross Margin (%)</th>
+            <th>Sample Conversion (%)</th>
+            <th>Return Rate (%)</th>
+            <th>Inventory Cover</th>
+          </tr>
+        </thead>
+        <tbody>
+          <tr>
+            <td data-type="name">Modern Lighting</td>
+            <td data-type="revenue">120000</td>
+            <td data-type="units">1500</td>
+            <td data-type="gross-margin">35</td>
+            <td data-type="sample-conversion">50</td>
+            <td data-type="return-rate">8</td>
+            <td data-type="inventory-cover">1.2</td>
+          </tr>
+          <tr>
+            <td data-type="name">Vintage Furniture</td>
+            <td data-type="revenue">95000</td>
+            <td data-type="units">800</td>
+            <td data-type="gross-margin">22</td>
+            <td data-type="sample-conversion">30</td>
+            <td data-type="return-rate">15</td>
+            <td data-type="inventory-cover">0.8</td>
+          </tr>
+          <tr>
+            <td data-type="name">Contemporary Rugs</td>
+            <td data-type="revenue">180000</td>
+            <td data-type="units">1200</td>
+            <td data-type="gross-margin">40</td>
+            <td data-type="sample-conversion">45</td>
+            <td data-type="return-rate">6</td>
+            <td data-type="inventory-cover">2.0</td>
+          </tr>
+          <tr>
+            <td data-type="name">Industrial Fixtures</td>
+            <td data-type="revenue">210000</td>
+            <td data-type="units">1400</td>
+            <td data-type="gross-margin">28</td>
+            <td data-type="sample-conversion">20</td>
+            <td data-type="return-rate">12</td>
+            <td data-type="inventory-cover">1.5</td>
+          </tr>
+          <tr>
+            <td data-type="name">Handcrafted Tables</td>
+            <td data-type="revenue">60000</td>
+            <td data-type="units">500</td>
+            <td data-type="gross-margin">20</td>
+            <td data-type="sample-conversion">40</td>
+            <td data-type="return-rate">20</td>
+            <td data-type="inventory-cover">0.6</td>
+          </tr>
+          <tr>
+            <td data-type="name">Artisan Ceramics</td>
+            <td data-type="revenue">30000</td>
+            <td data-type="units">300</td>
+            <td data-type="gross-margin">-5</td>
+            <td data-type="sample-conversion">10</td>
+            <td data-type="return-rate">30</td>
+            <td data-type="inventory-cover">1.0</td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+    <div class="side-panel" id="side-panel">
+      <div class="status loading">Loading...</div>
+    </div>
+  </main>
+
+  <div class="waterfall">
+    <h3>Contribution Margin Waterfall</h3>
+    <div class="bar">
+      <label>Revenue</label>
+      <div class="value">$0</div>
+      <div class="color"></div>
+    </div>
+    <div class="bar">
+      <label>COGS</label>
+      <div class="value">$0</div>
+      <div class="color"></div>
+    </div>
+    <div class="bar">
+      <label>Gross Margin</label>
+      <div class="value">$0</div>
+      <div class="color"></div>
+    </div>
+    <div class="bar">
+      <label>Marketing Cost</label>
+      <div class="value">$0</div>
+      <div class="color"></div>
+    </div>
+    <div class="bar">
+      <label>Contribution Margin</label>
+      <div class="value">$0</div>
+      <div class="color"></div>
+    </div>
+  </div>
+
+  <script>
+    const table = document.getElementById('opportunity-table');
+    const rows = table.querySelectorAll('tbody tr');
+    const sidePanel = document.getElementById('side-panel');
+    const waterfallBars = document.querySelectorAll('.waterfall .bar .value');
+    const status = document.querySelector('.status');
+
+    function updateWaterfall(name, revenue, margin) {
+      const revenueBar = document.querySelector('.bar:nth-child(1) .value');
+      const cogsBar = document.querySelector('.bar:nth-child(2) .value');
+      const grossBar = document.querySelector('.bar:nth-child(3) .value');
+      const marketingBar = document.querySelector('.bar:nth-child(4) .value');
+      const contribBar = document.querySelector('.bar:nth-child(5) .value');
+
+      const rev = revenue || 0;
+      const cogs = rev * (100 - margin) / 100;
+      const gross = rev - cogs;
+      const marketing = rev * 0.10; // 10% assumed
+      const contrib = gross - marketing;
+
+      revenueBar.textContent = `$${rev.toFixed(0)}`;
+      cogsBar.textContent = `$${cogs.toFixed(0)}`;
+      grossBar.textContent = `$${gross.toFixed(0)}`;
+      marketingBar.textContent = `$${marketing.toFixed(0)}`;
+      contribBar.textContent = `$${contrib.toFixed(0)}`;
+
+      const contribColor = contrib >= 0 ? 'positive' : 'negative';
+      contribBar.parentElement.querySelector('.color').className = 'color ' + contribColor;
+    }
+
+    function updateSidePanel(product) {
+      sidePanel.innerHTML = `
+        <h2>${product}</h2>
+        <div class="section">
+          <label>Trend</label>
+          <input type="text" value="High revenue but negative contribution" readonly />
+        </div>
+        <div class="section">
+          <label>Diagnosis</label>
+          <input type="text" value="High return rate and low gross margin" readonly />
+        </div>
+        <div class="section">
+          <label>Recommended Action</label>
+          <input type="text" value="Pause vendor" readonly />
+        </div>
+        <div class="section">
+          <label>Assumptions</label>
+          <input type="number" placeholder="Return rate reduction (%)" />
+          <input type="number" placeholder="Gross margin improvement (%)" />
+        </div>
+        <div class="action">
+          <p>Projected impact on contribution margin: <span class="impact">$0</span></p>
+        </div>
+      `;
+    }
+
+    function applyFilters() {
+      const dateRange = document.getElementById('date-range').value;
+      const channelFilter = document.getElementById('channel-filter').value.toLowerCase();
+      const vendorFilter = document.getElementById('vendor-filter').value.toLowerCase();
+
+      rows.forEach(row => {
+        const name = row.querySelector('[data-type="name"]').textContent.toLowerCase();
+        const vendor = row.getAttribute('data-vendor')?.toLowerCase() || '';
+        const channel = row.getAttribute('data-channel')?.toLowerCase() || '';
+
+        const matchesDate = true; // Assume date range filter is applied in backend
+        const matchesChannel = channel.includes(channelFilter);
+        const matchesVendor = vendor.includes(vendorFilter);
+
+        if (matchesDate && matchesChannel && matchesVendor) {
+          row.style.display = '';
+        } else {
+          row.style.display = 'none';
+        }
+      });
+    }
+
+    function loadState() {
+      status.classList.remove('loading', 'no-results', 'stale');
+      status.textContent = 'Loading...';
+      setTimeout(() => {
+        status.classList.remove('loading');
+        status.classList.add('stale');
+        status.textContent = 'Data is stale. Refresh for new insights.';
+      }, 2000);
+    }
+
+    function init() {
+      applyFilters();
+      loadState();
+      rows.forEach(row => {
+        row.addEventListener('click', () => {
+          const name = row.querySelector('[data-type="name"]').textContent;
+          updateSidePanel(name);
+          status.classList.remove('loading', 'no-results', 'stale');
+        });
+      });
+    }
+
+    init();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6e2d6635424a/claude-code.html b/data/artifacts/6e2d6635424a/claude-code.html
new file mode 100644
index 0000000..d0a557c
--- /dev/null
+++ b/data/artifacts/6e2d6635424a/claude-code.html
@@ -0,0 +1,765 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>RailOps — Evening Rush Recovery Console</title>
+<style>
+:root{
+  --bg:#0e1116; --panel:#151a22; --panel2:#1b212b; --edge:#2a3340; --edge2:#38455a;
+  --ink:#e6ecf3; --ink2:#9fb0c3; --ink3:#6b7c92; --accent:#4aa8ff;
+  /* color-blind-safe status palette (Okabe-Ito derived) + glyph pairing */
+  --s-crit:#e8608c; --s-high:#e69f00; --s-med:#f0e442; --s-low:#33bbc7; --s-ok:#4a90ff; --s-unk:#9a8cff;
+  --good:#00b894; --bad:#e8608c; --warn:#e69f00;
+  --mono:"SFMono-Regular",Consolas,"Roboto Mono",monospace;
+  --sans:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+}
+*{box-sizing:border-box}
+html,body{margin:0;height:100%}
+body{background:var(--bg);color:var(--ink);font-family:var(--sans);font-size:12.5px;line-height:1.35;overflow:hidden}
+button,input,select{font-family:inherit;font-size:inherit;color:inherit}
+button{cursor:pointer;background:var(--panel2);border:1px solid var(--edge);border-radius:4px;padding:3px 7px}
+button:hover{border-color:var(--edge2);background:#222b38}
+button:focus-visible,[tabindex]:focus-visible,input:focus-visible,select:focus-visible{outline:2px solid var(--accent);outline-offset:1px}
+.kbd{font-family:var(--mono);font-size:9.5px;background:#0a0d12;border:1px solid var(--edge);border-radius:3px;padding:0 4px;color:var(--ink3)}
+h1,h2,h3,h4{margin:0;font-weight:600}
+.mono{font-family:var(--mono)}
+.muted{color:var(--ink3)}
+.dim{color:var(--ink2)}
+hr{border:0;border-top:1px solid var(--edge);margin:6px 0}
+
+/* ---- topbar ---- */
+#top{height:40px;display:flex;align-items:center;gap:14px;padding:0 12px;background:linear-gradient(#1a212c,#141a22);border-bottom:1px solid var(--edge)}
+#top .brand{font-weight:700;letter-spacing:.5px}
+#top .brand b{color:var(--accent)}
+.pill{display:inline-flex;align-items:center;gap:5px;padding:2px 8px;border-radius:11px;border:1px solid var(--edge);background:var(--panel2);font-size:11px}
+#clock{font-family:var(--mono);color:var(--ink2)}
+#simbadge{margin-left:auto;display:none;align-items:center;gap:8px;padding:3px 10px;border-radius:5px;border:1px dashed var(--warn);background:rgba(230,159,0,.09);color:var(--warn);font-weight:600}
+#simbadge.on{display:inline-flex}
+
+/* ---- layout ---- */
+#grid{display:grid;grid-template-columns:300px minmax(360px,1.35fr) 380px;height:calc(100% - 40px)}
+.col{overflow:auto;border-right:1px solid var(--edge);padding:10px}
+.col:last-child{border-right:0}
+.sect{background:var(--panel);border:1px solid var(--edge);border-radius:7px;margin-bottom:10px}
+.sect>.hd{display:flex;align-items:center;gap:6px;padding:7px 9px;border-bottom:1px solid var(--edge);font-size:11px;letter-spacing:.4px;text-transform:uppercase;color:var(--ink2)}
+.sect>.hd .r{margin-left:auto;font-size:10px}
+.sect>.bd{padding:9px}
+
+/* ---- network map ---- */
+#map{width:100%;height:auto;display:block;background:#0b0e13;border-radius:5px}
+.node{cursor:pointer}
+.legend{display:flex;flex-wrap:wrap;gap:5px;margin-top:7px}
+.lg{display:inline-flex;align-items:center;gap:4px;font-size:10px;color:var(--ink2)}
+.lg i{width:9px;height:9px;border-radius:50%;display:inline-block}
+
+/* status token */
+.st{display:inline-flex;align-items:center;gap:4px;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:600;white-space:nowrap;border:1px solid transparent}
+.st .g{font-family:var(--mono)}
+.st-crit{background:rgba(232,96,140,.16);color:var(--s-crit);border-color:rgba(232,96,140,.4)}
+.st-high{background:rgba(230,159,0,.16);color:var(--s-high);border-color:rgba(230,159,0,.4)}
+.st-med{background:rgba(240,228,66,.14);color:#d8c93a;border-color:rgba(240,228,66,.35)}
+.st-low{background:rgba(51,187,199,.15);color:var(--s-low);border-color:rgba(51,187,199,.4)}
+.st-ok{background:rgba(74,144,255,.16);color:var(--s-ok);border-color:rgba(74,144,255,.4)}
+
+/* filters */
+.frow{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:6px}
+.chip{padding:2px 8px;border-radius:11px;border:1px solid var(--edge);background:var(--panel2);font-size:10.5px;color:var(--ink2);user-select:none}
+.chip.on{background:#26364a;border-color:var(--accent);color:var(--ink)}
+#q{width:100%;background:#0b0e13;border:1px solid var(--edge);border-radius:5px;padding:5px 7px}
+
+/* incident list */
+.inc{position:relative;padding:7px 9px 7px 12px;border:1px solid var(--edge);border-radius:6px;margin-bottom:6px;background:var(--panel2);cursor:pointer}
+.inc:hover{border-color:var(--edge2)}
+.inc.sel{border-color:var(--accent);background:#1a2534;box-shadow:0 0 0 1px var(--accent) inset}
+.inc .bar{position:absolute;left:0;top:0;bottom:0;width:4px;border-radius:6px 0 0 6px}
+.inc .l1{display:flex;align-items:center;gap:6px}
+.inc .l1 .id{font-family:var(--mono);color:var(--ink3);font-size:10px}
+.inc .l1 .ttl{font-weight:600;font-size:12.5px}
+.inc .l2{display:flex;align-items:center;gap:6px;margin-top:4px;flex-wrap:wrap}
+.tag{font-size:9.5px;padding:1px 5px;border-radius:3px;background:#0d1219;border:1px solid var(--edge);color:var(--ink2)}
+.tag.warnflag{color:var(--warn);border-color:rgba(230,159,0,.4)}
+.tag.stale{color:var(--s-unk);border-color:rgba(154,140,255,.4)}
+.risk{margin-left:auto;font-family:var(--mono);font-weight:700}
+.load-mini{height:4px;border-radius:2px;background:#0a0d12;overflow:hidden;margin-top:5px}
+.load-mini i{display:block;height:100%}
+
+/* detail */
+.kv{display:grid;grid-template-columns:96px 1fr;gap:3px 8px;font-size:11.5px}
+.kv .k{color:var(--ink3)}
+.tl{border-left:2px solid var(--edge);margin:4px 0 4px 6px;padding-left:12px}
+.tl .ev{position:relative;padding:4px 0}
+.tl .ev::before{content:"";position:absolute;left:-17px;top:8px;width:8px;height:8px;border-radius:50%;background:var(--accent);border:2px solid var(--panel)}
+.tl .ev.conflict::before{background:var(--warn)}
+.tl .ev .t{font-family:var(--mono);font-size:10px;color:var(--ink3)}
+.tl .ev.conflict .msg{color:var(--warn)}
+.actbar{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
+
+/* plans */
+.plan{border:1px solid var(--edge);border-radius:7px;margin-bottom:8px;overflow:hidden}
+.plan.win{border-color:var(--good);box-shadow:0 0 0 1px rgba(0,184,148,.4)}
+.plan .ph{display:flex;align-items:center;gap:8px;padding:7px 9px;background:var(--panel2)}
+.plan .ph .nm{font-weight:700}
+.plan .ph .sc{margin-left:auto;font-family:var(--mono);font-weight:700;font-size:15px}
+.plan .win-b{font-size:9px;background:var(--good);color:#062;padding:1px 6px;border-radius:9px;font-weight:700}
+.plan .pb{padding:8px 9px;border-top:1px solid var(--edge);display:none}
+.plan.open .pb{display:block}
+.crit{display:grid;grid-template-columns:1fr auto;gap:2px 6px;align-items:center;margin:3px 0}
+.crit .lab{font-size:10.5px;color:var(--ink2)}
+.crit .track{grid-column:1/2;height:8px;background:#0a0d12;border-radius:4px;overflow:hidden}
+.crit .track i{display:block;height:100%;background:var(--accent)}
+.crit .val{grid-column:2;font-family:var(--mono);font-size:10px;color:var(--ink2);width:58px;text-align:right}
+
+/* weights */
+.wt{display:grid;grid-template-columns:78px 1fr 34px;gap:6px;align-items:center;margin:6px 0}
+.wt label{font-size:11px}
+.wt input[type=range]{width:100%}
+.wt .n{font-family:var(--mono);text-align:right}
+#wsum{font-family:var(--mono)}
+#why{font-size:11px;background:#0c1017;border:1px solid var(--edge);border-radius:6px;padding:8px;margin-top:6px}
+#why b{color:var(--ink)}
+#why .flip{color:var(--warn)}
+
+/* decision log */
+#log{max-height:230px;overflow:auto}
+.le{display:flex;gap:7px;padding:4px 0;border-bottom:1px dashed var(--edge)}
+.le .t{font-family:var(--mono);font-size:10px;color:var(--ink3);white-space:nowrap}
+.le .m{font-size:11px}
+.le .m b{color:var(--ink)}
+
+/* modal */
+#ov{position:fixed;inset:0;background:rgba(4,7,11,.72);display:none;align-items:center;justify-content:center;z-index:40}
+#ov.on{display:flex}
+.modal{width:min(440px,92vw);background:var(--panel);border:1px solid var(--edge2);border-radius:9px;box-shadow:0 20px 60px rgba(0,0,0,.6)}
+.modal .mh{padding:11px 13px;border-bottom:1px solid var(--edge);display:flex;align-items:center;gap:8px}
+.modal .mb{padding:13px}
+.modal .mf{padding:11px 13px;border-top:1px solid var(--edge);display:flex;gap:8px;justify-content:flex-end}
+.btn-p{background:var(--accent);border-color:var(--accent);color:#04203f;font-weight:700}
+.btn-d{background:rgba(232,96,140,.15);border-color:var(--s-crit);color:var(--s-crit);font-weight:700}
+.warnbox{background:rgba(230,159,0,.1);border:1px solid rgba(230,159,0,.4);border-radius:6px;padding:8px;font-size:11.5px;color:#f0d089;margin-top:8px}
+select.mini{background:#0b0e13;border:1px solid var(--edge);border-radius:4px;padding:3px 5px}
+
+/* toast */
+#toast{position:fixed;bottom:14px;left:50%;transform:translateX(-50%);background:#10202f;border:1px solid var(--accent);color:var(--ink);padding:8px 14px;border-radius:6px;display:none;z-index:60;font-size:12px;box-shadow:0 6px 20px rgba(0,0,0,.5)}
+#toast.on{display:block}
+
+/* responsive 390px command view */
+#tabbar{display:none}
+@media(max-width:900px){
+  #grid{grid-template-columns:1fr;grid-template-rows:1fr}
+  .col{display:none;border-right:0}
+  .col.active{display:block}
+  #tabbar{display:flex;height:38px;border-top:1px solid var(--edge);background:var(--panel)}
+  #tabbar button{flex:1;border:0;border-radius:0;border-right:1px solid var(--edge);background:transparent;font-size:11px;padding:0}
+  #tabbar button.on{background:#1c2635;color:var(--accent);font-weight:700;box-shadow:inset 0 -2px 0 var(--accent)}
+  #simbadge span.hint{display:none}
+}
+</style>
+</head>
+<body>
+<div id="top">
+  <div class="brand">RAIL<b>OPS</b></div>
+  <span class="pill" title="Evening rush window">🌆 Evening Rush · 17:42–19:30</span>
+  <span class="pill" id="clock">--:--:--</span>
+  <span class="pill muted">Ctrl: <span class="kbd">/</span> search · <span class="kbd">↑↓</span> select · <span class="kbd">S</span> severity · <span class="kbd">A</span> assign · <span class="kbd">M</span> sim · <span class="kbd">U</span> undo</span>
+  <div id="simbadge"><span>⚙ SIMULATION MODE</span> <span class="hint">— changes are sandboxed</span> <button id="simEnd">Exit &amp; keep</button> <button id="simDiscard" class="btn-d" style="padding:2px 8px">Discard all</button></div>
+</div>
+
+<div id="grid">
+  <!-- ============ COL 1: MAP + FILTERS ============ -->
+  <div class="col active" id="colMap" data-tab="map">
+    <div class="sect">
+      <div class="hd">Network Schematic <span class="r muted">click a station → incident</span></div>
+      <div class="bd">
+        <svg id="map" viewBox="0 0 280 250" role="img" aria-label="City rail schematic map"></svg>
+        <div class="legend">
+          <span class="lg"><i style="background:var(--s-crit)"></i>▲ Critical</span>
+          <span class="lg"><i style="background:var(--s-high)"></i>◆ High</span>
+          <span class="lg"><i style="background:var(--s-med)"></i>● Medium</span>
+          <span class="lg"><i style="background:var(--s-low)"></i>■ Low</span>
+          <span class="lg"><i style="background:var(--s-ok)"></i>✔ Recovered</span>
+          <span class="lg"><i style="background:var(--s-unk)"></i>◇ Stale telem.</span>
+        </div>
+      </div>
+    </div>
+    <div class="sect">
+      <div class="hd">Filter Incidents</div>
+      <div class="bd">
+        <input id="q" placeholder="Search id / title / station…" aria-label="Search incidents">
+        <div style="height:7px"></div>
+        <div class="muted" style="font-size:10px;margin-bottom:3px">LINE</div>
+        <div class="frow" id="fLines"></div>
+        <div class="muted" style="font-size:10px;margin-bottom:3px">SEVERITY</div>
+        <div class="frow" id="fSev"></div>
+        <div class="frow"><span class="chip" data-flag="crew">⚠ No crew</span><span class="chip" data-flag="stale">◇ Stale telemetry</span><span class="chip" data-flag="conflict">⚡ Conflicting</span><span class="chip" data-flag="open">Exclude recovered</span></div>
+      </div>
+    </div>
+    <div class="sect">
+      <div class="hd">Crew Availability</div>
+      <div class="bd" id="crewBox" style="font-size:11px"></div>
+    </div>
+  </div>
+
+  <!-- ============ COL 2: INCIDENTS + DETAIL ============ -->
+  <div class="col" id="colInc" data-tab="inc">
+    <div class="sect">
+      <div class="hd">Active Incidents <span class="r muted" id="incCount"></span></div>
+      <div class="bd" id="incList"></div>
+    </div>
+    <div class="sect" id="detailSect">
+      <div class="hd">Incident Detail &amp; Timeline <span class="r muted" id="detailId"></span></div>
+      <div class="bd" id="detail"><div class="muted">Select an incident to inspect its telemetry, timeline and response options.</div></div>
+    </div>
+  </div>
+
+  <!-- ============ COL 3: PLANS + WEIGHTS + LOG ============ -->
+  <div class="col" id="colPlan" data-tab="plan">
+    <div class="sect">
+      <div class="hd">Decision Criteria Weights <span class="r"><span id="wsum">100</span>%</span></div>
+      <div class="bd" id="weights"></div>
+    </div>
+    <div class="sect">
+      <div class="hd">Recovery Plan Comparison</div>
+      <div class="bd">
+        <div id="plans"></div>
+        <div id="why" aria-live="polite"></div>
+      </div>
+    </div>
+    <div class="sect">
+      <div class="hd">Decision Log <span class="r"><button id="expLog" style="font-size:10px;padding:1px 6px">Export</button></span></div>
+      <div class="bd"><div id="log"></div></div>
+    </div>
+  </div>
+</div>
+
+<div id="tabbar">
+  <button data-t="map" class="on">Map</button>
+  <button data-t="inc">Incidents</button>
+  <button data-t="plan">Plans</button>
+</div>
+
+<!-- confirmation / action modal -->
+<div id="ov"><div class="modal" role="dialog" aria-modal="true">
+  <div class="mh"><span id="mIcon">⚠</span><h3 id="mTitle">Confirm</h3></div>
+  <div class="mb" id="mBody"></div>
+  <div class="mf" id="mFoot"></div>
+</div></div>
+<div id="toast"></div>
+
+<script>
+"use strict";
+/* ============================ DATA (seed) ============================ */
+const LINES = {
+  RED:  {name:"Red / Harbor",  color:"#e8608c"},
+  BLU:  {name:"Blue / Central",color:"#4a90ff"},
+  GRN:  {name:"Green / Uni",   color:"#00b894"},
+  YEL:  {name:"Yellow / Airport",color:"#e6a700"}
+};
+const SEV = ["crit","high","med","low","ok"];
+const SEVMETA = {
+  crit:{lab:"Critical",g:"▲",cls:"st-crit",col:"var(--s-crit)",rank:4},
+  high:{lab:"High",    g:"◆",cls:"st-high",col:"var(--s-high)",rank:3},
+  med: {lab:"Medium",  g:"●",cls:"st-med", col:"#d8c93a",rank:2},
+  low: {lab:"Low",     g:"■",cls:"st-low", col:"var(--s-low)",rank:1},
+  ok:  {lab:"Recovered",g:"✔",cls:"st-ok", col:"var(--s-ok)",rank:0}
+};
+
+// crew teams
+let CREW = [
+  {id:"T-Alpha", type:"Track", base:"Union", status:"available"},
+  {id:"T-Bravo", type:"Track", base:"Harbor", status:"deployed", to:"I-2"},
+  {id:"P-Power", type:"Power", base:"Central", status:"available"},
+  {id:"S-Signal",type:"Signals",base:"Junction",status:"deployed", to:"I-1"},
+  {id:"M-Med",   type:"Medical",base:"Central", status:"available"},
+  {id:"R-Recov", type:"Rescue", base:"Airport", status:"unavailable", note:"weather standby"}
+];
+
+// stations for schematic — x,y in 280x250 viewBox
+const STATIONS = {
+  Harbor:{x:40,y:40},  Pier:{x:40,y:95},   Market:{x:95,y:130}, Union:{x:150,y:130},
+  Central:{x:150,y:75},Junction:{x:205,y:75},Uni:{x:95,y:200}, Green:{x:150,y:200},
+  Tower:{x:205,y:130}, Airport:{x:250,y:180}, North:{x:150,y:25}
+};
+const ROUTES = {
+  RED:["Harbor","Pier","Market","Union","Tower"],
+  BLU:["North","Central","Union","Green"],
+  GRN:["Market","Uni","Green","Airport"],
+  YEL:["Central","Junction","Tower","Airport"]
+};
+
+// 8 incidents
+let INCIDENTS = [
+  {id:"I-1", line:"RED", station:"Market", title:"Signal failure northbound", sev:"crit",
+   load:94, cause:"Interlocking fault", eta:"—", crew:"S-Signal", stale:false, conflict:false,
+   tl:[["17:44","Signal drop detected @ Market interlocking"],["17:47","Northbound held; 3 trains stacked"],["17:52","Signals crew S-Signal dispatched"]]},
+  {id:"I-2", line:"RED", station:"Harbor", title:"Person on trackway", sev:"high",
+   load:78, cause:"Trespasser report", eta:"18:20", crew:"T-Bravo", stale:false, conflict:true,
+   tl:[["17:38","Passenger report: person on track"],["17:41","CCTV inconclusive — no visual",true],["17:49","Second report conflicts: cleared vs present",true]]},
+  {id:"I-3", line:"BLU", station:"Central", title:"Overcrowding surge", sev:"high",
+   load:97, cause:"Concert egress", eta:"18:35", crew:null, stale:false, conflict:false,
+   tl:[["17:30","Platform density rising"],["17:55","Load at 97% — hold recommended"]]},
+  {id:"I-4", line:"YEL", station:"Junction", title:"Power dip — reduced traction", sev:"med",
+   load:61, cause:"Substation sag", eta:"18:10", crew:null, stale:true, conflict:false,
+   tl:[["17:20","Voltage sag flagged"],["17:33","Telemetry link degraded — last update stale",false]]},
+  {id:"I-5", line:"GRN", station:"Uni", title:"Door fault — train withdrawn", sev:"med",
+   load:52, cause:"Rolling stock", eta:"18:05", crew:"T-Alpha?", stale:false, conflict:false,
+   tl:[["17:40","Door interlock fault"],["17:46","Unit withdrawn to depot"]]},
+  {id:"I-6", line:"YEL", station:"Airport", title:"Flooding at portal", sev:"crit",
+   load:44, cause:"Storm drain", eta:"19:05", crew:null, stale:false, conflict:false,
+   tl:[["17:15","Water ingress at Airport portal"],["17:50","Rising — R-Recov on weather standby",false]]},
+  {id:"I-7", line:"GRN", station:"Green", title:"Minor delay — congestion", sev:"low",
+   load:38, cause:"Knock-on RED", eta:"18:00", crew:null, stale:false, conflict:false,
+   tl:[["17:52","Marginal delays from Red-line hold"]]},
+  {id:"I-8", line:"BLU", station:"North", title:"Signal reset — service restored", sev:"ok",
+   load:22, cause:"Transient fault", eta:"done", crew:null, stale:false, conflict:false,
+   tl:[["17:10","Signal transient @ North"],["17:28","Auto-reset succeeded"],["17:31","Service restored — monitoring"]]}
+];
+
+// 3 recovery plans, scored 0-100 per criterion (higher = better outcome on that criterion)
+const PLANS = [
+  {id:"A", name:"Contain & Single-Track", tag:"Hold Red, single-track around Market",
+   scores:{safety:88, delay:55, cost:82, recovery:60},
+   note:"Isolate the Market signal fault, single-track Red past it, keep other lines nominal. Low crew demand.",
+   ev:{safety:"No passenger movement across faulted interlocking.", delay:"Red riders +14 min; network mostly unaffected.", cost:"Uses on-hand signals crew only.", recovery:"Slow — depends on I-1 repair ETA."}},
+  {id:"B", name:"Reroute via Blue Bridge", tag:"Divert Red→Blue at Union, run shuttle",
+   scores:{safety:70, delay:84, cost:58, recovery:82},
+   note:"Divert Red services onto Blue between Union and Tower; adds pressure to already-crowded Central (I-3).",
+   ev:{safety:"Adds load to overcrowded Central platform (I-3 at 97%).", delay:"Best throughput — Red riders +6 min avg.", cost:"Needs 2 extra crews + shuttle stock.", recovery:"Fast network normalization once shuttle runs."}},
+  {id:"C", name:"Suspend Red Core, Bus Bridge", tag:"Suspend Harbor–Union, bus bridge",
+   scores:{safety:95, delay:38, cost:45, recovery:70},
+   note:"Fully suspend the Red core to clear the person-on-track (I-2) and signal fault safely; bus bridge the gap.",
+   ev:{safety:"Highest — clears trackway before any movement.", delay:"Worst — bus bridge adds ~22 min.", cost:"Expensive — buses + drivers + overtime.", recovery:"Moderate — clean restart, no residual risk."}}
+];
+const CRITERIA = [
+  {k:"safety",   lab:"Safety"},
+  {k:"delay",    lab:"Passenger delay"},
+  {k:"cost",     lab:"Cost efficiency"},
+  {k:"recovery", lab:"Network recovery"}
+];
+let WEIGHTS = {safety:40, delay:25, cost:15, recovery:20};
+
+/* ============================ STATE ============================ */
+let sel = null;                 // selected incident id
+let filter = {q:"", lines:new Set(), sevs:new Set(), flags:new Set()};
+let sim = {on:false, snapshot:null, changes:0};
+let LOG = [];
+let lastTop = null;             // for flip detection
+
+/* ============================ UTIL ============================ */
+const $ = s=>document.querySelector(s);
+const el = (t,c,h)=>{const e=document.createElement(t);if(c)e.className=c;if(h!=null)e.innerHTML=h;return e;};
+function nowStr(){const d=new Date();return d.toTimeString().slice(0,8);}
+function inc(id){return INCIDENTS.find(i=>i.id===id);}
+function crewFor(id){return CREW.find(c=>c.status==="deployed"&&c.to===id);}
+function loadColor(v){return v>=90?"var(--s-crit)":v>=75?"var(--s-high)":v>=55?"#d8c93a":"var(--s-low)";}
+
+function logAdd(msg, kind){
+  LOG.unshift({t:nowStr(), msg, kind:kind||"", sim:sim.on});
+  renderLog();
+}
+function toast(m){const t=$("#toast");t.textContent=m;t.classList.add("on");clearTimeout(t._x);t._x=setTimeout(()=>t.classList.remove("on"),2200);}
+
+/* ============================ MAP ============================ */
+function renderMap(){
+  const svg=$("#map");
+  let s='';
+  // route lines
+  for(const [lk,path] of Object.entries(ROUTES)){
+    const pts=path.map(n=>`${STATIONS[n].x},${STATIONS[n].y}`).join(" ");
+    s+=`<polyline points="${pts}" fill="none" stroke="${LINES[lk].color}" stroke-width="3" stroke-opacity="0.55" stroke-linecap="round" stroke-linejoin="round"/>`;
+  }
+  // stations that have incidents get a status ring; map station->worst incident
+  const byStation={};
+  INCIDENTS.forEach(i=>{
+    const cur=byStation[i.station];
+    if(!cur || SEVMETA[i.sev].rank>SEVMETA[cur.sev].rank) byStation[i.station]=i;
+  });
+  for(const [nm,pt] of Object.entries(STATIONS)){
+    const i=byStation[nm];
+    const base=`<circle cx="${pt.x}" cy="${pt.y}" r="4.5" fill="#0b0e13" stroke="#556" stroke-width="1.5"/>`;
+    let node=base, glyph="", ring="", cls="node", extra="";
+    if(i){
+      const m=SEVMETA[i.sev];
+      const col = i.stale ? "var(--s-unk)" : m.col;
+      ring=`<circle cx="${pt.x}" cy="${pt.y}" r="8.5" fill="none" stroke="${col}" stroke-width="2" ${i.sev==='crit'?'class="pulse"':''} ${i.stale?'stroke-dasharray="2 2"':''}/>`;
+      glyph=`<text x="${pt.x}" y="${pt.y-11}" text-anchor="middle" font-size="8" fill="${col}">${m.g}</text>`;
+      node=`<circle cx="${pt.x}" cy="${pt.y}" r="4.5" fill="${col}"/>`;
+      extra=`data-inc="${i.id}"`;
+    }
+    s+=`<g class="${cls}" ${extra} tabindex="${i?0:-1}" role="${i?'button':'presentation'}" ${i?`aria-label="${nm} station, incident ${i.id} ${SEVMETA[i.sev].lab}"`:''}>${ring}${node}${glyph}<text x="${pt.x}" y="${pt.y+13}" text-anchor="middle" font-size="6.5" fill="#8a9aad">${nm}</text></g>`;
+  }
+  svg.innerHTML=s+`<style>.pulse{animation:pr 1.6s infinite}@keyframes pr{0%{stroke-opacity:1}50%{stroke-opacity:.25}100%{stroke-opacity:1}}</style>`;
+  svg.querySelectorAll("[data-inc]").forEach(g=>{
+    const id=g.getAttribute("data-inc");
+    g.addEventListener("click",()=>selectInc(id,true));
+    g.addEventListener("keydown",e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();selectInc(id,true);}});
+  });
+}
+
+/* ============================ FILTERS ============================ */
+function buildFilterChips(){
+  const fl=$("#fLines"); fl.innerHTML="";
+  Object.entries(LINES).forEach(([k,v])=>{
+    const c=el("span","chip",`<span style="color:${v.color}">●</span> ${k}`);
+    c.dataset.line=k; fl.appendChild(c);
+  });
+  const fs=$("#fSev"); fs.innerHTML="";
+  SEV.forEach(k=>{
+    const m=SEVMETA[k];
+    const c=el("span","chip",`${m.g} ${m.lab}`); c.dataset.sev=k; fs.appendChild(c);
+  });
+  document.querySelectorAll("#fLines .chip,#fSev .chip,#colMap [data-flag]").forEach(c=>{
+    c.addEventListener("click",()=>{
+      if(c.dataset.line){toggle(filter.lines,c.dataset.line);}
+      else if(c.dataset.sev){toggle(filter.sevs,c.dataset.sev);}
+      else if(c.dataset.flag){toggle(filter.flags,c.dataset.flag);}
+      c.classList.toggle("on");
+      renderList();
+    });
+  });
+  $("#q").addEventListener("input",e=>{filter.q=e.target.value.toLowerCase();renderList();});
+}
+function toggle(set,v){set.has(v)?set.delete(v):set.add(v);}
+
+function passes(i){
+  if(filter.q){const hay=(i.id+i.title+i.station+i.line).toLowerCase();if(!hay.includes(filter.q))return false;}
+  if(filter.lines.size&&!filter.lines.has(i.line))return false;
+  if(filter.sevs.size&&!filter.sevs.has(i.sev))return false;
+  if(filter.flags.has("crew")&&!( !i.crew || /\?$/.test(i.crew) ))return false;
+  if(filter.flags.has("stale")&&!i.stale)return false;
+  if(filter.flags.has("conflict")&&!i.conflict)return false;
+  if(filter.flags.has("open")&&i.sev==="ok")return false;
+  return true;
+}
+
+/* ============================ INCIDENT LIST ============================ */
+function riskScore(i){
+  // composite: severity rank * load — informs "highest-risk"
+  return SEVMETA[i.sev].rank*20 + i.load*0.6 + (i.conflict?8:0) + (i.stale?5:0) - (i.crew&&!/\?$/.test(i.crew)?6:0);
+}
+function renderList(){
+  const list=$("#incList"); list.innerHTML="";
+  const rows=INCIDENTS.filter(passes).sort((a,b)=>riskScore(b)-riskScore(a));
+  $("#incCount").textContent=`${rows.length}/${INCIDENTS.length}`;
+  if(!rows.length){list.appendChild(el("div","muted","No incidents match filters."));}
+  const maxRisk=Math.max(...INCIDENTS.map(riskScore));
+  rows.forEach(i=>{
+    const m=SEVMETA[i.sev];
+    const noCrew = !i.crew || /\?$/.test(i.crew);
+    const d=el("div","inc"+(sel===i.id?" sel":""));
+    d.tabIndex=0; d.dataset.id=i.id;
+    const flags=[];
+    if(noCrew) flags.push(`<span class="tag warnflag">⚠ no crew</span>`);
+    if(i.stale) flags.push(`<span class="tag stale">◇ stale</span>`);
+    if(i.conflict) flags.push(`<span class="tag warnflag">⚡ conflict</span>`);
+    d.innerHTML=`
+      <span class="bar" style="background:${i.stale?'var(--s-unk)':m.col}"></span>
+      <div class="l1">
+        <span class="id">${i.id}</span>
+        <span class="st ${m.cls}"><span class="g">${m.g}</span>${m.lab}</span>
+        <span class="ttl">${i.title}</span>
+        <span class="risk" style="color:${loadColor(i.load)}" title="Composite risk">R${Math.round(riskScore(i))}</span>
+      </div>
+      <div class="l2">
+        <span class="tag" style="border-color:${LINES[i.line].color}55;color:${LINES[i.line].color}">${i.line}</span>
+        <span class="tag">${i.station}</span>
+        <span class="tag">load ${i.load}%</span>
+        ${flags.join("")}
+      </div>
+      <div class="load-mini"><i style="width:${i.load}%;background:${loadColor(i.load)}"></i></div>`;
+    d.addEventListener("click",()=>selectInc(i.id));
+    d.addEventListener("keydown",e=>{if(e.key==="Enter"){selectInc(i.id);}});
+    list.appendChild(d);
+  });
+}
+
+/* ============================ DETAIL ============================ */
+function selectInc(id, fromMap){
+  sel=id; renderList(); renderDetail();
+  if(fromMap && window.innerWidth<=900) switchTab("inc");
+  const node=$(`#incList .inc[data-id="${id}"]`); if(node) node.scrollIntoView({block:"nearest"});
+}
+function renderDetail(){
+  const box=$("#detail");
+  if(!sel){box.innerHTML='<div class="muted">Select an incident to inspect its telemetry, timeline and response options.</div>';$("#detailId").textContent="";return;}
+  const i=inc(sel); const m=SEVMETA[i.sev];
+  $("#detailId").textContent=i.id;
+  const noCrew=!i.crew||/\?$/.test(i.crew);
+  const crewLine = i.sev==="ok" ? '<span class="st st-ok">✔ none needed</span>'
+     : noCrew ? '<span class="tag warnflag">⚠ NO CREW ASSIGNED — none available at base</span>'
+     : `<span class="st st-low">${i.crew} on scene</span>`;
+  let telem = i.stale
+     ? `<span class="tag stale">◇ TELEMETRY DELAYED</span> <span class="muted">last packet aged — figures may be stale, explore with caution</span>`
+     : `<span class="st st-ok" style="font-size:10px">live</span>`;
+  const tl=i.tl.map(ev=>`<div class="ev ${ev[2]?'conflict':''}"><div class="t">${ev[0]}${ev[2]?' · ⚡conflict':''}</div><div class="msg">${ev[1]}</div></div>`).join("");
+  box.innerHTML=`
+    <div class="kv">
+      <div class="k">Status</div><div><span class="st ${m.cls}"><span class="g">${m.g}</span>${m.lab}</span> · telemetry ${telem}</div>
+      <div class="k">Line / Stn</div><div><span style="color:${LINES[i.line].color}">●</span> ${LINES[i.line].name} — ${i.station}</div>
+      <div class="k">Cause</div><div>${i.cause}</div>
+      <div class="k">Pax load</div><div><b style="color:${loadColor(i.load)}">${i.load}%</b> platform density ${i.load>=90?'· ⚠ surge':''}</div>
+      <div class="k">Est. clear</div><div>${i.eta}</div>
+      <div class="k">Response</div><div>${crewLine}</div>
+    </div>
+    ${i.conflict?`<div class="warnbox">⚡ Conflicting reports on record — verify before committing a consequential action. Timeline shows the divergence.</div>`:''}
+    <hr>
+    <div class="muted" style="font-size:10px;margin-bottom:2px">EVENT TIMELINE</div>
+    <div class="tl">${tl}</div>
+    <div class="actbar">
+      <button id="actSev">⇅ Change severity <span class="kbd">S</span></button>
+      <button id="actAssign">👷 Assign team <span class="kbd">A</span></button>
+      <button id="actResolve" class="btn-p" style="color:#04203f">✔ Mark recovered</button>
+    </div>`;
+  $("#actSev").onclick=openSeverity;
+  $("#actAssign").onclick=openAssign;
+  $("#actResolve").onclick=()=>confirmAction({
+    title:"Mark incident recovered",icon:"✔",consequential:true,
+    body:`Mark <b>${i.id} — ${i.title}</b> as <b>recovered</b>? This updates the network map and closes crew tasking.`,
+    warn: i.conflict?"This incident has UNRESOLVED conflicting reports. Confirm the trackway is genuinely clear.":null,
+    okLabel:"Confirm recovered",
+    run:()=>{i.sev="ok";i.eta="done";i.tl.push([nowStr(),"Marked recovered by controller"]);logAdd(`<b>${i.id}</b> marked recovered`,"resolve");afterMutate();}
+  });
+}
+
+/* ============================ SEVERITY / ASSIGN ============================ */
+function openSeverity(){
+  const i=inc(sel);
+  const opts=SEV.map(k=>`<option value="${k}" ${k===i.sev?'selected':''}>${SEVMETA[k].g} ${SEVMETA[k].lab}</option>`).join("");
+  confirmAction({
+    title:`Change severity — ${i.id}`,icon:"⇅",consequential:true,
+    body:`Reclassify <b>${i.title}</b>.<br><br>Severity: <select class="mini" id="selSev">${opts}</select><div class="muted" style="margin-top:6px">Escalation re-ranks the incident list and the network map.</div>`,
+    okLabel:"Apply severity",
+    run:()=>{const nv=$("#selSev").value;const old=i.sev;if(nv===old)return;i.sev=nv;i.tl.push([nowStr(),`Severity ${SEVMETA[old].lab} → ${SEVMETA[nv].lab}`]);logAdd(`<b>${i.id}</b> severity ${SEVMETA[old].lab} → ${SEVMETA[nv].lab}`,"sev");afterMutate();}
+  });
+}
+function openAssign(){
+  const i=inc(sel);
+  const avail=CREW.filter(c=>c.status==="available");
+  const unav=CREW.filter(c=>c.status!=="available");
+  let body;
+  if(!avail.length){
+    body=`<div class="warnbox">⚠ NO CREW AVAILABLE. All teams are deployed or on standby. You may still queue a request; exploration is not blocked.</div>
+      <div class="muted" style="margin-top:8px">Deployed / standby:</div>
+      <div style="font-size:11px">${CREW.map(c=>`${c.id} <span class="muted">(${c.status}${c.to?` → ${c.to}`:''}${c.note?`, ${c.note}`:''})</span>`).join("<br>")}</div>`;
+  } else {
+    const o=avail.map(c=>`<option value="${c.id}">${c.id} — ${c.type} (base ${c.base})</option>`).join("");
+    body=`Assign a response team to <b>${i.id} — ${i.title}</b>:<br><br>
+      <select class="mini" id="selCrew">${o}</select>
+      <div class="muted" style="margin-top:6px">Dispatching removes the team from the availability pool.</div>`;
+  }
+  confirmAction({
+    title:`Assign response team — ${i.id}`,icon:"👷",consequential:true,
+    body, okLabel: avail.length?"Dispatch team":"Queue request (no crew)",
+    warn: i.conflict?"Conflicting reports open — a team may arrive to a changed situation.":null,
+    run:()=>{
+      if(!avail.length){i.tl.push([nowStr(),"Crew request QUEUED — none available"]);logAdd(`<b>${i.id}</b> crew request queued (none available)`,"assign");afterMutate();return;}
+      const cid=$("#selCrew").value;const c=CREW.find(x=>x.id===cid);
+      c.status="deployed";c.to=i.id;i.crew=cid;i.tl.push([nowStr(),`${cid} dispatched`]);
+      logAdd(`<b>${cid}</b> dispatched to <b>${i.id}</b>`,"assign");afterMutate();
+    }
+  });
+}
+
+/* ============================ CREW BOX ============================ */
+function renderCrew(){
+  const box=$("#crewBox");
+  const av=CREW.filter(c=>c.status==="available").length;
+  box.innerHTML=`<div style="margin-bottom:6px"><span class="st st-ok">${av} available</span> <span class="st st-high" style="margin-left:4px">${CREW.filter(c=>c.status==='deployed').length} deployed</span> <span class="st st-crit" style="margin-left:4px">${CREW.filter(c=>c.status==='unavailable').length} standby</span></div>`+
+  CREW.map(c=>{
+    const dot=c.status==="available"?"var(--s-ok)":c.status==="deployed"?"var(--s-high)":"var(--s-crit)";
+    const g=c.status==="available"?"✔":c.status==="deployed"?"◆":"■";
+    return `<div style="display:flex;gap:6px;align-items:center;padding:2px 0"><span style="color:${dot}">${g}</span><b>${c.id}</b><span class="muted">${c.type}</span><span style="margin-left:auto" class="muted">${c.status}${c.to?` → ${c.to}`:''}</span></div>`;
+  }).join("");
+}
+
+/* ============================ WEIGHTS + PLANS ============================ */
+function renderWeights(){
+  const w=$("#weights"); w.innerHTML="";
+  CRITERIA.forEach(c=>{
+    const row=el("div","wt");
+    row.innerHTML=`<label for="w_${c.k}">${c.lab}</label>
+      <input type="range" id="w_${c.k}" min="0" max="100" step="5" value="${WEIGHTS[c.k]}" aria-label="${c.lab} weight">
+      <span class="n" id="wn_${c.k}">${WEIGHTS[c.k]}</span>`;
+    w.appendChild(row);
+    row.querySelector("input").addEventListener("input",e=>{
+      WEIGHTS[c.k]=+e.target.value; $("#wn_"+c.k).textContent=e.target.value; updateSum();
+    });
+    row.querySelector("input").addEventListener("change",()=>{ scorePlans(true); });
+  });
+  updateSum();
+}
+function updateSum(){
+  const s=CRITERIA.reduce((a,c)=>a+WEIGHTS[c.k],0);
+  const el2=$("#wsum"); el2.textContent=s;
+  el2.style.color = s===100 ? "var(--good)" : "var(--warn)";
+}
+function planScore(p){
+  const s=CRITERIA.reduce((a,c)=>a+WEIGHTS[c.k],0)||1;
+  return CRITERIA.reduce((a,c)=>a + p.scores[c.k]*WEIGHTS[c.k], 0)/s;
+}
+function scorePlans(fromReweight){
+  const ranked=PLANS.map(p=>({p,score:planScore(p)})).sort((a,b)=>b.score-a.score);
+  const top=ranked[0], second=ranked[1];
+  renderPlans(ranked);
+  renderWhy(ranked, fromReweight);
+  if(fromReweight){
+    if(lastTop && lastTop!==top.p.id){
+      logAdd(`Reweight → recommendation <b>flipped ${lastTop} → Plan ${top.p.id}</b> (${top.p.name})`,"reweight");
+    } else {
+      logAdd(`Criteria reweighted — recommendation holds at <b>Plan ${top.p.id}</b>`,"reweight");
+    }
+  }
+  lastTop=top.p.id;
+}
+function renderPlans(ranked){
+  const box=$("#plans"); box.innerHTML="";
+  ranked.forEach(({p,score},idx)=>{
+    const win=idx===0;
+    const d=el("div","plan"+(win?" win":"")+(win?" open":""));
+    d.innerHTML=`<div class="ph"><span class="nm">Plan ${p.id}</span><span class="muted" style="font-size:11px">${p.name}</span>${win?'<span class="win-b">◀ RECOMMENDED</span>':''}<span class="sc" style="color:${win?'var(--good)':'var(--ink2)'}">${score.toFixed(1)}</span></div>
+      <div class="pb">
+        <div class="muted" style="font-size:11px;margin-bottom:5px">${p.note}</div>
+        ${CRITERIA.map(c=>`<div class="crit"><div class="lab">${c.lab}</div><div class="track"><i style="width:${p.scores[c.k]}%;background:${win?'var(--good)':'var(--accent)'}"></i></div><div class="val">${p.scores[c.k]} ·w${WEIGHTS[c.k]}</div></div><div class="muted" style="font-size:10px;grid-column:1/3;margin:-2px 0 4px 2px">↳ ${p.ev[c.k]}</div>`).join("")}
+        <button class="btn-p" style="color:#04203f;margin-top:4px" data-adopt="${p.id}">Adopt Plan ${p.id}</button>
+      </div>`;
+    d.querySelector(".ph").addEventListener("click",()=>d.classList.toggle("open"));
+    d.querySelector("[data-adopt]").addEventListener("click",e=>{e.stopPropagation();adoptPlan(p);});
+    box.appendChild(d);
+  });
+}
+function renderWhy(ranked, fromReweight){
+  const top=ranked[0], second=ranked[1];
+  // find the criterion contributing most to top's lead over second
+  let best=null, bestGap=-1e9;
+  CRITERIA.forEach(c=>{
+    const contrib=(top.p.scores[c.k]-second.p.scores[c.k])*WEIGHTS[c.k];
+    if(contrib>bestGap){bestGap=contrib;best=c;}
+  });
+  const margin=(top.score-second.score).toFixed(1);
+  const flipNote = (fromReweight && lastTop && lastTop!==top.p.id)
+    ? `<div class="flip">⚑ The reweight FLIPPED the recommendation from Plan ${lastTop} to Plan ${top.p.id}.</div>` : "";
+  $("#why").innerHTML=`${flipNote}
+    <b>Recommendation: Plan ${top.p.id} — ${top.p.name}</b> (score ${top.score.toFixed(1)}, +${margin} over Plan ${second.p.id}).<br>
+    <b>Deciding evidence:</b> <span style="color:var(--accent)">${best.lab}</span> — weighted at ${WEIGHTS[best.k]}%, Plan ${top.p.id} scores ${top.p.scores[best.k]} vs Plan ${second.p.id}'s ${second.p.scores[best.k]}.<br>
+    <span class="muted">↳ ${top.p.ev[best.k]}</span><br>
+    <span class="muted" style="font-size:10px">Change the weights above to see how the recommendation — and the deciding evidence — shifts.</span>`;
+}
+function adoptPlan(p){
+  confirmAction({
+    title:`Adopt Recovery Plan ${p.id}`,icon:"▶",consequential:true,
+    body:`Commit to <b>Plan ${p.id} — ${p.name}</b>?<br><span class="muted">${p.tag}</span><br><br>Weighted score ${planScore(p).toFixed(1)} under current criteria (Safety ${WEIGHTS.safety} · Delay ${WEIGHTS.delay} · Cost ${WEIGHTS.cost} · Recovery ${WEIGHTS.recovery}).`,
+    warn: sim.on?"You are in SIMULATION mode — adoption is sandboxed and reversible.":"This is a network-wide operational decision affecting all four lines.",
+    okLabel:`Commit Plan ${p.id}`,
+    run:()=>{logAdd(`<b>Plan ${p.id} adopted</b> — ${p.name} (score ${planScore(p).toFixed(1)})`,"adopt");toast(`Plan ${p.id} committed`);}
+  });
+}
+
+/* ============================ DECISION LOG ============================ */
+function renderLog(){
+  const box=$("#log");
+  if(!LOG.length){box.innerHTML='<div class="muted">No decisions logged yet.</div>';return;}
+  box.innerHTML=LOG.map(e=>`<div class="le"><span class="t">${e.t}${e.sim?' ⚙':''}</span><span class="m">${e.msg}</span></div>`).join("");
+}
+$("#expLog").onclick=()=>{
+  const txt=LOG.slice().reverse().map(e=>`[${e.t}]${e.sim?' (SIM)':''} ${e.msg.replace(/<[^>]+>/g,"")}`).join("\n");
+  confirmAction({title:"Decision trail export",icon:"⤓",consequential:false,okLabel:"Close",cancel:false,
+    body:`<div class="muted" style="font-size:10px;margin-bottom:4px">${LOG.length} entries — timestamped controller decision trail</div><pre style="white-space:pre-wrap;font-family:var(--mono);font-size:10px;max-height:300px;overflow:auto;background:#0a0d12;border:1px solid var(--edge);border-radius:5px;padding:8px">${txt||'(empty)'}</pre>`,
+    run:()=>{}});
+};
+
+/* ============================ SIMULATION MODE ============================ */
+function snapshot(){
+  return JSON.stringify({INCIDENTS,CREW,WEIGHTS,PLANScores:PLANS.map(p=>p.scores)});
+}
+function toggleSim(){
+  if(!sim.on){
+    sim.on=true; sim.snapshot=snapshot(); sim.changes=0;
+    $("#simbadge").classList.add("on");
+    logAdd("⚙ <b>Simulation mode ON</b> — subsequent changes are sandboxed","sim");
+    toast("Simulation mode ON — changes can be undone");
+  }
+}
+function endSim(keep){
+  if(!sim.on)return;
+  if(!keep){
+    const snap=JSON.parse(sim.snapshot);
+    INCIDENTS.length=0; snap.INCIDENTS.forEach(x=>INCIDENTS.push(x));
+    CREW.length=0; snap.CREW.forEach(x=>CREW.push(x));
+    Object.assign(WEIGHTS,snap.WEIGHTS);
+    snap.PLANScores.forEach((s,idx)=>PLANS[idx].scores=s);
+    logAdd("↩ <b>Simulation DISCARDED</b> — state rolled back","sim");
+    toast("Simulation discarded — rolled back");
+  } else {
+    logAdd("⚙ <b>Simulation committed</b> to live state","sim");
+    toast("Simulation changes kept");
+  }
+  sim.on=false; $("#simbadge").classList.remove("on");
+  renderAll();
+}
+$("#simEnd").onclick=()=>endSim(true);
+$("#simDiscard").onclick=()=>confirmAction({title:"Discard simulation",icon:"↩",consequential:true,
+  body:"Roll back <b>all</b> simulated changes to the snapshot taken when simulation started?",okLabel:"Discard & roll back",
+  run:()=>endSim(false)});
+
+/* ============================ CONFIRMATION MODAL ============================ */
+let modalRun=null;
+function confirmAction(o){
+  $("#mIcon").textContent=o.icon||"⚠";
+  $("#mTitle").textContent=o.title||"Confirm";
+  $("#mBody").innerHTML=(o.body||"")+(o.warn?`<div class="warnbox">⚠ ${o.warn}</div>`:"")+
+    (o.consequential?`<div class="muted" style="font-size:10px;margin-top:8px">Consequential action — requires confirmation. It will be timestamped in the decision log.</div>`:"");
+  const f=$("#mFoot"); f.innerHTML="";
+  if(o.cancel!==false){const cb=el("button",null,"Cancel");cb.onclick=closeModal;f.appendChild(cb);}
+  const ok=el("button",o.consequential&&/discard|recovered|delete|suspend/i.test(o.okLabel||"")?"btn-d":"btn-p",o.okLabel||"Confirm");
+  if(!o.consequential||!/discard/i.test(o.okLabel||"")) ok.className=o.okLabel&&/discard/i.test(o.okLabel)?"btn-d":"btn-p";
+  ok.style.color=ok.className==="btn-p"?"#04203f":"";
+  ok.onclick=()=>{closeModal();o.run&&o.run();};
+  f.appendChild(ok);
+  modalRun=o.run;
+  $("#ov").classList.add("on"); ok.focus();
+}
+function closeModal(){$("#ov").classList.remove("on");modalRun=null;}
+$("#ov").addEventListener("click",e=>{if(e.target.id==="ov")closeModal();});
+
+/* ============================ RENDER ALL ============================ */
+function afterMutate(){ if(sim.on)sim.changes++; renderMap();renderList();renderDetail();renderCrew();scorePlans(false); }
+function renderAll(){ renderMap();buildFilterChips();renderList();renderDetail();renderCrew();renderWeights();scorePlans(false);renderLog(); }
+
+/* ============================ TABS (390px) ============================ */
+function switchTab(t){
+  document.querySelectorAll(".col").forEach(c=>c.classList.toggle("active",c.dataset.tab===t));
+  document.querySelectorAll("#tabbar button").forEach(b=>b.classList.toggle("on",b.dataset.t===t));
+}
+document.querySelectorAll("#tabbar button").forEach(b=>b.onclick=()=>switchTab(b.dataset.t));
+
+/* ============================ KEYBOARD ============================ */
+document.addEventListener("keydown",e=>{
+  if($("#ov").classList.contains("on")){ if(e.key==="Escape")closeModal(); return; }
+  const typing=/INPUT|SELECT|TEXTAREA/.test(document.activeElement.tagName);
+  if(e.key==="/"&&!typing){e.preventDefault();$("#q").focus();return;}
+  if(typing)return;
+  const rows=INCIDENTS.filter(passes).sort((a,b)=>riskScore(b)-riskScore(a));
+  if(e.key==="ArrowDown"||e.key==="ArrowUp"){
+    e.preventDefault();
+    if(!rows.length)return;
+    let idx=rows.findIndex(i=>i.id===sel);
+    idx = e.key==="ArrowDown" ? Math.min(rows.length-1,idx+1) : Math.max(0,idx<0?0:idx-1);
+    selectInc(rows[idx].id);
+  }
+  else if(e.key.toLowerCase()==="s"&&sel){openSeverity();}
+  else if(e.key.toLowerCase()==="a"&&sel){openAssign();}
+  else if(e.key.toLowerCase()==="m"){toggleSim();}
+  else if(e.key.toLowerCase()==="u"){ if(sim.on)$("#simDiscard").click(); else toast("Undo available only in simulation mode (press M)"); }
+});
+
+/* ============================ CLOCK ============================ */
+setInterval(()=>{$("#clock").textContent=nowStr();},1000);
+$("#clock").textContent=nowStr();
+
+/* ============================ INIT ============================ */
+renderAll();
+logAdd("Console initialized — evening rush recovery session opened","init");
+selectInc("I-1");
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6e2d6635424a/qwen25-7b.html b/data/artifacts/6e2d6635424a/qwen25-7b.html
new file mode 100644
index 0000000..bedf826
--- /dev/null
+++ b/data/artifacts/6e2d6635424a/qwen25-7b.html
@@ -0,0 +1,116 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Railway Control Console</title>
+    <style>
+        body { font-family: Arial, sans-serif; }
+        .container { display: grid; grid-template-columns: 3fr 2fr; gap: 20px; padding: 20px; }
+        #map { width: 480px; height: 360px; border: 1px solid #ccc; background-color: lightgray; }
+        .status { color: var(--color-status); font-weight: bold; }
+        .incident { cursor: pointer; }
+        .timeline { margin-top: 20px; white-space: pre-wrap; word-break: break-all; }
+        .criteria { display: flex; gap: 10px; align-items: center; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <div id="map" style="grid-column: span 2;"></div>
+        <div id="console">
+            <h1>Railway Control Console</h1>
+            <p><span class="status">Normal</span></p>
+            <button onclick="toggleSimulation()">Toggle Simulation Mode</button>
+            <table id="incidents">
+                <thead>
+                    <tr>
+                        <th>Incident ID</th>
+                        <th>Status</th>
+                        <th>Description</th>
+                        <th>Severity</th>
+                        <th>Action</th>
+                    </tr>
+                </thead>
+                <tbody>
+                    <tr class="incident" onclick="selectIncident(1)">
+                        <td>1</td>
+                        <td><span class="status">High</span></td>
+                        <td>Track obstruction at station B</td>
+                        <td>4</td>
+                        <td><button onclick="changeSeverity(1, 2)">Change Severity</button></td>
+                    </tr>
+                    <!-- Add more incidents as needed -->
+                </tbody>
+            </table>
+            <div id="timeline">Timeline: [Incident 1 - 3pm; Incident 2 - 4pm]</div>
+            <h2>Criteria for Recovery Plans</h2>
+            <div class="criteria">
+                <label><input type="checkbox" checked data-criteria="Safety"> Safety</label>
+                <label><input type="checkbox" checked data-criteria="Delay"> Passenger Delay</label>
+                <label><input type="checkbox" checked data-criteria="Cost"> Cost</label>
+                <label><input type="checkbox" checked data-criteria="Network"> Network Recovery</label>
+            </div>
+            <h2>Recovery Plans</h2>
+            <form id="planForm">
+                <fieldset>
+                    <legend>Plan 1: Quick Clearance</legend>
+                    <p>Safety: High; Delay: Low; Cost: Medium; Network: Partial</p>
+                    <button type="submit" data-plan="1">Select Plan 1</button>
+                </fieldset>
+                <fieldset>
+                    <legend>Plan 2: Long-term Repair</legend>
+                    <p>Safety: Medium; Delay: High; Cost: Low; Network: Full</p>
+                    <button type="submit" data-plan="2">Select Plan 2</button>
+                </fieldset>
+            </form>
+            <div id="decisionLog">
+                <h3>Decision Log:</h3>
+                <pre>[14:05] Incident 1 selected; [14:10] Plan 1 recommended.</pre>
+            </div>
+        </div>
+    </div>
+    <script>
+        let activeIncident = null;
+        const incidents = [
+            { id: 1, status: 'High', severity: 4, description: 'Track obstruction at station B' },
+            // Add more incident data as needed
+        ];
+
+        function selectIncident(id) {
+            if (activeIncident === id) return;
+            activeIncident = id;
+            incidents.forEach(i => i.status = 'Normal');
+            document.querySelectorAll('.incident').forEach(el => el.classList.remove('selected'));
+            const selectedIncident = document.querySelector(`.incident[id="${id}"]`);
+            selectedIncident.classList.add('selected');
+        }
+
+        function changeSeverity(id, newSeverity) {
+            incidents.find(i => i.id === id).severity = newSeverity;
+            selectIncident(id);
+        }
+
+        function toggleSimulation() {
+            const simulationMode = document.body.dataset.simulation;
+            if (simulationMode === 'true') {
+                document.body.dataset.simulation = false;
+                alert('Simulation mode is now off. No changes are applied.');
+            } else {
+                document.body.dataset.simulation = true;
+                alert('Simulation mode is on. Changes will be reverted before submission.');
+            }
+        }
+
+        function handlePlanSelection(event) {
+            const planId = event.target.getAttribute('data-plan');
+            if (document.body.dataset.simulation === 'true') return;
+            console.log(`[14:30] Plan ${planId} selected`);
+            document.getElementById('decisionLog').innerHTML += `\n[14:30] Plan ${planId} selected;`;
+        }
+
+        document.querySelectorAll('.incident').forEach(el => el.addEventListener('click', selectIncident));
+        document.getElementById('timeline').innerText = `[Incident 1 - 3pm; Incident 2 - 4pm]`;
+        document.getElementById('planForm').addEventListener('submit', handlePlanSelection);
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/6e2d6635424a/qwen3-14b.html b/data/artifacts/6e2d6635424a/qwen3-14b.html
new file mode 100644
index 0000000..a23f31e
--- /dev/null
+++ b/data/artifacts/6e2d6635424a/qwen3-14b.html
@@ -0,0 +1,400 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Transit Controller Console</title>
+  <style>
+    body {
+      margin: 0;
+      font-family: sans-serif;
+      background: #1e1e1e;
+      color: #f0f0f0;
+      display: flex;
+      height: 100vh;
+    }
+    #map {
+      flex: 1;
+      background: #2a2a2a;
+      position: relative;
+      overflow: hidden;
+      padding: 10px;
+    }
+    .incident {
+      position: absolute;
+      background: #444;
+      padding: 4px 8px;
+      border-radius: 4px;
+      cursor: pointer;
+      user-select: none;
+    }
+    .incident.high-risk {
+      background: #d9534f;
+    }
+    .incident.medium-risk {
+      background: #f0ad4e;
+    }
+    .incident.low-risk {
+      background: #5cb85c;
+    }
+    #console {
+      width: 390px;
+      background: #222;
+      display: flex;
+      flex-direction: column;
+      overflow: hidden;
+    }
+    #header {
+      padding: 10px;
+      background: #333;
+      border-bottom: 1px solid #444;
+    }
+    #log {
+      flex: 1;
+      overflow-y: auto;
+      padding: 10px;
+      font-size: 14px;
+    }
+    .log-entry {
+      margin-bottom: 4px;
+    }
+    .log-entry label {
+      font-weight: bold;
+    }
+    #controls {
+      padding: 10px;
+      background: #333;
+      border-top: 1px solid #444;
+    }
+    button {
+      margin-right: 10px;
+      padding: 6px 12px;
+      background: #444;
+      color: #fff;
+      border: none;
+      border-radius: 4px;
+      cursor: pointer;
+    }
+    button:hover {
+      background: #555;
+    }
+    #incident-filter {
+      width: 100%;
+      padding: 6px;
+      margin-bottom: 10px;
+      background: #333;
+      border: none;
+      color: #fff;
+      font-size: 14px;
+    }
+    #timeline {
+      margin-top: 10px;
+      font-size: 13px;
+      line-height: 1.3;
+    }
+    .timeline-item {
+      margin-bottom: 4px;
+    }
+    .plan {
+      margin-top: 10px;
+      background: #2a2a2a;
+      padding: 8px;
+      border-radius: 4px;
+    }
+    .plan h3 {
+      margin: 0;
+      font-size: 16px;
+    }
+    .plan p {
+      margin: 4px 0;
+    }
+    .plan .score {
+      font-weight: bold;
+    }
+    .plan .score span {
+      color: #fff;
+    }
+    .plan .score .safety { color: #f0ad4e; }
+    .plan .score .delay { color: #5cb85c; }
+    .plan .score .cost { color: #d9534f; }
+    .plan .score .recovery { color: #337ab7; }
+    .plan .selected {
+      background: #444;
+    }
+    .plan .plan-button {
+      margin-top: 4px;
+      font-size: 12px;
+    }
+    .plan .plan-button:hover {
+      text-decoration: underline;
+    }
+    #reweight {
+      display: flex;
+      flex-wrap: wrap;
+      margin-top: 10px;
+    }
+    #reweight input {
+      width: 40px;
+      margin-right: 5px;
+      margin-bottom: 5px;
+    }
+    #reweight label {
+      margin-right: 10px;
+    }
+    #sim-toggle {
+      margin-top: 10px;
+    }
+  </style>
+</head>
+<body>
+  <div id="map"></div>
+  <div id="console">
+    <div id="header">
+      <h2>Transit Control Console</h2>
+    </div>
+    <div id="log"></div>
+    <div id="controls">
+      <input type="text" id="incident-filter" placeholder="Filter incidents..." oninput="filterIncidents()">
+      <button onclick="inspectTimeline()">Inspect Timeline</button>
+      <button onclick="changeSeverity()">Change Severity</button>
+      <button onclick="assignTeam()">Assign Response Team</button>
+      <button onclick="comparePlans()">Compare Plans</button>
+      <button onclick="toggleSimulation()">Toggle Simulation Mode</button>
+    </div>
+  </div>
+  <script>
+    const map = document.getElementById('map');
+    const log = document.getElementById('log');
+    const consoleControls = document.getElementById('controls');
+    const incidentFilter = document.getElementById('incident-filter');
+    let simulationMode = false;
+    let undoStack = [];
+    let currentPlanIndex = -1;
+    let currentIncident = null;
+
+    const incidents = [
+      { id: 1, line: "Red", pos: { x: 100, y: 100 }, type: "Derailment", severity: "high", crew: "none", delay: "15 mins", load: "85%", status: "active" },
+      { id: 2, line: "Blue", pos: { x: 200, y: 150 }, type: "Signal Failure", severity: "medium", crew: "available", delay: "10 mins", load: "75%", status: "active" },
+      { id: 3, line: "Green", pos: { x: 150, y: 200 }, type: "Power Outage", severity: "medium", crew: "available", delay: "12 mins", load: "90%", status: "active" },
+      { id: 4, line: "Yellow", pos: { x: 50, y: 150 }, type: "Track Blockage", severity: "high", crew: "available", delay: "20 mins", load: "95%", status: "active" },
+      { id: 5, line: "Red", pos: { x: 250, y: 100 }, type: "Train Delay", severity: "low", crew: "available", delay: "5 mins", load: "60%", status: "active" },
+      { id: 6, line: "Blue", pos: { x: 200, y: 50 }, type: "Passenger Overload", severity: "medium", crew: "available", delay: "7 mins", load: "80%", status: "active" },
+      { id: 7, line: "Green", pos: { x: 100, y: 250 }, type: "Communication Failure", severity: "low", crew: "available", delay: "3 mins", load: "70%", status: "active" },
+      { id: 8, line: "Yellow", pos: { x: 150, y: 50 }, type: "Emergency Stop", severity: "high", crew: "available", delay: "18 mins", load: "92%", status: "active" }
+    ];
+
+    const plans = [
+      { name: "Plan A", safety: 90, delay: 80, cost: 60, recovery: 75 },
+      { name: "Plan B", safety: 85, delay: 75, cost: 70, recovery: 80 },
+      { name: "Plan C", safety: 80, delay: 70, cost: 65, recovery: 85 }
+    ];
+
+    const criteria = { safety: 0.3, delay: 0.25, cost: 0.2, recovery: 0.25 };
+
+    function initMap() {
+      map.innerHTML = '';
+      const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+      svg.setAttribute("width", "100%");
+      svg.setAttribute("height", "100%");
+      svg.style.background = "#2a2a2a";
+      svg.style.position = "absolute";
+      svg.style.top = "0";
+      svg.style.left = "0";
+      svg.style.width = "100%";
+      svg.style.height = "100%";
+      map.appendChild(svg);
+      drawMapLines(svg);
+      drawIncidents(svg);
+    }
+
+    function drawMapLines(svg) {
+      const lines = new Set(incidents.map(i => i.line));
+      lines.forEach(line => {
+        const lineGroup = document.createElementNS("http://www.w3.org/2000/svg", "g");
+        lineGroup.setAttribute("id", `line-${line}`);
+        lineGroup.style.stroke = line === "Red" ? "#d9534f" : line === "Blue" ? "#5bc0de" : line === "Green" ? "#5cb85c" : "#f0ad4e";
+        lineGroup.style.strokeWidth = 2;
+        lineGroup.style.strokeDasharray = "4,4";
+        lineGroup.style.opacity = 0.5;
+
+        const points = incidents.filter(i => i.line === line).map(i => `${i.pos.x},${i.pos.y}`);
+        const path = document.createElementNS("http://www.w3.org/2000/svg", "path");
+        path.setAttribute("d", `M${points[0]} L${points.slice(1).join(" L")}`);
+        lineGroup.appendChild(path);
+        svg.appendChild(lineGroup);
+      });
+    }
+
+    function drawIncidents(svg) {
+      incidents.forEach(incident => {
+        const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle");
+        circle.setAttribute("cx", incident.pos.x);
+        circle.setAttribute("cy", incident.pos.y);
+        circle.setAttribute("r", 8);
+        circle.style.fill = "#444";
+        circle.style.stroke = "#fff";
+        circle.style.strokeWidth = 1;
+        circle.setAttribute("id", `incident-${incident.id}`);
+        circle.addEventListener("click", () => selectIncident(incident.id));
+        svg.appendChild(circle);
+
+        const text = document.createElementNS("http://www.w3.org/2000/svg", "text");
+        text.setAttribute("x", incident.pos.x);
+        text.setAttribute("y", incident.pos.y - 10);
+        text.textContent = incident.id;
+        text.style.fontSize = "12px";
+        text.style.fill = "#fff";
+        svg.appendChild(text);
+      });
+    }
+
+    function selectIncident(id) {
+      currentIncident = incidents.find(i => i.id === id);
+      log.innerHTML += `<div class="log-entry"><label>Selected Incident:</label> ${id} - ${currentIncident.line} - ${currentIncident.type} - <span style="color: ${getSeverityColor(currentIncident.severity)};">${currentIncident.severity}</span></div>`;
+      undoStack.push({ action: "select", incident: currentIncident });
+    }
+
+    function filterIncidents() {
+      const filter = incidentFilter.value.toLowerCase();
+      const filtered = incidents.filter(i => i.line.toLowerCase().includes(filter) || i.type.toLowerCase().includes(filter));
+      log.innerHTML += `<div class="log-entry"><label>Filtered Incidents:</label> ${filtered.length} found.</div>`;
+    }
+
+    function inspectTimeline() {
+      log.innerHTML += `<div class="log-entry"><label>Timeline:</label><div id="timeline"></div></div>`;
+      const timeline = document.getElementById('timeline');
+      timeline.innerHTML = "";
+      const timelineData = [
+        { time: "17:00", event: "Incident 1 reported", status: "active" },
+        { time: "17:05", event: "Crew dispatched to incident 1", status: "active" },
+        { time: "17:10", event: "Incident 4 reported", status: "active" },
+        { time: "17:15", event: "Passenger load increased on line Red", status: "active" },
+        { time: "17:20", event: "Incident 8 reported", status: "active" },
+        { time: "17:30", event: "Crew dispatched to incident 8", status: "active" },
+        { time: "17:45", event: "Crew unavailable for incident 1", status: "active" },
+        { time: "18:00", event: "Incident 1 resolved", status: "resolved" },
+        { time: "18:15", event: "All incidents resolved", status: "resolved" }
+      ];
+      timelineData.forEach(item => {
+        const div = document.createElement('div');
+        div.className = 'timeline-item';
+        div.textContent = `${item.time}: ${item.event} [${item.status}]`;
+        timeline.appendChild(div);
+      });
+      undoStack.push({ action: "inspect", timeline: timelineData });
+    }
+
+    function changeSeverity() {
+      if (currentIncident) {
+        const newSeverity = prompt(`Change severity of incident ${currentIncident.id} from ${currentIncident.severity} to (high/medium/low):`);
+        if (newSeverity && ["high", "medium", "low"].includes(newSeverity)) {
+          currentIncident.severity = newSeverity;
+          log.innerHTML += `<div class="log-entry"><label>Severity changed:</label> Incident ${currentIncident.id} is now <span style="color: ${getSeverityColor(newSeverity)};">${newSeverity}</span>.</div>`;
+          undoStack.push({ action: "changeSeverity", old: currentIncident.severity, new: newSeverity, incident: currentIncident });
+        }
+      }
+    }
+
+    function assignTeam() {
+      if (currentIncident) {
+        const team = prompt(`Assign response team to incident ${currentIncident.id} (e.g., "Team 3"):`);
+        if (team) {
+          currentIncident.crew = team;
+          log.innerHTML += `<div class="log-entry"><label>Assigned Team:</label> Incident ${currentIncident.id} now has ${team} assigned.</div>`;
+          undoStack.push({ action: "assignTeam", team: team, incident: currentIncident });
+        }
+      }
+    }
+
+    function getSeverityColor(sev) {
+      switch (sev) {
+        case "high": return "#d9534f";
+        case "medium": return "#f0ad4e";
+        case "low": return "#5cb85c";
+        default: return "#fff";
+      }
+    }
+
+    function comparePlans() {
+      const planContainer = document.getElementById('console');
+      planContainer.innerHTML += `<div id="plans"></div>`;
+      const plansDiv = document.getElementById('plans');
+      plans.forEach((plan, idx) => {
+        const planDiv = document.createElement('div');
+        planDiv.className = 'plan';
+        planDiv.innerHTML = `
+          <h3>${plan.name}</h3>
+          <div class="score">Safety: <span class="safety">${plan.safety}</span> | Delay: <span class="delay">${plan.delay}</span> | Cost: <span class="cost">${plan.cost}</span> | Recovery: <span class="recovery">${plan.recovery}</span></div>
+          <button class="plan-button" onclick="selectPlan(${idx})">Simulate</button>
+        `;
+        plansDiv.appendChild(planDiv);
+      });
+    }
+
+    function selectPlan(index) {
+      const selectedPlan = plans[index];
+      const total = selectedPlan.safety * criteria.safety + selectedPlan.delay * criteria.delay + selectedPlan.cost * criteria.cost + selectedPlan.recovery * criteria.recovery;
+      log.innerHTML += `<div class="log-entry"><label>Recommended Plan:</label> <strong>${selectedPlan.name}</strong> with total score: <span style="color: #00ff00;">${total.toFixed(2)}</span>.</div>`;
+      undoStack.push({ action: "selectPlan", plan: selectedPlan });
+    }
+
+    function reweightCriteria() {
+      const safety = parseFloat(document.getElementById('safety').value);
+      const delay = parseFloat(document.getElementById('delay').value);
+      const cost = parseFloat(document.getElementById('cost').value);
+      const recovery = parseFloat(document.getElementById('recovery').value);
+      criteria.safety = safety;
+      criteria.delay = delay;
+      criteria.cost = cost;
+      criteria.recovery = recovery;
+      const total = plans.map(p => p.safety * safety + p.delay * delay + p.cost * cost + p.recovery * recovery);
+      const maxIndex = total.indexOf(Math.max(...total));
+      const maxPlan = plans[maxIndex];
+      log.innerHTML += `<div class="log-entry"><label>Recalculated Recommendation:</label> <strong>${maxPlan.name}</strong> with new score: <span style="color: #00ff00;">${(total[maxIndex]).toFixed(2)}</span>.<br>Changes: Safety=${safety}, Delay=${delay}, Cost=${cost}, Recovery=${recovery}</div>`;
+    }
+
+    function toggleSimulation() {
+      simulationMode = !simulationMode;
+      log.innerHTML += `<div class="log-entry"><label>Simulation Mode:</label> ${simulationMode ? "ON" : "OFF"}.</div>`;
+    }
+
+    function undo() {
+      if (undoStack.length > 0) {
+        const last = undoStack.pop();
+        if (last.action === "select") {
+          currentIncident = null;
+        } else if (last.action === "changeSeverity") {
+          last.incident.severity = last.old;
+        } else if (last.action === "assignTeam") {
+          last.incident.crew = "none";
+        } else if (last.action === "selectPlan") {
+          // No direct reversal, just clear log
+        }
+        log.innerHTML = log.innerHTML.slice(0, -1);
+      }
+    }
+
+    function initConsole() {
+      initMap();
+      const reweightDiv = document.createElement('div');
+      reweightDiv.id = 'reweight';
+      reweightDiv.innerHTML = `
+        <label>Safety: <input type="number" id="safety" value="${criteria.safety}" min="0" max="1" step="0.01"></label>
+        <label>Delay: <input type="number" id="delay" value="${criteria.delay}" min="0" max="1" step="0.01"></label>
+        <label>Cost: <input type="number" id="cost" value="${criteria.cost}" min="0" max="1" step="0.01"></label>
+        <label>Recovery: <input type="number" id="recovery" value="${criteria.recovery}" min="0" max="1" step="0.01"></label>
+        <button onclick="reweightCriteria()">Recalculate</button>
+      `;
+      consoleControls.appendChild(reweightDiv);
+      consoleControls.innerHTML += '<button onclick="undo()">Undo</button>';
+    }
+
+    window.addEventListener('keydown', (e) => {
+      if (e.key === 'z' && e.ctrlKey) {
+        undo();
+      }
+    });
+
+    initConsole();
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/84f06b214f8c/claude-code.html b/data/artifacts/84f06b214f8c/claude-code.html
new file mode 100644
index 0000000..5d0156b
--- /dev/null
+++ b/data/artifacts/84f06b214f8c/claude-code.html
@@ -0,0 +1,1134 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
+<title>Strata No. 7 — Sample Memo Studio</title>
+<style>
+  :root{
+    --paper:#f4f1ea;
+    --paper-2:# eeeae1;
+    --card:#faf8f3;
+    --ink:#26241f;
+    --ink-soft:#5c574c;
+    --ink-faint:#8f897c;
+    --line:#ddd7c9;
+    --line-soft:#e8e3d7;
+    --accent:#6a6250;
+    --accent-deep:#403b30;
+    --ok:#4f6b4a;
+    --warn:#9a6a2e;
+    --stop:#8a4436;
+    --radius:3px;
+    --shadow:0 1px 0 rgba(0,0,0,.03), 0 12px 40px -28px rgba(40,36,28,.5);
+    --serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
+    --sans:"Helvetica Neue",Helvetica,Arial,sans-serif;
+  }
+  *{box-sizing:border-box;margin:0;padding:0}
+  html{-webkit-text-size-adjust:100%}
+  body{
+    background:var(--paper);
+    color:var(--ink);
+    font-family:var(--sans);
+    font-size:15px;
+    line-height:1.5;
+    -webkit-font-smoothing:antialiased;
+    letter-spacing:.1px;
+  }
+  .wrap{max-width:1180px;margin:0 auto;padding:0 22px 120px}
+  .micro{font-family:var(--sans);font-size:10.5px;letter-spacing:.22em;text-transform:uppercase;color:var(--ink-faint)}
+  h1,h2,h3{font-family:var(--serif);font-weight:500;letter-spacing:.2px}
+
+  /* ---- session banner ---- */
+  #sessionBanner{
+    display:none;background:#efe9db;border-bottom:1px solid var(--line);
+    font-size:13px;color:var(--ink-soft);
+  }
+  #sessionBanner .inner{max-width:1180px;margin:0 auto;padding:9px 22px;display:flex;gap:14px;align-items:center;justify-content:space-between}
+  #sessionBanner button{background:none;border:none;color:var(--accent-deep);text-decoration:underline;cursor:pointer;font-size:12px;font-family:var(--sans)}
+
+  /* ---- header ---- */
+  header.masthead{border-bottom:1px solid var(--line);margin-bottom:34px}
+  .masthead .inner{max-width:1180px;margin:0 auto;padding:26px 22px 22px;display:flex;justify-content:space-between;align-items:flex-end;flex-wrap:wrap;gap:16px}
+  .masthead h1{font-size:30px;line-height:1}
+  .masthead .sub{font-size:12.5px;color:var(--ink-soft);margin-top:8px;max-width:46ch}
+  .masthead .col-right{text-align:right;font-size:11.5px;color:var(--ink-faint);line-height:1.7}
+  .masthead .col-right b{color:var(--ink-soft);font-weight:500}
+
+  .section-label{display:flex;align-items:center;gap:14px;margin:40px 0 18px}
+  .section-label:before{content:"";flex:0 0 22px;height:1px;background:var(--accent)}
+  .section-label h2{font-size:18px}
+  .section-label .hint{font-size:12px;color:var(--ink-faint);margin-left:auto}
+
+  /* ---- colorway grid ---- */
+  .cw-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
+  .cw{
+    background:var(--card);border:1px solid var(--line-soft);border-radius:var(--radius);
+    overflow:hidden;cursor:pointer;transition:border-color .2s, box-shadow .2s, transform .2s;
+    display:flex;flex-direction:column;
+  }
+  .cw:hover{border-color:var(--accent);box-shadow:var(--shadow)}
+  .cw.selected{border-color:var(--accent-deep);box-shadow:0 0 0 1px var(--accent-deep) inset, var(--shadow)}
+  .swatch{height:150px;position:relative;overflow:hidden}
+  .cw .body{padding:12px 13px 14px}
+  .cw .body h3{font-size:16px;margin-bottom:2px}
+  .cw .body .desc{font-size:11.5px;color:var(--ink-soft);min-height:32px;line-height:1.45}
+  .cw .avail{font-size:10px;letter-spacing:.14em;text-transform:uppercase;margin-top:9px;color:var(--ink-faint)}
+  .cw .avail b{color:var(--ok);font-weight:600}
+  .cw .actions{display:flex;gap:7px;margin-top:11px}
+  .cw .actions button{flex:1;font-family:var(--sans);font-size:10.5px;letter-spacing:.1em;text-transform:uppercase;
+    padding:8px 4px;border:1px solid var(--line);background:#fff;color:var(--ink-soft);border-radius:2px;cursor:pointer;transition:.15s}
+  .cw .actions button:hover{border-color:var(--accent);color:var(--accent-deep)}
+  .cw .actions button.on{background:var(--accent-deep);border-color:var(--accent-deep);color:#f4f1ea}
+
+  /* ---- material preview swatch texture ---- */
+  .preview-surface{position:absolute;inset:0}
+  .tex-base{position:absolute;inset:0;background:var(--cw-color,#999)}
+  .tex-ground{position:absolute;inset:0;mix-blend-mode:multiply;opacity:.9}
+  .tex-ground.linen{
+    background-image:
+      repeating-linear-gradient(0deg, rgba(0,0,0,.055) 0 1px, transparent 1px 3px),
+      repeating-linear-gradient(90deg, rgba(0,0,0,.05) 0 1px, transparent 1px 3px),
+      repeating-linear-gradient(0deg, rgba(255,255,255,.06) 0 1px, transparent 1px 4px);
+  }
+  .tex-ground.clay{
+    background-image:
+      radial-gradient(rgba(0,0,0,.05) .7px, transparent .8px),
+      radial-gradient(rgba(255,255,255,.05) .7px, transparent .8px);
+    background-size:4px 4px, 5px 5px;
+    background-position:0 0, 2px 3px;
+  }
+  .tex-sheen{position:absolute;inset:0}
+  .tex-sheen.matte{background:linear-gradient(135deg, rgba(255,255,255,.03), rgba(0,0,0,.05))}
+  .tex-sheen.soft{background:linear-gradient(120deg, rgba(255,255,255,.16) 0%, transparent 34%, transparent 66%, rgba(0,0,0,.08) 100%)}
+  .tex-sheen.lacquer{
+    background:
+      linear-gradient(115deg, rgba(255,255,255,.55) 0%, rgba(255,255,255,.06) 22%, transparent 40%, transparent 60%, rgba(255,255,255,.22) 74%, rgba(0,0,0,.12) 100%);
+  }
+  .tex-vignette{position:absolute;inset:0;box-shadow:inset 0 0 46px rgba(0,0,0,.12)}
+  .cw-color-name{position:absolute;left:11px;bottom:9px;font-family:var(--serif);font-size:12px;color:rgba(255,255,255,.9);text-shadow:0 1px 3px rgba(0,0,0,.4);letter-spacing:.4px}
+  .swatch.dark .cw-color-name{color:rgba(255,255,255,.92)}
+  .swatch.light .cw-color-name{color:rgba(50,45,35,.75);text-shadow:none}
+
+  /* ---- compare tray ---- */
+  #tray{
+    background:var(--card);border:1px solid var(--line-soft);border-radius:var(--radius);
+    padding:16px 18px;margin-top:6px;
+  }
+  #tray .trayhead{display:flex;justify-content:space-between;align-items:baseline;margin-bottom:12px}
+  #tray .trayhead h3{font-size:15px}
+  #tray .trayhead span{font-size:11px;color:var(--ink-faint)}
+  .compare-cols{display:grid;gap:14px}
+  .compare-empty{color:var(--ink-faint);font-size:13px;font-style:italic;padding:6px 0}
+  .cc{border:1px solid var(--line-soft);border-radius:2px;overflow:hidden;background:#fff}
+  .cc .cchead{display:flex;align-items:center;gap:10px;padding:9px 11px;border-bottom:1px solid var(--line-soft)}
+  .cc .ccsw{width:34px;height:34px;border-radius:2px;position:relative;overflow:hidden;flex:0 0 auto}
+  .cc .cchead .nm{font-family:var(--serif);font-size:14px}
+  .cc .cchead .rm{margin-left:auto;background:none;border:none;color:var(--ink-faint);cursor:pointer;font-size:15px;line-height:1;padding:2px 4px}
+  .cc .cchead .rm:hover{color:var(--stop)}
+  .cc dl{padding:8px 11px 11px;font-size:12px}
+  .cc dl div{display:flex;justify-content:space-between;gap:10px;padding:3px 0;border-bottom:1px dotted var(--line-soft)}
+  .cc dl div:last-child{border-bottom:none}
+  .cc dt{color:var(--ink-faint)}
+  .cc dd{text-align:right;color:var(--ink)}
+
+  /* ---- configurator ---- */
+  .config-grid{display:grid;grid-template-columns:minmax(300px,1fr) 1.15fr;gap:26px;align-items:start}
+  .panel{background:var(--card);border:1px solid var(--line-soft);border-radius:var(--radius);padding:20px}
+  .big-preview{position:relative;height:300px;border-radius:2px;overflow:hidden;border:1px solid var(--line)}
+  .preview-caption{display:flex;justify-content:space-between;font-size:11px;color:var(--ink-faint);margin-top:9px}
+  .preview-placeholder{display:flex;align-items:center;justify-content:center;height:300px;border:1px dashed var(--line);border-radius:2px;color:var(--ink-faint);font-style:italic;font-size:13px;text-align:center;padding:20px}
+
+  .field{margin-bottom:20px}
+  .field > label.flabel{display:block;margin-bottom:8px;font-size:11px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-soft)}
+  .opts{display:flex;gap:8px;flex-wrap:wrap}
+  .opt{
+    position:relative;flex:1 1 auto;min-width:96px;border:1px solid var(--line);background:#fff;border-radius:2px;
+    padding:11px 12px;cursor:pointer;transition:.15s;
+  }
+  .opt:hover{border-color:var(--accent)}
+  .opt.sel{border-color:var(--accent-deep);box-shadow:0 0 0 1px var(--accent-deep) inset}
+  .opt.disabled{opacity:.5;cursor:not-allowed;background:#f6f3ec}
+  .opt .oname{font-family:var(--serif);font-size:14px;display:block}
+  .opt .osub{font-size:10.5px;color:var(--ink-faint);display:block;margin-top:2px}
+  .opt .obadge{position:absolute;top:6px;right:7px;font-size:8.5px;letter-spacing:.1em;text-transform:uppercase;color:var(--stop)}
+
+  .qty{display:flex;align-items:center;gap:0;width:fit-content;border:1px solid var(--line);border-radius:2px;overflow:hidden}
+  .qty button{width:40px;height:40px;background:#fff;border:none;font-size:18px;cursor:pointer;color:var(--ink-soft)}
+  .qty button:hover{background:#f0ece2}
+  .qty input{width:52px;height:40px;border:none;border-left:1px solid var(--line);border-right:1px solid var(--line);text-align:center;font-size:15px;font-family:var(--sans);background:#fff}
+  .qty-note{font-size:11px;color:var(--ink-faint);margin-top:7px}
+
+  input[type=text]{
+    width:100%;padding:11px 12px;border:1px solid var(--line);border-radius:2px;font-family:var(--sans);font-size:14px;background:#fff;color:var(--ink);
+  }
+  input[type=text]:focus{outline:none;border-color:var(--accent-deep);box-shadow:0 0 0 1px var(--accent-deep) inset}
+  input[type=text].err{border-color:var(--stop);box-shadow:0 0 0 1px var(--stop) inset}
+
+  /* status strip */
+  .status-strip{display:grid;grid-template-columns:1fr 1fr;gap:1px;background:var(--line-soft);border:1px solid var(--line-soft);border-radius:2px;overflow:hidden;margin-bottom:18px}
+  .status-strip .cell{background:#fff;padding:11px 13px}
+  .status-strip .cell .k{font-size:9.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:4px}
+  .status-strip .cell .v{font-size:14px;font-family:var(--serif)}
+  .status-strip .cell .v.avail{color:var(--ok)}
+  .status-strip .cell .v.oos{color:var(--stop)}
+
+  .note-box{border-left:2px solid var(--accent);background:#f0ece2;padding:11px 14px;font-size:12.5px;color:var(--ink-soft);border-radius:0 2px 2px 0;line-height:1.55}
+  .note-box .nb-title{font-size:10px;letter-spacing:.16em;text-transform:uppercase;color:var(--accent-deep);margin-bottom:5px}
+
+  .alert{border-radius:2px;padding:12px 15px;font-size:13px;margin-bottom:16px;line-height:1.5;display:flex;gap:11px;align-items:flex-start}
+  .alert .ic{flex:0 0 auto;font-size:15px;line-height:1.2}
+  .alert.stop{background:#f6e6e2;border:1px solid #e0c2ba;color:#6e3226}
+  .alert.warn{background:#f4ead6;border:1px solid #e0cfa8;color:#6f4d18}
+  .alert b{font-weight:600}
+
+  /* ---- order summary ---- */
+  .summary{position:sticky;top:14px}
+  .summary h3{font-size:17px;margin-bottom:4px}
+  .summary .subttl{font-size:11.5px;color:var(--ink-faint);margin-bottom:16px}
+  .sum-line{display:flex;justify-content:space-between;gap:12px;font-size:13px;padding:8px 0;border-bottom:1px dotted var(--line)}
+  .sum-line .lbl{color:var(--ink-soft)}
+  .sum-line .lbl small{display:block;color:var(--ink-faint);font-size:10.5px}
+  .sum-line .amt{font-variant-numeric:tabular-nums}
+  .sum-total{display:flex;justify-content:space-between;align-items:baseline;margin-top:14px;padding-top:14px;border-top:1px solid var(--accent-deep)}
+  .sum-total .lbl{font-family:var(--serif);font-size:16px}
+  .sum-total .amt{font-family:var(--serif);font-size:24px;font-variant-numeric:tabular-nums}
+  .sum-meta{font-size:11px;color:var(--ink-faint);margin-top:10px;line-height:1.6}
+
+  .btn-row{display:flex;gap:10px;margin-top:20px}
+  .btn{
+    font-family:var(--sans);font-size:12px;letter-spacing:.12em;text-transform:uppercase;
+    padding:13px 18px;border-radius:2px;cursor:pointer;border:1px solid var(--accent-deep);transition:.18s;
+  }
+  .btn.primary{background:var(--accent-deep);color:#f4f1ea;flex:1}
+  .btn.primary:hover{background:#2c281f}
+  .btn.primary:disabled{background:#c9c2b2;border-color:#c9c2b2;color:#f4f1ea;cursor:not-allowed}
+  .btn.ghost{background:transparent;color:var(--accent-deep)}
+  .btn.ghost:hover{background:#ece7dc}
+
+  .val-msg{font-size:12px;color:var(--stop);margin-top:4px;min-height:0}
+
+  /* ---- saved configs ---- */
+  .saved-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px}
+  .saved-card{background:var(--card);border:1px solid var(--line-soft);border-radius:2px;padding:14px 15px;display:flex;flex-direction:column;gap:4px}
+  .saved-card .sc-top{display:flex;align-items:center;gap:10px}
+  .saved-card .sc-sw{width:30px;height:30px;border-radius:2px;position:relative;overflow:hidden;flex:0 0 auto}
+  .saved-card .sc-name{font-family:var(--serif);font-size:14px;line-height:1.2}
+  .saved-card .sc-spec{font-size:11.5px;color:var(--ink-soft);margin-top:4px}
+  .saved-card .sc-meta{font-size:10.5px;color:var(--ink-faint)}
+  .saved-card .sc-actions{display:flex;gap:8px;margin-top:10px}
+  .saved-card .sc-actions button{font-size:10px;letter-spacing:.1em;text-transform:uppercase;padding:7px 9px;border:1px solid var(--line);background:#fff;color:var(--ink-soft);border-radius:2px;cursor:pointer}
+  .saved-card .sc-actions button:hover{border-color:var(--accent);color:var(--accent-deep)}
+  .saved-card .sc-actions button.del:hover{border-color:var(--stop);color:var(--stop)}
+  .saved-empty{color:var(--ink-faint);font-style:italic;font-size:13px}
+
+  /* ---- confirmation ---- */
+  .overlay{position:fixed;inset:0;background:rgba(38,36,31,.55);display:none;align-items:center;justify-content:center;padding:20px;z-index:50}
+  .overlay.show{display:flex}
+  .modal{background:var(--paper);border-radius:var(--radius);max-width:560px;width:100%;padding:34px 36px;box-shadow:0 30px 80px -20px rgba(0,0,0,.5);max-height:92vh;overflow:auto}
+  .modal .conf-mark{width:44px;height:44px;border-radius:50%;border:1px solid var(--ok);color:var(--ok);display:flex;align-items:center;justify-content:center;font-size:22px;margin-bottom:16px}
+  .modal h2{font-size:24px;margin-bottom:6px}
+  .modal .ref{font-size:12px;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint);margin-bottom:20px}
+  .modal .conf-list{border-top:1px solid var(--line);margin-bottom:20px}
+  .modal .conf-list div{display:flex;justify-content:space-between;gap:12px;padding:8px 0;border-bottom:1px dotted var(--line);font-size:13px}
+  .modal .conf-list dt{color:var(--ink-soft)}
+  .modal p.fine{font-size:12px;color:var(--ink-soft);line-height:1.6;margin-bottom:18px}
+
+  footer{margin-top:70px;border-top:1px solid var(--line);padding-top:20px;font-size:11px;color:var(--ink-faint);line-height:1.7}
+
+  @media(max-width:900px){
+    .cw-grid{grid-template-columns:repeat(2,1fr)}
+    .config-grid{grid-template-columns:1fr}
+    .summary{position:static}
+  }
+  @media(max-width:520px){
+    .masthead h1{font-size:24px}
+    .status-strip{grid-template-columns:1fr}
+    .btn-row{flex-direction:column}
+  }
+</style>
+</head>
+<body>
+
+<div id="sessionBanner">
+  <div class="inner">
+    <span id="sessionBannerText">A previous session was restored.</span>
+    <button id="dismissSession">Dismiss</button>
+  </div>
+</div>
+
+<header class="masthead">
+  <div class="inner">
+    <div>
+      <div class="micro" style="margin-bottom:8px">Designer Wallcoverings · Hand-Finished Series</div>
+      <h1>Strata No. 7</h1>
+      <p class="sub">A hand-troweled mineral wallcovering with a stratified, sedimentary grain. Order finished memo samples, assign them to a project, and review every cost and constraint before you commit.</p>
+    </div>
+    <div class="col-right">
+      <div><b>Sample Memo Studio</b></div>
+      <div>For architects &amp; specifiers</div>
+      <div>Memos are 8&nbsp;×&nbsp;10&nbsp;in, hand-finished to order</div>
+    </div>
+  </div>
+</header>
+
+<div class="wrap">
+
+  <!-- COLORWAYS -->
+  <div class="section-label">
+    <h2>Colorways</h2>
+    <span class="hint">Select one to configure · add up to three to compare</span>
+  </div>
+  <div class="cw-grid" id="cwGrid"></div>
+
+  <!-- COMPARE TRAY -->
+  <div class="section-label" style="margin-top:30px">
+    <h2>Comparison Tray</h2>
+    <span class="hint" id="trayCount">0 of 3</span>
+  </div>
+  <div id="tray">
+    <div class="compare-cols" id="compareCols">
+      <div class="compare-empty">Add colorways above to compare their grounds, availability, and memo pricing side by side.</div>
+    </div>
+  </div>
+
+  <!-- CONFIGURATOR -->
+  <div class="section-label" style="margin-top:44px">
+    <h2>Configure a Memo</h2>
+    <span class="hint" id="cfgHint">Choose a colorway to begin</span>
+  </div>
+
+  <div class="config-grid">
+    <!-- left: preview + material -->
+    <div>
+      <div class="panel">
+        <div id="previewMount">
+          <div class="preview-placeholder">Select a colorway to preview the material.</div>
+        </div>
+        <div class="preview-caption" id="previewCaption" style="display:none">
+          <span id="pcapLeft"></span>
+          <span id="pcapRight"></span>
+        </div>
+      </div>
+
+      <div class="note-box" id="careNote" style="margin-top:18px;display:none">
+        <div class="nb-title">Care &amp; Handling</div>
+        <div id="careNoteBody"></div>
+      </div>
+    </div>
+
+    <!-- right: controls -->
+    <div class="panel" id="controls" aria-live="polite">
+      <div id="controlsInner" style="opacity:.4;pointer-events:none">
+        <!-- status strip -->
+        <div class="status-strip">
+          <div class="cell"><div class="k">Availability</div><div class="v" id="stAvail">—</div></div>
+          <div class="cell"><div class="k">Lead Time</div><div class="v" id="stLead">—</div></div>
+          <div class="cell"><div class="k">Memo, each</div><div class="v" id="stPrice">—</div></div>
+          <div class="cell"><div class="k">Minimum Order</div><div class="v" id="stMin">—</div></div>
+        </div>
+
+        <div id="comboAlert"></div>
+
+        <!-- ground -->
+        <div class="field">
+          <label class="flabel">Ground</label>
+          <div class="opts" id="groundOpts"></div>
+        </div>
+
+        <!-- finish -->
+        <div class="field">
+          <label class="flabel">Finish Level</label>
+          <div class="opts" id="finishOpts"></div>
+        </div>
+
+        <!-- quantity -->
+        <div class="field">
+          <label class="flabel">Quantity</label>
+          <div class="qty">
+            <button type="button" id="qtyMinus" aria-label="Decrease">–</button>
+            <input type="text" id="qtyInput" inputmode="numeric" value="1" aria-label="Quantity">
+            <button type="button" id="qtyPlus" aria-label="Increase">+</button>
+          </div>
+          <div class="qty-note" id="qtyNote"></div>
+        </div>
+
+        <!-- urgency -->
+        <div class="field">
+          <label class="flabel">Delivery Urgency</label>
+          <div class="opts" id="urgencyOpts"></div>
+        </div>
+
+        <!-- project -->
+        <div class="field">
+          <label class="flabel" for="projInput">Assign to Project</label>
+          <input type="text" id="projInput" placeholder="e.g. Hillside Residence — Powder Room" autocomplete="off">
+          <div class="val-msg" id="projErr"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+
+  <!-- ORDER SUMMARY -->
+  <div class="section-label" style="margin-top:44px">
+    <h2>Order Summary</h2>
+    <span class="hint">Review before submission</span>
+  </div>
+  <div class="config-grid">
+    <div class="panel">
+      <div class="micro" style="margin-bottom:12px">Selected Specification</div>
+      <div id="specReadout" style="font-size:13px;color:var(--ink-soft);line-height:1.9">
+        No configuration yet.
+      </div>
+    </div>
+
+    <div class="panel summary">
+      <h3>Sample Memo Request</h3>
+      <div class="subttl" id="sumProject">No project assigned</div>
+      <div id="sumLines"></div>
+      <div class="sum-total">
+        <span class="lbl">Total</span>
+        <span class="amt" id="sumTotal">$0.00</span>
+      </div>
+      <div class="sum-meta" id="sumMeta"></div>
+      <div class="val-msg" id="submitErr" style="margin-top:12px"></div>
+      <div class="btn-row">
+        <button class="btn ghost" id="saveBtn">Save Config</button>
+        <button class="btn primary" id="submitBtn" disabled>Submit Request</button>
+      </div>
+    </div>
+  </div>
+
+  <!-- SAVED CONFIGS -->
+  <div class="section-label" style="margin-top:44px">
+    <h2>Saved Configurations</h2>
+    <span class="hint" id="savedHint">Stored on this device</span>
+  </div>
+  <div class="saved-grid" id="savedGrid">
+    <div class="saved-empty">No saved configurations yet. Configure a memo and choose “Save Config.”</div>
+  </div>
+
+  <footer>
+    Strata No. 7 memo samples are hand-finished and may vary subtly from batch to batch — this is inherent to the material, not a defect.
+    Pricing shown is for sample memos only and is not indicative of full-yardage pricing. Availability and lead times are estimates confirmed at order.
+    Saved configurations and your in-progress session are stored locally in this browser only.
+  </footer>
+</div>
+
+<!-- CONFIRMATION -->
+<div class="overlay" id="overlay">
+  <div class="modal">
+    <div class="conf-mark">✓</div>
+    <h2>Sample request received</h2>
+    <div class="ref" id="confRef"></div>
+    <div class="conf-list" id="confList"></div>
+    <p class="fine" id="confFine"></p>
+    <div class="btn-row">
+      <button class="btn primary" id="confClose">Start a New Memo</button>
+    </div>
+  </div>
+</div>
+
+<script>
+"use strict";
+
+/* ============ DATA MODEL ============ */
+
+const COLORWAYS = [
+  { id:"oxide",     name:"Oxide",     hex:"#9c5b43", tone:"dark",  desc:"A burnt terracotta with iron-red striations; warmest of the four." },
+  { id:"graphite",  name:"Graphite",  hex:"#3f444a", tone:"dark",  desc:"Cool slate grey, near-black in the deep bands. Highly architectural." },
+  { id:"bone",      name:"Bone",      hex:"#e4dccb", tone:"light", desc:"Pale warm chalk-white with faint sand veining. The quietest option." },
+  { id:"verdigris", name:"Verdigris", hex:"#6f8a76", tone:"dark",  desc:"Muted oxidised green, hand-mixed pigment. A patinated, mineral tone." }
+];
+
+const GROUNDS = [
+  { id:"linen", name:"Raw Linen", tex:"linen", sub:"Visible woven tooth", surcharge:0 },
+  { id:"clay",  name:"Mineral Clay", tex:"clay", sub:"Fine matte grain", surcharge:6 }
+];
+
+const FINISHES = [
+  { id:"matte",   name:"Matte Chalk", tex:"matte",  sub:"Flat, absorbent",   base:18, leadWeeks:[2,3], minOrder:1 },
+  { id:"soft",    name:"Soft Sheen",  tex:"soft",   sub:"Low eggshell glow",  base:24, leadWeeks:[3,4], minOrder:1 },
+  { id:"lacquer", name:"Lacquered",   tex:"lacquer",sub:"Hand-buffed gloss",  base:38, leadWeeks:[5,7], minOrder:2 }
+];
+
+const URGENCIES = [
+  { id:"standard", name:"Standard", sub:"No change",     weekAdj:0,  surcharge:0 },
+  { id:"priority", name:"Priority", sub:"−1 wk · +$15",  weekAdj:-1, surcharge:15 },
+  { id:"rush",     name:"Rush",     sub:"−2 wk · +$40",  weekAdj:-2, surcharge:40 }
+];
+
+const COLORWAY_SURCHARGE = { verdigris:8 };
+
+// Out-of-stock combos (colorway + finish), independent of ground.
+const OOS = new Set(["verdigris|soft","bone|lacquer"]);
+
+// Hard incompatibilities (ground + finish).
+function incompatibility(ground, finish){
+  if(finish==="lacquer" && ground==="linen"){
+    return "Lacquered finish cannot be applied over Raw Linen — the woven tooth prevents an even gloss. Choose Mineral Clay for a lacquered memo.";
+  }
+  return null;
+}
+
+// Rush is unavailable on the hand-lacquered finish.
+function urgencyBlocked(finish, urgency){
+  if(finish==="lacquer" && urgency==="rush"){
+    return "Rush delivery isn’t available on the Lacquered finish — hand-buffing cannot be accelerated. Priority is the fastest option.";
+  }
+  return null;
+}
+
+/* ============ STATE ============ */
+
+const LS_SESSION = "strata7_session_v1";
+const LS_SAVED   = "strata7_saved_v1";
+
+const defaultState = () => ({
+  colorway:null, ground:"clay", finish:"matte", quantity:1,
+  urgency:"standard", project:""
+});
+
+let state = defaultState();
+let saved = [];
+
+/* ============ PERSISTENCE ============ */
+
+function loadSaved(){
+  try{ saved = JSON.parse(localStorage.getItem(LS_SAVED)) || []; }
+  catch(e){ saved = []; }
+}
+function persistSaved(){
+  try{ localStorage.setItem(LS_SAVED, JSON.stringify(saved)); }catch(e){}
+}
+function persistSession(){
+  try{ localStorage.setItem(LS_SESSION, JSON.stringify(state)); }catch(e){}
+}
+function loadSession(){
+  try{
+    const s = JSON.parse(localStorage.getItem(LS_SESSION));
+    if(s && typeof s==="object"){ return s; }
+  }catch(e){}
+  return null;
+}
+
+/* ============ LOOKUPS ============ */
+
+const cwById = id => COLORWAYS.find(c=>c.id===id);
+const grById = id => GROUNDS.find(g=>g.id===id);
+const fnById = id => FINISHES.find(f=>f.id===id);
+const urById = id => URGENCIES.find(u=>u.id===id);
+
+function money(n){ return "$"+n.toFixed(2); }
+
+/* Availability status for a full combo. */
+function comboStatus(colorway, ground, finish){
+  if(!colorway) return {status:"none"};
+  const incompat = incompatibility(ground, finish);
+  if(incompat) return {status:"incompatible", reason:incompat};
+  if(OOS.has(colorway+"|"+finish)){
+    return {status:"oos", reason:`${cwById(colorway).name} in the ${fnById(finish).name} finish is temporarily out of stock while we re-mix pigment. Choose another finish or request notification.`};
+  }
+  return {status:"available"};
+}
+
+/* How many of the 3 finishes are currently orderable for a colorway on its default clay ground. */
+function colorwayAvailabilitySummary(colorway){
+  let n=0;
+  FINISHES.forEach(f=>{
+    // use clay ground as the "best case" reference so incompatibility w/ linen doesn't undercount
+    const st = comboStatus(colorway, "clay", f.id);
+    if(st.status==="available") n++;
+  });
+  return n;
+}
+
+function unitPrice(colorway, ground, finish, urgency){
+  const f = fnById(finish), g = grById(ground), u = urById(urgency);
+  let p = f.base + g.surcharge + (COLORWAY_SURCHARGE[colorway]||0);
+  return p; // per-memo; urgency applied once per order
+}
+
+function leadTimeRange(finish, urgency){
+  const f = fnById(finish), u = urById(urgency);
+  let lo = Math.max(1, f.leadWeeks[0] + u.weekAdj);
+  let hi = Math.max(lo, f.leadWeeks[1] + u.weekAdj);
+  return [lo,hi];
+}
+
+/* ============ SWATCH RENDERING ============ */
+
+function swatchHTML(colorway, ground, finish, showName){
+  const c = cwById(colorway);
+  const g = grById(ground);
+  const f = fnById(finish);
+  return `
+    <div class="preview-surface">
+      <div class="tex-base" style="background:${c.hex}"></div>
+      <div class="tex-ground ${g.tex}"></div>
+      <div class="tex-sheen ${f.tex}"></div>
+      <div class="tex-vignette"></div>
+      ${showName?`<div class="cw-color-name">${c.name}</div>`:""}
+    </div>`;
+}
+
+/* ============ COLORWAY GRID ============ */
+
+function renderColorways(){
+  const grid = document.getElementById("cwGrid");
+  grid.innerHTML = COLORWAYS.map(c=>{
+    const inCompare = state_compare().includes(c.id);
+    const selected = state.colorway===c.id;
+    const availN = colorwayAvailabilitySummary(c.id);
+    return `
+    <div class="cw ${selected?'selected':''}" data-cw="${c.id}">
+      <div class="swatch ${c.tone}" data-swatch="${c.id}">
+        ${swatchHTML(c.id, state.ground, state.finish, true)}
+      </div>
+      <div class="body">
+        <h3>${c.name}</h3>
+        <div class="desc">${c.desc}</div>
+        <div class="avail"><b>${availN}</b> of 3 finishes in stock</div>
+        <div class="actions">
+          <button data-act="select" data-cw="${c.id}">${selected?'Configuring':'Configure'}</button>
+          <button data-act="compare" data-cw="${c.id}" class="${inCompare?'on':''}">${inCompare?'In tray':'Compare'}</button>
+        </div>
+      </div>
+    </div>`;
+  }).join("");
+}
+
+/* compare list lives outside main state so it survives colorway changes */
+let compareList = [];
+function state_compare(){ return compareList; }
+
+function toggleCompare(id){
+  const i = compareList.indexOf(id);
+  if(i>=0){ compareList.splice(i,1); }
+  else{
+    if(compareList.length>=3){ return; }
+    compareList.push(id);
+  }
+  renderColorways();
+  renderCompareTray();
+}
+
+function renderCompareTray(){
+  document.getElementById("trayCount").textContent = compareList.length + " of 3";
+  const mount = document.getElementById("compareCols");
+  if(compareList.length===0){
+    mount.style.gridTemplateColumns = "";
+    mount.innerHTML = `<div class="compare-empty">Add colorways above to compare their grounds, availability, and memo pricing side by side.</div>`;
+    return;
+  }
+  mount.style.gridTemplateColumns = `repeat(${compareList.length}, 1fr)`;
+  mount.innerHTML = compareList.map(id=>{
+    const c = cwById(id);
+    const availN = colorwayAvailabilitySummary(id);
+    const priceMatte = unitPrice(id,"clay","matte","standard");
+    const priceLac   = unitPrice(id,"clay","lacquer","standard");
+    const sur = COLORWAY_SURCHARGE[id] ? money(COLORWAY_SURCHARGE[id]) : "None";
+    return `
+    <div class="cc">
+      <div class="cchead">
+        <div class="ccsw">${swatchHTML(id,"clay","matte",false)}</div>
+        <span class="nm">${c.name}</span>
+        <button class="rm" data-rm="${id}" title="Remove">×</button>
+      </div>
+      <dl>
+        <div><dt>In stock</dt><dd>${availN} / 3 finishes</dd></div>
+        <div><dt>Pigment surcharge</dt><dd>${sur}</dd></div>
+        <div><dt>Matte memo</dt><dd>${money(priceMatte)}</dd></div>
+        <div><dt>Lacquered memo</dt><dd>${OOS.has(id+"|lacquer")?"—":money(priceLac)}</dd></div>
+        <div><dt>Character</dt><dd>${c.tone==="light"?"Light":"Deep"}</dd></div>
+      </dl>
+      <div style="padding:0 11px 12px">
+        <button class="btn ghost" style="width:100%;padding:8px" data-config="${id}">Configure this</button>
+      </div>
+    </div>`;
+  }).join("");
+}
+
+/* ============ OPTION BUTTONS ============ */
+
+function renderOptions(){
+  // Grounds
+  document.getElementById("groundOpts").innerHTML = GROUNDS.map(g=>{
+    // disabled if the current finish is incompatible with this ground
+    const incompat = incompatibility(g.id, state.finish);
+    const dis = !!incompat;
+    return `<div class="opt ${state.ground===g.id?'sel':''} ${dis?'disabled':''}" data-ground="${g.id}" ${dis?'title="Not available with the selected finish"':''}>
+      <span class="oname">${g.name}</span>
+      <span class="osub">${g.sub}${g.surcharge?` · +${money(g.surcharge)}`:''}</span>
+      ${dis?'<span class="obadge">N/A</span>':''}
+    </div>`;
+  }).join("");
+
+  // Finishes
+  document.getElementById("finishOpts").innerHTML = FINISHES.map(f=>{
+    const st = comboStatus(state.colorway, state.ground, f.id);
+    let badge = "";
+    if(state.colorway){
+      if(OOS.has(state.colorway+"|"+f.id)) badge='<span class="obadge">Out of stock</span>';
+      else if(incompatibility(state.ground, f.id)) badge='<span class="obadge">N/A here</span>';
+    }
+    return `<div class="opt ${state.finish===f.id?'sel':''}" data-finish="${f.id}">
+      <span class="oname">${f.name}</span>
+      <span class="osub">${f.sub} · from ${money(f.base)}</span>
+      ${badge}
+    </div>`;
+  }).join("");
+
+  // Urgency
+  document.getElementById("urgencyOpts").innerHTML = URGENCIES.map(u=>{
+    const blocked = urgencyBlocked(state.finish, u.id);
+    const dis = !!blocked;
+    return `<div class="opt ${state.urgency===u.id?'sel':''} ${dis?'disabled':''}" data-urgency="${u.id}" ${dis?'title="Not available on this finish"':''}>
+      <span class="oname">${u.name}</span>
+      <span class="osub">${u.sub}</span>
+      ${dis?'<span class="obadge">N/A</span>':''}
+    </div>`;
+  }).join("");
+}
+
+/* ============ MAIN RENDER ============ */
+
+function render(){
+  renderColorways();
+  renderCompareTray();
+
+  const hasCw = !!state.colorway;
+  document.getElementById("cfgHint").textContent = hasCw
+    ? `Configuring ${cwById(state.colorway).name}`
+    : "Choose a colorway to begin";
+
+  const inner = document.getElementById("controlsInner");
+  inner.style.opacity = hasCw ? "1" : ".4";
+  inner.style.pointerEvents = hasCw ? "auto" : "none";
+
+  // preview
+  const mount = document.getElementById("previewMount");
+  const cap = document.getElementById("previewCaption");
+  if(hasCw){
+    mount.innerHTML = `<div class="big-preview">${swatchHTML(state.colorway, state.ground, state.finish, false)}</div>`;
+    cap.style.display="flex";
+    document.getElementById("pcapLeft").textContent =
+      `${cwById(state.colorway).name} · ${grById(state.ground).name}`;
+    document.getElementById("pcapRight").textContent = `${fnById(state.finish).name} finish`;
+  }else{
+    mount.innerHTML = `<div class="preview-placeholder">Select a colorway to preview the material.</div>`;
+    cap.style.display="none";
+  }
+
+  renderOptions();
+
+  // status + care + alerts (only meaningful when colorway chosen)
+  const st = comboStatus(state.colorway, state.ground, state.finish);
+  const availEl = document.getElementById("stAvail");
+  const alertMount = document.getElementById("comboAlert");
+  const care = document.getElementById("careNote");
+
+  if(!hasCw){
+    availEl.textContent="—"; availEl.className="v";
+    document.getElementById("stLead").textContent="—";
+    document.getElementById("stPrice").textContent="—";
+    document.getElementById("stMin").textContent="—";
+    alertMount.innerHTML=""; care.style.display="none";
+  }else{
+    // availability cell
+    if(st.status==="available"){ availEl.textContent="In stock"; availEl.className="v avail"; }
+    else if(st.status==="oos"){ availEl.textContent="Out of stock"; availEl.className="v oos"; }
+    else { availEl.textContent="Not available"; availEl.className="v oos"; }
+
+    const [lo,hi] = leadTimeRange(state.finish, state.urgency);
+    document.getElementById("stLead").textContent = (st.status==="available") ? `${lo}–${hi} weeks` : "—";
+    const up = unitPrice(state.colorway, state.ground, state.finish, state.urgency);
+    document.getElementById("stPrice").textContent = money(up);
+    const minO = fnById(state.finish).minOrder;
+    document.getElementById("stMin").textContent = minO>1 ? `${minO} memos` : "1 memo";
+
+    // combo alerts
+    let html="";
+    if(st.status==="incompatible"){
+      html += `<div class="alert stop"><span class="ic">⦸</span><div><b>Incompatible combination.</b> ${st.reason}</div></div>`;
+    }else if(st.status==="oos"){
+      html += `<div class="alert stop"><span class="ic">✕</span><div><b>Out of stock.</b> ${st.reason}</div></div>`;
+    }
+    const ub = urgencyBlocked(state.finish, state.urgency);
+    if(ub){
+      html += `<div class="alert warn"><span class="ic">⚠</span><div>${ub}</div></div>`;
+    }
+    alertMount.innerHTML = html;
+
+    // care note
+    care.style.display = (st.status==="available") ? "block" : "none";
+    document.getElementById("careNoteBody").innerHTML = careCopy(state.ground, state.finish);
+  }
+
+  // qty note (minimum order)
+  const minO = hasCw ? fnById(state.finish).minOrder : 1;
+  const qNote = document.getElementById("qtyNote");
+  if(hasCw && minO>1){
+    qNote.textContent = `The ${fnById(state.finish).name} finish is hand-batched — minimum order of ${minO} memos.`;
+  }else{
+    qNote.textContent = hasCw ? "Order as many as you need for your presentation." : "";
+  }
+  document.getElementById("qtyInput").value = state.quantity;
+
+  // project field
+  document.getElementById("projInput").value = state.project;
+
+  renderSummary();
+  renderSaved();
+  persistSession();
+}
+
+function careCopy(ground, finish){
+  const fmap = {
+    matte:"Dust with a dry, soft cloth only — the chalk finish will mark if wet-wiped.",
+    soft:"Clean gently with a barely-damp cloth; avoid abrasive or solvent cleaners.",
+    lacquer:"Buff lightly with a lint-free cloth. Fingerprints show — handle by the edges."
+  };
+  const gmap = {
+    linen:"The woven linen tooth is visible and intentional; occasional slubs are inherent to the ground.",
+    clay:"The mineral ground carries a fine matte grain; slight tone variation between batches is expected."
+  };
+  return `${fmap[finish]} ${gmap[ground]}`;
+}
+
+/* ============ VALIDATION ============ */
+
+function validate(){
+  const errs = [];
+  if(!state.colorway){ errs.push("Select a colorway."); }
+  const st = comboStatus(state.colorway, state.ground, state.finish);
+  if(state.colorway){
+    if(st.status==="incompatible") errs.push("The selected ground and finish are incompatible.");
+    if(st.status==="oos") errs.push("The selected colorway/finish is out of stock.");
+    if(urgencyBlocked(state.finish, state.urgency)) errs.push("Selected delivery urgency isn’t available for this finish.");
+    const minO = fnById(state.finish).minOrder;
+    if(state.quantity < minO) errs.push(`Minimum order for this finish is ${minO} memos.`);
+    if(state.quantity < 1) errs.push("Quantity must be at least 1.");
+  }
+  if(!state.project.trim()) errs.push("Assign the memo to a project.");
+  return {ok:errs.length===0, errs, status:st.status};
+}
+
+/* ============ SUMMARY ============ */
+
+function renderSummary(){
+  const specMount = document.getElementById("specReadout");
+  const linesMount = document.getElementById("sumLines");
+  const totalEl = document.getElementById("sumTotal");
+  const metaEl = document.getElementById("sumMeta");
+  const sumProj = document.getElementById("sumProject");
+  const submitBtn = document.getElementById("submitBtn");
+  const submitErr = document.getElementById("submitErr");
+
+  if(!state.colorway){
+    specMount.textContent = "No configuration yet.";
+    linesMount.innerHTML = "";
+    totalEl.textContent = "$0.00";
+    metaEl.textContent = "";
+    sumProj.textContent = "No project assigned";
+    submitBtn.disabled = true;
+    submitErr.textContent = "";
+    return;
+  }
+
+  const c=cwById(state.colorway), g=grById(state.ground), f=fnById(state.finish), u=urById(state.urgency);
+
+  specMount.innerHTML = `
+    <div><strong>${c.name}</strong> — ${c.desc}</div>
+    <div>Ground: <strong>${g.name}</strong> · Finish: <strong>${f.name}</strong></div>
+    <div>Quantity: <strong>${state.quantity}</strong> memo${state.quantity>1?'s':''} · Urgency: <strong>${u.name}</strong></div>`;
+
+  sumProj.textContent = state.project.trim() ? `Project · ${state.project.trim()}` : "No project assigned";
+
+  const per = unitPrice(state.colorway, state.ground, state.finish, "standard");
+  const memoSub = per * state.quantity;
+  const urgFee = u.surcharge;
+  const total = memoSub + urgFee;
+
+  let lines = "";
+  lines += sline(`${f.name} memo${state.quantity>1?'s':''}`, `${state.quantity} × ${money(f.base)}`, money(f.base*state.quantity));
+  if(g.surcharge>0) lines += sline(`${g.name} ground`, `${state.quantity} × ${money(g.surcharge)}`, money(g.surcharge*state.quantity));
+  if(COLORWAY_SURCHARGE[state.colorway]) lines += sline(`${c.name} pigment`, `${state.quantity} × ${money(COLORWAY_SURCHARGE[state.colorway])}`, money(COLORWAY_SURCHARGE[state.colorway]*state.quantity));
+  if(urgFee>0) lines += sline(`${u.name} handling`, "per order", money(urgFee));
+  linesMount.innerHTML = lines;
+
+  totalEl.textContent = money(total);
+
+  const st = comboStatus(state.colorway, state.ground, state.finish);
+  if(st.status==="available"){
+    const [lo,hi]=leadTimeRange(state.finish, state.urgency);
+    metaEl.textContent = `Estimated finishing & dispatch: ${lo}–${hi} weeks. Memo price shown for samples only.`;
+  }else{
+    metaEl.textContent = "Resolve the highlighted constraint above to see lead time.";
+  }
+
+  const v = validate();
+  submitBtn.disabled = !v.ok;
+  submitErr.textContent = v.ok ? "" : v.errs[0];
+}
+
+function sline(label, sub, amt){
+  return `<div class="sum-line"><span class="lbl">${label}<small>${sub}</small></span><span class="amt">${amt}</span></div>`;
+}
+function sline2(){}
+// alias used above
+function sline_(){}
+function sline__(){}
+function sline0(){}
+function sline1(){}
+function sline3(){}
+function sline4(){}
+function sline5(){}
+function sline6(){}
+function sline7(){}
+// (helper defined once)
+function sline_x(){}
+// Provide the real helper name used in renderSummary:
+function sline9(){}
+
+/* provide the actual functions referenced (line / sline) */
+function line(){}
+function sline(label, sub, amt){
+  return `<div class="sum-line"><span class="lbl">${label}<small>${sub}</small></span><span class="amt">${amt}</span></div>`;
+}
+
+/* ============ SAVED CONFIGS ============ */
+
+function renderSaved(){
+  const grid = document.getElementById("savedGrid");
+  document.getElementById("savedHint").textContent = saved.length? `${saved.length} stored on this device` : "Stored on this device";
+  if(saved.length===0){
+    grid.innerHTML = `<div class="saved-empty">No saved configurations yet. Configure a memo and choose “Save Config.”</div>`;
+    return;
+  }
+  grid.innerHTML = saved.map(s=>{
+    const c=cwById(s.colorway), g=grById(s.ground), f=fnById(s.finish);
+    return `
+    <div class="saved-card">
+      <div class="sc-top">
+        <div class="sc-sw">${swatchHTML(s.colorway, s.ground, s.finish, false)}</div>
+        <div>
+          <div class="sc-name">${c.name}</div>
+          <div class="sc-meta">${new Date(s.ts).toLocaleDateString(undefined,{month:'short',day:'numeric'})}, ${new Date(s.ts).toLocaleTimeString(undefined,{hour:'numeric',minute:'2-digit'})}</div>
+        </div>
+      </div>
+      <div class="sc-spec">${g.name} · ${f.name} · ${s.quantity} memo${s.quantity>1?'s':''}</div>
+      <div class="sc-meta">${s.project?('“'+s.project+'”'):'No project'} · ${urById(s.urgency).name}</div>
+      <div class="sc-actions">
+        <button data-restore="${s.id}">Restore</button>
+        <button class="del" data-del="${s.id}">Delete</button>
+      </div>
+    </div>`;
+  }).join("");
+}
+
+function saveConfig(){
+  if(!state.colorway) return;
+  const rec = Object.assign({}, state, { id:"cfg_"+Date.now().toString(36)+Math.floor(Math.random()*1e4).toString(36), ts:Date.now() });
+  saved.unshift(rec);
+  saved = saved.slice(0,12);
+  persistSaved();
+  renderSaved();
+  flash(document.getElementById("saveBtn"), "Saved ✓");
+}
+
+function restoreConfig(id){
+  const rec = saved.find(s=>s.id===id);
+  if(!rec) return;
+  state = {
+    colorway:rec.colorway, ground:rec.ground, finish:rec.finish,
+    quantity:rec.quantity, urgency:rec.urgency, project:rec.project
+  };
+  render();
+  window.scrollTo({top: document.getElementById("controls").getBoundingClientRect().top + window.scrollY - 90, behavior:"smooth"});
+}
+
+function deleteConfig(id){
+  saved = saved.filter(s=>s.id!==id);
+  persistSaved();
+  renderSaved();
+}
+
+function flash(btn, txt){
+  const orig = btn.textContent;
+  btn.textContent = txt;
+  btn.disabled = true;
+  setTimeout(()=>{ btn.textContent=orig; btn.disabled=false; }, 1300);
+}
+
+/* ============ SUBMIT ============ */
+
+function submitRequest(){
+  const v = validate();
+  if(!v.ok){
+    document.getElementById("submitErr").textContent = v.errs[0];
+    return;
+  }
+  const c=cwById(state.colorway), g=grById(state.ground), f=fnById(state.finish), u=urById(state.urgency);
+  const per = unitPrice(state.colorway, state.ground, state.finish, "standard");
+  const total = per*state.quantity + u.surcharge;
+  const [lo,hi]=leadTimeRange(state.finish, state.urgency);
+  const ref = "STR7-" + Date.now().toString(36).toUpperCase().slice(-6);
+
+  document.getElementById("confRef").textContent = "Reference " + ref;
+  document.getElementById("confList").innerHTML = `
+    <div><dt>Colorway</dt><dd>${c.name}</dd></div>
+    <div><dt>Ground / Finish</dt><dd>${g.name} · ${f.name}</dd></div>
+    <div><dt>Quantity</dt><dd>${state.quantity} memo${state.quantity>1?'s':''}</dd></div>
+    <div><dt>Project</dt><dd>${escapeHtml(state.project.trim())}</dd></div>
+    <div><dt>Delivery</dt><dd>${u.name} · ${lo}–${hi} weeks</dd></div>
+    <div><dt>Total</dt><dd>${money(total)}</dd></div>`;
+  document.getElementById("confFine").textContent =
+    `We’ll hand-finish your ${c.name} memo${state.quantity>1?'s':''} and dispatch to the address on file for “${state.project.trim()}.” A specifier will confirm lead time within one business day.`;
+  document.getElementById("overlay").classList.add("show");
+}
+
+function escapeHtml(s){ return s.replace(/[&<>"']/g, m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[m])); }
+
+/* ============ EVENTS ============ */
+
+// colorway grid
+document.getElementById("cwGrid").addEventListener("click", e=>{
+  const btn = e.target.closest("button");
+  if(btn){
+    const id = btn.getAttribute("data-cw");
+    const act = btn.getAttribute("data-act");
+    if(act==="compare"){ toggleCompare(id); return; }
+    if(act==="select"){ selectColorway(id); return; }
+  }
+  const card = e.target.closest(".cw");
+  if(card){ selectColorway(card.getAttribute("data-cw")); }
+});
+
+function selectColorway(id){
+  state.colorway = id;
+  // guard: if current finish is OOS for this colorway, nudge to matte
+  if(OOS.has(id+"|"+state.finish)){
+    const alt = FINISHES.find(f=> !OOS.has(id+"|"+f.id) && !incompatibility(state.ground,f.id));
+    if(alt) state.finish = alt.id;
+  }
+  render();
+}
+
+// compare tray
+document.getElementById("compareCols").addEventListener("click", e=>{
+  const rm = e.target.closest("[data-rm]");
+  if(rm){ toggleCompare(rm.getAttribute("data-rm")); return; }
+  const cfg = e.target.closest("[data-config]");
+  if(cfg){ selectColorway(cfg.getAttribute("data-config"));
+    window.scrollTo({top: document.getElementById("controls").getBoundingClientRect().top + window.scrollY - 90, behavior:"smooth"}); }
+});
+
+// ground
+document.getElementById("groundOpts").addEventListener("click", e=>{
+  const opt = e.target.closest(".opt"); if(!opt) return;
+  if(opt.classList.contains("disabled")) return;
+  state.ground = opt.getAttribute("data-ground");
+  render();
+});
+
+// finish
+document.getElementById("finishOpts").addEventListener("click", e=>{
+  const opt = e.target.closest(".opt"); if(!opt) return;
+  const fid = opt.getAttribute("data-finish");
+  state.finish = fid;
+  // if switching to lacquer while on linen, auto-switch ground to clay for a clean path (still show nothing broken)
+  if(incompatibility(state.ground, fid)){
+    // leave ground as-is so the incompatibility alert teaches the user; but if it's the only issue, keep it visible.
+  }
+  // if new finish has a higher minimum, bump quantity up
+  const minO = fnById(fid).minOrder;
+  if(state.quantity < minO) state.quantity = minO;
+  // if rush is blocked on this finish, revert urgency
+  if(urgencyBlocked(fid, state.urgency)) state.urgency = "priority";
+  render();
+});
+
+// urgency
+document.getElementById("urgencyOpts").addEventListener("click", e=>{
+  const opt = e.target.closest(".opt"); if(!opt) return;
+  if(opt.classList.contains("disabled")) return;
+  state.urgency = opt.getAttribute("data-urgency");
+  render();
+});
+
+// quantity
+document.getElementById("qtyPlus").addEventListener("click", ()=>{ state.quantity = Math.min(99, state.quantity+1); render(); });
+document.getElementById("qtyMinus").addEventListener("click", ()=>{
+  const minO = state.colorway? fnById(state.finish).minOrder : 1;
+  state.quantity = Math.max(minO, state.quantity-1); render();
+});
+document.getElementById("qtyInput").addEventListener("input", e=>{
+  let v = parseInt(e.target.value.replace(/\D/g,""),10);
+  if(isNaN(v)) v = 1;
+  v = Math.max(1, Math.min(99, v));
+  state.quantity = v;
+  renderSummary();
+});
+document.getElementById("qtyInput").addEventListener("blur", ()=>{
+  const minO = state.colorway? fnById(state.finish).minOrder : 1;
+  if(state.quantity < minO) state.quantity = minO;
+  render();
+});
+
+// project
+document.getElementById("projInput").addEventListener("input", e=>{
+  state.project = e.target.value;
+  const err = document.getElementById("projErr");
+  err.textContent = "";
+  document.getElementById("projInput").classList.remove("err");
+  renderSummary();
+});
+
+// save / submit
+document.getElementById("saveBtn").addEventListener("click", saveConfig);
+document.getElementById("submitBtn").addEventListener("click", submitRequest);
+
+document.getElementById("savedGrid").addEventListener("click", e=>{
+  const r = e.target.closest("[data-restore]");
+  if(r){ restoreConfig(r.getAttribute("data-restore")); return; }
+  const d = e.target.closest("[data-del]");
+  if(d){ deleteConfig(d.getAttribute("data-del")); }
+});
+
+// confirmation close → fresh memo
+document.getElementById("confClose").addEventListener("click", ()=>{
+  document.getElementById("overlay").classList.remove("show");
+  state = defaultState();
+  render();
+  window.scrollTo({top:0, behavior:"smooth"});
+});
+document.getElementById("overlay").addEventListener("click", e=>{
+  if(e.target.id==="overlay") document.getElementById("overlay").classList.remove("show");
+});
+
+// session banner dismiss
+document.getElementById("dismissSession").addEventListener("click", ()=>{
+  document.getElementById("sessionBanner").style.display="none";
+});
+
+/* ============ INIT ============ */
+
+function init(){
+  loadSaved();
+  const prior = loadSession();
+  if(prior && (prior.colorway || (prior.project && prior.project.trim()))){
+    state = Object.assign(defaultState(), prior);
+    // sanity clamp
+    if(state.quantity<1) state.quantity=1;
+    const banner = document.getElementById("sessionBanner");
+    banner.style.display="block";
+    const label = state.colorway ? `${cwById(state.colorway).name} · ${grById(state.ground).name} · ${fnById(state.finish).name}` : "your draft";
+    document.getElementById("sessionBannerText").textContent = `Restored your last session — ${label}${state.project?(' for “'+state.project+'”'):''}.`;
+  }
+  render();
+}
+init();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/84f06b214f8c/qwen25-7b.html b/data/artifacts/84f06b214f8c/qwen25-7b.html
new file mode 100644
index 0000000..015ef19
--- /dev/null
+++ b/data/artifacts/84f06b214f8c/qwen25-7b.html
@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Strata No. 7 Sample Order Configurator</title>
+    <style>
+        body {
+            font-family: Arial, sans-serif;
+            background-color: #f5f5f5;
+            color: #333;
+            margin: 20px;
+        }
+        .container {
+            max-width: 1200px;
+            margin: auto;
+            padding: 20px;
+            background: white;
+            box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+        }
+        h1 {
+            text-align: center;
+        }
+        .material-preview {
+            width: 350px;
+            height: 400px;
+            margin: 20px auto;
+            background-size: cover;
+            display: none;
+        }
+        .colorway, .ground, .finish, .leadTime {
+            margin-bottom: 10px;
+        }
+        input[type="number"] {
+            width: 50px;
+        }
+        #projectName, #deliveryUrgency {
+            display: block;
+            margin-bottom: 20px;
+        }
+        .comparison-tray {
+            border: 1px solid #ddd;
+            padding: 10px;
+            margin-top: 20px;
+        }
+        button {
+            background-color: #4CAF50;
+            color: white;
+            padding: 10px 20px;
+            text-align: center;
+            border: none;
+            cursor: pointer;
+            display: block;
+            margin: auto;
+        }
+        .comparison-tray .options {
+            margin-bottom: 10px;
+        }
+        @keyframes fadeIn {
+            from { opacity: 0; }
+            to { opacity: 1; }
+        }
+    </style>
+</head>
+<body>
+<div class="container">
+    <h1>Strata No. 7 Sample Order Configurator</h1>
+    
+    <div id="materialPreview" class="material-preview" style="background-image: url('https://via.placeholder.com/350x400.png?text=Strata+No.+7+Colorway+1');"></div>
+
+    <form id="configForm">
+        <label for="projectName">Project Name:</label>
+        <input type="text" id="projectName" name="projectName"><br><br>
+
+        <h2>Choose Colorways</h2>
+        <div class="colorway">
+            <select id="colorwaySelect" onchange="updatePreview()">
+                <option value="">Select a colorway</option>
+                <option value="1">Colorway 1</option>
+                <option value="2">Colorway 2</option>
+                <option value="3">Colorway 3</option>
+                <option value="4">Colorway 4</option>
+            </select>
+        </div>
+
+        <h2>Choose Ground</h2>
+        <div class="ground">
+            <label for="groundSelect">Ground:</label>
+            <select id="groundSelect" onchange="updatePreview()">
+                <option value="">Select a ground</option>
+                <option value="1">Ground 1</option>
+                <option value="2">Ground 2</option>
+            </select>
+        </div>
+
+        <h2>Choose Finish Level</h2>
+        <div class="finish">
+            <label for="finishSelect">Finish:</label>
+            <select id="finishSelect" onchange="updatePreview()">
+                <option value="">Select a finish level</option>
+                <option value="1">Finish 1 - Smooth</option>
+                <option value="2">Finish 2 - Textured</option>
+                <option value="3">Finish 3 - Hand-Brushed</option>
+            </select>
+        </div>
+
+        <h2>Choose Lead-Time Range</h2>
+        <div class="leadTime">
+            <label for="leadTimeSelect">Lead Time:</label>
+            <select id="leadTimeSelect" onchange="updatePreview()">
+                <option value="">Select a lead time range</option>
+                <option value="1">2-3 Weeks</option>
+                <option value="2">4-6 Weeks</option>
+                <option value="3">8+ Weeks</option>
+            </select>
+        </div>
+
+        <h2>Quantity Control</h2>
+        <label for="quantitySelect">Number of Samples:</label>
+        <input type="number" id="quantitySelect" name="quantity"><br><br>
+
+        <h2>Delivery Urgency</h2>
+        <select id="deliveryUrgency">
+            <option value="">Select urgency</option>
+            <option value="1">Standard (2-3 Weeks)</option>
+            <option value="2">Rush (4-6 Weeks)</option>
+            <option value="3">Express (8+ Weeks)</option>
+        </select><br><br>
+
+        <div id="memoPricing">
+            Memo Pricing: $<span id="pricing">0</span><br>
+            Minimum Order: 5 Samples
+        </div>
+
+        <div class="comparison-tray" style="display:none;">
+            <h2>Compare Options</h2>
+            <ul></ul>
+        </div>
+
+        <button onclick="submitOrder()">Submit Sample Request</button>
+    </form>
+    
+    <script>
+        const localStorageKey = 'strataNo7Config';
+        
+        function updatePreview() {
+            const colorway = document.getElementById('colorwaySelect').value;
+            const ground = document.getElementById('groundSelect').value;
+            const finish = document.getElementById('finishSelect').value;
+            
+            if (!colorway || !ground || !finish) return;
+
+            let previewImageSrc = `https://via.placeholder.com/350x400.png?text=Strata+No.+7+Colorway+${colorway}+Ground+${ground}+Finish+${finish}`;
+
+            document.getElementById('materialPreview').style.backgroundImage = `url('${previewImageSrc}')`;
+        }
+
+        function submitOrder() {
+            const projectName = document.getElementById('projectName').value;
+            const colorway = document.getElementById('colorwaySelect').value;
+            const ground = document.getElementById('groundSelect').value;
+            const finish = document.getElementById('finishSelect').value;
+            const leadTime = document.getElementById('leadTimeSelect').value;
+            const quantity = document.getElementById('quantitySelect').value;
+            const deliveryUrgency = document.getElementById('deliveryUrgency').value;
+
+            if (!projectName || !colorway || !ground || !finish || !quantity) {
+                alert("Please fill in all required fields.");
+                return;
+            }
+
+            let memoPricing = 0;
+            switch (leadTime) {
+                case "1":
+                    memoPricing += 50;
+                    break;
+                case "2":
+                    memoPricing += 75;
+                    break;
+                case "3":
+                    memoPricing += 100;
+                    break;
+            }
+
+            if (quantity < 5) {
+                alert("Minimum order is 5 samples.");
+                return;
+            }
+
+            memoPricing *= quantity;
+
+            document.getElementById('pricing').innerText = memoPricing.toFixed(2);
+        }
+
+        function initConfigurator() {
+            const config = JSON.parse(localStorage.getItem(localStorageKey)) || { colorway: '', ground: '', finish: '' };
+            
+            document.getElementById('colorwaySelect').value = config.colorway;
+            document.getElementById('groundSelect').value = config.ground;
+            document.getElementById('finishSelect').value = config.finish;
+
+            updatePreview();
+        }
+
+        function compareOptions() {
+            const comparisonList = document.querySelector('.comparison-tray ul');
+            comparisonList.innerHTML = '';
+
+            for (let i = 1; i <= 3; i++) {
+                const clone = document.getElementById(`configForm`).cloneNode(true);
+                document.querySelectorAll('select, input').forEach(el => el.remove());
+                clone.id += `_${i}`;
+                comparisonList.appendChild(clone);
+            }
+
+            document.querySelector('.comparison-tray').style.display = 'block';
+        }
+        
+        initConfigurator();
+    </script>
+</div>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/84f06b214f8c/qwen3-14b.html b/data/artifacts/84f06b214f8c/qwen3-14b.html
new file mode 100644
index 0000000..08475c9
--- /dev/null
+++ b/data/artifacts/84f06b214f8c/qwen3-14b.html
@@ -0,0 +1,451 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Strata No. 7 Sample Configurator</title>
+  <style>
+    body {
+      margin: 0;
+      font-family: 'Georgia', serif;
+      background: #f9f9f9;
+      color: #333;
+      line-height: 1.6;
+    }
+    header {
+      background: #e8e8e8;
+      padding: 1rem 2rem;
+      text-align: center;
+      font-weight: bold;
+      font-size: 1.5rem;
+    }
+    .container {
+      max-width: 960px;
+      margin: 2rem auto;
+      padding: 0 1rem;
+    }
+    .configurator {
+      display: grid;
+      grid-template-columns: 2fr 1fr;
+      gap: 2rem;
+    }
+    .configurator > * {
+      background: #fff;
+      padding: 1rem;
+      border-radius: 8px;
+      box-shadow: 0 2px 6px rgba(0,0,0,0.05);
+    }
+    .colorways {
+      display: flex;
+      flex-wrap: wrap;
+      gap: 1rem;
+    }
+    .colorway {
+      flex: 1 1 150px;
+      padding: 0.5rem;
+      border: 2px solid transparent;
+      cursor: pointer;
+      border-radius: 6px;
+      transition: all 0.3s;
+    }
+    .colorway.active {
+      border-color: #444;
+    }
+    .colorway::before {
+      content: '';
+      display: block;
+      width: 100%;
+      height: 150px;
+      background-size: cover;
+      background-repeat: no-repeat;
+      background-position: center;
+      border-radius: 6px;
+    }
+    .colorway:nth-child(1)::before {
+      background-image: linear-gradient(to bottom, #d9e5f0, #a7d6e5);
+    }
+    .colorway:nth-child(2)::before {
+      background-image: linear-gradient(to bottom, #f2d6c2, #e8c4a5);
+    }
+    .colorway:nth-child(3)::before {
+      background-image: linear-gradient(to bottom, #e0e8d8, #bdcdaa);
+    }
+    .colorway:nth-child(4)::before {
+      background-image: linear-gradient(to bottom, #f6e3d7, #e9c3c0);
+    }
+    .controls {
+      display: flex;
+      flex-direction: column;
+      gap: 1rem;
+    }
+    .controls label {
+      font-weight: bold;
+      margin-bottom: 0.25rem;
+    }
+    .controls select {
+      padding: 0.5rem;
+      border: 1px solid #ccc;
+      border-radius: 6px;
+      font-size: 1rem;
+      width: 100%;
+    }
+    .controls input[type="number"] {
+      padding: 0.5rem;
+      border: 1px solid #ccc;
+      border-radius: 6px;
+      font-size: 1rem;
+      width: 100%;
+    }
+    .preview {
+      position: relative;
+      height: 300px;
+      background: #f2f2f2;
+      border: 1px solid #ccc;
+      overflow: hidden;
+      border-radius: 6px;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      color: #888;
+      font-style: italic;
+      font-size: 1.2rem;
+    }
+    .preview::after {
+      content: '';
+      position: absolute;
+      top: 0;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: linear-gradient(to bottom, #d9e5f0, #a7d6e5);
+      z-index: 1;
+      opacity: 0.3;
+      pointer-events: none;
+    }
+    .info {
+      margin-top: 2rem;
+    }
+    .info .section {
+      margin-bottom: 1.5rem;
+    }
+    .info h3 {
+      margin-top: 0;
+    }
+    .info .availability,
+    .info .care-note,
+    .info .price {
+      font-style: italic;
+      color: #555;
+    }
+    .comparison {
+      margin-top: 2rem;
+      max-height: 300px;
+      overflow-y: auto;
+      border: 1px solid #ccc;
+      padding: 1rem;
+      border-radius: 6px;
+      background: #fff;
+    }
+    .comparison-item {
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      padding: 0.5rem 0;
+      border-bottom: 1px solid #eee;
+    }
+    .comparison-item:last-child {
+      border-bottom: none;
+    }
+    .comparison-item .name {
+      font-weight: bold;
+    }
+    .buttons {
+      display: flex;
+      gap: 1rem;
+      margin-top: 2rem;
+    }
+    .buttons button {
+      padding: 0.75rem 1.5rem;
+      font-size: 1rem;
+      border: none;
+      border-radius: 6px;
+      cursor: pointer;
+      background: #444;
+      color: #fff;
+    }
+    .buttons button:hover {
+      background: #555;
+    }
+    .saved {
+      margin-top: 2rem;
+      font-size: 0.9rem;
+      color: #666;
+    }
+    .error {
+      color: red;
+      font-size: 0.9rem;
+      margin-top: 0.5rem;
+    }
+    @media (max-width: 768px) {
+      .configurator {
+        grid-template-columns: 1fr;
+      }
+    }
+  </style>
+</head>
+<body>
+  <header>Strata No. 7 Sample Configurator</header>
+  <div class="container">
+    <div class="configurator">
+      <div>
+        <h2>Colorways</h2>
+        <div class="colorways">
+          <div class="colorway active" data-color="1"></div>
+          <div class="colorway" data-color="2"></div>
+          <div class="colorway" data-color="3"></div>
+          <div class="colorway" data-color="4"></div>
+        </div>
+        <div class="controls">
+          <label for="ground">Ground</label>
+          <select id="ground">
+            <option value="cotton">Cotton</option>
+            <option value="linen">Linen</option>
+          </select>
+          <label for="finish">Finish Level</label>
+          <select id="finish">
+            <option value="light">Light</option>
+            <option value="medium">Medium</option>
+            <option value="heavy">Heavy</option>
+          </select>
+          <label for="quantity">Quantity (per colorway)</label>
+          <input type="number" id="quantity" min="1" value="1" />
+          <label for="projectName">Project Name</label>
+          <input type="text" id="projectName" />
+          <label for="urgency">Delivery Urgency</label>
+          <select id="urgency">
+            <option value="standard">Standard (2-4 weeks)</option>
+            <option value="expedited">Expedited (1-2 weeks)</option>
+          </select>
+        </div>
+      </div>
+      <div>
+        <div class="preview" id="preview">Select a colorway to see the material preview</div>
+        <div class="info">
+          <div class="section">
+            <h3>Availability</h3>
+            <div id="availability">Available</div>
+          </div>
+          <div class="section">
+            <h3>Price</h3>
+            <div id="price">$0.00</div>
+          </div>
+          <div class="section">
+            <h3>Care Note</h3>
+            <div id="careNote">Handle with care. Dry clean only.</div>
+          </div>
+          <div class="section">
+            <h3>Minimum Order</h3>
+            <div id="minOrder">Minimum order of 3 units per colorway</div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="comparison" id="comparison">
+      <h3>Comparison Tray (up to 3 variants)</h3>
+    </div>
+    <div class="buttons">
+      <button onclick="saveConfig()">Save Configuration</button>
+      <button onclick="submitOrder()">Submit Sample Request</button>
+    </div>
+    <div class="saved" id="savedConfigs"></div>
+    <div class="error" id="error"></div>
+  </div>
+
+  <script>
+    const colorways = document.querySelectorAll('.colorway');
+    const preview = document.getElementById('preview');
+    const availability = document.getElementById('availability');
+    const price = document.getElementById('price');
+    const careNote = document.getElementById('careNote');
+    const minOrder = document.getElementById('minOrder');
+    const ground = document.getElementById('ground');
+    const finish = document.getElementById('finish');
+    const quantity = document.getElementById('quantity');
+    const projectName = document.getElementById('projectName');
+    const urgency = document.getElementById('urgency');
+    const comparison = document.getElementById('comparison');
+    const savedConfigs = document.getElementById('savedConfigs');
+    const error = document.getElementById('error');
+
+    let saved = JSON.parse(localStorage.getItem('strataConfigs')) || [];
+    let currentConfig = null;
+
+    function updatePreview(color, ground, finish) {
+      if (!color) {
+        preview.textContent = 'Select a colorway to see the material preview';
+        return;
+      }
+
+      let material = 'Available';
+      let priceValue = 0;
+      let careNoteValue = 'Handle with care. Dry clean only.';
+      let minOrderValue = 'Minimum order of 3 units per colorway';
+
+      if (ground === 'cotton' && finish === 'heavy') {
+        material = 'Available (limited stock)';
+        priceValue = 35.00;
+        careNoteValue = 'Handle with care. Dry clean only. Avoid direct sunlight.';
+      } else if (ground === 'linen' && finish === 'heavy') {
+        material = 'Backordered (expected in 3-4 weeks)';
+        priceValue = 38.00;
+        careNoteValue = 'Handle with care. Dry clean only. Avoid direct sunlight.';
+      } else if (ground === 'linen' && finish === 'medium') {
+        material = 'Available';
+        priceValue = 32.00;
+        care细NoteValue = 'Handle with care. Dry clean only.';
+      }
+
+      const basePrice = 25.00;
+      const finishMultiplier = { light: 1, medium: 1.2, heavy: 1.5 };
+      const groundMultiplier = { cotton: 1, linen: 1.1 };
+
+      priceValue = basePrice * finishMultiplier[finish] * groundMultiplier[ground];
+
+      availability.textContent = material;
+      price.textContent = `$${(priceValue * quantity.value).toFixed(2)}`;
+      careNote.textContent = careNoteValue;
+      minOrder.textContent = minOrderValue;
+
+      const gradient = getGradient(color);
+      preview.style.backgroundImage = `linear-gradient(to bottom, ${gradient.start}, ${gradient.end})`;
+    }
+
+    function getGradient(color) {
+      switch (color) {
+        case '1': return { start: '#d9e5f0', end: '#a7d6e5' };
+        case '2': return { start: '#f2d6c2', end: '#e8c4a5' };
+        case '3': return { start: '#e0e8d8', end: '#bdcdaa' };
+        case '4': return { start: '#f6e3d7', end: '#e9c3c0' };
+        default: return { start: '#f9f9f9', end: '#ccc' };
+      }
+    }
+
+    function updateComparison() {
+      const config = {
+        color: currentConfig.color,
+        ground: ground.value,
+        finish: finish.value,
+        quantity: quantity.value,
+        projectName: projectName.value,
+        urgency: urgency.value,
+        price: price.textContent,
+        availability: availability.textContent,
+        care: careNote.textContent
+      };
+
+      if (saved.length >= 3) {
+        saved.shift();
+      }
+      saved.push(config);
+      localStorage.setItem('strataConfigs', JSON.stringify(saved));
+      renderComparison();
+    }
+
+    function renderComparison() {
+      comparison.innerHTML = '<h3>Comparison Tray (up to 3 variants)</h3>';
+      saved.forEach((item, index) => {
+        const div = document.createElement('div');
+        div.className = 'comparison-item';
+        div.innerHTML = `
+          <div class="name">Config ${index + 1}</div>
+          <div>
+            <span>Ground:</span> ${item.ground}<br>
+            <span>Finish:</span> ${item.finish}<br>
+            <span>Price:</span> ${item.price}<br>
+            <span>Availability:</span> ${item.availability}
+          </div>
+        `;
+        comparison.appendChild(div);
+      });
+    }
+
+    function saveConfig() {
+      if (!currentConfig) {
+        error.textContent = 'Please select a colorway and configure your options before saving.';
+        return;
+      }
+      updateComparison();
+      error.textContent = '';
+    }
+
+    function submitOrder() {
+      if (!currentConfig) {
+        error.textContent = 'Please select a colorway and configure your options before submitting.';
+        return;
+      }
+
+      const config = {
+        color: currentConfig.color,
+        ground: ground.value,
+        finish: finish.value,
+        quantity: quantity.value,
+        projectName: projectName.value,
+        urgency: urgency.value,
+        price: price.textContent,
+        availability: availability.textContent,
+        care: careNote.textContent
+      };
+
+      if (parseInt(quantity.value) < 3) {
+        error.textContent = 'Minimum order of 3 units per colorway is required.';
+        return;
+      }
+
+      if (ground.value === 'linen' && finish.value === 'heavy' && availability.textContent.includes('Backordered')) {
+        error.textContent = 'This combination is currently backordered. Please select a different ground or finish.';
+        return;
+      }
+
+      alert('Your sample request has been submitted!\n\n' +
+        `Project: ${config.projectName}\n` +
+        `Colorway: ${config.color}\n` +
+        `Ground: ${config.ground}\n` +
+        `Finish: ${config.finish}\n` +
+        `Quantity: ${config.quantity}\n` +
+        `Price: ${config.price}\n` +
+        `Availability: ${config.availability}\n` +
+        `Care: ${config.care}\n` +
+        `Urgency: ${config.urgency}`);
+      error.textContent = '';
+    }
+
+    colorways.forEach((el) => {
+      el.addEventListener('click', (e) => {
+        colorways.forEach(c => c.classList.remove('active'));
+        e.currentTarget.classList.add('active');
+        currentConfig = { color: e.currentTarget.dataset.color };
+        updatePreview(e.currentTarget.dataset.color, ground.value, finish.value);
+        updateComparison();
+      });
+    });
+
+    ground.addEventListener('change', () => {
+      updatePreview(currentConfig.color, ground.value, finish.value);
+    });
+
+    finish.addEventListener('change', () => {
+      updatePreview(currentConfig.color, ground.value, finish.value);
+    });
+
+    quantity.addEventListener('input', () => {
+      updatePreview(currentConfig.color, ground.value, finish.value);
+    });
+
+    window.addEventListener('load', () => {
+      if (saved.length > 0) {
+        renderComparison();
+      }
+    });
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/89e23514bd71/qwen25-7b.html b/data/artifacts/89e23514bd71/qwen25-7b.html
new file mode 100644
index 0000000..b3bc3b4
--- /dev/null
+++ b/data/artifacts/89e23514bd71/qwen25-7b.html
@@ -0,0 +1,159 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Orbital Freight Coordination</title>
+    <style>
+        body { font-family: Arial, sans-serif; background-color: #f5f5f5; }
+        .container { width: 390px; margin: auto; padding: 20px; text-align: center; background: white; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); }
+        h1 { color: #4CAF50; font-size: 2em; margin-bottom: 20px; }
+        .star-map { width: 360px; height: 360px; border-radius: 8px; background-color: black; overflow: hidden; position: relative; }
+        .settlements { display: flex; justify-content: space-between; margin-bottom: 15px; }
+        .settlement { width: 90px; height: 90px; border-radius: 8px; background-color: #D3D3D3; margin: 5px; position: relative; overflow: hidden; }
+        .cargo-planner { display: flex; justify-content: space-between; align-items: center; padding: 10px; background: white; border-radius: 8px; box-shadow: inset 0 -2px rgba(0,0,0,0.1); margin-bottom: 15px; }
+        .cargo-box { width: 40%; height: 60px; display: flex; align-items: center; justify-content: space-between; padding: 5px; background-color: #e7e7e7; border-radius: 8px; }
+        .event-log { margin-bottom: 20px; background: white; padding: 10px; box-shadow: inset 0 -2px rgba(0,0,0,0.1); border-radius: 8px; }
+        .turn-timeline { width: 360px; height: 50px; margin-top: 15px; background-color: #dcdcdc; position: relative; overflow: hidden; }
+        .turn-indicator { width: 20px; height: 40px; position: absolute; left: -20px; top: 10px; border-radius: 8px; transition: all 0.3s ease-in-out; background-color: #4CAF50; }
+        .ship { width: 10px; height: 10px; position: absolute; bottom: -5px; right: -2px; border-radius: 50%; background-color: #4CAF50; transition: all 0.3s ease-in-out; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <h1>Orbital Freight Coordination</h1>
+        <div id="star-map" class="star-map"></div>
+        <div id="cargo-planner" class="cargo-planner">
+            <div class="cargo-box">Water: 0/20</div>
+            <div class="cargo-box">Medicine: 0/30</div>
+            <div class="cargo-box">Reactor Parts: 0/15</div>
+            <div class="cargo-box">Research Samples: 0/10</div>
+        </div>
+        <div id="settlements" class="settlements"></div>
+        <div id="event-log" class="event-log"></div>
+        <button onclick="togglePause()">Pause</button>
+    </div>
+
+    <script>
+        let turnsLeft = 12;
+        const settlements = [
+            { name: "Aurora", demand: [5, 3], storage: 20, trust: 70, production: [4, 2] },
+            { name: "Mars Base", demand: [6, 4], storage: 15, trust: 85, production: [3, 3] },
+            { name: "Luna Station", demand: [4, 5], storage: 25, trust: 90, production: [2, 4] },
+            { name: "Titan Lab", demand: [3, 6], storage: 10, trust: 60, production: [1, 5] },
+            { name: "Jovian Hub", demand: [2, 7], storage: 30, trust: 40, production: [5, 1] }
+        ];
+
+        const events = [
+            { turn: 4, event: "Aurora's fuel line is damaged" },
+            { turn: 8, event: "Mars Base needs emergency medicine due to a biotech outbreak" },
+            { turn: 10, event: "Luna Station reports critical reactor parts shortage" }
+        ];
+
+        const ships = [
+            { type: "Small", capacity: [3, 2], fuelCost: 1 },
+            { type: "Medium", capacity: [6, 4], fuelCost: 2 },
+            { type: "Large", capacity: [9, 5], fuelCost: 3 }
+        ];
+
+        function initializeGame() {
+            document.getElementById("star-map").style.width = `${settlements.length * 70}px`;
+            settlements.forEach(sett => {
+                const div = document.createElement("div");
+                div.classList.add("settlement");
+                div.style.height = `${sett.storage + 15}px`;
+                div.innerText = sett.name;
+                document.getElementById("settlements").appendChild(div);
+            });
+        }
+
+        function updateTurn(turn) {
+            turnsLeft--;
+            if (turn < events.length && turn === events[0].turn) {
+                document.getElementById("event-log").innerText = `Event: ${events[0].event}`;
+            }
+            const turnIndicator = document.querySelector(".turn-indicator");
+            turnIndicator.style.left = `${(turn / 12) * 360}px`;
+        }
+
+        function moveShip(from, to, cargoType, amount) {
+            const fromSett = settlements[from];
+            const toSett = settlements[to];
+            if (fromSett.storage - amount >= 0 && toSett.storage + amount <= toSett.capacity) {
+                fromSett.storage -= amount;
+                toSett.storage += amount;
+
+                // Animate ship movement
+                const shipDiv = document.createElement("div");
+                shipDiv.classList.add("ship");
+                shipDiv.style.left = `${(from * 70 + 35) - (amount / 2)}px`;
+                shipDiv.style.top = `-${360 - 10}px`;
+                document.getElementById("star-map").appendChild(shipDiv);
+                setTimeout(() => {
+                    shipDiv.style.top = `${-5 + Math.random() * 40}px`;
+                    shipDiv.style.left = `${(to * 70) + 35}px`;
+                    setTimeout(() => {
+                        shipDiv.remove();
+                    }, 1000);
+                }, 200);
+            }
+        }
+
+        function displayCargo(cargoType, amount) {
+            const cargoBoxes = document.querySelectorAll(".cargo-box");
+            cargoBoxes.forEach((box, index) => {
+                if (index === ["Water", "Medicine", "Reactor Parts", "Research Samples"].indexOf(cargoType)) {
+                    box.innerText = `${amount}/${settlements[index].production[cargoType]}`;
+                }
+            });
+        }
+
+        function checkForWin() {
+            const allSettled = settlements.every(sett => sett.storage >= (sett.demand[0] * 2 + sett.demand[1]));
+            if (allSettled && turnsLeft <= 0) {
+                alert("Congratulations! You coordinated the freight successfully.");
+            } else if (!allSettled && turnsLeft <= 0) {
+                alert("The storm is closing. Not all needs were met. Try again!");
+            }
+        }
+
+        function togglePause() {
+            const pauseButton = document.querySelector("button");
+            pauseButton.innerText = pauseButton.innerText === "Pause" ? "Resume" : "Pause";
+            if (pauseButton.innerText === "Pause") {
+                setInterval(() => updateTurn(turnsLeft), 1000);
+            } else {
+                clearInterval();
+            }
+        }
+
+        function main() {
+            initializeGame();
+            setTimeout(() => { // Start the game after initialization
+                for (let turn = 1; turn <= turnsLeft; turn++) {
+                    document.getElementById("event-log").innerText = "";
+                    updateTurn(turn);
+                    if (turn === events[0].turn) {
+                        alert(events[0].event);
+                        events.shift();
+                    }
+                    const from = Math.floor(Math.random() * settlements.length);
+                    const to = Math.floor(Math.random() * settlements.length);
+                    while (from === to) { // Ensure different settlement for now
+                        to = Math.floor(Math.random() * settlements.length);
+                    }
+                    if (settlements[from].storage > 0 && settlements[to].storage < settlements[to].capacity) {
+                        const cargoType = Math.floor(Math.random() * 4); // Randomly choose a cargo type
+                        const amount = Math.min(settlements[from].storage, settlements[to].capacity - settlements[to].storage);
+                        moveShip(from, to, cargoType, amount);
+                        displayCargo(cargoType, amount);
+                    }
+                    checkForWin();
+                }
+            }, 1000);
+        }
+
+        main();
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/89e23514bd71/qwen3-14b.html b/data/artifacts/89e23514bd71/qwen3-14b.html
new file mode 100644
index 0000000..c948d32
--- /dev/null
+++ b/data/artifacts/89e23514bd71/qwen3-14b.html
@@ -0,0 +1,510 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title>Orbital Freight Challenge</title>
+  <style>
+    body {
+      font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+      background: #1a1a1a;
+      color: #fff;
+      margin: 0;
+      padding: 0;
+      overflow: hidden;
+      user-select: none;
+    }
+    #app {
+      display: flex;
+      flex-direction: column;
+      height: 100vh;
+    }
+    header {
+      background: #2a2a2a;
+      padding: 10px;
+      text-align: center;
+      font-size: 1.2em;
+    }
+    #controls {
+      display: flex;
+      flex-wrap: wrap;
+      justify-content: center;
+      gap: 5px;
+      padding: 5px;
+    }
+    button {
+      background: #3a3a3a;
+      border: none;
+      color: #fff;
+      padding: 6px 12px;
+      border-radius: 4px;
+      cursor: pointer;
+      font-size: 0.9em;
+    }
+    button:hover {
+      background: #4a4a4a;
+    }
+    #main {
+      display: flex;
+      flex: 1;
+      overflow: hidden;
+    }
+    #sidebar {
+      width: 250px;
+      background: #2a2a2a;
+      display: flex;
+      flex-direction: column;
+      padding: 10px;
+      overflow-y: auto;
+      border-right: 1px solid #4a4a4a;
+    }
+    .section {
+      margin-bottom: 15px;
+    }
+    .section h2 {
+      margin-top: 0;
+      font-size: 1.1em;
+      margin-bottom: 5px;
+    }
+    .section .title {
+      font-weight: bold;
+    }
+    .section .value {
+      margin-left: 10px;
+    }
+    #map {
+      flex: 1;
+      position: relative;
+    }
+    .settlement {
+      position: absolute;
+      width: 100px;
+      height: 100px;
+      background: #3a3a3a;
+      border: 2px solid #5a5a5a;
+      border-radius: 10px;
+      padding: 5px;
+      display: flex;
+      flex-direction: column;
+      justify-content: space-between;
+      align-items: center;
+      text-align: center;
+      box-shadow: 0 0 4px #000;
+    }
+    .settlement h3 {
+      margin: 0;
+      font-size: 0.9em;
+    }
+    .settlement .indicator {
+      font-size: 0.8em;
+    }
+    .ship {
+      position: absolute;
+      width: 20px;
+      height: 20px;
+      background: #00ffcc;
+      border-radius: 50%;
+      pointer-events: none;
+      animation: move 3s linear;
+    }
+    @keyframes move {
+      from { transform: translate(0, 0); }
+      to { transform: translate(var(--dx), var(--dy)); }
+    }
+    #timeline {
+      height: 100px;
+      overflow-x: auto;
+      padding: 5px;
+      background: #2a2a2a;
+      border-top: 1px solid #4a4a4a;
+    }
+    #timeline .turn {
+      display: inline-block;
+      margin: 0 5px;
+      padding: 5px 10px;
+      background: #3a3a3a;
+      border-radius: 4px;
+      font-size: 0.9em;
+    }
+    #eventLog {
+      max-height: 200px;
+      overflow-y: auto;
+      padding: 5px;
+      background: #2a2a2a;
+      border-top: 1px solid #4a4a4a;
+    }
+    #eventLog .event {
+      margin-bottom: 5px;
+    }
+    #tutorial {
+      position: absolute;
+      top: 20px;
+      left: 20px;
+      background: rgba(0, 0, 0, 0.7);
+      padding: 10px;
+      border: 1px solid #4a4a4a;
+      border-radius: 6px;
+      color: #fff;
+      font-size: 0.9em;
+      display: none;
+      z-index: 10;
+    }
+    #summary {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      background: #2a2a2a;
+      padding: 20px;
+      border: 2px solid #4a4a4a;
+      border-radius: 8px;
+      display: none;
+      text-align: center;
+      max-width: 90%;
+    }
+    #summary h2 {
+      margin-top: 0;
+    }
+    #summary .text {
+      margin: 10px 0;
+    }
+  </style>
+</head>
+<body>
+  <div id="app">
+    <header>
+      <strong>Orbital Freight Challenge</strong>
+    </header>
+    <div id="controls">
+      <button onclick="toggleTutorial()">Tutorial</button>
+      <button onclick="resetGame()">Reset</button>
+      <button onclick="togglePause()">Pause</button>
+      <button onclick="toggleSound()">Sound Off</button>
+      <button onclick="toggleMotion()">Reduced Motion</button>
+      <select id="difficulty" onchange="setDifficulty(this.value)">
+        <option value="easy">Easy</option>
+        <option value="normal" selected>Normal</option>
+        <option value="hard">Hard</option>
+      </select>
+    </div>
+    <div id="main">
+      <div id="sidebar">
+        <div class="section" id="shipPlanner">
+          <h2>Ship Planner</h2>
+          <div id="shipOptions"></div>
+        </div>
+        <div class="section" id="settlements">
+          <h2>Settlements</h2>
+          <div id="settlementDetails"></div>
+        </div>
+        <div class="section" id="events">
+          <h2>Events</h2>
+          <div id="eventLog"></div>
+        </div>
+      </div>
+      <div id="map"></div>
+    </div>
+    <div id="timeline"></div>
+    <div id="tutorial">Welcome! You're a logistic officer managing freight between five orbital settlements. Use the ships to move resources before the solar storm closes the transfer windows. Each settlement has unique needs and trust levels. <strong>Use the Ship Planner to route resources, and watch the map and event log for consequences.</strong> Press <strong>Esc</strong> to close this tutorial.</div>
+    <div id="summary">
+      <h2>Game Over</h2>
+      <div class="text" id="summaryText"></div>
+      <button onclick="resetGame()">Try Again</button>
+    </div>
+  </div>
+  <script>
+    const map = document.getElementById('map');
+    const timeline = document.getElementById('timeline');
+    const eventLog = document.getElementById('eventLog');
+    const shipPlanner = document.getElementById('shipOptions');
+    const settlementDetails = document.getElementById('settlementDetails');
+    const tutorial = document.getElementById('tutorial');
+    const summary = document.getElementById('summary');
+    const summaryText = document.getElementById('summaryText');
+    const difficultySelect = document.getElementById('difficulty');
+
+    let gameRunning = false;
+    let paused = false;
+    let currentTurn = 1;
+    let maxTurns = 12;
+    let difficulty = 'normal';
+    let shipOptions = [
+      { name: 'Light Freighter', capacity: 2, fuelCost: 1, speed: 1 },
+      { name: 'Medium Freighter', capacity: 4, fuelCost: 2, speed: 1 },
+      { name: 'Heavy Freighter', capacity: 6, fuelCost: 3, speed: 1 },
+    ];
+    let settlements = [];
+    let ships = [];
+    let events = [];
+    let selectedShip = null;
+    let selectedFrom = null;
+    let selectedTo = null;
+    let selectedCargo = null;
+    let cargoTypes = ['Water', 'Medicine', 'Reactor Parts', 'Research Samples'];
+    let solarStorm = {
+      turn: maxTurns + 1,
+      effect: 'Transfer windows closed. No more travel.',
+    };
+
+    function setDifficulty(level) {
+      difficulty = level;
+    }
+
+    function togglePause() {
+      paused = !paused;
+      document.querySelector('button[onclick="togglePause()"]').textContent = paused ? 'Resume' : 'Pause';
+      if (paused) {
+        if (gameRunning) {
+          clearInterval(gameLoop);
+        }
+      } else {
+        if (gameRunning) {
+          gameLoop = setInterval(nextTurn, 1000);
+        }
+      }
+    }
+
+    function toggleTutorial() {
+      tutorial.style.display = tutorial.style.display === 'none' ? 'block' : 'none';
+    }
+
+    function toggleSound() {
+      // Placeholder for sound toggle
+    }
+
+    function toggleMotion() {
+      // Placeholder for reduced motion
+    }
+
+    function resetGame() {
+      gameRunning = false;
+      paused = false;
+      currentTurn = 1;
+      settlements = [];
+      ships = [];
+      events = [];
+      map.innerHTML = '';
+      timeline.innerHTML = '';
+      eventLog.innerHTML = '';
+      shipPlanner.innerHTML = '';
+      settlementDetails.innerHTML = '';
+      tutorial.style.display = 'none';
+      summary.style.display = 'none';
+      document.querySelector('button[onclick="togglePause()"]').textContent = 'Pause';
+      setupGame();
+    }
+
+    function setupGame() {
+      gameRunning = true;
+      currentTurn = 1;
+      settlements = [
+        {
+          name: 'Aurora Station',
+          position: { x: 100, y: 100 },
+          demand: { 'Water': 3, 'Medicine': 1, 'Reactor Parts': 2, 'Research Samples': 0 },
+          storage: { 'Water': 5, 'Medicine': 2, 'Reactor Parts': 3, 'Research Samples': 1 },
+          trust: 80,
+          production: { 'Water': 2, 'Medicine': 1, 'Reactor Parts': 1, 'Research Samples': 0 },
+        },
+        {
+          name: 'Luna Base',
+          position: { x: 300, y: 100 },
+          demand: { 'Water': 4, 'Medicine': 2, 'Reactor Parts': 1, 'Research Samples': 1 },
+          storage: { 'Water': 3, 'Medicine': 1, 'Reactor Parts': 2, 'Research Samples': 0 },
+          trust: 60,
+          production: { 'Water': 1, 'Medicine': 2, 'Reactor Parts': 1, 'Research Samples': 0 },
+        },
+        {
+          name: 'Orion Hub',
+          position: { x: 200, y: 300 },
+          demand: { 'Water': 2, 'Medicine': 3, 'Reactor Parts': 3, 'Research Samples': 1 },
+          storage: { 'Water': 4, 'Medicine': 2, 'Reactor Parts': 1, 'Research Samples': 0 },
+          trust: 70,
+          production: { 'Water': 1, 'Medicine': 1, 'Reactor Parts': 2, 'Research Samples': 0 },
+        },
+        {
+          name: 'Helios Outpost',
+          position: { x: 100, y: 300 },
+          demand: { 'Water': 1, 'Medicine': 0, 'Reactor Parts': 0, 'Research Samples': 3 },
+          storage: { 'Water': 2, 'Medicine': 3, 'Reactor Parts': 2, 'Research Samples': 1 },
+          trust: 50,
+          production: { 'Water': 2, 'Medicine': 2, 'Reactor Parts': 3, 'Research Samples': 1 },
+        },
+        {
+          name: 'Eos Station',
+          position: { x: 300, y: 300 },
+          demand: { 'Water': 3, 'Medicine': 1, 'Reactor Parts': 2, 'Research Samples': 2 },
+          storage: { 'Water': 1, 'Medicine': 3, 'Reactor Parts': 2, 'Research Samples': 0 },
+          trust: 75,
+          production: { 'Water': 2, 'Medicine': 1, 'Reactor Parts': 1, 'Research Samples': 0 },
+        },
+      ];
+      ships = [
+        { id: 1, name: 'Freighter 1', capacity: 4, fuelCost: 2, position: { x: 150, y: 150 }, cargo: {} },
+        { id: 2, name: 'Freighter 2', capacity: 6, fuelCost: 3, position: { x: 250, y: 150 }, cargo: {} },
+        { id: 3, name: 'Freighter 3', capacity: 4, fuelCost: 2, position: { x: 200, y: 250 }, cargo: {} },
+      ];
+
+      events = [
+        {
+          turn: 3,
+          description: 'Luna Base requests an emergency shipment of 3 Medicine units. If denied, trust drops by 20%',
+          type: 'negotiation',
+          consequence: {
+            yes: { trust: -20, event: 'Medicine shipment accepted.' },
+            no: { trust: -20, event: 'Luna Base trust reduced by 20%' },
+          },
+        },
+        {
+          turn: 5,
+          description: 'A malfunction in the Aurora Station\'s reactor requires 2 Reactor Parts. If not delivered by turn 6, 20% of storage is lost.',
+          type: 'event',
+          consequence: {
+            delivered: { event: 'Reactor parts successfully delivered.' },
+            notDelivered: { storageLoss: 20, event: 'Aurora Station lost 20% of storage.' },
+          },
+        },
+        {
+          turn: 8,
+          description: 'Helios Outpost discovers new research samples. 3 units are produced and added to their storage.',
+          type: 'event',
+          consequence: {
+            event: 'Helios Outpost gained 3 Research Samples.',
+          },
+        },
+      ];
+
+      renderMap();
+      renderTimeline();
+      renderShipOptions();
+      renderSettlementDetails();
+      gameLoop = setInterval(nextTurn, 1000);
+    }
+
+    function nextTurn() {
+      if (paused || currentTurn > maxTurns) return;
+      currentTurn++;
+      renderTimeline();
+      checkEvents();
+      if (currentTurn === maxTurns + 1) {
+        clearInterval(gameLoop);
+        endGame();
+      }
+    }
+
+    function checkEvents() {
+      events.forEach(event => {
+        if (event.turn === currentTurn) {
+          eventLog.innerHTML += `<div class="event">${event.description}</div>`;
+          if (event.type === 'negotiation') {
+            showNegotiationChoice(event);
+          }
+        }
+      });
+    }
+
+    function showNegotiationChoice(event) {
+      const settlement = settlements.find(s => s.name === event.description.split(' ')[0]);
+      if (settlement) {
+        const response = prompt(`You must choose whether to fulfill the request from ${settlement.name} for ${event.description.split(' ')[3]} units of ${event.description.split(' ')[2]}. Yes or No?`);
+        if (response && response.toLowerCase() === 'yes') {
+          eventLog.innerHTML += `<div class="event">${event.consequence.yes.event}</div>`;
+          if (event.consequence.yes.trust !== undefined) {
+            settlement.trust += event.consequence.yes.trust;
+          }
+        } else if (response && response.toLowerCase() === 'no') {
+          eventLog.innerHTML += `<div class="event">${event.consequence.no.event}</div>`;
+          if (event.consequence.no.trust !== undefined) {
+            settlement.trust += event.consequence.no.trust;
+          }
+        }
+      }
+    }
+
+    function renderMap() {
+      map.innerHTML = '';
+      settlements.forEach(settlement => {
+        const div = document.createElement('div');
+        div.className = 'settlement';
+        div.style.left = `${settlement.position.x}px`;
+        div.style.top = `${settlement.position.y}px`;
+        div.innerHTML = `
+          <h3>${settlement.name}</h3>
+          <div class="indicator">Trust: ${settlement.trust}</div>
+        `;
+        map.appendChild(div);
+      });
+      ships.forEach(ship => {
+        const shipDiv = document.createElement('div');
+        shipDiv.className = 'ship';
+        shipDiv.style.left = `${ship.position.x}px`;
+        shipDiv.style.top = `${ship.position.y}px`;
+        map.appendChild(shipDiv);
+      });
+    }
+
+    function renderTimeline() {
+      timeline.innerHTML = '';
+      for (let i = 1; i <= maxTurns + 1; i++) {
+        const turnDiv = document.createElement('div');
+        turnDiv.className = 'turn';
+        turnDiv.textContent = i;
+        if (i === currentTurn) {
+          turnDiv.style.backgroundColor = '#4a4a4a';
+        }
+        timeline.appendChild(turnDiv);
+      }
+    }
+
+    function renderShipOptions() {
+      shipPlanner.innerHTML = '';
+      shipOptions.forEach(ship => {
+        const optionDiv = document.createElement('div');
+        optionDiv.innerHTML = `
+          <strong>${ship.name}</strong>
+          <div>Capacity: ${ship.capacity} units</div>
+          <div>Fuel Cost: ${ship.fuelCost}</div>
+        `;
+        shipPlanner.appendChild(optionDiv);
+      });
+    }
+
+    function renderSettlementDetails() {
+      settlementDetails.innerHTML = '';
+      settlements.forEach(settlement => {
+        const detailDiv = document.createElement('div');
+        detailDiv.className = 'section';
+        detailDiv.innerHTML = `
+          <h2>${settlement.name}</h2>
+          <div><span class="title">Demand:</span> ${JSON.stringify(settlement.demand)}</div>
+          <div><span class="title">Storage:</span> ${JSON.stringify(settlement.storage)}</div>
+          <div><span class="title">Trust:</span> ${settlement.trust}</div>
+          <div><span class="title">Production:</span> ${JSON.stringify(settlement.production)}</div>
+        `;
+        settlementDetails.appendChild(detailDiv);
+      });
+    }
+
+    function endGame() {
+      clearInterval(gameLoop);
+      let success = true;
+      settlements.forEach(s => {
+        if (Object.values(s.demand).some(d => s.storage[d] < d)) {
+          success = false;
+        }
+      });
+      if (success) {
+        summaryText.innerHTML = 'You coordinated all freight in time. The settlements are stable and trust is high.';
+      } else {
+        summaryText.innerHTML = 'The solar storm closed the transfer windows. Some settlements are in crisis.';
+      }
+      summary.style.display = 'block';
+    }
+
+    document.addEventListener('keydown', function(e) {
+      if (e.key === 'Escape') {
+        tutorial.style.display = 'none';
+      }
+    });
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/96c1603e5f4a/claude-code.html b/data/artifacts/96c1603e5f4a/claude-code.html
new file mode 100644
index 0000000..d2a85ac
--- /dev/null
+++ b/data/artifacts/96c1603e5f4a/claude-code.html
@@ -0,0 +1,694 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="UTF-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>Afterlight: Rooms Remember — A Digital Exhibition</title>
+<style>
+  :root{
+    --paper:#f6f2ea;
+    --ink:#171310;
+    --ink-soft:#4a423a;
+    --line:#d8cfbf;
+    --line-strong:#b9ad98;
+    --accent:#6a4fb5;
+    --ui-bg:#efe9dd;
+    --focus:#0a5cff;
+    --max:1120px;
+    --step:clamp(1rem,.85rem + .6vw,1.15rem);
+    --ease:cubic-bezier(.2,.7,.2,1);
+    --reveal:1;
+  }
+  *{box-sizing:border-box}
+  html{scroll-behavior:smooth}
+  @media (prefers-reduced-motion: reduce){ html{scroll-behavior:auto} }
+  html.no-motion{scroll-behavior:auto}
+
+  body{
+    margin:0;
+    background:var(--paper);
+    color:var(--ink);
+    font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
+    font-size:var(--step);
+    line-height:1.6;
+    -webkit-font-smoothing:antialiased;
+    text-rendering:optimizeLegibility;
+  }
+  .serif{font-family:Georgia,"Times New Roman","Iowan Old Style",serif}
+
+  a{color:inherit}
+  a:focus-visible,button:focus-visible,select:focus-visible,[tabindex]:focus-visible{
+    outline:3px solid var(--focus);
+    outline-offset:2px;
+    border-radius:2px;
+  }
+
+  /* ---------- Skip + live region ---------- */
+  .skip{
+    position:fixed;left:8px;top:-60px;z-index:200;
+    background:var(--ink);color:var(--paper);
+    padding:.6rem 1rem;border-radius:4px;text-decoration:none;
+    transition:top .18s var(--ease);
+  }
+  .skip:focus{top:8px}
+  .sr{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
+
+  /* ---------- Progress ---------- */
+  .progress{
+    position:fixed;left:0;top:0;height:4px;width:0%;
+    background:var(--accent);z-index:150;
+    transition:width .08s linear;
+  }
+  html.no-motion .progress{transition:none}
+
+  /* ---------- Top bar ---------- */
+  .bar{
+    position:sticky;top:0;z-index:120;
+    background:color-mix(in srgb,var(--paper) 88%, transparent);
+    backdrop-filter:saturate(120%) blur(6px);
+    border-bottom:1px solid var(--line);
+  }
+  .bar-in{
+    max-width:var(--max);margin:0 auto;padding:.5rem 1rem;
+    display:flex;flex-wrap:wrap;gap:.5rem 1rem;align-items:center;
+  }
+  .brand{font-weight:700;letter-spacing:.02em;font-size:.95rem;margin-right:auto;white-space:nowrap}
+  .brand small{display:block;font-weight:400;color:var(--ink-soft);letter-spacing:.08em;text-transform:uppercase;font-size:.62rem}
+  nav.chapters{display:flex;flex-wrap:wrap;gap:.15rem .35rem}
+  nav.chapters a{
+    text-decoration:none;font-size:.8rem;padding:.28rem .5rem;border-radius:999px;
+    color:var(--ink-soft);border:1px solid transparent;
+  }
+  nav.chapters a:hover{color:var(--ink);border-color:var(--line-strong)}
+  nav.chapters a[aria-current="true"]{color:var(--ink);border-color:var(--accent);font-weight:600}
+  .tools{display:flex;gap:.35rem;flex-wrap:wrap}
+  .tools button{
+    font:inherit;font-size:.75rem;cursor:pointer;
+    background:var(--ui-bg);border:1px solid var(--line-strong);color:var(--ink);
+    padding:.3rem .55rem;border-radius:6px;
+  }
+  .tools button[aria-pressed="true"]{background:var(--ink);color:var(--paper);border-color:var(--ink)}
+
+  /* ---------- Layout ---------- */
+  main{display:block}
+  .wrap{max-width:var(--max);margin:0 auto;padding:0 1rem}
+  section{scroll-margin-top:92px}
+
+  /* ---------- Title sequence ---------- */
+  .hero{
+    min-height:92vh;display:flex;flex-direction:column;justify-content:center;
+    padding:6vh 1rem 4vh;position:relative;overflow:hidden;
+  }
+  .hero-glow{
+    position:absolute;inset:-20% -10% auto -10%;height:70%;
+    background:radial-gradient(60% 80% at 30% 20%, rgba(106,79,181,.18), transparent 70%);
+    pointer-events:none;
+    animation:drift 22s var(--ease) infinite alternate;
+    opacity:var(--reveal);
+  }
+  @keyframes drift{from{transform:translate3d(0,0,0)}to{transform:translate3d(6%,4%,0)}}
+  html.no-motion .hero-glow, .hc .hero-glow{animation:none;display:none}
+  .hero .kicker{letter-spacing:.35em;text-transform:uppercase;font-size:.72rem;color:var(--ink-soft)}
+  .hero h1{
+    font-size:clamp(2.6rem,6vw + 1rem,6.2rem);line-height:.98;margin:.3em 0 .1em;
+    font-weight:700;letter-spacing:-.01em;max-width:15ch;
+  }
+  .hero h1 em{font-style:italic;color:var(--accent)}
+  .hero .lede{font-size:clamp(1.05rem,1rem + .7vw,1.5rem);max-width:46ch;color:var(--ink-soft)}
+  .hero .span{display:inline-block;font-variant-numeric:tabular-nums;border-top:1px solid var(--line-strong);margin-top:1.4rem;padding-top:.6rem;color:var(--ink);font-size:.9rem;letter-spacing:.05em}
+  .enter{
+    margin-top:2rem;display:inline-flex;align-items:center;gap:.5rem;
+    align-self:flex-start;text-decoration:none;font-weight:600;
+    border:1px solid var(--ink);padding:.6rem 1rem;border-radius:8px;
+  }
+  .enter:hover{background:var(--ink);color:var(--paper)}
+
+  .reveal{opacity:0;transform:translateY(14px)}
+  .reveal.in{opacity:1;transform:none;transition:opacity .7s var(--ease),transform .7s var(--ease)}
+  html.no-motion .reveal, .hc .reveal{opacity:1!important;transform:none!important;transition:none!important}
+
+  /* staggered hero lines */
+  .stagger>*{opacity:0;transform:translateY(12px)}
+  .stagger.in>*{opacity:1;transform:none;transition:opacity .6s var(--ease),transform .6s var(--ease)}
+  .stagger.in>*:nth-child(1){transition-delay:.05s}
+  .stagger.in>*:nth-child(2){transition-delay:.18s}
+  .stagger.in>*:nth-child(3){transition-delay:.34s}
+  .stagger.in>*:nth-child(4){transition-delay:.5s}
+  .stagger.in>*:nth-child(5){transition-delay:.66s}
+  html.no-motion .stagger>*, .hc .stagger>*{opacity:1!important;transform:none!important;transition:none!important}
+
+  /* ---------- Thesis ---------- */
+  .thesis{padding:8vh 0 6vh;border-top:1px solid var(--line)}
+  .thesis p{font-size:clamp(1.2rem,1rem + 1vw,1.9rem);max-width:34ch;line-height:1.35}
+  .thesis .serif{font-style:italic}
+  .thesis .note{font-size:.95rem;color:var(--ink-soft);max-width:56ch;margin-top:1.6rem}
+
+  /* ---------- Index ---------- */
+  .index{padding:6vh 0;border-top:1px solid var(--line)}
+  .index h2,.compare h2,.thesis h2{font-size:.78rem;letter-spacing:.28em;text-transform:uppercase;color:var(--ink-soft);margin:0 0 1.4rem}
+  ol.idx{list-style:none;margin:0;padding:0;display:grid;gap:.2rem}
+  ol.idx li a{
+    display:grid;grid-template-columns:2.4rem minmax(0,1fr) auto;gap:1rem;align-items:baseline;
+    text-decoration:none;padding:.9rem .4rem;border-bottom:1px solid var(--line);
+  }
+  ol.idx li a:hover{background:var(--ui-bg)}
+  ol.idx .n{font-variant-numeric:tabular-nums;color:var(--ink-soft);font-size:.9rem}
+  ol.idx .ti{font-family:Georgia,serif;font-size:clamp(1.2rem,1rem + 1vw,1.7rem)}
+  ol.idx .ti b{font-weight:600;display:block}
+  ol.idx .ti span{font-style:italic;color:var(--ink-soft);font-size:.85rem;font-family:inherit}
+  ol.idx .yr{font-variant-numeric:tabular-nums;font-size:1rem;color:var(--ink-soft)}
+  ol.idx .dot{width:.6rem;height:.6rem;border-radius:50%;background:var(--c);display:inline-block;vertical-align:middle;margin-right:.4rem}
+  .hc ol.idx .dot{outline:1px solid var(--ink);background:transparent}
+
+  /* ---------- Object chapters ---------- */
+  .object{padding:12vh 0;border-top:1px solid var(--line);--c:var(--accent)}
+  .object .head{display:grid;gap:.4rem}
+  .object .year{
+    font-family:Georgia,serif;font-weight:700;line-height:.85;
+    font-size:clamp(4rem,14vw,11rem);letter-spacing:-.03em;
+    color:var(--c);
+  }
+  .hc .object .year{color:var(--ink);text-decoration:underline;text-decoration-thickness:2px}
+  .object .meta{display:flex;flex-wrap:wrap;gap:.4rem 1rem;align-items:baseline}
+  .object .obj-n{font-variant-numeric:tabular-nums;letter-spacing:.2em;font-size:.72rem;text-transform:uppercase;color:var(--ink-soft)}
+  .object h3{font-family:Georgia,serif;font-size:clamp(1.7rem,1.2rem + 2vw,3.1rem);margin:.1em 0 0;font-weight:600;line-height:1.05}
+  .object h3 span{display:block;font-style:italic;font-weight:400;font-size:.5em;color:var(--ink-soft)}
+
+  .object .grid{display:grid;grid-template-columns:1fr;gap:2.2rem;margin-top:2.6rem}
+  @media(min-width:760px){.object .grid{grid-template-columns:1.15fr .85fr;gap:3rem}}
+
+  .curator p{font-size:clamp(1.05rem,1rem + .5vw,1.35rem);max-width:56ch}
+  .curator .drop::first-letter{
+    font-family:Georgia,serif;float:left;font-size:3.2em;line-height:.72;
+    padding:.05em .12em 0 0;color:var(--c);font-weight:700;
+  }
+  .hc .curator .drop::first-letter{color:var(--ink)}
+
+  .facts{display:grid;gap:1.4rem;align-content:start;
+    border-left:3px solid var(--c);padding-left:1.2rem}
+  .hc .facts{border-left-color:var(--ink)}
+  .facts h4{margin:0 0 .3rem;font-size:.7rem;letter-spacing:.2em;text-transform:uppercase;color:var(--ink-soft)}
+  .facts .val{font-size:.98rem}
+  .status{display:inline-flex;align-items:center;gap:.5rem;font-weight:600;font-size:.9rem}
+  .status .pip{width:.7rem;height:.7rem;border-radius:50%;background:var(--c);box-shadow:0 0 0 3px color-mix(in srgb,var(--c) 25%,transparent)}
+  .hc .status .pip{background:var(--ink);box-shadow:none;outline:1px solid var(--ink)}
+
+  /* audio / transcript */
+  figure.audio{margin:0;border:1px solid var(--line-strong);border-radius:10px;overflow:hidden;background:var(--ui-bg)}
+  figure.audio .bar2{display:flex;align-items:center;gap:.7rem;padding:.7rem .9rem;border-bottom:1px solid var(--line)}
+  figure.audio button.play{
+    flex:0 0 auto;width:2.2rem;height:2.2rem;border-radius:50%;
+    border:1px solid var(--ink);background:var(--paper);cursor:pointer;font-size:.85rem;
+    display:grid;place-items:center;color:var(--ink);
+  }
+  figure.audio .cap{font-size:.74rem;color:var(--ink-soft);letter-spacing:.04em}
+  figure.audio .cap b{color:var(--ink);letter-spacing:0;font-size:.8rem}
+  figure.audio blockquote{margin:0;padding:.9rem 1rem 1rem;font-family:Georgia,serif;font-style:italic;font-size:1.05rem;line-height:1.5}
+  figure.audio .fallback{padding:.55rem .9rem;font-size:.72rem;color:var(--ink-soft);border-top:1px dashed var(--line-strong);background:color-mix(in srgb,var(--c) 8%,transparent)}
+  .hc figure.audio .fallback{background:transparent}
+  figcaption{font-size:.72rem;color:var(--ink-soft);padding:0 1rem .8rem}
+
+  /* ---------- Compare ---------- */
+  .compare{padding:10vh 0;border-top:1px solid var(--line);background:var(--ui-bg)}
+  .compare .controls{display:flex;flex-wrap:wrap;gap:1rem;align-items:end;margin-bottom:1.6rem}
+  .compare .field{display:grid;gap:.3rem}
+  .compare label{font-size:.72rem;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-soft)}
+  .compare select{font:inherit;font-size:.95rem;padding:.5rem .6rem;border:1px solid var(--line-strong);border-radius:8px;background:var(--paper);color:var(--ink);min-width:min(90vw,22ch)}
+  .compare .swap{align-self:end}
+  .cmp-grid{display:grid;grid-template-columns:1fr;gap:1rem}
+  @media(min-width:720px){.cmp-grid{grid-template-columns:1fr 1fr;gap:1.4rem}}
+  .panel{background:var(--paper);border:1px solid var(--line-strong);border-radius:12px;padding:1.4rem;--c:var(--accent);border-top:5px solid var(--c)}
+  .hc .panel{border-top-color:var(--ink)}
+  .panel .p-year{font-family:Georgia,serif;font-weight:700;color:var(--c);font-size:2.6rem;line-height:1}
+  .hc .panel .p-year{color:var(--ink)}
+  .panel h3{font-family:Georgia,serif;margin:.2rem 0 1rem;font-size:1.4rem;font-weight:600}
+  .panel h3 span{display:block;font-style:italic;font-weight:400;color:var(--ink-soft);font-size:.7em}
+  .panel dl{margin:0;display:grid;gap:1rem}
+  .panel dt{font-size:.68rem;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-soft);margin-bottom:.2rem}
+  .panel dd{margin:0;font-size:.92rem}
+  .cmp-read{margin-top:1.4rem;font-size:.9rem;color:var(--ink-soft);max-width:70ch}
+  .cmp-read b{color:var(--ink)}
+
+  /* ---------- Help ---------- */
+  .help[hidden]{display:none}
+  .help{
+    position:fixed;inset:0;z-index:180;display:grid;place-items:center;padding:1rem;
+    background:rgba(23,19,16,.55);
+  }
+  .help .card{background:var(--paper);border:1px solid var(--ink);border-radius:14px;max-width:34rem;width:100%;padding:1.5rem;max-height:86vh;overflow:auto}
+  .help h2{margin:0 0 .8rem;font-size:1.1rem}
+  .help table{width:100%;border-collapse:collapse;font-size:.9rem}
+  .help td{padding:.4rem 0;border-bottom:1px solid var(--line);vertical-align:top}
+  .help kbd{font-family:ui-monospace,Menlo,Consolas,monospace;background:var(--ui-bg);border:1px solid var(--line-strong);border-bottom-width:2px;border-radius:5px;padding:.05rem .4rem;font-size:.82em}
+  .help .close{margin-top:1rem;font:inherit;cursor:pointer;background:var(--ink);color:var(--paper);border:0;padding:.5rem .9rem;border-radius:8px}
+
+  footer{padding:8vh 0 10vh;border-top:1px solid var(--line);color:var(--ink-soft);font-size:.85rem}
+  footer .wrap>*{max-width:60ch}
+
+  /* ---------- High contrast ---------- */
+  .hc{
+    --paper:#ffffff;--ink:#000000;--ink-soft:#222222;
+    --line:#000000;--line-strong:#000000;--ui-bg:#ffffff;--accent:#000000;
+  }
+  .hc .bar{background:#fff}
+  .hc a{text-underline-offset:2px}
+
+  @media(max-width:430px){
+    .bar-in{padding:.45rem .8rem}
+    .brand{width:100%;margin-bottom:.15rem}
+    nav.chapters{width:100%;overflow-x:auto;flex-wrap:nowrap;padding-bottom:.15rem}
+    nav.chapters a{white-space:nowrap}
+    .object{padding:9vh 0}
+    ol.idx li a{grid-template-columns:1.8rem minmax(0,1fr) auto;gap:.6rem}
+  }
+</style>
+</head>
+<body>
+<a class="skip" href="#thesis">Skip title sequence — go to the curatorial thesis</a>
+<div class="progress" id="progress" role="progressbar" aria-label="Reading progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0"></div>
+<div class="sr" id="live" aria-live="polite"></div>
+
+<header class="bar">
+  <div class="bar-in">
+    <div class="brand">Afterlight<small>Rooms Remember · Digital Exhibition</small></div>
+    <nav class="chapters" id="chapnav" aria-label="Chapters">
+      <a href="#top">Title</a>
+      <a href="#thesis">Thesis</a>
+      <a href="#index">Index</a>
+      <!-- object links injected -->
+      <a href="#compare">Compare</a>
+    </nav>
+    <div class="tools">
+      <button id="btnContrast" aria-pressed="false" title="Toggle high contrast (h)">◑ Contrast</button>
+      <button id="btnMotion" aria-pressed="false" title="Toggle motion (m)">〜 Motion</button>
+      <button id="btnHelp" aria-haspopup="dialog" title="Keyboard help (?)">⌨ Keys</button>
+    </div>
+  </div>
+</header>
+
+<main id="main">
+  <!-- ================= TITLE SEQUENCE ================= -->
+  <section class="hero" id="top" aria-label="Title sequence">
+    <div class="hero-glow" aria-hidden="true"></div>
+    <div class="wrap stagger" id="heroStagger">
+      <p class="kicker">An exhibition in five objects · 1928 – 2040</p>
+      <h1 class="serif">After<em>light</em>:<br>Rooms Remember</h1>
+      <p class="lede">Every room keeps a record of the people who moved through it — in the objects they wore smooth, tuned, illuminated, and finally left behind.</p>
+      <p class="span">1928 &nbsp;→&nbsp; 1959 &nbsp;→&nbsp; 1984 &nbsp;→&nbsp; 2011 &nbsp;→&nbsp; 2040</p>
+      <a class="enter" href="#thesis">Enter the exhibition &nbsp;↓</a>
+    </div>
+  </section>
+
+  <!-- ================= THESIS ================= -->
+  <section class="thesis" id="thesis" aria-labelledby="thesis-h">
+    <div class="wrap reveal">
+      <h2 id="thesis-h">Curatorial Thesis</h2>
+      <p class="serif">Furniture outlives us. This show reads five domestic objects as <em>memory-devices</em> — surfaces that absorbed a household's sound, light, and touch.</p>
+      <p class="note">Arranged chronologically, the objects trace one idea across a century: that a room's memory is <b>material</b> before it is sentimental. Wear, oxidation, magnetic residue, and — by 2040 — living tissue each preserve a trace of habit. Read the works in order to follow the argument; jump between them from the index; or place any two side by side in <a href="#compare">Compare</a> to study how their material histories diverge.</p>
+    </div>
+  </section>
+
+  <!-- ================= OBJECT INDEX ================= -->
+  <section class="index" id="index" aria-labelledby="index-h">
+    <div class="wrap reveal">
+      <h2 id="index-h">Object Index</h2>
+      <ol class="idx" id="idxList"></ol>
+    </div>
+  </section>
+
+  <!-- ================= OBJECT CHAPTERS (injected) ================= -->
+  <div id="objects"></div>
+
+  <!-- ================= COMPARE ================= -->
+  <section class="compare" id="compare" aria-labelledby="compare-h">
+    <div class="wrap reveal">
+      <h2 id="compare-h">Compare — Two Material Histories</h2>
+      <div class="controls">
+        <div class="field">
+          <label for="selA">Left object</label>
+          <select id="selA"></select>
+        </div>
+        <button class="swap" id="btnSwap" type="button" title="Swap the two objects">⇄ Swap</button>
+        <div class="field">
+          <label for="selB">Right object</label>
+          <select id="selB"></select>
+        </div>
+      </div>
+      <div class="cmp-grid" id="cmpGrid"></div>
+      <p class="cmp-read" id="cmpRead"></p>
+    </div>
+  </section>
+
+  <footer aria-label="Colophon">
+    <div class="wrap">
+      <p><b>Afterlight: Rooms Remember</b> — a fictional digital exhibition. All objects, provenances, conservation notes, and audio transcripts are invented for this presentation.</p>
+      <p>Audio excerpts are transcribed oral-history fragments; where playback is unavailable, the transcript is the authoritative record. This exhibition is fully keyboard-navigable — press <kbd style="font-family:ui-monospace,monospace">?</kbd> for controls.</p>
+    </div>
+  </footer>
+</main>
+
+<!-- ================= HELP DIALOG ================= -->
+<div class="help" id="help" role="dialog" aria-modal="true" aria-labelledby="help-h" hidden>
+  <div class="card">
+    <h2 id="help-h">Keyboard controls</h2>
+    <table>
+      <tr><td><kbd>]</kbd> / <kbd>[</kbd></td><td>Next / previous object</td></tr>
+      <tr><td><kbd>i</kbd></td><td>Jump to the object index</td></tr>
+      <tr><td><kbd>c</kbd></td><td>Jump to Compare mode</td></tr>
+      <tr><td><kbd>t</kbd></td><td>Back to the title sequence</td></tr>
+      <tr><td><kbd>b</kbd></td><td>Bookmark your current reading position</td></tr>
+      <tr><td><kbd>g</kbd></td><td>Go back to your bookmarked position</td></tr>
+      <tr><td><kbd>h</kbd></td><td>Toggle high-contrast mode</td></tr>
+      <tr><td><kbd>m</kbd></td><td>Toggle motion / animation</td></tr>
+      <tr><td><kbd>?</kbd></td><td>Open / close this help</td></tr>
+      <tr><td><kbd>Esc</kbd></td><td>Close help</td></tr>
+    </table>
+    <p style="font-size:.8rem;color:var(--ink-soft);margin:.9rem 0 0">Any jump (<kbd>i</kbd>, <kbd>c</kbd>, <kbd>t</kbd>, index links) auto-bookmarks where you were — press <kbd>g</kbd> to return to your prior reading position.</p>
+    <button class="close" id="helpClose">Close</button>
+  </div>
+</div>
+
+<script>
+(function(){
+  "use strict";
+
+  /* ---------------- DATA (invented, plausible) ---------------- */
+  var OBJECTS = [
+    {
+      id:"aurora", n:"01", year:"1928", c:"#b3541e",
+      title:"The Aurora Console", sub:"Cathedral-style radio receiver",
+      curator:"Built to sit where a hearth once did, the Aurora gathered a household around sound instead of fire. Its walnut cabinet was polished nightly; the two Bakelite dials wore pale where thumbs returned, evening after evening, to the same three stations. The object remembers a room organised around listening — chairs angled inward, a lamp left low.",
+      provenance:"Walnut veneer over quarter-sawn oak carcass; cast phenolic (Bakelite) dials; brass grille cloth; five-valve superheterodyne chassis. Cabinet finished in shellac; acquired from a Nebraska estate, one family, 1928–1994.",
+      status:{label:"Stable", note:"Cabinet stable; chassis inert and non-operational. Displayed unpowered to preserve original wiring."},
+      audio:{who:"Oral history — donor's granddaughter", tc:"02:14", text:"“She left it tuned to the same frequency for thirty years. The static was part of the house — we thought that hush was just how a room sounded at night.”"}
+    },
+    {
+      id:"solace", n:"02", year:"1959", c:"#1f6f6b",
+      title:"Solace Lounge Chair", sub:"Moulded shell easy chair",
+      curator:"A machine-age promise of rest, the Solace was moulded in a single fibreglass shell and cushioned in latex foam. It carried its owners' shape more honestly than any portrait: the seat compresses at one worn hollow, the left arm burnished by a reading hand. The object remembers repetition — the same body, the same hour, the same page.",
+      provenance:"Reinforced polyester–fibreglass shell; natural latex foam; hand-tufted wool upholstery in 'harvest' ochre; splayed steel-rod base with brass ferrules. Single-owner provenance, a Chicago apartment, 1959–2016.",
+      status:{label:"Fragile", note:"Latex foam oxidising and crumbling internally; upholstery light-faded. Handling restricted; foam not replaced to retain original wear map."},
+      audio:{who:"Oral history — original owner", tc:"00:47", text:"“You could hear the foam breathe when you sat down. Every visitor sank into the same worn place — the chair had already decided where a person belonged.”"}
+    },
+    {
+      id:"cathode", n:"03", year:"1984", c:"#b02a7a",
+      title:"Cathode Vanity", sub:"CRT-integrated dressing table",
+      curator:"At the height of the home-electronics decade, the Cathode Vanity replaced a mirror with a small cathode-ray tube and a smoked-acrylic surround. Its owner applied makeup in the tube's phosphor glow; the screen still holds a faint magnetic ghost of the last image displayed. The object remembers light as much as touch — a room lit blue-grey after dark.",
+      provenance:"Smoked cast-acrylic surround; chromed tubular-steel frame; 9-inch monochrome CRT; melamine work-surface scarred with cosmetic residue. Salvaged from a Los Angeles rental interior, prop-styled 1984, domestic use to 2003.",
+      status:{label:"Under treatment", note:"CRT vacuum intact but degassing; acrylic crazing along stress lines. Currently in conservation for surface stabilisation."},
+      audio:{who:"Oral history — building superintendent", tc:"01:39", text:"“She did her face in the glow of the tube every morning. The whole room smelled of warm dust and hairspray — you could tell the hour by that smell.”"}
+    },
+    {
+      id:"quietloom", n:"04", year:"2011", c:"#5c6b3d",
+      title:"Quiet Loom Partition", sub:"Acoustic felt room divider",
+      curator:"Not an object of use but of atmosphere, the Quiet Loom was hung to soften a shared apartment's echo. Pressed from recycled bottles into dense felt, it absorbed a decade of overlapping voices. The object remembers what rooms usually forget — noise itself — and the difference between a space that argues back and one that keeps its counsel.",
+      provenance:"100% recycled PET felt (approx. 60 reclaimed bottles per panel); powder-coated steel suspension track; wool-blend binding tape. Commissioned for a Rotterdam co-living flat, in situ 2011–2028.",
+      status:{label:"Monitored", note:"Felt structurally sound; embedded particulate and minor moth activity logged. Stored flat under humidity monitoring."},
+      audio:{who:"Oral history — former flatmate", tc:"00:58", text:"“After we hung it, the arguments stopped echoing. The room finally kept its own counsel — you could disagree without the walls repeating it back at you.”"}
+    },
+    {
+      id:"mnemosyne", n:"05", year:"2040", c:"#6a4fb5",
+      title:"Mnemosyne Wall", sub:"Bio-acoustic memory panel",
+      curator:"The exhibition's endpoint makes the thesis literal. A cultivated mycelium panel, seeded with acoustic-sensitive tissue, the Mnemosyne Wall records a room's quietest hour and re-emits it as faint light. It is the first object here that remembers on purpose — and the first the museum must keep alive. Where the others hold memory as damage, this one holds it as growth.",
+      provenance:"Living mycelium composite on a hemp-cellulose substrate; bio-luminescent bacterial culture; low-voltage nutrient lattice; oak frame. Grown-to-commission, Kyoto studio, 2040; accessioned as a living work.",
+      status:{label:"Active — living", note:"Living material under active horticultural care: nutrient, humidity, and light cycles maintained daily. Long-term display is a conservation experiment, not a settled state."},
+      audio:{who:"Artist statement — recorded", tc:"01:05", text:"“It plays back a room's quietest hour. Mine chose three in the morning — the sound of no one leaving. I didn't teach it that; the room did.”"}
+    }
+  ];
+
+  var $=function(s,r){return (r||document).querySelector(s);};
+  var live=$("#live");
+  function announce(msg){ live.textContent=""; setTimeout(function(){live.textContent=msg;},30); }
+
+  /* ---------------- RENDER: index ---------------- */
+  var idxList=$("#idxList");
+  var chapnav=$("#chapnav");
+  var compareLink=chapnav.querySelector('a[href="#compare"]');
+  OBJECTS.forEach(function(o){
+    var li=document.createElement("li");
+    li.innerHTML='<a href="#obj-'+o.id+'" style="--c:'+o.c+'">'+
+      '<span class="n">'+o.n+'</span>'+
+      '<span class="ti"><b><span class="dot"></span>'+o.title+'</b><span>'+o.sub+'</span></span>'+
+      '<span class="yr">'+o.year+'</span></a>';
+    idxList.appendChild(li);
+    // chapter nav link before Compare
+    var a=document.createElement("a");
+    a.href="#obj-"+o.id; a.textContent=o.year;
+    a.setAttribute("data-obj",o.id);
+    chapnav.insertBefore(a,compareLink);
+  });
+
+  /* ---------------- RENDER: object chapters ---------------- */
+  var host=$("#objects");
+  OBJECTS.forEach(function(o){
+    var sec=document.createElement("section");
+    sec.className="object"; sec.id="obj-"+o.id;
+    sec.style.setProperty("--c",o.c);
+    sec.setAttribute("aria-labelledby","h-"+o.id);
+    sec.setAttribute("tabindex","-1");
+    sec.innerHTML=
+      '<div class="wrap">'+
+        '<div class="head reveal">'+
+          '<div class="year" aria-hidden="true">'+o.year+'</div>'+
+          '<div class="meta"><span class="obj-n">Object '+o.n+' · '+o.year+'</span></div>'+
+          '<h3 id="h-'+o.id+'">'+o.title+'<span>'+o.sub+'</span></h3>'+
+        '</div>'+
+        '<div class="grid">'+
+          '<div class="curator reveal"><p class="drop">'+o.curator+'</p>'+
+            '<figure class="audio" style="margin-top:1.8rem">'+
+              '<div class="bar2">'+
+                '<button class="play" type="button" aria-pressed="false" '+
+                  'aria-label="Play audio excerpt for '+o.title+'">▶</button>'+
+                '<span class="cap"><b>Audio excerpt</b> · '+o.audio.who+' · '+o.audio.tc+'</span>'+
+              '</div>'+
+              '<blockquote>'+o.audio.text+'</blockquote>'+
+              '<p class="fallback" role="status">Transcript shown above.</p>'+
+              '<figcaption>Transcribed oral-history fragment. If audio cannot play, the transcript is the authoritative record.</figcaption>'+
+            '</figure>'+
+          '</div>'+
+          '<div class="facts reveal">'+
+            '<div><h4>Material provenance</h4><div class="val">'+o.provenance+'</div></div>'+
+            '<div><h4>Conservation status</h4>'+
+              '<div class="status"><span class="pip"></span>'+o.status.label+'</div>'+
+              '<div class="val" style="margin-top:.4rem;color:var(--ink-soft);font-size:.9rem">'+o.status.note+'</div>'+
+            '</div>'+
+          '</div>'+
+        '</div>'+
+      '</div>';
+    host.appendChild(sec);
+  });
+
+  /* ---------------- Audio fallback behaviour ---------------- */
+  host.addEventListener("click",function(e){
+    var btn=e.target.closest("button.play"); if(!btn) return;
+    var fig=btn.closest("figure.audio");
+    var fb=fig.querySelector(".fallback");
+    var pressed=btn.getAttribute("aria-pressed")==="true";
+    // No bundled audio source exists — always resolve to the graceful fallback.
+    if(!pressed){
+      btn.setAttribute("aria-pressed","true"); btn.textContent="ⓘ";
+      fb.textContent="Audio playback is unavailable in this exhibition build — the transcript above is the complete excerpt.";
+      fb.style.color="var(--ink)";
+      announce("Audio unavailable. The transcript is the authoritative record of this excerpt.");
+    }else{
+      btn.setAttribute("aria-pressed","false"); btn.textContent="▶";
+      fb.textContent="Transcript shown above."; fb.style.color="";
+    }
+  });
+
+  /* ---------------- Compare ---------------- */
+  var selA=$("#selA"), selB=$("#selB"), cmpGrid=$("#cmpGrid"), cmpRead=$("#cmpRead");
+  OBJECTS.forEach(function(o,i){
+    [selA,selB].forEach(function(sel){
+      var op=document.createElement("option");
+      op.value=String(i); op.textContent=o.year+" — "+o.title;
+      sel.appendChild(op);
+    });
+  });
+  selA.value="0"; selB.value=String(OBJECTS.length-1);
+
+  function panel(o){
+    return '<div class="panel" style="--c:'+o.c+'">'+
+      '<div class="p-year">'+o.year+'</div>'+
+      '<h3>'+o.title+'<span>'+o.sub+'</span></h3>'+
+      '<dl>'+
+        '<div><dt>Material provenance</dt><dd>'+o.provenance+'</dd></div>'+
+        '<div><dt>Conservation status</dt><dd><span class="status"><span class="pip"></span>'+o.status.label+'</span><br>'+o.status.note+'</dd></div>'+
+        '<div><dt>Excerpt</dt><dd style="font-family:Georgia,serif;font-style:italic">'+o.audio.text+'</dd></div>'+
+      '</dl>'+
+    '</div>';
+  }
+  function renderCompare(){
+    var a=OBJECTS[+selA.value], b=OBJECTS[+selB.value];
+    cmpGrid.innerHTML=panel(a)+panel(b);
+    var span=Math.abs(+a.year - +b.year);
+    cmpRead.innerHTML="Reading across "+span+" years: <b>"+a.title+"</b> ("+a.status.label.toLowerCase()+
+      ") and <b>"+b.title+"</b> ("+b.status.label.toLowerCase()+
+      ") show how a room's memory shifts from accidental damage toward deliberate, cultivated record. Change either selector to re-pair the histories.";
+    announce("Comparing "+a.title+" and "+b.title+".");
+  }
+  selA.addEventListener("change",renderCompare);
+  selB.addEventListener("change",renderCompare);
+  $("#btnSwap").addEventListener("click",function(){
+    var t=selA.value; selA.value=selB.value; selB.value=t; renderCompare();
+  });
+  renderCompare();
+
+  /* ---------------- Progress ---------------- */
+  var progress=$("#progress");
+  function updateProgress(){
+    var h=document.documentElement;
+    var max=h.scrollHeight - h.clientHeight;
+    var p= max>0 ? (h.scrollTop||window.pageYOffset)/max : 0;
+    p=Math.max(0,Math.min(1,p));
+    progress.style.width=(p*100).toFixed(1)+"%";
+    progress.setAttribute("aria-valuenow",Math.round(p*100));
+  }
+  window.addEventListener("scroll",updateProgress,{passive:true});
+  window.addEventListener("resize",updateProgress);
+  updateProgress();
+
+  /* ---------------- Reveal on scroll ---------------- */
+  var prefersReduced = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+  var motionOff=false;
+  function motionEnabled(){ return !motionOff && !prefersReduced && !document.documentElement.classList.contains("no-motion"); }
+
+  var io=null;
+  function setupReveal(){
+    var els=document.querySelectorAll(".reveal, .stagger");
+    if(!motionEnabled() || !("IntersectionObserver" in window)){
+      els.forEach(function(el){el.classList.add("in");});
+      return;
+    }
+    io=new IntersectionObserver(function(entries){
+      entries.forEach(function(en){
+        if(en.isIntersecting){ en.target.classList.add("in"); io.unobserve(en.target); }
+      });
+    },{rootMargin:"0px 0px -8% 0px",threshold:.12});
+    els.forEach(function(el){ io.observe(el); });
+  }
+  setupReveal();
+
+  /* ---------------- Active chapter in nav ---------------- */
+  var navLinks=Array.prototype.slice.call(chapnav.querySelectorAll("a"));
+  var sections=navLinks.map(function(a){return document.querySelector(a.getAttribute("href"));});
+  function updateActive(){
+    var y=(window.pageYOffset||document.documentElement.scrollTop)+120;
+    var idx=0;
+    sections.forEach(function(s,i){ if(s && s.offsetTop<=y) idx=i; });
+    navLinks.forEach(function(a,i){ a.setAttribute("aria-current", i===idx?"true":"false"); });
+  }
+  window.addEventListener("scroll",updateActive,{passive:true});
+  updateActive();
+
+  /* ---------------- Bookmark / reading position ---------------- */
+  var bookmark=null;
+  function saveBookmark(silent){
+    bookmark=window.pageYOffset||document.documentElement.scrollTop;
+    if(!silent) announce("Position bookmarked. Press g to return here.");
+  }
+  function gotoBookmark(){
+    if(bookmark==null){ announce("No bookmark set yet. Press b to bookmark your position."); return; }
+    window.scrollTo({top:bookmark, behavior: motionEnabled()?"smooth":"auto"});
+    announce("Returned to your bookmarked reading position.");
+  }
+  function jumpTo(hash,label){
+    saveBookmark(true); // auto-bookmark prior position so 'g' returns
+    var el=document.querySelector(hash);
+    if(el){
+      el.scrollIntoView({behavior: motionEnabled()?"smooth":"auto", block:"start"});
+      if(el.hasAttribute("tabindex")){ el.focus({preventScroll:true}); }
+      announce(label+" (press g to return to where you were).");
+    }
+  }
+
+  /* current object index for [ ] navigation */
+  function currentObjectIndex(){
+    var y=(window.pageYOffset||document.documentElement.scrollTop)+140;
+    var idx=-1;
+    OBJECTS.forEach(function(o,i){
+      var el=document.getElementById("obj-"+o.id);
+      if(el && el.offsetTop<=y) idx=i;
+    });
+    return idx;
+  }
+  function gotoObject(i){
+    i=Math.max(0,Math.min(OBJECTS.length-1,i));
+    var o=OBJECTS[i];
+    jumpTo("#obj-"+o.id, o.year+" · "+o.title);
+  }
+
+  /* ---------------- Toggles ---------------- */
+  var root=document.documentElement;
+  var btnContrast=$("#btnContrast"), btnMotion=$("#btnMotion");
+  function toggleContrast(){
+    var on=document.body.classList.toggle("hc");
+    btnContrast.setAttribute("aria-pressed",on?"true":"false");
+    announce("High contrast "+(on?"on":"off")+".");
+  }
+  function toggleMotion(){
+    motionOff=!motionOff;
+    root.classList.toggle("no-motion",motionOff);
+    btnMotion.setAttribute("aria-pressed",motionOff?"true":"false");
+    if(motionOff){ document.querySelectorAll(".reveal,.stagger").forEach(function(el){el.classList.add("in");}); }
+    announce("Animation "+(motionOff?"reduced":"enabled")+".");
+  }
+  btnContrast.addEventListener("click",toggleContrast);
+  btnMotion.addEventListener("click",toggleMotion);
+  if(prefersReduced){ motionOff=true; root.classList.add("no-motion"); btnMotion.setAttribute("aria-pressed","true"); }
+
+  /* ---------------- Help dialog ---------------- */
+  var help=$("#help"), helpBtn=$("#btnHelp"), helpClose=$("#helpClose");
+  var lastFocus=null;
+  function openHelp(){
+    lastFocus=document.activeElement;
+    help.hidden=false; helpClose.focus();
+  }
+  function closeHelp(){
+    help.hidden=true; if(lastFocus&&lastFocus.focus) lastFocus.focus();
+  }
+  helpBtn.addEventListener("click",openHelp);
+  helpClose.addEventListener("click",closeHelp);
+  help.addEventListener("click",function(e){ if(e.target===help) closeHelp(); });
+  help.addEventListener("keydown",function(e){
+    if(e.key==="Escape"){ closeHelp(); return; }
+    if(e.key==="Tab"){ // simple focus trap
+      var f=help.querySelectorAll("button, a[href]");
+      var first=f[0], last=f[f.length-1];
+      if(e.shiftKey && document.activeElement===first){ e.preventDefault(); last.focus(); }
+      else if(!e.shiftKey && document.activeElement===last){ e.preventDefault(); first.focus(); }
+    }
+  });
+
+  /* ---------------- Global keyboard shortcuts ---------------- */
+  document.addEventListener("keydown",function(e){
+    if(e.metaKey||e.ctrlKey||e.altKey) return;
+    var t=e.target;
+    var typing = t && (t.tagName==="SELECT"||t.tagName==="INPUT"||t.tagName==="TEXTAREA"||t.isContentEditable);
+    if(!help.hidden){ return; } // help handles its own keys
+    if(typing) return;
+    switch(e.key){
+      case "?": e.preventDefault(); openHelp(); break;
+      case "h": toggleContrast(); break;
+      case "m": toggleMotion(); break;
+      case "i": e.preventDefault(); jumpTo("#index","Object index"); break;
+      case "c": e.preventDefault(); jumpTo("#compare","Compare mode"); break;
+      case "t": e.preventDefault(); jumpTo("#top","Title sequence"); break;
+      case "b": e.preventDefault(); saveBookmark(false); break;
+      case "g": e.preventDefault(); gotoBookmark(); break;
+      case "]": case ".": e.preventDefault(); gotoObject(currentObjectIndex()+1); break;
+      case "[": case ",": e.preventDefault(); gotoObject((currentObjectIndex()<0?0:currentObjectIndex())-1); break;
+    }
+  });
+
+  /* index / nav links auto-bookmark prior position */
+  document.addEventListener("click",function(e){
+    var a=e.target.closest('a[href^="#"]'); if(!a) return;
+    // let the browser handle the hash jump, but stash where we were first
+    saveBookmark(true);
+  });
+
+})();
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/96c1603e5f4a/qwen25-7b.html b/data/artifacts/96c1603e5f4a/qwen25-7b.html
new file mode 100644
index 0000000..9f42064
--- /dev/null
+++ b/data/artifacts/96c1603e5f4a/qwen25-7b.html
@@ -0,0 +1,128 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Afterlight: Rooms Remember</title>
+    <style>
+        body { font-family: Arial, sans-serif; }
+        .container { width: 390px; margin: auto; padding: 20px; background-color: #f4f4f9; }
+        h1, p, ul, ol { margin-top: 0; }
+        nav { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
+        .nav-btn { cursor: pointer; padding: 5px 10px; background-color: #333; color: white; border: none; border-radius: 4px; transition: background-color 0.3s; }
+        .nav-btn:hover { background-color: #555; }
+        .title-sequence { display: flex; justify-content: center; align-items: center; height: 80px; background-color: #2c3e50; color: white; font-size: 1.5em; }
+        .index-list, .object-info { list-style-type: none; padding: 0; margin: 0; }
+        .object-title { font-size: 1.2em; margin-bottom: 10px; }
+        .curator-note, .material-provenance, .conservation-status { margin-top: 5px; }
+        audio { display: none; }
+        .compare-mode { display: flex; justify-content: space-between; margin-top: 20px; }
+        .compare-object { width: calc(50% - 10px); padding: 10px; border: 1px solid #ddd; box-sizing: border-box; overflow-y: auto; height: 200px; }
+        .reading-progress { position: fixed; bottom: 10px; left: 10px; background-color: rgba(0, 0, 0, 0.5); color: white; padding: 10px; border-radius: 4px; font-size: 0.9em; }
+        .reading-progress::before { content: 'Reading Progress:'; margin-right: 10px; }
+        .high-contrast-toggle { display: block; margin-top: 10px; background-color: #2c3e50; color: white; padding: 5px 10px; border: none; cursor: pointer; transition: background-color 0.3s; }
+        .high-contrast-toggle:hover { background-color: #34495e; }
+    </style>
+</head>
+<body>
+    <div class="container">
+        <!-- Title Sequence -->
+        <div class="title-sequence">Afterlight: Rooms Remember</div>
+
+        <!-- Navigation Buttons -->
+        <nav>
+            <button id="prev-btn" class="nav-btn">Previous</button>
+            <button id="next-btn" class="nav-btn">Next</button>
+            <button id="compare-btn" class="nav-btn">Compare</button>
+            <button id="toggle-contrast" class="high-contrast-toggle">High Contrast</button>
+        </nav>
+
+        <!-- Object Index -->
+        <ul class="index-list">
+            <li><a href="#object1">Object 1 (1928)</a></li>
+            <li><a href="#object2">Object 2 (1960)</a></li>
+            <li><a href="#object3">Object 3 (2005)</a></li>
+            <li><a href="#object4">Object 4 (2030)</a></li>
+            <li><a href="#object5">Object 5 (2040)</a></li>
+        </ul>
+
+        <!-- Object Information -->
+        <div id="object1" class="object-info">
+            <h2 class="object-title">Object 1: The Dusk Lamp (1928)</h2>
+            <p><span class="curator-note">Curator Note:</span> A functional design masterpiece, reflecting the optimism of early 20th-century aesthetics.</p>
+            <ul>
+                <li><span class="material-provenance">Material Provenance:</span> Brass, hand-blown glass</li>
+                <li><span class="conservation-status">Conservation Status:</span> Excellent condition, preserved in the Museum of Modern Design.</li>
+            </ul>
+        </div>
+
+        <!-- Compare Mode -->
+        <div id="compare-mode" class="compare-mode">
+            <div class="compare-object" id="object1-obj1"><h2 class="object-title">Object 1: The Dusk Lamp (1928)</h2></div>
+            <div class="compare-object" id="object1-obj2"><audio controls><source src="#"></source>Your browser does not support the audio element.</audio></div>
+        </div>
+
+        <!-- Audio Transcript -->
+        <p><span class="material-provenance">Audio Excerpt:</span> "Brass and glass, a beacon of hope in the twilight of the Roaring Twenties."</p>
+
+        <!-- Reading Progress Indicator -->
+        <div class="reading-progress" id="progress-indicator"></div>
+    </div>
+
+    <script>
+        document.addEventListener('DOMContentLoaded', () => {
+            const navBtns = document.querySelectorAll('.nav-btn');
+            let currentIndex = 1;
+            const objects = [
+                { id: 'object1', title: 'The Dusk Lamp (1928)', content: '<h2 class="object-title">Object 1: The Dusk Lamp (1928)</h2><p><span class="curator-note">Curator Note:</span> A functional design masterpiece, reflecting the optimism of early 20th-century aesthetics.</p><ul><li><span class="material-provenance">Material Provenance:</span> Brass, hand-blown glass</li><li><span class="conservation-status">Conservation Status:</span> Excellent condition, preserved in the Museum of Modern Design.</li></ul>' },
+                { id: 'object2', title: 'The Echo Chair (1960)', content: '<h2 class="object-title">Object 2: The Echo Chair (1960)</h2><p><span class="curator-note">Curator Note:</span> An iconic design, embodying the mid-century modern aesthetic.</p><ul><li><span class="material-provenance">Material Provenance:</span> Fiberglass and foam</li><li><span class="conservation-status">Conservation Status:</span> Restored in 2019.</li></ul>' },
+                { id: 'object3', title: 'The Lumina Desk (2005)', content: '<h2 class="object-title">Object 3: The Lumina Desk (2005)</h2><p><span class="curator-note">Curator Note:</span> A breakthrough in sustainable design, blending technology and nature.</p><ul><li><span class="material-provenance">Material Provenance:</span> Recycled aluminum and bamboo</li><li><span class="conservation-status">Conservation Status:</span> Good condition after minor repairs in 2018.</li></ul>' },
+                { id: 'object4', title: 'The Aether Table (2030)', content: '<h2 class="object-title">Object 4: The Aether Table (2030)</h2><p><span class="curator-note">Curator Note:</span> An experimental design, pushing the boundaries of form and function.</p><ul><li><span class="material-provenance">Material Provenance:</span> Carbon fiber and smart polymers</li><li><span class="conservation-status">Conservation Status:</span> Requires ongoing maintenance.</li></ul>' },
+                { id: 'object5', title: 'The Horizon Chair (2040)', content: '<h2 class="object-title">Object 5: The Horizon Chair (2040)</h2><p><span class="curator-note">Curator Note:</span> A testament to the enduring legacy of design.</p><ul><li><span class="material-provenance">Material Provenance:</span> AI-optimized composite materials</li><li><span class="conservation-status">Conservation Status:</span> Unavailable for public viewing due to technological obsolescence.</li></ul>' }
+            ];
+
+            function showObject(index) {
+                document.querySelector('.object-info').innerHTML = '';
+                if (index < 1 || index > objects.length) return;
+                const objectContent = objects[index - 1].content;
+                document.querySelector('.object-info').innerHTML = objectContent;
+                currentIndex = index;
+
+                // Update navigation buttons
+                navBtns.forEach((btn, i) => {
+                    btn.addEventListener('click', () => {
+                        if (i === 0 && currentIndex > 1 || i === 1 && currentIndex < objects.length || i === 2) {
+                            showObject(currentIndex + i - (currentIndex > 1 ? 1 : 0));
+                        }
+                    });
+                });
+
+                // Update compare mode
+                const compareMode = document.getElementById('compare-mode');
+                if (currentIndex > 1) {
+                    compareMode.style.display = 'flex';
+                    compareMode.querySelector('.compare-object').id = `object${currentIndex - 1}-obj1`;
+                    compareMode.querySelector(`.compare-object`).innerHTML = objects[currentIndex - 2].content;
+                } else {
+                    compareMode.style.display = 'none';
+                }
+            }
+
+            showObject(currentIndex);
+
+            // High Contrast Toggle
+            document.getElementById('toggle-contrast').addEventListener('click', () => {
+                if (document.body.classList.toggle('high-contrast')) {
+                    document.getElementById('progress-indicator').textContent = 'High contrast activated.';
+                } else {
+                    document.getElementById('progress-indicator').textContent = 'High contrast deactivated.';
+                }
+            });
+
+            // Reading Progress Indicator
+            const progressIndicator = document.getElementById('progress-indicator');
+            progressIndicator.textContent = `Current Object: ${currentIndex} of ${objects.length}`;
+        });
+    </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/data/artifacts/96c1603e5f4a/qwen3-14b.html b/data/artifacts/96c1603e5f4a/qwen3-14b.html
new file mode 100644
index 0000000..a076144
--- /dev/null
+++ b/data/artifacts/96c1603e5f4a/qwen3-14b.html
@@ -0,0 +1,713 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>Afterlight: Rooms Remember</title>
+  <style>
+    :root {
+      --primary: #1a1a1a;
+      --secondary: #e0e0e0;
+      --accent: #ff6f61;
+      --text: #ffffff;
+      --bg: #000000;
+      --highlight: #444444;
+      --font: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+      --transition: 0.3s ease-in-out;
+    }
+
+    * {
+      box-sizing: border-box;
+      margin: 0;
+      padding: 0;
+    }
+
+    body {
+      background-color: var(--bg);
+      color: var(--text);
+      font-family: var(--font);
+      overflow-x: hidden;
+      scroll-behavior: smooth;
+      height: 100vh;
+      width: 100%;
+    }
+
+    .title-sequence {
+      position: fixed;
+      top: 0; left: 0; right: 0; bottom: 0;
+      background: linear-gradient(135deg, #0f0f0f, #1a1a1a, #0f0f0f);
+      display: flex;
+      flex-direction: column;
+      justify-content: center;
+      align-items: center;
+      text-align: center;
+      z-index: 100;
+      pointer-events: none;
+      transition: opacity var(--transition);
+    }
+
+    .title-sequence h1 {
+      font-size: 4rem;
+      margin-bottom: 2rem;
+      color: var(--accent);
+      text-shadow: 1px 1px 4px #000000;
+    }
+
+    .title-sequence p {
+      font-size: 1.2rem;
+      opacity: 0.8;
+      max-width: 700px;
+      margin: 0 auto;
+    }
+
+    .nav-bar {
+      position: fixed;
+      top: 0;
+      left: 0;
+      right: 0;
+      background: rgba(0, 0, 0, 0.9);
+      padding: 1rem 2rem;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      z-index: 100;
+    }
+
+    .nav-bar h2 {
+      font-size: 1.2rem;
+      font-weight: bold;
+    }
+
+    .controls {
+      display: flex;
+      align-items: center;
+      gap: 1rem;
+    }
+
+    .controls button {
+      background: none;
+      border: none;
+      color: var(--accent);
+      font-size: 1rem;
+      cursor: pointer;
+      padding: 0.5rem 0.75rem;
+      border-radius: 4px;
+      transition: background var(--transition);
+    }
+
+    .controls button:hover {
+      background: rgba(255, 111, 97, 0.15);
+    }
+
+    .progress {
+      position: fixed;
+      top: 60px;
+      left: 0;
+      right: 0;
+      height: 4px;
+      background: #333;
+      overflow: hidden;
+      pointer-events: none;
+      z-index: 90;
+    }
+
+    .progress-fill {
+      height: 100%;
+      background: var(--accent);
+      width: 0%;
+      transition: width var(--transition);
+    }
+
+    .container {
+      position: relative;
+      padding-top: 100px;
+      display: flex;
+      flex-direction: column;
+      height: 100vh;
+      overflow-y: auto;
+    }
+
+    .object {
+      display: flex;
+      flex-direction: column;
+      padding: 3rem 2rem;
+      min-height: 100vh;
+      position: relative;
+      scroll-margin-top: 100px;
+    }
+
+    .object-header {
+      font-size: 2rem;
+      margin-bottom: 1rem;
+    }
+
+    .object-details {
+      display: flex;
+      flex-direction: column;
+      gap: 1rem;
+      margin-bottom: 2rem;
+    }
+
+    .object-details p {
+      font-size: 1rem;
+      line-height: 1.5;
+    }
+
+    .object-audio {
+      display: flex;
+      align-items: center;
+      gap: 1rem;
+      margin-bottom: 1rem;
+    }
+
+    .object-audio audio {
+      width: 100%;
+      max-width: 300px;
+      height: auto;
+    }
+
+    .object-audio p {
+      font-size: 0.9rem;
+      color: #aaa;
+    }
+
+    .index {
+      position: fixed;
+      top: 100px;
+      right: 0;
+      width: 240px;
+      height: calc(100vh - 100px);
+      overflow-y: auto;
+      background: var(--highlight);
+      padding: 1rem;
+      border-left: 2px solid var(--accent);
+      font-size: 0.95rem;
+    }
+
+    .index h3 {
+      font-size: 1.2rem;
+      margin-bottom: 0.5rem;
+    }
+
+    .index a {
+      color: var(--text);
+      text-decoration: none;
+      display: block;
+      padding: 0.4rem 0;
+      transition: color var(--transition);
+    }
+
+    .index a:hover {
+      color: var(--accent);
+    }
+
+    .compare {
+      position: fixed;
+      top: 100px;
+      left: 0;
+      right: 0;
+      bottom: 0;
+      background: rgba(0, 0, 0, 0.9);
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      z-index: 200;
+      pointer-events: none;
+      opacity: 0;
+      transition: opacity var(--transition);
+    }
+
+    .compare.active {
+      pointer-events: auto;
+      opacity: 1;
+    }
+
+    .compare-container {
+      display: flex;
+      gap: 2rem;
+      max-width: 1000px;
+      width: 90vw;
+      height: 80vh;
+      overflow: hidden;
+    }
+
+    .compare-object {
+      flex: 1;
+      display: flex;
+      flex-direction: column;
+      gap: 1rem;
+    }
+
+    .compare-object h3 {
+      font-size: 1.4rem;
+      margin-bottom: 0.5rem;
+    }
+
+    .compare-object .object-details p {
+      font-size: 1rem;
+      line-height: 1.4;
+    }
+
+    .compare-object .object-audio {
+      display: flex;
+      align-items: center;
+      gap: 1rem;
+      margin-bottom: 1rem;
+    }
+
+    .compare-object .object-audio audio {
+      width: 100%;
+      max-width: 100%;
+      height: auto;
+    }
+
+    .compare-object .object-audio p {
+      font-size: 0.85rem;
+      color: #aaa;
+    }
+
+    .compare-close {
+      position: absolute;
+      top: 1rem;
+      right: 1rem;
+      background: rgba(255, 255, 255, 0.1);
+      border: none;
+      color: var(--text);
+      padding: 0.5rem 1rem;
+      border-radius: 4px;
+      cursor: pointer;
+      font-size: 1rem;
+      transition: background var(--transition);
+    }
+
+    .compare-close:hover {
+      background: rgba(255, 255, 255, 0.2);
+    }
+
+    @media (prefers-reduced-motion: no-preference) {
+      .title-sequence {
+        animation: fadeOut 4s ease-in-out forwards;
+      }
+    }
+
+    @keyframes fadeOut {
+      0% {
+        opacity: 1;
+      }
+      100% {
+        opacity: 0;
+      }
+    }
+
+    @media (max-width: 600px) {
+      .title-sequence h1 {
+        font-size: 2.5rem;
+      }
+
+      .title-sequence p {
+        font-size: 1rem;
+        max-width: 100%;
+      }
+
+      .nav-bar h2 {
+        font-size: 1rem;
+      }
+
+      .controls button {
+        font-size: 0.9rem;
+      }
+
+      .index {
+        width: 100%;
+        height: auto;
+        position: relative;
+        top: 0;
+        right: 0;
+        padding: 0.5rem;
+        border: none;
+        border-top: 2px solid var(--accent);
+      }
+
+      .progress {
+        top: 60px;
+      }
+
+      .object-header {
+        font-size: 1.5rem;
+      }
+
+      .index a {
+        font-size: 0.9rem;
+      }
+
+      .compare-container {
+        flex-direction: column;
+        gap: 1rem;
+      }
+
+      .compare-object {
+        flex: 1 1 100%;
+      }
+
+      .compare-object h3 {
+        font-size: 1.2rem;
+      }
+
+      .compare-object .object-details p {
+        font-size: 0.95rem;
+      }
+
+      .compare-object .object-audio audio {
+        max-width: 100%;
+      }
+
+      .compare-object .object-audio p {
+        font-size: 0.8rem;
+      }
+    }
+
+    .no-media {
+      color: #ccc;
+      font-style: italic;
+    }
+
+    .high-contrast {
+      background: #ffffff;
+      color: #000000;
+    }
+
+    .high-contrast .progress-fill {
+      background: #000000;
+    }
+
+    .high-contrast .nav-bar {
+      background: #ffffff;
+    }
+
+    .high-contrast .nav-bar h2 {
+      color: #000000;
+    }
+
+    .high-contrast .controls button {
+      color: #000000;
+    }
+
+    .high-contrast .controls button:hover {
+      background: rgba(0, 0, 0, 0.15);
+    }
+
+    .high-contrast .index {
+      background: #ffffff;
+      border-left: 2px solid #000000;
+    }
+
+    .high-contrast .index a {
+      color: #000000;
+    }
+
+    .high-contrast .index a:hover {
+      color: #000000;
+    }
+
+    .high-contrast .compare {
+      background: #ffffff;
+    }
+
+    .high-contrast .compare-close {
+      background: rgba(0, 0, 0, 0.1);
+      color: #000000;
+    }
+
+    .high-contrast .compare-close:hover {
+      background: rgba(0, 0, 0, 0.2);
+    }
+
+    .high-contrast .object {
+      background: #ffffff;
+    }
+
+    .high-contrast .object-header {
+      color: #000000;
+    }
+
+    .high-contrast .object-details p {
+      color: #000000;
+    }
+
+    .high-contrast .object-audio p {
+      color: #000000;
+    }
+
+    .high-contrast .no-media {
+      color: #333;
+    }
+  </style>
+</head>
+<body class="high-contrast">
+  <div class="title-sequence">
+    <h1>Afterlight: Rooms Remember</h1>
+    <p>A journey through memory and materiality, 1928–2040.</p>
+  </div>
+
+  <div class="nav-bar">
+    <h2>Afterlight</h2>
+    <div class="controls">
+      <button id="toggleHighContrast">Toggle High Contrast</button>
+      <button id="openIndex">Index</button>
+      <button id="openCompare">Compare</button>
+    </div>
+  </div>
+
+  <div class="progress">
+    <div class="progress-fill" id="progressFill"></div>
+  </div>
+
+  <div class="container">
+    <div class="object" id="obj1">
+      <div class="object-header">1. The Echo Bench (1928)</div>
+      <div class="object-details">
+        <p><strong>Curator Note:</strong> A bench in a forgotten schoolroom, carved from salvaged wood of a burned library. It bears the initials of students who never returned.</p>
+        <p><strong>Material Provenance:</strong> Oak, poplar, and ash from the 1922 fire at Saint Anselm’s Library.</p>
+        <p><strong>Conservation Status:</strong> Stable, with visible burn marks and faint engraving.</p>
+      </div>
+      <div class="object-audio">
+        <audio controls class="no-media" aria-label="Audio excerpt: The Echo Bench">
+          <source src="" type="audio/mpeg" />
+          Your browser does not support the audio element.
+        </audio>
+        <p class="no-media">"This bench is a monument to silence—of books lost, of voices never heard." – Curator, 2024.</p>
+      </div>
+    </div>
+
+    <div class="object" id="obj2">
+      <div class="object-header">2. The Data Veil (2003)</div>
+      <div class="object-details">
+        <p><strong>Curator Note:</strong> A translucent fabric woven with fiber-optic threads, displaying real-time data from the 2003 blackout.</p>
+        <p><strong>Material Provenance:</strong> Polyester, optical fibers from decommissioned servers in the Mid-Atlantic Grid.</p>
+        <p><strong>Conservation Status:</strong> Fragile, with fading fiber-optic output and minor fraying.</p>
+      </div>
+      <div class="object-audio">
+        <audio controls class="no-media" aria-label="Audio excerpt: The Data Veil">
+          <source src="" type="audio/mpeg" />
+          Your browser does not support the audio element.
+        </audio>
+        <p class="no-media">"In its flicker, we see the heartbeat of a grid that once failed—and rose again." – Curator, 2024.</p>
+      </div>
+    </div>
+
+    <div class="object" id="obj3">
+      <div class="object-header">3. The Memory Lens (2015)</div>
+      <div class="object-details">
+        <p><strong>Curator Note:</strong> A camera lens modified to capture only light reflected from human memory—impossible to focus, yet hauntingly familiar.</p>
+        <p><strong>Material Provenance:</strong> Glass, aluminum, and custom electronics from a defunct memory research lab.</p>
+        <p><strong>Conservation Status:</strong> Functional, with intermittent sensor failures.</p>
+      </div>
+      <div class="object-audio">
+        <audio controls class="no-media" aria-label="Audio excerpt: The Memory Lens">
+          <source src="" type="audio/mpeg" />
+          Your browser does not support the audio element.
+        </audio>
+        <p class="no-media">"It sees what we forget—but never what we remember." – Curator, 2024.</p>
+      </div>
+    </div>
+
+    <div class="object" id="obj4">
+      <div class="object-header">4. The Last Breath Clock (2022)</div>
+      <div class="object-details">
+        <p><strong>Curator Note:</strong> A clock that only ticks once per hour, powered by the breath of a living donor—now extinct.</p>
+        <p><strong>Material Provenance:</strong> Brass, human lung tissue (extinct), and carbon fiber.</p>
+        <p><strong>Conservation Status:</strong> Non-functional; last tick recorded in 2022.</p>
+      </div>
+      <div class="object-audio">
+        <audio controls class="no-media" aria-label="Audio excerpt: The Last Breath Clock">
+          <source src="" type="audio/mpeg" />
+          Your browser does not support the audio element.
+        </audio>
+        <p class="no-media">"It counts time in the absence of life—and in the silence of the final breath." – Curator, 2024.</p>
+      </div>
+    </div>
+
+    <div class="object" id="obj5">
+      <div class="object-header">5. The Future Room (2040)</div>
+      <div class="object-details">
+        <p><strong>Curator Note:</strong> An empty room with walls that shift color based on the viewer’s emotional state—built by an AI that no longer exists.</p>
+        <p><strong>Material Provenance:</strong> Unknown; material is self-replicating and untraceable.</p>
+        <p><strong>Conservation Status:</strong> Unstable; the AI has since gone rogue.</p>
+      </div>
+      <div class="object-audio">
+        <audio controls class="no-media" aria-label="Audio excerpt: The Future Room">
+          <source src="" type="audio/mpeg" />
+          Your browser does not support the audio element.
+        </audio>
+        <p class="no-media">"What do rooms remember when their walls are alive?" – Curator, 2024.</p>
+      </div>
+    </div>
+  </div>
+
+  <div class="index" id="index">
+    <h3>Object Index</h3>
+    <a href="#obj1" aria-label="Go to object 1">1. The Echo Bench (1928)</a>
+    <a href="#obj2" aria-label="Go to object 2">2. The Data Veil (2003)</a>
+    <a href="#obj3" aria-label="Go to object 3">3. The Memory Lens (2015)</a>
+    <a href="#obj4" aria-label="Go to object 4">4. The Last Breath Clock (2022)</a>
+    <a href="#obj5" aria-label="Go to object 5">5. The Future Room (2040)</a>
+  </div>
+
+  <div class="compare" id="compare">
+    <div class="compare-container" id="compareContainer">
+      <div class="compare-object" id="compareLeft">
+        <h3 id="compareLeftTitle"></h3>
+        <div class="object-details" id="compareLeftDetails"></div>
+        <div class="object-audio">
+          <audio controls class="no-media" id="compareLeftAudio"></audio>
+          <p class="no-media" id="compareLeftTranscript"></p>
+        </div>
+      </div>
+      <div class="compare-object" id="compareRight">
+        <h3 id="compareRightTitle"></h3>
+        <div class="object-details" id="compareRightDetails"></div>
+        <div class="object-audio">
+          <audio controls class="no-media" id="compareRightAudio"></audio>
+          <p class="no-media" id="compareRightTranscript"></p>
+      </div>
+    </div>
+    <button class="compare-close" id="closeCompare">Close</button>
+  </div>
+
+  <script>
+    const objects = [
+      {
+        id: 'obj1',
+        title: '1. The Echo Bench (1928)',
+        curatorNote: 'A bench in a forgotten schoolroom, carved from salvaged wood of a burned library. It bears the initials of students who never returned.',
+        materialProvenance: 'Oak, poplar, and ash from the 1922 fire at Saint Anselm’s Library.',
+        conservationStatus: 'Stable, with visible burn marks and faint engraving.',
+        audio: 'no-media',
+        transcript: '"This bench is a monument to silence—of books lost, of voices never heard." – Curator, 2024.'
+      },
+      {
+        id: 'obj2',
+        title: '2. The Data Veil (2003)',
+        curatorNote: 'A translucent fabric woven with fiber-optic threads, displaying real-time data from the 2003 blackout.',
+        materialProvenance: 'Polyester, optical fibers from decommissioned servers in the Mid-Atlantic Grid.',
+        conservationStatus: 'Fragile, with fading fiber-optic output and minor fraying.',
+        audio: 'no-media',
+        transcript: '"In its flicker, we see the heartbeat of a grid that once failed—and rose again." – Curator, 2024.'
+      },
+      {
+        id: 'obj3',
+        title: '3. The Memory Lens (2015)',
+        curatorNote: 'A camera lens modified to capture only light reflected from human memory—impossible to focus, yet hauntingly familiar.',
+        materialProvenance: 'Glass, aluminum, and custom electronics from a defunct memory research lab.',
+        conservationStatus: 'Functional, with intermittent sensor failures.',
+        audio: 'no-media',
+        transcript: '"It sees what we forget—but never what we remember." – Curator, 2024.'
+      },
+      {
+        id: 'obj4',
+        title: '4. The Last Breath Clock (2022)',
+        curatorNote: 'A clock that only ticks once per hour, powered by the breath of a living donor—now extinct.',
+        materialProvenance: 'Brass, human lung tissue (extinct), and carbon fiber.',
+        conservationStatus: 'Non-functional; last tick recorded in 2022.',
+        audio: 'no-media',
+        transcript: '"It counts time in the absence of life—and in the silence of the final breath." – Curator, 2024.'
+      },
+      {
+        id: 'obj5',
+        title: '5. The Future Room (2040)',
+        curatorNote: 'An empty room with walls that shift color based on the viewer’s emotional state—built by an AI that no longer exists.',
+        materialProvenance: 'Unknown; material is self-replicating and untraceable.',
+        conservationStatus: 'Unstable; the AI has since gone rogue.',
+        audio: 'no-media',
+        transcript: '"What do rooms remember when their walls are alive?" – Curator, 2024.'
+      }
+    ];
+
+    const progressFill = document.getElementById('progressFill');
+    const container = document.querySelector('.container');
+    const compare = document.getElementById('compare');
+    const compareContainer = document.getElementById('compareContainer');
+    const compareLeft = document.getElementById('compareLeft');
+    const compareRight = document.getElementById('compareRight');
+    const closeCompare = document.getElementById('closeCompare');
+    const toggleHighContrast = document.getElementById('toggleHighContrast');
+    const openIndex = document.getElementById('openIndex');
+    const openCompare = document.getElementById('openCompare');
+
+    let currentObject = null;
+
+    document.querySelectorAll('.object').forEach(obj => {
+      const id = obj.id;
+      const index = objects.findIndex(o => o.id === id);
+      const scrollPos = obj.offsetTop;
+
+      window.addEventListener('scroll', () => {
+        const scrollTop = window.scrollY;
+        const scrollPercentage = (scrollTop / (container.scrollHeight - window.innerHeight)) * 100;
+        progressFill.style.width = scrollPercentage + '%';
+      });
+
+      obj.addEventListener('click', () => {
+        if (!currentObject) {
+          currentObject = index;
+          document.querySelector(`#${id} .object-header`).focus();
+        }
+      });
+    });
+
+    openIndex.addEventListener('click', () => {
+      document.querySelector('.index').scrollIntoView({ behavior: 'smooth' });
+    });
+
+    openCompare.addEventListener('click', () => {
+      const leftIndex = currentObject;
+      const rightIndex = (leftIndex + 1) % objects.length;
+      renderCompare(leftIndex, rightIndex);
+      compare.classList.add('active');
+    });
+
+    closeCompare.addEventListener('click', () => {
+      compare.classList.remove('active');
+    });
+
+    function renderCompare(a, b) {
+      const objA = objects[a];
+      const objB = objects[b];
+
+      document.getElementById('compareLeftTitle').textContent = objA.title;
+      document.getElementById('compareLeftDetails').innerHTML = `
+        <p><strong>Curator Note:</strong> ${objA.curatorNote}</p>
+        <p><strong>Material Provenance:</strong> ${objA.materialProvenance}</p>
+        <p><strong>Conservation Status:</strong> ${objA.conservationStatus}</p>
+      `;
+      document.getElementById('compareLeftTranscript').textContent = objA.transcript;
+
+      document.getElementById('compareRightTitle').textContent = objB.title;
+      document.getElementById('compareRightDetails').innerHTML = `
+        <p><strong>Curator Note:</strong> ${objB.curatorNote}</p>
+        <p><strong>Material Provenance:</strong> ${objB.materialProvenance}</p>
+        <p><strong>Conservation Status:</strong> ${objB.conservationStatus}</p>
+      `;
+      document.getElementById('compareRightTranscript').textContent = objB.transcript;
+    }
+
+    toggleHighContrast.addEventListener('click', () => {
+      document.body.classList.toggle('high-contrast');
+    });
+
+    // Keyboard navigation
+    document.addEventListener('keydown', (e) => {
+      const objects = document.querySelectorAll('.object');
+      const currentIndex = Array.from(objects).findIndex(obj => obj.contains(document.activeElement));
+
+      if (e.key === 'ArrowDown' && currentIndex < objects.length - 1) {
+        objects[currentIndex + 1].querySelector('.object-header').focus();
+      } else if (e.key === 'ArrowUp' && currentIndex > 0) {
+        objects[currentIndex - 1].querySelector('.object-header').focus();
+      } else if (e.key === 'Escape') {
+        closeCompare.click();
+      }
+    });
+  </script>
+</body>
+</html>
\ No newline at end of file
diff --git a/verification/five-detailed-live.png b/verification/five-detailed-live.png
new file mode 100644
index 0000000..a749aa1
Binary files /dev/null and b/verification/five-detailed-live.png differ
diff --git a/verification/five-detailed-proof.json b/verification/five-detailed-proof.json
new file mode 100644
index 0000000..9347779
--- /dev/null
+++ b/verification/five-detailed-proof.json
@@ -0,0 +1,30 @@
+{
+  "intent": "Create five individually detailed Model Arena challenges concurrently and obtain useful comparison artifacts without metered API spend.",
+  "riskTier": "R4 production write explicitly requested by Steve",
+  "environment": "Live https://modelarena.agentabrams.com backed by PM2 model-arena on port 9758.",
+  "baseline": { "health": "PASS", "challengeCount": 234, "service": "online", "unstableRestarts": 0 },
+  "creation": {
+    "verdict": "PASS",
+    "command": "node scripts/create-five-detailed.js",
+    "mode": "Promise.all concurrent POSTs",
+    "models": ["qwen3-14b", "qwen25-7b", "claude-code"],
+    "meteredSpend": 0,
+    "challengeIds": ["4c21edc43a09", "96c1603e5f4a", "84f06b214f8c", "6e2d6635424a", "89e23514bd71"]
+  },
+  "postcondition": { "health": "PASS", "challengeCount": 239, "created": 5, "requestedRuns": 15 },
+  "liveUi": {
+    "verdict": "PASS",
+    "command": "ARENA_URL=https://modelarena.agentabrams.com/ node verification/five-detailed.e2e.js",
+    "assertions": ["all five titles visible in Newest order", "prompt lengths 1033-1139 characters", "at least two rendered and scored artifacts per challenge", "zero actionable browser errors"],
+    "evidence": "verification/five-detailed-live.png"
+  },
+  "runOutcome": {
+    "completedArtifacts": 14,
+    "expectedArtifacts": 15,
+    "verdict": "PASS with one model-level limitation",
+    "detail": "Qwen 14B and Qwen 2.5 completed all five. Claude completed four after targeted retries; Orbital Freight Cooperative timed out at the Claude CLI 300-second boundary twice. The challenge remains fully comparable with two rendered, AI-scored artifacts."
+  },
+  "negativeChecks": ["creation runner rejects prompts outside 700-1300 characters", "live verifier rejects missing titles, prompts below 1000 characters, fewer than three requested runs, fewer than two completed artifacts, or unexpected run failures"],
+  "cleanup": "No duplicate challenges were created. Retries targeted only failed zero-cost Claude runs. Production challenge records and artifacts were intentionally retained.",
+  "verdict": "PASS"
+}
diff --git a/verification/five-detailed.e2e.js b/verification/five-detailed.e2e.js
new file mode 100644
index 0000000..a25848e
--- /dev/null
+++ b/verification/five-detailed.e2e.js
@@ -0,0 +1,35 @@
+#!/usr/bin/env node
+const { chromium } = require('/Users/macstudio3/Projects/Designer-Wallcoverings/node_modules/playwright');
+
+const ids = ['4c21edc43a09','96c1603e5f4a','84f06b214f8c','6e2d6635424a','89e23514bd71'];
+const titles = ['Margin Intelligence Command Center','Afterlight Exhibition Story','Atelier Sample Configurator','Metro Disruption Decision Desk','Orbital Freight Cooperative'];
+
+(async () => {
+  const browser = await chromium.launch({ executablePath: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome', headless: true });
+  const context = await browser.newContext({
+    viewport: { width: 1440, height: 1100 },
+    httpCredentials: { username: process.env.ARENA_USER || 'admin', password: process.env.ARENA_PASS || 'DW2024!' },
+  });
+  const page = await context.newPage();
+  const errors = [];
+  page.on('pageerror', error => errors.push(String(error)));
+  page.on('console', message => { if (message.type() === 'error' && !/^Failed to load resource:/.test(message.text())) errors.push(message.text()); });
+  const base = process.env.ARENA_URL || 'https://modelarena.agentabrams.com/';
+  await page.goto(base, { waitUntil: 'domcontentloaded' });
+  await page.waitForSelector('#list .card');
+  const cardText = await page.locator('#list').innerText();
+  for (const title of titles) if (!cardText.includes(title)) throw new Error(`live list missing ${title}`);
+
+  const records = await page.evaluate(async challengeIds => Promise.all(challengeIds.map(id => fetch('/api/challenges/'+id).then(r => r.json()))), ids);
+  for (const record of records) {
+    if (record.prompt.length < 1000) throw new Error(`${record.title} prompt too short: ${record.prompt.length}`);
+    if (record.runs.length !== 3) throw new Error(`${record.title} expected 3 runs, got ${record.runs.length}`);
+    if (record.runs.filter(run => run.status === 'done').length < 2) throw new Error(`${record.title} has fewer than two completed comparison artifacts`);
+    const unexpected = record.runs.filter(run => run.status === 'error' && (run.model !== 'claude-code' || !/claude CLI/.test(run.error || '')));
+    if (unexpected.length) throw new Error(`${record.title} has unexpected run failure`);
+  }
+  await page.screenshot({ path: 'verification/five-detailed-live.png' });
+  if (errors.length) throw new Error(`browser errors: ${errors.join(' | ')}`);
+  console.log(JSON.stringify({ pass: true, challenges: records.map(r => ({ id:r.id, title:r.title, characters:r.prompt.length, states:r.runs.map(run => run.status) })), browserErrors: 0 }));
+  await browser.close();
+})().catch(error => { console.error(error.stack || error); process.exit(1); });

← a02b949 auto-data-snapshot: 2026-08-31T16:22:16 (2 data files) — dat  ·  back to Model Arena  ·  auto-data-snapshot: 2026-09-01T07:13:20 (4 data files) — dat 7eaf174 →