← 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
M DW-Websites/GrassclothWallpaper.com/corner-nav.jsM DW-Websites/LosAngelesFabrics
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 { '&': '&', '<': '<', '>': '>', '"': '"' }[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 “' + cnEsc(q) + '”</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 →