[object Object]

← back to Wallco Ai

Design Coordinate on /design/:id — palette-mode call to pairs.designerwallcoverings.com

769ab6cdf03892908581e5efd5b2715a12da32c1 · 2026-05-13 11:27:08 -0700 · SteveStudio2

Files touched

Diff

commit 769ab6cdf03892908581e5efd5b2715a12da32c1
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date:   Wed May 13 11:27:08 2026 -0700

    Design Coordinate on /design/:id — palette-mode call to pairs.designerwallcoverings.com
---
 server.js | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 160 insertions(+)

diff --git a/server.js b/server.js
index 25127d9..dbb0b21 100644
--- a/server.js
+++ b/server.js
@@ -6950,6 +6950,166 @@ ${htmlHeader('/designs')}
     </div>
   </div>
 
+  <!-- Design Coordinate — pulls coordinating stripes/plaids from the DW catalog
+       that use the design's palette, plus AI-generated coordinates. -->
+  <section
+    id="pairs-well-with"
+    class="dc-section"
+    data-pairs-endpoint="https://pairs.designerwallcoverings.com/api/pairs"
+    data-ai-endpoint="https://pairs.designerwallcoverings.com/api/ai-coordinates"
+    data-source-palette="${design.dominant_hex || ''}"
+    data-source-title="${(design.title || '').replace(/"/g, '&quot;')}"
+    data-source-image="${design.image_url || ''}"
+    style="margin: 48px auto 0; max-width: 1200px; padding: 0 24px; clear: both;"
+    hidden
+  >
+    <button type="button" class="pairs-well__cta" aria-expanded="false">
+      <span class="pairs-well__cta-text">Design Coordinate</span>
+      <span class="pairs-well__cta-icon" aria-hidden="true">+</span>
+    </button>
+
+    <div class="pairs-well__source-palette" id="pairs-well-source-palette" hidden></div>
+    <div class="pairs-well__vendor-picker" id="pairs-well-vendor-picker" hidden>
+      <h4>Paint brands</h4>
+      <div class="chips">
+        <label><input type="checkbox" value="sw"   checked> Sherwin-Williams</label>
+        <label><input type="checkbox" value="de"   checked> Dunn-Edwards</label>
+        <label><input type="checkbox" value="bm"   checked> Benjamin Moore</label>
+        <label><input type="checkbox" value="fb"   checked> Farrow &amp; Ball</label>
+        <label><input type="checkbox" value="behr"        > Behr</label>
+        <label><input type="checkbox" value="ppg"         > PPG</label>
+        <label><input type="checkbox" value="ral"         > RAL</label>
+      </div>
+    </div>
+
+    <div class="pairs-well__catalog" id="pairs-well-catalog" hidden>
+      <div class="pairs-well__panel-head">
+        <h3 style="font:500 22px/1.2 ui-serif,Georgia,serif;">Stripes &amp; Plaids from our catalog <span class="count" id="pairs-well-catalog-count"></span></h3>
+        <div class="pairs-well__controls">
+          <label for="pairs-well-cols-catalog">Columns</label>
+          <input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="8" step="1">
+          <span class="pairs-well__cols-val" id="pairs-well-cols-catalog-val">8</span>
+        </div>
+      </div>
+      <div id="pairs-well-grid" class="pairs-well__grid"></div>
+    </div>
+
+    <div class="pairs-well__ai" id="pairs-well-ai" hidden>
+      <div class="pairs-well__panel-head">
+        <h3 style="font:500 22px/1.2 ui-serif,Georgia,serif;">AI Generated Coordinates <span class="ai-badge">Preview · 24 options</span></h3>
+        <div class="pairs-well__controls">
+          <label for="pairs-well-cols-ai">Columns</label>
+          <input id="pairs-well-cols-ai" type="range" min="3" max="12" value="8" step="1">
+          <span class="pairs-well__cols-val" id="pairs-well-cols-ai-val">8</span>
+        </div>
+      </div>
+      <div id="pairs-well-ai-grid" class="pairs-well__grid pairs-well__grid--ai"></div>
+    </div>
+  </section>
+
+  <style>
+    #pairs-well-with { --cols-catalog: 8; --cols-ai: 8; font-family: inherit; }
+    .pairs-well__cta { display:flex; align-items:center; justify-content:space-between; width:100%; padding:18px 24px; margin:0; background:#111; color:#fff; border:0; border-radius:999px; font-size:15px; font-weight:500; letter-spacing:.06em; text-transform:uppercase; cursor:pointer; transition: background 160ms ease, transform 120ms ease; }
+    .pairs-well__cta:hover { background:#2a2a2a; }
+    .pairs-well__cta:active { transform:scale(.995); }
+    .pairs-well__cta[aria-expanded="true"] .pairs-well__cta-icon { transform: rotate(45deg); }
+    .pairs-well__cta-icon { display:inline-block; font-size:22px; line-height:1; transition: transform 200ms ease; }
+    .pairs-well__source-palette { margin: 18px 0 0; }
+    .pairs-well__source-palette h4 { margin: 0 0 8px; font-size: 11px; letter-spacing:.08em; text-transform: uppercase; color:#777; }
+    .pairs-well__source-palette table { border-collapse: collapse; width: 100%; font-size: 12px; }
+    .pairs-well__source-palette th, .pairs-well__source-palette td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #f0ebe2; }
+    .pairs-well__source-palette th { font-weight: 500; color: #777; font-size: 11px; letter-spacing:.06em; text-transform: uppercase; }
+    .pairs-well__source-palette td.swatch { width: 28px; }
+    .pw-dot    { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; }
+    .pw-square { display: inline-block; width: 13px; height: 13px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; margin-right: 4px; }
+    .pw-code   { color: #888; font-variant-numeric: tabular-nums; font-size: 11px; }
+    .pairs-well__vendor-picker { margin: 18px 0 0; }
+    .pairs-well__vendor-picker h4 { margin: 0 0 8px; font-size: 11px; letter-spacing:.08em; text-transform: uppercase; color: #777; }
+    .pairs-well__vendor-picker .chips { display: flex; flex-wrap: wrap; gap: 6px; }
+    .pairs-well__vendor-picker label { font-size: 12px; padding: 5px 10px; border: 1px solid #e7e7e7; border-radius: 999px; cursor: pointer; background: #fff; user-select: none; }
+    .pairs-well__vendor-picker label:has(input:checked) { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
+    .pairs-well__vendor-picker input { display: none; }
+    .pairs-well__panel-head { display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 12px; margin: 24px 0 12px; }
+    .pairs-well__panel-head h3 { margin: 0; }
+    .pairs-well__panel-head .count { color: #888; font-size: 12px; margin-left: 6px; font-family: ui-sans-serif, system-ui, sans-serif; font-weight: 400; }
+    .pairs-well__panel-head .ai-badge { display: inline-block; padding: 3px 8px; background: #2a2a2a; color: #fff; border-radius: 999px; font-size: 10px; letter-spacing:.08em; text-transform: uppercase; margin-left: 8px; font-family: ui-sans-serif, system-ui, sans-serif; font-weight: 600; }
+    .pairs-well__controls { display: flex; align-items: center; gap: 10px; font-size: 11px; color: #777; text-transform: uppercase; letter-spacing:.06em; }
+    .pairs-well__controls input[type=range] { width: 160px; accent-color: #1a1a1a; }
+    .pairs-well__cols-val { font-variant-numeric: tabular-nums; color: #1a1a1a; min-width: 1.5em; text-align: right; font-weight: 600; }
+    .pairs-well__grid { display: grid; gap: 14px; margin-top: 6px; grid-template-columns: repeat(var(--cols-catalog), minmax(0, 1fr)); }
+    .pairs-well__grid--ai { grid-template-columns: repeat(var(--cols-ai), minmax(0, 1fr)); }
+    @media (max-width: 1100px) { .pairs-well__grid { grid-template-columns: repeat(min(6, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(6, var(--cols-ai)), minmax(0, 1fr)); } }
+    @media (max-width: 900px)  { .pairs-well__grid { grid-template-columns: repeat(min(4, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(4, var(--cols-ai)), minmax(0, 1fr)); } }
+    @media (max-width: 600px)  { .pairs-well__grid { grid-template-columns: repeat(min(2, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(2, var(--cols-ai)), minmax(0, 1fr)); } }
+    .pairs-well__card { display: block; text-decoration: none; color: inherit; background: #fff; border: 1px solid #e7e7e7; border-radius: 6px; overflow: hidden; transition: box-shadow 160ms ease, transform 160ms ease; }
+    .pairs-well__card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.08); transform: translateY(-2px); }
+    .pairs-well__card--ai { border-style: dashed; border-color: #d9c7a8; }
+    .pairs-well__img-wrap { aspect-ratio: 1 / 1; background: #f4f4f4; overflow: hidden; }
+    .pairs-well__img { width: 100%; height: 100%; object-fit: cover; display: block; }
+    .pairs-well__meta { padding: 10px 12px 12px; }
+    .pairs-well__title { margin: 0 0 4px; font-size: 13px; font-weight: 600; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+    .pairs-well__vendor { margin: 0 0 6px; font-size: 10px; color: #777; letter-spacing:.04em; text-transform: uppercase; }
+    .pairs-well__why { display: inline-block; padding: 2px 7px; background: #f4ede4; color: #5a4628; border-radius: 999px; font-size: 10px; line-height: 1.4; margin-bottom: 8px; }
+    .pairs-well__card--ai .pairs-well__why { background: #efe6f5; color: #5b3478; }
+    .pairs-well__palette { display: flex; gap: 3px; margin: 0 0 8px; flex-wrap: wrap; }
+    .pairs-well__palette .pw-dot { width: 12px; height: 12px; }
+    .pairs-well__paints { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.5; color: #444; }
+    .pairs-well__paints .label { color: #888; font-weight: 600; margin-right: 4px; letter-spacing:.04em; }
+    .pairs-well__paints .row { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+    .pairs-well__skeleton { background: linear-gradient(90deg, #eee 0%, #f6f6f6 50%, #eee 100%); background-size: 200% 100%; animation: pwShim 1.2s linear infinite; aspect-ratio: 1 / 1; border-radius: 6px; }
+    @keyframes pwShim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
+    .pairs-well__empty { padding: 18px; color: #777; font-size: 14px; grid-column: 1 / -1; }
+  </style>
+
+  <script>
+  (function () {
+    var root = document.getElementById('pairs-well-with'); if (!root) return;
+    var endpointPairs = root.getAttribute('data-pairs-endpoint');
+    var endpointAi    = root.getAttribute('data-ai-endpoint');
+    var palette       = root.getAttribute('data-source-palette');
+    var sourceTitle   = root.getAttribute('data-source-title') || '';
+    var sourceImage   = root.getAttribute('data-source-image') || '';
+    if (!palette) return;
+    root.hidden = false;
+
+    var cta            = root.querySelector('.pairs-well__cta');
+    var catalogPanel   = document.getElementById('pairs-well-catalog');
+    var catalogGrid    = document.getElementById('pairs-well-grid');
+    var catalogCols    = document.getElementById('pairs-well-cols-catalog');
+    var catalogColsVal = document.getElementById('pairs-well-cols-catalog-val');
+    var catalogCount   = document.getElementById('pairs-well-catalog-count');
+    var aiPanel        = document.getElementById('pairs-well-ai');
+    var aiGrid         = document.getElementById('pairs-well-ai-grid');
+    var aiCols         = document.getElementById('pairs-well-cols-ai');
+    var aiColsVal      = document.getElementById('pairs-well-cols-ai-val');
+    var sourcePalette  = document.getElementById('pairs-well-source-palette');
+    var vendorPicker   = document.getElementById('pairs-well-vendor-picker');
+    var vendorChecks   = vendorPicker.querySelectorAll('input[type=checkbox]');
+    var loaded=false, cachedPairs=null, cachedAi=null, cachedSource=null;
+    var VENDOR_LABEL={sw:'Sherwin-Williams',de:'Dunn-Edwards',bm:'Benjamin Moore',fb:'Farrow & Ball',behr:'Behr',ppg:'PPG',ral:'RAL'};
+    var VENDOR_SHORT={sw:'SW',de:'DE',bm:'BM',fb:'F&B',behr:'Behr',ppg:'PPG',ral:'RAL'};
+    var DEFAULT_VENDORS=['sw','de','bm','fb'];
+    function selectedVendors(){var o=[];vendorChecks.forEach(function(c){if(c.checked)o.push(c.value)});return o.length?o:DEFAULT_VENDORS.slice()}
+    try{var st=JSON.parse(localStorage.getItem('pw.vendors')||'null');if(Array.isArray(st)&&st.length)vendorChecks.forEach(function(c){c.checked=st.indexOf(c.value)!==-1})}catch(_){}
+    function persistVendors(){try{localStorage.setItem('pw.vendors',JSON.stringify(selectedVendors()))}catch(_){}}
+    vendorChecks.forEach(function(c){c.addEventListener('change',function(){persistVendors();if(cachedSource)renderSourcePalette(cachedSource.palette);if(cachedPairs&&!catalogPanel.hidden)renderGrid(catalogGrid,cachedPairs,false)})});
+    function wireSlider(input,valEl,varName,storageKey){function apply(){var n=Math.max(3,Math.min(12,parseInt(input.value,10)||8));valEl.textContent=n;root.style.setProperty('--'+varName,n);try{localStorage.setItem(storageKey,n)}catch(_){}}try{var s=parseInt(localStorage.getItem(storageKey),10);if(!isNaN(s)&&s>=3&&s<=12)input.value=s}catch(_){}input.addEventListener('input',apply);apply()}
+    wireSlider(catalogCols,catalogColsVal,'cols-catalog','pw.cols.catalog');
+    wireSlider(aiCols,aiColsVal,'cols-ai','pw.cols.ai');
+    function esc(s){return String(s==null?'':s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;').replace(/'/g,'&#39;')}
+    function renderSkeletons(g,n){var h='';for(var i=0;i<n;i++)h+='<div class="pairs-well__skeleton"></div>';g.innerHTML=h}
+    function renderSourcePalette(pal){if(!pal||!pal.length){sourcePalette.hidden=true;vendorPicker.hidden=true;return}var vs=selectedVendors();var head='<th></th>'+vs.map(function(v){return '<th>'+esc(VENDOR_LABEL[v])+'</th>'}).join('');var body=pal.map(function(c){var hex=typeof c==='string'?c:c.hex;var cells='<td class="swatch"><span class="pw-dot" style="background:'+esc(hex)+'"></span></td>';vs.forEach(function(v){var p=c&&c.paints&&c.paints[v];cells+='<td>'+(p?'<span class="pw-square" style="background:'+esc(p.hex)+'"></span>'+esc(p.name)+' <span class="pw-code">'+esc(p.code)+'</span>':'—')+'</td>'});return '<tr>'+cells+'</tr>'}).join('');sourcePalette.innerHTML='<h4>Color story</h4><table><thead><tr>'+head+'</tr></thead><tbody>'+body+'</tbody></table>';sourcePalette.hidden=false;vendorPicker.hidden=false}
+    function renderCard(p,isAi){var img=p.image_url?esc(p.image_url):'';var why=(p.why&&p.why[0])?esc(p.why[0]):(p.motif?'AI '+esc(p.motif):'');var pal=(p.palette||[]).map(function(c){var hex=typeof c==='string'?c:c.hex;var sw=c&&c.paints&&c.paints.sw,de=c&&c.paints&&c.paints.de;var tip=(sw?'SW '+sw.name+' '+sw.code:'')+(de?' · DE '+de.name+' '+de.code:'');return '<span class="pw-dot" style="background:'+esc(hex)+'" title="'+esc(tip)+'"></span>'}).join('');var dom=(p.palette&&p.palette[0])||null;var pb='';if(dom&&dom.paints){var vs=selectedVendors();var rows=vs.map(function(v){var pt=dom.paints[v];if(!pt)return '';return '<span class="row"><span class="label">'+esc(VENDOR_SHORT[v])+'</span><span class="pw-square" style="background:'+esc(pt.hex)+'"></span>'+esc(pt.name)+' <span class="pw-code">'+esc(pt.code)+'</span></span>'}).filter(Boolean).join('');if(rows)pb='<div class="pairs-well__paints">'+rows+'</div>'}var href=p.url?esc(p.url):(p.handle?'https://www.designerwallcoverings.com/products/'+esc(p.handle):'#');return '<a class="pairs-well__card'+(isAi?' pairs-well__card--ai':'')+'" target="_blank" rel="noopener" href="'+href+'" title="'+esc((p.why||[]).join(' · '))+'"><div class="pairs-well__img-wrap">'+(img?'<img class="pairs-well__img" src="'+img+'" alt="'+esc(p.title)+'" loading="lazy">':'')+'</div><div class="pairs-well__meta"><p class="pairs-well__title">'+esc(p.title||'')+'</p><p class="pairs-well__vendor">'+esc(p.vendor||(isAi?'AI-Generated':''))+'</p>'+(why?'<span class="pairs-well__why">'+why+'</span>':'')+(pal?'<div class="pairs-well__palette">'+pal+'</div>':'')+pb+'</div></a>'}
+    function renderGrid(g,items,isAi){if(!items||!items.length){g.innerHTML='<div class="pairs-well__empty">No coordinates found.</div>';return}g.innerHTML=items.map(function(p){return renderCard(p,isAi)}).join('')}
+    function buildUrl(base,limit){var qs=['palette='+encodeURIComponent(palette)];if(sourceTitle)qs.push('title='+encodeURIComponent(sourceTitle));if(sourceImage)qs.push('image_url='+encodeURIComponent(sourceImage));if(limit)qs.push('limit='+limit);return base+'?'+qs.join('&')}
+    function load(){if(loaded)return Promise.resolve();loaded=true;renderSkeletons(catalogGrid,12);renderSkeletons(aiGrid,12);catalogPanel.hidden=false;aiPanel.hidden=false;
+      var pp=fetch(buildUrl(endpointPairs,24),{credentials:'omit'}).then(function(r){return r.ok?r.json():Promise.reject(new Error('http '+r.status))}).then(function(d){if(!d||d.ok===false)throw new Error((d&&d.error)||'bad payload');cachedSource=d.source||null;cachedPairs=d.pairs||[];if(cachedSource)renderSourcePalette(cachedSource.palette||[]);renderGrid(catalogGrid,cachedPairs,false);catalogCount.textContent='('+cachedPairs.length+')';if(!cachedPairs.length&&(!cachedSource||!cachedSource.palette||!cachedSource.palette.length))root.hidden=true}).catch(function(){catalogGrid.innerHTML='<div class="pairs-well__empty">Could not load coordinates right now.</div>'});
+      var ap=fetch(buildUrl(endpointAi,24),{credentials:'omit'}).then(function(r){return r.ok?r.json():Promise.reject(new Error('http '+r.status))}).then(function(d){if(!d||d.ok===false)throw new Error('bad payload');cachedAi=d.items||[];renderGrid(aiGrid,cachedAi,true)}).catch(function(){aiGrid.innerHTML='<div class="pairs-well__empty">AI generator not ready.</div>'});
+      return Promise.all([pp,ap])}
+    cta.addEventListener('click',function(){var op=cta.getAttribute('aria-expanded')==='true';if(op){cta.setAttribute('aria-expanded','false');sourcePalette.hidden=true;vendorPicker.hidden=true;catalogPanel.hidden=true;aiPanel.hidden=true;return}cta.setAttribute('aria-expanded','true');load()});
+  })();
+  </script>
+
   <!-- Generated Rooms gallery — hydrates from /api/rooms?design_id={{id}} -->
   <section id="rooms-gallery" class="rooms-gallery" data-design-id="${design.id}" style="margin:48px auto 0;max-width:1200px;padding:0 24px;">
     <div class="rg-head" style="display:flex;align-items:baseline;gap:16px;border-top:1px solid var(--rule,#e5e5e0);padding-top:24px;margin-bottom:18px;">

← 7e452f1 drunk-animals: drop overnight gate, fire 24/7 like stoned  ·  back to Wallco Ai  ·  wallco.ai homepage: pin newest 6 butterflies above Featured, da2aabc →