[object Object]

← back to Wallco Ai

feat(scenic): hospitality style-pack sub-filter — Aman / Auberge / Four Seasons

2bd382304b54717a7bf8b57be3a18afe72c4e7e6 · 2026-05-22 13:38:21 -0700 · Steve Abrams

Surfaces the hospitalityFit() classifier as a customer-facing filter so the
90 photoreal Scenic designs become 3 curated style packs.

Wiring:
- /designs accepts ?hospitality=Aman|Auberge|Four Seasons (case-insensitive,
  dashes-to-spaces tolerant). Filter is meaningful only when combined with
  ?line=Scenic since hospitalityFit() returns null for Studio.
- /scenic landing page renders a 3-card style-pack grid below the brand
  pitch — each card shows the pack name + 1-line subtitle + count of
  designs in that pack, links to /designs?line=Scenic&hospitality=...
- /designs?line=Scenic browse now renders a chip strip 'Hospitality:
  All Scenic · Aman · Auberge · Four Seasons' above the existing Hue
  swatches. Inherits cat / motif / hue filters when active, so chips
  compose cleanly.

Pack subtitles:
  Aman          — Minimalist · restrained palette · dark or neutral
  Auberge       — Warm · hand-crafted · amber-bronze
  Four Seasons  — Classic · traditional · everything else

Style-pack mapping is owned by hospitalityFit(d) at server.js:12405
(lightness < 0.25 OR saturation < 0.15 → Aman; hue 15-45° + sat < 0.5 →
Auberge; everything else → Four Seasons). When the catalog grows the
classifier auto-tags new designs by palette — no manual curation needed.

Smoke (all prod):
  /scenic                                              200, packs grid present
  /designs?line=Scenic                                 200, hosp-chip strip
  /designs?line=Scenic&hospitality=Aman                200
  /designs?line=Scenic&hospitality=Auberge             200
  /designs?line=Scenic&hospitality=Four%20Seasons      200

Files touched

Diff

commit 2bd382304b54717a7bf8b57be3a18afe72c4e7e6
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 22 13:38:21 2026 -0700

    feat(scenic): hospitality style-pack sub-filter — Aman / Auberge / Four Seasons
    
    Surfaces the hospitalityFit() classifier as a customer-facing filter so the
    90 photoreal Scenic designs become 3 curated style packs.
    
    Wiring:
    - /designs accepts ?hospitality=Aman|Auberge|Four Seasons (case-insensitive,
      dashes-to-spaces tolerant). Filter is meaningful only when combined with
      ?line=Scenic since hospitalityFit() returns null for Studio.
    - /scenic landing page renders a 3-card style-pack grid below the brand
      pitch — each card shows the pack name + 1-line subtitle + count of
      designs in that pack, links to /designs?line=Scenic&hospitality=...
    - /designs?line=Scenic browse now renders a chip strip 'Hospitality:
      All Scenic · Aman · Auberge · Four Seasons' above the existing Hue
      swatches. Inherits cat / motif / hue filters when active, so chips
      compose cleanly.
    
    Pack subtitles:
      Aman          — Minimalist · restrained palette · dark or neutral
      Auberge       — Warm · hand-crafted · amber-bronze
      Four Seasons  — Classic · traditional · everything else
    
    Style-pack mapping is owned by hospitalityFit(d) at server.js:12405
    (lightness < 0.25 OR saturation < 0.15 → Aman; hue 15-45° + sat < 0.5 →
    Auberge; everything else → Four Seasons). When the catalog grows the
    classifier auto-tags new designs by palette — no manual curation needed.
    
    Smoke (all prod):
      /scenic                                              200, packs grid present
      /designs?line=Scenic                                 200, hosp-chip strip
      /designs?line=Scenic&hospitality=Aman                200
      /designs?line=Scenic&hospitality=Auberge             200
      /designs?line=Scenic&hospitality=Four%20Seasons      200
---
 server.js | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/server.js b/server.js
index 1d7939f..fcfd0b0 100644
--- a/server.js
+++ b/server.js
@@ -3570,6 +3570,31 @@ function renderSubBrandLanding(req, res, line) {
     + '<section style="max-width:780px;margin:0 auto;padding:56px 24px 36px;text-align:center">'
     +   '<p style="font:16px/1.75 var(--sans,system-ui);color:var(--ink,#1a1a1a);margin:0">' + pitch + '</p>'
     + '</section>'
+    // Hospitality style-pack chips — Scenic only. Each chip filters the
+    // catalog to the designs whose dominant palette fits one of the three
+    // hospitality brand aesthetics (Aman = restrained-dark/neutral, Auberge
+    // = warm amber-bronze, Four Seasons = classic everything-else).
+    + (!isStudio ? (() => {
+        const packs = ['Aman', 'Auberge', 'Four Seasons'].map(name => {
+          const count = lineDesigns.filter(d => hospitalityFit(d) === name).length;
+          const subtitle = name === 'Aman'
+            ? 'Minimalist · restrained palette · dark or neutral'
+            : name === 'Auberge'
+            ? 'Warm · hand-crafted · amber-bronze'
+            : 'Classic · traditional · everything else';
+          return ''
+            + '<a href="/designs?line=Scenic&hospitality=' + encodeURIComponent(name) + '" style="display:flex;flex-direction:column;align-items:center;gap:6px;padding:18px 14px;background:#faf6ec;border:1px solid #e7dcc3;border-radius:10px;text-decoration:none;color:var(--ink,#1a1a1a);transition:transform .12s ease, box-shadow .12s ease" onmouseover="this.style.transform=\'translateY(-2px)\';this.style.boxShadow=\'0 6px 18px rgba(122,74,40,0.18)\'" onmouseout="this.style.transform=\'\';this.style.boxShadow=\'\'">'
+            +   '<div style="font:13px var(--serif,Georgia,serif);font-weight:700;letter-spacing:.05em">' + name + '</div>'
+            +   '<div style="font:11px var(--sans,system-ui);color:var(--ink-soft,#555);text-align:center;line-height:1.4">' + subtitle + '</div>'
+            +   '<div style="font:10px var(--sans,system-ui);letter-spacing:.10em;color:var(--ink-faint,#999);text-transform:uppercase">' + count + ' designs</div>'
+            + '</a>';
+        }).join('');
+        return ''
+          + '<section style="max-width:920px;margin:0 auto;padding:0 24px 36px">'
+          +   '<div style="font:11px var(--sans,system-ui);letter-spacing:.20em;text-transform:uppercase;color:var(--ink-faint,#999);text-align:center;margin-bottom:18px">Filter by hospitality aesthetic</div>'
+          +   '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:14px">' + packs + '</div>'
+          + '</section>';
+      })() : '')
     // Featured grid
     + (gridCards
         ? '<section style="max-width:1280px;margin:0 auto;padding:0 24px 24px"><div style="font:11px var(--sans,system-ui);letter-spacing:.20em;text-transform:uppercase;color:var(--ink-faint,#999);text-align:center;margin-bottom:18px">Featured ' + line + ' designs</div><div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px">' + gridCards + '</div></section>'
@@ -3721,6 +3746,15 @@ app.get('/designs', (req, res) => {
     const want = lineQ.toLowerCase();
     filtered = filtered.filter(d => String(productLine(d) || '').toLowerCase() === want);
   }
+  // Hospitality style-pack filter — `?hospitality=Aman|Auberge|Four Seasons`.
+  // Only meaningful when combined with line=Scenic (the classifier returns
+  // null for Studio). Case-insensitive accept; "four-seasons" + "four seasons"
+  // both work.
+  const hospQ = String(req.query.hospitality || '').trim();
+  if (hospQ) {
+    const want = hospQ.toLowerCase().replace(/-/g, ' ');
+    filtered = filtered.filter(d => String(hospitalityFit(d) || '').toLowerCase() === want);
+  }
   if (cat)    filtered = filtered.filter(d => d.category === cat);
   if (motifQ) filtered = filtered.filter(d => (d.motifs || []).includes(motifQ));
   if (hueQ)   filtered = filtered.filter(d => hueBucketOf(d.dominant_hex) === hueQ);
@@ -4184,6 +4218,27 @@ ${(req.query.source === 'all') ? `
     <!-- 2026-05-20 Steve directive — Color + Style scrub sliders stripped. -->
     ${''}
 
+    <!-- Hospitality style-pack chips — only render when ?line=Scenic.
+         Lets trade designers slice the 90+ photoreal scenics into 3 brand-fit
+         packs (Aman / Auberge / Four Seasons). Chips link to the same /designs
+         page with an extra ?hospitality= filter. -->
+    ${lineQ.toLowerCase() === 'scenic' ? (() => {
+      const hospQActive = String(req.query.hospitality || '').toLowerCase().replace(/-/g, ' ');
+      const baseQs = ['line=Scenic'];
+      if (cat)    baseQs.push('cat='   + encodeURIComponent(cat));
+      if (motifQ) baseQs.push('motif=' + encodeURIComponent(motifQ));
+      if (hueQ)   baseQs.push('hue='   + encodeURIComponent(hueQ));
+      // Render "All / Aman / Auberge / Four Seasons" chips with on-state when
+      // the active query matches.
+      const all = `<a href="/designs?${baseQs.join('&')}" class="hosp-chip${!hospQActive ? ' is-on' : ''}" style="font:11px var(--sans);padding:5px 11px;border:1px solid ${!hospQActive ? 'var(--accent)' : 'var(--line)'};background:${!hospQActive ? 'var(--accent)' : 'transparent'};color:${!hospQActive ? 'var(--bg)' : 'var(--ink-soft)'};border-radius:999px;text-decoration:none">All Scenic</a>`;
+      const packs = ['Aman', 'Auberge', 'Four Seasons'].map(name => {
+        const on = hospQActive === name.toLowerCase();
+        const href = '/designs?' + baseQs.concat('hospitality=' + encodeURIComponent(name)).join('&');
+        return `<a href="${href}" class="hosp-chip${on ? ' is-on' : ''}" style="font:11px var(--sans);padding:5px 11px;border:1px solid ${on ? 'var(--accent)' : 'var(--line)'};background:${on ? 'var(--accent)' : 'transparent'};color:${on ? 'var(--bg)' : 'var(--ink-soft)'};border-radius:999px;text-decoration:none">${name}</a>`;
+      }).join('');
+      return '<div class="hosp-swatches" style="display:flex;flex-wrap:wrap;gap:6px;margin:14px 0 4px;align-items:center"><span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-right:6px">Hospitality</span>' + all + packs + '</div>';
+    })() : ''}
+
     <!-- Color-wheel hue swatches -->
     <div class="hue-swatches" style="display:flex;flex-wrap:wrap;gap:6px;margin:18px 0 4px;align-items:center">
       <span style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-right:6px">Hue</span>

← 49e0ab6 ghost-layer: detector + scanner + pre-publish gate + fix-que  ·  back to Wallco Ai  ·  feat(admin): Crop & Fix tool — drag rect on any design, Gemi d2d3d8e →