[object Object]

← back to All Designerwallcoverings

microsites.html: add list/grid toggle + per-field on/off menu (full viewer control standard)

95446fa377c96d83fba14007aa9f7a37db086a62 · 2026-08-11 14:07:30 -0700 · Steve

Files touched

Diff

commit 95446fa377c96d83fba14007aa9f7a37db086a62
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue Aug 11 14:07:30 2026 -0700

    microsites.html: add list/grid toggle + per-field on/off menu (full viewer control standard)
---
 public/microsites.html | 113 +++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 105 insertions(+), 8 deletions(-)

diff --git a/public/microsites.html b/public/microsites.html
index fd6ac26..2acec69 100644
--- a/public/microsites.html
+++ b/public/microsites.html
@@ -44,7 +44,43 @@
   .dens { display:flex; align-items:center; gap:10px; font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:#7d766a; }
   .dens input { accent-color: var(--gold); width: 130px; }
 
+  /* Fields on/off menu — toggle any field on the card (down to image-only). */
+  .fieldmenu { position:relative; }
+  .fbtn { padding:9px 14px; border:1px solid var(--line); background:#fff; font-size:12px; letter-spacing:.1em;
+    text-transform:uppercase; color:#7d766a; cursor:pointer; border-radius:2px; }
+  .fbtn:hover { border-color:var(--gold); color:var(--ink); }
+  .fpop { position:absolute; top:calc(100% + 8px); right:0; background:#fff; border:1px solid var(--line);
+    border-radius:2px; box-shadow:0 14px 34px rgba(22,20,18,.16); padding:10px 6px; z-index:30; min-width:190px; }
+  .fpop[hidden] { display:none; }
+  .fpop label { display:flex; align-items:center; gap:10px; padding:7px 14px; font-size:12px; letter-spacing:.08em;
+    text-transform:uppercase; color:#4b463d; cursor:pointer; white-space:nowrap; }
+  .fpop label:hover { background:var(--paper); }
+  .fpop input { accent-color: var(--gold); }
+
   .grid { display:grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 26px; padding: 34px; }
+
+  /* List view — cards become horizontal rows (thumbnail left, meta right). */
+  .grid.list { display:block; padding: 20px 34px; }
+  .grid.list .card { flex-direction:row; align-items:stretch; margin-bottom:14px; }
+  .grid.list .card:hover { transform:none; }
+  .grid.list .collage { flex:0 0 clamp(120px, 22vw, 210px); aspect-ratio:4/3; }
+  .grid.list .meta { flex:1; display:flex; flex-direction:column; justify-content:center; }
+  .grid.list .badge { display:none; }
+
+  /* Extra record fields (off by default) — surfaced as labeled lines so each is toggleable. */
+  .card .fline { margin-top:6px; font-size:11px; letter-spacing:.14em; text-transform:uppercase; color:#9a927f; }
+  .card .fline b { color:#6b6459; font-weight:600; }
+
+  /* Per-field hide switches driven by body.hf-<field> classes (set from the Fields menu). */
+  body.hf-image  .collage,
+  body.hf-image  .card:not(.list) .collage { display:none; }
+  body.hf-name   [data-f="name"]   { display:none; }
+  body.hf-type   [data-f="type"]   { display:none; }
+  body.hf-slug   [data-f="slug"]   { display:none; }
+  body.hf-count  [data-f="count"]  { display:none; }
+  body.hf-status [data-f="status"] { display:none; }
+  body.hf-cta    [data-f="cta"]    { display:none; }
+  body.hf-inquiry [data-f="inquiry"] { display:none; }
   .card { background:#fff; border:1px solid var(--line); overflow:hidden; display:flex; flex-direction:column;
     transition: box-shadow .25s, transform .25s; position:relative; }
   .card:hover { box-shadow: 0 14px 34px rgba(22,20,18,.14); transform: translateY(-3px); }
@@ -139,6 +175,14 @@
     <option value="gated">Trade-Gated First</option>
   </select>
   <label class="dens">Density <input type="range" id="density" min="1" max="30" step="1" value="4"><span id="densN">4</span></label>
+  <div class="seg" role="tablist" aria-label="View">
+    <button id="vGrid" class="on">Grid</button>
+    <button id="vList">List</button>
+  </div>
+  <div class="fieldmenu">
+    <button id="fieldsBtn" class="fbtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+    <div class="fpop" id="fpop" hidden role="menu"></div>
+  </div>
 </div>
 
 <main class="grid" id="grid"></main>
@@ -168,12 +212,31 @@
 <script>
 const grid = document.getElementById('grid'), sentinel = document.getElementById('sentinel');
 const menu = document.getElementById('menu');
+// Every field on a card is user-toggleable; [key, label, default-visible].
+const FIELDS = [
+  ['image',   'Image',      true],
+  ['name',    'Name',       true],
+  ['type',    'Type',       false],
+  ['slug',    'Slug',       false],
+  ['count',   'Products',   true],
+  ['status',  'Status',     false],
+  ['cta',     'Visit link', true],
+  ['inquiry', 'Inquiry',    true],
+];
+function loadFields(){
+  const def = {}; FIELDS.forEach(([k,,d]) => def[k] = d);
+  try { return Object.assign(def, JSON.parse(localStorage.getItem('adwm_fields') || '{}')); }
+  catch(e){ return def; }
+}
+
 const state = {
   all: [], view: [], rendered: 0, BATCH: 40,
   q: '',
   filter: localStorage.getItem('adwm_filter') || 'feeds',
   sort: localStorage.getItem('adwm_sort') || 'products',
   cols: localStorage.getItem('adwm_cols') || '4',
+  viewMode: localStorage.getItem('adwm_view') || 'grid',
+  fields: loadFields(),
 };
 
 document.documentElement.style.setProperty('--cols', state.cols);
@@ -181,6 +244,27 @@ density.value = state.cols;
 densN.textContent = state.cols;
 sort.value = state.sort;
 setSeg();
+setView();
+buildFieldMenu();
+applyFields();
+
+function setView(){
+  grid.classList.toggle('list', state.viewMode === 'list');
+  vGrid.classList.toggle('on', state.viewMode === 'grid');
+  vList.classList.toggle('on', state.viewMode === 'list');
+}
+function applyFields(){
+  FIELDS.forEach(([k]) => document.body.classList.toggle('hf-' + k, !state.fields[k]));
+}
+function buildFieldMenu(){
+  fpop.innerHTML = FIELDS.map(([k,label]) =>
+    `<label><input type="checkbox" data-field="${k}"${state.fields[k] ? ' checked' : ''}> ${label}</label>`).join('');
+  fpop.querySelectorAll('input').forEach(cb => cb.addEventListener('change', () => {
+    state.fields[cb.dataset.field] = cb.checked;
+    localStorage.setItem('adwm_fields', JSON.stringify(state.fields));
+    applyFields();
+  }));
+}
 
 function setSeg(){
   fFeeds.classList.toggle('on', state.filter==='feeds');
@@ -258,24 +342,28 @@ function card(s){
   // Only when we have a real vendor name that maps to the product grid's vendor facet.
   const aggUrl = s.vendor ? '/?vendors=' + encodeURIComponent(s.vendor) : '';
   const countTxt = s.productCount.toLocaleString() + ' designs';
-  const badge = gated ? `<span class="badge gated">Trade Access</span>`
+  const badge = gated ? `<span class="badge gated" data-f="status">Trade Access</span>`
     : (s.productCount>0
-        ? (aggUrl ? `<span class="badge countdrill" data-agg="${aggUrl.replace(/"/g,'&quot;')}" title="See all ${countTxt} in the catalog aggregator">${countTxt}</span>`
-                  : `<span class="badge">${countTxt}</span>`)
+        ? (aggUrl ? `<span class="badge countdrill" data-f="count" data-agg="${aggUrl.replace(/"/g,'&quot;')}" title="See all ${countTxt} in the catalog aggregator">${countTxt}</span>`
+                  : `<span class="badge" data-f="count">${countTxt}</span>`)
         : '');
   const sub = gated ? 'By trade inquiry'
     : (s.productCount>0
         ? (aggUrl ? `<span class="countdrill" data-agg="${aggUrl.replace(/"/g,'&quot;')}" title="See all ${countTxt} in the catalog aggregator">${countTxt} →</span>`
                   : countTxt)
         : 'Editorial lookbook');
+  const clean = v => String(v == null ? '' : v).replace(/[<>]/g,'');
   el.innerHTML = `
     ${badge}
-    <div class="collage${pool.length<=1?' single':''}">${cells}</div>
+    <div class="collage${pool.length<=1?' single':''}" data-f="image">${cells}</div>
     <div class="meta">
-      <h3></h3>
-      <div class="sub">${sub}</div>
-      <div class="cta">Visit Microsite →</div>
-      <button type="button" class="mailv" title="Compose a stock &amp; price inquiry to this vendor — saves a Gmail DRAFT, never auto-sends">✉ Stock &amp; Price</button>
+      <h3 data-f="name"></h3>
+      <div class="sub" data-f="count">${sub}</div>
+      <div class="fline" data-f="type"><b>Type</b> ${clean(s.type) || '—'}</div>
+      <div class="fline" data-f="slug"><b>Slug</b> ${clean(s.slug) || '—'}</div>
+      <div class="fline" data-f="status"><b>Status</b> ${gated ? 'Trade-Gated' : 'Live'}</div>
+      <div class="cta" data-f="cta">Visit Microsite →</div>
+      <button type="button" class="mailv" data-f="inquiry" title="Compose a stock &amp; price inquiry to this vendor — saves a Gmail DRAFT, never auto-sends">✉ Stock &amp; Price</button>
     </div>`;
   el.querySelector('h3').textContent = name;
   // The card itself is an <a> — the inquiry button must not navigate.
@@ -352,6 +440,15 @@ density.addEventListener('input', e=>{ state.cols=e.target.value; localStorage.s
   densN.textContent=state.cols; document.documentElement.style.setProperty('--cols', state.cols); });
 fFeeds.onclick=()=>{ state.filter='feeds'; localStorage.setItem('adwm_filter','feeds'); setSeg(); apply(); };
 fAll.onclick=()=>{ state.filter='all'; localStorage.setItem('adwm_filter','all'); setSeg(); apply(); };
+
+vGrid.onclick=()=>{ state.viewMode='grid'; localStorage.setItem('adwm_view','grid'); setView(); };
+vList.onclick=()=>{ state.viewMode='list'; localStorage.setItem('adwm_view','list'); setView(); };
+
+// Fields menu open/close (click-away closes).
+fieldsBtn.addEventListener('click', (e)=>{ e.stopPropagation();
+  const open = fpop.hidden; fpop.hidden = !open; fieldsBtn.setAttribute('aria-expanded', String(open)); });
+document.addEventListener('click', (e)=>{ if (!fpop.hidden && !e.target.closest('.fieldmenu')) {
+  fpop.hidden = true; fieldsBtn.setAttribute('aria-expanded','false'); } });
 </script>
 </body>
 </html>

← 47d2616 all.: add greenland to BANNED (private-label leak) + drop ro  ·  back to All Designerwallcoverings  ·  viewer control standard: add Fields on/off menu to index.htm 7f2ae6c →