← back to Wallco Ai
feat(design page): INFO hamburger + buyer drawer next to ADMIN button
250dc8024ec6edc1b08d2752f2527169630dd683 · 2026-05-25 01:16:30 -0700 · Steve Abrams
Steve 2026-05-25: 'place all of this under the hamburger next to the
admin button now' — title, rate stars, provenance, width/material
tabs, wall-size calc, sample CTA group.
New right-anchored INFO button (top:14px right:124px when admin, else
62px). Opens a slide-in buyer-sheet (max 440px) with the same
backdrop-click + ESC-to-close pattern as the admin-sheet. ESC and
backdrop close both drawers.
Tagging pattern: any element marked .js-buyer-drawer-card is moved
client-side at DOMContentLoaded+250ms into #buyer-sheet-body. Sibling
inline <script> elements come along for the ride so wired-up handlers
keep working (#user-vote-card stars, .width-tabs delegation,
.material-tabs hydration, .wall-calc compute, .cta-group share).
Tagged in this commit:
- .detail-title (h1)
- .provenance-badge (Pattern No. X · category · kind)
- #user-vote-card (rate stars)
- .collapse-pane (Width & Material details, opened by default)
- .wall-calc (Wall-size calculator details)
- .cta-group (Request Sample / Back to Designs / Share)
NOT tagged: DW Original Number block, Curated-original provenance
paragraph, prev/next nav — easy follow-up if Steve wants them in too.
Admin hamburger shifts right (124px) when both buttons render so
they don't overlap.
Files touched
Diff
commit 250dc8024ec6edc1b08d2752f2527169630dd683
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon May 25 01:16:30 2026 -0700
feat(design page): INFO hamburger + buyer drawer next to ADMIN button
Steve 2026-05-25: 'place all of this under the hamburger next to the
admin button now' — title, rate stars, provenance, width/material
tabs, wall-size calc, sample CTA group.
New right-anchored INFO button (top:14px right:124px when admin, else
62px). Opens a slide-in buyer-sheet (max 440px) with the same
backdrop-click + ESC-to-close pattern as the admin-sheet. ESC and
backdrop close both drawers.
Tagging pattern: any element marked .js-buyer-drawer-card is moved
client-side at DOMContentLoaded+250ms into #buyer-sheet-body. Sibling
inline <script> elements come along for the ride so wired-up handlers
keep working (#user-vote-card stars, .width-tabs delegation,
.material-tabs hydration, .wall-calc compute, .cta-group share).
Tagged in this commit:
- .detail-title (h1)
- .provenance-badge (Pattern No. X · category · kind)
- #user-vote-card (rate stars)
- .collapse-pane (Width & Material details, opened by default)
- .wall-calc (Wall-size calculator details)
- .cta-group (Request Sample / Back to Designs / Share)
NOT tagged: DW Original Number block, Curated-original provenance
paragraph, prev/next nav — easy follow-up if Steve wants them in too.
Admin hamburger shifts right (124px) when both buttons render so
they don't overlap.
---
server.js | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 75 insertions(+), 8 deletions(-)
diff --git a/server.js b/server.js
index ced7781..f1bdedb 100644
--- a/server.js
+++ b/server.js
@@ -9233,6 +9233,52 @@ ${htmlHeader('/designs')}
aria-haspopup="dialog" aria-expanded="false">
<span></span><span></span><span></span>
</button>
+ <!-- BUYER HAMBURGER — info / sizing / sample / share, visible to everyone -->
+ <button id="buyer-hamburger" class="buyer-hamburger" aria-label="Open design info, sizing, and ordering"
+ aria-haspopup="dialog" aria-expanded="false" title="Info, sizing, ordering">INFO</button>
+ <div id="buyer-sheet-backdrop" class="buyer-sheet-backdrop" data-close-buyer="1"></div>
+ <aside id="buyer-sheet" class="buyer-sheet" role="dialog" aria-modal="false" aria-label="Design info, sizing, ordering">
+ <div class="buyer-sheet-head">
+ <h3>This design</h3>
+ <button class="buyer-sheet-close" data-close-buyer="1" aria-label="Close">✕</button>
+ </div>
+ <div id="buyer-sheet-body"><!-- title / rate / width / material / wall calc / sample CTA / share / provenance / prev-next hydrated client-side --></div>
+ </aside>
+ <script>
+ (function(){
+ const btn = document.getElementById('buyer-hamburger');
+ const sh = document.getElementById('buyer-sheet');
+ const bd = document.getElementById('buyer-sheet-backdrop');
+ if (!btn || !sh) return;
+ const open = () => { sh.classList.add('open'); bd.classList.add('open'); btn.setAttribute('aria-expanded','true'); };
+ const close = () => { sh.classList.remove('open'); bd.classList.remove('open'); btn.setAttribute('aria-expanded','false'); };
+ btn.addEventListener('click', () => sh.classList.contains('open') ? close() : open());
+ document.addEventListener('click', (e) => { if (e.target && e.target.matches('[data-close-buyer]')) close(); });
+ document.addEventListener('keydown', (e) => { if (e.key === 'Escape') close(); });
+ // Mover: collect all .js-buyer-drawer-card elements (and their
+ // immediately-following <script> wire-ups) and move into the buyer
+ // sheet body. Runs after admin-sheet mover (which steals
+ // .js-admin-drawer-card) so the two don't fight over the same
+ // elements. setTimeout 200ms gives both a chance to settle.
+ function moveBlock(el, body) {
+ if (!el || !body) return;
+ const sib = el.nextElementSibling && el.nextElementSibling.tagName === 'SCRIPT' ? el.nextElementSibling : null;
+ body.appendChild(el);
+ if (sib) body.appendChild(sib);
+ }
+ function moveAll() {
+ const body = document.getElementById('buyer-sheet-body');
+ if (!body) return;
+ document.querySelectorAll('.js-buyer-drawer-card').forEach(el => moveBlock(el, body));
+ }
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', () => setTimeout(moveAll, 250));
+ } else {
+ setTimeout(moveAll, 250);
+ }
+ })();
+ </script>
+
${_isAdmin ? `
<!-- RIGHT HAMBURGER (admin-only tabs: tone-on-tone fashion palettes) -->
<button id="admin-hamburger" class="admin-hamburger" aria-label="Open admin tools"
@@ -9424,6 +9470,22 @@ ${htmlHeader('/designs')}
Steve 2026-05-25: "move admin button to left of the hamburger". */
.admin-hamburger { position:absolute; top:14px; right:62px; height:42px; padding:0 12px; background:rgba(60,20,15,.85); backdrop-filter:blur(8px); color:#fdd6a0; border:1px solid rgba(255,180,120,.2); border-radius:8px; cursor:pointer; display:flex; align-items:center; justify-content:center; z-index:18; transition:background .15s; font:600 11px var(--sans,system-ui); letter-spacing:.06em; }
.admin-hamburger:hover { background:rgba(60,20,15,.95); border-color:#d2b15c; }
+ /* INFO hamburger — sits next to the ADMIN button on the right.
+ Opens the buyer drawer (right side, distinct from admin). Steve
+ 2026-05-25: "place all of this under the hamburger next to the
+ admin button now". Visible to everyone (no _isAdmin gate). */
+ .buyer-hamburger { position:absolute; top:14px; right:${_isAdmin ? '124px' : '62px'}; height:42px; padding:0 12px; background:rgba(20,18,15,.85); backdrop-filter:blur(8px); color:#f0eadc; border:1px solid rgba(255,255,255,.1); border-radius:8px; cursor:pointer; display:flex; align-items:center; justify-content:center; z-index:18; transition:background .15s; font:600 11px var(--sans,system-ui); letter-spacing:.06em; }
+ .buyer-hamburger:hover { background:rgba(20,18,15,.95); border-color:#d2b15c; }
+ .buyer-sheet { position:fixed; top:0; right:0; bottom:0; width:min(440px,94vw); background:#faf7f1; box-shadow:-12px 0 40px rgba(0,0,0,.2); border-left:1px solid var(--line,#ddd); transform:translateX(100%); transition:transform .25s ease; z-index:30; overflow-y:auto; padding:18px 18px 32px; }
+ .buyer-sheet.open { transform:translateX(0); }
+ .buyer-sheet-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.35); opacity:0; pointer-events:none; transition:opacity .25s ease; z-index:29; }
+ .buyer-sheet-backdrop.open { opacity:1; pointer-events:auto; }
+ .buyer-sheet-head { display:flex; align-items:center; justify-content:space-between; margin:0 0 14px; padding-bottom:10px; border-bottom:1px solid var(--line,#ddd); }
+ .buyer-sheet-head h3 { margin:0; font:600 12px var(--sans,system-ui); letter-spacing:.12em; text-transform:uppercase; color:#5a4628; }
+ .buyer-sheet-close { background:transparent; border:0; font:18px var(--sans,system-ui); color:#7a6e5a; cursor:pointer; padding:4px 8px; }
+ /* Inside the buyer sheet, any block that arrived via the mover
+ should have visible separation. */
+ #buyer-sheet-body > * { margin-bottom:18px; }
.admin-sheet { position:fixed; top:0; right:0; bottom:0; width:min(420px,92vw); background:#faf7f1; box-shadow:-12px 0 40px rgba(0,0,0,.2); border-left:1px solid var(--line,#ddd); transform:translateX(100%); transition:transform .25s ease; z-index:30; overflow-y:auto; padding:18px 18px 32px; }
.admin-sheet.open { transform:translateX(0); }
.admin-sheet-backdrop { position:fixed; inset:0; background:rgba(0,0,0,.35); opacity:0; pointer-events:none; transition:opacity .25s ease; z-index:29; }
@@ -9515,8 +9577,13 @@ ${htmlHeader('/designs')}
thumb to swap it with the main. Steve 2026-05-25: "stack these
into a main image then 2 thumbs below, until you click on the
thumb and it becomes the main image". */
- .img-stack-gallery .img-stack-main > * { display:block; }
- .img-stack-gallery .img-stack-thumbs { display:flex; gap:8px; margin-top:8px; }
+ .img-stack-gallery { margin-bottom:14px; }
+ .img-stack-gallery .img-stack-main { margin:0; padding:0; }
+ .img-stack-gallery .img-stack-main > * { display:block; margin:0 !important; padding-top:0 !important; }
+ /* Thumbs sit FLUSH directly under the hero — no gap, no separator
+ content. Steve 2026-05-25: "move both thumbs to directly below
+ the hero image". */
+ .img-stack-gallery .img-stack-thumbs { display:flex; gap:6px; margin:6px 0 0; padding:0; }
.img-stack-gallery .img-stack-thumb {
flex:1 1 0; min-width:0; max-height:108px; overflow:hidden;
border:1px solid var(--line,#ddd); border-radius:6px; cursor:pointer;
@@ -11514,7 +11581,7 @@ ${(() => {
} catch(_) {}
})();
</script>
- <h1 class="detail-title">${design.title}</h1>
+ <h1 class="detail-title js-buyer-drawer-card">${design.title}</h1>
${(() => {
// Product-line chip — Scenic (photoreal hospitality murals) or
@@ -11533,13 +11600,13 @@ ${(() => {
return _chip + _sub;
})()}
- <div class="provenance-badge">
+ <div class="provenance-badge js-buyer-drawer-card">
${_isAdmin ? `<span class="ai-badge">Curated-Original</span>` : ''}
Pattern No.${String(design.id).padStart(4,'0')} · ${categoryDisplay(design.category)} · ${design.kind === 'seamless_tile' ? 'Seamless Tile' : design.kind === 'mural_panel' ? 'Mural Panel' : (design.kind || 'pattern')}
</div>
<!-- USER VOTE — quieter, smaller stars, set behind CTA visually -->
- <div id="user-vote-card" style="margin:10px 0;padding:8px 12px;background:transparent;border:none;font-size:12px">
+ <div id="user-vote-card" class="js-buyer-drawer-card" style="margin:10px 0;padding:8px 12px;background:transparent;border:none;font-size:12px">
<div style="display:flex;align-items:center;gap:10px;flex-wrap:wrap;color:#7a6e5a">
<span style="font-family:'Cormorant Garamond',serif;font-weight:400;font-size:12px;letter-spacing:.04em;text-transform:uppercase;color:#7a6e5a">Rate this</span>
<div id="star-row" data-id="${design.id}" style="font-size:16px;letter-spacing:2px;cursor:pointer;color:rgba(201,161,75,.55)">
@@ -11717,7 +11784,7 @@ ${(() => {
})()}
<!-- Width + material selector -->
- <details class="collapse-pane">
+ <details class="collapse-pane js-buyer-drawer-card" open>
<summary>Width & Material <span class="cp-sub">choose roll width and material</span></summary>
<div class="collapse-body">
<div class="material-picker" style="padding:4px 0 0;border:0;background:transparent">
@@ -11840,7 +11907,7 @@ ${(() => {
</script>
<!-- Wall-size calculator: enter W×H, get panel count + total yardage + estimated cost -->
- <details class="wall-calc" style="margin:14px 0 0;padding:0;border:1px solid var(--line,#ddd);border-radius:6px;background:var(--card-bg,#faf8f3)">
+ <details class="wall-calc js-buyer-drawer-card" style="margin:14px 0 0;padding:0;border:1px solid var(--line,#ddd);border-radius:6px;background:var(--card-bg,#faf8f3)">
<summary style="cursor:pointer;padding:10px 14px;font:12px var(--sans,system-ui);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-soft,#555);list-style:none">
<span aria-hidden="true">📐</span> Wall-size calculator <span style="color:var(--ink-faint,#999);font-size:11px;text-transform:none;letter-spacing:0">— estimate panels, yardage, cost</span>
<span id="wc-summary-chip" style="display:none;margin-left:8px;padding:2px 8px;background:#d2b15c33;border:1px solid #d2b15c;border-radius:10px;color:#5a4520;font-size:11px;text-transform:none;letter-spacing:0;font-weight:600" aria-hidden="true"></span>
@@ -12022,7 +12089,7 @@ ${(() => {
</script>
<!-- Sample CTA (DW standing rule — non-negotiable) -->
- <div class="cta-group">
+ <div class="cta-group js-buyer-drawer-card">
<a id="cta-sample-link" href="/samples?design=${design.id}" class="btn-primary cta-sample">Request Free Sample</a>
<a href="/designs" class="btn-outline">Back to Designs</a>
<button id="cta-share" type="button" class="btn-outline cta-share"
← 8271d6d roll-luxe-c runner + /luxe-c-gallery.html viewer
·
back to Wallco Ai
·
fix(recolor): tighten brand-mode prompt so signature accent fd353db →