[object Object]

← back to Wallco Ai

feat(pdp): theme submenu inside UR hamburger — Classic + V2 Bento Grid

7f1d2f6809173e49d4d85c148cd7cd5ade63b1e6 · 2026-05-28 17:23:30 -0700 · Steve Abrams

Adds 'Theme ▾' parent to the unified hb-menu dropdown with a nested submenu
listing layout variants. V2 Bento Grid links to /design/:id/bento (already
shipped). Submenu collapses by default, caret rotates on expand, styled to
match the existing hb-menu luxe-dark aesthetic.

Files touched

Diff

commit 7f1d2f6809173e49d4d85c148cd7cd5ade63b1e6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu May 28 17:23:30 2026 -0700

    feat(pdp): theme submenu inside UR hamburger — Classic + V2 Bento Grid
    
    Adds 'Theme ▾' parent to the unified hb-menu dropdown with a nested submenu
    listing layout variants. V2 Bento Grid links to /design/:id/bento (already
    shipped). Submenu collapses by default, caret rotates on expand, styled to
    match the existing hb-menu luxe-dark aesthetic.
---
 server.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 51 insertions(+), 4 deletions(-)

diff --git a/server.js b/server.js
index be67d0b..27c9932 100644
--- a/server.js
+++ b/server.js
@@ -3386,6 +3386,18 @@ function ensureRoomsUserIdColumn() {
 }
 ensureRoomsUserIdColumn();
 
+// upscale_bytes_2400 — caches the empirical byte size of each design's 2400px
+// LANCZOS-upscaled PNG after the first Etsy push-bundle dry-run. Read by
+// generate-etsy-bundles.js for byte-budget packing (much more accurate than
+// count-based caps — commit c3a703f2). Defensive migration so a fresh DB
+// schema gets the column automatically.
+function ensureUpscaleBytesColumn() {
+  try {
+    psqlQuery(`ALTER TABLE all_designs ADD COLUMN IF NOT EXISTS upscale_bytes_2400 BIGINT;`);
+  } catch (e) { console.error('[migrate] ensureUpscaleBytesColumn:', e.message); }
+}
+ensureUpscaleBytesColumn();
+
 function ensureUserRemovedColumn() {
   try {
     psqlQuery(`ALTER TABLE all_designs ADD COLUMN IF NOT EXISTS user_removed BOOLEAN NOT NULL DEFAULT FALSE;`);
@@ -11260,6 +11272,17 @@ try {
         <button type="button" class="hb-menu-item" data-hb-action="info" role="menuitem">
           <span class="hb-menu-icon" aria-hidden="true">ℹ</span> Info
         </button>
+        <button type="button" class="hb-menu-item hb-menu-parent" data-hb-action="theme-toggle" role="menuitem" aria-haspopup="true" aria-expanded="false">
+          <span class="hb-menu-icon" aria-hidden="true">✦</span> Theme <span class="hb-menu-caret">▾</span>
+        </button>
+        <div id="hb-theme-sub" class="hb-submenu" hidden role="menu" aria-label="Theme variants">
+          <a class="hb-menu-item" data-hb-theme="classic" role="menuitem" href="/design/${design.id}">
+            <span class="hb-menu-icon" aria-hidden="true">●</span> Classic <span class="hb-active">(current)</span>
+          </a>
+          <a class="hb-menu-item" data-hb-theme="bento" role="menuitem" href="/design/${design.id}/bento">
+            <span class="hb-menu-icon" aria-hidden="true">▦</span> V2 · Bento Grid
+          </a>
+        </div>
         ${_isAdmin ? `
         <button type="button" class="hb-menu-item" data-hb-action="edit" role="menuitem">
           <span class="hb-menu-icon" aria-hidden="true">✎</span> Edit
@@ -11290,10 +11313,20 @@ try {
           if (e.key === 'Escape' && !menu.hidden) setOpen(false);
         });
         menu.addEventListener('click', function(e){
+          const sub  = document.getElementById('hb-theme-sub');
+          const themeLink = e.target.closest('[data-hb-theme]');
+          if (themeLink) { return; } // anchor handles nav
           const item = e.target.closest('[data-hb-action]');
           if (!item) return;
-          setOpen(false);
           const a = item.getAttribute('data-hb-action');
+          if (a === 'theme-toggle') {
+            e.preventDefault();
+            const open = sub.hidden;
+            sub.hidden = !open;
+            item.setAttribute('aria-expanded', open ? 'true' : 'false');
+            return;
+          }
+          setOpen(false);
           if (a === 'info')  document.getElementById('buyer-hamburger')?.click();
           if (a === 'edit')  { if (typeof window.__openEditModal === 'function') window.__openEditModal(); }
           if (a === 'admin') document.getElementById('admin-hamburger')?.click();
@@ -11505,11 +11538,23 @@ try {
           transition:background .12s ease;
         }
         .hb-menu-item:hover, .hb-menu-item:focus-visible { background:rgba(210,177,92,.18); outline:none; }
+        .hb-menu-item { text-decoration:none; }
         .hb-menu-item .hb-menu-icon {
           display:inline-flex; align-items:center; justify-content:center;
           width:20px; height:20px; border-radius:4px;
           background:rgba(210,177,92,.15); color:#d2b15c; font-size:12px;
         }
+        .hb-menu-parent { justify-content:space-between; }
+        .hb-menu-parent .hb-menu-caret { margin-left:auto; color:#d2b15c; font-size:10px; opacity:.85; transition:transform .15s ease; }
+        .hb-menu-parent[aria-expanded="true"] .hb-menu-caret { transform:rotate(180deg); }
+        .hb-submenu {
+          margin:2px 0 4px 14px; padding:4px;
+          border-left:2px solid rgba(210,177,92,.3);
+          display:flex; flex-direction:column; gap:2px;
+        }
+        .hb-submenu[hidden] { display:none; }
+        .hb-submenu .hb-menu-item { padding:8px 10px; font-size:11.5px; }
+        .hb-active { margin-left:auto; font-size:10px; color:#9a8a5c; font-weight:400; letter-spacing:.04em; }
         @media (max-width:560px){
           #hb-menu { top:54px; right:8px; min-width:160px; }
         }
@@ -16510,10 +16555,12 @@ ${(() => {
       <h2 style="margin:0;font:500 22px/1.2 ui-serif,Georgia,serif;">See it at scale</h2>
       <span style="font:11px/1.4 ui-sans-serif,system-ui,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:var(--ink-faint,#6e6e68);">drag the slider — try 24″ / 36″ / 54″ repeats</span>
     </div>
-    <iframe src="/wall-room-viewer.html?id=${design.id}"
-            style="width:100%;height:880px;border:1px solid var(--rule,#e5e5e0);border-radius:10px;display:block;background:#fafaf7"
+    <iframe id="wall-preview-iframe"
+            src="/wall-room-viewer.html?id=${design.id}"
+            style="width:100%;height:880px;border:0;display:block;background:transparent;color-scheme:light"
             loading="lazy"
-            title="Wall preview at scale"></iframe>
+            title="Wall preview at scale"
+            onload="(function(f){try{var d=f.contentDocument;if(!d)return;function fit(){f.style.height=Math.max(700,d.body.scrollHeight+20)+'px';}fit();new ResizeObserver(fit).observe(d.body);}catch(e){}})(this)"></iframe>
     <script>
       // The wall viewer iframe sends a postMessage when an anonymous user
       // clicks "Try in your room" — we redirect THIS page to the trade-login,

← 2973b9e scripts: stamp-shopify-bundle-metafields.js — repeatable sta  ·  back to Wallco Ai  ·  polish pass (#1+#2+#5 of 'all 6 threads'): (1) loop.sh singl 83ea0b2 →