← back to Wallco Ai
design coordinate: collapse paint chips into <details> table + full sort dropdown (client-side)
029a6cf5ca91e11945efaceff0c01c128cf4e414 · 2026-05-13 14:29:06 -0700 · SteveStudio2
Files touched
Diff
commit 029a6cf5ca91e11945efaceff0c01c128cf4e414
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 14:29:06 2026 -0700
design coordinate: collapse paint chips into <details> table + full sort dropdown (client-side)
---
server.js | 130 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
1 file changed, 126 insertions(+), 4 deletions(-)
diff --git a/server.js b/server.js
index ddba2af..c6721b8 100644
--- a/server.js
+++ b/server.js
@@ -8006,6 +8006,30 @@ ${htmlHeader('/designs')}
<div class="pairs-well__panel-head">
<h3 style="font:500 22px/1.2 ui-serif,Georgia,serif;">Stripes & Plaids from our catalog <span class="count" id="pairs-well-catalog-count"></span></h3>
<div class="pairs-well__controls">
+ <label for="pairs-well-sort">Sort</label>
+ <select id="pairs-well-sort" class="pw-sort"
+ style="padding:5px 10px;border:1px solid #d8d0c0;background:#fff;color:#1a1a1a;border-radius:999px;font:11px ui-sans-serif,system-ui,sans-serif;letter-spacing:.04em;cursor:pointer">
+ <optgroup label="Match quality">
+ <option value="match">Best match (default)</option>
+ </optgroup>
+ <optgroup label="Alphabetical">
+ <option value="title">Title A → Z</option>
+ <option value="title-desc">Title Z → A</option>
+ </optgroup>
+ <optgroup label="Style">
+ <option value="motif">Style / motif</option>
+ </optgroup>
+ <optgroup label="Color">
+ <option value="light-dark">Light → Dark</option>
+ <option value="dark-light">Dark → Light</option>
+ <option value="color-wheel">Color Wheel</option>
+ <option value="vivid">Most Vivid</option>
+ <option value="muted">Most Muted</option>
+ </optgroup>
+ <optgroup label="Other">
+ <option value="random">Random</option>
+ </optgroup>
+ </select>
<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>
@@ -8175,6 +8199,30 @@ ${htmlHeader('/designs')}
.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; }
+ /* Collapsed paint-match table on each coordinate card. Closed by default
+ so the card surface stays clean — palette dots stay visible above, the
+ full SW/DE/BM/F&B paint NAMES live behind a tap. */
+ .pairs-well__paints-d { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.4; color: #444; }
+ .pairs-well__paints-d > summary {
+ list-style: none; cursor: pointer; color: #888;
+ font-size: 10px; letter-spacing: .06em; text-transform: uppercase;
+ font-weight: 600; padding: 2px 0;
+ display: inline-flex; align-items: center; gap: 6px;
+ user-select: none;
+ }
+ .pairs-well__paints-d > summary::-webkit-details-marker { display: none; }
+ .pairs-well__paints-d > summary:hover { color: #1a1a1a; }
+ .pairs-well__paints-d .pw-d-caret { display:inline-block; transition: transform .15s ease; font-size: 10px; }
+ .pairs-well__paints-d[open] .pw-d-caret { transform: rotate(180deg); }
+ .pairs-well__paints-tbl { width: 100%; border-collapse: collapse; margin-top: 5px; }
+ .pairs-well__paints-tbl th, .pairs-well__paints-tbl td {
+ text-align: left; padding: 3px 6px 3px 0; vertical-align: middle;
+ font-size: 10px; line-height: 1.3;
+ }
+ .pairs-well__paints-tbl th { font-weight: 600; color: #777; width: 32px; letter-spacing:.04em; }
+ .pairs-well__paints-tbl td { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 0; }
+ .pairs-well__paints-tbl .pw-square { display:inline-block; width: 9px; height: 9px; vertical-align: middle; margin-right: 5px; border: 1px solid rgba(0,0,0,.12); }
+ .pairs-well__paints-tbl .pw-code { color: #999; font-family: ui-monospace, monospace; font-size: 9px; }
.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; }
@@ -8313,7 +8361,7 @@ ${htmlHeader('/designs')}
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)})});
+ vendorChecks.forEach(function(c){c.addEventListener('change',function(){persistVendors();if(cachedSource)renderSourcePalette(cachedSource.palette);if(cachedPairs&&!catalogPanel.hidden)renderGrid(catalogGrid,sortPairs(cachedPairs,currentSort),false)})});
function wireCols(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()}
wireCols(catalogCols,catalogColsVal,'cols-catalog','pw.cols.catalog');
@@ -8356,8 +8404,22 @@ ${htmlHeader('/designs')}
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>';
+ // Build the paint rows as a 2-column table (Brand · Color) — no per-row
+ // pill label, since the table header already says it. Hex squares stay
+ // so the table is scannable. Wrapped in <details> so the card surface
+ // stays minimal on load — user opens the table when they want it.
+ var rowsTbl = vs.map(function(v){
+ var pt = dom.paints[v]; if (!pt) return '';
+ return '<tr><th>'+esc(VENDOR_SHORT[v])+'</th>'
+ + '<td><span class="pw-square" style="background:'+esc(pt.hex)+'"></span>'
+ + esc(pt.name)+' <span class="pw-code">'+esc(pt.code)+'</span></td></tr>';
+ }).filter(Boolean).join('');
+ if (rowsTbl) {
+ pb = '<details class="pairs-well__paints-d">'
+ + '<summary>Paint matches <span class="pw-d-caret" aria-hidden="true">▾</span></summary>'
+ + '<table class="pairs-well__paints-tbl"><tbody>'+rowsTbl+'</tbody></table>'
+ + '</details>';
+ }
}
var href = p.url ? esc(p.url) : (p.handle ? 'https://www.designerwallcoverings.com/products/'+esc(p.handle) : '#');
var aiOnly = isAi
@@ -8386,6 +8448,66 @@ ${htmlHeader('/designs')}
if (!items || !items.length) { g.innerHTML = '<div class="pairs-well__empty">No coordinates found.</div>'; return; }
g.innerHTML = items.map(function (p, i) { return renderCard(p, isAi, i); }).join('');
}
+
+ // ── Client-side sort for the catalog grid. Server returns match-ranked
+ // order ("Best match"); these sorts re-order the cached array in place.
+ function _hex2hsl(hex) {
+ if (!hex || !/^#?[0-9a-fA-F]{6}$/.test(hex.replace('#',''))) return [0,0,0];
+ var c = hex.replace('#','');
+ var r = parseInt(c.slice(0,2),16)/255, g = parseInt(c.slice(2,4),16)/255, b = parseInt(c.slice(4,6),16)/255;
+ var mx = Math.max(r,g,b), mn = Math.min(r,g,b), d = mx-mn;
+ var l = (mx+mn)/2;
+ var s = d === 0 ? 0 : d/(1-Math.abs(2*l-1));
+ var h = 0;
+ if (d) { if (mx===r) h = ((g-b)/d) % 6; else if (mx===g) h = (b-r)/d + 2; else h = (r-g)/d + 4; }
+ h = (h*60+360) % 360;
+ return [h, s, l];
+ }
+ function _firstHex(p) {
+ if (!p || !p.palette || !p.palette.length) return '#888888';
+ var c = p.palette[0]; return typeof c === 'string' ? c : (c && c.hex) || '#888888';
+ }
+ function sortPairs(items, key) {
+ if (!items || !items.length || !key || key === 'match') return items;
+ var arr = items.slice();
+ switch (key) {
+ case 'title': return arr.sort(function(a,b){ return (a.title||'').localeCompare(b.title||''); });
+ case 'title-desc': return arr.sort(function(a,b){ return (b.title||'').localeCompare(a.title||''); });
+ case 'motif': return arr.sort(function(a,b){ return (a.motif||a.style||'').localeCompare(b.motif||b.style||''); });
+ case 'light-dark': return arr.sort(function(a,b){ return _hex2hsl(_firstHex(b))[2] - _hex2hsl(_firstHex(a))[2]; });
+ case 'dark-light': return arr.sort(function(a,b){ return _hex2hsl(_firstHex(a))[2] - _hex2hsl(_firstHex(b))[2]; });
+ case 'color-wheel': return arr.sort(function(a,b){
+ var ha = _hex2hsl(_firstHex(a)), hb = _hex2hsl(_firstHex(b));
+ var ba = Math.floor(ha[0]/30), bb = Math.floor(hb[0]/30);
+ return ba !== bb ? ba-bb : ha[2]-hb[2];
+ });
+ case 'vivid': return arr.sort(function(a,b){ return _hex2hsl(_firstHex(b))[1] - _hex2hsl(_firstHex(a))[1]; });
+ case 'muted': return arr.sort(function(a,b){ return _hex2hsl(_firstHex(a))[1] - _hex2hsl(_firstHex(b))[1]; });
+ case 'random': {
+ var seed = Math.floor(Date.now() / 60000);
+ var rand = function(n){ var x = Math.sin(seed * 9301 + n * 49297) * 233280; return x - Math.floor(x); };
+ return arr.map(function(x,i){ return { x: x, k: rand(i) }; }).sort(function(a,b){ return a.k - b.k; }).map(function(o){ return o.x; });
+ }
+ default: return arr;
+ }
+ }
+ // Sort dropdown — persist choice in localStorage (per Steve's standing
+ // rule that sort+density choices survive reload).
+ var sortSel = document.getElementById('pairs-well-sort');
+ var currentSort = 'match';
+ try { var s = localStorage.getItem('pw.sort'); if (s) { currentSort = s; if (sortSel) sortSel.value = s; } } catch (_) {}
+ function applySort() {
+ if (!cachedPairs) return;
+ var sorted = sortPairs(cachedPairs, currentSort);
+ renderGrid(catalogGrid, sorted, false);
+ }
+ if (sortSel) {
+ sortSel.addEventListener('change', function () {
+ currentSort = sortSel.value || 'match';
+ try { localStorage.setItem('pw.sort', currentSort); } catch (_) {}
+ applySort();
+ });
+ }
function renderAiGrid() {
var all = (customCards || []).concat(cachedAi || []);
renderGrid(aiGrid, all, true);
@@ -8420,7 +8542,7 @@ ${htmlHeader('/designs')}
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);
+ renderGrid(catalogGrid, sortPairs(cachedPairs, currentSort), false);
catalogCount.textContent = '(' + cachedPairs.length + ')';
if (!cachedPairs.length && (!cachedSource || !cachedSource.palette || !cachedSource.palette.length)) root.hidden = true;
})
← e52478f design page: admin tone-on-tone color-wheel buttons + /api/d
·
back to Wallco Ai
·
marketplace: designer-powered Wallco marketplace (Patternban 27158ce →