[object Object]

← back to Philipperomano

color buckets + colorway-hex + ENRICHED + junk-filter from kamatera (hunk 2)

30d1a8675485e5c43b6a49fc1c79ac0b5e4fd541 · 2026-05-21 18:52:42 -0700 · Steve Abrams

Files touched

Diff

commit 30d1a8675485e5c43b6a49fc1c79ac0b5e4fd541
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu May 21 18:52:42 2026 -0700

    color buckets + colorway-hex + ENRICHED + junk-filter from kamatera (hunk 2)
---
 server.js | 206 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 206 insertions(+)

diff --git a/server.js b/server.js
index 9f0b4da..d3e6d53 100644
--- a/server.js
+++ b/server.js
@@ -50,6 +50,212 @@ const NORM_TYPE = (t) => {
 
 const TYPES = [...new Set(DATA.map(p => NORM_TYPE(p.product_type)))].sort();
 
+// ─── Color buckets ────────────────────────────────────────────────────────
+// Extract the colorway from titles like "Pattern - Colorway Wallcovering | Phillipe Romano"
+// then map keywords to one of the buckets below.
+const COLOR_BUCKETS = [
+  { id: 'black',  label: 'Black',  hex: '#1a1a1a' },
+  { id: 'white',  label: 'White',  hex: '#f5f1ea' },
+  { id: 'cream',  label: 'Cream',  hex: '#e8d9c0' },
+  { id: 'beige',  label: 'Beige',  hex: '#cdb792' },
+  { id: 'brown',  label: 'Brown',  hex: '#7a4a23' },
+  { id: 'gray',   label: 'Gray',   hex: '#7c7c7c' },
+  { id: 'silver', label: 'Silver', hex: '#b8b8b8' },
+  { id: 'gold',   label: 'Gold',   hex: '#c9a14a' },
+  { id: 'red',    label: 'Red',    hex: '#a8242b' },
+  { id: 'pink',   label: 'Pink',   hex: '#d98ca7' },
+  { id: 'orange', label: 'Orange', hex: '#cc6b3a' },
+  { id: 'yellow', label: 'Yellow', hex: '#d8b94a' },
+  { id: 'green',  label: 'Green',  hex: '#5a7a4a' },
+  { id: 'blue',   label: 'Blue',   hex: '#3e5d80' },
+  { id: 'purple', label: 'Purple', hex: '#6e4a7a' },
+  { id: 'multi',  label: 'Multi',  hex: 'linear-gradient(135deg,#d98ca7,#c9a14a,#5a7a4a,#3e5d80)' },
+];
+
+const COLOR_KEYWORDS = {
+  black:  ['black','onyx','ebony','jet','noir','ink','coal','obsidian','raven'],
+  white:  ['white','ivory','snow','chalk','alabaster','porcelain','linen','milk','bone','swan','frost','vanilla'],
+  cream:  ['cream','ecru','natural','parchment','oat','oatmeal','wheat','flax','straw','buff','champagne','butter'],
+  beige:  ['beige','sand','tan','khaki','camel','taupe','stone','shell','desert','almond','sahara','dune','clay'],
+  brown:  ['brown','chocolate','espresso','mocha','java','walnut','cocoa','bronze','umber','sepia','chestnut','rust','cinnamon','copper','caramel','toast','hazel'],
+  gray:   ['gray','grey','slate','smoke','charcoal','ash','pewter','graphite','dove','fog','mist','steel','flint'],
+  silver:  ['silver','platinum','sterling','chrome','metallic','mercury','aluminum'],
+  gold:   ['gold','golden','brass','honey','amber','ochre','mustard','saffron','antique gold'],
+  red:    ['red','crimson','cherry','scarlet','burgundy','wine','garnet','ruby','brick','cardinal','poppy','vermillion','blood','sangria'],
+  pink:   ['pink','rose','blush','coral','salmon','peach','mauve','dusty rose','fuchsia','magenta','flamingo'],
+  orange: ['orange','tangerine','apricot','marigold','sunset','persimmon','spice','paprika','terracotta'],
+  yellow: ['yellow','citron','canary','dijon','primrose','lemon','sun','daffodil','goldenrod'],
+  green:  ['green','sage','olive','mint','seafoam','jade','emerald','forest','moss','celery','pistachio','fern','kelly','hunter','celadon','laurel','basil','sea','aqua green','grass','leaf','spring','meadow','willow','vert'],
+  blue:   ['blue','navy','indigo','sapphire','azure','cobalt','denim','peacock','turquoise','teal','aqua','sky','cerulean','royal','ocean','marine','cornflower','periwinkle','baltic','arctic','glacier','lagoon'],
+  purple: ['purple','violet','lavender','plum','aubergine','eggplant','orchid','lilac','iris','heather','wisteria','grape','amethyst','byzantine'],
+};
+const KW_TO_BUCKET = {};
+for (const [bucket, kws] of Object.entries(COLOR_KEYWORDS)) {
+  for (const kw of kws) KW_TO_BUCKET[kw] = bucket;
+}
+// Sort keys longest-first so multi-word matches win ("dusty rose" before "rose")
+const KW_ORDERED = Object.keys(KW_TO_BUCKET).sort((a, b) => b.length - a.length);
+
+function extractColorway(title) {
+  if (!title) return '';
+  // Strip trailing brand suffix
+  let t = title.replace(/\s*\|\s*Phillipe Romano.*$/i, '').trim();
+  // Pattern: "Pattern - Colorway Type"
+  const m = t.match(/-\s*(.+?)\s+(Wallcovering|Wallpaper|Fabric|Drapery|Natural|Velvet|Linen|Silk|Cotton|Sheer)/i);
+  if (m) return m[1].trim();
+  // Fallback: anything after the last hyphen
+  const idx = t.lastIndexOf(' - ');
+  if (idx > -1) return t.slice(idx + 3).trim();
+  return '';
+}
+
+function colorBucket(title) {
+  const cw = extractColorway(title).toLowerCase();
+  if (!cw) return null;
+  for (const kw of KW_ORDERED) {
+    if (cw.includes(kw)) return KW_TO_BUCKET[kw];
+  }
+  return null;
+}
+
+// Granular colorway-name → real hex lookup. Each entry maps a colorway keyword
+// (extracted from the title) to its actual hex code so the per-product dot
+// shows the product's own color, not the bucket's average.
+const COLORWAY_HEX = {
+  // Black / dark
+  black:'#1a1a1a', onyx:'#0f0f0f', ebony:'#181210', jet:'#0a0a0a', noir:'#161616',
+  ink:'#1a1d24', coal:'#2b2b2b', obsidian:'#0d0d0d', raven:'#1d1d1d',
+  // White / cream
+  white:'#f5f1ea', ivory:'#f1ead8', snow:'#fafafa', chalk:'#ece7d7',
+  alabaster:'#efe9d9', porcelain:'#f0e9d8', linen:'#e9dfc8', milk:'#efeae0',
+  bone:'#dccfb4', swan:'#f0ebde', frost:'#eaece9', vanilla:'#f3e7c4',
+  // Cream / oat
+  cream:'#e8d9c0', ecru:'#d9c8a3', natural:'#d6c39a', parchment:'#e1cd9c',
+  oat:'#d5c19a', oatmeal:'#cfbb96', wheat:'#d6b87b', flax:'#cdb789',
+  straw:'#d6b87b', buff:'#cdb287', champagne:'#e6cfa5', butter:'#e7d496',
+  // Beige / tan
+  beige:'#cdb792', sand:'#c8b48a', tan:'#b89460', khaki:'#a78d63',
+  camel:'#a87d4f', taupe:'#9c8470', stone:'#a89682', shell:'#dcc7a7',
+  desert:'#cdaa7d', almond:'#d2b48c', sahara:'#c8a877', dune:'#c5b18a',
+  clay:'#a96a4a',
+  // Brown
+  brown:'#7a4a23', chocolate:'#3d220e', espresso:'#3a2418', mocha:'#6f4e2a',
+  java:'#3b2618', walnut:'#5b3a1f', cocoa:'#4a3122', bronze:'#7a522d',
+  umber:'#5e3a1f', sepia:'#704d23', chestnut:'#714028', rust:'#9b4a1a',
+  cinnamon:'#8a4a22', copper:'#b35a23', caramel:'#a06a35', toast:'#9a6a44',
+  hazel:'#8c6238',
+  // Gray
+  gray:'#7c7c7c', grey:'#7c7c7c', slate:'#535e69', smoke:'#7a797a',
+  charcoal:'#36383b', ash:'#92918f', pewter:'#7d7e80', graphite:'#3b3d3f',
+  dove:'#a09e98', fog:'#8b8b8a', mist:'#c2c1bd', steel:'#5b6770', flint:'#5d6065',
+  // Silver
+  silver:'#b8b8b8', platinum:'#c8c8c5', sterling:'#bcbab5', chrome:'#d4d4d4',
+  metallic:'#a9a9aa', mercury:'#bfbebb', aluminum:'#c5c4c0',
+  // Gold / yellow-warm
+  gold:'#c9a14a', golden:'#c9a14a', brass:'#a78436', honey:'#cf9f3a',
+  amber:'#c5862e', ochre:'#a87523', mustard:'#c69a36', saffron:'#d09b25',
+  // Red
+  red:'#a8242b', crimson:'#7e1923', cherry:'#a8212e', scarlet:'#a01828',
+  burgundy:'#5c1a22', wine:'#5a1e23', garnet:'#621a22', ruby:'#841a26',
+  brick:'#8a4035', cardinal:'#94232b', poppy:'#bd2c30', vermillion:'#c0331a',
+  blood:'#5a1216', sangria:'#6e1d24',
+  // Pink
+  pink:'#d98ca7', rose:'#c97189', blush:'#e0bbb3', coral:'#cf6f5e',
+  salmon:'#dc8b76', peach:'#e3a987', mauve:'#a98091', fuchsia:'#bf2c6e',
+  magenta:'#a02963', flamingo:'#df6e83',
+  // Orange
+  orange:'#cc6b3a', tangerine:'#d8722a', apricot:'#daa177', marigold:'#d2952c',
+  sunset:'#cc6f3a', persimmon:'#c25827', spice:'#a35430', paprika:'#a8431f',
+  terracotta:'#a35a3b',
+  // Yellow
+  yellow:'#d8b94a', citron:'#c7c247', canary:'#dfc83a', dijon:'#9a7d23',
+  primrose:'#dac86b', lemon:'#d6c63b', sun:'#d4b22c', daffodil:'#d8b820',
+  goldenrod:'#b88a25',
+  // Green
+  green:'#5a7a4a', sage:'#9aa687', olive:'#7a7035', mint:'#9bc8a6',
+  seafoam:'#82bda1', jade:'#3f7361', emerald:'#235a3c', forest:'#264a30',
+  moss:'#5c6c3a', celery:'#a8b274', pistachio:'#9bbf72', fern:'#4a6a3b',
+  kelly:'#3b6a31', hunter:'#28482e', celadon:'#a4b89a', laurel:'#4f6b3e',
+  basil:'#566c34', sea:'#5e8e7a', grass:'#5a7e2c', leaf:'#598a3a',
+  spring:'#7ab83a', meadow:'#618d44', willow:'#869c6e', vert:'#3e6a3a',
+  // Blue
+  blue:'#3e5d80', navy:'#1c2a44', indigo:'#202852', sapphire:'#1f3a78',
+  azure:'#2c5e90', cobalt:'#1c4f8e', denim:'#3d6391', peacock:'#1c5d6c',
+  turquoise:'#2db1b8', teal:'#226e72', aqua:'#5fb4b6', sky:'#7ea8c8',
+  cerulean:'#2c5e8a', royal:'#1c358a', ocean:'#1f4d6d', marine:'#1f3d68',
+  cornflower:'#6a82bb', periwinkle:'#9aa4d2', baltic:'#365a78',
+  arctic:'#a8c5d2', glacier:'#a4c4cf', lagoon:'#357a86',
+  // Purple
+  purple:'#6e4a7a', violet:'#5b3a78', lavender:'#aa9bcb', plum:'#5b2a4a',
+  aubergine:'#3a1f33', eggplant:'#3d2436', orchid:'#a3679a', lilac:'#b69dca',
+  iris:'#6b4f99', heather:'#9a8aa8', wisteria:'#a48ac4', grape:'#552864',
+  amethyst:'#7e4d9c', byzantine:'#7a3784',
+};
+const COLORWAY_HEX_KEYS = Object.keys(COLORWAY_HEX).sort((a, b) => b.length - a.length);
+
+function colorHex(title) {
+  const cw = extractColorway(title).toLowerCase();
+  if (!cw) return null;
+  for (const kw of COLORWAY_HEX_KEYS) {
+    if (cw.includes(kw)) return COLORWAY_HEX[kw];
+  }
+  return null;
+}
+
+// Annotate every product with bucket + dwsku numeric (for sort)
+const skuNum = sku => {
+  const m = String(sku || '').match(/(\d+)/);
+  return m ? parseInt(m[1], 10) : 0;
+};
+// Fallback for products where dw_sku is null in JSON: extract DWxx-NNNNNN from the handle.
+function effectiveSku(p) {
+  if (p.dw_sku) return p.dw_sku;
+  const m = (p.handle || '').match(/(dw[a-z]{2,4}-\d+)$/i);
+  return m ? m[1].toUpperCase() : '';
+}
+const ENRICHED = DATA.map(p => {
+  const sku = effectiveSku(p);
+  return {
+    ...p,
+    dw_sku: sku || p.dw_sku,
+    _color: colorBucket(p.title),
+    _hex: colorHex(p.title),
+    _skuNum: skuNum(sku)
+  };
+});
+// Default order: newest first (highest DWxx sku numeric desc)
+ENRICHED.sort((a, b) => b._skuNum - a._skuNum);
+
+
+// --- Junk-product defense (codified in /dw-site-build) -------------------
+// Drop rows that don't belong on a textile-brand storefront even though
+// the dw_unified replica returned them under vendor='Phillipe Romano'.
+// Causes: variant_id leaked into dw_sku column, mis-tagged product_type,
+// cross-vendor catalog corruption.
+const JUNK_TITLE_RE = /\b(lamp|tripod|rug|pillow|throw|frame|mirror|vase|candle|sculpture|figurine)\b/i;
+const ALLOWED_TYPES = new Set([
+  'Wallcovering','Wallcoverings','Commercial Wallcovering','Commercial Wallcoverings',
+  'Natural Wallcovering','Naturals','Fabric','Fabrics','Drapery Fabric','Drapery'
+]);
+function isJunk(p) {
+  if (!p.image_url || !String(p.image_url).trim()) return true;
+  const sku = String(p.dw_sku || '').trim();
+  if (!sku) return true;
+  if (/^\d{13,}$/.test(sku)) return true;            // variant_id leaked into sku
+  if (!ALLOWED_TYPES.has(p.product_type || '')) return true;
+  if (JUNK_TITLE_RE.test(p.title || '')) return true;
+  return false;
+}
+const _kept = ENRICHED.filter(p => !isJunk(p));
+const _dropped = ENRICHED.length - _kept.length;
+console.log(`[junk-filter] kept=${_kept.length} dropped=${_dropped}`);
+ENRICHED.length = 0;
+ENRICHED.push(..._kept);
+// --- end junk-product defense -------------------------------------------
+
+const COLOR_COUNTS = {};
+for (const p of ENRICHED) if (p._color) COLOR_COUNTS[p._color] = (COLOR_COUNTS[p._color] || 0) + 1;
+
 const esc = s => String(s||'').replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
 
 function paginate(arr, page, perPage = 60) {

← 03b56e8 contacts: import DW_PHONE/PR_EMAIL constants from kamatera (  ·  back to Philipperomano  ·  Shopify fetch + cache + categorizeTags + safeBodyHtml + memo 00974f4 →