[object Object]

← back to Patterndesignlab

Fix contrarian findings: working detail-page search, sort+density on designer grid, density on admin grid

dbfae5145c3845958fba586f1f9e40afbe172bb3 · 2026-07-04 11:38:01 -0700 · Steve

Files touched

Diff

commit dbfae5145c3845958fba586f1f9e40afbe172bb3
Author: Steve <steve@designerwallcoverings.com>
Date:   Sat Jul 4 11:38:01 2026 -0700

    Fix contrarian findings: working detail-page search, sort+density on designer grid, density on admin grid
---
 public/admin.html    |  6 +++++-
 public/design.html   |  5 ++++-
 public/designer.html | 54 ++++++++++++++++++++++++++++++++++++++++++----------
 public/index.html    |  3 +++
 public/styles.css    |  2 +-
 5 files changed, 57 insertions(+), 13 deletions(-)

diff --git a/public/admin.html b/public/admin.html
index 1cc66e3..2a98506 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -27,6 +27,9 @@
           <option value="newest">Newest</option><option value="oldest">Oldest</option>
           <option value="title">Title A→Z</option><option value="designer">Designer</option>
         </select></div>
+      <div style="margin-left:14px"><label for="density" style="font-size:12px;color:#6b6b6b">Density</label>
+        <input id="density" type="range" min="160" max="360" step="20" value="240"
+          oninput="document.documentElement.style.setProperty('--admin-min',this.value+'px');localStorage.setItem('pdl.admdensity',this.value)"></div>
     </div>
     <div class="admgrid" id="grid"></div>
   </div>
@@ -74,7 +77,8 @@ async function loadApps(){
   }).join('');
 }
 
-async function load(){ const j=await (await fetch('/api/admin/designs')).json(); rows=j.designs; document.getElementById('count').textContent=j.count+' designs'; render(); }
+async function load(){ const j=await (await fetch('/api/admin/designs')).json(); rows=j.designs; document.getElementById('count').textContent=j.count+' designs'; restoreDensity(); render(); }
+function restoreDensity(){ const v=localStorage.getItem('pdl.admdensity'); if(v){ document.documentElement.style.setProperty('--admin-min',v+'px'); const el=document.getElementById('density'); if(el) el.value=v; } }
 function render(){
   const s=document.getElementById('sort').value;
   const r=[...rows];
diff --git a/public/design.html b/public/design.html
index bd9eef5..103cd07 100644
--- a/public/design.html
+++ b/public/design.html
@@ -9,7 +9,10 @@
 <body>
 <header class="top"><div class="wrap">
   <a class="brand" href="/">Pattern Design<span class="dot">.</span>Lab</a>
-  <form class="searchbox" onsubmit="location.href='/?';return false"><input type="search" placeholder="Search…" onkeydown="if(event.key==='Enter'){location.href='/'}"></form>
+  <form class="searchbox" onsubmit="event.preventDefault();var v=this.querySelector('input').value.trim();location.href='/'+(v?('?q='+encodeURIComponent(v)):'');return false;">
+    <input type="search" placeholder="Search patterns, styles, colorways…" autocomplete="off">
+    <button class="go" type="submit" aria-label="search">→</button>
+  </form>
   <nav class="navlinks"><a href="/">Explore</a><a href="/trends">Trends</a><a href="/designer-signup">Sell your designs</a></nav>
 </div></header>
 
diff --git a/public/designer.html b/public/designer.html
index f36e849..b2d99c4 100644
--- a/public/designer.html
+++ b/public/designer.html
@@ -17,10 +17,39 @@
 const slug = decodeURIComponent(location.pathname.split('/').pop());
 function esc(s){ return (s==null?'':String(s)).replace(/[&<>"]/g,c=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c])); }
 function fmtPrice(lo){ return lo==null?'Inquire':'$'+lo; }
+let DESIGNS = [];
+
+// same sort vocabulary as the main grid, so every product grid behaves identically (DW standing rule)
+function sortDesigns(list, mode){
+  const a = list.slice();
+  const cmp = {
+    newest:  (x,y)=> new Date(y.created_at||0) - new Date(x.created_at||0),
+    color:   (x,y)=> String(x.dominant_hex||'').localeCompare(String(y.dominant_hex||'')),
+    style:   (x,y)=> String(x.style||'').localeCompare(String(y.style||'')),
+    title_az:(x,y)=> String(x.title||'').localeCompare(String(y.title||'')),
+    price_up:(x,y)=> (x.price_min??1e9) - (y.price_min??1e9),
+    price_dn:(x,y)=> (y.price_min??-1) - (x.price_min??-1),
+  }[mode] || (()=>0);
+  return a.sort(cmp);
+}
+function renderGrid(){
+  const mode = document.getElementById('sort').value;
+  localStorage.setItem('pdl.sort', mode);
+  const rows = sortDesigns(DESIGNS, mode);
+  document.getElementById('grid').innerHTML = rows.map(x=>`
+    <a class="card" href="/design/${x.slug}">
+      <div class="thumb"><div class="tile" style="background-image:url('${x.img}')"></div>
+        ${x.seamless?'<span class="badge">Seamless</span>':'<span class="badge">Mural</span>'}</div>
+      <div class="cardbody"><h3>${esc(x.title)}</h3>
+        <div class="meta"><span class="swatch" style="background:${x.dominant_hex||'#ccc'}"></span><span>${esc(x.style||'')}</span><span>·</span><span>${esc(x.colorway||'')}</span></div>
+        <div class="price">${fmtPrice(x.price_min)}</div></div>
+    </a>`).join('');
+}
 (async()=>{
   const r = await fetch('/api/designers/'+encodeURIComponent(slug));
   if(!r.ok){ document.getElementById('root').innerHTML='<p style="padding:40px 0">Designer not found. <a href="/">Explore</a></p>'; return; }
   const {designer:d, designs, count} = await r.json();
+  DESIGNS = designs || [];
   document.title = d.name + ' · Pattern Design Lab';
   document.getElementById('root').innerHTML = `
     <div class="dhero">
@@ -30,18 +59,23 @@ function fmtPrice(lo){ return lo==null?'Inquire':'$'+lo; }
         <p style="font-size:13px;color:#6b6b6b;margin-top:6px">${esc(d.country||'')} · ${count} design${count===1?'':'s'}</p></div>
     </div>
     <div class="bar"><span class="count">${count} designs</span>
-      <label for="density">Density</label>
-      <input id="density" type="range" min="2" max="6" step="1" value="4" oninput="document.documentElement.style.setProperty('--cols',this.value);localStorage.setItem('pdl.density',this.value)"></div>
-    <div class="grid">${designs.map(x=>`
-      <a class="card" href="/design/${x.slug}">
-        <div class="thumb"><div class="tile" style="background-image:url('${x.img}')"></div>
-          ${x.seamless?'<span class="badge">Seamless</span>':'<span class="badge">Mural</span>'}</div>
-        <div class="cardbody"><h3>${esc(x.title)}</h3>
-          <div class="meta"><span class="swatch" style="background:${x.dominant_hex||'#ccc'}"></span><span>${esc(x.style||'')}</span><span>·</span><span>${esc(x.colorway||'')}</span></div>
-          <div class="price">${fmtPrice(x.price_min)}</div></div>
-      </a>`).join('')}</div>
+      <div><label for="sort">Sort</label>
+        <select id="sort" onchange="renderGrid()">
+          <option value="newest">Newest</option>
+          <option value="color">Color</option>
+          <option value="style">Style</option>
+          <option value="title_az">Title A→Z</option>
+          <option value="price_up">Price ↑</option>
+          <option value="price_dn">Price ↓</option>
+        </select></div>
+      <div><label for="density">Density</label>
+        <input id="density" type="range" min="2" max="6" step="1" value="4" oninput="document.documentElement.style.setProperty('--cols',this.value);localStorage.setItem('pdl.density',this.value)"></div></div>
+    <div class="grid" id="grid"></div>
     <div style="padding:24px 0"><a href="/" style="color:var(--accent-ink)">← Back to all patterns</a></div>`;
+  // restore persisted controls (DW spec)
+  const ss=localStorage.getItem('pdl.sort'); if(ss){const el=document.getElementById('sort'); if(el&&[...el.options].some(o=>o.value===ss)) el.value=ss;}
   const dd=localStorage.getItem('pdl.density'); if(dd){document.documentElement.style.setProperty('--cols',dd);const el=document.getElementById('density');if(el)el.value=dd;}
+  renderGrid();
 })();
 </script>
 </body>
diff --git a/public/index.html b/public/index.html
index 5fbd7cd..37d6418 100644
--- a/public/index.html
+++ b/public/index.html
@@ -122,6 +122,9 @@ function renderFacets(){
 function toggleFacet(key,val){ state[key] = state[key]===val ? '' : val; load(); }
 function setSeamless(v){ state.seamless=v; load(); }
 
+// honor an inbound ?q= search (e.g. from the detail-page search bar)
+const urlq = new URLSearchParams(location.search).get('q');
+if(urlq){ state.q = urlq; const qi = document.getElementById('q'); if(qi) qi.value = urlq; }
 loadControls(); loadFacets(); load();
 </script>
 </body>
diff --git a/public/styles.css b/public/styles.css
index 73ba750..a012ada 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -108,7 +108,7 @@ header.top{position:sticky;top:0;z-index:20;background:rgba(250,249,247,.86);bac
 /* admin */
 .adminbar{display:flex;align-items:center;gap:12px;padding:20px 0}
 .when{display:inline-flex;align-items:center;gap:5px;font-size:11.5px;color:var(--muted);background:var(--chip);border-radius:999px;padding:3px 9px}
-.admgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;padding-bottom:60px}
+.admgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(var(--admin-min,240px),1fr));gap:14px;padding-bottom:60px}
 .acard{background:var(--panel);border:1px solid var(--line);border-radius:12px;overflow:hidden;box-shadow:var(--shadow)}
 .acard .t{height:150px;background-size:cover;background-position:center}
 .acard .b{padding:10px 12px 12px}

← 35e033e iter-2: trend boards, designer onboarding funnel, correct 1:  ·  back to Patterndesignlab  ·  iter-3: deepen credible catalog to 243, harden settlement sc 76df8eb →