← back to Dw Theme Compact Toolbar

assets/dw-color-wheel.js

87 lines

/* DW "Shop by Color" — wheel + hue slider, live LAB re-sort over the whole catalog.
 * Injects a button on collection pages → opens a drawer with the wheel + results.
 * Data (handle→dominant hex + palette + image) lazy-loaded from window.DW_CW_DATA on first open. $0. */
(function(){
  if(!/\/collections\//.test(location.pathname)) return;           // collection pages only
  if(window.__dwcw) return; window.__dwcw=1;
  var DATA=null, loading=false, RESULTS=120;
  var CDN='https://cdn.shopify.com', STORE='';

  var hx2rgb=function(h){h=(''+h).replace('#','');if(h.length===3)h=h[0]+h[0]+h[1]+h[1]+h[2]+h[2];return[0,2,4].map(function(i){return parseInt(h.slice(i,i+2),16);});};
  function rgb2lab(c){var R=c.map(function(v){v/=255;return v>0.04045?Math.pow((v+0.055)/1.055,2.4):v/12.92;});
    var X=(R[0]*0.4124+R[1]*0.3576+R[2]*0.1805)/0.95047,Y=R[0]*0.2126+R[1]*0.7152+R[2]*0.0722,Z=(R[0]*0.0193+R[1]*0.1192+R[2]*0.9505)/1.08883;
    var f=function(t){return t>0.008856?Math.cbrt(t):7.787*t+16/116;};X=f(X);Y=f(Y);Z=f(Z);return[116*Y-16,500*(X-Y),200*(Y-Z)];}
  var dE=function(a,b){return Math.hypot(a[0]-b[0],a[1]-b[1],a[2]-b[2]);};
  function hsv2rgb(h,s,v){h/=360;var i=Math.floor(h*6),f=h*6-i,p=v*(1-s),q=v*(1-f*s),t=v*(1-(1-f)*s);
    var m=[[v,t,p],[q,v,p],[p,v,t],[p,q,v],[t,p,v],[v,p,q]][i%6];return[Math.round(m[0]*255),Math.round(m[1]*255),Math.round(m[2]*255)];}
  var toHex=function(c){return '#'+c.map(function(x){return x.toString(16).padStart(2,'0');}).join('');};

  var css='#dwcw-btn{position:fixed;right:18px;bottom:18px;z-index:9998;background:#1a1a1a;color:#fff;border:none;border-radius:30px;padding:12px 18px;font:600 14px system-ui;cursor:pointer;box-shadow:0 4px 16px rgba(0,0,0,.25);display:flex;align-items:center;gap:8px}'
   +'#dwcw-btn .sw{width:18px;height:18px;border-radius:50%;background:conic-gradient(red,#ff0,#0f0,#0ff,#00f,#f0f,red);border:2px solid #fff}'
   +'#dwcw-ov{position:fixed;inset:0;z-index:9999;background:rgba(20,18,16,.45);display:none}'
   +'#dwcw-ov.on{display:block}'
   +'#dwcw-panel{position:absolute;right:0;top:0;height:100%;width:min(560px,100%);background:#faf8f5;box-shadow:-6px 0 30px rgba(0,0,0,.3);display:flex;flex-direction:column;font:14px system-ui;color:#1a1a1a}'
   +'#dwcw-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px 8px}'
   +'#dwcw-head h2{font:600 19px Georgia,serif;margin:0}#dwcw-x{background:none;border:none;font-size:24px;cursor:pointer;color:#8a8278}'
   +'#dwcw-pick{display:flex;gap:16px;align-items:center;padding:4px 20px 10px;flex-wrap:wrap}'
   +'#dwcw-wheel{width:150px;height:150px;cursor:crosshair;border-radius:50%;box-shadow:0 3px 14px rgba(0,0,0,.15)}'
   +'#dwcw-hue{width:150px;height:22px;-webkit-appearance:none;border-radius:11px;outline:none;background:linear-gradient(90deg,hsl(0 90% 50%),hsl(60 90% 50%),hsl(120 90% 45%),hsl(180 80% 45%),hsl(240 85% 55%),hsl(300 80% 55%),hsl(360 90% 50%))}'
   +'#dwcw-hue::-webkit-slider-thumb{-webkit-appearance:none;width:20px;height:20px;border-radius:50%;background:#fff;border:3px solid #1a1a1a;cursor:pointer}'
   +'#dwcw-sw{display:flex;align-items:center;gap:8px}#dwcw-sw .chip{width:40px;height:40px;border-radius:9px;border:1px solid #e7e1d8}#dwcw-sw code{font:12px ui-monospace,monospace;color:#8a8278}'
   +'#dwcw-grid{flex:1;overflow:auto;display:grid;grid-template-columns:repeat(auto-fill,minmax(130px,1fr));gap:11px;padding:8px 20px 24px}'
   +'.dwcw-card{background:#fff;border:1px solid #e7e1d8;border-radius:9px;overflow:hidden;text-decoration:none;color:inherit}'
   +'.dwcw-card .ph{aspect-ratio:1;background:#eee center/cover;position:relative}.dwcw-card .dot{position:absolute;left:7px;bottom:7px;width:14px;height:14px;border-radius:50%;border:2px solid #fff}'
   +'.dwcw-card .t{font-size:11px;padding:6px 8px 3px;line-height:1.3;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:30px}'
   +'.dwcw-bar{display:flex;height:11px;margin:0 8px;border-radius:3px;overflow:hidden}.dwcw-row{font:9px ui-monospace,monospace;color:#9a9288;padding:4px 8px;display:flex;flex-wrap:wrap;gap:1px 6px}'
   +'#dwcw-load{padding:40px;text-align:center;color:#8a8278;grid-column:1/-1}';

  var ov,grid,chip,hexlbl,cv,cx,R=75,curHex='#c8102e';
  function build(){
    var s=document.createElement('style');s.textContent=css;document.head.appendChild(s);
    ov=document.createElement('div');ov.id='dwcw-ov';
    ov.innerHTML='<div id=dwcw-panel><div id=dwcw-head><h2>Shop by Color</h2><button id=dwcw-x>×</button></div>'
      +'<div id=dwcw-pick><canvas id=dwcw-wheel width=150 height=150></canvas>'
      +'<div><input type=range id=dwcw-hue min=0 max=360 value=350>'
      +'<div id=dwcw-sw style="margin-top:10px"><span class=chip id=dwcw-chip></span><code id=dwcw-hex>#c8102e</code></div></div></div>'
      +'<div id=dwcw-grid><div id=dwcw-load>Loading the color library…</div></div></div>';
    document.body.appendChild(ov);
    grid=ov.querySelector('#dwcw-grid');chip=ov.querySelector('#dwcw-chip');hexlbl=ov.querySelector('#dwcw-hex');
    cv=ov.querySelector('#dwcw-wheel');cx=cv.getContext('2d');
    var img=cx.createImageData(150,150),d=img.data;
    for(var y=0;y<150;y++)for(var x=0;x<150;x++){var dx=x-R,dy=y-R,r=Math.hypot(dx,dy),idx=(y*150+x)*4;
      if(r>R){d[idx+3]=0;continue;}var a=Math.atan2(dy,dx)*180/Math.PI;if(a<0)a+=360;var c=hsv2rgb(a,Math.min(1,r/R),1);d[idx]=c[0];d[idx+1]=c[1];d[idx+2]=c[2];d[idx+3]=255;}
    cx.putImageData(img,0,0);
    ov.querySelector('#dwcw-x').onclick=function(){ov.classList.remove('on');};
    ov.onclick=function(e){if(e.target===ov)ov.classList.remove('on');};
    var drag=false;
    function pick(e){var b=cv.getBoundingClientRect(),x=(e.clientX-b.left)/b.width*150-R,y=(e.clientY-b.top)/b.height*150-R,r=Math.hypot(x,y),a=Math.atan2(y,x)*180/Math.PI;if(a<0)a+=360;setHex(toHex(hsv2rgb(a,Math.min(1,r/R),1)));ov.querySelector('#dwcw-hue').value=Math.round(a);}
    cv.addEventListener('pointerdown',function(e){drag=true;pick(e);});
    addEventListener('pointermove',function(e){if(drag)requestAnimationFrame(function(){pick(e);});});
    addEventListener('pointerup',function(){drag=false;});
    ov.querySelector('#dwcw-hue').addEventListener('input',function(e){setHex(toHex(hsv2rgb(+e.target.value,0.85,0.85)));});
  }
  function setHex(hex){curHex=hex;if(chip)chip.style.background=hex;if(hexlbl)hexlbl.textContent=hex.toUpperCase();render();}
  function render(){
    if(!DATA){return;}
    var lab=rgb2lab(hx2rgb(curHex)),i;
    for(i=0;i<DATA.length;i++)DATA[i]._d=dE(DATA[i].lab,lab);
    var top=DATA.slice().sort(function(a,b){return a._d-b._d;}).slice(0,RESULTS);
    grid.innerHTML=top.map(function(p){
      var pal=(p.p||[]).map(function(x){return '<span style="flex:'+(x[1]||1)+';background:'+x[0]+'"></span>';}).join('');
      var palr=(p.p||[]).map(function(x){return '<span>'+x[1]+'% '+x[0]+'</span>';}).join('');
      return '<a class=dwcw-card href="/products/'+p.h+'"><div class=ph style="background-image:url(\''+(p.i?CDN+p.i+'?width=220':'')+'\')"><span class=dot style="background:'+p.d+'"></span></div>'
        +'<div class=t>'+(p.t||p.h)+'</div><div class=dwcw-bar>'+pal+'</div><div class=dwcw-row>'+palr+'</div></a>';
    }).join('');
  }
  function load(){
    if(DATA||loading)return;loading=true;
    fetch(window.DW_CW_DATA).then(function(r){return r.json();}).then(function(d){
      DATA=d.map(function(p){p.lab=p.d?rgb2lab(hx2rgb(p.d)):[50,0,0];return p;});
      setHex('#c8102e');
    }).catch(function(){grid.innerHTML='<div id=dwcw-load>Color library unavailable.</div>';});
  }
  var btn=document.createElement('button');btn.id='dwcw-btn';btn.innerHTML='<span class=sw></span> Shop by Color';
  btn.onclick=function(){if(!ov)build();ov.classList.add('on');load();};
  (document.body?document.body:document.documentElement).appendChild(btn);
})();