← back to Wallco Ai
wallco.ai: width + material picker on /design/:id — 24" residential (Paper/Vinyl/Peel-Stick/Grasscloth) vs 54" commercial (Vinyl Type II only) · localStorage-persisted · spec panel reactive · 'Designed by wallco.ai' (no Replicate leak)
b8132ca3d968f1145ed1019813f4ac23f317cd2a · 2026-05-11 19:20:45 -0700 · Steve
Files touched
M data/moodboards.jsonM data/pairings.jsonM server.js
Diff
commit b8132ca3d968f1145ed1019813f4ac23f317cd2a
Author: Steve <steve@designerwallcoverings.com>
Date: Mon May 11 19:20:45 2026 -0700
wallco.ai: width + material picker on /design/:id — 24" residential (Paper/Vinyl/Peel-Stick/Grasscloth) vs 54" commercial (Vinyl Type II only) · localStorage-persisted · spec panel reactive · 'Designed by wallco.ai' (no Replicate leak)
---
data/moodboards.json | 17 ++++++++++-
data/pairings.json | 70 ++++++++++++++++++++++++++++++++++++++++++-
server.js | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 167 insertions(+), 5 deletions(-)
diff --git a/data/moodboards.json b/data/moodboards.json
index 9e26dfe..867a977 100644
--- a/data/moodboards.json
+++ b/data/moodboards.json
@@ -1 +1,16 @@
-{}
\ No newline at end of file
+{
+ "14": [
+ {
+ "pin_key": "d:12",
+ "type": "design",
+ "design_id": 12,
+ "label": "Honey Blossom No.12",
+ "reason": "Same family (floral), different palette — pattern echo with chromatic counterpoint",
+ "thumb_url": "/designs/img/1778541051623_1771215304.png",
+ "hex": "#a09279",
+ "idx": 0,
+ "pinned": false,
+ "pinned_at": "2026-05-12T02:05:08.208Z"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/data/pairings.json b/data/pairings.json
index 9e26dfe..b48d4c9 100644
--- a/data/pairings.json
+++ b/data/pairings.json
@@ -1 +1,69 @@
-{}
\ No newline at end of file
+{
+ "14": {
+ "design_id": 14,
+ "vision_description": "Floral pattern with yellow flowers on a dark background, large-scale repeating floral motif, formal, rich, and luxurious atmosphere.",
+ "generated_at": "2026-05-12T02:05:08.028Z",
+ "suggestions": [
+ {
+ "type": "design",
+ "design_id": 12,
+ "label": "Honey Blossom No.12",
+ "reason": "Same family (floral), different palette — pattern echo with chromatic counterpoint",
+ "thumb_url": "/designs/img/1778541051623_1771215304.png",
+ "hex": "#a09279",
+ "idx": 0,
+ "pin_key": "d:12",
+ "pinned": true
+ },
+ {
+ "type": "design",
+ "design_id": 29,
+ "label": "Sapphire Fan No.29",
+ "reason": "Different language (geometric), shared palette — connector for a layered room",
+ "thumb_url": "/designs/img/1778543219867_428383571.png",
+ "hex": "#0b1631",
+ "idx": 1,
+ "pin_key": "d:29",
+ "pinned": false
+ },
+ {
+ "type": "color",
+ "hex": "#806224",
+ "label": "Complement · #806224",
+ "reason": "Direct chromatic opposite — trim, hardware, or a single accent piece",
+ "idx": 2,
+ "pin_key": "c:#806224",
+ "pinned": false
+ },
+ {
+ "type": "color",
+ "hex": "#342480",
+ "label": "Analogous · #342480",
+ "reason": "Neighboring hue — supporting fabric or upholstery, harmonious not loud",
+ "idx": 3,
+ "pin_key": "c:#342480",
+ "pinned": false
+ },
+ {
+ "type": "material",
+ "label": "Gold leaf accents",
+ "reason": "Contrasts with dark background, enhances floral luxury and adds opulence through metallic sheen.",
+ "icon": "✦",
+ "vision_grounded": true,
+ "idx": 4,
+ "pin_key": "m:gold-leaf-accents",
+ "pinned": false
+ },
+ {
+ "type": "material",
+ "label": "Walnut wood paneling",
+ "reason": "Rich, warm tones complement dark base, adding texture and depth to formal, luxurious atmosphere.",
+ "icon": "✦",
+ "vision_grounded": true,
+ "idx": 5,
+ "pin_key": "m:walnut-wood-paneling",
+ "pinned": false
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/server.js b/server.js
index 3c1c6fa..97bd838 100644
--- a/server.js
+++ b/server.js
@@ -1338,17 +1338,96 @@ ${htmlHeader('/designs')}
<span class="hex-label">${design.dominant_hex}</span>
</div>
+ <!-- Width + material selector -->
+ <div class="material-picker" style="margin:18px 0 20px;padding:16px 18px;border:1px solid var(--line);border-radius:8px;background:var(--card-bg)">
+ <div style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:10px">Width</div>
+ <div class="width-tabs" id="width-tabs" style="display:flex;gap:8px;margin-bottom:14px">
+ <button type="button" class="w-tab is-on" data-w="24" style="flex:1;padding:10px 14px;border:1px solid var(--accent);background:var(--accent);color:var(--bg);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">24" residential</button>
+ <button type="button" class="w-tab" data-w="54" style="flex:1;padding:10px 14px;border:1px solid var(--line);background:transparent;color:var(--ink);font:13px var(--sans);font-weight:500;border-radius:6px;cursor:pointer">54" commercial</button>
+ </div>
+ <div style="font:11px var(--sans);text-transform:uppercase;letter-spacing:.08em;color:var(--ink-faint);margin-bottom:10px">Material</div>
+ <div class="material-tabs" id="material-tabs" style="display:flex;flex-wrap:wrap;gap:6px"></div>
+ <div id="material-readout" style="margin-top:10px;font:12px var(--sans);color:var(--ink-soft)"></div>
+ </div>
+
<div class="detail-spec-panel">
<div class="spec-row"><span class="spec-key">Category</span><span class="spec-val">${design.category}</span></div>
- <div class="spec-row"><span class="spec-key">Width</span><span class="spec-val">54"</span></div>
+ <div class="spec-row"><span class="spec-key">Width</span><span class="spec-val" id="spec-width">24"</span></div>
+ <div class="spec-row"><span class="spec-key">Material</span><span class="spec-val" id="spec-material">Paper</span></div>
<div class="spec-row"><span class="spec-key">Sold by</span><span class="spec-val">Per yard</span></div>
<div class="spec-row"><span class="spec-key">Repeat</span><span class="spec-val" id="spec-repeat">24" (adjust on left)</span></div>
<div class="spec-row"><span class="spec-key">Kind</span><span class="spec-val">${design.kind === 'seamless_tile' ? 'Seamless tile' : design.kind}</span></div>
- <div class="spec-row"><span class="spec-key">Generator</span><span class="spec-val">${design.generator}</span></div>
- <div class="spec-row"><span class="spec-key">Seed</span><span class="spec-val">${design.seed}</span></div>
+ <div class="spec-row"><span class="spec-key">Designed by</span><span class="spec-val">${design.generator}</span></div>
<div class="spec-row"><span class="spec-key">Handle</span><span class="spec-val">${design.handle}</span></div>
</div>
+ <script>
+ (function(){
+ var WIDTH_MATERIALS = {
+ '24': [
+ {key:'paper', label:'Paper', note:'Traditional non-woven, easy install'},
+ {key:'vinyl', label:'Vinyl', note:'Scrubbable, kid-proof, kitchens & baths'},
+ {key:'peelstick', label:'Peel & Stick', note:'Rental-friendly, removable, no paste'},
+ {key:'grasscloth', label:'Grasscloth', note:'Natural fiber, textural, handcrafted'}
+ ],
+ '54': [
+ {key:'vinyl', label:'Vinyl', note:'Type II commercial, scrubbable, Class A fire-rated'}
+ ]
+ };
+ var widthTabs = document.querySelectorAll('.w-tab');
+ var matWrap = document.getElementById('material-tabs');
+ var readout = document.getElementById('material-readout');
+ var specW = document.getElementById('spec-width');
+ var specM = document.getElementById('spec-material');
+
+ var saved = JSON.parse(localStorage.getItem('wallco.material') || 'null') || { w:'24', m:'paper' };
+
+ function paintMaterials(w) {
+ var opts = WIDTH_MATERIALS[w];
+ matWrap.innerHTML = opts.map(function(o){
+ var on = (o.key === saved.m);
+ return '<button type="button" class="m-tab' + (on ? ' is-on' : '') + '" data-m="' + o.key + '" data-note="' + o.note + '" style="padding:8px 14px;border:1px solid ' + (on ? 'var(--accent)' : 'var(--line)') + ';background:' + (on ? 'var(--accent)' : 'transparent') + ';color:' + (on ? 'var(--bg)' : 'var(--ink)') + ';font:12px var(--sans);font-weight:500;border-radius:999px;cursor:pointer">' + o.label + '</button>';
+ }).join('');
+ // If saved.m isn't in this width's options, fall back to first
+ if (!opts.some(function(o){return o.key===saved.m;})) saved.m = opts[0].key;
+ // Wire material buttons
+ matWrap.querySelectorAll('.m-tab').forEach(function(btn){
+ btn.addEventListener('click', function(){
+ saved.m = btn.dataset.m;
+ paintMaterials(w);
+ persist();
+ });
+ });
+ var cur = opts.find(function(o){return o.key===saved.m;}) || opts[0];
+ readout.textContent = cur.note;
+ specW.textContent = w + '"';
+ specM.textContent = cur.label;
+ }
+ function persist(){ localStorage.setItem('wallco.material', JSON.stringify(saved)); }
+
+ widthTabs.forEach(function(t){
+ t.addEventListener('click', function(){
+ saved.w = t.dataset.w;
+ widthTabs.forEach(function(x){
+ var on = x===t;
+ x.classList.toggle('is-on', on);
+ x.style.background = on ? 'var(--accent)' : 'transparent';
+ x.style.color = on ? 'var(--bg)' : 'var(--ink)';
+ x.style.borderColor= on ? 'var(--accent)' : 'var(--line)';
+ });
+ paintMaterials(saved.w);
+ persist();
+ });
+ });
+ // Restore on load
+ widthTabs.forEach(function(t){
+ if (t.dataset.w === saved.w) t.click();
+ });
+ // If neither matched (shouldn't happen), default to 24"
+ if (!document.querySelector('.w-tab.is-on')) widthTabs[0].click();
+ })();
+ </script>
+
<!-- Sample CTA (DW standing rule — non-negotiable) -->
<div class="cta-group">
<a href="/samples?design=${design.id}" class="btn-primary cta-sample">Request Free Sample</a>
← 1fa03aa copyright: scrub AI vendor names + triple-layer watermark ev
·
back to Wallco Ai
·
wallco.ai: motif filter chips on /designs — 12 top motifs (p 6881a12 →