[object Object]

← back to Linenwallpaper

viewer standard: add per-field on/off menu (Fields ▾) to product grid

c565a5127ec4a65e1b0045b87d7fe469ea7db5f8 · 2026-08-12 08:10:57 -0700 · Steve

Files touched

Diff

commit c565a5127ec4a65e1b0045b87d7fe469ea7db5f8
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:10:57 2026 -0700

    viewer standard: add per-field on/off menu (Fields ▾) to product grid
---
 public/index.html | 92 +++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 83 insertions(+), 9 deletions(-)

diff --git a/public/index.html b/public/index.html
index dbc4a9d..f54f353 100644
--- a/public/index.html
+++ b/public/index.html
@@ -199,6 +199,23 @@ textarea:focus-visible,
   outline: 2px solid var(--accent);
   outline-offset: 2px;
 }
+
+/* ===== Fields on/off menu ===== */
+.fieldmenu { position:relative; flex-shrink:0 }
+.fbtn { padding:5px 11px; border:1px solid var(--line); background:transparent; font-family:inherit; font-size:10px; letter-spacing:0.22em; text-transform:uppercase; font-weight:700; color:var(--muted); cursor:pointer; transition:all .15s }
+.fbtn:hover { border-color:var(--paper); color:var(--paper) }
+.fpop { position:absolute; top:calc(100% + 8px); right:0; background:var(--bg); border:1px solid var(--line); box-shadow:0 14px 34px rgba(0,0,0,.22); padding:6px 4px; z-index:50; min-width:176px }
+.fpop[hidden] { display:none }
+.fpop label { display:flex; align-items:center; gap:10px; padding:7px 14px; font-family:inherit; font-size:10px; letter-spacing:0.18em; text-transform:uppercase; font-weight:600; color:var(--paper); cursor:pointer; white-space:nowrap }
+.fpop label:hover { background:var(--bg-soft) }
+.fpop input[type=checkbox] { accent-color:var(--accent); width:13px; height:13px; cursor:pointer }
+/* Per-field hide rules — keyed on html.hf-<field> (survives re-renders, no re-apply needed) */
+html.hf-image  [data-f="image"]   { display:none !important }
+html.hf-pattern [data-f="pattern"] { display:none !important }
+html.hf-vendor  [data-f="vendor"]  { display:none !important }
+html.hf-color   [data-f="color"]   { display:none !important }
+html.hf-price   [data-f="price"]   { display:none !important }
+html.hf-sample  [data-f="sample"]  { display:none !important }
 </style>
 <script>
 (function(){ try { var t = localStorage.getItem('linen_theme') || 'light'; document.documentElement.dataset.theme = t; } catch(e){} })();
@@ -573,6 +590,10 @@ textarea:focus-visible,
       <svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="M21 21l-5.5-5.5"/></svg>
       <input type="text" id="searchInput" placeholder="Search by pattern, color…" autocomplete="off">
     </div>
+    <div class="fieldmenu">
+      <button class="fbtn" id="fieldsBtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+      <div class="fpop" id="fpop" hidden role="menu"></div>
+    </div>
   </div>
 
   <div class="stat-line" id="statLine">Loading…</div>
@@ -740,12 +761,12 @@ function cleanSku(s){
 function safeImg(u) { u = u || ''; return (/^\/img\//.test(u) || /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u)) ? u : '/hero-bg.jpg'; }
 function cardHTML(p) {
   const eager = state.page === 1;
-  return '<img loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+  return '<img data-f="image" loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
     + '<div class="overlay">'
-    + '<div class="pat">' + escAttr(p.pattern_name || p.title) + '</div>'
-    + '<div class="ven">Designer Wallcoverings' + (cleanSku(p.sku || p.handle_display || p.handle) ? ' · ' + escAttr(cleanSku(p.sku || p.handle_display || p.handle)) : '') + '</div>'
+    + '<div class="pat" data-f="pattern">' + escAttr(p.pattern_name || p.title) + '</div>'
+    + '<div class="ven" data-f="vendor">Designer Wallcoverings' + (cleanSku(p.sku || p.handle_display || p.handle) ? ' · ' + escAttr(cleanSku(p.sku || p.handle_display || p.handle)) : '') + '</div>'
     + '<div class="actions">'
-    + '<button class="sample-btn" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle_display||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'&quot;') + ')">Sample</button>'
+    + '<button class="sample-btn" data-f="sample" onclick="event.stopPropagation();dwmOpen(\'Sample\',' + JSON.stringify({sku:p.sku||p.handle_display||p.handle, title:p.title, image_url:safeImg(p.image_url)}).replace(/"/g,'&quot;') + ')">Sample</button>'
     + '</div></div>';
 }
 
@@ -755,11 +776,11 @@ function rowHTML(p) {
   const mp = Number(p.max_price);
   const price = (p.max_price != null && !isNaN(mp) && mp > 0) ? '$' + mp.toFixed(2) : '—';
   const aes = LABELS[p.aesthetic] || p.aesthetic || '—';
-  return '<img loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
-    + '<div class="r-pat">' + escAttr(p.pattern_name || p.title) + '</div>'
-    + '<div class="r-col r-sku">' + escAttr(cleanSku(p.sku || p.handle_display || p.handle) || '—') + '</div>'
-    + '<div class="r-col r-aes">' + (p.aesthetic ? sfDrill(p.aesthetic, String(aes).replace(/-/g, ' '), 'span') : escAttr(String(aes).replace(/-/g, ' '))) + '</div>'
-    + '<div class="r-col r-price">' + price + '</div>';
+  return '<img data-f="image" loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+    + '<div class="r-pat" data-f="pattern">' + escAttr(p.pattern_name || p.title) + '</div>'
+    + '<div class="r-col r-sku" data-f="vendor">' + escAttr(cleanSku(p.sku || p.handle_display || p.handle) || '—') + '</div>'
+    + '<div class="r-col r-aes" data-f="color">' + (p.aesthetic ? sfDrill(p.aesthetic, String(aes).replace(/-/g, ' '), 'span') : escAttr(String(aes).replace(/-/g, ' '))) + '</div>'
+    + '<div class="r-col r-price" data-f="price">' + price + '</div>';
 }
 
 // Click-to-open-full-details — every card opens a rich detail modal showing
@@ -975,6 +996,59 @@ function setTheme(t){ document.documentElement.dataset.theme = t; try { localSto
 setTheme(document.documentElement.dataset.theme || 'light');
 tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
 
+// ===== Fields on/off menu =====
+// [key, label, defaultVisible] — color=OFF by default; image-only achievable by turning rest off.
+const FIELDS = [
+  ['image',   'Image',    true],
+  ['pattern', 'Pattern',  true],
+  ['vendor',  'Vendor',   true],
+  ['color',   'Color',    false],
+  ['price',   'Price',    true],
+  ['sample',  'Sample',   true],
+];
+function loadFields() {
+  const def = {}; FIELDS.forEach(([k,,d]) => def[k] = d);
+  try { return Object.assign(def, JSON.parse(localStorage.getItem(HOSTKEY + '_fields') || '{}')); } catch(e) { return def; }
+}
+let _fields = loadFields();
+function applyFields() {
+  FIELDS.forEach(([k]) => document.documentElement.classList.toggle('hf-' + k, !_fields[k]));
+}
+function buildFieldMenu() {
+  const fpop = document.getElementById('fpop');
+  if (!fpop) return;
+  fpop.innerHTML = FIELDS.map(([k, label]) =>
+    '<label><input type="checkbox" data-field="' + k + '"' + (_fields[k] ? ' checked' : '') + '> ' + label + '</label>'
+  ).join('');
+  fpop.querySelectorAll('input[type=checkbox]').forEach(function(cb) {
+    cb.addEventListener('change', function() {
+      _fields[cb.dataset.field] = cb.checked;
+      try { localStorage.setItem(HOSTKEY + '_fields', JSON.stringify(_fields)); } catch(e) {}
+      applyFields();
+    });
+  });
+}
+// Fields popover open/close
+(function() {
+  var btn = document.getElementById('fieldsBtn');
+  var pop = document.getElementById('fpop');
+  if (!btn || !pop) return;
+  btn.addEventListener('click', function(e) {
+    e.stopPropagation();
+    var open = pop.hidden;
+    pop.hidden = !open;
+    btn.setAttribute('aria-expanded', String(open));
+  });
+  document.addEventListener('click', function(e) {
+    if (!pop.hidden && !e.target.closest('.fieldmenu')) {
+      pop.hidden = true;
+      btn.setAttribute('aria-expanded', 'false');
+    }
+  });
+})();
+buildFieldMenu();
+applyFields();
+
 sfReadURL();
 loadFacets();
 loadGridPage();

← fb89df3 corner-nav: install fleet-ready two-hamburger accordion nav  ·  back to Linenwallpaper  ·  linenwallpaper: tighten linen filter — require linen/flax in c3c76d8 →