[object Object]

← back to Wallco Ai

Feature murals prominently on home: pinned Shop Murals quicknav + Featured Murals rail + /designs?murals=1 group filter

c85a001aad917906b20b6bacc7922328f4fa5595 · 2026-06-03 22:57:00 -0700 · Steve

Files touched

Diff

commit c85a001aad917906b20b6bacc7922328f4fa5595
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 3 22:57:00 2026 -0700

    Feature murals prominently on home: pinned Shop Murals quicknav + Featured Murals rail + /designs?murals=1 group filter
---
 public/css/site.css | 44 +++++++++++++++++++++++++
 server.js           | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 137 insertions(+)

diff --git a/public/css/site.css b/public/css/site.css
index 771d3cc..ee0aa3a 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -276,6 +276,50 @@ main { padding-top: 81px; } /* matches .site-header actual height (44px brand +
   margin: 0;
 }
 
+/* ── HERO QUICK-NAV (pinned Murals + Browse-all chips, Steve 2026-06-03) */
+.hero-quicknav {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  gap: 10px;
+  margin: 22px 0 0;
+}
+.quicknav-chip {
+  font-family: var(--sans, system-ui);
+  font-size: 13px;
+  letter-spacing: 0.04em;
+  text-decoration: none;
+  color: var(--ink-soft);
+  border: 1px solid var(--line);
+  border-radius: 999px;
+  padding: 9px 20px;
+  transition: background .15s ease, color .15s ease, border-color .15s ease;
+}
+.quicknav-chip:hover { color: var(--ink); border-color: var(--ink); }
+.quicknav-chip-primary {
+  background: var(--ink, #0d0d0d);
+  color: var(--bg, #fff);
+  border-color: var(--ink, #0d0d0d);
+  font-weight: 500;
+}
+.quicknav-chip-primary:hover { color: var(--bg, #fff); opacity: 0.88; }
+
+/* ── FEATURED MURALS RAIL (Steve 2026-06-03 — surface murals above the feed) */
+.mural-feature {
+  background: linear-gradient(180deg, rgba(120, 92, 64, 0.06), transparent 70%);
+  border-bottom: 1px solid var(--line);
+}
+.mural-eyebrow {
+  display: block;
+  font-family: var(--sans, system-ui);
+  font-size: 11px;
+  letter-spacing: 0.22em;
+  text-transform: uppercase;
+  color: var(--ink-faint, #999);
+  margin: 0 0 4px;
+}
+.mural-see-all { font-weight: 500; color: var(--ink); }
+
 /* ── SECTION HEADERS */
 .section-header {
   display: flex;
diff --git a/server.js b/server.js
index 79b8f60..473b581 100644
--- a/server.js
+++ b/server.js
@@ -1183,6 +1183,28 @@ function cardAriaLabel(d) {
   return `${d.title} — ${hue} ${cat} wallpaper${roomTxt}. View details.`;
 }
 
+// ── Mural browse-grouping (Steve 2026-06-03 "make murals prominent") ─────────
+// The catalog's `?cat=` filter is exact-match, so it can't surface a whole
+// MURAL family in one view (e.g. `designer-scenic · slate-mist` colorways won't
+// match a plain `designer-scenic`). This presentation-level predicate groups
+// every mural line — the canonical insert-guard set in lib/mural-categories.js
+// PLUS `designer-scenic*` (Steve explicitly named it a mural line; it isn't in
+// the strict insert guard because it's not subject to the never-tile rule the
+// guard enforces). Used by the home "Featured · Murals" rail and the
+// `/designs?murals=1` group filter. Deliberately NOT mutating
+// lib/mural-categories.js so that file stays a clean insert-time tile guard.
+const { MURAL_CATEGORIES: _MURAL_CAT_SET } = require('./lib/mural-categories.js');
+function isMuralBrowseCategory(category) {
+  const c = String(category || '').toLowerCase().trim();
+  if (!c) return false;
+  // Match the base category even when a " · colorway" suffix is appended.
+  const base = c.split('·')[0].trim();
+  if (_MURAL_CAT_SET.has(base)) return true;
+  // Steve-named scenic mural lines (designer-scenic + its colorways).
+  if (base === 'designer-scenic' || base.startsWith('designer-scenic')) return true;
+  return false;
+}
+
 function sortDesigns(designs, sort) {
   const d = [...designs];
   // Designs with a pre-generated room preview surface first within any sort —
@@ -6953,6 +6975,42 @@ app.get('/', (req, res) => {
   // generated work to be the first thing visitors see. created_at lex sort is
   // safe because every row is ISO-8601 UTC.
   const featured = [...DESIGNS].sort((a,b) => String(b.created_at||'').localeCompare(String(a.created_at||''))).slice(0, 8);
+  // ── Featured · Murals rail (Steve 2026-06-03) ──────────────────────────────
+  // The home feed is newest-first, so the high-value MURAL lines (monterey-mural,
+  // designer-scenic, cactus-11ft-mural, tree-mural, cactus-pine-scenic) — which
+  // have older ids ~40k-52k — get buried under the 57xxx animal designs. This
+  // rail pins them prominently at the very top of the page. We curate a diverse
+  // spread: take the best from EACH named mural family (room-preview first, then
+  // newest) so no single family dominates the shelf. Empty → rail hides itself.
+  const muralFamilyOrder = ['monterey-mural', 'designer-scenic', 'cactus-11ft-mural', 'tree-mural', 'cactus-pine-scenic'];
+  const muralFamilyOf = (cat) => {
+    const base = String(cat || '').toLowerCase().split('·')[0].trim();
+    return muralFamilyOrder.find(f => base === f || base.startsWith(f)) || base;
+  };
+  const allMurals = DESIGNS.filter(d => isMuralBrowseCategory(d.category) && designHasImage(d));
+  // Rank within a family: designs with a room mockup first, then newest.
+  const muralRank = (a, b) => {
+    const ra = (a.room_mockups || []).length > 0 ? 1 : 0;
+    const rb = (b.room_mockups || []).length > 0 ? 1 : 0;
+    if (rb !== ra) return rb - ra;
+    return String(b.created_at || '').localeCompare(String(a.created_at || ''));
+  };
+  const byMuralFamily = Object.create(null);
+  for (const d of allMurals) {
+    const fam = muralFamilyOf(d.category);
+    (byMuralFamily[fam] = byMuralFamily[fam] || []).push(d);
+  }
+  // Round-robin across families so the rail shows variety, up to 8 cards.
+  const muralFeatured = [];
+  const muralPools = muralFamilyOrder
+    .map(f => (byMuralFamily[f] || []).sort(muralRank))
+    .filter(pool => pool.length);
+  for (let round = 0; muralFeatured.length < 8 && muralPools.some(p => p.length); round++) {
+    for (const pool of muralPools) {
+      if (muralFeatured.length >= 8) break;
+      if (pool[round]) muralFeatured.push(pool[round]);
+    }
+  }
   // Pinned "Just-generated · Butterflies" rail — surfaces the latest 6
   // butterfly-flavored designs above the Featured grid. Matches on category
   // OR motif so we catch butterfly-trellis as well as future butterfly-*
@@ -7060,6 +7118,28 @@ app.get('/', (req, res) => {
     ${butterflyCards}
   </div>
 </section>
+` : '';
+
+  // ── Featured · Murals rail markup (Steve 2026-06-03) ───────────────────────
+  // Pinned at the very top of the page so the mural lines are the first thing a
+  // visitor sees, instead of being buried pages deep behind the newest-first
+  // feed. "See all murals →" links to /designs?murals=1 (the grouped, fully
+  // sortable + density-controlled grid). Hides entirely if no murals exist.
+  const muralCards = muralFeatured.map(renderCard).join('');
+  const muralSection = muralFeatured.length ? `
+<!-- Featured · Murals — pinned at the top of the page -->
+<section class="featured-section mural-feature" id="mural-feature">
+  <div class="section-header">
+    <div>
+      <span class="mural-eyebrow">Statement Walls</span>
+      <h2>Featured Murals</h2>
+    </div>
+    <a href="/designs?murals=1&amp;sort=newest" class="see-all mural-see-all">Shop all murals &rarr;</a>
+  </div>
+  <div class="design-grid featured-grid">
+    ${muralCards}
+  </div>
+</section>
 ` : '';
 
   res.type('html').send(`${htmlHead({
@@ -7092,8 +7172,13 @@ ${htmlHeader('/')}
 <div class="hero-wordmark-below">
   <h1 class="wordmark">wallco.ai</h1>
   <p class="tagline">Curated-original wallpaper &amp; murals &mdash; every pattern generated, never repeated.</p>
+  <div class="hero-quicknav">
+    <a href="/designs?murals=1&amp;sort=newest" class="quicknav-chip quicknav-chip-primary">&#9650;&nbsp; Shop Murals</a>
+    <a href="/designs?sort=newest" class="quicknav-chip">Browse all designs</a>
+  </div>
 </div>
 
+${muralSection}
 ${butterflySection}
 <!-- Featured designs grid -->
 <section class="featured-section">
@@ -7827,6 +7912,14 @@ app.get('/designs', (req, res) => {
     filtered = filtered.filter(d => String(studioPack(d) || '').toLowerCase() === want);
   }
   if (cat)    filtered = filtered.filter(d => d.category === cat);
+  // Murals group filter (Steve 2026-06-03) — `?murals=1` surfaces EVERY mural
+  // line (monterey-mural, designer-scenic + colorways, cactus-11ft-mural,
+  // tree-mural, cactus-pine-scenic, …) in one sortable view. The home page's
+  // "Featured · Murals" rail "See all" links here. Sort + density controls on
+  // this grid are untouched.
+  const muralsQ = String(req.query.murals || '').toLowerCase();
+  const wantMurals = muralsQ === '1' || muralsQ === 'true' || muralsQ === 'all';
+  if (wantMurals) filtered = filtered.filter(d => isMuralBrowseCategory(d.category));
   if (motifQ) filtered = filtered.filter(d => (d.motifs || []).includes(motifQ));
   if (hueQ)   filtered = filtered.filter(d => hueBucketOf(d.dominant_hex) === hueQ);
   // Room filter — supports binary ('1'/'any'/'all' = any mockup) AND specific

← 5499527 bulk-action 'digital': exempt designs with a built room-size  ·  back to Wallco Ai  ·  Add dedicated Monterey/California-coast scenic mural batch g bb95dd7 →