[object Object]

← back to Wallco Ai

feat(pdp): V4-V10 theme variants — full 10-variant Theme submenu

34281f4f0fea6a49a4231528e34a24385a5b1e8d · 2026-05-28 18:05:16 -0700 · Steve Abrams

Adds 7 standalone PDP layouts at /design/:id/{hero,vertical,drawer,editorial,
spec,config,stack}, each fetches /api/design/:id. Hamburger Theme submenu now
lists all 10 + Classic. Shared themeVariantSend() helper. No template-literal
surgery; all variants live in public/theme-variants/v{N}-*.html.

V4 hero       — full-bleed top hero + narrow 640px buy column + 11 accordions
V5 vertical   — 720px stack + 14 accordions + fixed floating buy bar
V6 drawer     — minimal page + 12 chips that open right-side dark drawers
V7 editorial  — magazine layout: 70vh hero w/ pull-quote, 2-col body + marginalia
V8 spec       — wine-list 20-row spec table right, image left
V9 config     — calculator UP TOP (4-step) + big-CTA, hero shrunk to 1/3
V10 stack     — single 1080px column of standalone section cards

Files touched

Diff

commit 34281f4f0fea6a49a4231528e34a24385a5b1e8d
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu May 28 18:05:16 2026 -0700

    feat(pdp): V4-V10 theme variants — full 10-variant Theme submenu
    
    Adds 7 standalone PDP layouts at /design/:id/{hero,vertical,drawer,editorial,
    spec,config,stack}, each fetches /api/design/:id. Hamburger Theme submenu now
    lists all 10 + Classic. Shared themeVariantSend() helper. No template-literal
    surgery; all variants live in public/theme-variants/v{N}-*.html.
    
    V4 hero       — full-bleed top hero + narrow 640px buy column + 11 accordions
    V5 vertical   — 720px stack + 14 accordions + fixed floating buy bar
    V6 drawer     — minimal page + 12 chips that open right-side dark drawers
    V7 editorial  — magazine layout: 70vh hero w/ pull-quote, 2-col body + marginalia
    V8 spec       — wine-list 20-row spec table right, image left
    V9 config     — calculator UP TOP (4-step) + big-CTA, hero shrunk to 1/3
    V10 stack     — single 1080px column of standalone section cards
---
 public/theme-variants/v10-stack.html    | 353 +++++++++++++++++++
 public/theme-variants/v4-hero.html      | 602 ++++++++++++++++++++++++++++++++
 public/theme-variants/v5-vertical.html  | 420 ++++++++++++++++++++++
 public/theme-variants/v6-drawer.html    | 402 +++++++++++++++++++++
 public/theme-variants/v7-editorial.html | 353 +++++++++++++++++++
 public/theme-variants/v8-spec.html      | 342 ++++++++++++++++++
 public/theme-variants/v9-config.html    | 376 ++++++++++++++++++++
 server.js                               |  43 +++
 8 files changed, 2891 insertions(+)

diff --git a/public/theme-variants/v10-stack.html b/public/theme-variants/v10-stack.html
new file mode 100644
index 0000000..8a7f508
--- /dev/null
+++ b/public/theme-variants/v10-stack.html
@@ -0,0 +1,353 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Card-Stack PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 14.5px/1.55 var(--sans); -webkit-font-smoothing: antialiased; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  .crumb { max-width: 1080px; margin: 0 auto; padding: 18px 28px 0; display: flex; gap: 16px; font-size: 12px; color: var(--muted); }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+
+  main.stack {
+    max-width: 1080px; margin: 0 auto; padding: 20px 28px 60px;
+    display: flex; flex-direction: column; gap: 22px;
+  }
+
+  /* card primitive */
+  .card {
+    background: var(--bg); border: 1px solid var(--line);
+    border-radius: 14px; padding: 22px;
+    box-shadow: 0 1px 0 var(--line);
+  }
+  .card h3 { margin: 0 0 14px; font: 600 11px/1.2 var(--sans); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
+  .card.tinted-cream { background: var(--panel); }
+  .card.tinted-room { background: var(--room); }
+  .card.tinted-coord { background: var(--coord); }
+  .card.tinted-calc { background: var(--calc); }
+  .card.tinted-share { background: var(--share); }
+  .card.dark { background: var(--accent-dk); color: #f0eada; border-color: transparent; }
+  .card.dark h3 { color: var(--accent); }
+
+  /* hero card */
+  .card.hero { padding: 0; overflow: hidden; aspect-ratio: 4/3; position: relative; background: var(--hero-bg); border: 1px solid var(--line); }
+  .card.hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .card.hero .pill { position: absolute; top: 16px; right: 16px; background: rgba(250,248,243,.94); color: var(--ink); font: 600 10.5px/1 var(--sans); letter-spacing: .06em; padding: 7px 12px; border-radius: 999px; text-transform: uppercase; }
+  .card.hero .info { position: absolute; left: 16px; bottom: 16px; background: rgba(26,22,17,.78); color: #f0eada; font: 600 11.5px/1 var(--sans); letter-spacing: .06em; padding: 9px 14px; border-radius: 6px; text-transform: uppercase; }
+
+  /* title card */
+  .card.title { padding: 22px 26px; }
+  .card.title h1 { font: 500 34px/1.1 var(--serif); margin: 0 0 8px; }
+  .card.title .meta { color: var(--muted); font-size: 13.5px; }
+  .card.title .meta .sep { padding: 0 6px; opacity: .55; }
+  .card.title .chips { margin-top: 16px; display: flex; flex-wrap: wrap; gap: 6px; }
+  .chip { display: inline-flex; align-items: center; gap: 6px; background: var(--panel-2); color: var(--ink); border-radius: 999px; padding: 5px 11px; font: 500 11.5px/1 var(--sans); letter-spacing: .02em; border: 1px solid var(--line); }
+  .chip .dot { width: 10px; height: 10px; border-radius: 999px; }
+
+  /* buy card */
+  .card.buy { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
+  .cta { display: flex; align-items: center; justify-content: center; border-radius: 10px; padding: 0 24px; font: 700 13px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase; height: 64px; border: 1px solid transparent; cursor: pointer; }
+  .cta.sample { background: var(--accent); color: var(--accent-dk); }
+  .cta.buy { background: var(--accent-dk); color: #f0eada; }
+
+  /* calc */
+  .calc-card .row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 10px; }
+  .calc-card label { font-size: 11.5px; color: var(--muted); display: block; margin-bottom: 3px; }
+  .calc-card input, .calc-card select { width: 100%; padding: 8px 10px; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
+  .calc-card .total { padding: 12px 14px; background: rgba(255,255,255,.6); border-radius: 8px; font-size: 14px; }
+  .calc-card .total b { font: 500 18px var(--serif); }
+
+  .palette-card .swatches { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }
+  .sw { width: 44px; height: 44px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); cursor: pointer; position: relative; }
+  .sw:hover .lbl { display: block; }
+  .sw .lbl { display: none; position: absolute; left: 50%; top: 100%; transform: translateX(-50%); margin-top: 6px; white-space: nowrap; font: 500 10px/1 var(--sans); background: var(--ink); color: var(--bg); padding: 4px 6px; border-radius: 4px; z-index: 5; }
+  .palette-card .dom { font-size: 12px; color: var(--muted); margin-top: 12px; text-align: center; }
+
+  .adjust-card .sl { display: grid; grid-template-columns: 90px 1fr 36px; align-items: center; gap: 10px; font-size: 12px; padding: 4px 0; }
+  .adjust-card .sl input[type=range] { accent-color: var(--accent); width: 100%; }
+  .adjust-card .sl .v { font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
+
+  /* 2-col card row */
+  .row-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 22px; }
+
+  .room-card { height: 280px; background-size: cover; background-position: center; padding: 0; position: relative; overflow: hidden; }
+  .room-card .badge { position: absolute; top: 16px; left: 16px; background: rgba(26,22,17,.78); color: #f0eada; font: 600 10.5px/1 var(--sans); letter-spacing: .06em; padding: 7px 12px; border-radius: 999px; text-transform: uppercase; }
+  .room-card .placeholder { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 12px; font-style: italic; }
+
+  .pair-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 8px; }
+  .pair-grid .pair { aspect-ratio: 1/1; background: rgba(255,255,255,.55); border-radius: 6px; background-size: cover; background-position: center; }
+
+  /* accordions card */
+  .acc-card { padding: 14px; }
+  details.acc { background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; font-size: 13px; margin-bottom: 8px; }
+  details.acc:last-child { margin-bottom: 0; }
+  details.acc[open] { padding-bottom: 14px; }
+  details.acc summary { cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; align-items: center; }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 11px; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11.5px; color: var(--muted); margin-left: auto; margin-right: 10px; }
+  details.acc .acc-body { margin-top: 12px; font-size: 12.5px; line-height: 1.55; }
+
+  .specs-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .specs-row:last-child { border-bottom: 0; }
+  .specs-row .k { color: var(--muted); }
+  .specs-row .v { font-weight: 600; text-align: right; }
+  .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); padding: 10px 8px; border-radius: 8px; font: 600 11.5px/1.2 var(--sans); color: var(--ink); text-align: center; text-decoration: none; }
+  .tile-btn:hover { background: #fff; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 28px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); margin-top: 30px; }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+
+  dialog.modal { border: 0; border-radius: 14px; padding: 0; max-width: 540px; width: 92vw; box-shadow: 0 24px 80px rgba(0,0,0,.25); background: var(--bg); }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  @media (max-width: 720px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    main.stack { padding: 14px; gap: 16px; }
+    .card { padding: 18px; }
+    .card.title h1 { font-size: 26px; }
+    .card.buy { grid-template-columns: 1fr; }
+    .row-2 { grid-template-columns: 1fr; }
+    .pair-grid { grid-template-columns: repeat(3, 1fr); }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav><a href="/designs?sort=newest">Designs</a><a href="/designs?cat=monterey-mural">Murals</a><a href="/samples">Samples</a><a href="/basket">Basket</a></nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Card-Stack (V10) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+<dialog class="modal" id="m-trade"><header><h3>Trade tools</h3><button class="close" onclick="document.getElementById('m-trade').close()">×</button></header><div class="body" id="m-trade-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<div class="crumb">
+  <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span><a href="/design/${id - 2}?theme=stack">← prev</a> · <a href="/design/${id + 2}?theme=stack">next →</a></span>
+</div>
+
+<main class="stack">
+
+  <!-- HERO CARD -->
+  <div class="card hero">
+    <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+    <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+    <span class="info">${escapeHtml(title)}</span>
+  </div>
+
+  <!-- TITLE CARD -->
+  <div class="card title">
+    <h1>${escapeHtml(title)}</h1>
+    <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}</div>
+    <div class="chips">
+      <span class="chip"><span class="dot" style="background:${escapeAttr(dominant)}"></span>${escapeHtml(dominant)}</span>
+      <span class="chip">No.${id}</span>
+      <span class="chip">${escapeHtml(handle)}</span>
+      <span class="chip">${escapeHtml(generator)}</span>
+    </div>
+  </div>
+
+  <!-- BUY CARD -->
+  <div class="card buy">
+    <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
+    <button class="cta buy" onclick="document.getElementById('stkw').focus()">Buy by the roll →</button>
+  </div>
+
+  <!-- CALCULATOR CARD -->
+  <div class="card tinted-calc calc-card">
+    <h3 style="color:#5a4f73">Calculator</h3>
+    <div class="row">
+      <div><label>Wall W (ft)</label><input id="stkw" type="number" min="1" max="80" step="0.5" value="10"></div>
+      <div><label>Wall H (ft)</label><input id="stkh" type="number" min="1" max="20" step="0.5" value="9"></div>
+    </div>
+    <div class="row">
+      <div><label>Roll width</label><select id="stkrw"><option value="24">24" residential</option><option value="36">36" wide-roll</option><option value="52">52" commercial</option></select></div>
+      <div><label>Repeat</label><input value='${repeatLabel}' readonly></div>
+    </div>
+    <div class="total" id="stktotal">— rolls · $—</div>
+  </div>
+
+  <!-- 2-col: PALETTE + ADJUST -->
+  <div class="row-2">
+    <div class="card tinted-cream palette-card">
+      <h3>Palette · drag to recolor</h3>
+      <div class="swatches">
+        ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" style="background:${escapeAttr(p.hex)}"><span class="lbl">${escapeHtml(p.hex)} · ${(p.pct || 0).toFixed(1)}%</span></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"><span class="lbl">${escapeHtml(dominant)}</span></div>`}
+      </div>
+      <div class="dom">Dominant ${escapeHtml(dominant)}</div>
+    </div>
+    <div class="card tinted-cream adjust-card">
+      <h3>Image adjustments</h3>
+      <div class="sl"><label>Hue</label><input type="range" min="-180" max="180" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Saturation</label><input type="range" min="0" max="200" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Brightness</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Contrast</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+    </div>
+  </div>
+
+  <!-- ROOM CARD -->
+  <div class="card tinted-room room-card" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}>
+    <span class="badge">See in a room</span>
+    ${roomImgUrl ? '' : '<div class="placeholder">Room mockup not yet generated</div>'}
+  </div>
+
+  <!-- PAIRS WELL CARD -->
+  <div class="card tinted-coord">
+    <h3>Pairs well with — 6 designer wallpapers</h3>
+    <div class="pair-grid">
+      <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+      <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+    </div>
+  </div>
+
+  <!-- ACCORDIONS CARD (rest of the long-tail content) -->
+  <div class="card acc-card">
+    <h3 style="padding:0 8px 4px">More about this design</h3>
+    <details class="acc"><summary>Specs <span class="hint">12 fields</span></summary><div class="acc-body">
+      <div class="specs-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+      <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+      <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+      <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="specs-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div>
+      <div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+      <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <div class="specs-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+      <div class="specs-row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div>
+    </div></details>
+    <details class="acc"><summary>Coordinate paints <span class="hint">SW · BM · F&amp;B</span></summary><div class="acc-body"><div class="pill-list">
+      <a class="pill" href="#">Sherwin-Williams</a><a class="pill" href="#">Dunn-Edwards</a><a class="pill" href="#">Benjamin Moore</a><a class="pill" href="#">Farrow &amp; Ball</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a><a class="pill" href="#">RAL</a>
+    </div></div></details>
+    <details class="acc"><summary>Coordinate stripes &amp; plaids <span class="hint">24 generated</span></summary><div class="acc-body"><div class="pair-grid">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div></div></details>
+    <details class="acc"><summary>Furnish this design <span class="hint">Res · Com · Hosp · Health</span></summary><div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="#" onclick="openFurnish();return false">Residential</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Commercial</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Hospitality</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Healthcare</a>
+    </div></div></details>
+    <details class="acc"><summary>Share this design <span class="hint">Pin · IG · Copy</span></summary><div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="#" onclick="openShare();return false">Pinterest</a><a class="tile-btn" href="#" onclick="openShare();return false">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a>
+    </div></div></details>
+    <details class="acc"><summary>Trade tools <span class="hint">Tear · Email · Compare · Save</span></summary><div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#" onclick="openTrade();return false">Email</a><a class="tile-btn" href="#" onclick="openTrade();return false">Compare</a><a class="tile-btn" href="#" onclick="saveDesign(${id});return false">Save</a>
+    </div></div></details>
+    <details class="acc"><summary>Ratings &amp; reviews <span class="hint">★★★★☆ · ${escapeHtml(design.user_vote_count || 0)} votes</span></summary><div class="acc-body"><div style="font-size:22px; letter-spacing:4px; color:#b89a4a">★★★★☆</div><div style="font-size:11.5px; color:var(--muted); margin-top:4px">Curated · 4.2</div></div></details>
+    <details class="acc"><summary>Generator + prompt <span class="hint">wallco.ai · seed ${escapeHtml(design.seed || '—')}</span></summary><div class="acc-body"><pre style="font:11px/1.5 ui-monospace,Menlo,monospace; background:var(--panel); padding:10px; border-radius:6px; max-height:200px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1200))}${(design.prompt && design.prompt.length > 1200) ? '\n…[truncated]' : ''}</pre></div></details>
+  </div>
+
+</main>
+
+<footer class="site">
+  <div class="h">wallco.ai — Curated-original wallpaper &amp; murals</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+`;
+
+  const stkw = document.getElementById('stkw'), stkh = document.getElementById('stkh'), stkrw = document.getElementById('stkrw');
+  const stktotal = document.getElementById('stktotal');
+  function recalc() {
+    const ww = parseFloat(stkw.value) || 0, wh = parseFloat(stkh.value) || 0;
+    const rwi = parseFloat(stkrw.value) || 24;
+    if (ww < 1 || wh < 1) { stktotal.textContent = '— rolls · $—'; return; }
+    if (isMural) { const p = Math.ceil((ww * 12) / rwi); stktotal.innerHTML = '<b>' + p + '</b> panels · <b>$' + (p * 245).toLocaleString() + '</b>'; }
+    else { const wp = Math.ceil((ww * 12) / rwi); const r = wp * Math.ceil(wh / 27); stktotal.innerHTML = '<b>' + r + '</b> rolls · <b>$' + (r * 165).toLocaleString() + '</b> · ' + wp + ' panels wide'; }
+  }
+  [stkw, stkh, stkrw].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  document.querySelectorAll('.adjust-card .sl').forEach(row => {
+    const r = row.querySelector('input[type=range]'); const v = row.querySelector('.v');
+    r.addEventListener('input', () => { v.textContent = r.value; });
+  });
+
+  document.getElementById('m-share-body').innerHTML = '<p>Share <b>' + escapeHtml(title) + '</b></p><div class="tile-btns"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML = '<p>Generate AI furnishings.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+  document.getElementById('m-trade-body').innerHTML = '<p>Trade tools.</p><div class="tile-btns"><a class="tile-btn" href="/design/' + id + '/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Email</a><a class="tile-btn" href="#">Compare</a><a class="tile-btn" href="#">Save</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+function openTrade() { document.getElementById('m-trade').showModal(); }
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/stack$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function saveDesign(id) { alert('Saved #' + id + ' (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v4-hero.html b/public/theme-variants/v4-hero.html
new file mode 100644
index 0000000..845bfd2
--- /dev/null
+++ b/public/theme-variants/v4-hero.html
@@ -0,0 +1,602 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Hero-First Storefront PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3;
+    --ink: #1a1611;
+    --muted: #8a8276;
+    --line: #e2dccd;
+    --panel: #f3efe5;
+    --panel-2: #ebe5d6;
+    --accent: #c9b07a;
+    --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2;
+    --calc: #ddd4ec;
+    --coord: #d3e3e5;
+    --share: #f3dcce;
+    --room: #cfdac4;
+    --rate: #f0e7c3;
+    --trade: #e6ddd2;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
+    --serif: "Cormorant Garamond", "Times New Roman", Georgia, serif;
+    --r: 10px;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body {
+    background: var(--bg);
+    color: var(--ink);
+    font: 14px/1.5 var(--sans);
+    -webkit-font-smoothing: antialiased;
+  }
+  a { color: inherit; }
+  button { font: inherit; cursor: pointer; }
+
+  /* ── topbar ─────────────────────────────────────────────────────────── */
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92);
+    backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line);
+    padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header nav a:hover { color: var(--ink); }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  /* ── full-bleed hero ────────────────────────────────────────────────── */
+  .hero-wrap {
+    position: relative;
+    width: 100%;
+    height: min(78vh, 920px);
+    background: var(--hero-bg);
+    overflow: hidden;
+  }
+  .hero-wrap img {
+    width: 100%; height: 100%; object-fit: cover; display: block;
+  }
+  .hero-wrap .pill {
+    position: absolute; top: 18px; right: 24px;
+    background: rgba(250,248,243,.94); color: var(--ink);
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 7px 12px; border-radius: 999px; text-transform: uppercase;
+  }
+  .hero-wrap .overlay {
+    position: absolute; left: 0; right: 0; bottom: 0;
+    padding: 40px 28px 28px;
+    background: linear-gradient(to top, rgba(7,6,5,.85) 0%, rgba(7,6,5,.55) 60%, rgba(7,6,5,0) 100%);
+    color: #f0eada;
+  }
+  .hero-wrap .overlay .name {
+    font: 400 44px/1.05 var(--serif);
+    margin: 0 0 8px;
+    max-width: 900px;
+  }
+  .hero-wrap .overlay .meta {
+    font-size: 13px; letter-spacing: .04em;
+    color: #d8cea8;
+    max-width: 900px;
+  }
+  .hero-wrap .overlay .meta .sep { padding: 0 8px; opacity: .55; }
+  .hero-wrap .overlay .chips { margin-top: 14px; display: flex; flex-wrap: wrap; gap: 6px; }
+  .hero-wrap .chip {
+    display: inline-flex; align-items: center; gap: 6px;
+    background: rgba(250,248,243,.16); color: #f0eada;
+    border-radius: 999px; padding: 5px 11px;
+    font: 500 11px/1 var(--sans); letter-spacing: .02em;
+    border: 1px solid rgba(250,248,243,.18);
+    backdrop-filter: blur(4px);
+  }
+  .hero-wrap .chip .dot { width: 10px; height: 10px; border-radius: 999px; }
+
+  /* ── breadcrumb under hero ─────────────────────────────────────────── */
+  .crumb {
+    max-width: 1180px; margin: 0 auto; padding: 18px 28px 0;
+    display: flex; align-items: center; gap: 16px; font-size: 12px; color: var(--muted);
+  }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+  .crumb .nav a { color: var(--muted); }
+
+  /* ── narrow centered buy column ─────────────────────────────────────── */
+  main.narrow {
+    max-width: 640px; margin: 0 auto; padding: 28px 24px 80px;
+    display: flex; flex-direction: column; gap: 14px;
+  }
+
+  .cta {
+    display: flex; align-items: center; justify-content: center;
+    border-radius: var(--r); padding: 0 18px;
+    font: 700 13px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+    height: 64px;
+    border: 1px solid transparent;
+  }
+  .cta.sample { background: var(--accent); color: var(--accent-dk); }
+  .cta.buy { background: var(--accent-dk); color: #f0eada; }
+  .cta:hover { filter: brightness(1.04); }
+  .cta:active { transform: translateY(1px); }
+
+  .card {
+    background: var(--panel);
+    border: 1px solid var(--line);
+    border-radius: var(--r);
+    padding: 16px;
+  }
+  .card h4 {
+    margin: 0 0 10px;
+    font: 600 11px/1.2 var(--sans);
+    text-transform: uppercase; letter-spacing: .08em;
+    color: var(--muted);
+  }
+
+  .calc { background: var(--calc); }
+  .calc .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 8px 9px; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
+  .calc .total { margin-top: 6px; padding: 10px; background: rgba(255,255,255,.6); border-radius: 8px; font-size: 13px; }
+  .calc .total b { font-size: 17px; font-family: var(--serif); font-weight: 500; }
+
+  .palette { background: var(--panel); }
+  .palette .swatches { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 4px; justify-content: center; }
+  .palette .sw { width: 40px; height: 40px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); position: relative; cursor: pointer; }
+  .palette .sw:hover .lbl { display: block; }
+  .palette .sw .lbl { display: none; position: absolute; left: 50%; top: 100%; transform: translateX(-50%); margin-top: 6px; white-space: nowrap; font: 500 10px/1 var(--sans); background: var(--ink); color: var(--bg); padding: 4px 6px; border-radius: 4px; z-index: 5; }
+  .palette .dom { font-size: 11px; color: var(--muted); margin-top: 10px; text-align: center; }
+
+  /* ── accordions ────────────────────────────────────────────────────── */
+  details.acc {
+    background: #fff; border: 1px solid var(--line); border-radius: 8px;
+    padding: 12px 16px; font-size: 13px;
+  }
+  details.acc[open] { padding-bottom: 16px; }
+  details.acc summary {
+    cursor: pointer; font-weight: 600; list-style: none;
+    display: flex; justify-content: space-between; align-items: center;
+    color: var(--ink); gap: 8px;
+  }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 11px; flex-shrink: 0; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11.5px; color: var(--muted); margin-left: auto; }
+  details.acc .acc-body { margin-top: 14px; color: var(--ink); line-height: 1.6; font-size: 12.5px; }
+
+  .specs-row { display: flex; justify-content: space-between; padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .specs-row:last-child { border-bottom: 0; }
+  .specs-row .k { color: var(--muted); }
+  .specs-row .v { font-weight: 600; text-align: right; }
+
+  .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+
+  .pair-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
+  .pair-grid .pair {
+    aspect-ratio: 1/1;
+    background: rgba(0,0,0,.06);
+    border-radius: 6px;
+    background-size: cover; background-position: center;
+  }
+
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn {
+    flex: 1; min-width: 90px;
+    background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06);
+    padding: 10px 8px; border-radius: 8px;
+    font: 600 11.5px/1.2 var(--sans); color: var(--ink); text-align: center;
+    text-decoration: none;
+  }
+  .tile-btn:hover { background: #fff; }
+
+  .room-mini {
+    height: 200px; border-radius: 8px;
+    background: var(--room); background-size: cover; background-position: center;
+    position: relative;
+  }
+  .room-mini .placeholder { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 12px; font-style: italic; }
+
+  .atscale-mini {
+    height: 180px; border-radius: 8px;
+    background: var(--hero-bg);
+    background-repeat: repeat;
+    background-size: 160px 160px;
+    margin-top: 10px;
+  }
+
+  .adjust .sl { display: grid; grid-template-columns: 84px 1fr 36px; align-items: center; gap: 8px; font-size: 11.5px; padding: 3px 0; }
+  .adjust .sl input[type=range] { accent-color: var(--accent); width: 100%; }
+  .adjust .sl .v { font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
+
+  .prevnext-band {
+    max-width: 640px; margin: 12px auto 0;
+    display: flex; padding: 0 24px; font-size: 12.5px;
+  }
+  .prevnext-band a { color: var(--muted); text-decoration: none; }
+  .prevnext-band a:hover { color: var(--ink); }
+  .prevnext-band .grow { flex: 1; }
+
+  /* ── footer ────────────────────────────────────────────────────────── */
+  footer.site {
+    border-top: 1px solid var(--line);
+    padding: 28px; text-align: center; color: var(--muted); font-size: 12px;
+    background: var(--panel); margin-top: 40px;
+  }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; font-size: 14px; }
+
+  /* ── modals ─────────────────────────────────────────────────────────── */
+  dialog.modal {
+    border: 0; border-radius: 14px; padding: 0;
+    max-width: 540px; width: 92vw;
+    box-shadow: 0 24px 80px rgba(0,0,0,.25);
+    background: var(--bg);
+  }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  /* ── responsive ────────────────────────────────────────────────────── */
+  @media (max-width: 640px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    .hero-wrap { height: min(70vh, 560px); }
+    .hero-wrap .overlay { padding: 30px 16px 18px; }
+    .hero-wrap .overlay .name { font-size: 32px; }
+    .crumb { padding: 14px 14px 0; }
+    main.narrow { padding: 22px 14px 50px; }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav>
+    <a href="/designs?sort=newest">Designs</a>
+    <a href="/designs?cat=monterey-mural">Murals</a>
+    <a href="/samples">Samples</a>
+    <a href="/basket">Basket</a>
+    <a href="/designer-studio">Designer Studio</a>
+  </nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Hero-First Storefront (V4) <a id="back-to-classic" href="#">← Classic view</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share this design</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish this design</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+<dialog class="modal" id="m-trade"><header><h3>Trade tools</h3><button class="close" onclick="document.getElementById('m-trade').close()">×</button></header><div class="body" id="m-trade-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) {
+    document.getElementById('root').textContent = 'No design id in URL.';
+    return;
+  }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) {
+    document.getElementById('root').textContent = 'Failed to load design #' + id + ' — ' + e.message;
+    return;
+  }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '')
+    .replace(/^botanical-wonders · /, 'Botanical Wonders — ')
+    .replace(/^designer-zoo-calm/, 'Designer Zoo Calm')
+    .replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic')
+    .replace(/^monterey-mural/, 'Coastal Scenic')
+    .replace(/^tree-mural/, 'Full-Room Scenic')
+    .replace(/^cactus-11ft-mural/, 'Single-Subject Mural')
+    .replace(/-/g, ' ')
+    .replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")'
+                  : design.kind === 'mural_panel' ? 'Mural Panel'
+                  : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24;
+  const h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (
+    typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups)
+      ? Object.keys(design.room_mockups)[0]
+      : design.room_mockups[0]
+  )) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+
+<!-- FULL-BLEED HERO ────────────────────────────────────────────────── -->
+<div class="hero-wrap">
+  <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+  <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+  <div class="overlay">
+    <h1 class="name">${escapeHtml(title)}</h1>
+    <div class="meta">
+      ${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}
+    </div>
+    <div class="chips">
+      <span class="chip"><span class="dot" style="background:${escapeAttr(dominant)}"></span>${escapeHtml(dominant)}</span>
+      <span class="chip">No.${id}</span>
+      <span class="chip">${escapeHtml(handle)}</span>
+    </div>
+  </div>
+</div>
+
+<div class="crumb">
+  <a href="/designs">Designs</a>
+  <span>›</span>
+  <a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span class="nav">
+    <a href="/design/${id - 2}?theme=hero">← prev</a>
+    <a href="/design/${id + 2}?theme=hero">next →</a>
+  </span>
+</div>
+
+<!-- NARROW CENTERED BUY COLUMN ─────────────────────────────────────── -->
+<main class="narrow">
+
+  <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
+  <button class="cta buy" onclick="document.getElementById('calc-input').focus()">Buy by the roll →</button>
+
+  <div class="card calc">
+    <h4 style="color:#5a4f73">Calculator</h4>
+    <div class="row">
+      <div><label>Wall W (ft)</label><input id="calc-input" type="number" min="1" max="80" step="0.5" value="10"></div>
+      <div><label>Wall H (ft)</label><input id="calc-h" type="number" min="1" max="20" step="0.5" value="9"></div>
+    </div>
+    <div class="row">
+      <div><label>Roll width</label>
+        <select id="calc-rw">
+          <option value="24">24" residential</option>
+          <option value="36">36" wide-roll</option>
+          <option value="52">52" commercial</option>
+        </select>
+      </div>
+      <div><label>Repeat</label><input value='${repeatLabel}' readonly></div>
+    </div>
+    <div class="total" id="calc-total">— rolls · $—</div>
+  </div>
+
+  <div class="card palette">
+    <h4>Palette · drag to recolor</h4>
+    <div class="swatches">
+      ${palette.length ? palette.slice(0, 5).map(p =>
+        `<div class="sw" style="background:${escapeAttr(p.hex)}"><span class="lbl">${escapeHtml(p.hex)} · ${(p.pct || 0).toFixed(1)}%</span></div>`
+      ).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"><span class="lbl">${escapeHtml(dominant)}</span></div>`}
+    </div>
+    <div class="dom">Dominant ${escapeHtml(dominant)}</div>
+  </div>
+
+  <details class="acc">
+    <summary>Specs <span class="hint">Kind · W · Repeat · Roll · Material</span></summary>
+    <div class="acc-body">
+      <div class="specs-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+      <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+      <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+      <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="specs-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div>
+      <div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+      <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <div class="specs-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+      <div class="specs-row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Coordinate paints <span class="hint">SW · BM · F&amp;B · Behr · PPG</span></summary>
+    <div class="acc-body">
+      <div class="pill-list">
+        <a class="pill" href="#">Sherwin-Williams</a>
+        <a class="pill" href="#">Dunn-Edwards</a>
+        <a class="pill" href="#">Benjamin Moore</a>
+        <a class="pill" href="#">Farrow &amp; Ball</a>
+        <a class="pill" href="#">Behr</a>
+        <a class="pill" href="#">PPG</a>
+        <a class="pill" href="#">RAL</a>
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Coordinate stripes &amp; plaids <span class="hint">24 generated</span></summary>
+    <div class="acc-body">
+      <div class="pair-grid">
+        ${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>See in a room <span class="hint">residential preview</span></summary>
+    <div class="acc-body">
+      <div class="room-mini" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}>
+        ${roomImgUrl ? '' : '<div class="placeholder">Room mockup not yet generated</div>'}
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>At-scale preview <span class="hint">10 × 9 ft wall</span></summary>
+    <div class="acc-body">
+      <div class="atscale-mini" style="background-image:url('${img}')"></div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Pairs well with <span class="hint">6 designs</span></summary>
+    <div class="acc-body">
+      <div class="pair-grid">
+        <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+        <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Furnish this design <span class="hint">Res · Com · Hosp · Health</span></summary>
+    <div class="acc-body">
+      <div class="tile-btns">
+        <a class="tile-btn" href="#" onclick="openFurnish();return false">Residential</a>
+        <a class="tile-btn" href="#" onclick="openFurnish();return false">Commercial</a>
+        <a class="tile-btn" href="#" onclick="openFurnish();return false">Hospitality</a>
+        <a class="tile-btn" href="#" onclick="openFurnish();return false">Healthcare</a>
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Share this design <span class="hint">Pin · IG · Copy</span></summary>
+    <div class="acc-body">
+      <div class="tile-btns">
+        <a class="tile-btn" href="#" onclick="openShare();return false">Pinterest</a>
+        <a class="tile-btn" href="#" onclick="openShare();return false">Instagram</a>
+        <a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a>
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Trade tools <span class="hint">Tear · Email · Compare · Save</span></summary>
+    <div class="acc-body">
+      <div class="tile-btns">
+        <a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a>
+        <a class="tile-btn" href="#" onclick="openTrade();return false">Email client</a>
+        <a class="tile-btn" href="#" onclick="openTrade();return false">Compare</a>
+        <a class="tile-btn" href="#" onclick="saveDesign(${id});return false">Save</a>
+      </div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Image adjustments <span class="hint">hue/sat/bri/contrast</span></summary>
+    <div class="acc-body adjust">
+      <div class="sl"><label>Hue</label><input type="range" min="-180" max="180" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Saturation</label><input type="range" min="0" max="200" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Brightness</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Contrast</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Ratings &amp; reviews <span class="hint">★★★★☆ · ${escapeHtml(design.user_vote_count || 0)}</span></summary>
+    <div class="acc-body">
+      <div style="font-size:24px; letter-spacing:4px; color:#b89a4a">★★★★☆</div>
+      <div style="font-size:11.5px; color:var(--muted); margin-top:4px">Curated · 4.2 · ${escapeHtml(design.user_vote_count || 0)} votes</div>
+    </div>
+  </details>
+
+  <details class="acc">
+    <summary>Generator + prompt <span class="hint">wallco.ai · seed ${escapeHtml(design.seed || '—')}</span></summary>
+    <div class="acc-body">
+      <pre style="font:11px/1.5 ui-monospace,Menlo,monospace; background:var(--panel); padding:10px; border-radius:6px; max-height:200px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1200))}${(design.prompt && design.prompt.length > 1200) ? '\n…[truncated]' : ''}</pre>
+    </div>
+  </details>
+
+</main>
+
+<div class="prevnext-band">
+  <a href="/design/${id - 2}?theme=hero">← previous pattern</a>
+  <span class="grow"></span>
+  <a href="/design/${id + 2}?theme=hero">next pattern →</a>
+</div>
+
+<footer class="site">
+  <div class="h">wallco.ai — Curated-original wallpaper &amp; murals</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA · steve@designerwallcoverings.com</div>
+</footer>
+`;
+
+  // ── calculator ──
+  const calcW = root.querySelector('#calc-input');
+  const calcH = root.querySelector('#calc-h');
+  const calcRW = root.querySelector('#calc-rw');
+  const total = root.querySelector('#calc-total');
+  function recalc() {
+    const ww = parseFloat(calcW.value) || 0;
+    const wh = parseFloat(calcH.value) || 0;
+    const rwInches = parseFloat(calcRW.value) || 24;
+    if (ww < 1 || wh < 1) { total.textContent = '— rolls · $—'; return; }
+    if (isMural) {
+      const panels = Math.ceil((ww * 12) / rwInches);
+      const cost = panels * 245;
+      total.innerHTML = '<b>' + panels + '</b> panels · <b>$' + cost.toLocaleString() + '</b>';
+    } else {
+      const widthPanels = Math.ceil((ww * 12) / rwInches);
+      const rollsPerPanel = Math.ceil(wh / 27);
+      const rolls = widthPanels * rollsPerPanel;
+      const cost = rolls * 165;
+      total.innerHTML = '<b>' + rolls + '</b> rolls · <b>$' + cost.toLocaleString() + '</b> · ' + widthPanels + ' panels wide';
+    }
+  }
+  [calcW, calcH, calcRW].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  // ── adjust sliders ──
+  root.querySelectorAll('.adjust .sl').forEach(row => {
+    const r = row.querySelector('input[type=range]');
+    const v = row.querySelector('.v');
+    r.addEventListener('input', () => { v.textContent = r.value; });
+  });
+
+  // ── modal bodies ──
+  document.getElementById('m-share-body').innerHTML =
+    '<p>Share <b>' + escapeHtml(title) + '</b> on:</p>' +
+    '<div class="tile-btns" style="margin-top:8px"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML =
+    '<p>Generate AI furnishings curated to this pattern\'s palette.</p>' +
+    '<div class="tile-btns" style="margin-top:8px"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+  document.getElementById('m-trade-body').innerHTML =
+    '<p>Trade-only tools (interior designers + specifiers).</p>' +
+    '<div class="tile-btns" style="margin-top:8px"><a class="tile-btn" href="/design/' + id + '/tearsheet" target="_blank">Tear sheet PDF</a><a class="tile-btn" href="#">Email to client</a><a class="tile-btn" href="#">Compare 2 patterns</a><a class="tile-btn" href="#">Add to saved</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+function openTrade() { document.getElementById('m-trade').showModal(); }
+async function copyLink() {
+  try { await navigator.clipboard.writeText(location.href.replace(/\/hero$/, '')); alert('Link copied'); }
+  catch { alert('Copy failed — ' + location.href); }
+}
+function addSample() { alert('Sample added (stub — wire to /api/cart/sample/' + (new URL(location).pathname.match(/\d+/) || [0])[0] + ')'); }
+function saveDesign(id) { alert('Saved design #' + id + ' (stub — wire to /api/saved)'); }
+
+function escapeHtml(v) {
+  if (v == null) return '';
+  return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
+}
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v5-vertical.html b/public/theme-variants/v5-vertical.html
new file mode 100644
index 0000000..cc044bf
--- /dev/null
+++ b/public/theme-variants/v5-vertical.html
@@ -0,0 +1,420 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Mobile-Up Vertical PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 14px/1.5 var(--sans); -webkit-font-smoothing: antialiased; padding-bottom: 96px; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header nav a:hover { color: var(--ink); }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  main.vertical {
+    max-width: 720px; margin: 0 auto; padding: 18px 16px 30px;
+    display: flex; flex-direction: column; gap: 10px;
+  }
+
+  .hero {
+    aspect-ratio: 1/1; background: var(--hero-bg);
+    border-radius: 12px; overflow: hidden; position: relative;
+  }
+  .hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .hero .pill {
+    position: absolute; top: 12px; right: 12px;
+    background: rgba(250,248,243,.94); color: var(--ink);
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 6px 10px; border-radius: 999px; text-transform: uppercase;
+  }
+
+  .title-card {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 10px; padding: 14px 16px;
+  }
+  .title-card .name { font: 500 22px/1.15 var(--serif); margin: 2px 0 4px; }
+  .title-card .meta { color: var(--muted); font-size: 12px; }
+  .title-card .meta .sep { padding: 0 5px; opacity: .55; }
+
+  .chip-strip {
+    display: flex; flex-wrap: wrap; gap: 6px;
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 10px; padding: 10px 12px;
+    font: 500 11px/1 var(--sans); align-items: center;
+  }
+  .chip {
+    display: inline-flex; align-items: center; gap: 6px;
+    background: #fff; color: var(--ink);
+    border-radius: 999px; padding: 4px 9px;
+    border: 1px solid var(--line);
+  }
+  .chip .dot { width: 9px; height: 9px; border-radius: 999px; }
+
+  details.acc {
+    background: #fff; border: 1px solid var(--line); border-radius: 10px;
+    padding: 12px 14px; font-size: 13px;
+  }
+  details.acc[open] { padding-bottom: 14px; }
+  details.acc summary {
+    cursor: pointer; font-weight: 600; list-style: none;
+    display: flex; justify-content: space-between; align-items: center;
+    color: var(--ink); gap: 8px;
+  }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 11px; flex-shrink: 0; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11.5px; color: var(--muted); margin-left: auto; }
+  details.acc .acc-body { margin-top: 12px; line-height: 1.6; font-size: 12.5px; }
+
+  .specs-row { display: flex; justify-content: space-between; padding: 7px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .specs-row:last-child { border-bottom: 0; }
+  .specs-row .k { color: var(--muted); }
+  .specs-row .v { font-weight: 600; text-align: right; }
+
+  .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+
+  .pair-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
+  .pair-grid .pair { aspect-ratio: 1/1; background: rgba(0,0,0,.06); border-radius: 6px; background-size: cover; background-position: center; }
+
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn {
+    flex: 1; min-width: 100px;
+    background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06);
+    padding: 10px 8px; border-radius: 8px;
+    font: 600 11.5px/1.2 var(--sans); color: var(--ink); text-align: center; text-decoration: none;
+  }
+  .tile-btn:hover { background: #fff; }
+
+  .calc-mini .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
+  .calc-mini label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc-mini input, .calc-mini select { width: 100%; padding: 7px 8px; border: 1px solid var(--line); border-radius: 6px; font: inherit; background: #fff; }
+  .calc-mini .total { padding: 8px 10px; background: var(--calc); border-radius: 6px; font-size: 13px; margin-top: 4px; }
+  .calc-mini .total b { font-size: 16px; font-family: var(--serif); font-weight: 500; }
+
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+
+  .adjust .sl { display: grid; grid-template-columns: 80px 1fr 32px; align-items: center; gap: 8px; font-size: 11.5px; padding: 3px 0; }
+  .adjust .sl input[type=range] { accent-color: var(--accent); width: 100%; }
+  .adjust .sl .v { font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
+
+  .room-mini { height: 200px; border-radius: 8px; background: var(--room); background-size: cover; background-position: center; }
+  .atscale-mini { height: 200px; border-radius: 8px; background: var(--hero-bg); background-repeat: repeat; background-size: 160px 160px; }
+
+  /* floating buy bar */
+  .buy-bar {
+    position: fixed; bottom: 0; left: 0; right: 0; z-index: 40;
+    background: var(--accent-dk); color: #f0eada;
+    padding: 12px 16px;
+    display: flex; gap: 10px; align-items: center;
+    box-shadow: 0 -8px 24px rgba(0,0,0,.18);
+  }
+  .buy-bar .sample {
+    background: var(--accent); color: var(--accent-dk);
+    padding: 12px 18px; border-radius: 8px; border: 0;
+    font: 700 12px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+    cursor: pointer;
+  }
+  .buy-bar .buy {
+    flex: 1; background: rgba(255,255,255,.08); color: #f0eada;
+    padding: 12px 18px; border-radius: 8px; border: 1px solid rgba(255,255,255,.12);
+    font: 700 12px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+    cursor: pointer; text-align: center;
+  }
+  .buy-bar .est { font-size: 12px; color: #c2b88a; white-space: nowrap; }
+
+  footer.site {
+    border-top: 1px solid var(--line);
+    padding: 22px 16px; text-align: center; color: var(--muted); font-size: 12px;
+    background: var(--panel);
+  }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; font-size: 14px; }
+
+  dialog.modal { border: 0; border-radius: 14px; padding: 0; max-width: 540px; width: 92vw; box-shadow: 0 24px 80px rgba(0,0,0,.25); background: var(--bg); }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  @media (max-width: 480px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    .hero { aspect-ratio: 4/5; }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav>
+    <a href="/designs?sort=newest">Designs</a>
+    <a href="/designs?cat=monterey-mural">Murals</a>
+    <a href="/samples">Samples</a>
+    <a href="/basket">Basket</a>
+    <a href="/designer-studio">Designer Studio</a>
+  </nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Mobile-Up Vertical (V5) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+<dialog class="modal" id="m-trade"><header><h3>Trade tools</h3><button class="close" onclick="document.getElementById('m-trade').close()">×</button></header><div class="body" id="m-trade-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No design id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '')
+    .replace(/^botanical-wonders · /, 'Botanical Wonders — ')
+    .replace(/^designer-zoo-calm/, 'Designer Zoo Calm')
+    .replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic')
+    .replace(/^monterey-mural/, 'Coastal Scenic')
+    .replace(/^tree-mural/, 'Full-Room Scenic')
+    .replace(/^cactus-11ft-mural/, 'Single-Subject Mural')
+    .replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")'
+                  : design.kind === 'mural_panel' ? 'Mural Panel'
+                  : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (
+    typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups)
+      ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="vertical">
+  <div class="hero">
+    <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+    <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+  </div>
+
+  <div class="title-card">
+    <div class="name">${escapeHtml(title)}</div>
+    <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}</div>
+  </div>
+
+  <div class="chip-strip">
+    <span class="chip"><span class="dot" style="background:${escapeAttr(dominant)}"></span>${escapeHtml(dominant)}</span>
+    <span class="chip">No.${id}</span>
+    <span class="chip">${escapeHtml(handle)}</span>
+    <span class="chip" style="background:${design.is_published ? '#d6eedb' : '#f1e7c5'}; color:${design.is_published ? '#1a5a30' : '#7a5520'}">${design.is_published ? 'Live' : 'Curating'}</span>
+  </div>
+
+  <details class="acc" open><summary>Buy (sample / roll) <span class="hint">$4.25 sample · per-roll</span></summary>
+    <div class="acc-body" style="display:flex; gap:8px;">
+      <button class="tile-btn" style="background:var(--accent); color:var(--accent-dk); flex:1" onclick="addSample()">Add sample $4.25</button>
+      <button class="tile-btn" style="background:var(--accent-dk); color:#f0eada; flex:1" onclick="document.getElementById('calc-input').focus()">Buy by roll</button>
+    </div>
+  </details>
+
+  <details class="acc" open><summary>Calculator <span class="hint">wall × panels × total</span></summary>
+    <div class="acc-body calc-mini">
+      <div class="row">
+        <div><label>Wall W (ft)</label><input id="calc-input" type="number" min="1" max="80" step="0.5" value="10"></div>
+        <div><label>Wall H (ft)</label><input id="calc-h" type="number" min="1" max="20" step="0.5" value="9"></div>
+      </div>
+      <div class="row">
+        <div><label>Roll width</label><select id="calc-rw"><option value="24">24" residential</option><option value="36">36" wide-roll</option><option value="52">52" commercial</option></select></div>
+        <div><label>Repeat</label><input value='${repeatLabel}' readonly></div>
+      </div>
+      <div class="total" id="calc-total">— rolls · $—</div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>Palette + recolor <span class="hint">${palette.length || 1} colors</span></summary>
+    <div class="acc-body">
+      <div class="swatches">
+        ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" title="${escapeAttr(p.hex)} · ${(p.pct || 0).toFixed(1)}%" style="background:${escapeAttr(p.hex)}"></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"></div>`}
+      </div>
+      <div style="font-size:11px; color:var(--muted); margin-top:8px">Dominant ${escapeHtml(dominant)}</div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>Image adjustments <span class="hint">hue/sat/bri/blur</span></summary>
+    <div class="acc-body adjust">
+      <div class="sl"><label>Hue</label><input type="range" min="-180" max="180" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Saturation</label><input type="range" min="0" max="200" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Brightness</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Contrast</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>Specs <span class="hint">Kind · W · Repeat · Roll</span></summary>
+    <div class="acc-body">
+      <div class="specs-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+      <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+      <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+      <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="specs-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div>
+      <div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+      <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <div class="specs-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+      <div class="specs-row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>See in a room <span class="hint">residential preview</span></summary>
+    <div class="acc-body">
+      <div class="room-mini" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}></div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>At-scale preview <span class="hint">10 × 9 ft wall</span></summary>
+    <div class="acc-body">
+      <div class="atscale-mini" style="background-image:url('${img}')"></div>
+    </div>
+  </details>
+
+  <details class="acc"><summary>Pairs well with <span class="hint">6 designs</span></summary>
+    <div class="acc-body"><div class="pair-grid"><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div></div></div>
+  </details>
+
+  <details class="acc"><summary>Coordinate paints <span class="hint">SW · BM · F&amp;B</span></summary>
+    <div class="acc-body"><div class="pill-list">
+      <a class="pill" href="#">Sherwin-Williams</a><a class="pill" href="#">Dunn-Edwards</a><a class="pill" href="#">Benjamin Moore</a><a class="pill" href="#">Farrow &amp; Ball</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a><a class="pill" href="#">RAL</a>
+    </div></div>
+  </details>
+
+  <details class="acc"><summary>Coordinate stripes &amp; plaids <span class="hint">24 generated</span></summary>
+    <div class="acc-body"><div class="pair-grid">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div></div>
+  </details>
+
+  <details class="acc"><summary>Furnish this design <span class="hint">Res · Com · Hosp</span></summary>
+    <div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="#" onclick="openFurnish();return false">Residential</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Commercial</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Hospitality</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Healthcare</a>
+    </div></div>
+  </details>
+
+  <details class="acc"><summary>Share this design <span class="hint">Pin · IG · Copy</span></summary>
+    <div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="#" onclick="openShare();return false">Pinterest</a><a class="tile-btn" href="#" onclick="openShare();return false">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a>
+    </div></div>
+  </details>
+
+  <details class="acc"><summary>Trade tools <span class="hint">Tear · Email · Compare</span></summary>
+    <div class="acc-body"><div class="tile-btns">
+      <a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#" onclick="openTrade();return false">Email</a><a class="tile-btn" href="#" onclick="openTrade();return false">Compare</a><a class="tile-btn" href="#" onclick="saveDesign(${id});return false">Save</a>
+    </div></div>
+  </details>
+
+  <details class="acc"><summary>Ratings &amp; reviews <span class="hint">★★★★☆ · ${escapeHtml(design.user_vote_count || 0)}</span></summary>
+    <div class="acc-body"><div style="font-size:24px; letter-spacing:4px; color:#b89a4a">★★★★☆</div><div style="font-size:11.5px; color:var(--muted); margin-top:4px">Curated · 4.2</div></div>
+  </details>
+
+  <details class="acc"><summary>Generator + prompt <span class="hint">wallco.ai</span></summary>
+    <div class="acc-body"><pre style="font:11px/1.5 ui-monospace,Menlo,monospace; background:var(--panel); padding:10px; border-radius:6px; max-height:200px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1000))}${(design.prompt && design.prompt.length > 1000) ? '\n…[truncated]' : ''}</pre></div>
+  </details>
+
+</main>
+
+<footer class="site">
+  <div class="h">wallco.ai</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+
+<div class="buy-bar">
+  <button class="sample" onclick="addSample()">Sample $4.25</button>
+  <button class="buy" onclick="document.getElementById('calc-input').scrollIntoView({behavior:'smooth',block:'center'})">Buy by the roll →</button>
+  <span class="est" id="bar-est">calc above</span>
+</div>
+`;
+
+  const calcW = root.querySelector('#calc-input');
+  const calcH = root.querySelector('#calc-h');
+  const calcRW = root.querySelector('#calc-rw');
+  const total = root.querySelector('#calc-total');
+  const barEst = root.querySelector('#bar-est') || document.getElementById('bar-est');
+  function recalc() {
+    const ww = parseFloat(calcW.value) || 0, wh = parseFloat(calcH.value) || 0;
+    const rwInches = parseFloat(calcRW.value) || 24;
+    if (ww < 1 || wh < 1) { total.textContent = '— rolls · $—'; if(barEst)barEst.textContent='—'; return; }
+    if (isMural) {
+      const panels = Math.ceil((ww * 12) / rwInches); const cost = panels * 245;
+      total.innerHTML = '<b>' + panels + '</b> panels · <b>$' + cost.toLocaleString() + '</b>';
+      if(barEst)barEst.textContent = panels + ' panels · $' + cost.toLocaleString();
+    } else {
+      const widthPanels = Math.ceil((ww * 12) / rwInches);
+      const rollsPerPanel = Math.ceil(wh / 27);
+      const rolls = widthPanels * rollsPerPanel; const cost = rolls * 165;
+      total.innerHTML = '<b>' + rolls + '</b> rolls · <b>$' + cost.toLocaleString() + '</b> · ' + widthPanels + ' panels wide';
+      if(barEst)barEst.textContent = rolls + ' rolls · $' + cost.toLocaleString();
+    }
+  }
+  [calcW, calcH, calcRW].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  root.querySelectorAll('.adjust .sl').forEach(row => {
+    const r = row.querySelector('input[type=range]');
+    const v = row.querySelector('.v');
+    r.addEventListener('input', () => { v.textContent = r.value; });
+  });
+
+  document.getElementById('m-share-body').innerHTML = '<p>Share <b>' + escapeHtml(title) + '</b></p><div class="tile-btns"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML = '<p>Generate AI furnishings.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+  document.getElementById('m-trade-body').innerHTML = '<p>Trade tools.</p><div class="tile-btns"><a class="tile-btn" href="/design/' + id + '/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Email</a><a class="tile-btn" href="#">Compare</a><a class="tile-btn" href="#">Save</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+function openTrade() { document.getElementById('m-trade').showModal(); }
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/vertical$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function saveDesign(id) { alert('Saved #' + id + ' (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v6-drawer.html b/public/theme-variants/v6-drawer.html
new file mode 100644
index 0000000..c1ead2c
--- /dev/null
+++ b/public/theme-variants/v6-drawer.html
@@ -0,0 +1,402 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Drawer Heavy PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3;
+    --drawer-bg: #1d1814; --drawer-ink: #f0eada; --drawer-mute: #9a8e72;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 14px/1.5 var(--sans); -webkit-font-smoothing: antialiased; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  .crumb { max-width: 1100px; margin: 0 auto; padding: 16px 28px 0; display: flex; gap: 16px; font-size: 12px; color: var(--muted); }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+
+  main.minimal {
+    max-width: 1100px; margin: 0 auto; padding: 20px 28px 60px;
+    display: flex; flex-direction: column; gap: 18px;
+  }
+
+  .hero {
+    aspect-ratio: 1/1; background: var(--hero-bg);
+    border-radius: 12px; overflow: hidden; position: relative;
+    max-height: 920px;
+  }
+  .hero img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .hero .pill {
+    position: absolute; top: 14px; right: 14px;
+    background: rgba(250,248,243,.94); color: var(--ink);
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 7px 11px; border-radius: 999px; text-transform: uppercase;
+  }
+  .hero .info {
+    position: absolute; left: 14px; bottom: 14px;
+    background: rgba(26,22,17,.78); color: #f0eada;
+    font: 600 11px/1 var(--sans); letter-spacing: .06em;
+    padding: 8px 12px; border-radius: 6px; text-transform: uppercase;
+  }
+
+  .title-card {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 12px; padding: 16px 20px;
+    display: flex; align-items: baseline; gap: 16px; flex-wrap: wrap;
+  }
+  .title-card .name { font: 500 26px/1.15 var(--serif); margin: 0; }
+  .title-card .meta { color: var(--muted); font-size: 13px; }
+  .title-card .meta .sep { padding: 0 6px; opacity: .55; }
+
+  .chip-rail {
+    display: flex; flex-wrap: wrap; gap: 8px;
+    padding: 6px 0;
+  }
+  .drawer-chip {
+    background: #fff; border: 1px solid var(--line);
+    border-radius: 8px; padding: 10px 14px;
+    font: 600 12px/1 var(--sans); color: var(--ink);
+    cursor: pointer; display: inline-flex; align-items: center; gap: 6px;
+    transition: all .12s;
+  }
+  .drawer-chip:hover { background: var(--accent); color: var(--accent-dk); border-color: var(--accent); }
+  .drawer-chip .arrow { color: var(--muted); font-size: 10px; }
+  .drawer-chip:hover .arrow { color: var(--accent-dk); }
+
+  .buy-box {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 12px; padding: 18px;
+    display: grid; grid-template-columns: 1fr 1fr 1.2fr; gap: 12px;
+    align-items: stretch;
+  }
+  .buy-box .cta {
+    display: flex; align-items: center; justify-content: center;
+    border-radius: 8px; padding: 18px;
+    font: 700 12.5px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+    border: 1px solid transparent;
+  }
+  .buy-box .cta.sample { background: var(--accent); color: var(--accent-dk); }
+  .buy-box .cta.buy { background: var(--accent-dk); color: #f0eada; }
+  .buy-box .summary {
+    background: var(--calc); border-radius: 8px; padding: 12px 14px;
+    font-size: 12px; color: var(--ink);
+  }
+  .buy-box .summary b { font: 500 16px var(--serif); display: block; }
+
+  /* ── drawers (dialog as right-side sheet) ──────────────────────────── */
+  dialog.drawer {
+    border: 0; padding: 0;
+    background: var(--drawer-bg); color: var(--drawer-ink);
+    width: 480px; max-width: 92vw;
+    height: 100vh; max-height: 100vh;
+    margin: 0 0 0 auto;
+    box-shadow: -16px 0 40px rgba(0,0,0,.32);
+    overflow-y: auto;
+  }
+  dialog.drawer::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.drawer .drawer-head {
+    position: sticky; top: 0; background: var(--drawer-bg);
+    padding: 18px 22px; border-bottom: 1px solid rgba(255,255,255,.08);
+    display: flex; align-items: center; gap: 12px; z-index: 5;
+  }
+  dialog.drawer .drawer-head h3 { margin: 0; font: 500 18px/1 var(--serif); color: var(--drawer-ink); flex: 1; }
+  dialog.drawer .drawer-close { background: transparent; border: 0; font-size: 22px; color: var(--drawer-mute); cursor: pointer; }
+  dialog.drawer .drawer-body { padding: 18px 22px 32px; }
+  dialog.drawer .drawer-row {
+    display: flex; justify-content: space-between; padding: 10px 0;
+    border-bottom: 1px solid rgba(255,255,255,.06); font-size: 13px;
+  }
+  dialog.drawer .drawer-row:last-child { border-bottom: 0; }
+  dialog.drawer .drawer-row .k { color: var(--drawer-mute); }
+  dialog.drawer .drawer-row .v { font-weight: 600; color: var(--drawer-ink); text-align: right; }
+  dialog.drawer .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  dialog.drawer .pill { font-size: 12px; padding: 6px 10px; border-radius: 999px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12); color: var(--drawer-ink); }
+  dialog.drawer .pair-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
+  dialog.drawer .pair-grid .pair { aspect-ratio: 1/1; background: rgba(255,255,255,.08); border-radius: 6px; background-size: cover; background-position: center; }
+  dialog.drawer .tile-btns { display: flex; flex-wrap: wrap; gap: 8px; }
+  dialog.drawer .tile-btn { flex: 1; min-width: 110px; background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.12); padding: 12px 8px; border-radius: 8px; font: 600 12px/1.2 var(--sans); color: var(--drawer-ink); text-align: center; text-decoration: none; cursor: pointer; }
+  dialog.drawer .tile-btn:hover { background: rgba(255,255,255,.14); }
+  dialog.drawer .calc-mini { background: rgba(255,255,255,.05); padding: 14px; border-radius: 8px; }
+  dialog.drawer .calc-mini .row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 8px; }
+  dialog.drawer .calc-mini label { font-size: 11px; color: var(--drawer-mute); display: block; margin-bottom: 2px; }
+  dialog.drawer .calc-mini input, dialog.drawer .calc-mini select { width: 100%; padding: 8px; border: 1px solid rgba(255,255,255,.12); border-radius: 6px; font: inherit; background: rgba(0,0,0,.25); color: var(--drawer-ink); }
+  dialog.drawer .calc-mini .total { padding: 10px; background: var(--accent); color: var(--accent-dk); border-radius: 6px; font-size: 13px; margin-top: 6px; }
+  dialog.drawer .calc-mini .total b { font: 500 17px var(--serif); }
+  dialog.drawer .swatches { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 6px; }
+  dialog.drawer .sw { width: 38px; height: 38px; border-radius: 50%; border: 2px solid #2a241d; box-shadow: 0 0 0 1px var(--drawer-mute); }
+  dialog.drawer pre { white-space: pre-wrap; word-break: break-word; font: 11px/1.5 ui-monospace, Menlo, monospace; background: rgba(0,0,0,.32); color: var(--drawer-ink); padding: 12px; border-radius: 6px; max-height: 240px; overflow: auto; }
+  dialog.drawer .adjust .sl { display: grid; grid-template-columns: 80px 1fr 32px; align-items: center; gap: 8px; font-size: 12px; padding: 4px 0; }
+  dialog.drawer .adjust .sl input[type=range] { accent-color: var(--accent); width: 100%; }
+  dialog.drawer .adjust .sl .v { font-variant-numeric: tabular-nums; color: var(--drawer-mute); text-align: right; }
+  dialog.drawer .room-mini { height: 220px; border-radius: 8px; background: var(--room); background-size: cover; background-position: center; }
+  dialog.drawer .atscale-mini { height: 180px; border-radius: 8px; background: var(--hero-bg); background-repeat: repeat; background-size: 160px 160px; margin-top: 10px; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 28px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); margin-top: 30px; }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; font-size: 14px; }
+
+  @media (max-width: 640px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    main.minimal { padding: 16px; }
+    .buy-box { grid-template-columns: 1fr; }
+    .title-card .name { font-size: 22px; }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav>
+    <a href="/designs?sort=newest">Designs</a>
+    <a href="/designs?cat=monterey-mural">Murals</a>
+    <a href="/samples">Samples</a>
+    <a href="/basket">Basket</a>
+    <a href="/designer-studio">Designer Studio</a>
+  </nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Drawer Heavy (V6) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<!-- 12 drawers — each chip opens one -->
+<dialog class="drawer" id="d-specs"><div class="drawer-head"><h3>Specs</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-specs-body"></div></dialog>
+<dialog class="drawer" id="d-calc"><div class="drawer-head"><h3>Calculator</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-calc-body"></div></dialog>
+<dialog class="drawer" id="d-recolor"><div class="drawer-head"><h3>Palette &amp; recolor</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-recolor-body"></div></dialog>
+<dialog class="drawer" id="d-adjust"><div class="drawer-head"><h3>Image adjustments</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-adjust-body"></div></dialog>
+<dialog class="drawer" id="d-room"><div class="drawer-head"><h3>See in a room</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-room-body"></div></dialog>
+<dialog class="drawer" id="d-pairs"><div class="drawer-head"><h3>Pairs well with</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-pairs-body"></div></dialog>
+<dialog class="drawer" id="d-paints"><div class="drawer-head"><h3>Coordinate paints</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-paints-body"></div></dialog>
+<dialog class="drawer" id="d-stripes"><div class="drawer-head"><h3>Stripes &amp; plaids</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-stripes-body"></div></dialog>
+<dialog class="drawer" id="d-share"><div class="drawer-head"><h3>Share this design</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-share-body"></div></dialog>
+<dialog class="drawer" id="d-furnish"><div class="drawer-head"><h3>Furnish this design</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-furnish-body"></div></dialog>
+<dialog class="drawer" id="d-trade"><div class="drawer-head"><h3>Trade tools</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-trade-body"></div></dialog>
+<dialog class="drawer" id="d-ratings"><div class="drawer-head"><h3>Ratings &amp; reviews</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-ratings-body"></div></dialog>
+<dialog class="drawer" id="d-generator"><div class="drawer-head"><h3>Generator + prompt</h3><button class="drawer-close" onclick="this.closest('dialog').close()">×</button></div><div class="drawer-body" id="d-generator-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<div class="crumb">
+  <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span><a href="/design/${id - 2}?theme=drawer">← prev</a> · <a href="/design/${id + 2}?theme=drawer">next →</a></span>
+</div>
+
+<main class="minimal">
+  <div class="hero">
+    <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+    <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+    <span class="info">${escapeHtml(title)}</span>
+  </div>
+
+  <div class="title-card">
+    <h1 class="name">${escapeHtml(title)}</h1>
+    <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}<span class="sep">·</span>${escapeHtml(dominant)}<span class="sep">·</span>${escapeHtml(handle)}</div>
+  </div>
+
+  <div class="chip-rail">
+    <button class="drawer-chip" data-drawer="specs">Specs <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="calc">Calc <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="recolor">Recolor <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="adjust">Adjust <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="room">Room <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="pairs">Pairs <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="paints">Paints <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="stripes">Stripes <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="share">Share <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="furnish">Furnish <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="trade">Trade <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="ratings">Ratings <span class="arrow">↗</span></button>
+    <button class="drawer-chip" data-drawer="generator">Generator <span class="arrow">↗</span></button>
+  </div>
+
+  <div class="buy-box">
+    <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
+    <button class="cta buy" onclick="document.getElementById('d-calc').showModal()">Buy by the roll →</button>
+    <div class="summary">
+      <b id="buy-summary">— rolls · $—</b>
+      <span>tap <b>Calc</b> to configure</span>
+    </div>
+  </div>
+</main>
+
+<footer class="site">
+  <div class="h">wallco.ai</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+`;
+
+  // Populate drawer bodies
+  document.getElementById('d-specs-body').innerHTML = `
+    <div class="drawer-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+    <div class="drawer-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+    <div class="drawer-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+    <div class="drawer-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+    <div class="drawer-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+    <div class="drawer-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+    <div class="drawer-row"><span class="k">Material</span><span class="v">Type II vinyl, water-resist</span></div>
+    <div class="drawer-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+    <div class="drawer-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+    <div class="drawer-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+    <div class="drawer-row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div>
+    <div class="drawer-row"><span class="k">Status</span><span class="v">${design.is_published ? 'Live' : 'Curating'}</span></div>
+  `;
+
+  document.getElementById('d-calc-body').innerHTML = `
+    <div class="calc-mini">
+      <div class="row">
+        <div><label>Wall W (ft)</label><input id="dcw" type="number" min="1" max="80" step="0.5" value="10"></div>
+        <div><label>Wall H (ft)</label><input id="dch" type="number" min="1" max="20" step="0.5" value="9"></div>
+      </div>
+      <div class="row">
+        <div><label>Roll width</label><select id="dcrw"><option value="24">24" residential</option><option value="36">36" wide-roll</option><option value="52">52" commercial</option></select></div>
+        <div><label>Repeat</label><input value='${repeatLabel}' readonly></div>
+      </div>
+      <div class="total" id="dctotal">— rolls · $—</div>
+    </div>
+  `;
+  const dcw = document.getElementById('dcw'), dch = document.getElementById('dch'), dcrw = document.getElementById('dcrw');
+  const dctotal = document.getElementById('dctotal');
+  const buySummary = document.getElementById('buy-summary');
+  function recalc() {
+    const ww = parseFloat(dcw.value) || 0, wh = parseFloat(dch.value) || 0;
+    const rwInches = parseFloat(dcrw.value) || 24;
+    if (ww < 1 || wh < 1) { dctotal.textContent = '— rolls · $—'; buySummary.textContent = '— rolls · $—'; return; }
+    let txt;
+    if (isMural) {
+      const panels = Math.ceil((ww * 12) / rwInches); const cost = panels * 245;
+      txt = '<b>' + panels + '</b> panels · <b>$' + cost.toLocaleString() + '</b>';
+      buySummary.innerHTML = panels + ' panels · $' + cost.toLocaleString();
+    } else {
+      const widthPanels = Math.ceil((ww * 12) / rwInches);
+      const rollsPerPanel = Math.ceil(wh / 27);
+      const rolls = widthPanels * rollsPerPanel; const cost = rolls * 165;
+      txt = '<b>' + rolls + '</b> rolls · <b>$' + cost.toLocaleString() + '</b>';
+      buySummary.innerHTML = rolls + ' rolls · $' + cost.toLocaleString();
+    }
+    dctotal.innerHTML = txt;
+  }
+  [dcw, dch, dcrw].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  document.getElementById('d-recolor-body').innerHTML = `
+    <p style="font-size:13px; color:var(--drawer-mute); margin:0 0 14px">Drag a swatch onto the live preview to recolor a layer.</p>
+    <div class="swatches">
+      ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" title="${escapeAttr(p.hex)} · ${(p.pct || 0).toFixed(1)}%" style="background:${escapeAttr(p.hex)}"></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"></div>`}
+    </div>
+    <div style="font-size:11px; color:var(--drawer-mute); margin-top:10px">Dominant ${escapeHtml(dominant)}</div>
+  `;
+
+  document.getElementById('d-adjust-body').innerHTML = `
+    <div class="adjust">
+      <div class="sl"><label>Hue</label><input type="range" min="-180" max="180" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Saturation</label><input type="range" min="0" max="200" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Brightness</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Contrast</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Sepia</label><input type="range" min="0" max="100" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Blur</label><input type="range" min="0" max="20" value="0"><span class="v">0</span></div>
+    </div>
+  `;
+  document.querySelectorAll('#d-adjust-body .sl').forEach(row => {
+    const r = row.querySelector('input[type=range]'); const v = row.querySelector('.v');
+    r.addEventListener('input', () => { v.textContent = r.value; });
+  });
+
+  document.getElementById('d-room-body').innerHTML = `
+    <div class="room-mini" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}></div>
+    <div class="atscale-mini" style="background-image:url('${img}')"></div>
+    <p style="font-size:12px; color:var(--drawer-mute); margin-top:12px">Curated rendering · swap room template above</p>
+  `;
+
+  document.getElementById('d-pairs-body').innerHTML = `<div class="pair-grid"><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div></div><p style="font-size:12px; color:var(--drawer-mute); margin-top:12px">6 designer wallpapers that pair with ${escapeHtml(title)}</p>`;
+
+  document.getElementById('d-paints-body').innerHTML = `<div class="pill-list">
+    <a class="pill" href="#">Sherwin-Williams</a><a class="pill" href="#">Dunn-Edwards</a><a class="pill" href="#">Benjamin Moore</a><a class="pill" href="#">Farrow &amp; Ball</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a><a class="pill" href="#">RAL</a>
+  </div>`;
+
+  document.getElementById('d-stripes-body').innerHTML = `<div class="pair-grid" style="grid-template-columns:repeat(4,1fr)">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div><p style="font-size:12px; color:var(--drawer-mute); margin-top:12px">24 stripes &amp; plaids generated for this colorway</p>`;
+
+  document.getElementById('d-share-body').innerHTML = `<div class="tile-btns"><a class="tile-btn" href="#" onclick="event.preventDefault()">Pinterest</a><a class="tile-btn" href="#" onclick="event.preventDefault()">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();event.preventDefault()">Copy link</a></div>`;
+
+  document.getElementById('d-furnish-body').innerHTML = `<p style="font-size:13px; color:var(--drawer-mute); margin:0 0 14px">Generate AI furnishings curated to this palette.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>`;
+
+  document.getElementById('d-trade-body').innerHTML = `<div class="tile-btns"><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet PDF</a><a class="tile-btn" href="#">Email client</a><a class="tile-btn" href="#">Compare 2</a><a class="tile-btn" href="#" onclick="saveDesign(${id});event.preventDefault()">Save</a></div>`;
+
+  document.getElementById('d-ratings-body').innerHTML = `<div style="font-size:30px; letter-spacing:6px; color:#e8c66e">★★★★☆</div><div style="font-size:12px; color:var(--drawer-mute); margin-top:6px">Curated · 4.2 · ${escapeHtml(design.user_vote_count || 0)} votes</div>`;
+
+  document.getElementById('d-generator-body').innerHTML = `<div style="font-size:12px; color:var(--drawer-mute); margin-bottom:8px">Generator: <b style="color:var(--drawer-ink)">${escapeHtml(generator)}</b> · seed <code style="color:var(--drawer-ink)">${escapeHtml(design.seed || '—')}</code></div><pre>${escapeHtml((design.prompt || '').slice(0, 1500))}${(design.prompt && design.prompt.length > 1500) ? '\n…[truncated]' : ''}</pre>`;
+
+  // Wire chip click → open drawer
+  document.querySelectorAll('[data-drawer]').forEach(btn => {
+    btn.addEventListener('click', () => {
+      const name = btn.getAttribute('data-drawer');
+      const d = document.getElementById('d-' + name);
+      if (d && typeof d.showModal === 'function') d.showModal();
+    });
+  });
+})();
+
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/drawer$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function saveDesign(id) { alert('Saved #' + id + ' (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v7-editorial.html b/public/theme-variants/v7-editorial.html
new file mode 100644
index 0000000..2d0ab49
--- /dev/null
+++ b/public/theme-variants/v7-editorial.html
@@ -0,0 +1,353 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Editorial Magazine PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 14.5px/1.65 var(--sans); -webkit-font-smoothing: antialiased; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  /* hero — 70vh editorial */
+  .hero-wrap { position: relative; width: 100%; height: 78vh; max-height: 980px; min-height: 600px; background: var(--hero-bg); overflow: hidden; }
+  .hero-wrap img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .hero-wrap .overlay {
+    position: absolute; left: 0; right: 0; bottom: 0;
+    padding: 60px 40px 40px;
+    background: linear-gradient(to top, rgba(7,6,5,.88) 0%, rgba(7,6,5,.45) 60%, rgba(7,6,5,0) 100%);
+    color: #f0eada;
+  }
+  .hero-wrap .overlay .kicker { font: 600 11px/1 var(--sans); letter-spacing: .14em; color: var(--accent); text-transform: uppercase; margin-bottom: 12px; }
+  .hero-wrap .overlay h1 { font: 400 56px/1.05 var(--serif); margin: 0 0 12px; max-width: 1100px; }
+  .hero-wrap .overlay blockquote { font: 400 19px/1.5 var(--serif); font-style: italic; color: #d8cea8; margin: 0; max-width: 720px; padding-left: 18px; border-left: 2px solid var(--accent); }
+  .hero-wrap .overlay .meta { margin-top: 18px; font-size: 12.5px; color: #c2b88a; letter-spacing: .04em; }
+  .hero-wrap .pill { position: absolute; top: 18px; right: 24px; background: rgba(250,248,243,.94); color: var(--ink); font: 600 10.5px/1 var(--sans); letter-spacing: .06em; padding: 7px 12px; border-radius: 999px; text-transform: uppercase; }
+
+  /* breadcrumb */
+  .crumb { max-width: 1320px; margin: 0 auto; padding: 18px 32px 0; display: flex; gap: 16px; font-size: 12px; color: var(--muted); }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+
+  /* magazine body — 2 cols + marginalia */
+  main.editorial { max-width: 1320px; margin: 0 auto; padding: 28px 32px 0; }
+  .grid {
+    display: grid;
+    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 280px;
+    gap: 32px;
+    align-items: start;
+  }
+
+  .col1 .lead { font: 400 18px/1.65 var(--serif); color: var(--ink); }
+  .col1 .lead .drop { font: 500 56px/.85 var(--serif); float: left; margin: 6px 8px 0 0; color: var(--accent); }
+  .col1 .lead p { margin: 0 0 14px; }
+
+  .col2 h3 { font: 500 16px/1.2 var(--serif); margin: 0 0 12px; color: var(--ink); }
+  .pair-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
+  .pair-grid .pair { aspect-ratio: 1/1; background: var(--panel); border-radius: 6px; background-size: cover; background-position: center; border: 1px solid var(--line); }
+  .col2 .designer-note { margin-top: 14px; font: 400 13px/1.55 var(--serif); color: var(--muted); font-style: italic; }
+
+  /* marginalia (specs + palette) */
+  .margin {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 10px; padding: 16px;
+    font-size: 12.5px;
+    position: sticky; top: 70px;
+  }
+  .margin h4 { margin: 0 0 10px; font: 600 10.5px/1.2 var(--sans); text-transform: uppercase; letter-spacing: .12em; color: var(--muted); }
+  .specs-row { display: flex; justify-content: space-between; padding: 5px 0; border-bottom: 1px solid var(--line); font-size: 12px; }
+  .specs-row:last-child { border-bottom: 0; }
+  .specs-row .k { color: var(--muted); }
+  .specs-row .v { font-weight: 600; text-align: right; }
+  .margin .swatches { display: flex; gap: 6px; flex-wrap: wrap; margin: 8px 0 4px; }
+  .margin .sw { width: 28px; height: 28px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .margin .pull { margin-top: 16px; padding-top: 16px; border-top: 1px solid var(--line); }
+  .margin .pull a { display: block; padding: 6px 0; font-size: 12px; color: var(--ink); text-decoration: none; }
+  .margin .pull a:hover { color: var(--accent); }
+  .margin .pull a:after { content: ' ↗'; color: var(--muted); }
+
+  /* at-scale interrupt */
+  .interrupt {
+    width: 100%;
+    height: 460px;
+    margin: 48px 0 0;
+    background: var(--hero-bg);
+    background-repeat: repeat;
+    background-size: 220px 220px;
+    position: relative;
+  }
+  .interrupt .label {
+    position: absolute; left: 32px; bottom: 28px;
+    background: rgba(26,22,17,.85); color: #f0eada;
+    font: 600 11.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 10px 16px; border-radius: 6px; text-transform: uppercase;
+  }
+  .interrupt .slider {
+    position: absolute; right: 32px; bottom: 28px;
+    background: rgba(250,248,243,.96); border-radius: 999px; padding: 8px 16px;
+    font: 500 11px/1 var(--sans); display: flex; align-items: center; gap: 10px;
+  }
+  .interrupt .slider input[type=range] { width: 160px; accent-color: var(--accent); }
+
+  /* buy ribbon below interrupt */
+  .buy-ribbon {
+    background: var(--accent-dk); color: #f0eada;
+    padding: 26px 32px;
+    display: flex; gap: 18px; align-items: center; flex-wrap: wrap;
+  }
+  .buy-ribbon .cta {
+    display: inline-flex; align-items: center; justify-content: center;
+    padding: 16px 24px; border-radius: 8px; border: 0;
+    font: 700 12.5px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+  }
+  .buy-ribbon .cta.sample { background: var(--accent); color: var(--accent-dk); }
+  .buy-ribbon .cta.buy { background: var(--bg); color: var(--ink); }
+  .buy-ribbon .calc { background: rgba(255,255,255,.08); border: 1px solid rgba(255,255,255,.14); padding: 14px 20px; border-radius: 8px; display: flex; gap: 10px; flex-wrap: wrap; align-items: center; flex: 1; min-width: 280px; }
+  .buy-ribbon .calc label { font-size: 11px; color: #c2b88a; }
+  .buy-ribbon .calc input { width: 64px; padding: 6px 8px; border-radius: 4px; border: 1px solid rgba(255,255,255,.18); background: rgba(0,0,0,.25); color: #f0eada; font: inherit; }
+  .buy-ribbon .calc .summary { font: 500 14px var(--serif); margin-left: auto; }
+
+  /* collapsed footer rows */
+  .footer-acc { max-width: 1320px; margin: 30px auto 0; padding: 0 32px 40px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
+  details.acc { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; font-size: 13px; }
+  details.acc[open] { padding-bottom: 14px; }
+  details.acc summary { cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; align-items: center; }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 11px; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11.5px; color: var(--muted); margin-left: auto; margin-right: 10px; }
+  details.acc .acc-body { margin-top: 12px; line-height: 1.6; font-size: 12.5px; }
+  .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); padding: 10px 8px; border-radius: 8px; font: 600 11.5px/1.2 var(--sans); color: var(--ink); text-align: center; text-decoration: none; }
+  .tile-btn:hover { background: #fff; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 28px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+
+  dialog.modal { border: 0; border-radius: 14px; padding: 0; max-width: 540px; width: 92vw; box-shadow: 0 24px 80px rgba(0,0,0,.25); background: var(--bg); }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  @media (max-width: 1024px) {
+    .grid { grid-template-columns: 1fr; }
+    .margin { position: static; }
+    .hero-wrap .overlay h1 { font-size: 38px; }
+    .interrupt { height: 320px; }
+  }
+  @media (max-width: 640px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    .hero-wrap .overlay { padding: 30px 16px 18px; }
+    .hero-wrap .overlay h1 { font-size: 30px; }
+    main.editorial { padding: 18px 16px 0; }
+    .footer-acc { grid-template-columns: 1fr; padding: 0 16px 24px; }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav>
+    <a href="/designs?sort=newest">Designs</a><a href="/designs?cat=monterey-mural">Murals</a><a href="/samples">Samples</a><a href="/basket">Basket</a><a href="/designer-studio">Designer Studio</a>
+  </nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Editorial Magazine (V7) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+<dialog class="modal" id="m-trade"><header><h3>Trade tools</h3><button class="close" onclick="document.getElementById('m-trade').close()">×</button></header><div class="body" id="m-trade-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  // ── editorial copy (templated from design data) ──
+  const pullQuote = isMural
+    ? '“A scenic wallcovering meant to be lived inside, not merely seen.”'
+    : '“Tone-on-tone restraint — a pattern that listens before it speaks.”';
+  const leadCopy = `<p><span class="drop">${escapeHtml(title.charAt(0))}</span>${escapeHtml(title.slice(1))} arrives from the <em>${escapeHtml(catDisplay)}</em> series — a quiet study in ${escapeHtml(palette.length > 2 ? 'layered neutrals' : 'two-color restraint')} anchored by a ${escapeHtml(dominant)} ground. ${escapeHtml(kindLabel)}, ${rollOrPanel.toLowerCase()}.</p><p>The composition reads as a single hand-blocked screen rather than a digital pattern. Motifs are placed with the discipline of a herbarium plate: each instance the same scale, the same orientation, the same ink — repetition as architecture rather than decoration.</p><p>Specified by interior designers for ${escapeHtml(palette.length > 3 ? 'salon walls and powder rooms' : 'libraries and reading corners')}, it sits well alongside hand-loomed neutrals and lacquered millwork.</p>`;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+
+<div class="hero-wrap">
+  <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+  <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+  <div class="overlay">
+    <div class="kicker">${escapeHtml(catDisplay)} · No.${id}</div>
+    <h1>${escapeHtml(title)}</h1>
+    <blockquote>${escapeHtml(pullQuote)}</blockquote>
+    <div class="meta">${escapeHtml(kindLabel)} · ${rollOrPanel} · ${escapeHtml(handle)}</div>
+  </div>
+</div>
+
+<div class="crumb">
+  <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span><a href="/design/${id - 2}?theme=editorial">← prev</a> · <a href="/design/${id + 2}?theme=editorial">next →</a></span>
+</div>
+
+<main class="editorial">
+  <div class="grid">
+    <div class="col1">
+      <div class="lead">${leadCopy}</div>
+    </div>
+    <div class="col2">
+      <h3>Pairs well with</h3>
+      <div class="pair-grid">
+        <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+        <div class="pair"></div><div class="pair"></div><div class="pair"></div>
+      </div>
+      <p class="designer-note">Six designer wallpapers selected from the wallco.ai catalog whose palette and scale complement ${escapeHtml(title)}.</p>
+    </div>
+    <aside class="margin">
+      <h4>Specifications</h4>
+      <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+      <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+      <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="specs-row"><span class="k">${isMural ? 'Panel' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div>
+      <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <h4 style="margin-top:18px">Palette</h4>
+      <div class="swatches">
+        ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" title="${escapeAttr(p.hex)} · ${(p.pct || 0).toFixed(1)}%" style="background:${escapeAttr(p.hex)}"></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"></div>`}
+      </div>
+      <div style="font-size:11px; color:var(--muted); margin-top:6px">Dominant ${escapeHtml(dominant)}</div>
+      <div class="pull">
+        <a href="#interrupt">See at scale ↓</a>
+        <a href="#" onclick="openShare();return false">Share this design</a>
+        <a href="#" onclick="openFurnish();return false">Furnish with this</a>
+        <a href="/design/${id}/tearsheet" target="_blank">Tear sheet PDF</a>
+      </div>
+    </aside>
+  </div>
+</main>
+
+<div class="interrupt" id="interrupt" style="background-image:url('${img}')">
+  <span class="label">At-scale preview · 10 × 9 ft wall</span>
+  <span class="slider">scale <input type="range" min="120" max="400" value="220" id="scale-slider"></span>
+</div>
+
+<div class="buy-ribbon">
+  <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
+  <button class="cta buy" onclick="document.getElementById('rcw').focus()">Buy by the roll →</button>
+  <div class="calc">
+    <label>W</label><input id="rcw" type="number" min="1" max="80" step="0.5" value="10">
+    <label>×&nbsp;H</label><input id="rch" type="number" min="1" max="20" step="0.5" value="9">
+    <label>Roll</label>
+    <select id="rcrw" style="padding:6px 8px; border-radius:4px; border:1px solid rgba(255,255,255,.18); background:rgba(0,0,0,.25); color:#f0eada; font:inherit"><option value="24">24"</option><option value="36">36"</option><option value="52">52"</option></select>
+    <span class="summary" id="rsum">— rolls · $—</span>
+  </div>
+</div>
+
+<div class="footer-acc">
+  <details class="acc"><summary>Coordinate paints <span class="hint">SW · BM · F&amp;B</span></summary><div class="acc-body"><div class="pill-list"><a class="pill" href="#">Sherwin-Williams</a><a class="pill" href="#">Dunn-Edwards</a><a class="pill" href="#">Benjamin Moore</a><a class="pill" href="#">Farrow &amp; Ball</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a></div></div></details>
+  <details class="acc"><summary>Coordinate stripes &amp; plaids <span class="hint">24 generated</span></summary><div class="acc-body"><div class="pair-grid" style="grid-template-columns:repeat(6,1fr)">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div></div></details>
+  <details class="acc"><summary>See in a room <span class="hint">residential preview</span></summary><div class="acc-body"><div style="height:200px; border-radius:8px; background:var(--room); background-size:cover; background-position:center; ${roomImgUrl ? `background-image:url('${roomImgUrl}')` : ''}"></div></div></details>
+  <details class="acc"><summary>Image adjustments <span class="hint">hue/sat/bri/contrast</span></summary><div class="acc-body" style="font-size:12px; color:var(--muted)">Sliders for hue · saturation · brightness · contrast · sepia · blur (omitted in editorial; open in Classic view)</div></details>
+  <details class="acc"><summary>Share / Furnish / Trade tools <span class="hint">3 actions</span></summary><div class="acc-body"><div class="tile-btns"><a class="tile-btn" href="#" onclick="openShare();return false">Share</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Furnish</a><a class="tile-btn" href="#" onclick="openTrade();return false">Trade tools</a></div></div></details>
+  <details class="acc"><summary>Ratings &amp; reviews <span class="hint">★★★★☆ · ${escapeHtml(design.user_vote_count || 0)}</span></summary><div class="acc-body"><div style="font-size:22px; letter-spacing:4px; color:#b89a4a">★★★★☆</div><div style="font-size:11.5px; color:var(--muted); margin-top:4px">Curated · 4.2</div></div></details>
+  <details class="acc"><summary>Generator + prompt <span class="hint">wallco.ai · seed ${escapeHtml(design.seed || '—')}</span></summary><div class="acc-body"><pre style="font:11px/1.5 ui-monospace,Menlo,monospace; background:var(--panel); padding:10px; border-radius:6px; max-height:200px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1200))}${(design.prompt && design.prompt.length > 1200) ? '\n…[truncated]' : ''}</pre></div></details>
+  <details class="acc"><summary>Full specs ledger <span class="hint">12 fields</span></summary><div class="acc-body"><div class="specs-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div><div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div><div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div><div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div><div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div><div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div><div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div><div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div><div class="specs-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div><div class="specs-row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div><div class="specs-row"><span class="k">Status</span><span class="v">${design.is_published ? 'Live' : 'Curating'}</span></div></div></details>
+</div>
+
+<footer class="site">
+  <div class="h">wallco.ai — Curated-original wallpaper &amp; murals</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+`;
+
+  // ribbon calc
+  const rcw = document.getElementById('rcw'), rch = document.getElementById('rch'), rcrw = document.getElementById('rcrw');
+  const rsum = document.getElementById('rsum');
+  function recalc() {
+    const ww = parseFloat(rcw.value) || 0, wh = parseFloat(rch.value) || 0;
+    const rwi = parseFloat(rcrw.value) || 24;
+    if (ww < 1 || wh < 1) { rsum.textContent = '— rolls · $—'; return; }
+    if (isMural) { const p = Math.ceil((ww * 12) / rwi); rsum.textContent = p + ' panels · $' + (p * 245).toLocaleString(); }
+    else { const wp = Math.ceil((ww * 12) / rwi); const r = wp * Math.ceil(wh / 27); rsum.textContent = r + ' rolls · $' + (r * 165).toLocaleString(); }
+  }
+  [rcw, rch, rcrw].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  // scale slider
+  const scale = document.getElementById('scale-slider');
+  const interrupt = document.getElementById('interrupt');
+  scale.addEventListener('input', () => { interrupt.style.backgroundSize = scale.value + 'px ' + scale.value + 'px'; });
+
+  document.getElementById('m-share-body').innerHTML = '<p>Share <b>' + escapeHtml(title) + '</b></p><div class="tile-btns"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML = '<p>Generate AI furnishings.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+  document.getElementById('m-trade-body').innerHTML = '<p>Trade tools.</p><div class="tile-btns"><a class="tile-btn" href="/design/' + id + '/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Email</a><a class="tile-btn" href="#">Compare</a><a class="tile-btn" href="#">Save</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+function openTrade() { document.getElementById('m-trade').showModal(); }
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/editorial$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v8-spec.html b/public/theme-variants/v8-spec.html
new file mode 100644
index 0000000..09a19a7
--- /dev/null
+++ b/public/theme-variants/v8-spec.html
@@ -0,0 +1,342 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Compact Spec-Sheet PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+    --mono: ui-monospace, Menlo, "SF Mono", monospace;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 13px/1.4 var(--sans); -webkit-font-smoothing: antialiased; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 8px 24px;
+    display: flex; align-items: center; gap: 20px;
+  }
+  header .brand { font: 600 17px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12px; color: var(--muted); display: flex; gap: 16px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  .crumb { max-width: 1640px; margin: 0 auto; padding: 12px 24px 0; display: flex; gap: 14px; font-size: 11.5px; color: var(--muted); }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+
+  main.specsheet {
+    max-width: 1640px; margin: 0 auto; padding: 12px 24px 40px;
+    display: grid; grid-template-columns: minmax(0, 720px) minmax(0, 1fr);
+    gap: 20px; align-items: start;
+  }
+
+  .col-image { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
+  .image-main {
+    aspect-ratio: 1/1; background: var(--hero-bg);
+    border-radius: 8px; overflow: hidden; position: relative;
+    box-shadow: 0 1px 0 var(--line);
+  }
+  .image-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .image-main .pill {
+    position: absolute; top: 12px; right: 12px;
+    background: rgba(250,248,243,.94); color: var(--ink);
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 6px 10px; border-radius: 999px; text-transform: uppercase;
+  }
+  .thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
+  .thumb {
+    aspect-ratio: 1/1; background: var(--panel); border: 1px solid var(--line);
+    border-radius: 6px; background-size: cover; background-position: center;
+    cursor: pointer; transition: border-color .12s;
+  }
+  .thumb:hover { border-color: var(--accent); }
+  .chip-strip {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 6px; padding: 8px 12px;
+    font: 600 11px/1.4 var(--mono); color: var(--ink); letter-spacing: .02em;
+    display: flex; flex-wrap: wrap; gap: 12px; align-items: center;
+  }
+  .chip-strip .sep { color: var(--muted); }
+
+  /* right column — dense spec table */
+  .col-spec { min-width: 0; }
+  .name-bar {
+    background: var(--bg); border: 1px solid var(--line);
+    border-radius: 8px; padding: 14px 18px; margin-bottom: 12px;
+  }
+  .name-bar h1 { font: 500 26px/1.15 var(--serif); margin: 0 0 4px; }
+  .name-bar .meta { color: var(--muted); font-size: 12px; }
+
+  .cta-row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; margin-bottom: 12px; }
+  .cta {
+    display: flex; align-items: center; justify-content: center;
+    border-radius: 8px; padding: 14px;
+    font: 700 12px/1 var(--sans); letter-spacing: .08em; text-transform: uppercase;
+    border: 1px solid transparent;
+  }
+  .cta.sample { background: var(--accent); color: var(--accent-dk); }
+  .cta.buy { background: var(--accent-dk); color: #f0eada; }
+
+  .spec-table {
+    background: var(--bg); border: 1px solid var(--line);
+    border-radius: 8px; overflow: hidden;
+  }
+  .spec-table .row {
+    display: grid; grid-template-columns: 200px 1fr;
+    align-items: center; padding: 7px 14px;
+    font-size: 12px; line-height: 1.4;
+    border-bottom: 1px solid var(--line);
+  }
+  .spec-table .row:last-child { border-bottom: 0; }
+  .spec-table .row:nth-child(odd) { background: var(--panel); }
+  .spec-table .row .k { color: var(--muted); font: 600 10.5px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spec-table .row .v { color: var(--ink); font-weight: 500; font-family: var(--mono); font-size: 12.5px; }
+
+  .calc-compact {
+    background: var(--calc); border-radius: 8px; padding: 12px 14px; margin-top: 12px;
+  }
+  .calc-compact h4 { margin: 0 0 8px; font: 600 10.5px/1.2 var(--sans); text-transform: uppercase; letter-spacing: .08em; color: #5a4f73; }
+  .calc-compact .row { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
+  .calc-compact label { font-size: 10.5px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc-compact input, .calc-compact select { width: 100%; padding: 6px 8px; border: 1px solid var(--line); border-radius: 5px; font: inherit; background: #fff; font-size: 12px; }
+  .calc-compact .total { margin-top: 8px; padding: 8px 10px; background: rgba(255,255,255,.6); border-radius: 5px; font-size: 12.5px; }
+  .calc-compact .total b { font: 500 16px var(--serif); }
+
+  details.acc {
+    margin-top: 8px; background: #fff; border: 1px solid var(--line); border-radius: 6px;
+    padding: 8px 14px; font-size: 12px;
+  }
+  details.acc[open] { padding-bottom: 12px; }
+  details.acc summary { cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; align-items: center; }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 10px; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11px; color: var(--muted); margin-left: auto; margin-right: 8px; }
+  details.acc .acc-body { margin-top: 10px; font-size: 12px; line-height: 1.55; }
+  .pill-list { display: flex; flex-wrap: wrap; gap: 5px; }
+  .pill { font-size: 10.5px; padding: 4px 8px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 5px; }
+  .tile-btn { flex: 1; min-width: 90px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); padding: 8px 6px; border-radius: 6px; font: 600 11px/1.2 var(--sans); color: var(--ink); text-align: center; text-decoration: none; }
+  .pair-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 5px; }
+  .pair-grid .pair { aspect-ratio: 1/1; background: rgba(0,0,0,.06); border-radius: 4px; background-size: cover; background-position: center; }
+  .swatches { display: flex; gap: 6px; flex-wrap: wrap; }
+  .sw { width: 28px; height: 28px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+
+  footer.site { border-top: 1px solid var(--line); padding: 22px; text-align: center; color: var(--muted); font-size: 11.5px; background: var(--panel); }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+
+  dialog.modal { border: 0; border-radius: 14px; padding: 0; max-width: 540px; width: 92vw; box-shadow: 0 24px 80px rgba(0,0,0,.25); background: var(--bg); }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  @media (max-width: 1024px) { main.specsheet { grid-template-columns: 1fr; } .calc-compact .row { grid-template-columns: 1fr 1fr; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.specsheet { padding: 12px 14px; } .thumbs { grid-template-columns: repeat(2, 1fr); } .cta-row { grid-template-columns: 1fr; } }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav><a href="/designs?sort=newest">Designs</a><a href="/designs?cat=monterey-mural">Murals</a><a href="/samples">Samples</a><a href="/basket">Basket</a></nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Compact Spec-Sheet (V8) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const paletteStr = palette.length ? palette.slice(0, 5).map(p => p.hex).join(' · ') : dominant;
+  const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<div class="crumb">
+  <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span><a href="/design/${id - 2}?theme=spec">← prev</a> · <a href="/design/${id + 2}?theme=spec">next →</a></span>
+</div>
+
+<main class="specsheet">
+  <div class="col-image">
+    <div class="image-main">
+      <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+      <span class="pill">${design.is_published ? 'Live' : 'Curating'}</span>
+    </div>
+    <div class="thumbs">
+      <div class="thumb" style="background-image:url('${img}')"></div>
+      <div class="thumb" style="background-image:url('${img}'); background-size: 60%;"></div>
+      <div class="thumb" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}></div>
+      <div class="thumb" style="background-image:url('${img}'); background-size: 30%;"></div>
+    </div>
+    <div class="chip-strip">
+      <span>${escapeHtml(catDisplay)}</span><span class="sep">·</span>
+      <span>${escapeHtml(kindLabel)}</span><span class="sep">·</span>
+      <span>${w}×${h}"</span><span class="sep">·</span>
+      <span>${escapeHtml(dominant)}</span><span class="sep">·</span>
+      <span>DIG#${id}</span><span class="sep">·</span>
+      <span>edges PASS</span>
+    </div>
+  </div>
+
+  <div class="col-spec">
+    <div class="name-bar">
+      <h1>${escapeHtml(title)}</h1>
+      <div class="meta">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    </div>
+
+    <div class="cta-row">
+      <button class="cta sample" onclick="addSample()">Add sample · $4.25</button>
+      <button class="cta buy" onclick="document.getElementById('scw').focus()">Buy by the roll →</button>
+    </div>
+
+    <div class="spec-table">
+      <div class="row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+      <div class="row"><span class="k">Width</span><span class="v">${w}" (${escapeHtml(rollOrPanel.toLowerCase())})</span></div>
+      <div class="row"><span class="k">Material</span><span class="v">Type II vinyl · water-resist · class A</span></div>
+      <div class="row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="row"><span class="k">Repeat</span><span class="v">${repeatLabel} straight match</span></div>
+      <div class="row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+      <div class="row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+      <div class="row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <div class="row"><span class="k">Dominant hex</span><span class="v">${escapeHtml(dominant)}</span></div>
+      <div class="row"><span class="k">Palette</span><span class="v">${escapeHtml(paletteStr)}</span></div>
+      <div class="row"><span class="k">${isMural ? 'Orientation' : 'Mural / tile'}</span><span class="v">${isMural ? 'Pre-pasted, hangs L→R' : 'Tile · 24" repeat'}</span></div>
+      <div class="row"><span class="k">Walls compatible</span><span class="v">Smooth · textured · drywall</span></div>
+      <div class="row"><span class="k">Lead time</span><span class="v">3–5 business days</span></div>
+      <div class="row"><span class="k">Settlement</span><span class="v">OK (audit logged)</span></div>
+      <div class="row"><span class="k">Edges scan</span><span class="v">PASS · 6-lens ΔE&lt;tol</span></div>
+      <div class="row"><span class="k">Ghost layer</span><span class="v">clean</span></div>
+      <div class="row"><span class="k">Rating (curated)</span><span class="v">★★★★☆ 4.2 · ${escapeHtml(design.user_vote_count || 0)} votes</span></div>
+      <div class="row"><span class="k">Status</span><span class="v">${design.is_published ? 'Live on wallco.ai' : 'Curating · is_published=FALSE'}</span></div>
+    </div>
+
+    <div class="calc-compact">
+      <h4>Calculator</h4>
+      <div class="row">
+        <div><label>Wall W (ft)</label><input id="scw" type="number" min="1" max="80" step="0.5" value="10"></div>
+        <div><label>Wall H (ft)</label><input id="sch" type="number" min="1" max="20" step="0.5" value="9"></div>
+        <div><label>Roll W</label><select id="scrw"><option value="24">24"</option><option value="36">36"</option><option value="52">52"</option></select></div>
+        <div><label>Repeat</label><input value='${repeatLabel}' readonly></div>
+      </div>
+      <div class="total" id="stotal">— rolls · $—</div>
+    </div>
+
+    <details class="acc"><summary>Palette + recolor <span class="hint">${palette.length || 1} colors · drag to recolor</span></summary>
+      <div class="acc-body">
+        <div class="swatches">
+          ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" title="${escapeAttr(p.hex)} · ${(p.pct || 0).toFixed(1)}%" style="background:${escapeAttr(p.hex)}"></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"></div>`}
+        </div>
+      </div>
+    </details>
+
+    <details class="acc"><summary>Image adjustments <span class="hint">hue/sat/bri/contrast</span></summary>
+      <div class="acc-body" style="color:var(--muted)">Sliders for hue · saturation · brightness · contrast · sepia · blur (compact view — full controls in Classic)</div>
+    </details>
+
+    <details class="acc"><summary>See in a room <span class="hint">residential preview</span></summary>
+      <div class="acc-body"><div style="height:200px; border-radius:6px; background:var(--room); background-size:cover; background-position:center; ${roomImgUrl ? `background-image:url('${roomImgUrl}')` : ''}"></div></div>
+    </details>
+
+    <details class="acc"><summary>Pairs well with <span class="hint">6 designs</span></summary>
+      <div class="acc-body"><div class="pair-grid"><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div><div class="pair"></div></div></div>
+    </details>
+
+    <details class="acc"><summary>Coordinate paints + stripes &amp; plaids <span class="hint">7 brands · 24 stripes</span></summary>
+      <div class="acc-body">
+        <div class="pill-list" style="margin-bottom:10px"><a class="pill" href="#">Sherwin-Williams</a><a class="pill" href="#">Dunn-Edwards</a><a class="pill" href="#">Benjamin Moore</a><a class="pill" href="#">Farrow &amp; Ball</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a><a class="pill" href="#">RAL</a></div>
+        <div class="pair-grid">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div>
+      </div>
+    </details>
+
+    <details class="acc"><summary>Share · Furnish · Trade <span class="hint">3 actions</span></summary>
+      <div class="acc-body"><div class="tile-btns"><a class="tile-btn" href="#" onclick="openShare();return false">Share</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Furnish</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Email client</a><a class="tile-btn" href="#">Save</a></div></div>
+    </details>
+
+    <details class="acc"><summary>Generator + prompt <span class="hint">wallco.ai · seed ${escapeHtml(design.seed || '—')}</span></summary>
+      <div class="acc-body"><pre style="font:11px/1.45 var(--mono); background:var(--panel); padding:10px; border-radius:5px; max-height:180px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1000))}${(design.prompt && design.prompt.length > 1000) ? '\n…[truncated]' : ''}</pre></div>
+    </details>
+  </div>
+</main>
+
+<footer class="site">
+  <div class="h">wallco.ai</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+`;
+
+  const scw = document.getElementById('scw'), sch = document.getElementById('sch'), scrw = document.getElementById('scrw');
+  const stotal = document.getElementById('stotal');
+  function recalc() {
+    const ww = parseFloat(scw.value) || 0, wh = parseFloat(sch.value) || 0;
+    const rwi = parseFloat(scrw.value) || 24;
+    if (ww < 1 || wh < 1) { stotal.textContent = '— rolls · $—'; return; }
+    if (isMural) { const p = Math.ceil((ww * 12) / rwi); stotal.innerHTML = '<b>' + p + '</b> panels · <b>$' + (p * 245).toLocaleString() + '</b>'; }
+    else { const wp = Math.ceil((ww * 12) / rwi); const r = wp * Math.ceil(wh / 27); stotal.innerHTML = '<b>' + r + '</b> rolls · <b>$' + (r * 165).toLocaleString() + '</b> · ' + wp + ' panels wide'; }
+  }
+  [scw, sch, scrw].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  document.getElementById('m-share-body').innerHTML = '<p>Share <b>' + escapeHtml(title) + '</b></p><div class="tile-btns"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML = '<p>Generate AI furnishings.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/spec$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/public/theme-variants/v9-config.html b/public/theme-variants/v9-config.html
new file mode 100644
index 0000000..2f91a92
--- /dev/null
+++ b/public/theme-variants/v9-config.html
@@ -0,0 +1,376 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Configurator-First PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    --bg: #faf8f3; --ink: #1a1611; --muted: #8a8276; --line: #e2dccd;
+    --panel: #f3efe5; --panel-2: #ebe5d6; --accent: #c9b07a; --accent-dk: #1a1611;
+    --hero-bg: #e6dfd2; --calc: #ddd4ec; --coord: #d3e3e5;
+    --share: #f3dcce; --room: #cfdac4; --rate: #f0e7c3; --trade: #e6ddd2;
+    --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Arial, sans-serif;
+    --serif: "Cormorant Garamond", Georgia, serif;
+  }
+  * { box-sizing: border-box; }
+  html, body { margin: 0; padding: 0; }
+  body { background: var(--bg); color: var(--ink); font: 14px/1.5 var(--sans); -webkit-font-smoothing: antialiased; }
+  a { color: inherit; } button { font: inherit; cursor: pointer; }
+
+  header.topbar {
+    position: sticky; top: 0; z-index: 30;
+    background: rgba(250,248,243,.92); backdrop-filter: blur(10px);
+    border-bottom: 1px solid var(--line); padding: 10px 28px;
+    display: flex; align-items: center; gap: 24px;
+  }
+  header .brand { font: 600 18px/1 var(--serif); letter-spacing: .05em; }
+  header nav { font-size: 12.5px; color: var(--muted); display: flex; gap: 18px; flex-wrap: wrap; }
+  header nav a { text-decoration: none; }
+  header .grow { flex: 1; }
+  header .switcher { font-size: 11px; color: var(--muted); }
+  header .switcher a { text-decoration: underline; color: var(--ink); margin-left: 8px; }
+
+  .crumb { max-width: 1640px; margin: 0 auto; padding: 16px 28px 0; display: flex; gap: 16px; font-size: 12px; color: var(--muted); }
+  .crumb .grow { flex: 1; }
+  .crumb a { color: var(--ink); text-decoration: none; }
+
+  main.config { max-width: 1640px; margin: 0 auto; padding: 18px 28px 60px; display: flex; flex-direction: column; gap: 22px; }
+
+  /* CONFIGURATOR up top */
+  .configurator {
+    background: var(--calc);
+    border: 1px solid var(--line);
+    border-radius: 14px;
+    padding: 24px;
+  }
+  .configurator h2 { margin: 0 0 4px; font: 500 24px/1.1 var(--serif); color: #2c2640; }
+  .configurator .tag { font-size: 12px; color: #5a4f73; letter-spacing: .05em; }
+  .steps { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 18px; }
+  .step {
+    background: var(--bg); border: 1px solid var(--line);
+    border-radius: 10px; padding: 14px 16px;
+  }
+  .step .num { font: 600 10.5px/1 var(--sans); color: var(--accent); letter-spacing: .1em; text-transform: uppercase; }
+  .step .lbl { font: 600 13px/1.3 var(--sans); margin: 4px 0 6px; color: var(--ink); }
+  .step .input { display: flex; gap: 6px; align-items: center; }
+  .step input, .step select { width: 100%; padding: 7px 8px; border: 1px solid var(--line); border-radius: 5px; font: inherit; background: #fff; font-size: 13px; }
+  .step .ghost { background: var(--panel); border-radius: 5px; padding: 7px 9px; font: 500 13px var(--serif); color: var(--ink); }
+  .big-cta {
+    margin-top: 18px;
+    background: var(--accent); color: var(--accent-dk);
+    border: 0; border-radius: 10px; padding: 22px;
+    font: 700 16px/1 var(--sans); letter-spacing: .1em; text-transform: uppercase;
+    width: 100%; cursor: pointer;
+    display: flex; align-items: center; justify-content: center; gap: 16px;
+  }
+  .big-cta .est { font: 500 16px var(--serif); text-transform: none; letter-spacing: 0; opacity: .85; }
+
+  /* 3-col preview row */
+  .preview-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
+  .preview {
+    background: var(--hero-bg); border-radius: 12px; aspect-ratio: 4/5;
+    overflow: hidden; position: relative;
+  }
+  .preview img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .preview .badge {
+    position: absolute; top: 12px; left: 12px;
+    background: rgba(26,22,17,.8); color: #f0eada;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em;
+    padding: 6px 10px; border-radius: 999px; text-transform: uppercase;
+  }
+  .preview.room { background: var(--room); background-size: cover; background-position: center; }
+  .preview.atscale { background-repeat: repeat; background-size: 220px 220px; }
+  .preview.atscale .scale-pill { position: absolute; right: 12px; bottom: 12px; background: rgba(250,248,243,.94); border-radius: 999px; padding: 6px 12px; font: 500 11px var(--sans); display: flex; gap: 8px; align-items: center; }
+  .preview.atscale .scale-pill input[type=range] { width: 90px; accent-color: var(--accent); }
+  .preview .room-placeholder { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--muted); font-size: 12px; font-style: italic; }
+
+  .title-strip {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 10px; padding: 14px 18px;
+    display: flex; align-items: baseline; gap: 14px; flex-wrap: wrap;
+  }
+  .title-strip h1 { font: 500 22px/1.15 var(--serif); margin: 0; }
+  .title-strip .meta { color: var(--muted); font-size: 12.5px; }
+  .title-strip .meta .sep { padding: 0 6px; opacity: .55; }
+
+  /* bottom 3-col helper row */
+  .bottom-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 18px; }
+  .card {
+    background: var(--panel); border: 1px solid var(--line);
+    border-radius: 10px; padding: 16px;
+  }
+  .card h3 { margin: 0 0 12px; font: 600 11px/1.2 var(--sans); text-transform: uppercase; letter-spacing: .08em; color: var(--muted); }
+  .card.trade { background: var(--trade); }
+  .swatches { display: flex; gap: 10px; flex-wrap: wrap; }
+  .sw { width: 36px; height: 36px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .adjust .sl { display: grid; grid-template-columns: 80px 1fr 34px; align-items: center; gap: 8px; font-size: 11.5px; padding: 3px 0; }
+  .adjust .sl input[type=range] { accent-color: var(--accent); width: 100%; }
+  .adjust .sl .v { font-variant-numeric: tabular-nums; color: var(--muted); text-align: right; }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 90px; background: rgba(255,255,255,.7); border: 1px solid rgba(0,0,0,.06); padding: 10px 8px; border-radius: 8px; font: 600 11.5px/1.2 var(--sans); color: var(--ink); text-align: center; text-decoration: none; }
+  .tile-btn:hover { background: #fff; }
+
+  /* footer collapsibles */
+  .footer-acc { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; }
+  details.acc { background: #fff; border: 1px solid var(--line); border-radius: 8px; padding: 12px 16px; font-size: 13px; }
+  details.acc[open] { padding-bottom: 14px; }
+  details.acc summary { cursor: pointer; font-weight: 600; list-style: none; display: flex; justify-content: space-between; align-items: center; }
+  details.acc summary::after { content: '▾'; color: var(--muted); transition: transform .2s; font-size: 11px; }
+  details.acc[open] summary::after { transform: rotate(180deg); }
+  details.acc summary .hint { font-weight: 400; font-size: 11.5px; color: var(--muted); margin-left: auto; margin-right: 8px; }
+  details.acc .acc-body { margin-top: 12px; font-size: 12.5px; line-height: 1.55; }
+  .specs-row { display: flex; justify-content: space-between; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .specs-row:last-child { border-bottom: 0; }
+  .specs-row .k { color: var(--muted); }
+  .specs-row .v { font-weight: 600; text-align: right; }
+  .pill-list { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 9px; border-radius: 999px; background: rgba(0,0,0,.04); border: 1px solid rgba(0,0,0,.06); }
+  .pair-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 6px; }
+  .pair-grid .pair { aspect-ratio: 1/1; background: rgba(0,0,0,.06); border-radius: 6px; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 28px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); margin-top: 30px; }
+  footer.site .h { font: 500 14px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading-shell { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+
+  dialog.modal { border: 0; border-radius: 14px; padding: 0; max-width: 540px; width: 92vw; box-shadow: 0 24px 80px rgba(0,0,0,.25); background: var(--bg); }
+  dialog.modal::backdrop { background: rgba(20,17,14,.55); backdrop-filter: blur(4px); }
+  dialog.modal header { display: flex; justify-content: space-between; align-items: center; padding: 18px 22px; border-bottom: 1px solid var(--line); }
+  dialog.modal header h3 { margin: 0; font: 500 18px/1 var(--serif); }
+  dialog.modal .close { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  dialog.modal .body { padding: 18px 22px 24px; }
+
+  @media (max-width: 1024px) {
+    .steps { grid-template-columns: repeat(2, 1fr); }
+    .preview-row, .bottom-row { grid-template-columns: 1fr; }
+  }
+  @media (max-width: 640px) {
+    header.topbar { flex-wrap: wrap; padding: 10px 14px; }
+    main.config { padding: 14px; }
+    .steps { grid-template-columns: 1fr; }
+    .footer-acc { grid-template-columns: 1fr; }
+  }
+</style>
+</head>
+<body>
+
+<header class="topbar">
+  <a href="/" class="brand">wallco.ai</a>
+  <nav><a href="/designs?sort=newest">Designs</a><a href="/designs?cat=monterey-mural">Murals</a><a href="/samples">Samples</a><a href="/basket">Basket</a></nav>
+  <span class="grow"></span>
+  <span class="switcher">Theme: Configurator-First (V9) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<div id="root" class="loading-shell">Loading design…</div>
+
+<dialog class="modal" id="m-share"><header><h3>Share</h3><button class="close" onclick="document.getElementById('m-share').close()">×</button></header><div class="body" id="m-share-body"></div></dialog>
+<dialog class="modal" id="m-furnish"><header><h3>Furnish</h3><button class="close" onclick="document.getElementById('m-furnish').close()">×</button></header><div class="body" id="m-furnish-body"></div></dialog>
+
+<script>
+(async function () {
+  const pathMatch = location.pathname.match(/\/design\/(\d+)/);
+  const idFromQuery = new URLSearchParams(location.search).get('id');
+  const id = pathMatch ? parseInt(pathMatch[1], 10) : (idFromQuery ? parseInt(idFromQuery, 10) : null);
+  if (!Number.isFinite(id) || id < 1) { document.getElementById('root').textContent = 'No id'; return; }
+  document.getElementById('back-to-classic').href = '/design/' + id;
+
+  let design;
+  try {
+    const r = await fetch('/api/design/' + id, { credentials: 'same-origin' });
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    const j = await r.json();
+    design = j.design;
+  } catch (e) { document.getElementById('root').textContent = 'Failed: ' + e.message; return; }
+
+  const title = design.title || ('Design No.' + id);
+  const cat = design.category || 'wallpaper';
+  const catDisplay = (cat || '').replace(/^botanical-wonders · /, 'Botanical Wonders — ').replace(/^designer-zoo-calm/, 'Designer Zoo Calm').replace(/^cactus-pine-scenic/, 'Cactus Pine Scenic').replace(/^monterey-mural/, 'Coastal Scenic').replace(/^tree-mural/, 'Full-Room Scenic').replace(/^cactus-11ft-mural/, 'Single-Subject Mural').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : design.kind === 'mural_panel' ? 'Mural Panel' : design.kind === 'mural' ? 'Single-Subject Mural' : (design.kind || 'pattern');
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#888';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const w = Number(design.width_in) || 24, h = Number(design.height_in) || 24;
+  const repeatLabel = w + '"' + (w !== h ? ' × ' + h + '"' : '');
+  const rollOrPanel = isMural ? 'Per panel (36" × 11 ft)' : 'Per roll (27 ft)';
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const roomMockup = (design.room_mockups && (typeof design.room_mockups === 'object' && !Array.isArray(design.room_mockups) ? Object.keys(design.room_mockups)[0] : design.room_mockups[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<div class="crumb">
+  <a href="/designs">Designs</a><span>›</span><a href="/designs?cat=${encodeURIComponent(cat)}">${escapeHtml(catDisplay)}</a>
+  <span class="grow"></span>
+  <span><a href="/design/${id - 2}?theme=config">← prev</a> · <a href="/design/${id + 2}?theme=config">next →</a></span>
+</div>
+
+<main class="config">
+
+  <!-- CONFIGURATOR (UP TOP — primary action) -->
+  <div class="configurator">
+    <h2>Configure your order</h2>
+    <div class="tag">Step 1 — wall dimensions · Step 2 — roll width · Step 3 — repeat fit · Step 4 — total + lead time</div>
+
+    <div class="steps">
+      <div class="step">
+        <div class="num">Step 1</div>
+        <div class="lbl">Wall dimensions</div>
+        <div class="input"><input id="ccw" type="number" min="1" max="80" step="0.5" value="10"><span style="color:var(--muted)">×</span><input id="cch" type="number" min="1" max="20" step="0.5" value="9"></div>
+      </div>
+      <div class="step">
+        <div class="num">Step 2</div>
+        <div class="lbl">Roll / panel choice</div>
+        <div class="input"><select id="ccrw"><option value="24">24" residential</option><option value="36">36" wide-roll</option><option value="52">52" commercial</option></select></div>
+      </div>
+      <div class="step">
+        <div class="num">Step 3</div>
+        <div class="lbl">Pattern repeat fit</div>
+        <div class="ghost" id="cc-fit">5 panels wide × 9 repeats tall</div>
+      </div>
+      <div class="step">
+        <div class="num">Step 4</div>
+        <div class="lbl">Total + lead time</div>
+        <div class="ghost" id="cc-total">— rolls · $— · 3–5 days</div>
+      </div>
+    </div>
+
+    <button class="big-cta" id="big-cta" onclick="addBundle()">
+      <span>Add to basket</span>
+      <span class="est" id="big-est">— rolls · $—</span>
+    </button>
+  </div>
+
+  <!-- TITLE STRIP -->
+  <div class="title-strip">
+    <h1>${escapeHtml(title)}</h1>
+    <div class="meta">${escapeHtml(catDisplay)}<span class="sep">·</span>${escapeHtml(kindLabel)}<span class="sep">·</span>${rollOrPanel}<span class="sep">·</span>${escapeHtml(dominant)}<span class="sep">·</span>${escapeHtml(handle)}</div>
+  </div>
+
+  <!-- 3 PREVIEWS — hero ⅓, room ⅓, at-scale ⅓ -->
+  <div class="preview-row">
+    <div class="preview">
+      <img loading="eager" src="${img}" alt="${escapeAttr(title)}">
+      <span class="badge">Pattern · ${escapeHtml(title)}</span>
+    </div>
+    <div class="preview room" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : ''}>
+      <span class="badge">See in a room</span>
+      ${roomImgUrl ? '' : '<div class="room-placeholder">Room mockup not yet generated</div>'}
+    </div>
+    <div class="preview atscale" id="atscale-preview" style="background-image:url('${img}')">
+      <span class="badge">At-scale · 10 × 9 ft wall</span>
+      <span class="scale-pill">scale <input type="range" min="120" max="360" value="220" id="scale-slider"></span>
+    </div>
+  </div>
+
+  <!-- BOTTOM 3 -->
+  <div class="bottom-row">
+    <div class="card">
+      <h3>Palette · drag to recolor</h3>
+      <div class="swatches">
+        ${palette.length ? palette.slice(0, 5).map(p => `<div class="sw" title="${escapeAttr(p.hex)} · ${(p.pct || 0).toFixed(1)}%" style="background:${escapeAttr(p.hex)}"></div>`).join('') : `<div class="sw" style="background:${escapeAttr(dominant)}"></div>`}
+      </div>
+      <div style="font-size:11px; color:var(--muted); margin-top:8px">Dominant ${escapeHtml(dominant)}</div>
+    </div>
+    <div class="card adjust">
+      <h3>Image adjustments</h3>
+      <div class="sl"><label>Hue</label><input type="range" min="-180" max="180" value="0"><span class="v">0</span></div>
+      <div class="sl"><label>Saturation</label><input type="range" min="0" max="200" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Brightness</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+      <div class="sl"><label>Contrast</label><input type="range" min="50" max="150" value="100"><span class="v">100</span></div>
+    </div>
+    <div class="card trade">
+      <h3>Sample + trade tools</h3>
+      <div class="tile-btns">
+        <a class="tile-btn" href="#" onclick="addSample();return false">Sample $4.25</a>
+        <a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a>
+        <a class="tile-btn" href="#" onclick="openShare();return false">Share</a>
+        <a class="tile-btn" href="#" onclick="openFurnish();return false">Furnish</a>
+      </div>
+    </div>
+  </div>
+
+  <!-- Collapsed footer rows -->
+  <div class="footer-acc">
+    <details class="acc"><summary>Specs <span class="hint">Kind · W · Repeat · Roll · Material</span></summary><div class="acc-body">
+      <div class="specs-row"><span class="k">Category</span><span class="v">${escapeHtml(catDisplay)}</span></div>
+      <div class="specs-row"><span class="k">Kind</span><span class="v">${escapeHtml(kindLabel)}</span></div>
+      <div class="specs-row"><span class="k">Width</span><span class="v">${w}"</span></div>
+      <div class="specs-row"><span class="k">Repeat</span><span class="v">${repeatLabel}</span></div>
+      <div class="specs-row"><span class="k">Sold by</span><span class="v">${rollOrPanel}</span></div>
+      <div class="specs-row"><span class="k">${isMural ? 'Panel size' : 'Roll length'}</span><span class="v">${isMural ? (w + '" × 11 ft') : '27 ft'}</span></div>
+      <div class="specs-row"><span class="k">Material</span><span class="v">Type II vinyl</span></div>
+      <div class="specs-row"><span class="k">Designed by</span><span class="v">${escapeHtml(generator)}</span></div>
+      <div class="specs-row"><span class="k">Pattern No.</span><span class="v">${id}</span></div>
+      <div class="specs-row"><span class="k">Handle</span><span class="v">${escapeHtml(handle)}</span></div>
+    </div></details>
+    <details class="acc"><summary>Pairs well with · coordinate paints · stripes <span class="hint">7 brands · 24 stripes</span></summary><div class="acc-body">
+      <div class="pill-list" style="margin-bottom:10px"><a class="pill" href="#">SW</a><a class="pill" href="#">Dunn-E</a><a class="pill" href="#">BM</a><a class="pill" href="#">F&amp;B</a><a class="pill" href="#">Behr</a><a class="pill" href="#">PPG</a><a class="pill" href="#">RAL</a></div>
+      <div class="pair-grid">${Array.from({length: 12}).map(() => '<div class="pair"></div>').join('')}</div>
+    </div></details>
+    <details class="acc"><summary>Furnish this design <span class="hint">Res · Com · Hosp · Health</span></summary><div class="acc-body"><div class="tile-btns"><a class="tile-btn" href="#" onclick="openFurnish();return false">Residential</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Commercial</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Hospitality</a><a class="tile-btn" href="#" onclick="openFurnish();return false">Healthcare</a></div></div></details>
+    <details class="acc"><summary>Ratings &amp; reviews + Generator + prompt <span class="hint">★★★★☆ · seed ${escapeHtml(design.seed || '—')}</span></summary><div class="acc-body">
+      <div style="font-size:22px; letter-spacing:4px; color:#b89a4a">★★★★☆</div>
+      <div style="font-size:11.5px; color:var(--muted); margin-top:4px; margin-bottom:14px">Curated · 4.2 · ${escapeHtml(design.user_vote_count || 0)} votes</div>
+      <pre style="font:11px/1.5 ui-monospace,Menlo,monospace; background:var(--panel); padding:10px; border-radius:6px; max-height:180px; overflow:auto; margin:0; white-space:pre-wrap; word-break:break-word">${escapeHtml((design.prompt || '').slice(0, 1000))}${(design.prompt && design.prompt.length > 1000) ? '\n…[truncated]' : ''}</pre>
+    </div></details>
+  </div>
+
+</main>
+
+<footer class="site">
+  <div class="h">wallco.ai</div>
+  <div>A Designer Wallcoverings company · Sherman Oaks, CA</div>
+</footer>
+`;
+
+  const ccw = document.getElementById('ccw'), cch = document.getElementById('cch'), ccrw = document.getElementById('ccrw');
+  const ccFit = document.getElementById('cc-fit'), ccTotal = document.getElementById('cc-total'), bigEst = document.getElementById('big-est');
+  function recalc() {
+    const ww = parseFloat(ccw.value) || 0, wh = parseFloat(cch.value) || 0;
+    const rwi = parseFloat(ccrw.value) || 24;
+    if (ww < 1 || wh < 1) { ccTotal.textContent = '— rolls · $— · 3–5 days'; bigEst.textContent = '—'; return; }
+    if (isMural) {
+      const p = Math.ceil((ww * 12) / rwi); const cost = p * 245;
+      ccFit.textContent = p + ' panels wide × 1 tall';
+      ccTotal.textContent = p + ' panels · $' + cost.toLocaleString() + ' · 3–5 days';
+      bigEst.textContent = p + ' panels · $' + cost.toLocaleString();
+    } else {
+      const wp = Math.ceil((ww * 12) / rwi);
+      const rep = Math.ceil(wh / (parseFloat(String(w)) / 12 || 2));
+      const rolls = wp * Math.ceil(wh / 27);
+      const cost = rolls * 165;
+      ccFit.textContent = wp + ' panels wide × ' + rep + ' repeats tall';
+      ccTotal.textContent = rolls + ' rolls · $' + cost.toLocaleString() + ' · 3–5 days';
+      bigEst.textContent = rolls + ' rolls · $' + cost.toLocaleString();
+    }
+  }
+  [ccw, cch, ccrw].forEach(el => el.addEventListener('input', recalc));
+  recalc();
+
+  const scale = document.getElementById('scale-slider');
+  const ascale = document.getElementById('atscale-preview');
+  scale.addEventListener('input', () => { ascale.style.backgroundSize = scale.value + 'px ' + scale.value + 'px'; });
+
+  document.querySelectorAll('.adjust .sl').forEach(row => {
+    const r = row.querySelector('input[type=range]'); const v = row.querySelector('.v');
+    r.addEventListener('input', () => { v.textContent = r.value; });
+  });
+
+  document.getElementById('m-share-body').innerHTML = '<p>Share <b>' + escapeHtml(title) + '</b></p><div class="tile-btns"><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a></div>';
+  document.getElementById('m-furnish-body').innerHTML = '<p>Generate AI furnishings.</p><div class="tile-btns"><a class="tile-btn" href="#">Residential</a><a class="tile-btn" href="#">Commercial</a><a class="tile-btn" href="#">Hospitality</a><a class="tile-btn" href="#">Healthcare</a></div>';
+})();
+
+function openShare() { document.getElementById('m-share').showModal(); }
+function openFurnish() { document.getElementById('m-furnish').showModal(); }
+async function copyLink() { try { await navigator.clipboard.writeText(location.href.replace(/\/config$/, '')); alert('Link copied'); } catch { alert('Copy failed'); } }
+function addSample() { alert('Sample added (stub)'); }
+function addBundle() { alert('Bundle added (stub)'); }
+function escapeHtml(v) { if (v == null) return ''; return String(v).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c])); }
+function escapeAttr(v) { return escapeHtml(v); }
+</script>
+</body>
+</html>
diff --git a/server.js b/server.js
index 34ef814..a891979 100644
--- a/server.js
+++ b/server.js
@@ -444,6 +444,28 @@ app.get('/design/:id/tabs', (req, res) => {
   res.sendFile(path.join(__dirname, 'public', 'theme-variants', 'v3-tabs.html'));
 });
 
+// ── Theme variants V4–V10. All mirror V1–V3: static HTML at
+// public/theme-variants/<slug>.html, fetches /api/design/:id client-side.
+//   V4 hero      — full-bleed hero + narrow buy column
+//   V5 vertical  — 720px stack + floating buy bar
+//   V6 drawer    — minimal page + side drawers
+//   V7 editorial — magazine layout w/ marginalia
+//   V8 spec      — wine-list spec sheet
+//   V9 config    — configurator-first
+//   V10 stack    — single-column card stack
+function themeVariantSend(req, res, file) {
+  const id = parseInt(req.params.id, 10);
+  if (!Number.isFinite(id) || id < 1) return res.status(404).type('html').send('<h1>404</h1>');
+  res.sendFile(path.join(__dirname, 'public', 'theme-variants', file));
+}
+app.get('/design/:id/hero',      (req, res) => themeVariantSend(req, res, 'v4-hero.html'));
+app.get('/design/:id/vertical',  (req, res) => themeVariantSend(req, res, 'v5-vertical.html'));
+app.get('/design/:id/drawer',    (req, res) => themeVariantSend(req, res, 'v6-drawer.html'));
+app.get('/design/:id/editorial', (req, res) => themeVariantSend(req, res, 'v7-editorial.html'));
+app.get('/design/:id/spec',      (req, res) => themeVariantSend(req, res, 'v8-spec.html'));
+app.get('/design/:id/config',    (req, res) => themeVariantSend(req, res, 'v9-config.html'));
+app.get('/design/:id/stack',     (req, res) => themeVariantSend(req, res, 'v10-stack.html'));
+
 // ── Lightweight in-memory search across the catalog.
 // /api/designs/search?q=<term>&limit=20
 // Scores each design on motif (3), title (2), category/handle (1) substring match.
@@ -11341,6 +11363,27 @@ try {
           <a class="hb-menu-item" data-hb-theme="tabs" role="menuitem" href="/design/${design.id}/tabs">
             <span class="hb-menu-icon" aria-hidden="true">▤</span> V3 · Sticky Hero + Tabs
           </a>
+          <a class="hb-menu-item" data-hb-theme="hero" role="menuitem" href="/design/${design.id}/hero">
+            <span class="hb-menu-icon" aria-hidden="true">◆</span> V4 · Hero-First Storefront
+          </a>
+          <a class="hb-menu-item" data-hb-theme="vertical" role="menuitem" href="/design/${design.id}/vertical">
+            <span class="hb-menu-icon" aria-hidden="true">▮</span> V5 · Mobile-Up Vertical
+          </a>
+          <a class="hb-menu-item" data-hb-theme="drawer" role="menuitem" href="/design/${design.id}/drawer">
+            <span class="hb-menu-icon" aria-hidden="true">▷</span> V6 · Drawer Heavy
+          </a>
+          <a class="hb-menu-item" data-hb-theme="editorial" role="menuitem" href="/design/${design.id}/editorial">
+            <span class="hb-menu-icon" aria-hidden="true">§</span> V7 · Editorial Magazine
+          </a>
+          <a class="hb-menu-item" data-hb-theme="spec" role="menuitem" href="/design/${design.id}/spec">
+            <span class="hb-menu-icon" aria-hidden="true">≡</span> V8 · Compact Spec-Sheet
+          </a>
+          <a class="hb-menu-item" data-hb-theme="config" role="menuitem" href="/design/${design.id}/config">
+            <span class="hb-menu-icon" aria-hidden="true">⚙</span> V9 · Configurator-First
+          </a>
+          <a class="hb-menu-item" data-hb-theme="stack" role="menuitem" href="/design/${design.id}/stack">
+            <span class="hb-menu-icon" aria-hidden="true">▣</span> V10 · Card-Stack
+          </a>
         </div>
         ${_isAdmin ? `
         <button type="button" class="hb-menu-item" data-hb-action="edit" role="menuitem">

← 441d381 /designs: replace binary 'In a room' chip with a chip row —  ·  back to Wallco Ai  ·  docs: SHOPIFY-WEBHOOK-REGISTRATION one-pager (UI registratio 74ee628 →