← 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 & 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">& 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- & 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 & 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 => ({ '&':'&','<':'<','>':'>','"':'"',"'":''' }[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 →