← back to Restaurantwallpaper
homepage: add retrowalls-canonical rail-sections (id=rails + renderRailSections)
1ee316f67c8a8640476de8a17d630537dd6bf60b · 2026-05-26 08:17:46 -0700 · Steve Abrams
Fleet rails sweep A3 — adds the canonical DW rail-sections pattern from
retrowalls (per LEDGER 2026-05-26 correction note). Renders rails from
/api/sliders ABOVE the flat grid on the unfiltered '/' home view; hides
itself when state.q or state.facet !== 'all'. Clicking 'View all -> on a
rail title calls state.facet=aes + resetGrid + scrollIntoView('#shop')
which is the canonical jumpToFacet behavior. Patch is 3 pieces:
(1) <div id='rails' style='display:none'></div> before <div id='grid'>
(2) .rail-section/.rail-head/.rail-all/.grid.rail-grid CSS
(3) async renderRailSections() fn + startup call right after loadGridPage().
Coexists cleanly with the existing 2-rail Ideas block above (which renders
'New this week' + 'Curated picks' from /api/products and survives in any
filter state).
Files touched
Diff
commit 1ee316f67c8a8640476de8a17d630537dd6bf60b
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue May 26 08:17:46 2026 -0700
homepage: add retrowalls-canonical rail-sections (id=rails + renderRailSections)
Fleet rails sweep A3 — adds the canonical DW rail-sections pattern from
retrowalls (per LEDGER 2026-05-26 correction note). Renders rails from
/api/sliders ABOVE the flat grid on the unfiltered '/' home view; hides
itself when state.q or state.facet !== 'all'. Clicking 'View all -> on a
rail title calls state.facet=aes + resetGrid + scrollIntoView('#shop')
which is the canonical jumpToFacet behavior. Patch is 3 pieces:
(1) <div id='rails' style='display:none'></div> before <div id='grid'>
(2) .rail-section/.rail-head/.rail-all/.grid.rail-grid CSS
(3) async renderRailSections() fn + startup call right after loadGridPage().
Coexists cleanly with the existing 2-rail Ideas block above (which renders
'New this week' + 'Curated picks' from /api/products and survives in any
filter state).
---
public/index.html | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/public/index.html b/public/index.html
index 339b7d8..e4d1b1c 100644
--- a/public/index.html
+++ b/public/index.html
@@ -391,6 +391,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>
@@ -748,6 +749,7 @@ tb.addEventListener('click', () => setTheme(document.documentElement.dataset.the
loadFacets();
loadGridPage();
+renderRailSections();
</script>
<!-- ============================================================
DW UNIVERSAL CONTACT MODULE — fashion-house UX
@@ -905,6 +907,15 @@ loadGridPage();
#ideas .rail-card .rc-title { font-size:13px; font-weight:500; color:var(--paper,#fff); padding:8px 0 2px; line-height:1.3 }
#ideas .rail-card .rc-meta { font-size:11px; color:var(--muted,#888); letter-spacing:0.06em }
@media (max-width:720px) { #ideas .rail-card { flex:0 0 160px } }
+
+/* Rail sections (retrowalls-canonical pattern — fleet sweep 2026-05-26) */
+.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-head .rail-title{font-family:inherit;font-size:clamp(18px,2.2vw,26px);margin:0;font-weight:500;letter-spacing:-0.01em;color:var(--ink,#1a1714)}
+.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;text-decoration:none}
+.rail-all:hover{color:var(--accent,#9A6E3A);text-decoration:none}
+.grid.rail-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:14px}
+
</style>
<!-- Product details modal — opens on any chip click. Vendor-redacted. -->
@@ -1162,6 +1173,85 @@ loadGridPage();
});
})();
+
+// === RAILS PATCH (DW fleet sweep A3 2026-05-26 — retrowalls canonical) ===
+// Renders rail sections on unfiltered homepage above flat grid, fed by /api/sliders.
+// Hides flat grid + sentinel while rails are visible. Click rail title -> jumpToFacet.
+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;
+ if (typeof state !== 'undefined' && (state.q || (state.facet && 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();
+ if (typeof state !== 'undefined') state.facet = aes;
+ document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === aes));
+ if (typeof resetGrid === 'function') 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(); if (typeof openDetails === 'function') openDetails(p); };
+ card.innerHTML = (typeof cardHTML === 'function') ? cardHTML(p) : ('<img src="'+(p.image_url||'')+'" loading="lazy" alt=""><div class="ti">'+(p.title||'')+'</div>');
+ 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 = '';
+ if (grid) grid.style.display = 'none';
+ if (sentinel) sentinel.style.display = 'none';
+ if (loading) loading.style.display = 'none';
+ if (listHead) listHead.style.display = 'none';
+}
+// === END RAILS PATCH ===
+
+
// graphics-loop pass 11: hydrate Ideas rails from /api/products
(async function hydrateIdeas() {
try {
← df39dce remove p.vendor render on Ideas rail (replace with 'Designer
·
back to Restaurantwallpaper
·
rails: editorial labels for firing aesthetic buckets 9690b49 →