← 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
M public/css/site.cssM public/js/build.jsM server.js
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 →