← back to Dw Yolo Loop
Artmura site (B/catalog): dominant-color extraction → true-hue color sort + color dots + color filter bar
3e3a3bd4ee8bfb8d022b06b5a242af566ee03993 · 2026-06-12 09:25:17 -0700 · Steve Abrams
- extract_colors.py: PIL dominant hex+hue+bucket per swatch (161); neutrals-first color-wheel sort
- color dot on every card; color-filter chip bar (grey 62, orange 26, white 20, teal 10...) wired to /api/facets
- honors standing color-sort rule with real per-SKU hue, not name-matching
Files touched
M artmura-site/public/index.htmlM artmura-site/public/styles.cssM artmura-site/server.jsA scripts/artmura-onboard/data/artmura-colors.jsonA scripts/artmura-onboard/extract_colors.py
Diff
commit 3e3a3bd4ee8bfb8d022b06b5a242af566ee03993
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Jun 12 09:25:17 2026 -0700
Artmura site (B/catalog): dominant-color extraction → true-hue color sort + color dots + color filter bar
- extract_colors.py: PIL dominant hex+hue+bucket per swatch (161); neutrals-first color-wheel sort
- color dot on every card; color-filter chip bar (grey 62, orange 26, white 20, teal 10...) wired to /api/facets
- honors standing color-sort rule with real per-SKU hue, not name-matching
---
artmura-site/public/index.html | 34 +-
artmura-site/public/styles.css | 13 +-
artmura-site/server.js | 10 +-
scripts/artmura-onboard/data/artmura-colors.json | 1129 ++++++++++++++++++++++
scripts/artmura-onboard/extract_colors.py | 55 ++
5 files changed, 1231 insertions(+), 10 deletions(-)
diff --git a/artmura-site/public/index.html b/artmura-site/public/index.html
index ac64e93..9edd064 100644
--- a/artmura-site/public/index.html
+++ b/artmura-site/public/index.html
@@ -65,6 +65,9 @@
</div>
</div>
+<!-- COLOR FILTER -->
+<div class="colorbar" id="colorbar"></div>
+
<!-- GRID -->
<main class="grid-wrap">
<div class="grid" id="grid"></div>
@@ -84,8 +87,9 @@
<script>
const $=s=>document.querySelector(s), grid=$('#grid');
-let ALL=[], view=[], activeBook=null;
+let ALL=[], view=[], activeBook=null, activeColor=null, FACETS=null;
const LS={sort:'artmura.sort', dens:'artmura.density'};
+const BUCKET_HEX={white:'#efeae0',grey:'#9e978c',black:'#2a2722',red:'#a23b2e',orange:'#c8763a',brown:'#7b5a3c',gold:'#b8923f',green:'#5d7150',teal:'#3f8a86',blue:'#5a7494',purple:'#7a5f86',pink:'#bb8aa0'};
const heroBg = url => `linear-gradient(0deg,rgba(0,0,0,0),rgba(0,0,0,0)),url("${url}")`;
@@ -103,14 +107,19 @@ function startHero(items){
let i=0; if(slides.length>1) setInterval(()=>{slides[i].classList.remove('on');i=(i+1)%slides.length;slides[i].classList.add('on');},5200);
}
-const COLOR_ORDER=['white','off-white','cream','beige','sand','silver','grey','gray','taupe','gold','golden','green','blue','navy','red','brown','charcoal','black'];
-function colorRank(c){const k=(c||'').toLowerCase();const i=COLOR_ORDER.findIndex(x=>k.includes(x));return i<0?99:i;}
-
+// true color-wheel order: neutrals first (by lightness), then chromatic by hue
+function colorKey(p){
+ const b=p.color_bucket, hue=p.hue??999, sat=p.sat??0;
+ if(b==='white')return [0, p.val??1];
+ if(b==='grey') return [1, p.val??.5];
+ if(b==='black')return [2, -(p.val??0)];
+ return [3, hue]; // chromatic by hue angle
+}
function sortItems(items,mode){
const a=[...items];
const num=p=>p.price||0;
switch(mode){
- case 'color': a.sort((x,y)=>colorRank(x.color)-colorRank(y.color)||x.series.localeCompare(y.series));break;
+ case 'color': a.sort((x,y)=>{const kx=colorKey(x),ky=colorKey(y);return kx[0]-ky[0]||kx[1]-ky[1];});break;
case 'series': a.sort((x,y)=>(x.series||'').localeCompare(y.series)|| (x.color||'').localeCompare(y.color));break;
case 'sku': a.sort((x,y)=>(x.sku||'').localeCompare(y.sku));break;
case 'title': a.sort((x,y)=>(x.title||'').localeCompare(y.title));break;
@@ -122,7 +131,9 @@ function sortItems(items,mode){
}
function render(){
- let items = activeBook ? ALL.filter(p=>p.book===activeBook) : ALL;
+ let items = ALL;
+ if(activeBook) items = items.filter(p=>p.book===activeBook);
+ if(activeColor) items = items.filter(p=>p.color_bucket===activeColor);
items = sortItems(items, $('#sort').value);
view=items;
$('#cnt').textContent=items.length;
@@ -133,7 +144,7 @@ function render(){
<div class="ser">${p.series||''}</div>
<div class="nm">${p.color||p.title}</div>
<div class="meta">
- <span class="col">${p.book||'Artmura'}</span>
+ <span class="col">${p.hex?`<span class="dot" style="background:${p.hex}"></span>`:''}${p.book||'Artmura'}</span>
<span class="pr">$${(p.price||0).toFixed(0)} <small>/yd</small></span>
</div>
</div>
@@ -147,6 +158,13 @@ function chips(facets){
wrap.innerHTML=all+facets.books.map(([b,n])=>`<div class="chip ${activeBook===b?'active':''}" data-b="${b}">${b}<span class="n">${n}</span></div>`).join('');
wrap.querySelectorAll('.chip').forEach(ch=>ch.onclick=()=>{activeBook=ch.dataset.b||null;chips(facets);render();$('#catalog').scrollIntoView({behavior:'smooth'});});
}
+function colorbar(facets){
+ const wrap=$('#colorbar');
+ if(!facets.colors||!facets.colors.length){wrap.style.display='none';return;}
+ const all=`<button class="cdot all ${!activeColor?'active':''}" data-c="" title="All colors">All</button>`;
+ wrap.innerHTML=all+facets.colors.map(([c,n])=>`<button class="cdot ${activeColor===c?'active':''}" data-c="${c}" title="${c} (${n})"><span style="background:${BUCKET_HEX[c]||'#ccc'}"></span>${c}</button>`).join('');
+ wrap.querySelectorAll('.cdot').forEach(b=>b.onclick=()=>{activeColor=b.dataset.c||null;colorbar(facets);render();});
+}
// density + sort persistence
function applyDensity(v){document.documentElement.style.setProperty('--cols',v);}
@@ -160,7 +178,7 @@ addEventListener('scroll',()=>{document.body.classList.toggle('scrolled',scrollY
const savedSort=localStorage.getItem(LS.sort); if(savedSort)$('#sort').value=savedSort;
const savedDens=localStorage.getItem(LS.dens)||'4'; $('#density').value=savedDens; applyDensity(savedDens);
const [pd,fc]=await Promise.all([fetch('/api/products').then(r=>r.json()),fetch('/api/facets').then(r=>r.json())]);
- ALL=pd.products; startHero(ALL); chips(fc); render();
+ ALL=pd.products; FACETS=fc; startHero(ALL); chips(fc); colorbar(fc); render();
})();
</script>
</body>
diff --git a/artmura-site/public/styles.css b/artmura-site/public/styles.css
index 88582da..f27a152 100644
--- a/artmura-site/public/styles.css
+++ b/artmura-site/public/styles.css
@@ -91,6 +91,16 @@ body.scrolled .corner{opacity:0;pointer-events:none}
background-repeat:no-repeat;background-position:right 11px center}
.ctl input[type=range]{width:120px;accent-color:var(--accent)}
+/* ---------- color filter bar ---------- */
+.colorbar{max-width:var(--maxw);margin:0 auto;padding:16px 26px 0;display:flex;gap:8px;flex-wrap:wrap;align-items:center}
+.cdot{display:inline-flex;align-items:center;gap:7px;border:1px solid var(--line);background:var(--paper);
+ border-radius:30px;padding:6px 13px 6px 8px;font-size:11px;letter-spacing:.04em;text-transform:capitalize;
+ color:var(--ink-soft);cursor:pointer;transition:.18s}
+.cdot span{width:14px;height:14px;border-radius:50%;border:1px solid rgba(0,0,0,.15)}
+.cdot.all{padding:6px 14px}
+.cdot:hover{border-color:var(--taupe);color:var(--ink)}
+.cdot.active{background:var(--ink);color:#fff;border-color:var(--ink)}
+
/* ---------- grid ---------- */
.grid-wrap{max-width:var(--maxw);margin:0 auto;padding:26px 26px 80px}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:22px}
@@ -102,7 +112,8 @@ body.scrolled .corner{opacity:0;pointer-events:none}
.card .ser{font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:var(--gold)}
.card .nm{font-family:var(--serif);font-size:20px;line-height:1.1;margin:3px 0 2px}
.card .meta{display:flex;justify-content:space-between;align-items:baseline;margin-top:7px}
-.card .col{font-size:12px;color:var(--ink-soft)}
+.card .col{font-size:12px;color:var(--ink-soft);display:inline-flex;align-items:center;gap:6px}
+.card .col .dot{width:9px;height:9px;border-radius:50%;border:1px solid rgba(0,0,0,.12);flex:0 0 auto}
.card .pr{font-size:13px;font-variant-numeric:tabular-nums;color:var(--ink)}
.card .pr small{color:var(--ink-soft);font-size:10px;letter-spacing:.06em}
diff --git a/artmura-site/server.js b/artmura-site/server.js
index 1320d82..9b34a70 100644
--- a/artmura-site/server.js
+++ b/artmura-site/server.js
@@ -20,12 +20,18 @@ function localImages(r) {
return `/images/${r.mfr_sku}-${i + 1}${ext}`;
});
}
+const COLORS_PATH = path.join(ROOT, 'scripts/artmura-onboard/data/artmura-colors.json');
let CATALOG = [];
function load() {
const d = JSON.parse(fs.readFileSync(PKG, 'utf8'));
+ let colors = {};
+ try { colors = JSON.parse(fs.readFileSync(COLORS_PATH, 'utf8')); } catch {}
CATALOG = d.products.map(r => {
const imgs = localImages(r);
+ const c = colors[r.mfr_sku] || {};
return {
+ hex: c.hex || null, hue: c.hue ?? null, color_bucket: c.bucket || null,
+ sat: c.sat ?? null, val: c.val ?? null,
handle: r.handle,
sku: r.mfr_sku,
series: r.pattern_series,
@@ -62,15 +68,17 @@ app.get('/api/product/:handle', (req, res) => {
res.json(p);
});
app.get('/api/facets', (_req, res) => {
- const books = {}, series = {};
+ const books = {}, series = {}, colors = {};
for (const p of CATALOG) {
if (p.book) books[p.book] = (books[p.book] || 0) + 1;
if (p.series) series[p.series] = (series[p.series] || 0) + 1;
+ if (p.color_bucket) colors[p.color_bucket] = (colors[p.color_bucket] || 0) + 1;
}
res.json({
total: CATALOG.length,
books: Object.entries(books).sort((a, b) => b[1] - a[1]),
series: Object.entries(series).sort((a, b) => b[1] - a[1]),
+ colors: Object.entries(colors).sort((a, b) => b[1] - a[1]),
});
});
diff --git a/scripts/artmura-onboard/data/artmura-colors.json b/scripts/artmura-onboard/data/artmura-colors.json
new file mode 100644
index 0000000..348ef1a
--- /dev/null
+++ b/scripts/artmura-onboard/data/artmura-colors.json
@@ -0,0 +1,1129 @@
+{
+ "ARC553": {
+ "hex": "#f9f7e8",
+ "hue": 52.9,
+ "sat": 0.068,
+ "val": 0.976,
+ "bucket": "white"
+ },
+ "ARC555": {
+ "hex": "#c3b4a4",
+ "hue": 31.0,
+ "sat": 0.159,
+ "val": 0.765,
+ "bucket": "orange"
+ },
+ "ARC551": {
+ "hex": "#c1c1c2",
+ "hue": 240.0,
+ "sat": 0.005,
+ "val": 0.761,
+ "bucket": "grey"
+ },
+ "ARC552": {
+ "hex": "#a29d9b",
+ "hue": 17.1,
+ "sat": 0.043,
+ "val": 0.635,
+ "bucket": "grey"
+ },
+ "ARC550": {
+ "hex": "#e7e3db",
+ "hue": 40.0,
+ "sat": 0.052,
+ "val": 0.906,
+ "bucket": "white"
+ },
+ "ARC556": {
+ "hex": "#883437",
+ "hue": 357.9,
+ "sat": 0.618,
+ "val": 0.533,
+ "bucket": "red"
+ },
+ "ARC554": {
+ "hex": "#b1987f",
+ "hue": 30.0,
+ "sat": 0.282,
+ "val": 0.694,
+ "bucket": "orange"
+ },
+ "ARD923": {
+ "hex": "#3f484a",
+ "hue": 190.9,
+ "sat": 0.149,
+ "val": 0.29,
+ "bucket": "teal"
+ },
+ "ARD922": {
+ "hex": "#8b362e",
+ "hue": 5.2,
+ "sat": 0.669,
+ "val": 0.545,
+ "bucket": "red"
+ },
+ "ARD924": {
+ "hex": "#979794",
+ "hue": 60.0,
+ "sat": 0.02,
+ "val": 0.592,
+ "bucket": "grey"
+ },
+ "ARD920": {
+ "hex": "#efebe1",
+ "hue": 42.9,
+ "sat": 0.059,
+ "val": 0.937,
+ "bucket": "white"
+ },
+ "ARD921": {
+ "hex": "#928e89",
+ "hue": 33.3,
+ "sat": 0.062,
+ "val": 0.573,
+ "bucket": "grey"
+ },
+ "COM241": {
+ "hex": "#3c4244",
+ "hue": 195.0,
+ "sat": 0.118,
+ "val": 0.267,
+ "bucket": "grey"
+ },
+ "COM602": {
+ "hex": "#598385",
+ "hue": 182.7,
+ "sat": 0.331,
+ "val": 0.522,
+ "bucket": "teal"
+ },
+ "COM245": {
+ "hex": "#745a43",
+ "hue": 28.2,
+ "sat": 0.422,
+ "val": 0.455,
+ "bucket": "brown"
+ },
+ "COM564": {
+ "hex": "#50463e",
+ "hue": 26.7,
+ "sat": 0.225,
+ "val": 0.314,
+ "bucket": "brown"
+ },
+ "COM603": {
+ "hex": "#545047",
+ "hue": 41.5,
+ "sat": 0.155,
+ "val": 0.329,
+ "bucket": "brown"
+ },
+ "COM566": {
+ "hex": "#777a7d",
+ "hue": 210.0,
+ "sat": 0.048,
+ "val": 0.49,
+ "bucket": "grey"
+ },
+ "COM563": {
+ "hex": "#ac9d81",
+ "hue": 39.1,
+ "sat": 0.25,
+ "val": 0.675,
+ "bucket": "orange"
+ },
+ "COM600": {
+ "hex": "#b6af8b",
+ "hue": 50.2,
+ "sat": 0.236,
+ "val": 0.714,
+ "bucket": "gold"
+ },
+ "COM565": {
+ "hex": "#649f81",
+ "hue": 149.5,
+ "sat": 0.371,
+ "val": 0.624,
+ "bucket": "green"
+ },
+ "COM606": {
+ "hex": "#dcdcdd",
+ "hue": 240.0,
+ "sat": 0.005,
+ "val": 0.867,
+ "bucket": "white"
+ },
+ "COM242": {
+ "hex": "#5c6883",
+ "hue": 221.5,
+ "sat": 0.298,
+ "val": 0.514,
+ "bucket": "blue"
+ },
+ "COM240": {
+ "hex": "#f8f5f0",
+ "hue": 37.5,
+ "sat": 0.032,
+ "val": 0.973,
+ "bucket": "white"
+ },
+ "COM560": {
+ "hex": "#bebead",
+ "hue": 60.0,
+ "sat": 0.089,
+ "val": 0.745,
+ "bucket": "grey"
+ },
+ "COM243": {
+ "hex": "#c5c6c5",
+ "hue": 120.0,
+ "sat": 0.005,
+ "val": 0.776,
+ "bucket": "grey"
+ },
+ "COM604": {
+ "hex": "#fbfbf6",
+ "hue": 60.0,
+ "sat": 0.02,
+ "val": 0.984,
+ "bucket": "white"
+ },
+ "COM601": {
+ "hex": "#7c3e2c",
+ "hue": 13.5,
+ "sat": 0.645,
+ "val": 0.486,
+ "bucket": "red"
+ },
+ "COM561": {
+ "hex": "#979c95",
+ "hue": 102.9,
+ "sat": 0.045,
+ "val": 0.612,
+ "bucket": "grey"
+ },
+ "COM605": {
+ "hex": "#a3a9ab",
+ "hue": 195.0,
+ "sat": 0.047,
+ "val": 0.671,
+ "bucket": "grey"
+ },
+ "COM244": {
+ "hex": "#a5a79c",
+ "hue": 70.9,
+ "sat": 0.066,
+ "val": 0.655,
+ "bucket": "grey"
+ },
+ "COM562": {
+ "hex": "#d5d6d0",
+ "hue": 70.0,
+ "sat": 0.028,
+ "val": 0.839,
+ "bucket": "grey"
+ },
+ "COM246": {
+ "hex": "#b9883a",
+ "hue": 36.9,
+ "sat": 0.686,
+ "val": 0.725,
+ "bucket": "orange"
+ },
+ "CO802": {
+ "hex": "#f0ece1",
+ "hue": 44.0,
+ "sat": 0.063,
+ "val": 0.941,
+ "bucket": "white"
+ },
+ "CO804": {
+ "hex": "#343a3c",
+ "hue": 195.0,
+ "sat": 0.133,
+ "val": 0.235,
+ "bucket": "grey"
+ },
+ "CO800": {
+ "hex": "#6f5b44",
+ "hue": 32.1,
+ "sat": 0.387,
+ "val": 0.435,
+ "bucket": "brown"
+ },
+ "CO803": {
+ "hex": "#9da49c",
+ "hue": 112.5,
+ "sat": 0.049,
+ "val": 0.643,
+ "bucket": "grey"
+ },
+ "CO801": {
+ "hex": "#b77334",
+ "hue": 28.9,
+ "sat": 0.716,
+ "val": 0.718,
+ "bucket": "orange"
+ },
+ "3100": {
+ "hex": "#b5b2a7",
+ "hue": 47.1,
+ "sat": 0.077,
+ "val": 0.71,
+ "bucket": "grey"
+ },
+ "3104": {
+ "hex": "#c3c1c8",
+ "hue": 257.1,
+ "sat": 0.035,
+ "val": 0.784,
+ "bucket": "grey"
+ },
+ "3101": {
+ "hex": "#d5d5d7",
+ "hue": 240.0,
+ "sat": 0.009,
+ "val": 0.843,
+ "bucket": "grey"
+ },
+ "3705": {
+ "hex": "#b2afc0",
+ "hue": 250.6,
+ "sat": 0.089,
+ "val": 0.753,
+ "bucket": "grey"
+ },
+ "3702": {
+ "hex": "#c5c6d6",
+ "hue": 236.5,
+ "sat": 0.079,
+ "val": 0.839,
+ "bucket": "grey"
+ },
+ "WOOD F-004": {
+ "hex": "#1d7b98",
+ "hue": 194.1,
+ "sat": 0.809,
+ "val": 0.596,
+ "bucket": "teal"
+ },
+ "WOOD F-006": {
+ "hex": "#5ba13d",
+ "hue": 102.0,
+ "sat": 0.621,
+ "val": 0.631,
+ "bucket": "green"
+ },
+ "WOOD F-008": {
+ "hex": "#a45243",
+ "hue": 9.3,
+ "sat": 0.591,
+ "val": 0.643,
+ "bucket": "red"
+ },
+ "WOOD F-007": {
+ "hex": "#01a51f",
+ "hue": 131.0,
+ "sat": 0.994,
+ "val": 0.647,
+ "bucket": "green"
+ },
+ "WOOD F-005": {
+ "hex": "#35a3ab",
+ "hue": 184.1,
+ "sat": 0.69,
+ "val": 0.671,
+ "bucket": "teal"
+ },
+ "WOOD F-001": {
+ "hex": "#d1aea1",
+ "hue": 16.3,
+ "sat": 0.23,
+ "val": 0.82,
+ "bucket": "orange"
+ },
+ "WOOD F-009": {
+ "hex": "#71395a",
+ "hue": 324.6,
+ "sat": 0.496,
+ "val": 0.443,
+ "bucket": "pink"
+ },
+ "WOOD F-003": {
+ "hex": "#0093a3",
+ "hue": 185.9,
+ "sat": 1.0,
+ "val": 0.639,
+ "bucket": "teal"
+ },
+ "WOOD F-002": {
+ "hex": "#c7c4bc",
+ "hue": 43.6,
+ "sat": 0.055,
+ "val": 0.78,
+ "bucket": "grey"
+ },
+ "3204": {
+ "hex": "#9b8b89",
+ "hue": 6.7,
+ "sat": 0.116,
+ "val": 0.608,
+ "bucket": "grey"
+ },
+ "3201": {
+ "hex": "#b7b4bb",
+ "hue": 265.7,
+ "sat": 0.037,
+ "val": 0.733,
+ "bucket": "grey"
+ },
+ "3203": {
+ "hex": "#d9d7e1",
+ "hue": 252.0,
+ "sat": 0.044,
+ "val": 0.882,
+ "bucket": "white"
+ },
+ "3200": {
+ "hex": "#a9a5ad",
+ "hue": 270.0,
+ "sat": 0.046,
+ "val": 0.678,
+ "bucket": "grey"
+ },
+ "GOL852": {
+ "hex": "#7e735c",
+ "hue": 40.6,
+ "sat": 0.27,
+ "val": 0.494,
+ "bucket": "brown"
+ },
+ "GOL850": {
+ "hex": "#bb8b49",
+ "hue": 34.7,
+ "sat": 0.61,
+ "val": 0.733,
+ "bucket": "orange"
+ },
+ "GOL851": {
+ "hex": "#946b31",
+ "hue": 35.2,
+ "sat": 0.669,
+ "val": 0.58,
+ "bucket": "orange"
+ },
+ "GOL854": {
+ "hex": "#b4bdb8",
+ "hue": 146.7,
+ "sat": 0.048,
+ "val": 0.741,
+ "bucket": "grey"
+ },
+ "GOL853": {
+ "hex": "#dad7ce",
+ "hue": 45.0,
+ "sat": 0.055,
+ "val": 0.855,
+ "bucket": "white"
+ },
+ "KR901": {
+ "hex": "#354147",
+ "hue": 200.0,
+ "sat": 0.254,
+ "val": 0.278,
+ "bucket": "blue"
+ },
+ "KR900": {
+ "hex": "#7a8485",
+ "hue": 185.5,
+ "sat": 0.083,
+ "val": 0.522,
+ "bucket": "grey"
+ },
+ "KR902": {
+ "hex": "#643818",
+ "hue": 25.3,
+ "sat": 0.76,
+ "val": 0.392,
+ "bucket": "brown"
+ },
+ "KR903": {
+ "hex": "#642523",
+ "hue": 1.8,
+ "sat": 0.65,
+ "val": 0.392,
+ "bucket": "red"
+ },
+ "KR904": {
+ "hex": "#2b7796",
+ "hue": 197.4,
+ "sat": 0.713,
+ "val": 0.588,
+ "bucket": "teal"
+ },
+ "LAC703": {
+ "hex": "#aaafa8",
+ "hue": 102.9,
+ "sat": 0.04,
+ "val": 0.686,
+ "bucket": "grey"
+ },
+ "LAC704": {
+ "hex": "#070707",
+ "hue": 0.0,
+ "sat": 0.0,
+ "val": 0.027,
+ "bucket": "black"
+ },
+ "LAC701": {
+ "hex": "#b7babd",
+ "hue": 210.0,
+ "sat": 0.032,
+ "val": 0.741,
+ "bucket": "grey"
+ },
+ "LAC700": {
+ "hex": "#600d09",
+ "hue": 2.8,
+ "sat": 0.906,
+ "val": 0.376,
+ "bucket": "red"
+ },
+ "LAC702": {
+ "hex": "#f6f6f6",
+ "hue": 0.0,
+ "sat": 0.0,
+ "val": 0.965,
+ "bucket": "white"
+ },
+ "MAT266": {
+ "hex": "#3d413f",
+ "hue": 150.0,
+ "sat": 0.062,
+ "val": 0.255,
+ "bucket": "grey"
+ },
+ "MAT346": {
+ "hex": "#8d6c56",
+ "hue": 24.0,
+ "sat": 0.39,
+ "val": 0.553,
+ "bucket": "orange"
+ },
+ "MAT265": {
+ "hex": "#7e603d",
+ "hue": 32.3,
+ "sat": 0.516,
+ "val": 0.494,
+ "bucket": "brown"
+ },
+ "MAT262": {
+ "hex": "#c9b995",
+ "hue": 41.5,
+ "sat": 0.259,
+ "val": 0.788,
+ "bucket": "orange"
+ },
+ "MAT345": {
+ "hex": "#7d6f4a",
+ "hue": 43.5,
+ "sat": 0.408,
+ "val": 0.49,
+ "bucket": "brown"
+ },
+ "MAT341": {
+ "hex": "#a9afa9",
+ "hue": 120.0,
+ "sat": 0.034,
+ "val": 0.686,
+ "bucket": "grey"
+ },
+ "MAT342": {
+ "hex": "#fcfdf8",
+ "hue": 72.0,
+ "sat": 0.02,
+ "val": 0.992,
+ "bucket": "white"
+ },
+ "MAT260": {
+ "hex": "#f9f8f3",
+ "hue": 50.0,
+ "sat": 0.024,
+ "val": 0.976,
+ "bucket": "white"
+ },
+ "MAT340": {
+ "hex": "#f6f3ec",
+ "hue": 42.0,
+ "sat": 0.041,
+ "val": 0.965,
+ "bucket": "white"
+ },
+ "MAT263": {
+ "hex": "#c1c3c5",
+ "hue": 210.0,
+ "sat": 0.02,
+ "val": 0.773,
+ "bucket": "grey"
+ },
+ "MAT343": {
+ "hex": "#b4bcbe",
+ "hue": 192.0,
+ "sat": 0.053,
+ "val": 0.745,
+ "bucket": "grey"
+ },
+ "MAT344": {
+ "hex": "#8e8c7c",
+ "hue": 53.3,
+ "sat": 0.127,
+ "val": 0.557,
+ "bucket": "grey"
+ },
+ "MAT264": {
+ "hex": "#98c7c6",
+ "hue": 178.7,
+ "sat": 0.236,
+ "val": 0.78,
+ "bucket": "teal"
+ },
+ "MAT261": {
+ "hex": "#fbf6e7",
+ "hue": 45.0,
+ "sat": 0.08,
+ "val": 0.984,
+ "bucket": "white"
+ },
+ "WOOD M-005": {
+ "hex": "#3d3e43",
+ "hue": 230.0,
+ "sat": 0.09,
+ "val": 0.263,
+ "bucket": "grey"
+ },
+ "WOOD M-001": {
+ "hex": "#e4e6e6",
+ "hue": 180.0,
+ "sat": 0.009,
+ "val": 0.902,
+ "bucket": "white"
+ },
+ "WOOD M-002": {
+ "hex": "#cacbc4",
+ "hue": 68.6,
+ "sat": 0.034,
+ "val": 0.796,
+ "bucket": "grey"
+ },
+ "WOOD M-003": {
+ "hex": "#b0b4b5",
+ "hue": 192.0,
+ "sat": 0.028,
+ "val": 0.71,
+ "bucket": "grey"
+ },
+ "WOOD M-004": {
+ "hex": "#4b5054",
+ "hue": 206.7,
+ "sat": 0.107,
+ "val": 0.329,
+ "bucket": "grey"
+ },
+ "WOOD N-008": {
+ "hex": "#9aa3a2",
+ "hue": 173.3,
+ "sat": 0.055,
+ "val": 0.639,
+ "bucket": "grey"
+ },
+ "WOOD N-009": {
+ "hex": "#8ca2b0",
+ "hue": 203.3,
+ "sat": 0.205,
+ "val": 0.69,
+ "bucket": "blue"
+ },
+ "WOOD N-003": {
+ "hex": "#cdd1b0",
+ "hue": 67.3,
+ "sat": 0.158,
+ "val": 0.82,
+ "bucket": "gold"
+ },
+ "WOOD N-007": {
+ "hex": "#cccece",
+ "hue": 180.0,
+ "sat": 0.01,
+ "val": 0.808,
+ "bucket": "grey"
+ },
+ "WOOD N-004": {
+ "hex": "#cfc5a8",
+ "hue": 44.6,
+ "sat": 0.188,
+ "val": 0.812,
+ "bucket": "orange"
+ },
+ "WOOD N-005": {
+ "hex": "#dbdade",
+ "hue": 255.0,
+ "sat": 0.018,
+ "val": 0.871,
+ "bucket": "white"
+ },
+ "WOOD N-002": {
+ "hex": "#cccdca",
+ "hue": 80.0,
+ "sat": 0.015,
+ "val": 0.804,
+ "bucket": "grey"
+ },
+ "WOOD N-001": {
+ "hex": "#e1e2e2",
+ "hue": 180.0,
+ "sat": 0.004,
+ "val": 0.886,
+ "bucket": "white"
+ },
+ "WOOD N-006": {
+ "hex": "#ac816e",
+ "hue": 18.4,
+ "sat": 0.36,
+ "val": 0.675,
+ "bucket": "orange"
+ },
+ "NY823": {
+ "hex": "#b4957a",
+ "hue": 27.9,
+ "sat": 0.322,
+ "val": 0.706,
+ "bucket": "orange"
+ },
+ "NY821": {
+ "hex": "#aaa27d",
+ "hue": 49.3,
+ "sat": 0.265,
+ "val": 0.667,
+ "bucket": "gold"
+ },
+ "NY822": {
+ "hex": "#cdced0",
+ "hue": 220.0,
+ "sat": 0.014,
+ "val": 0.816,
+ "bucket": "grey"
+ },
+ "NY824": {
+ "hex": "#975c53",
+ "hue": 7.9,
+ "sat": 0.45,
+ "val": 0.592,
+ "bucket": "red"
+ },
+ "NY820": {
+ "hex": "#726e63",
+ "hue": 44.0,
+ "sat": 0.132,
+ "val": 0.447,
+ "bucket": "grey"
+ },
+ "OAK754": {
+ "hex": "#415b69",
+ "hue": 201.0,
+ "sat": 0.381,
+ "val": 0.412,
+ "bucket": "blue"
+ },
+ "OAK750": {
+ "hex": "#23121b",
+ "hue": 328.2,
+ "sat": 0.486,
+ "val": 0.137,
+ "bucket": "black"
+ },
+ "OAK753": {
+ "hex": "#9ca6aa",
+ "hue": 197.1,
+ "sat": 0.082,
+ "val": 0.667,
+ "bucket": "grey"
+ },
+ "OAK752": {
+ "hex": "#ceced1",
+ "hue": 240.0,
+ "sat": 0.014,
+ "val": 0.82,
+ "bucket": "grey"
+ },
+ "OAK751": {
+ "hex": "#b07233",
+ "hue": 30.2,
+ "sat": 0.71,
+ "val": 0.69,
+ "bucket": "orange"
+ },
+ "OT893": {
+ "hex": "#151e29",
+ "hue": 213.0,
+ "sat": 0.488,
+ "val": 0.161,
+ "bucket": "black"
+ },
+ "OT892": {
+ "hex": "#3a292d",
+ "hue": 345.9,
+ "sat": 0.293,
+ "val": 0.227,
+ "bucket": "red"
+ },
+ "OT890": {
+ "hex": "#bb9f56",
+ "hue": 43.4,
+ "sat": 0.54,
+ "val": 0.733,
+ "bucket": "orange"
+ },
+ "OT894": {
+ "hex": "#2d8d8b",
+ "hue": 178.7,
+ "sat": 0.681,
+ "val": 0.553,
+ "bucket": "teal"
+ },
+ "OT891": {
+ "hex": "#a06b4b",
+ "hue": 22.6,
+ "sat": 0.531,
+ "val": 0.627,
+ "bucket": "orange"
+ },
+ "ORI453": {
+ "hex": "#c5c0bb",
+ "hue": 30.0,
+ "sat": 0.051,
+ "val": 0.773,
+ "bucket": "grey"
+ },
+ "ORI444": {
+ "hex": "#2f3336",
+ "hue": 205.7,
+ "sat": 0.13,
+ "val": 0.212,
+ "bucket": "grey"
+ },
+ "ORI440": {
+ "hex": "#c0c7c9",
+ "hue": 193.3,
+ "sat": 0.045,
+ "val": 0.788,
+ "bucket": "grey"
+ },
+ "ORI446": {
+ "hex": "#4e453f",
+ "hue": 24.0,
+ "sat": 0.192,
+ "val": 0.306,
+ "bucket": "brown"
+ },
+ "ORI454": {
+ "hex": "#9a8c7c",
+ "hue": 32.0,
+ "sat": 0.195,
+ "val": 0.604,
+ "bucket": "orange"
+ },
+ "ORI441": {
+ "hex": "#655535",
+ "hue": 40.0,
+ "sat": 0.475,
+ "val": 0.396,
+ "bucket": "brown"
+ },
+ "ORI442": {
+ "hex": "#c3b187",
+ "hue": 42.0,
+ "sat": 0.308,
+ "val": 0.765,
+ "bucket": "orange"
+ },
+ "ORI455": {
+ "hex": "#998a63",
+ "hue": 43.3,
+ "sat": 0.353,
+ "val": 0.6,
+ "bucket": "orange"
+ },
+ "ORI456": {
+ "hex": "#699c7e",
+ "hue": 144.7,
+ "sat": 0.327,
+ "val": 0.612,
+ "bucket": "green"
+ },
+ "ORI452": {
+ "hex": "#bfc4c3",
+ "hue": 168.0,
+ "sat": 0.026,
+ "val": 0.769,
+ "bucket": "grey"
+ },
+ "ORI451": {
+ "hex": "#ebe7dd",
+ "hue": 42.9,
+ "sat": 0.06,
+ "val": 0.922,
+ "bucket": "white"
+ },
+ "ORI445": {
+ "hex": "#844936",
+ "hue": 14.6,
+ "sat": 0.591,
+ "val": 0.518,
+ "bucket": "red"
+ },
+ "ORI443": {
+ "hex": "#979e9d",
+ "hue": 171.4,
+ "sat": 0.044,
+ "val": 0.62,
+ "bucket": "grey"
+ },
+ "ORI450": {
+ "hex": "#b5b4a9",
+ "hue": 55.0,
+ "sat": 0.066,
+ "val": 0.71,
+ "bucket": "grey"
+ },
+ "3303": {
+ "hex": "#bca189",
+ "hue": 28.2,
+ "sat": 0.271,
+ "val": 0.737,
+ "bucket": "orange"
+ },
+ "3301": {
+ "hex": "#bdbdca",
+ "hue": 240.0,
+ "sat": 0.064,
+ "val": 0.792,
+ "bucket": "grey"
+ },
+ "3305": {
+ "hex": "#bdbed0",
+ "hue": 236.8,
+ "sat": 0.091,
+ "val": 0.816,
+ "bucket": "grey"
+ },
+ "3300": {
+ "hex": "#c3c5d2",
+ "hue": 232.0,
+ "sat": 0.071,
+ "val": 0.824,
+ "bucket": "grey"
+ },
+ "3004": {
+ "hex": "#ded3cb",
+ "hue": 25.3,
+ "sat": 0.086,
+ "val": 0.871,
+ "bucket": "white"
+ },
+ "3007": {
+ "hex": "#e5b16e",
+ "hue": 33.8,
+ "sat": 0.52,
+ "val": 0.898,
+ "bucket": "orange"
+ },
+ "3001": {
+ "hex": "#aeb6c9",
+ "hue": 222.2,
+ "sat": 0.134,
+ "val": 0.788,
+ "bucket": "grey"
+ },
+ "3002": {
+ "hex": "#acb9db",
+ "hue": 223.4,
+ "sat": 0.215,
+ "val": 0.859,
+ "bucket": "blue"
+ },
+ "3003": {
+ "hex": "#cac6c7",
+ "hue": 345.0,
+ "sat": 0.02,
+ "val": 0.792,
+ "bucket": "grey"
+ },
+ "3000": {
+ "hex": "#bbbac5",
+ "hue": 245.5,
+ "sat": 0.056,
+ "val": 0.773,
+ "bucket": "grey"
+ },
+ "TABC-001": {
+ "hex": "#a15e2d",
+ "hue": 25.3,
+ "sat": 0.72,
+ "val": 0.631,
+ "bucket": "orange"
+ },
+ "TABC-002": {
+ "hex": "#848473",
+ "hue": 60.0,
+ "sat": 0.129,
+ "val": 0.518,
+ "bucket": "grey"
+ },
+ "TABL-001": {
+ "hex": "#d1c4b4",
+ "hue": 33.1,
+ "sat": 0.139,
+ "val": 0.82,
+ "bucket": "grey"
+ },
+ "TABL-007": {
+ "hex": "#2f2f2f",
+ "hue": 0.0,
+ "sat": 0.0,
+ "val": 0.184,
+ "bucket": "grey"
+ },
+ "TABL-003": {
+ "hex": "#9d9f9c",
+ "hue": 100.0,
+ "sat": 0.019,
+ "val": 0.624,
+ "bucket": "grey"
+ },
+ "TABL-002": {
+ "hex": "#c6aea5",
+ "hue": 16.4,
+ "sat": 0.167,
+ "val": 0.776,
+ "bucket": "orange"
+ },
+ "TABL-006": {
+ "hex": "#722c1c",
+ "hue": 11.2,
+ "sat": 0.754,
+ "val": 0.447,
+ "bucket": "red"
+ },
+ "TABL-005": {
+ "hex": "#154042",
+ "hue": 182.7,
+ "sat": 0.682,
+ "val": 0.259,
+ "bucket": "teal"
+ },
+ "TABL-004": {
+ "hex": "#c7b64a",
+ "hue": 51.8,
+ "sat": 0.628,
+ "val": 0.78,
+ "bucket": "gold"
+ },
+ "TABM-002": {
+ "hex": "#bead95",
+ "hue": 35.1,
+ "sat": 0.216,
+ "val": 0.745,
+ "bucket": "orange"
+ },
+ "TABM-007": {
+ "hex": "#202122",
+ "hue": 210.0,
+ "sat": 0.059,
+ "val": 0.133,
+ "bucket": "black"
+ },
+ "TABM-005": {
+ "hex": "#8795a0",
+ "hue": 206.4,
+ "sat": 0.156,
+ "val": 0.627,
+ "bucket": "blue"
+ },
+ "TABM-003": {
+ "hex": "#87755e",
+ "hue": 33.7,
+ "sat": 0.304,
+ "val": 0.529,
+ "bucket": "orange"
+ },
+ "TABM-006": {
+ "hex": "#ce9687",
+ "hue": 12.7,
+ "sat": 0.345,
+ "val": 0.808,
+ "bucket": "red"
+ },
+ "TABM-004": {
+ "hex": "#838079",
+ "hue": 42.0,
+ "sat": 0.076,
+ "val": 0.514,
+ "bucket": "grey"
+ },
+ "TABM-001": {
+ "hex": "#e9e9ea",
+ "hue": 240.0,
+ "sat": 0.004,
+ "val": 0.918,
+ "bucket": "white"
+ },
+ "TABW-007": {
+ "hex": "#57554e",
+ "hue": 46.7,
+ "sat": 0.103,
+ "val": 0.341,
+ "bucket": "grey"
+ },
+ "TABW-006": {
+ "hex": "#4f674e",
+ "hue": 117.6,
+ "sat": 0.243,
+ "val": 0.404,
+ "bucket": "green"
+ },
+ "TABW-001": {
+ "hex": "#acafa0",
+ "hue": 72.0,
+ "sat": 0.086,
+ "val": 0.686,
+ "bucket": "grey"
+ },
+ "TABW-004": {
+ "hex": "#581f36",
+ "hue": 335.8,
+ "sat": 0.648,
+ "val": 0.345,
+ "bucket": "pink"
+ },
+ "TABW-005": {
+ "hex": "#0b6d8a",
+ "hue": 193.7,
+ "sat": 0.92,
+ "val": 0.541,
+ "bucket": "teal"
+ },
+ "TABW-003": {
+ "hex": "#bc5c48",
+ "hue": 10.3,
+ "sat": 0.617,
+ "val": 0.737,
+ "bucket": "red"
+ },
+ "TABW-002": {
+ "hex": "#bf862b",
+ "hue": 36.9,
+ "sat": 0.775,
+ "val": 0.749,
+ "bucket": "orange"
+ }
+}
\ No newline at end of file
diff --git a/scripts/artmura-onboard/extract_colors.py b/scripts/artmura-onboard/extract_colors.py
new file mode 100644
index 0000000..090033c
--- /dev/null
+++ b/scripts/artmura-onboard/extract_colors.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python3
+"""Extract dominant color (hex + hue + coarse bucket) from each Artmura swatch image.
+Writes data/artmura-colors.json {sku: {hex, hue, bucket}} for the site to merge in."""
+import json, os, glob, colorsys
+from collections import Counter
+from PIL import Image
+
+HERE = os.path.dirname(os.path.abspath(__file__))
+IMG = os.path.join(HERE, "..", "..", ".newwall-ref", "artmura", "images")
+PKG = json.load(open(os.path.join(HERE, "data", "artmura.json")))
+
+def dominant(path):
+ im = Image.open(path).convert("RGB").resize((64, 64))
+ # quantize to 16 colors, take the most common that isn't near-pure-white background noise
+ q = im.quantize(colors=16).convert("RGB")
+ cnt = Counter(q.getdata())
+ ranked = cnt.most_common()
+ # prefer the most saturated/among top to avoid flat near-white winning on tonal swatches
+ def score(item):
+ (r, g, b), n = item
+ h, s, v = colorsys.rgb_to_hsv(r/255, g/255, b/255)
+ # weight count but boost saturated colors so the swatch's character shows
+ return n * (0.5 + s)
+ (r, g, b), _ = max(ranked[:6], key=score)
+ return r, g, b
+
+def bucket(h, s, v):
+ if v < 0.18: return "black"
+ if s < 0.12 and v > 0.85: return "white"
+ if s < 0.14: return "grey"
+ deg = h * 360
+ if deg < 16 or deg >= 345: return "red"
+ if deg < 45: return "orange" if v > 0.5 else "brown"
+ if deg < 70: return "gold"
+ if deg < 160: return "green"
+ if deg < 200: return "teal"
+ if deg < 255: return "blue"
+ if deg < 290: return "purple"
+ return "pink"
+
+out = {}
+for p in PKG["products"]:
+ sku = p["mfr_sku"]
+ cand = sorted(glob.glob(os.path.join(IMG, f"{sku}-1.*")) or glob.glob(os.path.join(IMG, f"{sku}-*.*")))
+ if not cand:
+ continue
+ r, g, b = dominant(cand[0])
+ h, s, v = colorsys.rgb_to_hsv(r/255, g/255, b/255)
+ out[sku] = {"hex": f"#{r:02x}{g:02x}{b:02x}", "hue": round(h*360, 1),
+ "sat": round(s, 3), "val": round(v, 3), "bucket": bucket(h, s, v)}
+
+open(os.path.join(HERE, "data", "artmura-colors.json"), "w").write(json.dumps(out, indent=1))
+from collections import Counter as C
+print(f"extracted {len(out)} colors")
+print("buckets:", dict(C(v["bucket"] for v in out.values()).most_common()))
← ecdd0e4 Artmura site (C/production-readiness): APCA hero fix + perf
·
back to Dw Yolo Loop
·
Artmura site (B): 'Pairs well with' recs + per-product JSON- 528cdf4 →