← back to Chinoiseriewallpaper
viewer standard: add Grid/List toggle + per-field on/off menu (Fields) to product grid
190ea5cc88d18197f059c18d9810500f331a6c34 · 2026-08-12 08:33:41 -0700 · Steve
Files touched
M public/css/site.cssM public/index.htmlM public/js/site.js
Diff
commit 190ea5cc88d18197f059c18d9810500f331a6c34
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 08:33:41 2026 -0700
viewer standard: add Grid/List toggle + per-field on/off menu (Fields) to product grid
---
public/css/site.css | 59 +++++++++++++++++++++++++++++++++++
public/index.html | 10 ++++++
public/js/site.js | 89 ++++++++++++++++++++++++++++++++++++++++++++++++-----
3 files changed, 151 insertions(+), 7 deletions(-)
diff --git a/public/css/site.css b/public/css/site.css
index 26aad93..c66723c 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -677,6 +677,65 @@ blockquote cite {
.empty-state p { font-family: var(--font-serif); font-size: 22px; margin-bottom: 8px; }
.empty-state small { font-size: 13px; }
+/* ── Grid / List view toggle ──────────────────────────────────────────────── */
+.view-seg { display:flex; border:1px solid var(--line); overflow:hidden; flex-shrink:0; }
+.view-seg button {
+ padding:8px 14px; background:var(--card-bg); border:none; border-right:1px solid var(--line);
+ font-family:var(--font-sans); font-size:10px; letter-spacing:0.14em; text-transform:uppercase;
+ font-weight:500; cursor:pointer; color:var(--ink-faint); transition:background var(--transition), color var(--transition);
+}
+.view-seg button:last-child { border-right:none; }
+.view-seg button.on { background:var(--gold); color:#1a1208; }
+.view-seg button:hover:not(.on) { background:rgba(201,162,39,0.08); color:var(--ink); }
+
+/* List mode — cards become horizontal rows (thumbnail left, meta right) */
+.product-grid.list-view { display:block; }
+.product-grid.list-view .product-card {
+ display:flex; flex-direction:row; align-items:stretch;
+ margin-bottom:2px; box-shadow:none;
+}
+.product-grid.list-view .product-card:hover { box-shadow:var(--shadow-card); }
+.product-grid.list-view .card-image-wrap {
+ flex:0 0 clamp(100px,18vw,180px); aspect-ratio:auto; min-height:100px;
+}
+.product-grid.list-view .card-image-wrap::after { display:none; }
+.product-grid.list-view .card-body {
+ flex:1; border-top:none; border-left:1px solid var(--line);
+ display:flex; align-items:center; flex-wrap:wrap; gap:0 24px;
+ padding:12px 18px;
+}
+.product-grid.list-view .card-footer { margin-top:6px; }
+
+/* ── Fields on/off menu ────────────────────────────────────────────────────── */
+.fieldmenu { position:relative; flex-shrink:0; }
+.fbtn {
+ padding:8px 12px; border:1px solid var(--line); background:var(--card-bg);
+ font-family:var(--font-sans); font-size:10px; letter-spacing:0.12em; text-transform:uppercase;
+ font-weight:500; color:var(--ink-faint); cursor:pointer; transition:border-color var(--transition), color var(--transition);
+}
+.fbtn:hover { border-color:var(--gold); color:var(--ink); }
+.fpop {
+ position:absolute; top:calc(100% + 8px); right:0; background:var(--card-bg);
+ border:1px solid var(--line); box-shadow:0 14px 34px rgba(26,18,8,0.18);
+ padding:6px 4px; z-index:30; min-width:180px;
+}
+.fpop[hidden] { display:none; }
+.fpop label {
+ display:flex; align-items:center; gap:10px; padding:7px 14px;
+ font-family:var(--font-sans); font-size:10px; letter-spacing:0.10em; text-transform:uppercase;
+ font-weight:500; color:var(--ink-soft); cursor:pointer; white-space:nowrap;
+}
+.fpop label:hover { background:var(--ivory); }
+.fpop input[type=checkbox] { accent-color:var(--gold); width:13px; height:13px; }
+
+/* Per-field hide rules — keyed on html.hf-<field> (survives pagination re-renders) */
+html.hf-image [data-f="image"] { display:none !important; }
+html.hf-aesthetic [data-f="aesthetic"] { display:none !important; }
+html.hf-title [data-f="title"] { display:none !important; }
+html.hf-dots [data-f="dots"] { display:none !important; }
+html.hf-price [data-f="price"] { display:none !important; }
+html.hf-sample [data-f="sample"] { display:none !important; }
+
/* ── Responsive ───────────────────────────────────────────────────────────── */
@media (max-width: 900px) {
.product-grid[data-cols="3"],
diff --git a/public/index.html b/public/index.html
index 7d6938a..10be25e 100644
--- a/public/index.html
+++ b/public/index.html
@@ -366,6 +366,16 @@
<label for="densitySlider">Density</label>
<input type="range" id="densitySlider" min="1" max="4" value="3" step="1">
</div>
+
+ <div class="view-seg" role="tablist" aria-label="View">
+ <button id="cwViewGrid" class="on" aria-label="Grid view">Grid</button>
+ <button id="cwViewList" aria-label="List view">List</button>
+ </div>
+
+ <div class="fieldmenu">
+ <button class="fbtn" id="cwFieldsBtn" aria-haspopup="true" aria-expanded="false">Fields ▾</button>
+ <div class="fpop" id="cwFpop" hidden role="menu"></div>
+ </div>
</div>
<!-- Results count -->
diff --git a/public/js/site.js b/public/js/site.js
index c34eb1d..6a26fad 100644
--- a/public/js/site.js
+++ b/public/js/site.js
@@ -15,6 +15,21 @@ if (themeToggle) {
});
}
+// ── Fields — every toggleable card field [key, label, default-visible] ────────
+const CW_FIELDS = [
+ ['image', 'Image', true],
+ ['aesthetic', 'Aesthetic', true],
+ ['title', 'Title', true],
+ ['dots', 'Color Dots', false],
+ ['price', 'Price', true],
+ ['sample', 'Sample CTA', true],
+];
+
+function cwLoadFields() {
+ const def = {}; CW_FIELDS.forEach(([k,,d]) => def[k] = d);
+ try { return Object.assign(def, JSON.parse(localStorage.getItem('cw-fields') || '{}')); } catch(e) { return def; }
+}
+
// ── State ─────────────────────────────────────────────────────────────────────
const state = {
q: '',
@@ -25,6 +40,8 @@ const state = {
cols: parseInt(localStorage.getItem('cw-cols'), 10) || 3,
total: 0,
pages: 0,
+ viewMode: localStorage.getItem('cw-view') || 'grid',
+ fields: cwLoadFields(),
};
// ── DOM refs ──────────────────────────────────────────────────────────────────
@@ -116,7 +133,7 @@ function buildCard(p) {
: '';
card.innerHTML = `
- <div class="card-image-wrap">
+ <div class="card-image-wrap" data-f="image">
<img
src="${escHtml(p.image_url)}"
alt="${escHtml(p.title)}"
@@ -125,7 +142,7 @@ function buildCard(p) {
>
<div class="card-overlay">
<div class="card-overlay-actions">
- <a href="${escHtml(sampleUrl)}" target="_blank" rel="noopener noreferrer" class="btn-sample">
+ <a href="${escHtml(sampleUrl)}" target="_blank" rel="noopener noreferrer" class="btn-sample" data-f="sample">
Order Memo Sample (free)
</a>
<a href="${escHtml(productUrl)}" target="_blank" rel="noopener noreferrer" class="btn-view">
@@ -135,12 +152,12 @@ function buildCard(p) {
</div>
</div>
<div class="card-body">
- <div class="card-aesthetic">${escHtml(aesthetic)}</div>
- <div class="card-title">${escHtml(p.title)}</div>
- ${dotsHtml}
+ <div class="card-aesthetic" data-f="aesthetic">${escHtml(aesthetic)}</div>
+ <div class="card-title" data-f="title">${escHtml(p.title)}</div>
+ <div data-f="dots">${dotsHtml}</div>
<div class="card-footer">
- ${priceDisplay}
- <a href="${escHtml(sampleUrl)}" target="_blank" rel="noopener noreferrer" class="card-sample-link">
+ <span data-f="price">${priceDisplay}</span>
+ <a href="${escHtml(sampleUrl)}" target="_blank" rel="noopener noreferrer" class="card-sample-link" data-f="sample">
Free Sample
</a>
</div>
@@ -256,5 +273,63 @@ if (densitySlider) {
});
}
+// ── Grid / List view toggle ───────────────────────────────────────────────────
+function cwSetView() {
+ const isList = state.viewMode === 'list';
+ grid.classList.toggle('list-view', isList);
+ const vg = document.getElementById('cwViewGrid');
+ const vl = document.getElementById('cwViewList');
+ if (vg) vg.classList.toggle('on', !isList);
+ if (vl) vl.classList.toggle('on', isList);
+}
+
+const cwViewGrid = document.getElementById('cwViewGrid');
+const cwViewList = document.getElementById('cwViewList');
+if (cwViewGrid) cwViewGrid.addEventListener('click', () => {
+ state.viewMode = 'grid'; localStorage.setItem('cw-view', 'grid'); cwSetView();
+});
+if (cwViewList) cwViewList.addEventListener('click', () => {
+ state.viewMode = 'list'; localStorage.setItem('cw-view', 'list'); cwSetView();
+});
+cwSetView();
+
+// ── Fields menu ───────────────────────────────────────────────────────────────
+function cwApplyFields() {
+ CW_FIELDS.forEach(([k]) => document.documentElement.classList.toggle('hf-' + k, !state.fields[k]));
+}
+
+function cwBuildFieldMenu() {
+ const fpop = document.getElementById('cwFpop');
+ if (!fpop) return;
+ fpop.innerHTML = CW_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('cw-fields', JSON.stringify(state.fields));
+ cwApplyFields();
+ }));
+}
+
+const cwFieldsBtn = document.getElementById('cwFieldsBtn');
+const cwFpop = document.getElementById('cwFpop');
+if (cwFieldsBtn && cwFpop) {
+ cwFieldsBtn.addEventListener('click', (e) => {
+ e.stopPropagation();
+ const open = cwFpop.hidden;
+ cwFpop.hidden = !open;
+ cwFieldsBtn.setAttribute('aria-expanded', String(open));
+ });
+ document.addEventListener('click', (e) => {
+ if (!cwFpop.hidden && !e.target.closest('.fieldmenu')) {
+ cwFpop.hidden = true;
+ cwFieldsBtn.setAttribute('aria-expanded', 'false');
+ }
+ });
+}
+
+cwBuildFieldMenu();
+cwApplyFields();
+
// ── Initial load ──────────────────────────────────────────────────────────────
fetchAndRender();
← 5eb4ab6 Wire DW Meta Pixel 1431180262113856 + DW-network ad slot [ad
·
back to Chinoiseriewallpaper
·
TK-11341: add AdSense Auto-Ads loader + ads.txt (revert to r 1b8c881 →