[object Object]

← back to Bestwallpaperplace

homepage: insert Browse-by-style rails section (textured/contemporary/traditional/beige/brown) above flat grid; add ?tag= filter to /api/products

1c009de4a6163f9834054fb799b24f7eba0205ca · 2026-05-26 08:11:30 -0700 · Steve

Files touched

Diff

commit 1c009de4a6163f9834054fb799b24f7eba0205ca
Author: Steve <steve@designerwallcoverings.com>
Date:   Tue May 26 08:11:30 2026 -0700

    homepage: insert Browse-by-style rails section (textured/contemporary/traditional/beige/brown) above flat grid; add ?tag= filter to /api/products
---
 public/index.html | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 server.js         |  5 +++++
 2 files changed, 64 insertions(+)

diff --git a/public/index.html b/public/index.html
index 73d5a6a..37f91c8 100644
--- a/public/index.html
+++ b/public/index.html
@@ -334,6 +334,65 @@ a:focus-visible, button:focus-visible, input:focus-visible, select:focus-visible
   </div>
 </section>
 
+<!-- SWEEP-A1: Browse-by-style rails — populated client-side from /api/products?tag=<name> -->
+<section class="section" id="browse-rails" style="padding-bottom:0">
+  <div class="section-header" style="margin-bottom:24px">
+    <div>
+      <div class="section-eyebrow" style="font-size:10px;letter-spacing:.4em;text-transform:uppercase;font-weight:700;color:var(--muted);margin-bottom:8px">Browse</div>
+      <h2 style="font-size:clamp(28px,4vw,42px);font-weight:300;letter-spacing:-0.01em;margin:0">By style.</h2>
+    </div>
+  </div>
+  <div id="brwRails"></div>
+</section>
+<style>
+#browse-rails .brw-block { margin-bottom:48px }
+#browse-rails .brw-head { display:flex;justify-content:space-between;align-items:baseline;margin-bottom:18px }
+#browse-rails .brw-title { font-size:clamp(20px,2.2vw,28px);font-weight:500;letter-spacing:-0.01em;margin:0;color:var(--ink,#111) }
+#browse-rails .brw-allink { font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted,#888);text-decoration:none;border-bottom:1px solid var(--line,rgba(0,0,0,.2));padding-bottom:2px }
+#browse-rails .brw-rail { display:flex;gap:14px;overflow-x:auto;scroll-snap-type:x mandatory;padding-bottom:8px;scrollbar-width:none }
+#browse-rails .brw-rail::-webkit-scrollbar { display:none }
+#browse-rails .brw-card { flex:0 0 240px;scroll-snap-align:start;cursor:pointer;transition:transform 0.2s;text-decoration:none;color:inherit }
+#browse-rails .brw-card:hover { transform:translateY(-3px) }
+#browse-rails .brw-card img { width:100%;aspect-ratio:1/1.15;object-fit:cover;display:block;background:#eee }
+#browse-rails .brw-card .brw-cap { font-size:13px;font-weight:500;padding:8px 0 2px;line-height:1.3 }
+@media (max-width:720px) { #browse-rails .brw-card { flex:0 0 160px } }
+</style>
+<script>
+// SWEEP-A1: tag-bucket rails — Browse by style
+(async function hydrateBrowseRails() {
+  try {
+    const tags = ["textured","contemporary","traditional","beige","brown"];
+    const host = document.getElementById('brwRails');
+    if (!host) return;
+    function cardOne(p) {
+      const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
+      const title = ((p.title || '').split('|')[0] || '').trim().replace(/"/g, '&quot;');
+      return '<a class="brw-card" href="/sample/' + encodeURIComponent(p.handle || p.sku) + '">' +
+        '<img src="' + url + '" alt="' + title + '" loading="lazy">' +
+        '<div class="brw-cap">' + title + '</div>' +
+      '</a>';
+    }
+    for (const tag of tags) {
+      const r = await fetch('/api/products?limit=12&tag=' + encodeURIComponent(tag));
+      if (!r.ok) continue;
+      const d = await r.json();
+      const items = (d.items || []).filter(p => p && p.image_url);
+      if (items.length < 4) continue;
+      const block = document.createElement('div');
+      block.className = 'brw-block';
+      const nice = tag.replace(/\b\w/g, c => c.toUpperCase());
+      block.innerHTML =
+        '<div class="brw-head">' +
+          '<h3 class="brw-title">' + nice + '</h3>' +
+          '<a class="brw-allink" href="/?tag=' + encodeURIComponent(tag) + '">View all ' + (d.total || items.length) + ' &rarr;</a>' +
+        '</div>' +
+        '<div class="brw-rail">' + items.map(cardOne).join('') + '</div>';
+      host.appendChild(block);
+    }
+  } catch (e) {}
+})();
+</script>
+
 <section class="section">
   <div class="toolbar">
     <div class="search">
diff --git a/server.js b/server.js
index fa8f0fd..732ecc4 100644
--- a/server.js
+++ b/server.js
@@ -149,6 +149,11 @@ app.get('/api/products', (req, res) => {
     );
   }
   if (vendor && vendor !== 'all') list = list.filter(p => p.vendor === vendor);
+  // SWEEP-A1: tag filter — sub-bucket rails on the homepage call /api/products?tag=<name>
+  if (req.query.tag) {
+    const wanted = String(req.query.tag).toLowerCase();
+    list = list.filter(p => (p.tags||[]).some(t => String(t).toLowerCase() === wanted));
+  }
   list = sortProducts(list, sort);
   const total = list.length;
   const pageNum = Math.max(1, parseInt(page) || 1);

← 4b9975c wire api-vendor-redact middleware (close vendors-facet leak)  ·  back to Bestwallpaperplace  ·  Add corner-nav.js (hard-rule: top-right 4-icon nav on produc 4298d7c →