← back to Dw Theme Boost Fix
assets/sbc-interactive.js
955 lines
{
"@context": "https://schema.org",
"@type": "CollectionPage",
"name": "Shop Wallpaper by Color",
"description": "Browse luxury wallcoverings and wallpapers by color. Curated collections in blue, gold, green, neutral, coral, and more for interior designers and homeowners.",
"url": "https://www.designerwallcoverings.com/pages/shop-by-color",
"publisher": {
"@type": "Organization",
"name": "Designer Wallcoverings",
"url": "https://www.designerwallcoverings.com"
},
"hasPart": [
{
"@type": "ItemList",
"name": "Color Collections",
"itemListElement": [
{"@type": "ListItem", "position": 1, "name": "Blue Wallpaper", "url": "https://www.designerwallcoverings.com/collections/blue-wallpaper-collection"},
{"@type": "ListItem", "position": 2, "name": "Turquoise & Teal Wallpaper", "url": "https://www.designerwallcoverings.com/collections/turquoise"},
{"@type": "ListItem", "position": 3, "name": "Green Wallpaper", "url": "https://www.designerwallcoverings.com/collections/green-wallpaper-collection"},
{"@type": "ListItem", "position": 4, "name": "Yellow Wallpaper", "url": "https://www.designerwallcoverings.com/collections/yellow-wallpaper-collection"},
{"@type": "ListItem", "position": 5, "name": "Gold & Metallic Wallpaper", "url": "https://www.designerwallcoverings.com/collections/gold-wallpaper"},
{"@type": "ListItem", "position": 6, "name": "Orange Wallpaper", "url": "https://www.designerwallcoverings.com/collections/orange"},
{"@type": "ListItem", "position": 7, "name": "Coral & Pink Wallpaper", "url": "https://www.designerwallcoverings.com/collections/coral"},
{"@type": "ListItem", "position": 8, "name": "Red Wallpaper", "url": "https://www.designerwallcoverings.com/collections/red-wallpaper-collection"},
{"@type": "ListItem", "position": 9, "name": "Purple Wallpaper", "url": "https://www.designerwallcoverings.com/collections/purple"},
{"@type": "ListItem", "position": 10, "name": "Brown Wallpaper", "url": "https://www.designerwallcoverings.com/collections/brown-wallpaper-collection"},
{"@type": "ListItem", "position": 11, "name": "Beige & Cream Wallpaper", "url": "https://www.designerwallcoverings.com/collections/beige-cream-wallpaper-collection"},
{"@type": "ListItem", "position": 12, "name": "White Wallpaper", "url": "https://www.designerwallcoverings.com/collections/white-wallpaper-collection"},
{"@type": "ListItem", "position": 13, "name": "Grey & Silver Wallpaper", "url": "https://www.designerwallcoverings.com/collections/grey-silver"},
{"@type": "ListItem", "position": 14, "name": "Black Wallpaper", "url": "https://www.designerwallcoverings.com/collections/black"},
{"@type": "ListItem", "position": 15, "name": "Multi-Color Wallpaper", "url": "https://www.designerwallcoverings.com/collections/multi"}
]
}
]
}
(function() {
'use strict';
var selectedColors = [];
var MAX_COLORS = 3;
var colorCollections = [
{ name: 'Blue', handle: '/collections/blue-wallpaper-collection', hueMin: 195, hueMax: 250, hex: '#3B6B9E', pantone: '19-4052 TCX', pantoneName: 'Classic Blue' },
{ name: 'Blue Green', handle: '/collections/turquoise', hueMin: 165, hueMax: 195, hex: '#2A9D8F', pantone: '17-5024 TCX', pantoneName: 'Teal' },
{ name: 'Green', handle: '/collections/green-wallpaper-collection', hueMin: 90, hueMax: 165, hex: '#5A7247', pantone: '18-0119 TCX', pantoneName: 'Willow Bough' },
{ name: 'Yellow', handle: '/collections/yellow-wallpaper-collection', hueMin: 55, hueMax: 90, hex: '#E8C840', pantone: '14-0848 TCX', pantoneName: 'Mimosa' },
{ name: 'Gold', handle: '/collections/gold-wallpaper', hueMin: 40, hueMax: 55, hex: '#C5A55A', pantone: '16-0947 TCX', pantoneName: 'Ochre' },
{ name: 'Orange', handle: '/collections/orange', hueMin: 15, hueMax: 40, hex: '#D4763B', pantone: '16-1350 TCX', pantoneName: 'Amberglow' },
{ name: 'Coral', handle: '/collections/coral', hueMin: 0, hueMax: 15, hex: '#E8725C', pantone: '16-1546 TCX', pantoneName: 'Living Coral' },
{ name: 'Coral Rose', handle: '/collections/coral', hueMin: 345, hueMax: 360, hex: '#D36070', pantone: '17-1736 TCX', pantoneName: 'Hot Pink' },
{ name: 'Red', handle: '/collections/red-wallpaper-collection', hueMin: 345, hueMax: 15, hex: '#B22234', pantone: '19-1763 TCX', pantoneName: 'Racing Red' },
{ name: 'Purple', handle: '/collections/purple', hueMin: 250, hueMax: 320, hex: '#6A4C93', pantone: '18-3838 TCX', pantoneName: 'Ultra Violet' },
{ name: 'Brown', handle: '/collections/brown-wallpaper-collection', hueMin: 20, hueMax: 40, hex: '#8B6C5C', pantone: '18-1222 TCX', pantoneName: 'Cocoa Brown', isBrown: true },
{ name: 'Beige & Cream', handle: '/collections/beige-cream-wallpaper-collection', hueMin: 30, hueMax: 50, hex: '#D9C9A5', pantone: '14-1025 TCX', pantoneName: 'Chamois', isNeutral: true },
{ name: 'White', handle: '/collections/white-wallpaper-collection', hueMin: 0, hueMax: 360, hex: '#F5F0E8', pantone: '11-0602 TCX', pantoneName: 'Pristine', isWhite: true },
{ name: 'Grey & Silver', handle: '/collections/grey-silver', hueMin: 0, hueMax: 360, hex: '#A0A0A0', pantone: '17-5104 TCX', pantoneName: 'Ultimate Gray', isGrey: true },
{ name: 'Black', handle: '/collections/black', hueMin: 0, hueMax: 360, hex: '#2C2C2C', pantone: '19-0303 TCX', pantoneName: 'Jet Black', isBlack: true },
{ name: 'Multi-Color', handle: '/collections/multi', hueMin: 0, hueMax: 360, hex: 'linear-gradient(135deg,#E8725C,#C5A55A,#5A7247,#3B6B9E)', isMulti: true, pantone: 'Multi', pantoneName: 'Multi-Color' }
];
var curatedSwatches = [
{ name: 'Midnight Navy', hex: '#1B2A4A', ref: '19-4024 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Classic Blue', hex: '#0F4C81', ref: '19-4052 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Cerulean', hex: '#9BB7D4', ref: '15-4020 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Airy Blue', hex: '#92B6D5', ref: '14-4122 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Serenity', hex: '#92A8D1', ref: '15-3919 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Powder Blue', hex: '#B0C4DE', ref: '14-4115 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Provence', hex: '#658DC6', ref: '17-3932 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Indigo Bunting', hex: '#4B6CB7', ref: '18-4244 TCX', family: 'blue', collection: '/collections/blue-wallpaper-collection' },
{ name: 'Forest Biome', hex: '#2D5A27', ref: '18-0135 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Willow Bough', hex: '#5A7247', ref: '18-0119 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Sage', hex: '#9CAF88', ref: '15-6316 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Celadon', hex: '#ACE1AF', ref: '14-6312 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Olive Branch', hex: '#6B6B47', ref: '18-0527 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Hunter Green', hex: '#335648', ref: '19-5513 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Moss', hex: '#8A9A5B', ref: '16-0430 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Eucalyptus', hex: '#5F8575', ref: '16-5907 TCX', family: 'green', collection: '/collections/green-wallpaper-collection' },
{ name: 'Tidewater', hex: '#2A9D8F', ref: '17-5024 TCX', family: 'teal', collection: '/collections/turquoise' },
{ name: 'Aqua Haze', hex: '#6ECFBD', ref: '14-5416 TCX', family: 'teal', collection: '/collections/turquoise' },
{ name: 'Deep Lake', hex: '#186A62', ref: '18-5121 TCX', family: 'teal', collection: '/collections/turquoise' },
{ name: 'Mineral Blue', hex: '#4E8B98', ref: '17-4716 TCX', family: 'teal', collection: '/collections/turquoise' },
{ name: 'Ochre', hex: '#C5A55A', ref: '16-0947 TCX', family: 'gold', collection: '/collections/gold-wallpaper' },
{ name: 'Honey Gold', hex: '#D4A937', ref: '15-1049 TCX', family: 'gold', collection: '/collections/gold-wallpaper' },
{ name: 'Antique Gold', hex: '#B8943E', ref: '16-0940 TCX', family: 'gold', collection: '/collections/gold-wallpaper' },
{ name: 'Champagne', hex: '#E8D5B5', ref: '13-1015 TCX', family: 'gold', collection: '/collections/gold-wallpaper' },
{ name: 'Buttercup', hex: '#FAD02E', ref: '13-0755 TCX', family: 'gold', collection: '/collections/gold-wallpaper' },
{ name: 'Amberglow', hex: '#D4763B', ref: '16-1350 TCX', family: 'orange', collection: '/collections/orange' },
{ name: 'Terracotta', hex: '#C4633A', ref: '17-1444 TCX', family: 'orange', collection: '/collections/orange' },
{ name: 'Burnt Sienna', hex: '#A0522D', ref: '18-1354 TCX', family: 'orange', collection: '/collections/orange' },
{ name: 'Apricot', hex: '#F5B895', ref: '14-1228 TCX', family: 'orange', collection: '/collections/orange' },
{ name: 'Living Coral', hex: '#FF6F61', ref: '16-1546 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Rose Quartz', hex: '#F7CAC9', ref: '13-1520 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Marsala', hex: '#964F4C', ref: '18-1438 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Dusty Cedar', hex: '#AD655F', ref: '17-1520 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Cranberry', hex: '#9B2335', ref: '19-1652 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Blush', hex: '#DE9DA5', ref: '14-1911 TCX', family: 'coral', collection: '/collections/coral' },
{ name: 'Racing Red', hex: '#B22234', ref: '19-1763 TCX', family: 'red', collection: '/collections/red-wallpaper-collection' },
{ name: 'Scarlet Sage', hex: '#C0362C', ref: '18-1564 TCX', family: 'red', collection: '/collections/red-wallpaper-collection' },
{ name: 'Chili Pepper', hex: '#9B1B30', ref: '19-1557 TCX', family: 'red', collection: '/collections/red-wallpaper-collection' },
{ name: 'Bossa Nova', hex: '#7B2D3A', ref: '19-1840 TCX', family: 'red', collection: '/collections/red-wallpaper-collection' },
{ name: 'Ultra Violet', hex: '#6A4C93', ref: '18-3838 TCX', family: 'purple', collection: '/collections/purple' },
{ name: 'Lavender', hex: '#9B8BB4', ref: '16-3810 TCX', family: 'purple', collection: '/collections/purple' },
{ name: 'Plum', hex: '#6C3461', ref: '19-2524 TCX', family: 'purple', collection: '/collections/purple' },
{ name: 'Amethyst', hex: '#845EC2', ref: '17-3628 TCX', family: 'purple', collection: '/collections/purple' },
{ name: 'Mimosa', hex: '#E8C840', ref: '14-0848 TCX', family: 'yellow', collection: '/collections/yellow-wallpaper-collection' },
{ name: 'Illuminating', hex: '#F5DF4D', ref: '13-0647 TCX', family: 'yellow', collection: '/collections/yellow-wallpaper-collection' },
{ name: 'Lemon Verbena', hex: '#F0E68C', ref: '12-0740 TCX', family: 'yellow', collection: '/collections/yellow-wallpaper-collection' },
{ name: 'Spicy Mustard', hex: '#D4A843', ref: '15-0953 TCX', family: 'yellow', collection: '/collections/yellow-wallpaper-collection' },
{ name: 'Cocoa Brown', hex: '#8B6C5C', ref: '18-1222 TCX', family: 'brown', collection: '/collections/brown-wallpaper-collection' },
{ name: 'Mocha Mousse', hex: '#A47764', ref: '17-1230 TCX', family: 'brown', collection: '/collections/brown-wallpaper-collection' },
{ name: 'Warm Taupe', hex: '#AF9483', ref: '16-1318 TCX', family: 'brown', collection: '/collections/brown-wallpaper-collection' },
{ name: 'Timber Wolf', hex: '#6B4226', ref: '19-1218 TCX', family: 'brown', collection: '/collections/brown-wallpaper-collection' },
{ name: 'Chamois', hex: '#D9C9A5', ref: '14-1025 TCX', family: 'neutral', collection: '/collections/beige-cream-wallpaper-collection' },
{ name: 'Almond Buff', hex: '#D2B48C', ref: '15-1119 TCX', family: 'neutral', collection: '/collections/beige-cream-wallpaper-collection' },
{ name: 'Ivory Cream', hex: '#F5F0E0', ref: '12-0812 TCX', family: 'neutral', collection: '/collections/beige-cream-wallpaper-collection' },
{ name: 'Sand Dollar', hex: '#D5C4A1', ref: '14-1116 TCX', family: 'neutral', collection: '/collections/beige-cream-wallpaper-collection' },
{ name: 'Ultimate Gray', hex: '#939597', ref: '17-5104 TCX', family: 'grey', collection: '/collections/grey-silver' },
{ name: 'Silver Lining', hex: '#C0C0C0', ref: '14-4102 TCX', family: 'grey', collection: '/collections/grey-silver' },
{ name: 'Pewter', hex: '#848482', ref: '17-4402 TCX', family: 'grey', collection: '/collections/grey-silver' },
{ name: 'Charcoal', hex: '#4A4A4A', ref: '19-0201 TCX', family: 'grey', collection: '/collections/grey-silver' },
{ name: 'Dove Grey', hex: '#B5B5B5', ref: '15-4003 TCX', family: 'grey', collection: '/collections/grey-silver' },
{ name: 'Pristine White', hex: '#F5F0E8', ref: '11-0602 TCX', family: 'white', collection: '/collections/white-wallpaper-collection' },
{ name: 'Star White', hex: '#F0EDE5', ref: '11-0601 TCX', family: 'white', collection: '/collections/white-wallpaper-collection' },
{ name: 'Whisper White', hex: '#EEEBE3', ref: '12-0601 TCX', family: 'white', collection: '/collections/white-wallpaper-collection' },
{ name: 'Jet Black', hex: '#2C2C2C', ref: '19-0303 TCX', family: 'black', collection: '/collections/black' },
{ name: 'Anthracite', hex: '#383838', ref: '19-0405 TCX', family: 'black', collection: '/collections/black' },
{ name: 'Obsidian', hex: '#1C1C1C', ref: '19-0000 TCX', family: 'black', collection: '/collections/black' },
{ name: 'Multi-Color', hex: '#E8725C', ref: 'Multi', family: 'multi', collection: '/collections/multi', isMulti: true }
];
var swatchFamilies = [
{ key: 'all', label: 'All' },{ key: 'blue', label: 'Blues' },{ key: 'green', label: 'Greens' },{ key: 'teal', label: 'Teals' },
{ key: 'gold', label: 'Golds' },{ key: 'orange', label: 'Oranges' },{ key: 'coral', label: 'Corals' },{ key: 'red', label: 'Reds' },
{ key: 'purple', label: 'Purples' },{ key: 'yellow', label: 'Yellows' },{ key: 'brown', label: 'Browns' },{ key: 'neutral', label: 'Neutrals' },
{ key: 'grey', label: 'Greys' },{ key: 'white', label: 'Whites' },{ key: 'black', label: 'Blacks' }
];
var moodPalettes = [
{ name: 'Coastal Calm', desc: 'Airy beach house serenity', note: 'Blue wallcoverings paired with sandy neutrals create the most-requested palette for coastal bedrooms and sunrooms.', colors: [{ hex: '#92B6D5', name: 'Airy Blue' },{ hex: '#D9C9A5', name: 'Sandy Beige' },{ hex: '#2A9D8F', name: 'Seafoam' },{ hex: '#F5F0E8', name: 'White' }]},
{ name: 'Modern Minimalist', desc: 'Clean lines, quiet luxury', note: 'Charcoal and silver metallic wallcoverings anchor modern living spaces where restraint becomes the statement.', colors: [{ hex: '#4A4A4A', name: 'Charcoal' },{ hex: '#F5F0E8', name: 'White' },{ hex: '#C0C0C0', name: 'Silver' },{ hex: '#2C2C2C', name: 'Black' }]},
{ name: 'Bohemian Warmth', desc: 'Eclectic earth tones', note: 'Terracotta and sage grasscloth bring organic warmth to dining rooms and creative studios.', colors: [{ hex: '#C4633A', name: 'Terracotta' },{ hex: '#C5A55A', name: 'Gold' },{ hex: '#9CAF88', name: 'Sage' },{ hex: '#A0522D', name: 'Burnt Sienna' }]},
{ name: 'Art Deco Glamour', desc: 'Bold geometric opulence', note: 'Black and gold wallpaper with geometric patterns transforms entryways and formal dining spaces.', colors: [{ hex: '#2C2C2C', name: 'Black' },{ hex: '#C5A55A', name: 'Gold' },{ hex: '#335648', name: 'Emerald' },{ hex: '#D9C9A5', name: 'Cream' }]},
{ name: 'Scandinavian Serenity', desc: 'Nordic simplicity', note: 'White and pale grey linen textures achieve the effortless Nordic aesthetic for nurseries and open-plan spaces.', colors: [{ hex: '#F5F0E8', name: 'White' },{ hex: '#B5B5B5', name: 'Pale Grey' },{ hex: '#DE9DA5', name: 'Blush' },{ hex: '#D2B48C', name: 'Almond' }]},
{ name: 'Rustic Country', desc: 'Warm heritage charm', note: 'Brown and olive wallcoverings with natural fiber textures bring warmth to country kitchens and libraries.', colors: [{ hex: '#8B6C5C', name: 'Cocoa Brown' },{ hex: '#D9C9A5', name: 'Cream' },{ hex: '#6B6B47', name: 'Olive' },{ hex: '#9B1B30', name: 'Burgundy' }]},
{ name: 'Tropical Paradise', desc: 'Vibrant island escape', note: 'Coral and teal wallpapers energize bathrooms, powder rooms, and accent walls with bold color.', colors: [{ hex: '#FF6F61', name: 'Coral' },{ hex: '#2A9D8F', name: 'Teal' },{ hex: '#E8C840', name: 'Yellow' },{ hex: '#6ECFBD', name: 'Turquoise' }]},
{ name: 'Classic Elegance', desc: 'Timeless sophistication', note: 'Navy and gold wallcoverings remain the top choice for studies, home offices, and formal living rooms.', colors: [{ hex: '#1B2A4A', name: 'Navy' },{ hex: '#C5A55A', name: 'Gold' },{ hex: '#9B1B30', name: 'Cranberry' },{ hex: '#D9C9A5', name: 'Cream' }]}
];
var seasonPalettes = [
{ name: 'Spring Garden', desc: 'Fresh blooms and new beginnings', note: 'Blush and celadon floral wallpapers are trending for bedrooms and nurseries this spring.', colors: [{ hex: '#DE9DA5', name: 'Blush' },{ hex: '#ACE1AF', name: 'Celadon' },{ hex: '#9B8BB4', name: 'Lavender' },{ hex: '#FAD02E', name: 'Buttercup' }]},
{ name: 'Summer Brights', desc: 'Sun-drenched vibrancy', note: 'Coral and teal create energizing accent walls for kitchens, bathrooms, and outdoor entertaining spaces.', colors: [{ hex: '#FF6F61', name: 'Living Coral' },{ hex: '#2A9D8F', name: 'Tidewater' },{ hex: '#E8C840', name: 'Mimosa' },{ hex: '#92B6D5', name: 'Airy Blue' }]},
{ name: 'Autumn Harvest', desc: 'Rich and warm earth tones', note: 'Burnt sienna and olive grasscloth wallcoverings bring autumnal warmth to dining rooms and libraries.', colors: [{ hex: '#D4763B', name: 'Amberglow' },{ hex: '#A0522D', name: 'Burnt Sienna' },{ hex: '#6B6B47', name: 'Olive' },{ hex: '#9B1B30', name: 'Cranberry' }]},
{ name: 'Winter Neutrals', desc: 'Cozy and serene sophistication', note: 'Silver metallic and powder blue wallpapers reflect winter light beautifully in north-facing rooms.', colors: [{ hex: '#4A4A4A', name: 'Charcoal' },{ hex: '#C0C0C0', name: 'Silver' },{ hex: '#B0C4DE', name: 'Powder Blue' },{ hex: '#F5F0E8', name: 'White' }]}
];
var tonalColumns = ['Red','Coral','Orange','Gold','Yellow','Green','Teal','Blue','Purple','Brown','Neutral','Grey','Black'];
var tonalData = {
light: ['#E8A0A0','#F5B895','#F0C08A','#E8D5B5','#F0E68C','#ACE1AF','#6ECFBD','#92B6D5','#C4B5D4','#D2B48C','#F5F0E0','#D0D0D0','#808080'],
medium: ['#B22234','#E8725C','#D4763B','#C5A55A','#E8C840','#5A7247','#2A9D8F','#3B6B9E','#6A4C93','#8B6C5C','#D9C9A5','#939597','#4A4A4A'],
dark: ['#7B2D3A','#964F4C','#A0522D','#8B7335','#9E8C20','#2D5A27','#186A62','#1B2A4A','#3D2B5A','#6B4226','#A89272','#5A5A5A','#1C1C1C']
};
var tonalNames = {
light: ['Rose','Apricot','Peach','Champagne','Lemon','Celadon','Aqua','Sky','Lilac','Tan','Ivory','Silver','Slate'],
medium: ['Red','Coral','Orange','Gold','Yellow','Green','Teal','Blue','Purple','Brown','Beige','Grey','Charcoal'],
dark: ['Burgundy','Marsala','Sienna','Bronze','Olive Gold','Forest','Deep Lake','Navy','Plum','Timber','Taupe','Dark Grey','Black']
};
function hslToHex(h, s, l) {
h /= 360; s /= 100; l /= 100;
var r, g, b;
if (s === 0) { r = g = b = l; } else {
var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3);
}
return '#' + [r, g, b].map(function(x) { var hex = Math.round(x * 255).toString(16); return hex.length === 1 ? '0' + hex : hex; }).join('');
}
function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; }
function hexToHsl(hex) {
hex = hex.replace('#', ''); var r = parseInt(hex.substring(0, 2), 16) / 255; var g = parseInt(hex.substring(2, 4), 16) / 255; var b = parseInt(hex.substring(4, 6), 16) / 255;
var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2;
if (max === min) { h = s = 0; } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6; else if (max === g) h = ((b - r) / d + 2) / 6; else h = ((r - g) / d + 4) / 6;
} return { h: Math.round(h * 360), s: Math.round(s * 100), l: Math.round(l * 100) };
}
function getColorName(hue, sat, light) {
if (light > 90) return 'White'; if (light < 12) return 'Black'; if (sat < 10) return light > 60 ? 'Silver' : 'Grey'; if (sat < 20 && light > 40 && light < 70) return 'Taupe';
var names = [[0,'Red'],[15,'Coral'],[30,'Orange'],[45,'Gold'],[60,'Yellow'],[80,'Lime'],[120,'Green'],[160,'Teal'],[180,'Cyan'],[210,'Azure'],[240,'Blue'],[270,'Indigo'],[290,'Violet'],[320,'Magenta'],[345,'Rose'],[360,'Red']];
for (var i = 0; i < names.length - 1; i++) { if (hue >= names[i][0] && hue < names[i + 1][0]) return names[i][1]; } return 'Red';
}
function matchHueToCollections(hue, sat, light) {
var matched = [];
if (sat < 10 || light > 90 || light < 12) { if (light > 85) matched.push(findCol('isWhite')); else if (light < 15) matched.push(findCol('isBlack')); else matched.push(findCol('isGrey')); return matched.filter(Boolean); }
if (sat < 40 && hue >= 15 && hue <= 50 && light < 55) matched.push(findCol('isBrown'));
if (sat < 40 && hue >= 25 && hue <= 55 && light >= 55) matched.push(findCol('isNeutral'));
colorCollections.forEach(function(col) { if (col.isWhite || col.isBlack || col.isGrey || col.isMulti || col.isBrown || col.isNeutral) return;
if (col.hueMin < col.hueMax) { if (hue >= col.hueMin && hue < col.hueMax) matched.push(col); } else { if (hue >= col.hueMin || hue < col.hueMax) matched.push(col); }
}); if (matched.length === 0) matched.push(findCol('isMulti')); return matched.filter(Boolean);
}
function findCol(prop) { for (var i = 0; i < colorCollections.length; i++) { if (colorCollections[i][prop]) return colorCollections[i]; } return null; }
function drawColorWheel() { /* Phase 3: CSS conic-gradient renders the wheel — no canvas needed */ }
function getColorFromWheel(w, h, x, y) {
var cx = w / 2, cy = h / 2, radius = Math.min(cx, cy) - 4;
var dx = x - cx, dy = y - cy; var dist = Math.sqrt(dx * dx + dy * dy);
if (dist > radius || dist < radius * 0.18) return null;
var angle = Math.atan2(dy, dx) * 180 / Math.PI; if (angle < 0) angle += 360;
var satRatio = (dist - radius * 0.25) / (radius * 0.75); satRatio = Math.max(0, Math.min(1, satRatio));
return { h: angle, s: 10 + satRatio * 85, l: 92 - satRatio * 62, hex: hslToHex(angle, 10 + satRatio * 85, 92 - satRatio * 62) };
}
function clearEl(el) { while (el.firstChild) el.removeChild(el.firstChild); }
function createEl(tag, attrs, text) {
var el = document.createElement(tag);
if (attrs) { var keys = Object.keys(attrs); for (var i = 0; i < keys.length; i++) { var k = keys[i];
if (k === 'className') el.className = attrs[k]; else if (k === 'style' && typeof attrs[k] === 'string') el.setAttribute('style', attrs[k]);
else if (k === 'href' || k === 'viewBox' || k === 'role' || k === 'type' || k.indexOf('data-') === 0 || k.indexOf('aria-') === 0) el.setAttribute(k, attrs[k]);
else el[k] = attrs[k];
}} if (text) el.textContent = text; return el;
}
function createSvgEl(tag, attrs) { var el = document.createElementNS('http://www.w3.org/2000/svg', tag); if (attrs) { var keys = Object.keys(attrs); for (var i = 0; i < keys.length; i++) el.setAttribute(keys[i], attrs[keys[i]]); } return el; }
/* ===== PRODUCT FETCH FUNCTIONS ===== */
var productFetchTimer = null;
var fetchedProductIds = {};
var productCache = {};
var CACHE_TTL = 5 * 60 * 1000;
var scrollAfterProducts = false;
function getCachedProducts(handle) {
var entry = productCache[handle];
if (entry && (Date.now() - entry.ts < CACHE_TTL)) return entry.products;
return null;
}
function setCachedProducts(handle, products) {
productCache[handle] = { products: products, ts: Date.now() };
}
function updateCountBadge(count) {
var badge = document.getElementById('sbc-product-count-badge');
if (!badge) return;
if (count > 0) {
badge.textContent = count + ' product' + (count > 1 ? 's' : '') + ' found';
badge.style.display = 'inline-block';
} else {
badge.style.display = 'none';
}
}
function showProductSkeletons() {
var zone = document.getElementById('sbc-products-zone');
var grid = document.getElementById('sbc-products-grid');
zone.style.display = 'block';
clearEl(grid);
for (var i = 0; i < 8; i++) {
var skel = createEl('div', { className: 'sbc-product-skeleton' });
skel.appendChild(createEl('div', { className: 'sbc-skeleton-img' }));
var textWrap = createEl('div', { className: 'sbc-skeleton-text' });
textWrap.appendChild(createEl('div', { className: 'sbc-skeleton-line' }));
textWrap.appendChild(createEl('div', { className: 'sbc-skeleton-line' }));
textWrap.appendChild(createEl('div', { className: 'sbc-skeleton-line' }));
skel.appendChild(textWrap);
grid.appendChild(skel);
}
}
function fetchProductsForColors() {
var zone = document.getElementById('sbc-products-zone');
if (selectedColors.length === 0) {
zone.style.display = 'none';
zone.style.borderColor = 'rgba(180,160,120,0.15)';
updateCountBadge(0);
return;
}
zone.style.borderColor = selectedColors[0].hex;
if (productFetchTimer) clearTimeout(productFetchTimer);
productFetchTimer = setTimeout(function() { doFetchProducts(); }, 300);
}
function doFetchProducts() {
var seen = {};
var allMatched = [];
for (var j = 0; j < selectedColors.length; j++) {
var hsl = hexToHsl(selectedColors[j].hex);
var matched = matchHueToCollections(hsl.h, hsl.s, hsl.l);
for (var k = 0; k < matched.length; k++) {
if (!seen[matched[k].handle]) {
seen[matched[k].handle] = true;
allMatched.push(matched[k]);
}
}
}
if (allMatched.length === 0) {
document.getElementById('sbc-products-zone').style.display = 'none';
updateCountBadge(0);
return;
}
showProductSkeletons();
fetchedProductIds = {};
var allProducts = [];
var remaining = allMatched.length;
var perCollection = Math.max(2, Math.floor(12 / allMatched.length));
for (var i = 0; i < allMatched.length; i++) {
(function(col) {
var cached = getCachedProducts(col.handle);
if (cached) {
var limited = cached.slice(0, perCollection);
for (var p = 0; p < limited.length; p++) {
if (!fetchedProductIds[limited[p].id]) {
fetchedProductIds[limited[p].id] = true;
limited[p]._fromCollection = col.name;
allProducts.push(limited[p]);
}
}
remaining--;
if (remaining <= 0) { renderProducts(allProducts, allMatched); updateCountBadge(allProducts.length); }
return;
}
var url = '/collections' + col.handle.replace('/collections', '') + '/products.json?limit=' + perCollection;
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
try {
var data = JSON.parse(xhr.responseText);
var products = data.products || [];
setCachedProducts(col.handle, products);
for (var p = 0; p < products.length; p++) {
if (!fetchedProductIds[products[p].id]) {
fetchedProductIds[products[p].id] = true;
products[p]._fromCollection = col.name;
allProducts.push(products[p]);
}
}
} catch (e) { /* parse error — skip this collection */ }
}
remaining--;
if (remaining <= 0) { renderProducts(allProducts, allMatched); updateCountBadge(allProducts.length); }
}
};
xhr.send();
})(allMatched[i]);
}
}
function renderProducts(products, collections) {
var zone = document.getElementById('sbc-products-zone');
var grid = document.getElementById('sbc-products-grid');
var countEl = document.getElementById('sbc-products-count');
var viewall = document.getElementById('sbc-products-viewall');
zone.style.display = 'block';
clearEl(grid);
clearEl(viewall);
if (products.length === 0) {
countEl.textContent = '';
var emptyMsg = createEl('div', { className: 'sbc-placeholder-msg' });
emptyMsg.appendChild(createEl('div', null, 'No products found for these colors.'));
emptyMsg.appendChild(createEl('div', { style: 'margin-top:8px;font-size:14px;color:#777;' }, 'Try selecting a different color or browse our collections below.'));
grid.appendChild(emptyMsg);
return;
}
countEl.textContent = products.length + ' product' + (products.length > 1 ? 's' : '');
var limit = Math.min(products.length, 16);
for (var i = 0; i < limit; i++) {
var p = products[i];
var imgSrc = (p.images && p.images.length > 0) ? p.images[0].src : '';
var price = (p.variants && p.variants.length > 0) ? p.variants[0].price : '';
var vendor = p.vendor || '';
var card = createEl('a', { className: 'sbc-product-card', href: '/products/' + p.handle });
if (imgSrc) {
var img = createEl('img', { className: 'sbc-product-img sbc-lazy' });
img.setAttribute('data-src', imgSrc.replace(/(\.(jpg|jpeg|png|gif|webp))(\?|$)/i, '_480x$1$3'));
img.alt = p.title;
img.onerror = function() { this.style.background = '#1a1a1a'; this.style.minHeight = '200px'; this.removeAttribute('src'); this.classList.remove('sbc-lazy'); };
card.appendChild(img);
} else {
card.appendChild(createEl('div', { className: 'sbc-product-img', style: 'background:#1a1a1a;min-height:200px;' }));
}
var info = createEl('div', { className: 'sbc-product-info' });
if (vendor) {
info.appendChild(createEl('div', { className: 'sbc-product-vendor' }, vendor));
}
info.appendChild(createEl('div', { className: 'sbc-product-title' }, p.title));
if (price && price !== '0.00') {
info.appendChild(createEl('div', { className: 'sbc-product-price' }, 'From $' + price));
}
info.appendChild(createEl('span', { className: 'sbc-product-sample' }, 'Request Sample'));
card.appendChild(info);
grid.appendChild(card);
}
for (var c = 0; c < collections.length; c++) {
var col = collections[c];
var vlink = createEl('a', { className: 'sbc-viewall-link', href: col.handle });
vlink.appendChild(document.createTextNode('View All ' + col.name));
vlink.appendChild(createEl('span', null, '\u2192'));
viewall.appendChild(vlink);
}
/* Phase 3: Observe new lazy images */
observeLazyImages();
if (scrollAfterProducts && products.length > 0) {
scrollAfterProducts = false;
setTimeout(function() {
zone.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 150);
}
}
/* ===== COLOR MANAGEMENT ===== */
function addColor(colorObj) {
if (selectedColors.length >= MAX_COLORS) return;
for (var i = 0; i < selectedColors.length; i++) { if (selectedColors[i].hex.toLowerCase() === colorObj.hex.toLowerCase()) return; }
selectedColors.push(colorObj); renderSelectionBar(); updateTonalHighlights(); fetchProductsForColors();
}
function removeColor(idx) { selectedColors.splice(idx, 1); renderSelectionBar(); updateTonalHighlights(); fetchProductsForColors(); }
function clearColors() { selectedColors = []; renderSelectionBar(); updateTonalHighlights(); fetchProductsForColors(); }
function detectHarmony(colors) {
if (colors.length < 2) return '';
var hues = [];
for (var i = 0; i < colors.length; i++) {
var hsl = colors[i].h !== undefined ? colors[i] : hexToHsl(colors[i].hex);
hues.push(hsl.h);
}
if (hues.length === 2) {
var diff = Math.abs(hues[0] - hues[1]); if (diff > 180) diff = 360 - diff;
if (diff > 160 && diff < 200) return 'Complementary pair';
if (diff < 40) return 'Analogous pair';
if (diff > 110 && diff < 140) return 'Triadic harmony';
}
if (hues.length === 3) {
var diffs = [];
for (var a = 0; a < 3; a++) { for (var b = a + 1; b < 3; b++) { var d = Math.abs(hues[a] - hues[b]); if (d > 180) d = 360 - d; diffs.push(d); } }
diffs.sort(function(x, y) { return x - y; });
if (diffs[0] < 50 && diffs[1] < 50) return 'Analogous trio';
if (diffs[0] > 100 && diffs[1] > 100 && diffs[2] > 100) return 'Triadic harmony';
var maxD = Math.max.apply(null, diffs);
if (maxD > 150) return 'High contrast palette';
}
return 'Custom palette';
}
function renderSelectionBar() {
var chipsRow = document.getElementById('sbc-chips-row'); var clearBtn = document.getElementById('sbc-clear-all');
var matchedBar = document.getElementById('sbc-matched-bar'); var matchedLinks = document.getElementById('sbc-matched-bar-links');
var limitMsg = document.getElementById('sbc-selection-limit'); var shopCta = document.getElementById('sbc-shop-cta');
var shopCtaSub = document.getElementById('sbc-shop-cta-sub'); clearEl(chipsRow);
if (selectedColors.length === 0) {
chipsRow.appendChild(createEl('span', { className: 'sbc-chips-empty' }, 'Click any color to start building your palette'));
clearBtn.style.display = 'none'; matchedBar.style.display = 'none'; shopCta.style.display = 'none'; limitMsg.textContent = 'Select up to 3 colors'; return;
}
clearBtn.style.display = selectedColors.length >= 2 ? 'inline-block' : 'none';
limitMsg.textContent = selectedColors.length + ' of 3 selected';
for (var i = 0; i < selectedColors.length; i++) {
(function(idx) {
var c = selectedColors[idx]; var chip = createEl('div', { className: 'sbc-color-chip' });
chip.appendChild(createEl('div', { className: 'sbc-chip-swatch', style: 'background-color: ' + c.hex }));
chip.appendChild(createEl('span', { className: 'sbc-chip-name' }, c.name));
chip.appendChild(createEl('span', { className: 'sbc-chip-hex' }, c.hex.toUpperCase()));
var rb = createEl('button', { className: 'sbc-chip-remove', 'aria-label': 'Remove ' + c.name }); rb.textContent = '\u00D7';
rb.addEventListener('click', function() { removeColor(idx); }); chip.appendChild(rb); chipsRow.appendChild(chip);
})(i);
}
if (selectedColors.length >= 2) {
var hint = detectHarmony(selectedColors);
if (hint) chipsRow.appendChild(createEl('span', { className: 'sbc-harmony-hint' }, hint));
}
var seen = {}; var allMatched = [];
for (var j = 0; j < selectedColors.length; j++) { var hsl = hexToHsl(selectedColors[j].hex); var matched = matchHueToCollections(hsl.h, hsl.s, hsl.l);
for (var k = 0; k < matched.length; k++) { if (!seen[matched[k].handle]) { seen[matched[k].handle] = true; allMatched.push(matched[k]); } } }
clearEl(matchedLinks);
if (allMatched.length > 0) {
shopCta.style.display = 'block';
shopCtaSub.textContent = allMatched.length + ' matching collection' + (allMatched.length > 1 ? 's' : '') + ' \u2014 request free swatches';
matchedBar.style.display = 'block';
for (var m = 0; m < allMatched.length; m++) { var col = allMatched[m];
var link = createEl('a', { className: 'sbc-matched-bar-link', href: col.handle });
var ls = createEl('div', { className: 'sbc-matched-link-swatch' }); if (col.isMulti) ls.style.background = col.hex; else ls.style.backgroundColor = col.hex;
link.appendChild(ls); link.appendChild(createEl('span', { className: 'sbc-matched-link-name' }, col.name));
link.appendChild(createEl('span', { className: 'sbc-matched-link-arrow' }, '\u2192')); matchedLinks.appendChild(link);
}
} else { matchedBar.style.display = 'none'; shopCta.style.display = 'none'; }
}
function initColorWheel() {
var el = document.getElementById('sbc-color-wheel'); if (!el) return;
var indicator = document.getElementById('sbc-wheel-indicator'); var hint = document.getElementById('sbc-wheel-hint');
function handleWheelClick(e) {
var rect = el.getBoundingClientRect(); var x = e.clientX - rect.left; var y = e.clientY - rect.top;
var color = getColorFromWheel(rect.width, rect.height, x, y); if (!color) return;
indicator.style.display = 'block'; indicator.style.left = x + 'px'; indicator.style.top = y + 'px'; indicator.style.backgroundColor = color.hex;
var name = getColorName(color.h, color.s, color.l);
if (selectedColors.length < MAX_COLORS) { addColor({ hex: color.hex, name: name, h: color.h, s: color.s, l: color.l }); hint.textContent = selectedColors.length >= MAX_COLORS ? 'Palette full (3 colors)' : 'Click to add another color'; }
else { hint.textContent = 'Palette full. Remove a color to add more.'; }
}
el.addEventListener('click', handleWheelClick);
el.addEventListener('touchend', function(e) { e.preventDefault(); var t = e.changedTouches[0]; handleWheelClick({ clientX: t.clientX, clientY: t.clientY }); });
}
var spectrumLightness = 50;
function drawSpectrumBar() {
var canvas = document.getElementById('sbc-spectrum-canvas'); if (!canvas) return;
var ctx = canvas.getContext('2d'); var w = canvas.width, h = canvas.height; ctx.clearRect(0, 0, w, h);
for (var x = 0; x < w; x++) { ctx.fillStyle = hslToHex((x / w) * 360, 85, spectrumLightness); ctx.fillRect(x, 0, 1, h); }
}
function initSpectrum() {
var canvas = document.getElementById('sbc-spectrum-canvas'); if (!canvas) return; drawSpectrumBar();
var indicator = document.getElementById('sbc-spectrum-indicator'); var slider = document.getElementById('sbc-lightness-slider');
function handleSpectrumClick(e) {
var wrap = document.getElementById('sbc-spectrum-canvas-wrap'); var rect = wrap.getBoundingClientRect();
var ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)); var hue = ratio * 360;
var hex = hslToHex(hue, 85, spectrumLightness); var name = getColorName(hue, 85, spectrumLightness);
indicator.style.display = 'block'; indicator.style.left = (ratio * 100) + '%';
if (selectedColors.length < MAX_COLORS) { addColor({ hex: hex, name: name, h: hue, s: 85, l: spectrumLightness }); }
}
var wrap = document.getElementById('sbc-spectrum-canvas-wrap'); wrap.addEventListener('click', handleSpectrumClick);
wrap.addEventListener('touchend', function(e) { e.preventDefault(); handleSpectrumClick({ clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY }); });
slider.addEventListener('input', function() { spectrumLightness = parseInt(this.value); drawSpectrumBar(); });
}
function initTonalGrid() {
var grid = document.getElementById('sbc-tonal-grid'); if (!grid) return; clearEl(grid);
grid.appendChild(createEl('div', { className: 'sbc-tonal-header' }, ''));
for (var c = 0; c < tonalColumns.length; c++) { grid.appendChild(createEl('div', { className: 'sbc-tonal-header' }, tonalColumns[c])); }
var rows = ['light', 'medium', 'dark']; var rowLabels = ['Light', 'Medium', 'Dark'];
for (var r = 0; r < rows.length; r++) { var rowKey = rows[r]; grid.appendChild(createEl('div', { className: 'sbc-tonal-row-label' }, rowLabels[r]));
for (var ci = 0; ci < tonalData[rowKey].length; ci++) { (function(hex, name) {
var cell = createEl('div', { className: 'sbc-tonal-cell', style: 'background-color: ' + hex, 'data-hex': hex, 'data-name': name, 'aria-label': name + ' ' + hex });
cell.addEventListener('click', function() { if (selectedColors.length < MAX_COLORS) { var hsl = hexToHsl(hex); addColor({ hex: hex, name: name, h: hsl.h, s: hsl.s, l: hsl.l }); } });
grid.appendChild(cell);
})(tonalData[rowKey][ci], tonalNames[rowKey][ci]); }
}
}
function updateTonalHighlights() {
var cells = document.querySelectorAll('.sbc-tonal-cell');
for (var i = 0; i < cells.length; i++) { var ch = cells[i].getAttribute('data-hex'); var isSel = false;
for (var j = 0; j < selectedColors.length; j++) { if (selectedColors[j].hex.toLowerCase() === ch.toLowerCase()) { isSel = true; break; } }
if (isSel) cells[i].classList.add('selected'); else cells[i].classList.remove('selected');
}
var cards = document.querySelectorAll('.sbc-palette-card');
for (var k = 0; k < cards.length; k++) { var cardH = cards[k].getAttribute('data-hex'); if (!cardH) continue; var cSel = false;
for (var m = 0; m < selectedColors.length; m++) { if (selectedColors[m].hex.toLowerCase() === cardH.toLowerCase()) { cSel = true; break; } }
if (cSel) cards[k].classList.add('selected'); else cards[k].classList.remove('selected');
}
}
var harmonyState = { baseHue: null, baseSat: 75, baseLight: 50, mode: 'complementary' };
function getHarmonyColors(hue, mode) {
var c = [{ h: hue, role: 'Base' }];
if (mode === 'complementary') c.push({ h: (hue + 180) % 360, role: 'Complement' });
else if (mode === 'triadic') { c.push({ h: (hue + 120) % 360, role: 'Triadic A' }); c.push({ h: (hue + 240) % 360, role: 'Triadic B' }); }
else if (mode === 'analogous') { c.push({ h: (hue + 30) % 360, role: 'Analogous +' }); c.push({ h: (hue - 30 + 360) % 360, role: 'Analogous -' }); }
else if (mode === 'split') { c.push({ h: (hue + 150) % 360, role: 'Split A' }); c.push({ h: (hue + 210) % 360, role: 'Split B' }); }
return c;
}
function initHarmonyWheel() {
var el = document.getElementById('sbc-harmony-wheel'); if (!el) return;
function handleClick(e) {
var rect = el.getBoundingClientRect(); var x = e.clientX - rect.left; var y = e.clientY - rect.top;
var color = getColorFromWheel(rect.width, rect.height, x, y); if (!color) return;
harmonyState.baseHue = color.h; harmonyState.baseSat = color.s; harmonyState.baseLight = color.l; updateHarmony();
}
el.addEventListener('click', handleClick);
el.addEventListener('touchend', function(e) { e.preventDefault(); var t = e.changedTouches[0]; handleClick({ clientX: t.clientX, clientY: t.clientY }); });
var modeBtns = document.querySelectorAll('.sbc-harmony-mode-btn');
for (var i = 0; i < modeBtns.length; i++) { (function(btn) { btn.addEventListener('click', function() {
for (var j = 0; j < modeBtns.length; j++) modeBtns[j].classList.remove('active');
btn.classList.add('active'); harmonyState.mode = btn.getAttribute('data-harmony'); if (harmonyState.baseHue !== null) updateHarmony();
}); })(modeBtns[i]); }
document.getElementById('sbc-harmony-add-btn').addEventListener('click', function() {
if (harmonyState.baseHue === null) return; var colors = getHarmonyColors(harmonyState.baseHue, harmonyState.mode); clearColors();
var limit = Math.min(colors.length, MAX_COLORS);
for (var ci = 0; ci < limit; ci++) { var hex = hslToHex(colors[ci].h, harmonyState.baseSat, harmonyState.baseLight);
addColor({ hex: hex, name: getColorName(colors[ci].h, harmonyState.baseSat, harmonyState.baseLight), h: colors[ci].h, s: harmonyState.baseSat, l: harmonyState.baseLight }); }
});
}
function updateHarmony() {
if (harmonyState.baseHue === null) return;
var colors = getHarmonyColors(harmonyState.baseHue, harmonyState.mode);
var el = document.getElementById('sbc-harmony-wheel'); var rect = el.getBoundingClientRect(); var w = rect.width, h = rect.height;
var cx = w / 2, cy = h / 2, radius = (Math.min(cx, cy) - 4) * 0.7;
var svg = document.getElementById('sbc-harmony-svg'); var wrap = el.parentElement;
var oldDots = wrap.querySelectorAll('.sbc-harmony-dot'); for (var d = 0; d < oldDots.length; d++) oldDots[d].parentNode.removeChild(oldDots[d]);
clearEl(svg); svg.setAttribute('viewBox', '0 0 ' + w + ' ' + h);
var points = []; for (var pi = 0; pi < colors.length; pi++) { var rad = colors[pi].h * Math.PI / 180; points.push({ x: cx + radius * Math.cos(rad), y: cy + radius * Math.sin(rad), h: colors[pi].h, role: colors[pi].role }); }
if (points.length >= 2) { var ptStr = ''; for (var pp = 0; pp < points.length; pp++) { ptStr += points[pp].x + ',' + points[pp].y; if (pp < points.length - 1) ptStr += ' '; }
svg.appendChild(createSvgEl('polygon', { points: ptStr, fill: 'rgba(180,160,120,0.06)', stroke: 'rgba(180,160,120,0.3)', 'stroke-width': '1.5', 'stroke-dasharray': '4,4' })); }
var scaleX = 1; var scaleY = 1;
for (var di = 0; di < points.length; di++) { var dot = createEl('div', { className: 'sbc-harmony-dot ' + (di === 0 ? 'primary' : 'secondary') });
dot.style.left = (points[di].x * scaleX) + 'px'; dot.style.top = (points[di].y * scaleY) + 'px';
dot.style.backgroundColor = hslToHex(points[di].h, harmonyState.baseSat, harmonyState.baseLight); wrap.appendChild(dot); }
var palC = document.getElementById('sbc-harmony-palette'); clearEl(palC);
for (var ci = 0; ci < colors.length; ci++) { var hex = hslToHex(colors[ci].h, harmonyState.baseSat, harmonyState.baseLight);
var item = createEl('div', { className: 'sbc-harmony-swatch-item' });
item.appendChild(createEl('div', { className: 'sbc-harmony-swatch-box', style: 'background-color: ' + hex }));
item.appendChild(createEl('div', { className: 'sbc-harmony-swatch-label' }, colors[ci].role));
item.appendChild(createEl('div', { className: 'sbc-harmony-swatch-hex' }, hex.toUpperCase())); palC.appendChild(item); }
document.getElementById('sbc-harmony-add-btn').style.display = 'block'; renderHarmonyResults(colors);
}
function renderHarmonyResults(colors) {
var container = document.getElementById('sbc-harmony-results'); clearEl(container);
container.appendChild(createEl('div', { className: 'sbc-harmony-results-title' }, 'Coordinating Collections'));
var grid = createEl('div', { className: 'sbc-harmony-collection-grid' }); var seen = {};
for (var i = 0; i < colors.length; i++) { var hex = hslToHex(colors[i].h, harmonyState.baseSat, harmonyState.baseLight); var hsl = hexToHsl(hex);
var matched = matchHueToCollections(hsl.h, hsl.s, hsl.l);
for (var j = 0; j < matched.length; j++) { var col = matched[j]; if (seen[col.handle]) continue; seen[col.handle] = true;
var card = createEl('a', { className: 'sbc-harmony-col-card', href: col.handle });
var sw = createEl('div', { className: 'sbc-harmony-col-swatch' }); sw.style.backgroundColor = col.hex; card.appendChild(sw);
var info = createEl('div'); info.appendChild(createEl('div', { className: 'sbc-harmony-col-name' }, col.name));
info.appendChild(createEl('div', { className: 'sbc-harmony-col-role' }, colors[i].role + ' match')); card.appendChild(info); grid.appendChild(card);
}
} container.appendChild(grid);
}
function buildMoodCard(palette) {
var card = createEl('div', { className: 'sbc-mood-card' }); var colorsRow = createEl('div', { className: 'sbc-mood-colors' });
for (var i = 0; i < palette.colors.length; i++) colorsRow.appendChild(createEl('div', { className: 'sbc-mood-circle', style: 'background-color: ' + palette.colors[i].hex }));
card.appendChild(colorsRow); card.appendChild(createEl('div', { className: 'sbc-mood-name' }, palette.name)); card.appendChild(createEl('div', { className: 'sbc-mood-desc' }, palette.desc));
if (palette.note) card.appendChild(createEl('div', { className: 'sbc-mood-note' }, palette.note));
var btn = createEl('button', { className: 'sbc-shop-palette-btn' }, 'Try This Palette');
btn.addEventListener('click', function() { clearColors(); var limit = Math.min(palette.colors.length, MAX_COLORS);
for (var j = 0; j < limit; j++) { var hsl = hexToHsl(palette.colors[j].hex); addColor({ hex: palette.colors[j].hex, name: palette.colors[j].name, h: hsl.h, s: hsl.s, l: hsl.l }); }
scrollAfterProducts = true;
}); card.appendChild(btn); return card;
}
function initMoodPalettes() {
var mg = document.getElementById('sbc-mood-grid'); var sg = document.getElementById('sbc-season-grid'); if (!mg || !sg) return;
for (var i = 0; i < moodPalettes.length; i++) mg.appendChild(buildMoodCard(moodPalettes[i]));
for (var j = 0; j < seasonPalettes.length; j++) sg.appendChild(buildMoodCard(seasonPalettes[j]));
}
function initPalette() {
var fc = document.getElementById('sbc-palette-filters'); var gc = document.getElementById('sbc-palette-grid');
for (var i = 0; i < swatchFamilies.length; i++) { var f = swatchFamilies[i]; fc.appendChild(createEl('button', { className: 'sbc-palette-filter' + (f.key === 'all' ? ' active' : ''), 'data-family': f.key }, f.label)); }
function renderSwatches(family) { clearEl(gc); var filtered = [];
for (var i = 0; i < curatedSwatches.length; i++) { if (family === 'all' || curatedSwatches[i].family === family) filtered.push(curatedSwatches[i]); }
for (var j = 0; j < filtered.length; j++) { (function(s) {
var card = createEl('div', { className: 'sbc-palette-card', 'data-hex': s.hex });
var block = createEl('div', { className: 'sbc-palette-color-block' });
if (s.isMulti) block.style.background = 'linear-gradient(135deg, #E8725C, #C5A55A, #5A7247, #3B6B9E)'; else block.style.backgroundColor = s.hex;
card.appendChild(block); var info = createEl('div', { className: 'sbc-palette-info' });
info.appendChild(createEl('div', { className: 'sbc-palette-name' }, s.name)); info.appendChild(createEl('div', { className: 'sbc-palette-ref' }, s.ref));
if (!s.isMulti) info.appendChild(createEl('div', { className: 'sbc-palette-hex' }, s.hex.toUpperCase()));
card.appendChild(info); card.addEventListener('click', function() { if (selectedColors.length < MAX_COLORS) { var hsl = hexToHsl(s.hex); addColor({ hex: s.hex, name: s.name, h: hsl.h, s: hsl.s, l: hsl.l }); } });
gc.appendChild(card);
})(filtered[j]); } updateTonalHighlights();
} renderSwatches('all');
fc.addEventListener('click', function(e) { var btn = e.target; while (btn && !btn.classList.contains('sbc-palette-filter')) btn = btn.parentElement; if (!btn) return;
var allBtns = fc.querySelectorAll('.sbc-palette-filter'); for (var i = 0; i < allBtns.length; i++) allBtns[i].classList.remove('active');
btn.classList.add('active'); renderSwatches(btn.getAttribute('data-family'));
});
}
function initTabs() {
var tabs = document.querySelectorAll('.sbc-mode-tab');
for (var i = 0; i < tabs.length; i++) { (function(tab) { tab.addEventListener('click', function() {
for (var j = 0; j < tabs.length; j++) { tabs[j].classList.remove('active'); tabs[j].setAttribute('aria-selected', 'false'); }
var sections = document.querySelectorAll('.sbc-section'); for (var k = 0; k < sections.length; k++) sections[k].classList.remove('active');
tab.classList.add('active'); tab.setAttribute('aria-selected', 'true');
var target = document.getElementById('sbc-' + tab.getAttribute('data-mode')); if (target) target.classList.add('active');
}); })(tabs[i]); }
}
function loadFont() {
if (!document.querySelector('link[href*="Cormorant+Garamond"]')) {
var link = document.createElement('link'); link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Cormorant+Garamond:wght@300;400;500;600&display=swap'; document.head.appendChild(link);
}
}
function initStickyBar() {
var bar = document.getElementById('sbc-selection-bar');
if (!bar) return;
var sentinel = document.createElement('div');
sentinel.style.cssText = 'height:1px;margin:0;padding:0;visibility:hidden;';
bar.parentNode.insertBefore(sentinel, bar);
if (window.IntersectionObserver) {
var obs = new IntersectionObserver(function(entries) {
bar.classList.toggle('stuck', !entries[0].isIntersecting);
}, { threshold: [0] });
obs.observe(sentinel);
}
}
function initTooltip() {
var tip = document.getElementById('sbc-tooltip');
var tipSwatch = document.getElementById('sbc-tooltip-swatch');
var tipName = document.getElementById('sbc-tooltip-name');
var tipHex = document.getElementById('sbc-tooltip-hex');
if (!tip) return;
function showTip(e, name, hex) {
tipSwatch.style.backgroundColor = hex;
tipName.textContent = name;
tipHex.textContent = hex.toUpperCase();
tip.style.display = 'block';
tip.style.left = (e.clientX + 12) + 'px';
tip.style.top = (e.clientY - 36) + 'px';
}
function hideTip() { tip.style.display = 'none'; }
document.addEventListener('mouseover', function(e) {
var cell = e.target.closest ? e.target.closest('.sbc-tonal-cell') : null;
if (cell) { showTip(e, cell.getAttribute('data-name') || '', cell.getAttribute('data-hex') || ''); return; }
var pCard = e.target.closest ? e.target.closest('.sbc-palette-card') : null;
if (pCard) { var nm = pCard.querySelector('.sbc-palette-name'); var hx = pCard.getAttribute('data-hex');
if (nm && hx) { showTip(e, nm.textContent, hx); return; } }
hideTip();
});
document.addEventListener('mousemove', function(e) {
if (tip.style.display === 'block') { tip.style.left = (e.clientX + 12) + 'px'; tip.style.top = (e.clientY - 36) + 'px'; }
});
}
// ===== EXACT MATCH TAB =====
function initExactMatch() {
var API_BASE = 'https://grassclothwallcoverings.com/color-api';
var input = document.getElementById('sbc-exact-input');
var preview = document.getElementById('sbc-exact-preview');
var btn = document.getElementById('sbc-exact-btn');
var thresholdSlider = document.getElementById('sbc-exact-threshold');
var thresholdVal = document.getElementById('sbc-exact-threshold-val');
if (!input || !btn) return;
input.addEventListener('input', function() {
var v = input.value.trim();
if (/^#?[0-9a-f]{6}$/i.test(v.replace('#', ''))) {
preview.style.background = v.startsWith('#') ? v : '#' + v;
}
});
input.addEventListener('keydown', function(e) { if (e.key === 'Enter') doExactSearch(); });
btn.addEventListener('click', doExactSearch);
thresholdSlider.addEventListener('input', function() { thresholdVal.textContent = thresholdSlider.value; });
function sanitize(str) { var d = document.createElement('div'); d.textContent = str || ''; return d.textContent; }
async function doExactSearch() {
var q = input.value.trim();
if (!q) return;
btn.disabled = true;
var loading = document.getElementById('sbc-exact-loading');
loading.classList.add('show');
document.getElementById('sbc-exact-products').classList.remove('show');
document.getElementById('sbc-exact-similar').classList.remove('show');
try {
var lr = await fetch(API_BASE + '/lookup?q=' + encodeURIComponent(q));
var lookup = await lr.json();
var hex;
var resolved = document.getElementById('sbc-exact-resolved');
if (lookup.hex) {
hex = lookup.hex;
resolved.classList.add('show');
document.getElementById('sbc-exact-resolved-swatch').style.background = hex;
document.getElementById('sbc-exact-resolved-hex').textContent = hex;
preview.style.background = hex;
if (lookup.name) {
document.getElementById('sbc-exact-resolved-name').textContent = sanitize(lookup.name);
var bDisp = lookup.brand ? lookup.brand.replace(/-/g, ' ').replace(/\b\w/g, function(c){ return c.toUpperCase(); }) : '';
document.getElementById('sbc-exact-resolved-details').textContent = (bDisp + ' ' + (lookup.code || '')).trim();
} else if (lookup.results) {
hex = lookup.results[0].hex;
document.getElementById('sbc-exact-resolved-swatch').style.background = hex;
document.getElementById('sbc-exact-resolved-hex').textContent = hex;
document.getElementById('sbc-exact-resolved-name').textContent = sanitize(lookup.results[0].name);
var bDisp2 = lookup.results[0].brand.replace(/-/g, ' ').replace(/\b\w/g, function(c){ return c.toUpperCase(); });
document.getElementById('sbc-exact-resolved-details').textContent = bDisp2 + ' ' + lookup.results[0].code;
preview.style.background = hex;
} else {
document.getElementById('sbc-exact-resolved-name').textContent = 'Custom Hex';
document.getElementById('sbc-exact-resolved-details').textContent = '';
}
} else {
resolved.classList.remove('show');
loading.classList.remove('show');
btn.disabled = false;
return;
}
var thVal = thresholdSlider.value;
var [simR, matchR] = await Promise.all([
fetch(API_BASE + '/similar?hex=' + encodeURIComponent(hex) + '&limit=8'),
fetch(API_BASE + '/match?hex=' + encodeURIComponent(hex) + '&threshold=' + thVal + '&limit=50')
]);
var similar = await simR.json();
var match = await matchR.json();
// Render similar
var simPanel = document.getElementById('sbc-exact-similar');
var simRow = document.getElementById('sbc-exact-similar-row');
simRow.textContent = '';
if (similar.similar && similar.similar.length > 0) {
simPanel.classList.add('show');
similar.similar.forEach(function(s) {
var chip = document.createElement('div');
chip.className = 'sbc-exact-similar-chip';
var sw = document.createElement('div');
sw.className = 'sbc-exact-similar-swatch';
sw.style.background = s.hex;
var tw = document.createElement('div');
var nm = document.createElement('div');
nm.className = 'sbc-exact-similar-name';
nm.textContent = sanitize(s.name);
var br = document.createElement('div');
br.className = 'sbc-exact-similar-brand';
br.textContent = s.brand.replace(/-/g, ' ');
var de = document.createElement('div');
de.className = 'sbc-exact-similar-de';
de.textContent = s.code + ' | dE ' + s.distance.toFixed(1);
tw.appendChild(nm); tw.appendChild(br); tw.appendChild(de);
chip.appendChild(sw); chip.appendChild(tw);
chip.addEventListener('click', function() { input.value = s.hex; preview.style.background = s.hex; doExactSearch(); });
simRow.appendChild(chip);
});
} else {
simPanel.classList.remove('show');
}
loading.classList.remove('show');
// Render products
var prodPanel = document.getElementById('sbc-exact-products');
var prodGrid = document.getElementById('sbc-exact-products-grid');
prodGrid.textContent = '';
if (match.products && match.products.length > 0) {
prodPanel.classList.add('show');
document.getElementById('sbc-exact-products-count').textContent = match.total_matches + ' product' + (match.total_matches !== 1 ? 's' : '') + ' within dE ' + thVal;
match.products.forEach(function(p) {
var card = document.createElement('a');
card.className = 'sbc-exact-product-card';
card.href = p.url;
card.target = '_blank';
var img = document.createElement('img');
img.className = 'sbc-exact-product-img';
img.src = p.image_url || '';
img.alt = sanitize(p.title);
img.loading = 'lazy';
img.addEventListener('error', function() { this.style.display = 'none'; });
card.appendChild(img);
var info = document.createElement('div');
info.className = 'sbc-exact-product-info';
var title = document.createElement('div');
title.className = 'sbc-exact-product-title';
title.textContent = sanitize(p.title);
var vendor = document.createElement('div');
vendor.className = 'sbc-exact-product-vendor';
vendor.textContent = sanitize(p.vendor);
info.appendChild(title); info.appendChild(vendor);
card.appendChild(info);
var bar = document.createElement('div');
bar.className = 'sbc-exact-product-bar';
var psw = document.createElement('div');
psw.className = 'sbc-exact-product-swatch';
psw.style.background = p.hex;
bar.appendChild(psw);
var deSpan = document.createElement('span');
var deCls = p.distance < 3 ? 'sbc-exact-de-exact' : p.distance < 7 ? 'sbc-exact-de-close' : 'sbc-exact-de-near';
var deLbl = p.distance < 1 ? 'Exact' : p.distance < 3 ? 'Very Close' : p.distance < 7 ? 'Close' : 'Near';
deSpan.className = 'sbc-exact-product-de ' + deCls;
deSpan.textContent = 'dE ' + p.distance + ' — ' + deLbl;
bar.appendChild(deSpan);
card.appendChild(bar);
prodGrid.appendChild(card);
});
}
} catch(err) {
console.error('Exact match error:', err);
loading.classList.remove('show');
}
btn.disabled = false;
}
}
/* ===== LAZY LOADING OBSERVER ===== */
var sbcLazyObserver = null;
function initLazyLoading() {
if (!window.IntersectionObserver) { /* Fallback: load all immediately */
var imgs = document.querySelectorAll('.sbc-product-img.sbc-lazy[data-src]');
for (var i = 0; i < imgs.length; i++) { imgs[i].src = imgs[i].getAttribute('data-src'); imgs[i].classList.remove('sbc-lazy'); imgs[i].classList.add('sbc-loaded'); }
return;
}
sbcLazyObserver = new IntersectionObserver(function(entries) {
for (var i = 0; i < entries.length; i++) {
if (entries[i].isIntersecting) {
var img = entries[i].target; var src = img.getAttribute('data-src');
if (src) { img.src = src; img.removeAttribute('data-src'); img.addEventListener('load', function() { this.classList.remove('sbc-lazy'); this.classList.add('sbc-loaded'); }); }
sbcLazyObserver.unobserve(img);
}
}
}, { rootMargin: '200px 0px', threshold: 0.01 });
}
function observeLazyImages() {
if (!sbcLazyObserver) { /* Fallback */
var imgs = document.querySelectorAll('.sbc-product-img.sbc-lazy[data-src]');
for (var i = 0; i < imgs.length; i++) { imgs[i].src = imgs[i].getAttribute('data-src'); imgs[i].classList.remove('sbc-lazy'); imgs[i].classList.add('sbc-loaded'); }
return;
}
var imgs = document.querySelectorAll('.sbc-product-img.sbc-lazy[data-src]');
for (var i = 0; i < imgs.length; i++) sbcLazyObserver.observe(imgs[i]);
}
/* ===== MOBILE HUE STRIP ===== */
function initMobileHueStrip() {
var strip = document.getElementById('sbc-mobile-hue-strip'); if (!strip) return;
var indicator = document.getElementById('sbc-mobile-hue-indicator');
var hint = document.getElementById('sbc-mobile-hue-hint');
function handleStripClick(e) {
var rect = strip.getBoundingClientRect();
var ratio = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width));
var hue = ratio * 360; var sat = 75; var light = 50;
var hex = hslToHex(hue, sat, light); var name = getColorName(hue, sat, light);
indicator.style.display = 'block'; indicator.style.left = (ratio * 100) + '%';
indicator.style.backgroundColor = hex;
if (selectedColors.length < MAX_COLORS) {
addColor({ hex: hex, name: name, h: hue, s: sat, l: light });
hint.textContent = selectedColors.length >= MAX_COLORS ? 'Palette full (3 colors)' : 'Tap to add another color';
} else { hint.textContent = 'Palette full. Remove a color to add more.'; }
}
strip.addEventListener('click', handleStripClick);
strip.addEventListener('touchend', function(e) { e.preventDefault(); handleStripClick({ clientX: e.changedTouches[0].clientX, clientY: e.changedTouches[0].clientY }); });
}
function init() {
loadFont(); initLazyLoading(); initTabs(); initColorWheel(); initSpectrum(); initTonalGrid(); initHarmonyWheel(); initMoodPalettes(); initPalette(); renderSelectionBar(); initStickyBar(); initTooltip(); initExactMatch(); initMobileHueStrip();
var clearBtn = document.getElementById('sbc-clear-all'); if (clearBtn) clearBtn.addEventListener('click', function() { clearColors(); });
document.addEventListener('keydown', function(e) { if (e.key === 'Escape' && selectedColors.length > 0) clearColors(); });
var ctaBtn = document.getElementById('sbc-shop-cta-btn');
if (ctaBtn) ctaBtn.addEventListener('click', function() {
var productsZone = document.getElementById('sbc-products-zone');
if (productsZone && productsZone.style.display !== 'none') {
productsZone.scrollIntoView({ behavior: 'smooth', block: 'start' });
productsZone.style.boxShadow = '0 0 30px rgba(180,160,120,0.15)';
setTimeout(function() { productsZone.style.boxShadow = ''; }, 1500);
} else {
var matchedBar = document.getElementById('sbc-matched-bar');
if (matchedBar && matchedBar.style.display !== 'none') {
matchedBar.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init); else init();
})();