[object Object]

← back to Chinoiseriewallpaper

homepage rails: motif rail sections + pills strip wired to /api/sliders

3e1bbcd6776903ae65ca9306a43dcce83a52694e · 2026-05-26 08:11:05 -0700 · Steve

Adds 4 motif rail sections (green/commercial/architectural/white, 8 cards each
with View all →) above the collection grid + a horizontal pill strip wired to
the existing aestheticFilter <select>. Fetches /api/sliders + /api/facets at
load. Replaces stale Motif dropdown options (chinoiserie/imperial/scenic/...)
with the actual aesthetics present in data/products.json (green 93, commercial
100, white 7, architectural 25, botanical 2, floral, all). Same pattern as
hollywood-wallcoverings 3a7da85 + blockprintedwallpaper rails.

Files touched

Diff

commit 3e1bbcd6776903ae65ca9306a43dcce83a52694e
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 26 08:11:05 2026 -0700

    homepage rails: motif rail sections + pills strip wired to /api/sliders
    
    Adds 4 motif rail sections (green/commercial/architectural/white, 8 cards each
    with View all →) above the collection grid + a horizontal pill strip wired to
    the existing aestheticFilter <select>. Fetches /api/sliders + /api/facets at
    load. Replaces stale Motif dropdown options (chinoiserie/imperial/scenic/...)
    with the actual aesthetics present in data/products.json (green 93, commercial
    100, white 7, architectural 25, botanical 2, floral, all). Same pattern as
    hollywood-wallcoverings 3a7da85 + blockprintedwallpaper rails.
---
 public/index.html | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 file changed, 108 insertions(+), 8 deletions(-)

diff --git a/public/index.html b/public/index.html
index a537157..86ae791 100644
--- a/public/index.html
+++ b/public/index.html
@@ -121,6 +121,26 @@
 [data-theme="dark"] .ns-action .ico { background: rgba(245,241,232,0.08); }
 [data-theme="dark"] .ns-action .txt b { color: #f5f1e8; }
 [data-theme="dark"] .ns-action .txt span { color: #a39c8b; }
+
+/* ── Motif rails (added 2026-05-26 — fleet rails sweep) ─────────────── */
+.rail-section { margin-bottom: 56px; }
+.rail-head { display:flex; align-items:baseline; justify-content:space-between; margin-bottom:18px; padding-bottom:10px; border-bottom:1px solid var(--line, rgba(0,0,0,.10)); }
+.rail-title-h { font-size: 22px; letter-spacing: .18em; text-transform: uppercase; font-weight: 600; margin: 0; }
+.rail-all { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--ink-soft, #888); font-weight: 500; cursor: pointer; background: transparent; border: 0; padding: 0; font-family: inherit; }
+.rail-all:hover { color: var(--accent, #b89060); }
+.rail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 18px; }
+.rail-card { display: block; aspect-ratio: 1/1; overflow: hidden; position: relative; }
+.rail-card img { width: 100%; height: 100%; object-fit: cover; object-position: center 12%; transition: transform .4s ease; }
+.rail-card:hover img { transform: scale(1.04); }
+.rail-card-label { position: absolute; bottom: 0; left: 0; right: 0; padding: 12px 14px; background: linear-gradient(to top, rgba(0,0,0,0.7), transparent); color: #fff; font-size: 11px; letter-spacing: .12em; text-transform: uppercase; }
+.rail-pill { display: inline-flex; align-items: center; gap: 8px; padding: 7px 14px; border: 1px solid var(--line, rgba(0,0,0,.18)); background: transparent; color: var(--ink-soft, #555); font: inherit; font-size: 11px; letter-spacing: .14em; text-transform: uppercase; font-weight: 500; cursor: pointer; border-radius: 0; transition: border-color .15s, color .15s, background .15s; }
+.rail-pill:hover { border-color: var(--accent, #b89060); color: var(--ink, #111); }
+.rail-pill.active { border-color: var(--accent, #b89060); color: var(--accent, #b89060); background: rgba(184,144,96,0.06); }
+.rail-pill span { font-size: 10px; opacity: .7; font-weight: 600; }
+[data-theme="dark"] .rail-pill { border-color: rgba(245,241,232,0.18); color: #a39c8b; }
+[data-theme="dark"] .rail-pill:hover { color: #f5f1e8; }
+[data-theme="dark"] .rail-pill.active { background: rgba(184,144,96,0.10); color: var(--accent, #b89060); }
+[data-theme="dark"] .rail-head { border-bottom-color: rgba(245,241,232,0.10); }
 </style>
 <!-- Meta Pixel — INERT until set-fb-pixel.sh is run (does NOT load fbevents.js or hit Meta until flipped) -->
 <script>
@@ -208,6 +228,11 @@
   </div>
 </section>
 
+<!-- ── BROWSE BY MOTIF (rail sections) ────────────────────────────────── -->
+<section class="rails" id="motifRails" aria-label="Browse by motif" style="max-width:1400px;margin:0 auto;padding:48px 32px 0">
+  <div id="railSections"></div>
+</section>
+
 <!-- ── COLLECTION ─────────────────────────────────────────────────────── -->
 <section class="collection" id="collection">
   <div class="collection-header">
@@ -215,6 +240,9 @@
     <p class="collection-sub">Distilled from the world's finest wallcovering ateliers — every pattern curated for its fidelity to the chinoiserie tradition.</p>
   </div>
 
+  <!-- Pills strip — wired to the Motif filter -->
+  <nav class="rail-pills" id="railPills" aria-label="Filter by motif" style="display:flex;gap:10px;flex-wrap:wrap;padding:14px 0 22px;margin-bottom:6px;border-bottom:1px solid var(--line,rgba(0,0,0,.08))"></nav>
+
   <!-- Controls bar -->
   <div class="controls-bar">
     <div class="search-wrap">
@@ -226,14 +254,12 @@
       <label for="aestheticFilter">Motif</label>
       <select id="aestheticFilter">
         <option value="all">All Motifs</option>
-        <option value="chinoiserie">Chinoiserie</option>
-        <option value="imperial">Imperial</option>
-        <option value="scenic">Scenic</option>
-        <option value="botanica">Botanica</option>
-        <option value="avian">Avian</option>
-        <option value="garden">Garden</option>
-        <option value="handpainted">Hand-Painted</option>
-        <option value="japanese">Japanese</option>
+        <option value="green">Green</option>
+        <option value="commercial">Commercial</option>
+        <option value="white">White</option>
+        <option value="architectural">Architectural</option>
+        <option value="botanical">Botanical</option>
+        <option value="floral">Floral</option>
       </select>
     </div>
 
@@ -313,6 +339,80 @@
 </footer>
 
 <script src="/js/site.js"></script>
+<script>
+/* ── Motif rails + pills (fleet rails sweep 2026-05-26) ──────────────
+   Fetches /api/sliders (rail sections) + /api/facets (pill counts),
+   renders them, and wires the pills to the existing #aestheticFilter
+   select so clicks scroll-to the grid filtered by motif. */
+(function(){
+  function el(tag, props, html){ var n=document.createElement(tag); Object.assign(n, props||{}); if(html) n.innerHTML=html; return n; }
+  function esc(s){ return String(s).replace(/[&<>"]/g, function(c){ return ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]); }); }
+
+  function setMotif(m){
+    var sel = document.getElementById('aestheticFilter');
+    if (!sel) return;
+    sel.value = m || 'all';
+    // Trigger the existing change handler that re-renders the grid.
+    sel.dispatchEvent(new Event('change', { bubbles: true }));
+    var grid = document.getElementById('productGrid');
+    if (grid) grid.scrollIntoView({ behavior: 'smooth', block: 'start' });
+  }
+
+  function renderPills(facets){
+    var bar = document.getElementById('railPills');
+    if (!bar || !facets) return;
+    var aesth = facets.aesthetics || {};
+    var total = facets.total || 0;
+    bar.innerHTML = '';
+    var current = (localStorage.getItem('cw-aesthetic') || 'all').toLowerCase();
+    var allBtn = el('button', { type:'button', className: 'rail-pill' + (current==='all' ? ' active' : '') });
+    allBtn.innerHTML = 'All <span>' + total.toLocaleString() + '</span>';
+    allBtn.addEventListener('click', function(){ setMotif('all'); document.querySelectorAll('#railPills .rail-pill').forEach(function(p){ p.classList.remove('active'); }); allBtn.classList.add('active'); });
+    bar.appendChild(allBtn);
+    var entries = Object.entries(aesth).filter(function(e){ return e[0] && e[1]>=4; }).sort(function(a,b){ return b[1]-a[1]; }).slice(0,8);
+    entries.forEach(function(e){
+      var key = e[0], count = e[1];
+      var p = el('button', { type:'button', className: 'rail-pill' + (current===key ? ' active' : '') });
+      p.innerHTML = esc(key.charAt(0).toUpperCase()+key.slice(1)) + ' <span>' + count.toLocaleString() + '</span>';
+      p.addEventListener('click', function(){ setMotif(key); document.querySelectorAll('#railPills .rail-pill').forEach(function(x){ x.classList.remove('active'); }); p.classList.add('active'); });
+      bar.appendChild(p);
+    });
+  }
+
+  function railCard(p){
+    var img = p.image_url || (p.images && p.images[0]) || '';
+    var title = p.title || p.handle || '';
+    var href = p.product_url || ('/sample/' + encodeURIComponent(p.handle || p.sku || ''));
+    return '<a class="rail-card" href="' + esc(href) + '" target="_blank" rel="noopener noreferrer">' +
+           (img ? '<img src="' + esc(img) + '" alt="' + esc(title) + '" loading="lazy">' : '') +
+           '<span class="rail-card-label">' + esc(title) + '</span></a>';
+  }
+
+  function renderRails(rails){
+    var host = document.getElementById('railSections');
+    if (!host || !rails || !rails.length) return;
+    host.innerHTML = rails.slice(0, 4).map(function(r){
+      var cards = (r.items || []).slice(0, 8).map(railCard).join('');
+      var label = r.aesthetic.charAt(0).toUpperCase() + r.aesthetic.slice(1);
+      return '<section class="rail-section">' +
+             '<div class="rail-head"><h2 class="rail-title-h">' + esc(label) + '</h2>' +
+             '<button class="rail-all" type="button" data-motif="' + esc(r.aesthetic) + '">View all →</button></div>' +
+             '<div class="rail-grid">' + cards + '</div></section>';
+    }).join('');
+    host.querySelectorAll('.rail-all').forEach(function(b){
+      b.addEventListener('click', function(){ setMotif(b.dataset.motif); });
+    });
+  }
+
+  Promise.all([
+    fetch('/api/facets').then(function(r){ return r.json(); }).catch(function(){ return null; }),
+    fetch('/api/sliders').then(function(r){ return r.json(); }).catch(function(){ return null; })
+  ]).then(function(results){
+    if (results[0]) renderPills(results[0]);
+    if (results[1] && results[1].rails) renderRails(results[1].rails);
+  });
+})();
+</script>
 <!-- DW-HAMBURGER-NAV v1 (canonical 2026-05-10) -->
 <style id="dw-hamburger-styles">
   /* Canonical hamburger nav — always visible top-right, all viewports.

← b78efdd add 404-guard middleware  ·  back to Chinoiseriewallpaper  ·  redact vendor names from customer-facing /api (close fleet l bd45b5d →