/** * Mapowanie odcienia tkaniny na wartość predefiniowanego słownika * parametru „Kolor” w BaseLinkerze. Słownik odrzuca dowolne wartości * (handlowe nazwy odcieni typu „Grey 14”), więc wysyłamy kategorię * ogólną, a kod handlowy idzie w osobnym parametrze. * Kolejność: nazwa odcienia → color_hex (HSL) → „Inny kolor”. */ /** Wartości słownika obserwowane na żywo w inwentarzu Vilmax (2026-09). */ export const BASE_COLOR_VALUES = [ "Antracyt", "Beżowy", "Biały", "Brązowy", "Czarny", "Inny kolor", "Kremowy", "Natura", "Niebieski", "Różowy", "Srebrny", "Szary", "Wielokolorowy", "Zielony", ] as const; const NAME_RULES: Array<[RegExp, string]> = [ [/antracyt|anthracite|grafit|graphite|charcoal/i, "Antracyt"], [/ecru|be[zż]|beige|karmel|caramel|sand|natu|camel|latte|mocca|moka|cappuccino/i, "Beżowy"], [/krem|cream|ivory|ko[sś][cć]|wanili|vanilla/i, "Kremowy"], [/biel|white|blanc|bia[lł]/i, "Biały"], [/br[aą]z|brown|marron|choco|orzech|walnut|wenge/i, "Brązowy"], [/czarn|black|noir/i, "Czarny"], [/srebr|silver/i, "Srebrny"], [/szar|grey|gray|gri(z|s)/i, "Szary"], [/zielon|green|vert|oliw|olive|mi[eę]t|mint|butelk|bottle|emerald|sza[lł]wi|sage/i, "Zielony"], [/niebiesk|blue|bleu|granat|navy|indygo|indigo|denim/i, "Niebieski"], [/r[oó][zż]|pink|rose|pudr|blush/i, "Różowy"], ]; function hexToHsl(hex: string): { h: number; s: number; l: number } | null { const m = hex.trim().match(/^#?([0-9a-f]{6})$/i); if (!m) return null; const n = parseInt(m[1]!, 16); const r = ((n >> 16) & 255) / 255, g = ((n >> 8) & 255) / 255, b = (n & 255) / 255; const max = Math.max(r, g, b), min = Math.min(r, g, b); const l = (max + min) / 2; if (max === min) return { h: 0, s: 0, l }; const d = max - min; const s = l > 0.5 ? d / (2 - max - min) : d / (max + min); const h = max === r ? ((g - b) / d + (g < b ? 6 : 0)) * 60 : max === g ? ((b - r) / d + 2) * 60 : ((r - g) / d + 4) * 60; return { h, s, l }; } /** Kategoria ogólna ze składowych HSL próbki — meble tapicerowane. */ function colorFromHex(hex: string): string { const hsl = hexToHsl(hex); if (!hsl) return "Inny kolor"; const { h, s, l } = hsl; if (l <= 0.13) return "Czarny"; if (s < 0.1) { if (l >= 0.85) return "Biały"; if (l >= 0.62) return "Kremowy"; // ciepłe neutralne (taupe, popielaty brąz) → rodzina brązu/beżu, nie szarości if (h >= 20 && h < 50) return l >= 0.5 ? "Beżowy" : "Brązowy"; if (l <= 0.3) return "Antracyt"; return "Szary"; } if (s < 0.22 && l >= 0.6) return "Beżowy"; if (h < 20 || h >= 340) { if (l < 0.35 && h >= 8) return "Brązowy"; // ciemne ciepłe (gorzka czekolada) → brąz return l >= 0.6 && s < 0.6 ? "Różowy" : "Inny kolor"; } if (h < 48) { // pasma pomarańczowo-brązowe: jasne mało nasycone → beż, reszta → brąz if (l < 0.38) return "Brązowy"; if (s < 0.4 || l > 0.65) return "Beżowy"; return "Brązowy"; } if (h < 75) return s > 0.35 && l < 0.45 ? "Brązowy" : "Beżowy"; if (h < 165) return "Zielony"; if (h < 255) return "Niebieski"; return l >= 0.6 && s < 0.5 ? "Różowy" : "Inny kolor"; } /** * Kategoria „Kolor” dla odcienia. Najpierw słowa kluczowe z nazwy * handlowej (deklaracja producenta), potem klasyfikacja color_hex. */ export function baseColorForShade(shade: { name?: string | null; color_hex?: string | null; code?: string | null }): string { const haystack = [shade.name, shade.code].filter(Boolean).join(" "); for (const [re, color] of NAME_RULES) if (re.test(haystack)) return color; if (shade.color_hex) return colorFromHex(shade.color_hex); return "Inny kolor"; }