[object Object]

← back to Hospitalitywallcoverings

sort-skill UI: add Light→Dark / Dark→Light / Color Wheel options

9e0d849666e3e8c330addd232db274a503f473c0 · 2026-05-10 12:43:38 -0700 · Steve

Files touched

Diff

commit 9e0d849666e3e8c330addd232db274a503f473c0
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun May 10 12:43:38 2026 -0700

    sort-skill UI: add Light→Dark / Dark→Light / Color Wheel options
---
 public/index.html | 332 ++++++++++++++++++++++++------------------------------
 1 file changed, 145 insertions(+), 187 deletions(-)

diff --git a/public/index.html b/public/index.html
index 66da262..11ab848 100644
--- a/public/index.html
+++ b/public/index.html
@@ -179,163 +179,36 @@ textarea:focus-visible,
 <style id="ns-header-modal-styles">
         .ns-header { position: sticky; top: 0; z-index: 100; display: flex; align-items: center; justify-content: space-between; padding: 18px 36px; background: rgba(10,10,10,0.92) !important; backdrop-filter: blur(8px); border-bottom: 1px solid rgba(245,241,232,0.06); }
         .ns-brand { display: inline-flex; align-items: center; gap: 14px; text-decoration: none; }
-        .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #a98b5b 0%, #5d4c32 60%, #332a1b 100%); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
+        .ns-dot { width: 28px; height: 28px; border-radius: 50%; background: var(--ns-dot-bg, radial-gradient(circle at 35% 35%, #b66a4d 0%, #8a4a30 60%, #5a2d1c 100%)); box-shadow: 0 0 0 1px rgba(245,241,232,0.10), 0 2px 12px rgba(180,90,60,0.30); flex-shrink: 0; }
         .ns-name { font-family: -apple-system, "SF Pro Display", "Helvetica Neue", system-ui, sans-serif; font-size: 18px !important; font-weight: 300 !important; letter-spacing: 0.02em !important; color: #f5f1e8 !important; text-transform: none !important; margin: 0 !important; }
         .ns-name sup { font-size: 0.55em; font-weight: 300; margin-left: 1px; }
-        .ns-nav { display: flex; gap: 32px; align-items: center; list-style: none; padding: 0; margin: 0; }
-        .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; padding: 0 !important; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
+        .ns-nav a, .ns-nav button { font-family: -apple-system, "SF Pro Text", system-ui, sans-serif; font-size: 15px !important; font-weight: 400 !important; color: #f5f1e8 !important; text-decoration: none !important; background: none !important; border: 0 !important; cursor: pointer; transition: color 200ms; letter-spacing: 0 !important; text-transform: none !important; }
         .ns-nav a:hover, .ns-nav button:hover { color: #ffffff !important; }
-        @media (max-width: 820px) { .ns-header { padding: 14px 18px; } .ns-nav { gap: 16px; } .ns-nav a, .ns-nav button { font-size: 13px !important; } }
-        @media (max-width: 760px) {
-          .ns-header { flex-wrap: wrap; }
-          .ns-nav { display: none !important; flex-basis: 100%; flex-direction: column; align-items: flex-start; gap: 6px; padding: 14px 0 4px; }
-          .ns-nav.open { display: flex !important; }
-          .ns-nav a, .ns-nav button { padding: 10px 0 !important; border-bottom: 1px solid rgba(245,241,232,0.08); width: 100%; text-align: left; }
-          .ns-nav a:nth-child(n+5), .ns-nav button:nth-child(n+5) { display: block !important; }
-          .ns-header .mobile-toggle { display: flex !important; }
+        /* All widths: hide nav by default, hamburger always visible on right, nav opens as right-anchored dropdown panel */
+        .ns-nav {
+            display: none !important;
+            position: absolute; top: 100%; right: 18px;
+            flex-direction: column; align-items: stretch; gap: 0;
+            background: rgba(10,10,10,0.96); backdrop-filter: blur(10px);
+            border: 1px solid rgba(245,241,232,0.10); border-top: 0;
+            min-width: 220px; padding: 10px 0; margin: 0;
+            box-shadow: 0 16px 40px rgba(0,0,0,0.55);
+            z-index: 150;
         }
-        @media (min-width: 761px) {
-          .ns-header .mobile-toggle { display: none !important; }
+        .ns-nav.open { display: flex !important; }
+        .ns-nav a, .ns-nav button {
+            padding: 12px 22px !important;
+            border-bottom: 1px solid rgba(245,241,232,0.06);
+            width: 100%; text-align: left;
         }
-        .ns-modal { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.78); z-index: 200; align-items: center; justify-content: center; padding: 24px; backdrop-filter: blur(6px); }
-        .ns-modal.show { display: flex; }
-        .ns-modal-card { max-width: 460px; width: 100%; background: #141414 !important; border: 1px solid rgba(245,241,232,0.14); border-radius: 6px; padding: 28px 30px 30px; position: relative; }
-        .ns-modal-card .close-x { position: absolute; top: 12px; right: 14px; width: 30px; height: 30px; background: none; border: 1px solid rgba(245,241,232,0.18); color: rgba(245,241,232,0.6) !important; border-radius: 50%; cursor: pointer; font-size: 14px; display: flex; align-items: center; justify-content: center; }
-        .ns-modal-card .close-x:hover { color: #ffffff !important; border-color: #ffffff; }
-        .ns-modal-card h3 { font-family: -apple-system, "SF Pro Display", system-ui; font-weight: 300 !important; font-size: 22px !important; margin: 0 0 6px !important; color: #f5f1e8 !important; }
-        .ns-modal-card .sub { color: rgba(245,241,232,0.65) !important; font-size: 13px; margin: 0 0 22px; }
-        .ns-actions { display: flex; flex-direction: column; gap: 10px; }
-        .ns-action { display: flex; align-items: center; gap: 14px; padding: 14px 16px; background: rgba(245,241,232,0.04) !important; border: 1px solid rgba(245,241,232,0.12) !important; color: #f5f1e8 !important; text-decoration: none !important; cursor: pointer; transition: all 200ms; font: inherit; text-align: left; }
-        .ns-action:hover { background: rgba(245,241,232,0.08) !important; border-color: #d4b683 !important; }
-        .ns-action .ico { width: 32px; height: 32px; border-radius: 50%; background: rgba(212,182,131,0.12); display: flex; align-items: center; justify-content: center; font-size: 15px; flex-shrink: 0; }
-        .ns-action .txt { flex: 1; }
-        .ns-action .txt b { display: block; font-weight: 500; font-size: 14px; color: #f5f1e8 !important; }
-        .ns-action .txt span { font-size: 12px; color: rgba(245,241,232,0.55) !important; }
-
-        /* ─── FINAL CONTRAST PASS — kill remaining gold-solid + white-island defects ─── */
-        /* Replace all gold-fill accents with off-white (Gucci-aesthetic, no DW gold solids) */
-        .new-badge, .new-arrivals-pill,
-        .color-filter-btn.active,
-        .drapery-btn, .sample-btn-chip, .cb-cta {
-          background: #f5f1e8 !important;
-          color: #0a0a0a !important;
-          border-color: #f5f1e8 !important;
-        }
-        .new-badge:hover, .new-arrivals-pill:hover,
-        .color-filter-btn.active:hover,
-        .drapery-btn:hover, .sample-btn-chip:hover, .cb-cta:hover {
-          background: #fff !important;
-          color: #0a0a0a !important;
-        }
-        /* Gold accents stay only as edge color (links, glyphs) — not fills */
-        a { color: #d4b683; }
-        a:hover { color: #f5d97a; }
-        /* Authorized-dealer footer line — small caps tracked, muted ink, no gold pill */
-        .authorized, .authorized-dealer p {
-          background: transparent !important;
-          color: rgba(245,241,232,0.5) !important;
-          font-size: 10px !important;
-          letter-spacing: 0.22em !important;
-          text-transform: uppercase !important;
-          padding: 0 !important;
-          border: none !important;
-          margin-top: 14px !important;
-        }
-        /* Kill any straggler white bg in info-card / sample-related cards */
-        .info-card, .info-card-link, .care-highlight, .feature-card, .spec-item {
-          background-color: rgba(245,241,232,0.04) !important;
-          border-color: rgba(245,241,232,0.12) !important;
-        }
-        /* Ensure modal content stays readable — Steve's earlier sweep made it #141414 with white text */
-        .modal-content { background: #141414 !important; color: #f5f1e8 !important; }
-        .modal-content * { color: #f5f1e8 !important; }
-        .modal-content input, .modal-content textarea, .modal-content select {
-          background: rgba(245,241,232,0.05) !important;
-          color: #f5f1e8 !important;
-          border-color: rgba(245,241,232,0.2) !important;
-        }
-        .modal-content label { color: rgba(245,241,232,0.6) !important; }
-        /* Force the wide-tracked NOVASUEDE wordmark to stay white */
-        .ns-wordmark, .ns-wordmark sup { color: #f5f1e8 !important; }
-
-        /* ─── Modal-internal contrast (post-QA defects) ─── */
-        /* Backdrop covers full viewport */
-        .modal { width: 100vw !important; height: 100vh !important; left: 0 !important; top: 0 !important; background: rgba(0, 0, 0, 0.78) !important; backdrop-filter: blur(6px); }
-        /* Chip — flip from white-on-white island to dark card */
-        .request-chip {
-          background: rgba(245, 241, 232, 0.06) !important;
-          color: #f5f1e8 !important;
-          border: 1px solid rgba(245, 241, 232, 0.18) !important;
-          border-radius: 4px !important;
-        }
-        .request-chip * { color: #f5f1e8 !important; }
-        .request-chip .chip-swatch {
-          /* swatch dot ALWAYS displays the inline-style hex from JS — fight any sweep */
-          border: 1px solid rgba(0, 0, 0, 0.4) !important;
-        }
-        /* + Add to request — hairline white-border on transparent (no gold) */
-        .add-to-request-btn {
-          background: transparent !important;
-          color: #f5f1e8 !important;
-          border: 1px solid rgba(245, 241, 232, 0.6) !important;
-          border-radius: 0 !important;
-          padding: 12px 22px !important;
-          font-size: 11px !important;
-          letter-spacing: 0.32em !important;
-          text-transform: uppercase !important;
-          font-weight: 400 !important;
-        }
-        .add-to-request-btn:hover {
-          background: #f5f1e8 !important;
-          color: #0a0a0a !important;
-          border-color: #f5f1e8 !important;
-        }
-        /* Hex caption visible */
-        .preview-hex, #colorPreviewHex {
-          display: block !important;
-          color: rgba(245, 241, 232, 0.55) !important;
-          font-family: ui-monospace, "SF Mono", Menlo, monospace !important;
-          font-size: 11px !important;
-          margin-top: 4px !important;
-          letter-spacing: 0.04em !important;
-          text-transform: lowercase !important;
-        }
-        /* Color preview name — stays single, prominent */
-        #colorPreviewName {
-          color: #f5f1e8 !important;
-          font-size: 11px !important;
-          letter-spacing: 0.32em !important;
-          text-transform: uppercase !important;
-          font-weight: 500 !important;
-          display: block !important;
-          margin-top: 12px !important;
-        }
-
-        /* ─── Mobile hamburger nav (replaces the overflow at <760px) ─── */
-        @media (max-width: 760px) {
-          header .nav-container { flex-wrap: wrap; }
-          header nav { display: none; flex-basis: 100%; flex-direction: column; gap: 6px; padding: 16px 0 6px; }
-          header nav.open { display: flex; }
-          header nav a { padding: 10px 0; border-bottom: 1px solid rgba(245,241,232,0.1); }
-          header .mobile-toggle {
-            display: flex !important;
-            flex-direction: column;
-            gap: 5px;
-            background: transparent;
-            border: 0;
-            cursor: pointer;
-            padding: 8px;
-          }
-          header .mobile-toggle span {
-            width: 22px; height: 1.5px;
-            background: #f5f1e8;
-            display: block;
-          }
-        }
-        @media (min-width: 761px) {
-          header .mobile-toggle { display: none !important; }
-        }
-
+        .ns-nav a:last-child, .ns-nav button:last-child { border-bottom: 0; }
+        .ns-nav a:hover, .ns-nav button:hover { background: rgba(245,241,232,0.06) !important; color: #ffffff !important; }
+        @media (max-width: 480px) { .ns-nav { right: 12px; min-width: 200px; } }
+        .ns-header .mobile-toggle { display: flex !important; align-items: center; justify-content: center; width: 36px; height: 36px; background: transparent; border: 0; cursor: pointer; padding: 0; gap: 4px; flex-direction: column; }
+        .ns-header .mobile-toggle span { display: block; width: 22px; height: 1.5px; background: #f5f1e8; transition: transform 200ms, opacity 200ms; }
+        .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
+        .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
+        .ns-header .mobile-toggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }
     </style>
 <style id="ns-gold-kill">
     /* novasuede: kill gold-solid fills, off-white instead. Edge color stays gold. */
@@ -376,7 +249,7 @@ textarea:focus-visible,
             <div class="ns-actions">
                 <a class="ns-action" href="#contact" onclick="nsContactClose()"><span class="ico">📋</span><div class="txt"><b>Get a Quote</b><span>Project specs → priced estimate</span></div></a>
                 <a class="ns-action" href="#contact" onclick="nsContactClose();openModal();"><span class="ico">📦</span><div class="txt"><b>Order a Sample</b><span>Free swatch to your shop</span></div></a>
-                <a class="ns-action" href="mailto:info@hospitalitywallcoverings.com?subject=Heritage%20Atelier%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@hospitalitywallcoverings.com</span></div></a>
+                <a class="ns-action" href="mailto:info@hospitalitywallcoverings.com?subject=Hospitality%20Wallcoverings%20inquiry"><span class="ico">✉️</span><div class="txt"><b>Email Us</b><span>info@hospitalitywallcoverings.com</span></div></a>
                 <a class="ns-action" href="tel:888-373-4564"><span class="ico">📞</span><div class="txt"><b>Call Us</b><span>(888) 373-4564 · M–F, 9–5 PT</span></div></a>
             </div>
         </div>
@@ -410,22 +283,22 @@ textarea:focus-visible,
 
 <section class="cinema">
   <div class="cinema-bg" id="heroBg"></div>
-  <div class="corner-mark">Trade-Grade</div>
-  <div class="center-mark">Hospitality Wallcoverings</div>
-  <div class="meta-line">Hotel · Restaurant · Spa · Class A<span class="num" id="heroNum"></span></div>
+  <div class="corner-mark">Contract-Grade · Hotel-Tested</div>
+  <h1 class="center-mark">Contract-grade. Hotel-tested.<span class="sub">Type-II vinyl, acoustic, healthcare-rated wallcoverings for hotels, restaurants, and contract spaces.</span></h1>
+  <div class="meta-line">Type-II · Acoustic · Healthcare-rated · Class A<span class="num" id="heroNum"></span></div>
   <a class="enter" href="#shop">Enter <svg viewBox="0 0 24 12" fill="none" stroke="currentColor" stroke-width="1.4"><path d="M0 6h22M16 1l6 5-6 5"/></svg></a>
 </section>
 
 
 <section class="section" id="ideas">
   <div class="rail-block">
-    <div class="rail-eyebrow">Ideas</div>
-    <h2 class="rail-title">New this week</h2>
+    <div class="rail-eyebrow">Just Specified</div>
+    <h2 class="rail-title">New for hotels &amp; restaurants this week</h2>
     <div class="rail" id="railNewest"></div>
   </div>
   <div class="rail-block">
-    <div class="rail-eyebrow">Ideas</div>
-    <h2 class="rail-title">Curated picks</h2>
+    <div class="rail-eyebrow">Designer Picks</div>
+    <h2 class="rail-title">Contract favorites</h2>
     <div class="rail" id="railCurated"></div>
   </div>
 </section>
@@ -439,14 +312,22 @@ textarea:focus-visible,
     <div class="section-meta">Hotel · Restaurant · Spa · Class A</div>
   </div>
 
-  <div class="filters" id="facets">
-    <button class="chip active" data-facet="all">All</button>
+  <div class="filters" id="facets" role="tablist" aria-label="Hospitality grade filters">
+    <button class="chip active" data-grade="all">All</button>
+    <button class="chip" data-grade="type-ii">Type-II Vinyl</button>
+    <button class="chip" data-grade="acoustic">Acoustic</button>
+    <button class="chip" data-grade="healthcare">Healthcare-rated</button>
+    <button class="chip" data-grade="class-a">Class A</button>
+    <button class="chip" data-grade="wovens">Wovens</button>
   </div>
 
   <div class="density">
     <label>Sort</label>
     <select id="sortSelect" aria-label="Sort products" style="background:transparent;color:inherit;border:1px solid var(--line,rgba(255,255,255,.2));font:inherit;font-size:11px;letter-spacing:.10em;text-transform:uppercase;padding:4px 8px;cursor:pointer;outline:none;margin-right:14px">
       <option value="newest">Newest</option>
+      <option value="light-dark">Light → Dark</option>
+      <option value="dark-light">Dark → Light</option>
+      <option value="wheel">Color Wheel</option>
       <option value="color">Color</option>
       <option value="style">Style</option>
       <option value="sku">SKU A→Z</option>
@@ -469,15 +350,80 @@ textarea:focus-visible,
   <div class="sentinel" id="sentinel"></div>
 </section>
 
+<section class="section" id="specs" aria-labelledby="specsTitle">
+  <div class="section-header">
+    <div>
+      <div class="section-eyebrow">Specifications</div>
+      <h2 class="section-title" id="specsTitle">Spec sheets <span class="accent">&amp; contract docs</span></h2>
+    </div>
+    <div class="section-meta">For architects · designers · purchasing</div>
+  </div>
+  <div class="spec-grid">
+    <a class="spec-card" href="/specs/type-ii-vinyl-spec.pdf" download>
+      <div class="spec-eyebrow">Type-II Vinyl</div>
+      <div class="spec-title">CFFA-W-101-D · Type-II Spec Sheet</div>
+      <div class="spec-meta">28 oz/lin yd · scrubbable · stain-resistant · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/acoustic-wallcovering-spec.pdf" download>
+      <div class="spec-eyebrow">Acoustic</div>
+      <div class="spec-title">NRC 0.30–0.85 · Sound-Absorbing Spec</div>
+      <div class="spec-meta">Open-cell construction · ASTM C423 · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/healthcare-spec.pdf" download>
+      <div class="spec-eyebrow">Healthcare</div>
+      <div class="spec-title">CDC- &amp; FGI-aligned Healthcare Spec</div>
+      <div class="spec-meta">Bleach-cleanable · antimicrobial · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/class-a-fire-rating.pdf" download>
+      <div class="spec-eyebrow">Fire Rating</div>
+      <div class="spec-title">Class A · ASTM E84 Fire Rating</div>
+      <div class="spec-meta">Flame Spread ≤ 25 · Smoke Developed ≤ 450 · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/installation-guide.pdf" download>
+      <div class="spec-eyebrow">Installation</div>
+      <div class="spec-title">Contract Installation Guide</div>
+      <div class="spec-meta">Substrate prep · adhesive · seam method · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/maintenance-care.pdf" download>
+      <div class="spec-eyebrow">Care</div>
+      <div class="spec-title">Maintenance &amp; Cleaning Schedule</div>
+      <div class="spec-meta">Daily · weekly · disinfection protocols · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/sustainability-leed.pdf" download>
+      <div class="spec-eyebrow">Sustainability</div>
+      <div class="spec-title">LEED v4 / WELL · Material Disclosure</div>
+      <div class="spec-meta">VOC · recycled content · HPD/EPD · PDF</div>
+    </a>
+    <a class="spec-card" href="/specs/contract-warranty.pdf" download>
+      <div class="spec-eyebrow">Warranty</div>
+      <div class="spec-title">Commercial 5-Year Warranty</div>
+      <div class="spec-meta">Trade installation · scope · claims · PDF</div>
+    </a>
+  </div>
+  <p class="spec-note">Need an MSDS, CSI 3-part spec, or custom submittal? <a href="#" onclick="event.preventDefault();dwmOpen('Inquiry')">Request from our trade team →</a></p>
+</section>
+
+<style>
+.spec-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(260px, 1fr)); gap:1px; background:var(--line); border:1px solid var(--line); margin-bottom:24px }
+.spec-card { display:block; padding:24px 22px; background:var(--bg); color:var(--paper); text-decoration:none; transition:background 0.18s, transform 0.18s; cursor:pointer }
+.spec-card:hover { background:var(--bg-soft); transform:translateY(-1px) }
+.spec-card .spec-eyebrow { font-size:10px; letter-spacing:0.32em; text-transform:uppercase; font-weight:700; color:var(--accent); margin-bottom:14px }
+.spec-card .spec-title { font-family:'Cormorant Garamond', Georgia, serif; font-size:19px; font-weight:500; line-height:1.3; margin-bottom:10px; color:var(--paper) }
+.spec-card .spec-meta { font-size:11px; letter-spacing:0.06em; color:var(--muted); line-height:1.5 }
+.spec-note { font-size:12px; letter-spacing:0.06em; color:var(--muted); text-align:center; padding:8px 0 }
+.spec-note a { color:var(--accent); text-decoration:none; border-bottom:1px solid var(--accent); padding-bottom:1px }
+.spec-note a:hover { color:var(--paper); border-bottom-color:var(--paper) }
+</style>
+
 <footer>
   <div class="footer-grid">
     <div>
       <div class="footer-brand">Hospitality Wallcoverings</div>
-      <p class="footer-text">Contract-grade wallcoverings. Real samples ship free.</p>
+      <p class="footer-text">Contract-grade · hotel-tested. Type-II vinyl, acoustic, and healthcare-rated wallcoverings. Real samples ship free.</p>
       <p class="footer-text" style="margin-top:14px;font-size:11px;letter-spacing:0.18em;text-transform:uppercase;font-weight:600;color:var(--paper);opacity:0.7"><a href="/about" style="color:inherit;text-decoration:none;border-bottom:1px solid var(--accent)">About →</a></p>
     </div>
     <div class="footer-col">
-      <h4>Aesthetic</h4>
+      <h4>Grade</h4>
       <div id="footerFacets"></div>
     </div>
     <div class="footer-col">
@@ -493,7 +439,7 @@ textarea:focus-visible,
     </div>
   </div>
   <div class="footer-bottom">
-    <span>hospitalitywallcoverings.com · Contract-Grade Wallcoverings</span>
+    <span>hospitalitywallcoverings.com · contract-grade · hotel-tested</span>
     <span><a href="mailto:info@hospitalitywallcoverings.com" style="color:var(--paper);text-decoration:none;border-bottom:1px solid var(--accent);padding-bottom:1px">info@hospitalitywallcoverings.com</a></span>
     <span id="footerStat"></span>
   </div>
@@ -563,8 +509,8 @@ textarea:focus-visible,
 </script>
 
 <script>
-const state = { q:'', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
-const LABELS = {"all":"All"};
+const state = { q:'', grade:'all', facet:'all', page:1, pages:1, total:0, loading:false, exhausted:false };
+const LABELS = {"all":"All","type-ii":"Type-II Vinyl","acoustic":"Acoustic","healthcare":"Healthcare-rated","class-a":"Class A","wovens":"Wovens"};
 
 function escAttr(s) { return String(s == null ? '' : s).replace(/[&<>"']/g, c => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c])); }
 // Image-URL allowlist: only DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json)
@@ -581,34 +527,45 @@ function cardHTML(p) {
 }
 
 async function loadFacets() {
-  let f;
+  // Hydrate total counts + per-grade chip badges from /api/grades.
+  let g;
   try {
-    const r = await fetch('/api/facets');
+    const r = await fetch('/api/grades');
     if (!r.ok) throw new Error('HTTP ' + r.status);
-    f = await r.json();
+    g = await r.json();
   } catch (e) { console.error('loadFacets failed:', e); return; }
+
   const el = document.getElementById('facets');
-  for (const [k, v] of Object.entries(f.aesthetics).sort((a,b) => b[1] - a[1])) {
-    const b = document.createElement('button');
-    b.className = 'chip'; b.dataset.facet = k;
-    b.innerHTML = (LABELS[k] || k) + ' <span style="opacity:.55;font-weight:500;margin-left:4px">' + v + '</span>';
-    el.appendChild(b);
-  }
+  // Append count badges to each static chip
+  el.querySelectorAll('button[data-grade]').forEach(btn => {
+    const k = btn.dataset.grade;
+    const n = k === 'all' ? g.total : (g.counts[k] || 0);
+    if (!btn.querySelector('.cct')) {
+      btn.insertAdjacentHTML('beforeend', ' <span class="cct" style="opacity:.55;font-weight:500;margin-left:4px">' + n + '</span>');
+    }
+  });
   el.addEventListener('click', e => {
-    if (e.target.tagName !== 'BUTTON') return;
+    const btn = e.target.closest('button[data-grade]');
+    if (!btn) return;
     document.querySelectorAll('#facets button').forEach(b => b.classList.remove('active'));
-    e.target.classList.add('active');
-    state.facet = e.target.dataset.facet;
+    btn.classList.add('active');
+    state.grade = btn.dataset.grade;
     resetGrid();
   });
-  document.getElementById('totalCount').textContent = f.total;
-  document.getElementById('footerStat').textContent = f.total + ' patterns · live archive';
-  // Footer aesthetic links
+  document.getElementById('totalCount').textContent = g.total;
+  document.getElementById('footerStat').textContent = g.total + ' patterns · live archive';
+
+  // Footer grade quick-links — same set as the chips
   const footerEl = document.getElementById('footerFacets');
-  for (const [k] of Object.entries(f.aesthetics)) {
+  for (const k of Object.keys(g.counts)) {
     const b = document.createElement('button');
-    b.textContent = LABELS[k] || k;
-    b.onclick = () => { state.facet = k; resetGrid(); document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.facet === k)); document.getElementById('shop').scrollIntoView({behavior:'smooth'}); };
+    b.textContent = (LABELS[k] || k) + ' (' + (g.counts[k] || 0) + ')';
+    b.onclick = () => {
+      state.grade = k;
+      document.querySelectorAll('#facets button').forEach(x => x.classList.toggle('active', x.dataset.grade === k));
+      resetGrid();
+      document.getElementById('shop').scrollIntoView({behavior:'smooth'});
+    };
     footerEl.appendChild(b);
   }
 }
@@ -625,7 +582,8 @@ async function loadGridPage() {
   state.loading = true;
   const params = new URLSearchParams({ page:state.page, limit:24 });
   if (state.q) params.set('q', state.q);
-  if (state.facet !== 'all') params.set('aesthetic', state.facet);
+  if (state.grade && state.grade !== 'all') params.set('grade', state.grade);
+  if (state.sort) params.set('sort', state.sort);
   let data;
   try {
     const r = await fetch('/api/products?' + params);
@@ -640,7 +598,7 @@ async function loadGridPage() {
   state.total = data.total; state.pages = data.pages;
 
   if (state.page === 1) {
-    document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.facet === 'all' ? 'all aesthetics' : (LABELS[state.facet] || state.facet)) + (state.q ? ' · matching "' + state.q + '"' : '');
+    document.getElementById('statLine').textContent = data.total + ' patterns · ' + (state.grade === 'all' ? 'all grades' : (LABELS[state.grade] || state.grade)) + (state.q ? ' · matching "' + state.q + '"' : '');
   }
   const grid = document.getElementById('grid');
   for (const p of data.items) {

← 0ac8521 sort-skill canonical: add light-dark/dark-light/wheel + help  ·  back to Hospitalitywallcoverings  ·  snapshot before fleet-grid refine e36309c →