[object Object]

← back to Patterndesignlab

Credible-first default storefront: lead page 1 with design-forward content, add Collection segment (Design-forward/Playful/All) + class filter + facet counts

eeacf3d3f1f4ebfcbe8cdb63d2197e779f76e137 · 2026-07-04 13:06:18 -0700 · steve

Files touched

Diff

commit eeacf3d3f1f4ebfcbe8cdb63d2197e779f76e137
Author: steve <steve@designerwallcoverings.com>
Date:   Sat Jul 4 13:06:18 2026 -0700

    Credible-first default storefront: lead page 1 with design-forward content, add Collection segment (Design-forward/Playful/All) + class filter + facet counts
---
 public/index.html | 12 +++++++++---
 public/styles.css |  4 ++++
 server.js         | 25 +++++++++++++++++++++++--
 3 files changed, 36 insertions(+), 5 deletions(-)

diff --git a/public/index.html b/public/index.html
index d44e6e3..58e4633 100644
--- a/public/index.html
+++ b/public/index.html
@@ -55,7 +55,7 @@
 <footer><div class="wrap">Pattern Design Lab · owned, settlement-passed originals only · local preview build</div></footer>
 
 <script>
-const state = { q:'', sort:'newest', style:'', colorway:'', motif:'', technique:'', seamless:'', pmin:'', pmax:'' };
+const state = { q:'', sort:'newest', class:'', style:'', colorway:'', motif:'', technique:'', seamless:'', pmin:'', pmax:'' };
 const PAGE_LIMIT = 48;
 let page = 1, total = 0, loaded = 0, loading = false;
 
@@ -68,7 +68,7 @@ function loadControls(){
 function applyControls(){ state.sort=document.getElementById('sort').value; localStorage.setItem('pdl.sort', state.sort); load(); }
 
 function doSearch(e){ e.preventDefault(); state.q=document.getElementById('q').value.trim(); load(); return false; }
-function clearAll(){ Object.assign(state,{q:'',style:'',colorway:'',motif:'',technique:'',seamless:'',pmin:'',pmax:''}); document.getElementById('q').value=''; load(); }
+function clearAll(){ Object.assign(state,{q:'',class:'',style:'',colorway:'',motif:'',technique:'',seamless:'',pmin:'',pmax:''}); document.getElementById('q').value=''; load(); }
 
 function fmtPrice(lo,hi){ if(lo==null) return 'Inquire'; return hi&&hi!=lo ? '$'+lo+'–$'+hi : '$'+lo; }
 
@@ -90,7 +90,7 @@ function cardEl(d){
 // build the query string for the CURRENT filters + a given page
 function queryFor(pg){
   const p = new URLSearchParams();
-  for(const k of ['q','sort','style','colorway','motif','technique','seamless','pmin','pmax']) if(state[k]) p.set(k,state[k]);
+  for(const k of ['q','sort','class','style','colorway','motif','technique','seamless','pmin','pmax']) if(state[k]) p.set(k,state[k]);
   p.set('page', pg); p.set('limit', PAGE_LIMIT);
   return p.toString();
 }
@@ -138,7 +138,12 @@ function renderFacets(){
   if(!FACETS) return;
   const f = document.getElementById('filters');
   const p = FACETS.price||{};
+  const c = FACETS.collection||{};
   f.innerHTML =
+    `<div class="fgroup"><h4>Collection</h4><div class="seg seg-collection">
+      <button class="${state.class===''?'active':''}" onclick="setClass('')">All${c.all!=null?` <span class="n">${c.all}</span>`:''}</button>
+      <button class="${state.class==='credible'?'active':''}" onclick="setClass('credible')">Design-forward${c.credible!=null?` <span class="n">${c.credible}</span>`:''}</button>
+      <button class="${state.class==='novelty'?'active':''}" onclick="setClass('novelty')">Playful${c.novelty!=null?` <span class="n">${c.novelty}</span>`:''}</button></div></div>`+
     facetGroup('Style','style',FACETS.style)+
     facetGroup('Colorway','colorway',FACETS.colorway)+
     facetGroup('Motif','motif',FACETS.motif)+
@@ -156,6 +161,7 @@ function renderFacets(){
 }
 function toggleFacet(key,val){ state[key] = state[key]===val ? '' : val; load(); }
 function setSeamless(v){ state.seamless=v; load(); }
+function setClass(v){ state.class=v; load(); }
 
 // honor an inbound ?q= search (e.g. from the detail-page search bar)
 const urlq = new URLSearchParams(location.search).get('q');
diff --git a/public/styles.css b/public/styles.css
index 62582ff..423c779 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -46,6 +46,10 @@ header.top{position:sticky;top:0;z-index:20;background:rgba(250,249,247,.86);bac
 .seg{display:flex;gap:6px}
 .seg button{flex:1;height:32px;border:1px solid var(--line);background:var(--panel);border-radius:8px;cursor:pointer;font-size:13px}
 .seg button.active{background:var(--accent);color:#fff;border-color:var(--accent)}
+.seg-collection{flex-direction:column}
+.seg-collection button{height:34px;display:flex;align-items:center;justify-content:space-between;padding:0 12px;text-align:left}
+.seg-collection button .n{font-size:12px;color:var(--muted)}
+.seg-collection button.active .n{color:rgba(255,255,255,.85)}
 .clearall{margin-top:12px;width:100%;height:36px;border:1px solid var(--line);background:var(--panel);border-radius:8px;cursor:pointer;font-size:13px}
 
 /* controls bar (sort + density — DW spec) */
diff --git a/server.js b/server.js
index 7775506..e69760b 100644
--- a/server.js
+++ b/server.js
@@ -62,6 +62,9 @@ function buildWhere(q, params) {
   }
   if (q.seamless === 'yes') w.push('x.seamless = true');
   if (q.seamless === 'no')  w.push('x.seamless = false');
+  // Collection segment — buyer-facing "Design-forward" (credible) / "Playful" (novelty); internal class column.
+  if (q.class === 'credible') w.push(`x.content_class = 'credible'`);
+  if (q.class === 'novelty')  w.push(`x.content_class = 'novelty'`);
   if (q.pmin) { params.push(Number(q.pmin)); w.push(`x.price_min >= $${params.length}`); }
   if (q.pmax) { params.push(Number(q.pmax)); w.push(`x.price_min <= $${params.length}`); }
   if (q.designer) { params.push(q.designer); w.push(`d.slug = $${params.length}`); }
@@ -72,7 +75,17 @@ app.get('/api/designs', async (req, res) => {
   try {
     const params = [];
     const where = buildWhere(req.query, params);
-    const order = SORTS[req.query.sort] || SORTS.newest;
+    // Default landing = credible-first: on the pristine grid (default/newest sort, no search, no
+    // facet/class filter) lead with credible-class designs so page 1 shows the marketplace at its
+    // best. Any explicit sort, search, or filter reverts to the exact prior behavior.
+    const sortKey = req.query.sort && SORTS[req.query.sort] ? req.query.sort : 'newest';
+    const pristine = (!req.query.sort || req.query.sort === 'newest') &&
+      !req.query.q && !req.query.style && !req.query.colorway && !req.query.motif &&
+      !req.query.technique && !req.query.seamless && !req.query.pmin && !req.query.pmax &&
+      !req.query.designer && !req.query.class;
+    const order = pristine
+      ? `(x.content_class='credible') DESC, x.created_at DESC`
+      : SORTS[sortKey];
     // ---- pagination (?page=N&limit=48) ----
     const page  = Math.max(1, parseInt(req.query.page, 10) || 1);
     const limit = Math.min(200, Math.max(1, parseInt(req.query.limit, 10) || 48));
@@ -107,9 +120,17 @@ app.get('/api/facets', async (req, res) => {
     const facet = async (col) => (await pool.query(
       `SELECT ${col} AS value, count(*)::int AS n FROM designs WHERE ${col} IS NOT NULL GROUP BY ${col} ORDER BY n DESC, value ASC`)).rows;
     const price = (await pool.query('SELECT min(price_min)::float lo, max(price_min)::float hi FROM designs')).rows[0];
+    // Collection facet counts — buyer-friendly labels applied in the UI, internal class values here.
+    const cc = (await pool.query(
+      `SELECT content_class AS value, count(*)::int AS n FROM designs WHERE content_class IS NOT NULL GROUP BY content_class`)).rows;
+    const collection = {
+      credible: (cc.find(r => r.value === 'credible') || {}).n || 0,
+      novelty:  (cc.find(r => r.value === 'novelty')  || {}).n || 0,
+    };
+    collection.all = collection.credible + collection.novelty;
     res.json({
       style: await facet('style'), colorway: await facet('colorway'),
-      motif: await facet('motif'), technique: await facet('technique'), price,
+      motif: await facet('motif'), technique: await facet('technique'), price, collection,
     });
   } catch (e) { console.error(e); res.status(500).json({ error: 'facets failed' }); }
 });

← 76df8eb iter-3: deepen credible catalog to 243, harden settlement sc  ·  back to Patterndesignlab  ·  Pull 1451 owned credible masters from prod (chinoiserie/stri c2f385a →