← back to Dw Marketing Reels
colorwheel: Shopify /pages/colors widget (scoped inline build, hits public color-widen API)
1dfe5d52cd838a420310cb986e6ee68377225a26 · 2026-07-16 11:01:54 -0700 · Steve
Files touched
A colorwheel/colors-page.shopify.html
Diff
commit 1dfe5d52cd838a420310cb986e6ee68377225a26
Author: Steve <steve@designerwallcoverings.com>
Date: Thu Jul 16 11:01:54 2026 -0700
colorwheel: Shopify /pages/colors widget (scoped inline build, hits public color-widen API)
---
colorwheel/colors-page.shopify.html | 197 ++++++++++++++++++++++++++++++++++++
1 file changed, 197 insertions(+)
diff --git a/colorwheel/colors-page.shopify.html b/colorwheel/colors-page.shopify.html
new file mode 100644
index 0000000..a6990aa
--- /dev/null
+++ b/colorwheel/colors-page.shopify.html
@@ -0,0 +1,197 @@
+<!-- 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:340px;height:340px;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:34px;height:34px;border-radius:50%;border:3px 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:118px;height:118px;
+ 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:78px;height:78px;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:14px}
+#sbc .sbc-nav{border:1px solid var(--line);background:#fff;color:var(--ink);width:32px;height:32px;
+ border-radius:50%;font-size:20px;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:14px}
+#sbc .sbc-pdot{width:34px;height:34px;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;text-decoration:none;color:inherit;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 .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)}
+#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}
+</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">‹</button>
+ <div class="sbc-dots" id="sbc-dots"></div>
+ <button class="sbc-nav" id="sbc-next" aria-label="More colors">›</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();
+
+ 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,k:300})})
+ .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 a=document.createElement('a');a.className='sbc-card';
+ a.href='/products/'+encodeURIComponent(p.handle);a.target='_blank';a.rel='noopener';
+ a.style.animationDelay=Math.min(i*12,700)+'ms';
+ var im=document.createElement('img');im.className='sbc-im';im.loading='lazy';im.src=p.image;
+ im.onerror=function(){a.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);grid.appendChild(a);
+ });
+ })
+ .catch(function(e){grid.innerHTML='<div class="sbc-empty">Could not load right now. '+e.message+'</div>';});
+ }
+ load(place(curHue,curSat));
+})();
+</script>
← 5f28b85 colorwheel: add 6-dot palette below wheel, paginated left/ri
·
back to Dw Marketing Reels
·
colorwheel: canonical /pages/shop-by-color body (new wheel, 907a60d →