[object Object]

← back to Interiordesignershowroom

Room Builder: fix hamburger click (was behind sticky header); show room's pieces as a shoppable grid below the generated render; single-color product column at all widths

012c8741c7b241bf8d1e74a42db175901b31b869 · 2026-08-01 21:28:41 -0700 · Steve Abrams

Files touched

Diff

commit 012c8741c7b241bf8d1e74a42db175901b31b869
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Aug 1 21:28:41 2026 -0700

    Room Builder: fix hamburger click (was behind sticky header); show room's pieces as a shoppable grid below the generated render; single-color product column at all widths
---
 public/css/site.css | 18 ++++++++++++++----
 public/js/build.js  | 14 ++++++++++++--
 server.js           |  2 ++
 3 files changed, 28 insertions(+), 6 deletions(-)

diff --git a/public/css/site.css b/public/css/site.css
index 8eec114..b6af6e8 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -992,9 +992,9 @@ h2.ruled::after {
 /* ---- rb-ham hamburger pill ---- */
 .rb-ham {
   position: fixed;
-  top: 20px;
-  right: 20px;
-  z-index: 50;
+  top: 84px;
+  right: 24px;
+  z-index: 101;
   display: inline-flex;
   align-items: center;
   gap: 8px;
@@ -1130,7 +1130,7 @@ body:has(.rb-right:not(.collapsed)) .rb-ham { opacity: 0; pointer-events: none;
   .rb-body { grid-template-columns: 1fr; }
   .rb-left { position: static; }
   .rb-right { width: 100%; border-left: none; border-top: 1px solid var(--line); }
-  .rb-products { grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); }
+  .rb-products { grid-template-columns: 1fr; }
   .rb-ham { top: 14px; right: 14px; padding: 9px 16px; font-size: .74rem; }
 }
 @media (max-width: 840px) {
@@ -1149,3 +1149,13 @@ body:has(.rb-right:not(.collapsed)) .rb-ham { opacity: 0; pointer-events: none;
   .hero { padding: 72px 24px 64px; }
   .hero h1 { font-size: 2.2rem; }
 }
+
+/* Room pieces grid (below the generated render) */
+.rb-pieces-h{margin:26px 0 12px;font-family:var(--serif);font-weight:400;font-size:1.05rem}
+.rb-pieces{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:14px}
+.rb-piece{background:var(--card);border:1px solid var(--line-lt);border-radius:3px;overflow:hidden;display:flex;flex-direction:column;transition:box-shadow .2s,transform .2s}
+.rb-piece:hover{box-shadow:0 6px 20px rgba(22,19,15,.1);transform:translateY(-2px)}
+.rb-piece img{aspect-ratio:1/1;object-fit:cover;width:100%;background:var(--bg-warm)}
+.rb-piece-b{padding:8px 9px 10px}
+.rb-piece-t{font-size:.78rem;font-weight:500;line-height:1.25}
+.rb-piece-m{font-size:.72rem;color:var(--muted);margin-top:3px}
diff --git a/public/js/build.js b/public/js/build.js
index 1ca9bd7..465daf1 100644
--- a/public/js/build.js
+++ b/public/js/build.js
@@ -55,8 +55,18 @@
   function loadBrands() { fetch('/api/brands').then(function (r) { return r.json(); }).then(function (rows) { $('rbBrands').innerHTML = rows.map(function (b) { return '<button class="rb-opt" data-brand="' + b.advertiser.replace(/"/g, '') + '">' + b.advertiser + ' <span class="subtle">' + b.n + '</span></button>'; }).join('') || '<span class="empty">—</span>'; }); }
 
   function selOne(attr, val) { Array.prototype.forEach.call(document.querySelectorAll('[' + attr + ']'), function (b) { b.classList.toggle('sel', b.getAttribute(attr) === val); }); }
-  function add(id) { var p = lastRows[id]; if (p) { board[id] = p; chips(); refresh(); } }
+  function add(id) { var p = lastRows[id]; if (p) { board[id] = p; chips(); refresh(); renderPieces(); } }
   function refresh() { Array.prototype.forEach.call(document.querySelectorAll('.rbp-add'), function (b) { b.textContent = board[b.getAttribute('data-id')] ? '✓ Added' : '+ Add'; }); }
+  function renderPieces() {
+    var ids = Object.keys(board), h = $('rbPiecesH'), el = $('rbPieces');
+    if (!ids.length) { h.hidden = true; el.innerHTML = ''; return; }
+    h.hidden = false;
+    el.innerHTML = ids.map(function (id) { var p = board[id];
+      return '<a class="rb-piece" href="/go/' + id + '" target="_blank" rel="nofollow sponsored">' +
+        '<img loading="lazy" src="' + p.image_url + '" alt=""><div class="rb-piece-b">' +
+        '<div class="rb-piece-t">' + (p.title || '').slice(0, 46) + '</div>' +
+        '<div class="rb-piece-m">' + (p.sale_price ? money(p.sale_price) : money(p.price)) + ' · ' + (p.advertiser || p.brand || '') + '</div></div></a>'; }).join('');
+  }
 
   document.addEventListener('click', function (e) {
     var sw = e.target.closest('.rb-swatch');
@@ -73,7 +83,7 @@
       if (opt.dataset.dim) { var d = opt.dataset.dim, v = opt.dataset.val; sel[d] = (sel[d] === v) ? '' : v; Array.prototype.forEach.call(document.querySelectorAll('.rb-opt[data-dim="' + d + '"]'), function (b) { b.classList.toggle('sel', b.getAttribute('data-val') === sel[d]); }); chips(); loadProducts(); return; }
     }
     var rbpc = e.target.closest('.rbp'); if (rbpc) { add(rbpc.getAttribute('data-id')); return; }
-    var rm = e.target.closest('[data-rm]'); if (rm) { delete board[rm.getAttribute('data-rm')]; chips(); refresh(); return; }
+    var rm = e.target.closest('[data-rm]'); if (rm) { delete board[rm.getAttribute('data-rm')]; chips(); refresh(); renderPieces(); return; }
     var rmd = e.target.closest('[data-rmdim]'); if (rmd) { var dd = rmd.getAttribute('data-rmdim'); sel[dd] = ''; Array.prototype.forEach.call(document.querySelectorAll('.rb-opt[data-dim="' + dd + '"]'), function (b) { b.classList.remove('sel'); }); chips(); loadProducts(); return; }
     var rmsw = e.target.closest('[data-rmsw]'); if (rmsw) { var n = rmsw.getAttribute('data-rmsw'); delete swatches[n]; var sb = document.querySelector('.rb-swatch[data-name="' + n + '"]'); if (sb) sb.classList.remove('sel'); chips(); loadProducts(); return; }
     var rmk = e.target.closest('[data-rmk]'); if (rmk) { var k = rmk.getAttribute('data-rmk'); if (k === 'room') { room = ''; $('rbRoom').value = ''; } if (k === 'cat') { cat = ''; selOne('data-cat', ''); } if (k === 'brand') { brand = ''; selOne('data-brand', ''); } chips(); loadProducts(); return; }
diff --git a/server.js b/server.js
index 9ea9597..b6dab21 100644
--- a/server.js
+++ b/server.js
@@ -331,6 +331,8 @@ app.get('/build', (_req, res) => {
           <span class="rb-cost" id="rbCost">~$0.039 each · uses your vibe + selected pieces</span>
         </div>
         <div class="rb-renders" id="rbRenders"><div class="rb-render-ph">Your generated room settings appear here.<br><small>Pick a vibe on the left (open <b>☰ Products</b> to add pieces), then <b>Generate</b>. Make several, pick your favorite, and hit <b>GO</b> to save.</small></div></div>
+        <h4 class="rb-pieces-h" id="rbPiecesH" hidden>Shop the pieces in this room</h4>
+        <div class="rb-pieces" id="rbPieces"></div>
       </div>
       <button class="rb-ham" id="rbHam" title="Show products">☰ Products</button>
       <aside class="rb-right collapsed" id="rbRight">

← 03357d1 Room Builder: right products = single image-forward column;  ·  back to Interiordesignershowroom  ·  Fix products panel + close button z-index (were behind stick 582513e →