[object Object]

← back to Designer Wallcoverings

auto-save: 2026-07-29T10:07:58 (2 files) — DW-Websites/GrassclothWallpaper.com/corner-nav.js DW-Websites/LosAngelesFabrics

6213362a985068bf91f1f2356f3bc99309d3d061 · 2026-07-29 10:08:05 -0700 · Steve Abrams

Files touched

Diff

commit 6213362a985068bf91f1f2356f3bc99309d3d061
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed Jul 29 10:08:05 2026 -0700

    auto-save: 2026-07-29T10:07:58 (2 files) — DW-Websites/GrassclothWallpaper.com/corner-nav.js DW-Websites/LosAngelesFabrics
---
 DW-Websites/GrassclothWallpaper.com/corner-nav.js | 91 +++++++++++++++++++----
 DW-Websites/LosAngelesFabrics                     |  2 +-
 2 files changed, 79 insertions(+), 14 deletions(-)

diff --git a/DW-Websites/GrassclothWallpaper.com/corner-nav.js b/DW-Websites/GrassclothWallpaper.com/corner-nav.js
index d390a171..ca74b20e 100644
--- a/DW-Websites/GrassclothWallpaper.com/corner-nav.js
+++ b/DW-Websites/GrassclothWallpaper.com/corner-nav.js
@@ -66,6 +66,11 @@
 .cnav-search{padding:14px 16px;width:100%;font-size:18px;background:transparent;border:0;border-bottom:1px solid rgba(255,255,255,0.18);color:#f5f1e8;outline:none;box-sizing:border-box;margin-bottom:18px;font-family:inherit}\
 .cnav-search::placeholder{color:rgba(245,241,232,0.4)}\
 .cnav-sub{font-size:12px;color:rgba(245,241,232,0.55);margin:18px 0 8px;letter-spacing:0.08em;text-transform:uppercase}\
+.cnav-panel a.cnav-res{display:flex;align-items:center;gap:12px;padding:10px 4px;border-bottom:1px solid rgba(255,255,255,0.08);text-decoration:none;color:#f5f1e8}\
+.cnav-panel a.cnav-res:hover{opacity:1;background:rgba(255,255,255,0.04)}\
+.cnav-res img,.cnav-res .cnav-res-ph{width:48px;height:48px;object-fit:cover;border-radius:4px;background:rgba(255,255,255,0.06);flex:0 0 auto}\
+.cnav-res-t{font-size:14px;letter-spacing:0.02em;line-height:1.3;text-transform:none}\
+.cnav-searching,.cnav-nores{font-size:13px;color:rgba(245,241,232,0.55);padding:12px 4px;letter-spacing:0.02em}\
 @media (max-width:520px){.cnav-wrap{top:10px;right:10px;padding:8px 12px;gap:12px}.cnav-loginwrap{top:10px;left:10px;padding:8px 12px;gap:8px}.cnav-btn .cnav-lbl{display:none}.cnav-btn svg{width:20px;height:20px}}\
 ';
   var styleEl = document.createElement('style');
@@ -133,7 +138,7 @@
   var srch = panel('\
     <h3>Search</h3>\
     <input type="search" class="cnav-search" id="cnav-search-input" placeholder="Search ' + BRAND + '..." autocomplete="off">\
-    <div class="cnav-sub">Press enter to search · ESC to close</div>\
+    <div class="cnav-sub">Type to search · enter opens the first result · ESC to close</div>\
     <div id="cnav-search-results" style="margin-top:18px"></div>\
   ');
 
@@ -183,27 +188,87 @@
   document.querySelectorAll('.cnav-close').forEach(function (b) { b.addEventListener('click', closeAll); });
   document.addEventListener('keydown', function (e) { if (e.key === 'Escape') closeAll(); });
 
-  // Search — live-filter on-page grid if one exists; else submit to /search
+  // Search — LIVE preview as you type. Queries the site product API (which
+  // searches title + tags + description) on a short debounce and renders
+  // clickable thumbnail results. Falls back to filtering the on-page grid
+  // only when no API is reachable, so pages without /api/products still work.
   var input = document.getElementById('cnav-search-input');
   var results = document.getElementById('cnav-search-results');
+  var SEARCH_API = CFG.searchApi || '/api/products';
+  var _st, _seq = 0;
+
+  function cnEsc(s) {
+    return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
+      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c];
+    });
+  }
+  function cnSafeUrl(u) {
+    u = String(u == null ? '' : u).trim();
+    if (!u) return '';
+    var m = u.match(/^([a-z][a-z0-9+.\-]*):/i);
+    if (m && !/^https?$/i.test(m[1])) return '#'; // block javascript:, data:, vbscript:, ...
+    return u;
+  }
+  function resHref(p) {
+    return cnSafeUrl(p.product_url || p.url || (p.handle ? '/products/' + p.handle : '#'));
+  }
+  function resImg(p)   { return cnSafeUrl(p.image_url || p.image || p.featured_image || p.thumbnail || ''); }
+  function resTitle(p) { return p.title || p.name || p.sku || 'Untitled'; }
+
+  function renderResults(items, q) {
+    if (!items || !items.length) {
+      results.innerHTML = '<div class="cnav-nores">No matches for &ldquo;' + cnEsc(q) + '&rdquo;</div>';
+      return;
+    }
+    results.innerHTML = items.slice(0, 8).map(function (p) {
+      var img = resImg(p);
+      return '<a class="cnav-res" href="' + cnEsc(resHref(p)) + '">' +
+        (img ? '<img loading="lazy" src="' + cnEsc(img) + '" alt="">' : '<span class="cnav-res-ph"></span>') +
+        '<span class="cnav-res-t">' + cnEsc(resTitle(p)) + '</span></a>';
+    }).join('');
+  }
+
+  // Fallback: filter whatever cards are already rendered on the page.
+  function gridFilter(q) {
+    if (!hasGrid) { results.innerHTML = ''; return; }
+    var cards = hasGrid.querySelectorAll('[data-card], .product-card, .pcard, .grid-item, .card');
+    var shown = 0;
+    cards.forEach(function (c) {
+      var t = c.textContent.toLowerCase();
+      var hit = !q || t.indexOf(q) !== -1;
+      c.style.display = hit ? '' : 'none';
+      if (hit) shown++;
+    });
+    results.textContent = q ? (shown + ' result' + (shown === 1 ? '' : 's') + ' on this page') : '';
+  }
+
   if (input) {
     input.addEventListener('input', function () {
-      var q = input.value.trim().toLowerCase();
-      if (!hasGrid) return;
-      var cards = hasGrid.querySelectorAll('[data-card], .product-card, .pcard, .grid-item');
-      var shown = 0;
-      cards.forEach(function (c) {
-        var t = c.textContent.toLowerCase();
-        var hit = !q || t.indexOf(q) !== -1;
-        c.style.display = hit ? '' : 'none';
-        if (hit) shown++;
-      });
-      results.textContent = q ? (shown + ' result' + (shown === 1 ? '' : 's') + ' on this page') : '';
+      var q = input.value.trim();
+      clearTimeout(_st);
+      if (!q) { results.innerHTML = ''; gridFilter(''); return; }
+      results.innerHTML = '<div class="cnav-searching">Searching…</div>';
+      var myseq = ++_seq;
+      _st = setTimeout(function () {
+        fetch(SEARCH_API + '?q=' + encodeURIComponent(q) + '&limit=8')
+          .then(function (r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
+          .then(function (d) {
+            if (myseq !== _seq) return; // a newer keystroke already fired
+            var items = d.items || d.products || (Array.isArray(d) ? d : []);
+            renderResults(items, q);
+          })
+          .catch(function () {
+            if (myseq !== _seq) return;
+            gridFilter(q.toLowerCase()); // no API → live-filter the page instead
+          });
+      }, 180);
     });
     input.addEventListener('keydown', function (e) {
       if (e.key === 'Enter') {
         var q = input.value.trim();
         if (!q) return;
+        var first = results.querySelector('a.cnav-res');
+        if (first) { location.href = first.getAttribute('href'); return; }
         if (hasGrid) { closeAll(); hasGrid.scrollIntoView({ behavior: 'smooth' }); }
         else { location.href = '/search?q=' + encodeURIComponent(q); }
       }
diff --git a/DW-Websites/LosAngelesFabrics b/DW-Websites/LosAngelesFabrics
index 12b5df9a..6a6749b7 160000
--- a/DW-Websites/LosAngelesFabrics
+++ b/DW-Websites/LosAngelesFabrics
@@ -1 +1 @@
-Subproject commit 12b5df9a0badcdc78052bdc8a53d39931132bc0d
+Subproject commit 6a6749b759bdacf1939fd488d16b2f57278aee8c

← b215ffb5 TK-10002: replace APPLY=1 env-var gate with signed approval-  ·  back to Designer Wallcoverings  ·  TK-10002: DRY-RUN residual-collision plan from AUTHORITATIVE 7635e1c2 →