[object Object]

← back to Silkwallcoverings

rails: wire /api/sliders into horizontal scroll-snap rail sections [fixed]

30c767ea5cedb44845483a059b9f4678557f559c · 2026-05-26 08:12:07 -0700 · Steve Abrams

Files touched

Diff

commit 30c767ea5cedb44845483a059b9f4678557f559c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 26 08:12:07 2026 -0700

    rails: wire /api/sliders into horizontal scroll-snap rail sections [fixed]
---
 public/index.html | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)

diff --git a/public/index.html b/public/index.html
index c074209..3ac4eb8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -189,6 +189,20 @@ textarea:focus-visible,
   outline: 2px solid var(--accent);
   outline-offset: 2px;
 }
+
+/* === RAILS PATCH CSS (DW fleet sweep A4 2026-05-26) === */
+#rails{display:block;margin:0 0 40px}
+.rail-section{margin-bottom:48px}
+.rail-head{display:flex;align-items:baseline;justify-content:space-between;margin-bottom:14px;padding-bottom:10px;border-bottom:1px solid var(--line,rgba(0,0,0,.12))}
+.rail-title{font-family:inherit;font-size:18px;letter-spacing:.16em;text-transform:uppercase;color:var(--accent,#9A6E3A);font-weight:600;margin:0}
+.rail-all{font-family:inherit;font-size:11px;letter-spacing:.18em;text-transform:uppercase;color:var(--muted,#5b5042);font-weight:500;transition:color .15s;cursor:pointer}
+.rail-all:hover{color:var(--accent,#9A6E3A);text-decoration:none}
+.grid.rail-grid{display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:8px;scrollbar-width:none}
+.grid.rail-grid::-webkit-scrollbar{display:none}
+.grid.rail-grid > .card{flex:0 0 240px;scroll-snap-align:start}
+@media (max-width:720px){.grid.rail-grid > .card{flex:0 0 160px}}
+/* === END RAILS PATCH CSS === */
+
 </style>
 <script>
 (function(){ try { var t = localStorage.getItem('sc_theme') || 'dark'; document.documentElement.dataset.theme = t; } catch(e){} })();
@@ -364,6 +378,7 @@ textarea:focus-visible,
     <button data-sort-col="aesthetic" class="col-aes">Aesthetic <span class="arr"></span></button>
     <button data-sort-col="price">Price <span class="arr"></span></button>
   </div>
+  <div id="rails" style="display:none"></div>
   <div class="grid" id="grid"></div>
   <div class="loading" id="loading">Loading more…</div>
   <div class="sentinel" id="sentinel"></div>
@@ -587,6 +602,7 @@ function resetGrid() {
   document.getElementById('grid').innerHTML = '';
   document.getElementById('loading').textContent = 'Loading…';
   loadGridPage();
+  if (typeof renderRailSections === 'function') renderRailSections();
 }
 
 async function loadGridPage() {
@@ -725,8 +741,94 @@ function setTheme(t){ document.documentElement.dataset.theme = t; try { localSto
 setTheme(document.documentElement.dataset.theme || 'dark');
 tb.addEventListener('click', () => setTheme(document.documentElement.dataset.theme === 'dark' ? 'light' : 'dark'));
 
+// === RAILS PATCH (DW fleet sweep A4 2026-05-26) ===
+// Renders rail sections on the unfiltered homepage above the flat grid.
+// Fed by /api/sliders. Hides flat grid + sentinel while rails are visible.
+async function renderRailSections(){
+  const railsHost = document.getElementById('rails');
+  const grid = document.getElementById('grid');
+  const sentinel = document.getElementById('sentinel');
+  const loading = document.getElementById('loading');
+  const listHead = document.getElementById('listHead');
+  if (!railsHost) return;
+  // Only render rails on the unfiltered "all" home view.
+  if (state.q || state.facet !== 'all') {
+    railsHost.innerHTML = '';
+    railsHost.style.display = 'none';
+    if (grid) grid.style.display = '';
+    if (sentinel) sentinel.style.display = '';
+    if (loading) loading.style.display = '';
+    return;
+  }
+  let payload;
+  try {
+    const r = await fetch('/api/sliders');
+    if (!r.ok) throw new Error('HTTP ' + r.status);
+    payload = await r.json();
+  } catch (e) {
+    console.warn('renderRailSections failed:', e);
+    return;
+  }
+  const rails = Array.isArray(payload && payload.rails) ? payload.rails : [];
+  if (rails.length < 2) return; // no meaningful segmentation
+  const frag = document.createElement('div');
+  for (const rail of rails) {
+    const aes = rail.aesthetic;
+    const items = (rail.items || []).slice(0, 8);
+    if (items.length < 4) continue;
+    const sec = document.createElement('section');
+    sec.className = 'rail-section';
+    const head = document.createElement('div');
+    head.className = 'rail-head';
+    const title = document.createElement('h2');
+    title.className = 'rail-title';
+    title.textContent = ((typeof LABELS !== 'undefined' && LABELS[aes]) || aes).replace(/-/g,' ').replace(/\b\w/g, c => c.toUpperCase());
+    const all = document.createElement('a');
+    all.className = 'rail-all';
+    all.href = '#shop';
+    all.textContent = 'View all →';
+    all.dataset.facet = aes;
+    all.onclick = (e) => {
+      e.preventDefault();
+      state.facet = aes;
+      document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === aes));
+      resetGrid();
+      const shop = document.getElementById('shop');
+      if (shop) shop.scrollIntoView({behavior:'smooth'});
+    };
+    head.appendChild(title); head.appendChild(all);
+    const grid8 = document.createElement('div');
+    grid8.className = 'grid rail-grid';
+    for (const p of items) {
+      const card = document.createElement('a');
+      card.className = 'card';
+      card.href = '#';
+      card.onclick = (e) => { e.preventDefault(); openDetails(p); };
+      card.innerHTML = cardHTML(p);
+      grid8.appendChild(card);
+    }
+    sec.appendChild(head);
+    sec.appendChild(grid8);
+    frag.appendChild(sec);
+  }
+  if (!frag.childNodes.length) return;
+  railsHost.innerHTML = '';
+  railsHost.appendChild(frag);
+  railsHost.style.display = '';
+  // Hide flat grid + scroll bait while rails are showing — clicking a rail
+  // "View all →" or a chip will un-hide them via resetGrid path.
+  if (grid) grid.style.display = 'none';
+  if (sentinel) sentinel.style.display = 'none';
+  if (loading) loading.style.display = 'none';
+  if (listHead) listHead.style.display = 'none';
+  const stat = document.getElementById('statLine');
+  if (stat) stat.textContent = (state.total || '') + ' patterns · browse by aesthetic';
+}
+// === END RAILS PATCH ===
+
 loadFacets();
 loadGridPage();
+renderRailSections();
 </script>
 <!-- ============================================================
      DW UNIVERSAL CONTACT MODULE — fashion-house UX

← ae0ab5a wire api-vendor-redact middleware as first app.use so /api/*  ·  back to Silkwallcoverings  ·  rails: editorial labels for firing aesthetic buckets d886855 →