[object Object]

← 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')} &middot; ${categoryDisplay(design.category)} &middot; ${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 &amp; 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 →