[object Object]

← back to Quadrille House Site

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

3ea5258a9e29696d0d7a36172281968287ba26d6 · 2026-08-12 08:33:44 -0700 · Steve

Files touched

Diff

commit 3ea5258a9e29696d0d7a36172281968287ba26d6
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Aug 12 08:33:44 2026 -0700

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

diff --git a/public/index.html b/public/index.html
index 562c28d..7792a28 100644
--- a/public/index.html
+++ b/public/index.html
@@ -87,8 +87,28 @@
       <input type="range" id="density" min="2" max="20" step="1" value="4">
     </div>
     <button class="chip view-btn" id="view" type="button">▤ List view</button>
+    <div class="fieldmenu" style="position:relative">
+      <button class="chip view-btn" id="fieldsBtn" type="button" aria-haspopup="true" aria-expanded="false" style="font-family:var(--sans)">Fields ▾</button>
+      <div id="fpop" hidden role="menu" style="position:absolute;top:calc(100% + 8px);right:0;background:var(--paper);border:1px solid var(--line);border-radius:8px;box-shadow:0 14px 34px rgba(0,0,0,.16);padding:6px 4px;z-index:200;min-width:190px"></div>
+    </div>
   </div>
 </div>
+<style>
+/* Fields popover rows */
+#fpop label{display:flex;align-items:center;gap:10px;padding:7px 14px;font-family:var(--sans);font-size:12px;letter-spacing:.08em;text-transform:uppercase;color:var(--ink-soft);cursor:pointer;white-space:nowrap}
+#fpop label:hover{background:var(--bg)}
+#fpop input[type=checkbox]{accent-color:var(--accent)}
+/* Per-field hide on root.hf-<field> (HTML element, survives card re-renders) */
+html.hf-image  .card .ph        {display:none!important}
+html.hf-series .card .ser,
+html.hf-series [data-f="series"]{display:none!important}
+html.hf-name   .card .nm,
+html.hf-name   [data-f="name"]  {display:none!important}
+html.hf-color  .card .col,
+html.hf-color  [data-f="color"] {display:none!important}
+html.hf-cta    .card .pr,
+html.hf-cta    [data-f="cta"]   {display:none!important}
+</style>
 
 <!-- COLOR FILTER -->
 <div class="colorbar" id="colorbar"></div>
@@ -270,13 +290,13 @@ function render(){
   $('#cnt').textContent=items.length;
   grid.innerHTML = items.map(p=>`
     <div class="card" data-h="${p.handle}">
-      <img class="ph" loading="lazy" decoding="async" width="400" height="400" alt="${(p.title||'').replace(/"/g,'&quot;')} — ${p.brand}" src="${p.swatch||p.room||''}">
+      <img class="ph" data-f="image" loading="lazy" decoding="async" width="400" height="400" alt="${(p.title||'').replace(/"/g,'&quot;')} — ${p.brand}" src="${p.swatch||p.room||''}">
       <div class="body">
-        <div class="ser">${p.series?drill('series',p.series,p.display_eyebrow||p.series):(p.display_eyebrow||'')}</div>
-        <div class="nm">${p.display_name||p.color||p.title}</div>
+        <div class="ser" data-f="series">${p.series?drill('series',p.series,p.display_eyebrow||p.series):(p.display_eyebrow||'')}</div>
+        <div class="nm" data-f="name">${p.display_name||p.color||p.title}</div>
         <div class="meta">
-          <span class="col">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.brand}</span>
-          <span class="pr view">${p.cta_mode==='live'?'View →':p.cta_mode==='sample'?('Sample $'+Number(p.sample_price||4.25).toFixed(2)+' →'):'Request →'}</span>
+          <span class="col" data-f="color">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.brand}</span>
+          <span class="pr view" data-f="cta">${p.cta_mode==='live'?'View →':p.cta_mode==='sample'?('Sample $'+Number(p.sample_price||4.25).toFixed(2)+' →'):'Request →'}</span>
         </div>
         ${COLS.map(c=>lcCell(p,c)).join('')}
       </div>
@@ -422,6 +442,43 @@ async function loadBrand(){
   startHero(ALL.length?ALL:[]);
   loadLookbook();
 })();
+
+// ── Fields menu ────────────────────────────────────────────────────────────────
+const QH_FIELDS = [
+  ['image',  'Image',   true],
+  ['series', 'Pattern', true],
+  ['name',   'Name',    true],
+  ['color',  'Color',   true],
+  ['cta',    'CTA',     true],
+];
+function qhLoadFields(){
+  const def={}; QH_FIELDS.forEach(([k,,d])=>def[k]=d);
+  try{return Object.assign(def,JSON.parse(localStorage.getItem('qhouse.fields')||'{}'));}
+  catch(e){return def;}
+}
+const qhFields=qhLoadFields();
+function qhApplyFields(){
+  QH_FIELDS.forEach(([k])=>document.documentElement.classList.toggle('hf-'+k,!qhFields[k]));
+}
+function qhBuildFieldMenu(){
+  const pop=$('#fpop'); if(!pop)return;
+  pop.innerHTML=QH_FIELDS.map(([k,label])=>
+    `<label><input type="checkbox" data-field="${k}"${qhFields[k]?' checked':''}> ${label}</label>`).join('');
+  pop.querySelectorAll('input').forEach(cb=>cb.addEventListener('change',()=>{
+    qhFields[cb.dataset.field]=cb.checked;
+    localStorage.setItem('qhouse.fields',JSON.stringify(qhFields));
+    qhApplyFields();
+  }));
+}
+(function qhFieldsInit(){
+  qhApplyFields();
+  qhBuildFieldMenu();
+  const fb=$('#fieldsBtn'), fp=$('#fpop');
+  if(fb&&fp){
+    fb.addEventListener('click',e=>{e.stopPropagation();const open=fp.hidden;fp.hidden=!open;fb.setAttribute('aria-expanded',String(open));});
+    document.addEventListener('click',e=>{if(!fp.hidden&&!e.target.closest('.fieldmenu')){fp.hidden=true;fb.setAttribute('aria-expanded','false');}});
+  }
+})();
 </script>
 </body>
 </html>

← 9127fe1 SEO: rel=nofollow sponsored on designerwallcoverings.com lin  ·  back to Quadrille House Site  ·  Add search and AI discovery endpoints 6c6b2d8 →