← back to Dw Theme Hamburger
snippets/color-dots.liquid
246 lines
{% assign color_dots_json_val = product.metafields.custom.color_dots_json.value %}
{% assign color_details_val = product.metafields.custom.color_details.value %}
{% if color_dots_json_val != blank %}
{% assign color_details_raw = color_dots_json_val | json %}
{% elsif color_details_val != blank %}
{% assign color_details_raw = color_details_val | json %}
{% else %}
{% assign color_details_raw = blank %}
{% endif %}
{% assign color_hex = product.metafields.custom.color_hex.value %}
{% assign primary_hex = product.metafields.custom.primary_color_hex.value %}
{% assign ai_colors_raw = product.metafields.dwc.ai_generated_colors.value | json %}
{% assign color_name = product.metafields.custom.color_name.value %}
{%- comment -%}
Color Dots v3 — 3-tier color system
Tier 1: color_details metafield (Gemini-enriched)
Tier 2: primary_color_hex + ai_generated_colors
Tier 3: Canvas pixel extraction from product image (onload)
{%- endcomment -%}
<div class="dw-color-dots" id="dw-color-dots-wrapper" style="display:flex;justify-content:center;gap:10px;padding:12px 0 8px;flex-wrap:wrap;min-height:30px;">
<div id="dw-color-dots-container" style="display:flex;justify-content:center;gap:10px;flex-wrap:wrap;"
{% if color_details_raw != blank and color_details_raw != 'null' %}data-colors='{{ color_details_raw | json }}'{% endif %}
{% if primary_hex != blank %}data-primary-hex="{{ primary_hex }}"{% endif %}
{% if ai_colors_raw != blank and ai_colors_raw != 'null' %}data-ai-colors='{{ ai_colors_raw }}'{% endif %}
{% if color_name != blank %}data-color-name="{{ color_name }}"{% endif %}
{% if color_hex != blank %}data-color-hex="{{ color_hex }}"{% endif %}
{% if product.featured_image %}data-product-image="{{ product.featured_image | img_url: '200x200' }}"{% endif %}
></div>
</div>
<script>
(function(){
function init() {
try {
var container = document.getElementById('dw-color-dots-container');
if (!container || container.dataset.rendered) return;
container.dataset.rendered = '1';
var C = {
'red':'red-wallpaper-collection','crimson':'red-wallpaper-collection','burgundy':'red-wallpaper-collection','wine':'red-wallpaper-collection','maroon':'red-wallpaper-collection',
'blue':'blue-wallpaper','navy':'blue-wallpaper','cobalt':'blue-wallpaper','indigo':'blue-wallpaper','sky blue':'blue-wallpaper','steel blue':'blue-wallpaper','royal blue':'blue-wallpaper',
'green':'green-wallpaper-collection','sage':'green-wallpaper-collection','olive':'green-wallpaper-collection','forest green':'green-wallpaper-collection','emerald':'green-wallpaper-collection','mint':'green-wallpaper-collection','moss':'green-wallpaper-collection',
'gold':'gold-wallpaper','golden':'gold-wallpaper','amber':'gold-wallpaper',
'yellow':'yellow-wallpaper-collection','mustard':'yellow-wallpaper-collection',
'orange':'orange','rust':'orange','terracotta':'orange',
'pink':'pink-wallcovering-2','blush':'pink-wallcovering-2','rose':'pink-wallcovering-2','salmon':'pink-wallcovering-2','fuchsia':'pink-wallcovering-2',
'purple':'purple','violet':'purple','plum':'purple','lavender':'purple','lilac':'purple','mauve':'purple',
'black':'black','charcoal':'black','ebony':'black','onyx':'black',
'white':'white-wallpaper-collection','ivory':'white-wallpaper-collection','cream':'white-wallpaper-collection','pearl':'white-wallpaper-collection','off-white':'white-wallpaper-collection',
'gray':'grey-silver','grey':'grey-silver','silver':'grey-silver','slate':'grey-silver',
'brown':'brown-wallpaper-collection','chocolate':'brown-wallpaper-collection','espresso':'brown-wallpaper-collection','walnut':'brown-wallpaper-collection','mocha':'brown-wallpaper-collection','sienna':'brown-wallpaper-collection',
'beige':'beige-cream-wallpaper-collection','tan':'beige-cream-wallpaper-collection','taupe':'beige-cream-wallpaper-collection','sand':'beige-cream-wallpaper-collection','khaki':'beige-cream-wallpaper-collection','camel':'beige-cream-wallpaper-collection','oatmeal':'beige-cream-wallpaper-collection','linen':'beige-cream-wallpaper-collection',
'coral':'coral','peach':'coral',
'teal':'turquoise','aqua':'turquoise','turquoise':'turquoise','cyan':'turquoise',
'metallic':'metallic-wallpaper-collections','copper':'metallic-wallpaper-collections','bronze':'metallic-wallpaper-collections'
};
var N = {
'red':'#DC2626','crimson':'#DC143C','burgundy':'#800020','wine':'#722F37','maroon':'#800000',
'blue':'#2563EB','navy':'#1B2A4A','cobalt':'#0047AB','indigo':'#3F51B5','sky blue':'#87CEEB','steel blue':'#4682B4','royal blue':'#4169E1',
'green':'#16A34A','sage':'#87AE73','olive':'#808000','forest green':'#228B22','emerald':'#50C878','mint':'#98FF98','moss':'#8A9A5B',
'gold':'#D4AF37','golden':'#FFD700','amber':'#FFBF00',
'yellow':'#EAB308','mustard':'#FFDB58',
'orange':'#EA580C','rust':'#B7410E','terracotta':'#E2725B',
'pink':'#EC4899','blush':'#DE5D83','rose':'#FF007F','salmon':'#FA8072','fuchsia':'#FF00FF',
'purple':'#9333EA','violet':'#7C3AED','plum':'#8E4585','lavender':'#E6E6FA','lilac':'#C8A2C8','mauve':'#E0B0FF',
'black':'#1A1A1A','charcoal':'#36454F','ebony':'#555D50','onyx':'#353839',
'white':'#FAFAFA','ivory':'#FFFFF0','cream':'#FFFDD0','pearl':'#F0EAD6','off-white':'#FAF9F6',
'gray':'#6B7280','grey':'#6B7280','silver':'#C0C0C0','slate':'#708090',
'brown':'#92400E','chocolate':'#7B3F00','espresso':'#3C1414','walnut':'#5C3A1D','mocha':'#967969','sienna':'#A0522D',
'beige':'#F5F5DC','tan':'#D2B48C','taupe':'#483C32','sand':'#C2B280','khaki':'#F0E68C','camel':'#C19A6B','oatmeal':'#B8AB8C','linen':'#FAF0E6',
'coral':'#FF6F61','peach':'#FFCBA4',
'teal':'#0D9488','aqua':'#00FFFF','turquoise':'#40E0D0','cyan':'#06B6D4',
'metallic':'#AAA9AD','copper':'#B87333','bronze':'#CD7F32',
'deep indigo':'#1B2A4A','dark navy':'#1B2A4A','midnight':'#191970'
};
function findColl(name) {
var l = (name||'').toLowerCase();
if (C[l]) return C[l];
for (var k in C) { if (l.indexOf(k) > -1) return C[k]; }
return null;
}
function makeDot(hex, name, pct) {
try {
var coll = findColl(name);
var a = document.createElement('a');
a.href = coll ? '/collections/' + coll + '?sort_by=created-descending' : '/search?q=' + encodeURIComponent(name||hex) + '&sort_by=created-descending';
a.title = (coll?'Shop ':'Search ') + (name||'color') + ' (' + hex + ')';
a.style.textDecoration = 'none';
var d = document.createElement('div');
var p = (pct == null ? 20 : pct);
var sz=Math.max(22,Math.min(52,16+p*0.6));d.style.cssText='width:'+sz+'px;height:'+sz+'px;border-radius:50%;background:'+hex+';border:2px solid #eee;box-shadow:0 1px 3px rgba(0,0,0,0.1);cursor:pointer;transition:transform 0.15s,box-shadow 0.15s;flex-shrink:0;';
d.onmouseenter = function(){this.style.transform='scale(1.3)';this.style.boxShadow='0 2px 8px rgba(0,0,0,0.2)';};
d.onmouseleave = function(){this.style.transform='scale(1)';this.style.boxShadow='0 1px 3px rgba(0,0,0,0.1)';};
a.appendChild(d);
return a;
} catch(e) { return null; }
}
function renderDots(colors) {
colors.forEach(function(c){ var el = makeDot(c.hex, c.name, (c.pct || c.percentage)); if (el) container.appendChild(el); });
}
/* Canvas color refs */
var REF = {
'red':[220,38,38],'navy':[27,42,74],'blue':[37,99,235],'green':[22,163,74],'sage':[135,174,115],
'gold':[212,175,55],'yellow':[234,179,8],'orange':[234,88,12],'pink':[236,72,153],
'purple':[147,51,234],'black':[26,26,26],'charcoal':[54,69,79],'white':[250,250,250],
'ivory':[255,255,240],'cream':[255,253,208],'gray':[107,114,128],'silver':[192,192,192],
'brown':[146,64,14],'beige':[245,245,220],'tan':[210,180,140],'taupe':[72,60,50],
'teal':[13,148,136],'coral':[255,111,97],'olive':[128,128,0],'rust':[183,65,14],
'burgundy':[128,0,32],'slate':[112,128,144],'sand':[194,178,128],'mint':[152,255,152],
'turquoise':[64,224,208],'copper':[184,115,51],'bronze':[205,127,50],'mauve':[224,176,255]
};
function closestName(r,g,b) {
var best='',bestD=1e9;
for(var n in REF){var c=REF[n],d=Math.sqrt(Math.pow(r-c[0],2)+Math.pow(g-c[1],2)+Math.pow(b-c[2],2));if(d<bestD){bestD=d;best=n;}}
return best;
}
function rgbHex(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
function extractFromCanvas(imgUrl) {
var img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function() {
try {
var cv = document.createElement('canvas'), sz = 50;
cv.width=sz; cv.height=sz;
var ctx=cv.getContext('2d');
ctx.drawImage(img,0,0,sz,sz);
var px=ctx.getImageData(0,0,sz,sz).data;
var bk={};
for(var i=0;i<px.length;i+=4){
var kr=Math.round(px[i]/32)*32,kg=Math.round(px[i+1]/32)*32,kb=Math.round(px[i+2]/32)*32;
var key=kr+','+kg+','+kb;
if(!bk[key])bk[key]={r:0,g:0,b:0,n:0};
bk[key].r+=px[i];bk[key].g+=px[i+1];bk[key].b+=px[i+2];bk[key].n++;
}
var sorted=Object.values(bk).sort(function(a,b){return b.n-a.n;});
var tot=sz*sz,colors=[],seen={};
for(var j=0;j<Math.min(sorted.length,8);j++){
var s=sorted[j],ar=Math.round(s.r/s.n),ag=Math.round(s.g/s.n),ab=Math.round(s.b/s.n);
var nm=closestName(ar,ag,ab);
if(seen[nm])continue; seen[nm]=true;
colors.push({name:nm,hex:rgbHex(ar,ag,ab),percentage:Math.round(s.n/tot*100)});
if(colors.length>=5)break;
}
if(colors.length>0) renderDots(colors);
} catch(e) {
/* CORS or canvas security error — try without crossOrigin */
var img2 = new Image();
img2.onload = function() {
try {
var cv2 = document.createElement('canvas'), sz2 = 50;
cv2.width=sz2; cv2.height=sz2;
var ctx2=cv2.getContext('2d');
ctx2.drawImage(img2,0,0,sz2,sz2);
/* Cannot getImageData without CORS — use image dominant color via 1x1 scale */
cv2.width=1; cv2.height=1;
ctx2.drawImage(img2,0,0,1,1);
var p=ctx2.getImageData(0,0,1,1).data;
var nm=closestName(p[0],p[1],p[2]);
renderDots([{name:nm,hex:rgbHex(p[0],p[1],p[2]),percentage:100}]);
} catch(e2) {}
};
img2.src = imgUrl;
}
};
img.onerror = function(){};
img.src = imgUrl;
}
function getImageUrl() {
var url = container.dataset.productImage;
if (url && url.length > 5) return url.indexOf('//') === 0 ? 'https:' + url : url;
var sels = [
'.product-gallery--viewport img[src]',
'.product-gallery img[src]',
'[data-product-gallery] img[src]',
'.product-gallery--viewport--figure img[src]',
'.product__media img[src]',
'.product-single__photo img[src]'
];
for (var i = 0; i < sels.length; i++) {
var el = document.querySelector(sels[i]);
if (el && el.src) return el.src;
}
return null;
}
/* ── Tier 1: color_details metafield ── */
var colorsData = container.dataset.colors;
if (colorsData) {
try {
/* Fix Shopify Liquid Ruby hash syntax: => becomes : */
var fixed = colorsData.replace(/=>/g, ':');
renderDots(JSON.parse(fixed));
return;
} catch(e) {}
}
/* ── Tier 2: primary_color_hex + ai_generated_colors ── */
var ph = container.dataset.primaryHex;
var aiRaw = container.dataset.aiColors;
if (ph || aiRaw) {
var added = {};
if (ph) { var pd=makeDot(ph, container.dataset.colorName||'primary'); if(pd)container.appendChild(pd); added[ph.toLowerCase()]=true; }
try {
var ai = JSON.parse((aiRaw||'[]').replace(/=>/g, ':'));
ai.forEach(function(nm){ var h=N[nm.toLowerCase()]; if(h&&!added[h.toLowerCase()]){var ad=makeDot(h,nm); if(ad)container.appendChild(ad);added[h.toLowerCase()]=true;} });
} catch(e) {}
if (container.children.length > 0) return;
}
/* ── Tier 2.5: single color_hex ── */
var sh = container.dataset.colorHex;
if (sh) { var shd=makeDot(sh, container.dataset.colorName||'color'); if(shd)container.appendChild(shd); return; }
/* ── Tier 3: Canvas extraction from product image ── */
var imgUrl = getImageUrl();
if (imgUrl) {
extractFromCanvas(imgUrl);
} else {
var obs = new MutationObserver(function(m, o) {
var url = getImageUrl();
if (url) { o.disconnect(); extractFromCanvas(url); }
});
obs.observe(document.body, { childList: true, subtree: true });
setTimeout(function(){ obs.disconnect(); }, 5000);
}
} catch(e) { /* never let color-dot rendering abort other page init */ }
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
</script>