[object Object]

← back to Vinylwallpaper

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

85440a58b795cb3543f44fe63e54d7afd5e851c9 · 2026-08-12 08:11:04 -0700 · Steve

Files touched

Diff

commit 85440a58b795cb3543f44fe63e54d7afd5e851c9
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:11:04 2026 -0700

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

diff --git a/public/index.html b/public/index.html
index 862a0ef..be76133 100644
--- a/public/index.html
+++ b/public/index.html
@@ -189,6 +189,22 @@ 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 }
 
 /* === RAILS PATCH CSS (DW fleet sweep A4 2026-05-26) === */
 #rails{display:block;margin:0 0 40px}
@@ -464,6 +480,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 &#9662;</button>
+      <div class="fpop" id="fpop" hidden role="menu"></div>
+    </div>
   </div>
 
   <div class="stat-line" id="statLine">Loading…</div>
@@ -651,12 +671,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>';
 }
 
@@ -666,11 +686,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 +995,59 @@ async function renderRailSections(){
 }
 // === END RAILS PATCH ===
 
+// ===== 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();

← e5b4b70 fix: take infinite-scroll .loading indicator out of flow (ki  ·  back to Vinylwallpaper  ·  auto-data-snapshot: 2026-08-22T11:19:22 (1 data files) — dat c1e7b65 →