← 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
M public/index.htmlM public/styles.cssM server.js
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 →