← back to Wallco Ai
tone-on-tone hex picker: sort by hue + collapse near-duplicate swatches
c07a04ea6e0b4242b8028f8725d4819c18fbb483 · 2026-05-28 06:47:23 -0700 · Steve Abrams
- hex→HSL, dedupe by 15° hue × 5 sat × 5 light buckets (most-saturated rep wins)
- sort chromatic by hue (then sat desc, light asc); achromatic (s<0.08) at the end by lightness
- result on current FASHION_PALETTES: 44 unique hexes → 35 deduped (~20% collapse)
- pure visual-luxe ask from Steve — 'sort in color wheel order and only show 1 of each'
Files touched
Diff
commit c07a04ea6e0b4242b8028f8725d4819c18fbb483
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Thu May 28 06:47:23 2026 -0700
tone-on-tone hex picker: sort by hue + collapse near-duplicate swatches
- hex→HSL, dedupe by 15° hue × 5 sat × 5 light buckets (most-saturated rep wins)
- sort chromatic by hue (then sat desc, light asc); achromatic (s<0.08) at the end by lightness
- result on current FASHION_PALETTES: 44 unique hexes → 35 deduped (~20% collapse)
- pure visual-luxe ask from Steve — 'sort in color wheel order and only show 1 of each'
---
server.js | 47 ++++++++++++++++++++++++++++++++++++++++++++---
1 file changed, 44 insertions(+), 3 deletions(-)
diff --git a/server.js b/server.js
index 3f957fe..ce731df 100644
--- a/server.js
+++ b/server.js
@@ -12397,13 +12397,54 @@ ${(() => {
try { return require('./scripts/fashion_palettes.js').FASHION_PALETTES; }
catch { return []; }
})();
+ // hex (#RRGGBB) -> [h(0-360), s(0-1), l(0-1)]
+ const hsl = (hex) => {
+ const m = /^#([0-9a-f]{6})$/i.exec(hex);
+ if (!m) return null;
+ const r = parseInt(m[1].slice(0,2),16)/255, g = parseInt(m[1].slice(2,4),16)/255, b = parseInt(m[1].slice(4,6),16)/255;
+ const mx = Math.max(r,g,b), mn = Math.min(r,g,b), L = (mx+mn)/2;
+ if (mx === mn) return [0, 0, L];
+ const d = mx-mn;
+ const S = L > 0.5 ? d/(2-mx-mn) : d/(mx+mn);
+ let H = 0;
+ if (mx === r) H = ((g-b)/d) + (g<b?6:0);
+ else if (mx === g) H = ((b-r)/d) + 2;
+ else H = ((r-g)/d) + 4;
+ return [H*60, S, L];
+ };
+ // 1) Unique exact-hex collect (preserve old uppercase + #RRGGBB validation).
const seen = new Set();
- const hexes = [];
+ const all = [];
for (const p of brands) for (const h of p.hex) {
const k = String(h).toUpperCase();
- if (k.length === 7 && k.startsWith('#') && !seen.has(k)) { seen.add(k); hexes.push(k); }
+ if (k.length === 7 && k.startsWith('#') && !seen.has(k)) {
+ const v = hsl(k);
+ if (v) { seen.add(k); all.push({ hex: k, hsl: v }); }
+ }
}
- return hexes.map(h => {
+ // 2) Near-duplicate collapse — bucket quantized HSL, keep the most-saturated rep
+ // per bucket. Hue 15-deg buckets * sat 5 bands * light 5 bands. Achromatic
+ // (s<0.08) bucketed by lightness only so all grays don't collapse to one.
+ const bucket = new Map();
+ for (const x of all) {
+ const [H, S, L] = x.hsl;
+ const gray = S < 0.08;
+ const key = gray
+ ? 'g:' + Math.round(L*8)
+ : Math.round(H/15) + ':' + Math.round(S*5) + ':' + Math.round(L*5);
+ const prev = bucket.get(key);
+ if (!prev || x.hsl[1] > prev.hsl[1]) bucket.set(key, x);
+ }
+ // 3) Sort: chromatic by hue (then sat desc, then light asc); grays at the end by lightness.
+ const rows = Array.from(bucket.values()).sort((a, b) => {
+ const ag = a.hsl[1] < 0.08, bg = b.hsl[1] < 0.08;
+ if (ag !== bg) return ag ? 1 : -1;
+ if (ag && bg) return a.hsl[2] - b.hsl[2];
+ if (a.hsl[0] !== b.hsl[0]) return a.hsl[0] - b.hsl[0];
+ if (a.hsl[1] !== b.hsl[1]) return b.hsl[1] - a.hsl[1];
+ return a.hsl[2] - b.hsl[2];
+ });
+ return rows.map(({hex: h}) => {
return ` <button data-hex="${h}" title="${h}" aria-label="Recolor in ${h}" style="width:100%;aspect-ratio:1;background:${h};border:1px solid rgba(0,0,0,.12);border-radius:50%;cursor:pointer;padding:0;transition:transform .12s,box-shadow .12s" onmouseover="this.style.transform='scale(1.15)';this.style.boxShadow='0 2px 8px rgba(0,0,0,.18)'" onmouseout="this.style.transform='';this.style.boxShadow=''"></button>`;
}).join('\n');
})()}
← e9fe6eb cactus-curator: 'By-base view' toggle — 1 pattern + thumbs o
·
back to Wallco Ai
·
etsy export: add print_ready_in / print_ready_ft computed fi 8794315 →