[object Object]

← back to Wallsandfabrics

viewer standard: add Grid/List toggle + per-field on/off menu (Fields) to product grid

8e3604a732987e22de3a9f3f004ca1613df55674 · 2026-08-12 08:33:43 -0700 · Steve

Files touched

Diff

commit 8e3604a732987e22de3a9f3f004ca1613df55674
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:33:43 2026 -0700

    viewer standard: add Grid/List toggle + per-field on/off menu (Fields) to product grid
---
 public/index.html | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 84 insertions(+), 3 deletions(-)

diff --git a/public/index.html b/public/index.html
index 750d1bc..2314e9d 100644
--- a/public/index.html
+++ b/public/index.html
@@ -115,6 +115,25 @@ footer.site .grid-foot{display:grid;grid-template-columns:2fr 1fr 1fr;gap:30px}
 footer.site h4{font-size:11px;text-transform:uppercase;letter-spacing:.18em;color:var(--fg);margin:0 0 10px}
 footer.site a{color:var(--accent2)}
 .legal{margin-top:18px;font-size:12px;color:var(--muted)}
+
+/* ── Grid / List view ── */
+html.waf-list #results.grid { display:block; padding-bottom:60px; }
+html.waf-list .card { display:flex; flex-direction:row; align-items:stretch; border-radius:8px; margin-bottom:10px; }
+html.waf-list .card .img { flex:0 0 clamp(72px,14vw,140px); width:clamp(72px,14vw,140px); min-height:72px; aspect-ratio:auto; border-radius:8px 0 0 8px; }
+html.waf-list .card .body { flex:1; display:flex; flex-direction:column; justify-content:center; }
+html.waf-list .card:hover { transform:none; }
+
+/* ── Per-field hide rules (html.hf-<field>) ── */
+html.hf-image .card .img { display:none; }
+html.hf-surface [data-f="surface"] { display:none; }
+html.hf-title [data-f="title"] { display:none; }
+html.hf-meta [data-f="meta"] { display:none; }
+html.hf-sample [data-f="sample"] { display:none; }
+
+/* ── Fields popover labels ── */
+#wafFpop label { display:flex; align-items:center; gap:10px; padding:7px 14px; font-size:12px; letter-spacing:.05em; text-transform:uppercase; color:var(--fg); cursor:pointer; white-space:nowrap; font-family:inherit; }
+#wafFpop label:hover { background:var(--bg); border-radius:4px; }
+#wafFpop input[type=checkbox] { accent-color:var(--accent); width:13px; height:13px; cursor:pointer; }
 </style>
 <!-- Meta Pixel — INERT until set-fb-pixel.sh is run (does NOT load fbevents.js or hit Meta until flipped) -->
 <script>
@@ -284,6 +303,16 @@ footer.site a{color:var(--accent2)}
     <div class="group"><label for="cols">Density</label>
       <input id="cols" type="range" min="3" max="8" step="1" value="5">
     </div>
+    <!-- Grid/List view toggle -->
+    <div class="group" role="group" aria-label="View mode" style="gap:0;border:1px solid var(--line);border-radius:8px;overflow:hidden;">
+      <button id="wafVGrid" style="padding:9px 14px;background:var(--fg);color:var(--bg);border:0;font-size:12px;letter-spacing:.05em;text-transform:uppercase;cursor:pointer;font-family:inherit;" title="Grid view">Grid</button>
+      <button id="wafVList" style="padding:9px 14px;background:transparent;color:var(--muted);border:0;border-left:1px solid var(--line);font-size:12px;letter-spacing:.05em;text-transform:uppercase;cursor:pointer;font-family:inherit;" title="List view">List</button>
+    </div>
+    <!-- Fields ▾ menu -->
+    <div class="waf-fieldmenu" style="position:relative;">
+      <button id="wafFieldsBtn" style="padding:10px 14px;border:1px solid var(--line);background:var(--paper);border-radius:8px;font:inherit;font-size:12px;letter-spacing:.05em;text-transform:uppercase;color:var(--muted);cursor:pointer;" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+      <div id="wafFpop" hidden style="position:absolute;top:calc(100% + 8px);right:0;background:var(--paper);border:1px solid var(--line);box-shadow:0 12px 28px rgba(0,0,0,.12);padding:6px 4px;z-index:40;min-width:180px;border-radius:8px;" role="menu"></div>
+    </div>
   </div>
 
   <div id="results" class="grid"></div>
@@ -420,9 +449,9 @@ footer.site a{color:var(--accent2)}
     var surfaceAtom='<span class="surface drill" data-filter="surface" data-surface="'+surface+'" data-v="'+surface+'" title="See all '+label+'">'+label+'</span>';
     return '<a href="'+url+'" target="_blank" rel="noopener noreferrer" class="card" data-sku="'+(p.sku||p.handle||'')+'">'
       +img
-      +'<div class="body">'+surfaceAtom+'<div class="ttl">'+ttl+'</div>'
-      +'<div class="meta"><span>'+brand+'</span><span>'+fmt$(p)+'</span></div>'
-      +'<span class="smp">Request memo →</span></div></a>';
+      +'<div class="body">'+surfaceAtom+'<div class="ttl" data-f="title">'+ttl+'</div>'
+      +'<div class="meta" data-f="meta"><span>'+brand+'</span><span>'+fmt$(p)+'</span></div>'
+      +'<span class="smp" data-f="sample">Request memo →</span></div></a>';
   }
 
   function renderEmpty(){
@@ -451,6 +480,58 @@ footer.site a{color:var(--accent2)}
   sfReadURL();          // deep-link / back-button: hydrate surface + q + sort + page from the URL
   reload(state.page);   // render at the URL-provided state
   sfWriteURL(false);    // normalize the URL to the canonical filter-set
+
+  // ── Grid/List view toggle ─────────────────────────────────────────────────
+  const WAF_FIELDS = [
+    ['image',   'Image',   true],
+    ['surface', 'Surface', true],
+    ['title',   'Title',   true],
+    ['meta',    'Price',   true],
+    ['sample',  'Sample',  true],
+  ];
+  function wafLoadFields(){
+    const def = {}; WAF_FIELDS.forEach(([k,,d]) => def[k] = d);
+    try { return Object.assign(def, JSON.parse(localStorage.getItem('waf-fields') || '{}')); } catch(e){ return def; }
+  }
+  var wafFieldState = wafLoadFields();
+
+  function wafApplyView(){
+    var v = localStorage.getItem('waf-view') || 'grid';
+    document.documentElement.classList.toggle('waf-list', v === 'list');
+    var gBtn = document.getElementById('wafVGrid'), lBtn = document.getElementById('wafVList');
+    if(gBtn){ gBtn.style.background = v==='grid'?'var(--fg)':'transparent'; gBtn.style.color = v==='grid'?'var(--bg)':'var(--muted)'; }
+    if(lBtn){ lBtn.style.background = v==='list'?'var(--fg)':'transparent'; lBtn.style.color = v==='list'?'var(--bg)':'var(--muted)'; }
+  }
+  var wafVG = document.getElementById('wafVGrid'), wafVL = document.getElementById('wafVList');
+  if(wafVG) wafVG.addEventListener('click', function(){ localStorage.setItem('waf-view','grid'); wafApplyView(); });
+  if(wafVL) wafVL.addEventListener('click', function(){ localStorage.setItem('waf-view','list'); wafApplyView(); });
+  wafApplyView();
+
+  // ── Fields ▾ menu ─────────────────────────────────────────────────────────
+  function wafApplyFields(){
+    WAF_FIELDS.forEach(function(f){ document.documentElement.classList.toggle('hf-' + f[0], !wafFieldState[f[0]]); });
+  }
+  function wafBuildFieldMenu(){
+    var pop = document.getElementById('wafFpop'); if(!pop) return;
+    pop.innerHTML = WAF_FIELDS.map(function(f){ return '<label><input type="checkbox" data-field="'+f[0]+'"'+(wafFieldState[f[0]]?' checked':'')+'> '+f[1]+'</label>'; }).join('');
+    pop.querySelectorAll('input').forEach(function(cb){ cb.addEventListener('change', function(){
+      wafFieldState[cb.dataset.field] = cb.checked;
+      localStorage.setItem('waf-fields', JSON.stringify(wafFieldState));
+      wafApplyFields();
+    }); });
+  }
+  var wafFBtn = document.getElementById('wafFieldsBtn'), wafFpop = document.getElementById('wafFpop');
+  if(wafFBtn && wafFpop){
+    wafFBtn.addEventListener('click', function(e){
+      e.stopPropagation(); var open = wafFpop.hidden; wafFpop.hidden = !open;
+      wafFBtn.setAttribute('aria-expanded', String(open));
+    });
+    document.addEventListener('click', function(e){
+      if(!wafFpop.hidden && !e.target.closest('.waf-fieldmenu')){ wafFpop.hidden = true; wafFBtn.setAttribute('aria-expanded','false'); }
+    });
+  }
+  wafBuildFieldMenu();
+  wafApplyFields();
 })();
 </script>
 

← 97292cb Wire DW Meta Pixel 1431180262113856 + DW-network ad slot [ad  ·  back to Wallsandfabrics  ·  TK-11341: add AdSense Auto-Ads loader + ads.txt (revert to r 5fcb3de →