← back to Wallco Ai
wallco.ai · home 'Stumble through the catalog' rail gets a category chip strip — server computes top-6 categories from DESIGNS sorted by count (damask, geometric, floral, mixed, arts and crafts, art deco), renders 'All' + 6 chips above the rail. Click → updates activeCat → fetch /api/designs/random?category=… → re-renders rail. Persists last-pick in localStorage[wallco-home-rail-cat] so the chosen category survives reloads. Same chip pattern as /designs hue/motif chips (is-on state, var(--accent) bg). Verified: / 200 29.8KB, 7 chips emitted, 11 wiring refs (home-rail-cats, home-cat-chip, paintChips, localStorage key)
3ae8ad8a8a1a7194f6c0fc11283f3e1905aa527a · 2026-05-12 10:13:37 -0700 · SteveStudio2
Files touched
Diff
commit 3ae8ad8a8a1a7194f6c0fc11283f3e1905aa527a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 10:13:37 2026 -0700
wallco.ai · home 'Stumble through the catalog' rail gets a category chip strip — server computes top-6 categories from DESIGNS sorted by count (damask, geometric, floral, mixed, arts and crafts, art deco), renders 'All' + 6 chips above the rail. Click → updates activeCat → fetch /api/designs/random?category=… → re-renders rail. Persists last-pick in localStorage[wallco-home-rail-cat] so the chosen category survives reloads. Same chip pattern as /designs hue/motif chips (is-on state, var(--accent) bg). Verified: / 200 29.8KB, 7 chips emitted, 11 wiring refs (home-rail-cats, home-cat-chip, paintChips, localStorage key)
---
server.js | 42 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 2 deletions(-)
diff --git a/server.js b/server.js
index c734903..45ca914 100644
--- a/server.js
+++ b/server.js
@@ -1633,6 +1633,14 @@ app.get('/', (req, res) => {
`<div class="hero-slide ${i===0?'active':''}" data-idx="${i}" style="background-image:url('${d.image_url}')" aria-label="${d.title}"></div>`
).join('\n');
+ // Top categories by count — used to render the home random-rail chip strip.
+ const homeCatCounts = Object.create(null);
+ for (const d of DESIGNS) {
+ const c = (d.category || '').toLowerCase().trim();
+ if (c) homeCatCounts[c] = (homeCatCounts[c] || 0) + 1;
+ }
+ const homeCats = Object.entries(homeCatCounts).sort((a,b) => b[1] - a[1]).slice(0, 6).map(([k,n]) => ({ key: k, count: n }));
+
const featuredCards = featured.map(d => {
const roomKey = (d.room_mockups || []).includes('living_room') ? 'living_room'
: (d.room_mockups || []).includes('bedroom') ? 'bedroom'
@@ -1696,24 +1704,54 @@ ${htmlHeader('/')}
<h2>Stumble through the catalog</h2>
<button type="button" id="home-rail-shuffle" style="font:11px var(--sans,system-ui);background:transparent;border:1px solid var(--line,#ddd);color:var(--ink-soft,#555);padding:6px 14px;border-radius:999px;cursor:pointer">↻ Shuffle</button>
</div>
+ <div id="home-rail-cats" role="tablist" aria-label="Random rail category" style="display:flex;flex-wrap:wrap;gap:6px;margin:0 0 14px;align-items:center">
+ <button type="button" role="tab" data-cat="" class="home-cat-chip is-on" style="font:11px var(--sans,system-ui);padding:5px 12px;border-radius:999px;border:1px solid var(--accent,#0d0d0d);background:var(--accent,#0d0d0d);color:var(--bg,#fff);cursor:pointer">All</button>
+ ${homeCats.map(c => `<button type="button" role="tab" data-cat="${c.key}" class="home-cat-chip" style="font:11px var(--sans,system-ui);padding:5px 12px;border-radius:999px;border:1px solid var(--line,#ddd);background:transparent;color:var(--ink-soft,#555);cursor:pointer">${c.key.charAt(0).toUpperCase()+c.key.slice(1)} <span style="opacity:.55">·${c.count}</span></button>`).join('')}
+ </div>
<div id="home-random-rail" style="display:grid;grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:14px;min-height:240px"></div>
</section>
<script>
(function(){
var rail = document.getElementById('home-random-rail');
var btn = document.getElementById('home-rail-shuffle');
+ var catBar = document.getElementById('home-rail-cats');
if (!rail) return;
var esc = function(s){ return String(s||'').replace(/[<>&"]/g, function(c){ return {'<':'<','>':'>','&':'&','"':'"'}[c]; }); };
+ var activeCat = '';
+ try { activeCat = localStorage.getItem('wallco-home-rail-cat') || ''; } catch(e){}
+ function paintChips(){
+ if (!catBar) return;
+ catBar.querySelectorAll('.home-cat-chip').forEach(function(b){
+ var on = (b.dataset.cat || '') === activeCat;
+ b.classList.toggle('is-on', on);
+ b.style.background = on ? 'var(--accent,#0d0d0d)' : 'transparent';
+ b.style.color = on ? 'var(--bg,#fff)' : 'var(--ink-soft,#555)';
+ b.style.borderColor= on ? 'var(--accent,#0d0d0d)' : 'var(--line,#ddd)';
+ b.setAttribute('aria-selected', on ? 'true' : 'false');
+ });
+ }
function load(){
rail.innerHTML = '<div style="grid-column:1/-1;padding:50px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Loading…</div>';
- fetch('/api/designs/random?n=12').then(function(r){ return r.json(); }).then(function(j){
- if (!j.results || !j.results.length) { rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Catalog is still seeding.</div>'; return; }
+ var url = '/api/designs/random?n=12' + (activeCat ? '&category=' + encodeURIComponent(activeCat) : '');
+ fetch(url).then(function(r){ return r.json(); }).then(function(j){
+ if (!j.results || !j.results.length) { rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">No designs in “'+esc(activeCat||'(any)')+'” yet.</div>'; return; }
rail.innerHTML = j.results.map(function(r){
var dot = r.dominant_hex ? '<span style="display:inline-block;width:8px;height:8px;border-radius:50%;background:'+esc(r.dominant_hex)+';border:1px solid rgba(0,0,0,.12);vertical-align:middle;margin-right:4px"></span>' : '';
return '<a href="'+esc(r.url)+'" style="display:block;text-decoration:none;color:inherit"><div style="aspect-ratio:1/1.15;background:#f4f1ea url('+JSON.stringify(r.image_url).slice(1,-1)+') center/cover;border-radius:6px;border:1px solid var(--line,#e6e2d8);margin-bottom:6px"></div><div style="font:13px var(--sans,system-ui);color:var(--ink,#111);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+esc(r.title || ('Design #'+r.id))+'</div><div style="font:11px var(--sans,system-ui);color:var(--ink-faint,#999);margin-top:2px">'+dot+esc(r.category||'mixed')+'</div></a>';
}).join('');
}).catch(function(){ rail.innerHTML = '<div style="grid-column:1/-1;padding:40px;text-align:center;color:var(--ink-faint,#999);font:13px var(--sans,system-ui)">Couldn\\'t load — try again.</div>'; });
}
+ if (catBar) {
+ catBar.addEventListener('click', function(e){
+ var b = e.target.closest('.home-cat-chip');
+ if (!b) return;
+ activeCat = b.dataset.cat || '';
+ try { localStorage.setItem('wallco-home-rail-cat', activeCat); } catch(e){}
+ paintChips();
+ load();
+ });
+ }
+ paintChips();
load();
if (btn) btn.addEventListener('click', load);
})();
← 0fccfb6 wallco.ai · /api/me/saved bulk endpoint + ♥ saved badges in
·
back to Wallco Ai
·
wallco.ai · PDP wall-calculator persists last-used W×H to lo 869d5a0 →