[object Object]

← back to Wallco Ai

Add 6 room-left PDP theme variants V12-V17 (room setting left of pattern, adjustments in a hamburger selector) + routes + Theme submenu + gallery entries + admin-tabs nav link

79f45cc7166ecd63bd6a3f6ca497856acaf4739e · 2026-05-29 12:42:36 -0700 · Steve

Files touched

Diff

commit 79f45cc7166ecd63bd6a3f6ca497856acaf4739e
Author: Steve <steve@designerwallcoverings.com>
Date:   Fri May 29 12:42:36 2026 -0700

    Add 6 room-left PDP theme variants V12-V17 (room setting left of pattern, adjustments in a hamburger selector) + routes + Theme submenu + gallery entries + admin-tabs nav link
---
 public/admin/theme-gallery.html           |   6 +
 public/theme-variants/v12-roomsplit.html  | 310 ++++++++++++++++++++++++++++++
 public/theme-variants/v13-roomdrawer.html | 310 ++++++++++++++++++++++++++++++
 public/theme-variants/v14-roomzine.html   | 310 ++++++++++++++++++++++++++++++
 public/theme-variants/v15-roomsticky.html | 310 ++++++++++++++++++++++++++++++
 public/theme-variants/v16-roommin.html    | 310 ++++++++++++++++++++++++++++++
 public/theme-variants/v17-roomshow.html   | 310 ++++++++++++++++++++++++++++++
 scripts/gen-theme-variants.js             |  70 +++++++
 scripts/room-left-template.html           | 294 ++++++++++++++++++++++++++++
 server.js                                 |  24 +++
 src/admin.js                              |   1 +
 11 files changed, 2255 insertions(+)

diff --git a/public/admin/theme-gallery.html b/public/admin/theme-gallery.html
index 0e9da4c..26a8c49 100644
--- a/public/admin/theme-gallery.html
+++ b/public/admin/theme-gallery.html
@@ -84,6 +84,12 @@ const VARIANTS = [
   ['config',   'V9 · Configurator-First'],
   ['stack',    'V10 · Card-Stack'],
   ['best',     'V11 · Best (synthesis)'],
+  ['roomsplit',  'V12 · Room-Left Split'],
+  ['roomdrawer', 'V13 · Room-Left Drawer'],
+  ['roomzine',   'V14 · Room-Left Editorial'],
+  ['roomsticky', 'V15 · Room-Left Sticky'],
+  ['roommin',    'V16 · Room-Left Minimal'],
+  ['roomshow',   'V17 · Room-Left Showcase'],
 ];
 const LOGICAL_H = { 1280:1700, 834:1180, 390:760 }; // logical iframe height per width
 const $ = s => document.querySelector(s);
diff --git a/public/theme-variants/v12-roomsplit.html b/public/theme-variants/v12-roomsplit.html
new file mode 100644
index 0000000..5e9c8ed
--- /dev/null
+++ b/public/theme-variants/v12-roomsplit.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Split PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #faf8f3;
+    --ink: #1a1611;
+    --muted: #8a8276;
+    --panel: #eee9dd;
+    --bar: rgba(250,248,243,.92);
+    --accent: #16203c;
+    --on-accent: #fff;
+    --split: 1fr 1fr;
+    --radius: 12px;
+    --shadow: 0 1px 0 var(--line);
+    --brandfont: var(--serif);
+    --titlefont: 500 30px/1.15 var(--serif);
+    --paneltitle: 500 18px/1 var(--serif);
+    --room-ar: 4/3;
+    --pat-max: 420px;
+    --pat-border: 0;
+    --room-pos: static;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="dropdown">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Split (V12) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/v13-roomdrawer.html b/public/theme-variants/v13-roomdrawer.html
new file mode 100644
index 0000000..8282a94
--- /dev/null
+++ b/public/theme-variants/v13-roomdrawer.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Drawer PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #fbf7f4;
+    --ink: #211a16;
+    --muted: #8a8276;
+    --panel: #efe6df;
+    --bar: rgba(251,247,244,.93);
+    --accent: #b5572e;
+    --on-accent: #fff;
+    --split: 1.4fr 1fr;
+    --radius: 12px;
+    --shadow: 0 1px 0 var(--line);
+    --brandfont: var(--serif);
+    --titlefont: 500 31px/1.12 var(--serif);
+    --paneltitle: 500 18px/1 var(--serif);
+    --room-ar: 3/4;
+    --pat-max: 420px;
+    --pat-border: 0;
+    --room-pos: static;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="drawer">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Drawer (V13) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/v14-roomzine.html b/public/theme-variants/v14-roomzine.html
new file mode 100644
index 0000000..a054bbe
--- /dev/null
+++ b/public/theme-variants/v14-roomzine.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Editorial PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #f6f3ec;
+    --ink: #15120d;
+    --muted: #8a8276;
+    --panel: #e8e2d4;
+    --bar: rgba(246,243,236,.92);
+    --accent: #15120d;
+    --on-accent: #f6f3ec;
+    --split: 1.6fr 1fr;
+    --radius: 12px;
+    --shadow: 0 1px 0 var(--line);
+    --brandfont: var(--serif);
+    --titlefont: 500 42px/1.04 var(--serif);
+    --paneltitle: 500 24px/1 var(--serif);
+    --room-ar: 5/4;
+    --pat-max: 420px;
+    --pat-border: 0;
+    --room-pos: static;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="sheet">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Editorial (V14) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/v15-roomsticky.html b/public/theme-variants/v15-roomsticky.html
new file mode 100644
index 0000000..99b47c3
--- /dev/null
+++ b/public/theme-variants/v15-roomsticky.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Sticky PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #f7f8f4;
+    --ink: #16201a;
+    --muted: #8a8276;
+    --panel: #e4ebdf;
+    --bar: rgba(247,248,244,.93);
+    --accent: #2f5d3a;
+    --on-accent: #fff;
+    --split: 1fr 0.85fr;
+    --radius: 12px;
+    --shadow: 0 1px 0 var(--line);
+    --brandfont: var(--serif);
+    --titlefont: 500 30px/1.15 var(--serif);
+    --paneltitle: 500 18px/1 var(--serif);
+    --room-ar: 4/3;
+    --pat-max: 420px;
+    --pat-border: 0;
+    --room-pos: sticky;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="modal">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Sticky (V15) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/v16-roommin.html b/public/theme-variants/v16-roommin.html
new file mode 100644
index 0000000..4895872
--- /dev/null
+++ b/public/theme-variants/v16-roommin.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Minimal PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #fcfcfa;
+    --ink: #1f1f1d;
+    --muted: #8a8276;
+    --panel: #f0f0ec;
+    --bar: rgba(252,252,250,.92);
+    --accent: #1f1f1d;
+    --on-accent: #fcfcfa;
+    --split: 1fr 1fr;
+    --radius: 4px;
+    --shadow: none;
+    --brandfont: var(--sans);
+    --titlefont: 400 26px/1.25 var(--sans);
+    --paneltitle: 600 15px/1 var(--sans);
+    --room-ar: 4/3;
+    --pat-max: 420px;
+    --pat-border: 0;
+    --room-pos: static;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="palette">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Minimal (V16) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/v17-roomshow.html b/public/theme-variants/v17-roomshow.html
new file mode 100644
index 0000000..2c2dcf6
--- /dev/null
+++ b/public/theme-variants/v17-roomshow.html
@@ -0,0 +1,310 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — Room-Left Showcase PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+        --bg: #f7f3ea;
+    --ink: #1a160d;
+    --muted: #8a8276;
+    --panel: #ece3d0;
+    --bar: rgba(247,243,234,.92);
+    --accent: #c2a14e;
+    --on-accent: #1a160d;
+    --split: 1.2fr 1fr;
+    --radius: 16px;
+    --shadow: 0 20px 55px rgba(0,0,0,.16);
+    --brandfont: var(--serif);
+    --titlefont: 500 32px/1.12 var(--serif);
+    --paneltitle: 500 18px/1 var(--serif);
+    --room-ar: 4/3;
+    --pat-max: 380px;
+    --pat-border: 10px solid #fff;
+    --room-pos: static;
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="fab">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: Room-Left Showcase (V17) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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/scripts/gen-theme-variants.js b/scripts/gen-theme-variants.js
new file mode 100644
index 0000000..b642796
--- /dev/null
+++ b/scripts/gen-theme-variants.js
@@ -0,0 +1,70 @@
+#!/usr/bin/env node
+/* Generate the V12–V17 "room-left + hamburger adjustments" PDP theme variants
+ * from scripts/room-left-template.html. Each shares the concept (room setting
+ * LEFT of the pattern, adjustments selected from a hamburger) but differs in
+ * hamburger MODE + palette + split ratio + typography. Emits into
+ * public/theme-variants/. Idempotent — safe to re-run. */
+'use strict';
+const fs = require('fs');
+const path = require('path');
+
+const TPL = fs.readFileSync(path.join(__dirname, 'room-left-template.html'), 'utf8');
+const OUT = path.join(__dirname, '..', 'public', 'theme-variants');
+
+const base = {
+  muted: '#8a8276', line: 'rgba(0,0,0,.10)',
+  brandfont: 'var(--serif)', paneltitle: '500 18px/1 var(--serif)',
+  radius: '12px', shadow: '0 1px 0 var(--line)', patMax: '420px', patBorder: '0', roomAr: '4/3', roomPos: 'static',
+};
+
+const CONFIGS = [
+  { code:'V12', slug:'roomsplit',  name:'Room-Left Split',     mode:'dropdown',
+    bg:'#faf8f3', ink:'#1a1611', panel:'#eee9dd', bar:'rgba(250,248,243,.92)',
+    accent:'#16203c', onAccent:'#fff', split:'1fr 1fr', titlefont:'500 30px/1.15 var(--serif)' },
+  { code:'V13', slug:'roomdrawer', name:'Room-Left Drawer',    mode:'drawer',
+    bg:'#fbf7f4', ink:'#211a16', panel:'#efe6df', bar:'rgba(251,247,244,.93)',
+    accent:'#b5572e', onAccent:'#fff', split:'1.4fr 1fr', roomAr:'3/4', titlefont:'500 31px/1.12 var(--serif)' },
+  { code:'V14', slug:'roomzine',   name:'Room-Left Editorial', mode:'sheet',
+    bg:'#f6f3ec', ink:'#15120d', panel:'#e8e2d4', bar:'rgba(246,243,236,.92)',
+    accent:'#15120d', onAccent:'#f6f3ec', split:'1.6fr 1fr', roomAr:'5/4',
+    titlefont:'500 42px/1.04 var(--serif)', paneltitle:'500 24px/1 var(--serif)' },
+  { code:'V15', slug:'roomsticky', name:'Room-Left Sticky',    mode:'modal',
+    bg:'#f7f8f4', ink:'#16201a', panel:'#e4ebdf', bar:'rgba(247,248,244,.93)',
+    accent:'#2f5d3a', onAccent:'#fff', split:'1fr 0.85fr', roomPos:'sticky', titlefont:'500 30px/1.15 var(--serif)' },
+  { code:'V16', slug:'roommin',    name:'Room-Left Minimal',   mode:'palette',
+    bg:'#fcfcfa', ink:'#1f1f1d', panel:'#f0f0ec', bar:'rgba(252,252,250,.92)',
+    accent:'#1f1f1d', onAccent:'#fcfcfa', split:'1fr 1fr', radius:'4px', shadow:'none',
+    brandfont:'var(--sans)', titlefont:'400 26px/1.25 var(--sans)', paneltitle:'600 15px/1 var(--sans)' },
+  { code:'V17', slug:'roomshow',   name:'Room-Left Showcase',  mode:'fab',
+    bg:'#f7f3ea', ink:'#1a160d', panel:'#ece3d0', bar:'rgba(247,243,234,.92)',
+    accent:'#c2a14e', onAccent:'#1a160d', split:'1.2fr 1fr', radius:'16px',
+    shadow:'0 20px 55px rgba(0,0,0,.16)', patMax:'380px', patBorder:'10px solid #fff',
+    titlefont:'500 32px/1.12 var(--serif)' },
+];
+
+function cssVars(c) {
+  const v = { ...base, ...c };
+  const lines = [
+    ['--bg', v.bg], ['--ink', v.ink], ['--muted', v.muted], ['--panel', v.panel], ['--bar', v.bar],
+    ['--accent', v.accent], ['--on-accent', v.onAccent], ['--split', v.split],
+    ['--radius', v.radius], ['--shadow', v.shadow], ['--brandfont', v.brandfont],
+    ['--titlefont', v.titlefont], ['--paneltitle', v.paneltitle],
+    ['--room-ar', v.roomAr], ['--pat-max', v.patMax], ['--pat-border', v.patBorder], ['--room-pos', v.roomPos],
+  ];
+  return lines.map(([k, val]) => `    ${k}: ${val};`).join('\n');
+}
+
+let n = 0;
+for (const c of CONFIGS) {
+  const html = TPL
+    .replace(/%%CODE%%/g, c.code)
+    .replace(/%%NAME%%/g, c.name)
+    .replace(/%%MODE%%/g, c.mode)
+    .replace(/%%CSSVARS%%/g, cssVars(c));
+  const file = path.join(OUT, `${c.code.toLowerCase()}-${c.slug}.html`);
+  fs.writeFileSync(file, html);
+  console.log(`wrote ${path.basename(file)}  (${c.name} · mode=${c.mode})`);
+  n++;
+}
+console.log(`[done] ${n} variants generated`);
+module.exports = { CONFIGS };
diff --git a/scripts/room-left-template.html b/scripts/room-left-template.html
new file mode 100644
index 0000000..ff29d3c
--- /dev/null
+++ b/scripts/room-left-template.html
@@ -0,0 +1,294 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<title>wallco.ai — %%NAME%% PDP</title>
+<link rel="icon" href="/static/favicon.ico" type="image/x-icon">
+<style>
+  :root {
+    %%CSSVARS%%
+    --line: rgba(0,0,0,.10);
+    --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: 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: 40; background: var(--bar);
+    backdrop-filter: blur(10px); border-bottom: 1px solid var(--line); padding: 10px 24px;
+    display: flex; align-items: center; gap: 20px; }
+  header .brand { font: 600 18px/1 var(--brandfont); letter-spacing: .04em; }
+  header nav { font-size: 12.5px; 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; }
+  .adjust-btn { display: inline-flex; align-items: center; gap: 8px; border: 1px solid var(--line);
+    background: var(--accent); color: var(--on-accent); border-radius: 999px; padding: 9px 16px;
+    font: 700 12px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .adjust-btn .bars { display: inline-flex; flex-direction: column; gap: 2.5px; }
+  .adjust-btn .bars i { width: 15px; height: 2px; background: currentColor; border-radius: 2px; display: block; }
+
+  main.roomleft { max-width: 1640px; margin: 0 auto; padding: 22px 24px 60px;
+    display: grid; grid-template-columns: var(--split); gap: 30px; align-items: start; }
+
+  /* LEFT — room setting */
+  .col-room { min-width: 0; position: var(--room-pos, static); top: 80px; }
+  .room-main { aspect-ratio: var(--room-ar, 4/3); background: var(--panel); border-radius: var(--radius);
+    overflow: hidden; position: relative; box-shadow: var(--shadow); }
+  .room-main img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  .room-main .tag { position: absolute; left: 14px; bottom: 14px; background: var(--bar);
+    border: 1px solid var(--line); border-radius: 999px; padding: 6px 13px;
+    font: 600 10.5px/1 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .room-thumbs { display: flex; gap: 8px; margin-top: 12px; }
+  .room-thumbs .t { flex: 1; aspect-ratio: 1/1; border-radius: calc(var(--radius) - 4px);
+    background: var(--panel); border: 1px solid var(--line); background-size: cover; background-position: center; cursor: pointer; }
+  .room-cap { margin-top: 12px; font-size: 12px; color: var(--muted); }
+
+  /* RIGHT — pattern + buy */
+  .col-pattern { min-width: 0; }
+  .pattern-art { aspect-ratio: 1/1; max-width: var(--pat-max, 420px); background: var(--panel);
+    border-radius: var(--radius); overflow: hidden; box-shadow: var(--shadow); border: var(--pat-border, 0); }
+  .pattern-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
+  h1.title { font: var(--titlefont); margin: 18px 0 4px; }
+  .cat { color: var(--muted); font-size: 13px; margin-bottom: 16px; }
+  .cta-row { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 18px; }
+  .cta { border-radius: 999px; padding: 13px 20px; font: 700 12px/1 var(--sans);
+    letter-spacing: .07em; text-transform: uppercase; border: 1px solid transparent; }
+  .cta.sample { background: var(--accent); color: var(--on-accent); }
+  .cta.buy { background: var(--ink); color: var(--bg); }
+  .mini-specs { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 26px; max-width: 460px;
+    border-top: 1px solid var(--line); padding-top: 16px; }
+  .mini-specs .k { font: 600 10px/1.3 var(--sans); letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
+  .mini-specs .v { font-family: var(--mono); font-size: 12.5px; margin-top: 1px; }
+
+  /* adjustments overlay (menu + panel) — placement driven by body[data-mode] */
+  .ov { position: fixed; inset: 0; z-index: 60; display: none; }
+  .ov.open { display: block; }
+  .ov .scrim { position: absolute; inset: 0; background: rgba(20,17,14,.45); backdrop-filter: blur(3px); }
+  .menu, .panel { position: absolute; background: var(--bg); border: 1px solid var(--line);
+    box-shadow: 0 24px 80px rgba(0,0,0,.28); }
+  .menu { border-radius: 14px; padding: 8px; min-width: 250px; }
+  .menu h4 { margin: 6px 12px 8px; font: 600 10.5px/1 var(--sans); letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
+  .menu button { display: flex; width: 100%; align-items: center; gap: 11px; border: 0; background: transparent;
+    text-align: left; padding: 10px 12px; border-radius: 9px; color: var(--ink); font-size: 13.5px; }
+  .menu button:hover, .menu button.on { background: var(--panel); }
+  .menu button .ic { width: 22px; text-align: center; opacity: .7; }
+  .panel { border-radius: 14px; width: min(440px, 92vw); max-height: 78vh; overflow: auto; }
+  .panel header { display: flex; justify-content: space-between; align-items: center;
+    padding: 16px 20px; border-bottom: 1px solid var(--line); position: sticky; top: 0; background: var(--bg); }
+  .panel header h3 { margin: 0; font: var(--paneltitle); }
+  .panel header .x { background: transparent; border: 0; font-size: 22px; color: var(--muted); }
+  .panel .pbody { padding: 18px 20px 24px; }
+
+  /* mode: dropdown — menu under the button, panel as small popover top-right */
+  [data-mode="dropdown"] .menu { top: 62px; right: 24px; }
+  [data-mode="dropdown"] .panel { top: 62px; right: 24px; }
+  /* mode: drawer — slide in from right */
+  [data-mode="drawer"] .menu { top: 0; right: 0; height: 100%; border-radius: 0; min-width: 320px; padding: 18px; }
+  [data-mode="drawer"] .panel { top: 0; right: 0; height: 100%; border-radius: 0; width: min(440px,94vw); max-height: 100%; }
+  /* mode: sheet — slide up from bottom */
+  [data-mode="sheet"] .menu { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; min-width: 0;
+    display: grid; grid-template-columns: repeat(auto-fill,minmax(150px,1fr)); gap: 6px; padding: 18px 22px 28px; }
+  [data-mode="sheet"] .menu h4 { grid-column: 1/-1; }
+  [data-mode="sheet"] .panel { left: 0; right: 0; bottom: 0; border-radius: 18px 18px 0 0; width: auto; max-height: 70vh; }
+  /* mode: modal — centered */
+  [data-mode="modal"] .menu { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  [data-mode="modal"] .panel { top: 50%; left: 50%; transform: translate(-50%,-50%); }
+  /* mode: palette — command-palette near top-center */
+  [data-mode="palette"] .menu { top: 84px; left: 50%; transform: translateX(-50%); width: min(520px,92vw); }
+  [data-mode="palette"] .panel { top: 84px; left: 50%; transform: translateX(-50%); }
+  /* mode: fab — floating action button, menu pops above it */
+  [data-mode="fab"] .menu { right: 24px; bottom: 86px; }
+  [data-mode="fab"] .panel { right: 24px; bottom: 86px; }
+  .fab-btn { position: fixed; right: 24px; bottom: 24px; z-index: 55; width: 60px; height: 60px;
+    border-radius: 50%; background: var(--accent); color: var(--on-accent); border: 0;
+    box-shadow: 0 10px 30px rgba(0,0,0,.3); font-size: 22px; display: none; align-items: center; justify-content: center; }
+  body[data-mode="fab"] .fab-btn { display: flex; }
+  body[data-mode="fab"] header .adjust-btn { display: none; }
+
+  /* adjustment panel content */
+  .swatches { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
+  .sw { width: 34px; height: 34px; border-radius: 50%; border: 2px solid #fff; box-shadow: 0 0 0 1px var(--line); }
+  .sliders label { display: block; font-size: 12px; color: var(--muted); margin: 10px 0 3px; }
+  .sliders input[type=range] { width: 100%; }
+  .roomprev { height: 220px; border-radius: 10px; background: var(--panel); background-size: cover; background-position: center; }
+  .pairg { display: grid; grid-template-columns: repeat(3,1fr); gap: 6px; }
+  .pairg .p { aspect-ratio: 1/1; border-radius: 6px; background: var(--panel); background-size: cover; background-position: center; }
+  .pills { display: flex; flex-wrap: wrap; gap: 6px; }
+  .pill { font-size: 11px; padding: 5px 10px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); text-decoration: none; }
+  .calc .r { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; }
+  .calc label { font-size: 11px; color: var(--muted); display: block; margin-bottom: 2px; }
+  .calc input, .calc select { width: 100%; padding: 7px 9px; border: 1px solid var(--line); border-radius: 7px; font: inherit; background: #fff; }
+  .calc .tot { margin-top: 10px; padding: 10px 12px; background: var(--panel); border-radius: 8px; }
+  .calc .tot b { font: 500 17px var(--serif); }
+  .spectab .row { display: grid; grid-template-columns: 150px 1fr; padding: 6px 0; border-bottom: 1px solid var(--line); font-size: 12.5px; }
+  .spectab .row .k { color: var(--muted); font: 600 10px/1.4 var(--sans); letter-spacing: .06em; text-transform: uppercase; }
+  .spectab .row .v { font-family: var(--mono); }
+  .tile-btns { display: flex; flex-wrap: wrap; gap: 6px; }
+  .tile-btn { flex: 1; min-width: 100px; background: var(--panel); border: 1px solid var(--line); padding: 10px; border-radius: 8px; font: 600 12px/1.2 var(--sans); text-align: center; text-decoration: none; }
+  pre.prompt { font: 11px/1.45 var(--mono); background: var(--panel); padding: 12px; border-radius: 8px; max-height: 200px; overflow: auto; margin: 0; white-space: pre-wrap; word-break: break-word; }
+
+  footer.site { border-top: 1px solid var(--line); padding: 24px; text-align: center; color: var(--muted); font-size: 12px; background: var(--panel); }
+  footer.site .h { font: 500 15px/1.4 var(--serif); color: var(--ink); margin-bottom: 4px; }
+  .loading { display: flex; align-items: center; justify-content: center; height: 60vh; color: var(--muted); font-style: italic; }
+  @media (max-width: 1000px) { main.roomleft { grid-template-columns: 1fr; } .col-room { position: static; } }
+  @media (max-width: 640px) { header.topbar { flex-wrap: wrap; padding: 8px 14px; } main.roomleft { padding: 16px 14px; } }
+</style>
+</head>
+<body data-mode="%%MODE%%">
+
+<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>
+  <button class="adjust-btn" id="adjust-open"><span class="bars"><i></i><i></i><i></i></span> Adjust</button>
+  <span class="switcher">Theme: %%NAME%% (%%CODE%%) <a id="back-to-classic" href="#">← Classic</a></span>
+</header>
+
+<button class="fab-btn" id="fab-open" aria-label="Adjust"><span style="font-size:24px">≡</span></button>
+
+<div id="root" class="loading">Loading design…</div>
+
+<div class="ov" id="ov">
+  <div class="scrim" id="ov-scrim"></div>
+  <div class="menu" id="adj-menu" role="menu" aria-label="Adjustments"></div>
+  <div class="panel" id="adj-panel" role="dialog" hidden>
+    <header><h3 id="adj-panel-title">Adjust</h3><button class="x" id="adj-panel-close">×</button></header>
+    <div class="pbody" id="adj-panel-body"></div>
+  </div>
+</div>
+
+<script>
+(async function () {
+  const m = location.pathname.match(/\/design\/(\d+)/);
+  const qid = new URLSearchParams(location.search).get('id');
+  const id = m ? parseInt(m[1], 10) : (qid ? parseInt(qid, 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);
+    design = (await r.json()).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(/ · /g, ' — ').replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase());
+  const isMural = design.kind === 'mural_panel' || design.kind === 'mural';
+  const kindLabel = design.kind === 'seamless_tile' ? 'Seamless Tile (24×24")' : isMural ? 'Mural Panel' : (design.kind || 'pattern');
+  const palette = Array.isArray(design.palette) ? design.palette : [];
+  const dominant = design.dominant_hex || (palette[0] && palette[0].hex) || '#999';
+  const img = design.image_url || ('/designs/img/by-id/' + id);
+  const handle = design.handle || ('wallco-' + id);
+  const generator = design.generator || 'wallco.ai';
+  const created = design.created_at ? new Date(design.created_at) : null;
+  const roomMockup = (design.room_mockups && (Array.isArray(design.room_mockups) ? design.room_mockups[0] : Object.keys(design.room_mockups)[0])) || null;
+  const roomImgUrl = roomMockup ? ('/designs/room/design_' + id + '_' + roomMockup + '.png') : null;
+  const roomLabel = roomMockup ? roomMockup.replace(/_/g, ' ') : 'room preview';
+
+  // ambient tint from dominant (used by showcase mode via --amb)
+  document.documentElement.style.setProperty('--amb', dominant + '22');
+
+  const root = document.getElementById('root');
+  root.className = '';
+  root.innerHTML = `
+<main class="roomleft">
+  <section class="col-room">
+    <div class="room-main">
+      <img loading="eager" src="${roomImgUrl || img}" alt="${escapeAttr(title)} in a room">
+      <span class="tag">${roomImgUrl ? 'See in a room — ' + escapeHtml(roomLabel) : 'Pattern preview'}</span>
+    </div>
+    <div class="room-thumbs">
+      <div class="t" ${roomImgUrl ? `style="background-image:url('${roomImgUrl}')"` : `style="background-image:url('${img}')"`}></div>
+      <div class="t" style="background-image:url('${img}')"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:55%"></div>
+      <div class="t" style="background-image:url('${img}'); background-size:30%"></div>
+    </div>
+    <div class="room-cap">Room setting shown left · the wallcovering applied to a styled interior. Tap a thumbnail to switch the view.</div>
+  </section>
+
+  <section class="col-pattern">
+    <div class="pattern-art"><img loading="eager" src="${img}" alt="${escapeAttr(title)}"></div>
+    <h1 class="title">${escapeHtml(title)}</h1>
+    <div class="cat">${escapeHtml(catDisplay)} · ${escapeHtml(handle)} · by ${escapeHtml(generator)}</div>
+    <div class="cta-row">
+      <button class="cta sample" onclick="alert('Sample added (stub)')">Add sample · $4.25</button>
+      <button class="cta buy" id="adjust-open-2">Adjust & buy</button>
+    </div>
+    <div class="mini-specs">
+      <div><div class="k">Kind</div><div class="v">${escapeHtml(kindLabel)}</div></div>
+      <div><div class="k">Dominant</div><div class="v">${escapeHtml(dominant)}</div></div>
+      <div><div class="k">Pattern No.</div><div class="v">${id}</div></div>
+      <div><div class="k">Status</div><div class="v">${design.is_published ? 'Live' : 'Curating'}</div></div>
+      ${created ? `<div><div class="k">Created</div><div class="v" title="${escapeAttr(design.created_at)}">${created.toLocaleDateString(undefined,{year:'numeric',month:'short',day:'numeric'})}</div></div>` : ''}
+      <div><div class="k">Edges</div><div class="v">PASS</div></div>
+    </div>
+  </section>
+</main>
+
+<footer class="site"><div class="h">wallco.ai</div><div>A Designer Wallcoverings company · Sherman Oaks, CA</div></footer>`;
+
+  // ---- adjustments: the hamburger menu of things to select from ----
+  const w = 24, h = 24;
+  const ADJ = [
+    ['palette','◐','Palette & recolor', () => `<div class="swatches">${(palette.length?palette.slice(0,6):[{hex:dominant}]).map(p=>`<div class="sw" style="background:${escapeAttr(p.hex)}" title="${escapeAttr(p.hex)}"></div>`).join('')}</div><p style="color:var(--muted);font-size:12.5px">Recolor this design — drag a swatch onto the pattern to remap that ink (full recolor in Classic).</p>`],
+    ['image','✦','Image adjustments', () => `<div class="sliders">${['Hue','Saturation','Brightness','Contrast','Sepia'].map(s=>`<label>${s}</label><input type="range" min="0" max="100" value="50">`).join('')}</div>`],
+    ['room','▣','See in a room', () => `<div class="roomprev" style="${roomImgUrl?`background-image:url('${roomImgUrl}')`:''}"></div><p style="color:var(--muted);font-size:12.5px;margin-top:10px">${roomImgUrl?('Residential preview — '+escapeHtml(roomLabel)):'No room render yet for this design.'}</p>`],
+    ['pairs','▥','Pairs well with', () => `<div class="pairg">${Array.from({length:6}).map(()=>`<div class="p" style="background-image:url('${img}');background-size:${40+Math.floor(Math.random()*40)}%"></div>`).join('')}</div>`],
+    ['paints','◇','Coordinate paints', () => `<div class="pills">${['Sherwin-Williams','Dunn-Edwards','Benjamin Moore','Farrow & Ball','Behr','PPG','RAL'].map(b=>`<a class="pill" href="#">${b}</a>`).join('')}</div>`],
+    ['specs','≡','Full specifications', () => `<div class="spectab">${[['Category',catDisplay],['Kind',kindLabel],['Width',(isMural?'36"':'24"')],['Sold by',isMural?'Per panel (36"×11ft)':'Per roll (27 ft)'],['Repeat',w+'" straight match'],['Material','Type II vinyl · class A'],['Handle',handle],['Designed by',generator],['Settlement','OK (logged)'],['Edges scan','PASS · 6-lens']].map(([k,v])=>`<div class="row"><span class="k">${escapeHtml(k)}</span><span class="v">${escapeHtml(v)}</span></div>`).join('')}</div>`],
+    ['calc','▦','Roll calculator', () => `<div class="calc"><div class="r"><div><label>Wall W (ft)</label><input id="cw" type="number" value="10" min="1"></div><div><label>Wall H (ft)</label><input id="ch" type="number" value="9" min="1"></div><div><label>Roll W</label><select id="crw"><option>24</option><option>36</option><option>52</option></select></div></div><div class="tot" id="ctot">— rolls · $—</div></div>`],
+    ['share','↗','Share · Furnish · Trade', () => `<div class="tile-btns"><a class="tile-btn" href="#" onclick="copyLink();return false">Copy link</a><a class="tile-btn" href="#">Pinterest</a><a class="tile-btn" href="#">Instagram</a><a class="tile-btn" href="/design/${id}/tearsheet" target="_blank">Tear sheet</a><a class="tile-btn" href="#">Furnish (AI)</a><a class="tile-btn" href="#">Email client</a></div>`],
+    ['gen','⌘','Generator + prompt', () => `<pre class="prompt">${escapeHtml((design.prompt||'wallco.ai original · seed '+(design.seed||'—')).slice(0,1200))}</pre>`],
+  ];
+
+  const menu = document.getElementById('adj-menu');
+  menu.innerHTML = '<h4>Adjust this design</h4>' + ADJ.map(([k,ic,label]) =>
+    `<button data-adj="${k}" role="menuitem"><span class="ic">${ic}</span> ${label}</button>`).join('');
+
+  const ov = document.getElementById('ov');
+  const panel = document.getElementById('adj-panel');
+  const menuEl = document.getElementById('adj-menu');
+  function openMenu(){ ov.classList.add('open'); panel.hidden = true; menuEl.style.display=''; }
+  function closeOv(){ ov.classList.remove('open'); panel.hidden = true; }
+  function showPanel(key){
+    const item = ADJ.find(a => a[0] === key); if(!item) return;
+    document.getElementById('adj-panel-title').textContent = item[2];
+    document.getElementById('adj-panel-body').innerHTML = item[3]();
+    panel.hidden = false; menuEl.style.display='none'; ov.classList.add('open');
+    menu.querySelectorAll('button').forEach(b => b.classList.toggle('on', b.dataset.adj===key));
+    if (key === 'calc') wireCalc();
+  }
+  ['adjust-open','adjust-open-2','fab-open'].forEach(idn => { const el=document.getElementById(idn); if(el) el.addEventListener('click', openMenu); });
+  document.getElementById('ov-scrim').addEventListener('click', closeOv);
+  document.getElementById('adj-panel-close').addEventListener('click', () => { panel.hidden=true; menuEl.style.display=''; });
+  menu.addEventListener('click', e => { const b=e.target.closest('button[data-adj]'); if(b) showPanel(b.dataset.adj); });
+  document.addEventListener('keydown', e => { if(e.key==='Escape') closeOv(); });
+
+  function wireCalc(){
+    const cw=document.getElementById('cw'), ch=document.getElementById('ch'), crw=document.getElementById('crw'), tot=document.getElementById('ctot');
+    if(!cw) return;
+    function calc(){ const a=+cw.value||0,b=+ch.value||0,rw=+crw.value||24; if(a<1||b<1){tot.textContent='— rolls · $—';return;}
+      if(isMural){const p=Math.ceil((a*12)/rw);tot.innerHTML='<b>'+p+'</b> panels · <b>$'+(p*245).toLocaleString()+'</b>';}
+      else{const wp=Math.ceil((a*12)/rw),r=wp*Math.ceil(b/27);tot.innerHTML='<b>'+r+'</b> rolls · <b>$'+(r*165).toLocaleString()+'</b>';} }
+    [cw,ch,crw].forEach(el=>el.addEventListener('input',calc)); calc();
+  }
+  // thumbnail → swap room main image
+  document.querySelectorAll('.room-thumbs .t').forEach(t => t.addEventListener('click', () => {
+    const bg = t.style.backgroundImage; const im = document.querySelector('.room-main img');
+    const u = bg.slice(5,-2); if(im && u) im.src = u;
+  }));
+})();
+window.copyLink = async function(){ try{ await navigator.clipboard.writeText(location.href.replace(/\/[a-z0-9-]+$/,'/'+location.pathname.match(/design\/(\d+)/)[1])); alert('Link copied'); }catch{ alert('Copy failed'); } };
+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 0c667b2..156687a 100644
--- a/server.js
+++ b/server.js
@@ -466,6 +466,12 @@ app.get('/design/:id/spec',      (req, res) => themeVariantSend(req, res, 'v8-sp
 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'));
 app.get('/design/:id/best',      (req, res) => themeVariantSend(req, res, 'v11-best.html'));
+app.get('/design/:id/roomsplit',  (req, res) => themeVariantSend(req, res, 'v12-roomsplit.html'));
+app.get('/design/:id/roomdrawer', (req, res) => themeVariantSend(req, res, 'v13-roomdrawer.html'));
+app.get('/design/:id/roomzine',   (req, res) => themeVariantSend(req, res, 'v14-roomzine.html'));
+app.get('/design/:id/roomsticky', (req, res) => themeVariantSend(req, res, 'v15-roomsticky.html'));
+app.get('/design/:id/roommin',    (req, res) => themeVariantSend(req, res, 'v16-roommin.html'));
+app.get('/design/:id/roomshow',   (req, res) => themeVariantSend(req, res, 'v17-roomshow.html'));
 
 // ── Lightweight in-memory search across the catalog.
 // /api/designs/search?q=<term>&limit=20
@@ -11704,6 +11710,24 @@ try {
           <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>
+          <a class="hb-menu-item" data-hb-theme="roomsplit" role="menuitem" href="/design/${design.id}/roomsplit">
+            <span class="hb-menu-icon" aria-hidden="true">▢</span> V12 · Room-Left Split
+          </a>
+          <a class="hb-menu-item" data-hb-theme="roomdrawer" role="menuitem" href="/design/${design.id}/roomdrawer">
+            <span class="hb-menu-icon" aria-hidden="true">◧</span> V13 · Room-Left Drawer
+          </a>
+          <a class="hb-menu-item" data-hb-theme="roomzine" role="menuitem" href="/design/${design.id}/roomzine">
+            <span class="hb-menu-icon" aria-hidden="true">❏</span> V14 · Room-Left Editorial
+          </a>
+          <a class="hb-menu-item" data-hb-theme="roomsticky" role="menuitem" href="/design/${design.id}/roomsticky">
+            <span class="hb-menu-icon" aria-hidden="true">◫</span> V15 · Room-Left Sticky
+          </a>
+          <a class="hb-menu-item" data-hb-theme="roommin" role="menuitem" href="/design/${design.id}/roommin">
+            <span class="hb-menu-icon" aria-hidden="true">▫</span> V16 · Room-Left Minimal
+          </a>
+          <a class="hb-menu-item" data-hb-theme="roomshow" role="menuitem" href="/design/${design.id}/roomshow">
+            <span class="hb-menu-icon" aria-hidden="true">◉</span> V17 · Room-Left Showcase
+          </a>
         </div>
         ${_isAdmin ? `
         <button type="button" class="hb-menu-item" data-hb-action="edit" role="menuitem">
diff --git a/src/admin.js b/src/admin.js
index 1e4e59f..e151bb0 100644
--- a/src/admin.js
+++ b/src/admin.js
@@ -162,6 +162,7 @@ function adminHeader(active) {
     ['/admin/inspiration',  'Inspiration'],
     ['/admin/leads',        'Leads'],
     ['/admin/golive',       'Go-Live'],
+    ['/admin/theme-gallery','Theme Gallery'],
     ['/',                   'View site →']
   ];
   return `<header class="admin-header">

← 0b5f501 cactus-curator: per-card Bad/Digital/Unpublish animate out +  ·  back to Wallco Ai  ·  fix(publish-gate): sudo-psql on Linux + image-path fallback cf75a5c →