← back to Dw Marketing Reels

colorwheel/colors-page.shopify.html

246 lines

<!-- Designer Wallcoverings · Shop by Color — self-contained widget for /pages/colors.
     Everything scoped under #sbc; JS in an IIFE; talks to the public color-widen API. -->
<style>
#sbc{--ink:#1a1a1a;--line:#e6e0d6;--gold:#b9892f;color:var(--ink);
  font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Helvetica,Arial,sans-serif;
  max-width:1220px;margin:0 auto;padding:6px 0 44px}
#sbc *{box-sizing:border-box}
#sbc .sbc-head{text-align:center;padding:14px 16px 4px}
#sbc .sbc-head h2{margin:0;font-weight:300;letter-spacing:.18em;text-transform:uppercase;font-size:22px}
#sbc .sbc-head p{margin:6px 0 0;color:#7a7266;font-size:13px;letter-spacing:.04em}
#sbc .sbc-stage{display:flex;gap:40px;align-items:center;justify-content:center;flex-wrap:wrap;padding:22px 16px 8px}
#sbc .sbc-wheelcol{display:flex;flex-direction:column;align-items:center;gap:16px}
#sbc .sbc-wheelwrap{position:relative;width:170px;height:170px;flex:0 0 auto}
#sbc .sbc-spin{position:absolute;inset:0;animation:sbcspin 40s linear infinite}
#sbc .sbc-wheelwrap.sbc-dragging .sbc-spin{animation-play-state:paused}
#sbc .sbc-wheel{width:100%;height:100%;border-radius:50%;cursor:crosshair;
  box-shadow:0 10px 40px rgba(0,0,0,.18),inset 0 0 0 1px rgba(0,0,0,.06)}
@keyframes sbcspin{to{transform:rotate(360deg)}}
#sbc .sbc-pick{position:absolute;width:17px;height:17px;border-radius:50%;border:2px solid #fff;
  box-shadow:0 2px 10px rgba(0,0,0,.35),0 0 0 1px rgba(0,0,0,.25);transform:translate(-50%,-50%);
  left:50%;top:14%;pointer-events:none}
#sbc .sbc-hub{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);width:59px;height:59px;
  border-radius:50%;background:#fff;box-shadow:0 6px 22px rgba(0,0,0,.16),inset 0 0 0 1px var(--line);
  display:flex;align-items:center;justify-content:center}
#sbc .sbc-sw{width:39px;height:39px;border-radius:50%;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);
  transition:background .2s;background:#8aa}
#sbc .sbc-side{max-width:300px}
#sbc .sbc-side h3{font-weight:400;font-size:15px;letter-spacing:.06em;text-transform:uppercase;margin:0 0 8px}
#sbc .sbc-side p{color:#7a7266;font-size:13px;line-height:1.6;margin:0}
#sbc .sbc-palette{display:flex;align-items:center;justify-content:center;gap:7px}
#sbc .sbc-nav{border:1px solid var(--line);background:#fff;color:var(--ink);width:16px;height:16px;
  border-radius:50%;font-size:13px;line-height:1;cursor:pointer;display:flex;align-items:center;
  justify-content:center;box-shadow:0 1px 4px rgba(0,0,0,.08);transition:transform .12s,box-shadow .12s}
#sbc .sbc-nav:hover{transform:translateY(-2px);box-shadow:0 5px 14px rgba(0,0,0,.16)}
#sbc .sbc-nav:disabled{opacity:.28;cursor:default;transform:none;box-shadow:none}
#sbc .sbc-dots{display:flex;gap:7px}
#sbc .sbc-pdot{width:17px;height:17px;border-radius:50%;cursor:pointer;
  box-shadow:inset 0 0 0 1px rgba(0,0,0,.14),0 1px 4px rgba(0,0,0,.12);
  transition:transform .14s,box-shadow .14s}
#sbc .sbc-pdot:hover{transform:translateY(-3px) scale(1.1)}
#sbc .sbc-pdot.sbc-on{box-shadow:0 0 0 3px var(--gold),inset 0 0 0 1px rgba(0,0,0,.14)}
#sbc .sbc-grid{margin:16px auto 20px;padding:0 6px;display:grid;
  grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:18px}
#sbc .sbc-card{position:relative;display:flex;flex-direction:column;background:#fff;border:1px solid var(--line);
  border-radius:12px;overflow:hidden;opacity:0;transform:translateY(14px) scale(.98);
  animation:sbcpop .5s forwards;box-shadow:0 1px 0 rgba(0,0,0,.03)}
@keyframes sbcpop{to{opacity:1;transform:none}}
#sbc .sbc-card:hover{box-shadow:0 12px 30px rgba(0,0,0,.12)}
#sbc .sbc-card-link{display:block;text-decoration:none;color:inherit}
#sbc .sbc-card .sbc-im{aspect-ratio:1/1;width:100%;object-fit:cover;display:block;background:#f2efe9}
#sbc .sbc-card .sbc-cap{padding:10px 12px;font-size:12.5px;line-height:1.35}
#sbc .sbc-card .sbc-cap i{display:block;color:#9a9276;font-style:normal;font-size:11px;margin-top:3px}
#sbc .sbc-card .sbc-cdot{position:absolute;top:8px;right:8px;width:14px;height:14px;border-radius:50%;
  box-shadow:0 0 0 2px #fff,inset 0 0 0 1px rgba(0,0,0,.15)}
/* add-sample button — pinned to the bottom of every card so the grid stays even */
#sbc .sbc-add{margin-top:auto;border:0;border-top:1px solid var(--line);background:#faf8f4;color:var(--ink);
  font:600 11.5px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;letter-spacing:.04em;
  text-transform:uppercase;padding:11px 8px;cursor:pointer;transition:background .15s,color .15s}
#sbc .sbc-add:hover{background:var(--gold);color:#fff}
#sbc .sbc-add.sbc-added{background:#2e7d32;color:#fff;cursor:default}
#sbc .sbc-empty{grid-column:1/-1;text-align:center;color:#9a9276;padding:40px;font-size:14px}
#sbc .sbc-loading{grid-column:1/-1;text-align:center;color:var(--gold);padding:40px;font-size:13px;letter-spacing:.1em}
  /* touch: let the wheel be dragged without scrolling the page */
  #sbc .sbc-wheelwrap,#sbc .sbc-wheel,#sbc .sbc-spin{touch-action:none}
  /* mobile */
  @media(max-width:600px){
    #sbc .sbc-stage{gap:20px;padding:16px 12px}
    #sbc .sbc-side{max-width:92%}
    #sbc .sbc-head h2{font-size:19px}
    #sbc .sbc-grid{grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:12px;padding:0 10px}
  }
</style>

<div id="sbc">
  <div class="sbc-head">
    <h2>Shop by Color</h2>
    <p>Spin the wheel · click a hue · discover every wallcovering in that color</p>
  </div>
  <div class="sbc-stage">
    <div class="sbc-wheelcol">
      <div class="sbc-wheelwrap" id="sbc-ww">
        <div class="sbc-spin" id="sbc-spin">
          <canvas class="sbc-wheel" id="sbc-wheel" width="680" height="680"></canvas>
          <div class="sbc-pick" id="sbc-pick"></div>
        </div>
        <div class="sbc-hub"><div class="sbc-sw" id="sbc-sw"></div></div>
      </div>
      <div class="sbc-palette">
        <button class="sbc-nav" id="sbc-prev" aria-label="Previous colors">&lsaquo;</button>
        <div class="sbc-dots" id="sbc-dots"></div>
        <button class="sbc-nav" id="sbc-next" aria-label="More colors">&rsaquo;</button>
      </div>
    </div>
    <div class="sbc-side">
      <h3>Curated by hue</h3>
      <p>Every color on the wheel maps to the actual dominant colors extracted from our
         full catalog. Pick a shade and we surface the closest perceptual matches — the
         same color intelligence behind each product page.</p>
    </div>
  </div>
  <div class="sbc-grid" id="sbc-grid"></div>
</div>

<script>
(function(){
  var API='https://photo.designerwallcoverings.com/apps/color-widen';
  var cv=document.getElementById('sbc-wheel'); if(!cv||cv.dataset.sbcInit) return; cv.dataset.sbcInit='1';
  var ctx=cv.getContext('2d'), R=cv.width/2;
  function hsl2rgb(h,s,l){h/=360;var r,g,b;if(s===0){r=g=b=l;}else{
    var q=l<.5?l*(1+s):l+s-l*s,p=2*l-q;var t=function(a){if(a<0)a+=1;if(a>1)a-=1;
      if(a<1/6)return p+(q-p)*6*a;if(a<1/2)return q;if(a<2/3)return p+(q-p)*(2/3-a)*6;return p;};
    r=t(h+1/3);g=t(h);b=t(h-1/3);}return[Math.round(r*255),Math.round(g*255),Math.round(b*255)];}
  function rgbHex(r,g,b){return '#'+[r,g,b].map(function(x){return x.toString(16).padStart(2,'0');}).join('');}
  (function paint(){
    var img=ctx.createImageData(cv.width,cv.height),d=img.data;
    for(var y=0;y<cv.height;y++)for(var x=0;x<cv.width;x++){
      var dx=x-R,dy=y-R,r=Math.sqrt(dx*dx+dy*dy),i=(y*cv.width+x)*4;
      if(r>R){d[i+3]=0;continue;}
      var h=Math.atan2(dy,dx)*180/Math.PI;if(h<0)h+=360;
      var rgb=hsl2rgb(h,Math.min(1,r/R),0.5);
      d[i]=rgb[0];d[i+1]=rgb[1];d[i+2]=rgb[2];d[i+3]=255;
    }
    ctx.putImageData(img,0,0);
  })();
  var ww=document.getElementById('sbc-ww'),spin=document.getElementById('sbc-spin'),
    pick=document.getElementById('sbc-pick'),sw=document.getElementById('sbc-sw'),
    grid=document.getElementById('sbc-grid');
  var curHue=140,curSat=0.45;
  function place(hue,sat){
    var ang=hue*Math.PI/180,rad=sat*48;
    pick.style.left=(50+Math.cos(ang)*rad)+'%';
    pick.style.top=(50+Math.sin(ang)*rad)+'%';
    var rgb=hsl2rgb(hue,sat,0.5),hx=rgbHex(rgb[0],rgb[1],rgb[2]);
    pick.style.background=hx;sw.style.background=hx;
    return hx;
  }
  function spinDeg(){
    var tr=getComputedStyle(spin).transform;
    if(!tr||tr==='none')return 0;
    var m=new DOMMatrixReadOnly(tr);
    var d=Math.atan2(m.b,m.a)*180/Math.PI;if(d<0)d+=360;return d;
  }
  function fromEvent(e){
    var rect=cv.getBoundingClientRect();
    var cx=e.touches?e.touches[0].clientX:e.clientX,cy=e.touches?e.touches[0].clientY:e.clientY;
    var dx=cx-(rect.left+rect.width/2),dy=cy-(rect.top+rect.height/2);
    var scr=Math.atan2(dy,dx)*180/Math.PI;if(scr<0)scr+=360;
    var h=(scr-spinDeg()+360)%360;
    var s=Math.min(1,Math.sqrt(dx*dx+dy*dy)/(rect.width/2));
    curHue=h;curSat=Math.max(.12,s);return place(curHue,curSat);
  }
  var t=null;
  function selected(hx){clearTimeout(t);t=setTimeout(function(){load(hx);},140);}
  ww.addEventListener('pointerdown',function(e){ww.classList.add('sbc-dragging');selected(fromEvent(e));
    var mv=function(ev){selected(fromEvent(ev));};
    var up=function(){ww.classList.remove('sbc-dragging');
      window.removeEventListener('pointermove',mv);window.removeEventListener('pointerup',up);};
    window.addEventListener('pointermove',mv);window.addEventListener('pointerup',up);});

  var PALETTE=[
    [0,.62],[22,.62],[42,.6],[60,.58],[85,.55],[110,.55],
    [140,.55],[165,.55],[190,.58],[210,.6],[232,.6],[255,.58],
    [280,.56],[300,.56],[322,.58],[345,.6],[10,.34],[200,.34],
    [320,.3],[45,.14],[150,.14],[220,.14],[0,.06],[0,0]
  ];
  var PAGE=6,dotsEl=document.getElementById('sbc-dots'),
    prevB=document.getElementById('sbc-prev'),nextB=document.getElementById('sbc-next');
  var pStart=0,activeDot=-1;
  function renderDots(){
    dotsEl.innerHTML='';
    PALETTE.slice(pStart,pStart+PAGE).forEach(function(pr,k){
      var h=pr[0],s=pr[1],idx=pStart+k,el=document.createElement('div');el.className='sbc-pdot';
      var rgb=hsl2rgb(h,s,0.5),hx=rgbHex(rgb[0],rgb[1],rgb[2]);
      el.style.background=hx;el.title=hx.toUpperCase();
      if(idx===activeDot)el.classList.add('sbc-on');
      el.onclick=function(){activeDot=idx;curHue=h;curSat=Math.max(.12,s);place(h,s);load(hx);renderDots();};
      dotsEl.appendChild(el);
    });
    prevB.disabled=pStart<=0;
    nextB.disabled=pStart+PAGE>=PALETTE.length;
  }
  prevB.onclick=function(){pStart=Math.max(0,pStart-PAGE);renderDots();};
  nextB.onclick=function(){pStart=Math.min(Math.max(0,PALETTE.length-PAGE),pStart+PAGE);renderDots();};
  renderDots();

  // add the product's SAMPLE variant ($4.25) straight to the Shopify cart,
  // resolved from the storefront product JSON on click (no variant id needed upfront)
  function addSample(handle, btn){
    if(btn.dataset.busy) return; btn.dataset.busy='1';
    var orig=btn.textContent; btn.textContent='Adding…';
    fetch('/products/'+encodeURIComponent(handle)+'.js')
      .then(function(r){return r.json();})
      .then(function(pj){
        var vs=pj.variants||[];
        var sv=vs.filter(function(v){return v.available!==false;}).find(function(v){
                 return /sample/i.test(v.title||'')||/(^|[-\s])sample$/i.test(v.sku||'');})
             || vs.find(function(v){return /sample/i.test(v.title||'')||/(^|[-\s])sample$/i.test(v.sku||'');})
             || vs.find(function(v){return v.price===425;});
        if(!sv){btn.textContent='View product';btn.onclick=function(){window.open('/products/'+encodeURIComponent(handle),'_blank');};delete btn.dataset.busy;return;}
        return fetch('/cart/add.js',{method:'POST',headers:{'Content-Type':'application/json'},
                 body:JSON.stringify({items:[{id:sv.id,quantity:1}]})})
          .then(function(r){if(!r.ok)throw new Error('add');return r.json();})
          .then(function(){
            btn.textContent='Added ✓'; btn.classList.add('sbc-added');
            // nudge common theme cart-drawer listeners to refresh the count
            document.dispatchEvent(new CustomEvent('cart:refresh'));
            document.dispatchEvent(new CustomEvent('ajaxProduct:added'));
            if(window.jQuery){try{window.jQuery(document).trigger('ajaxCart:afterCartLoad');}catch(e){}}
          });
      })
      .catch(function(){btn.textContent='Try again';setTimeout(function(){btn.textContent=orig;delete btn.dataset.busy;},2000);});
  }
  function load(hex){
    grid.innerHTML='<div class="sbc-loading">Finding wallcoverings in this color</div>';
    fetch(API,{method:'POST',headers:{'Content-Type':'application/json'},
      body:JSON.stringify({hex:hex,ceiling:15,min:80,k:300})})   // always surface at least 80 SKUs
      .then(function(r){return r.json();})
      .then(function(j){
        var res=(j.results||[]);
        if(!res.length){grid.innerHTML='<div class="sbc-empty">No close matches for this shade yet — try a neighboring hue.</div>';return;}
        grid.innerHTML='';
        res.forEach(function(p,i){
          if(!p.handle||!p.image)return;
          var card=document.createElement('div');card.className='sbc-card';
          card.style.animationDelay=Math.min(i*8,700)+'ms';
          var a=document.createElement('a');a.className='sbc-card-link';
          a.href='/products/'+encodeURIComponent(p.handle);a.target='_blank';a.rel='noopener';
          var im=document.createElement('img');im.className='sbc-im';im.loading='lazy';im.src=p.image;
          im.onerror=function(){card.remove();};
          var dot=document.createElement('span');dot.className='sbc-cdot';dot.style.background=p.hex||hex;
          var cap=document.createElement('div');cap.className='sbc-cap';
          cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,'');
          if(p.vendor){var v=document.createElement('i');v.textContent=p.vendor;cap.appendChild(v);}
          a.appendChild(im);a.appendChild(dot);a.appendChild(cap);
          var add=document.createElement('button');add.className='sbc-add';add.type='button';
          add.textContent='Add sample · $4.25';
          add.onclick=function(){addSample(p.handle,add);};
          card.appendChild(a);card.appendChild(add);grid.appendChild(card);
        });
      })
      .catch(function(e){grid.innerHTML='<div class="sbc-empty">Could not load right now. '+e.message+'</div>';});
  }
  load(place(curHue,curSat));
})();
</script>