← back to Dw Theme Compact Toolbar

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>