[object Object]

← back to Dw Domain Fleet

Add sort <select> + density control bar to dwf homepage Featured grid (standing-rule compliance, client-side sort, shared localStorage with /catalog)

49997ea9dbd8089addcb9b4f9ccfb0143aa7cd16 · 2026-05-31 08:42:46 -0700 · steve

Files touched

Diff

commit 49997ea9dbd8089addcb9b4f9ccfb0143aa7cd16
Author: steve <steve@designerwallcoverings.com>
Date:   Sun May 31 08:42:46 2026 -0700

    Add sort <select> + density control bar to dwf homepage Featured grid (standing-rule compliance, client-side sort, shared localStorage with /catalog)
---
 shared/render.js | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++---
 1 file changed, 66 insertions(+), 3 deletions(-)

diff --git a/shared/render.js b/shared/render.js
index d8d02c1..58ea85e 100644
--- a/shared/render.js
+++ b/shared/render.js
@@ -352,7 +352,7 @@ function card(p) {
   return `<div class="card" data-handle="${esc(productSlug(p))}"
     data-title="${esc(t)}" data-sku="${esc(scrubVendor(p.sku||p.handle))}"
     data-img="${esc(p.image_url)}" data-tags="${esc(scrubTags(p.tags).slice(0,12).join(', '))}"
-    data-price="${esc(p.price)}">
+    data-price="${esc(p.price)}" data-hex="${esc(hx)}">
   <a class="card-link" href="${esc(href)}" aria-label="${esc(t)}">
     <div class="card-img" style="background-image:url('${esc(p.image_url)}');--card-bg:url('${esc(p.image_url)}')"></div>
     <div class="card-body">
@@ -385,8 +385,24 @@ function homePage(cfg, heroImgs, featured) {
   <p>${esc(clean(cfg.tagline))} · Curated by Designer Wallcoverings</p>
 </div>
 <div class="wrap">
-  <div class="controls"><label>Featured Selection</label><span class="count">${featured.length} patterns</span></div>
-  <div class="grid">${featured.map(card).join('')}</div>
+  <div class="controls">
+    <div class="ctl-sort">
+      <label for="sortSel">Featured Selection · Sort</label>
+      <select id="sortSel">
+        <option value="newest">Featured</option><option value="color">Color</option>
+        <option value="light-dark">Light → Dark</option><option value="dark-light">Dark → Light</option>
+        <option value="style">Style</option><option value="sku">SKU A→Z</option>
+        <option value="title">Title A→Z</option><option value="price-asc">Price ↑</option>
+        <option value="price-desc">Price ↓</option>
+      </select>
+    </div>
+    <div class="ctl-density">
+      <label for="densSel">Density</label>
+      <input type="range" id="densSel" min="3" max="8" value="5">
+      <span class="count">${featured.length} patterns</span>
+    </div>
+  </div>
+  <div class="grid" id="grid">${featured.map(card).join('')}</div>
   <div style="text-align:center;padding:10px 0 50px">
     <a class="hero-cta__btn" style="position:static;background:var(--accent);color:#fff" href="/catalog">View Full Catalog</a>
   </div>
@@ -394,10 +410,57 @@ function homePage(cfg, heroImgs, featured) {
 ${footer(cfg)}
 ${nsModal(cfg)}
 ${script(cfg)}
+${gridControls(cfg)}
 ${heroRotate(slides.length)}
 </body></html>`;
 }
 
+/* client-side sort + density for the homepage Featured grid (catalog does it
+   server-side; the featured set is small + server-rendered, so reorder in JS).
+   Shares localStorage keys with /catalog so the shopper's choice carries over. */
+function gridControls(cfg){
+  return `<script>
+(function(){
+  var dk='${cfg.slug}_density',sk='${cfg.slug}_sort';
+  var g=document.getElementById('grid'),d=document.getElementById('densSel'),s=document.getElementById('sortSel');
+  if(!g||!d||!s)return;
+  var cards=Array.prototype.slice.call(g.children);
+  cards.forEach(function(c,i){c.__i=i;}); // original "Featured" order
+  var sv=localStorage.getItem(dk);if(sv)d.value=sv;
+  function applyD(){g.style.setProperty('--cols',d.value);}
+  d.addEventListener('input',function(){applyD();localStorage.setItem(dk,d.value);});
+  applyD();
+  function hl(hex){var m=/([0-9a-f]{6})/i.exec(hex||'');if(!m)return[999,999];
+    var n=parseInt(m[1],16),r=(n>>16&255)/255,gg=(n>>8&255)/255,b=(n&255)/255;
+    var mx=Math.max(r,gg,b),mn=Math.min(r,gg,b),l=(mx+mn)/2,h=0,df=mx-mn;
+    if(df){if(mx==r)h=((gg-b)/df)%6;else if(mx==gg)h=(b-r)/df+2;else h=(r-gg)/df+4;h*=60;if(h<0)h+=360;}
+    return[h,l];}
+  function val(c,k){var x=c.dataset;
+    if(k==='title')return(x.title||'').toLowerCase();
+    if(k==='sku')return(x.sku||'').toLowerCase();
+    if(k==='style')return(x.tags||'').toLowerCase();
+    if(k==='price-asc'||k==='price-desc')return parseFloat(x.price)||0;
+    return hl(x.hex);}
+  function sortBy(k){
+    var arr=cards.slice();
+    arr.sort(function(a,b){
+      if(k==='newest')return a.__i-b.__i;
+      if(k==='price-asc')return val(a,k)-val(b,k);
+      if(k==='price-desc')return val(b,k)-val(a,k);
+      if(k==='color')return val(a,'color')[0]-val(b,'color')[0];
+      if(k==='light-dark')return val(b,'color')[1]-val(a,'color')[1];
+      if(k==='dark-light')return val(a,'color')[1]-val(b,'color')[1];
+      var av=val(a,k),bv=val(b,k);return av<bv?-1:av>bv?1:0;
+    });
+    arr.forEach(function(c){g.appendChild(c);});
+  }
+  var saved=localStorage.getItem(sk);
+  if(saved){var ok=Array.prototype.some.call(s.options,function(o){return o.value===saved;});if(ok){s.value=saved;sortBy(saved);}}
+  s.addEventListener('change',function(){localStorage.setItem(sk,s.value);sortBy(s.value);});
+})();
+</script>`;
+}
+
 /* ---------- catalog page ---------- */
 function catalogPage(cfg, products, q, sort, page, pages, total) {
   const title = `Catalog — ${cfg.siteName}`;

← 55a7f0b Add 2026-05-31 read-only vendor-leak re-verify: slug/chip sc  ·  back to Dw Domain Fleet  ·  render: route PDP buy CTA + main-store anchor + canonical th a85eb02 →