[object Object]

← back to Dw Marketing Reels

shop-by-color wheel: -50% wheel+dots, mobile+touch, >=80 SKUs (min:80), add-sample-to-cart per card

f7b39879dfe7094a3d7d08d281f0d9abb5345cdb · 2026-07-16 11:40:44 -0700 · Steve

Files touched

Diff

commit f7b39879dfe7094a3d7d08d281f0d9abb5345cdb
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Jul 16 11:40:44 2026 -0700

    shop-by-color wheel: -50% wheel+dots, mobile+touch, >=80 SKUs (min:80), add-sample-to-cart per card
---
 colorwheel/colors-page.shopify.html     | 78 ++++++++++++++++++++++++++-------
 colorwheel/shop-by-color.page-body.html | 78 ++++++++++++++++++++++++++-------
 2 files changed, 126 insertions(+), 30 deletions(-)

diff --git a/colorwheel/colors-page.shopify.html b/colorwheel/colors-page.shopify.html
index a6990aa..10bce02 100644
--- a/colorwheel/colors-page.shopify.html
+++ b/colorwheel/colors-page.shopify.html
@@ -10,49 +10,65 @@
 #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-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:34px;height:34px;border-radius:50%;border:3px solid #fff;
+#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:118px;height:118px;
+#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:78px;height:78px;border-radius:50%;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);
+#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: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;
+#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:14px}
-#sbc .sbc-pdot{width:34px;height:34px;border-radius:50%;cursor:pointer;
+#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;text-decoration:none;color:inherit;background:#fff;border:1px solid var(--line);
+#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">
@@ -167,10 +183,37 @@
   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,k:300})})
+      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||[]);
@@ -178,16 +221,21 @@
         grid.innerHTML='';
         res.forEach(function(p,i){
           if(!p.handle||!p.image)return;
-          var a=document.createElement('a');a.className='sbc-card';
+          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';
-          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();};
+          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);grid.appendChild(a);
+          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>';});
diff --git a/colorwheel/shop-by-color.page-body.html b/colorwheel/shop-by-color.page-body.html
index 980ef4f..5b62793 100644
--- a/colorwheel/shop-by-color.page-body.html
+++ b/colorwheel/shop-by-color.page-body.html
@@ -20,49 +20,65 @@
 #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-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:34px;height:34px;border-radius:50%;border:3px solid #fff;
+#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:118px;height:118px;
+#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:78px;height:78px;border-radius:50%;box-shadow:inset 0 0 0 1px rgba(0,0,0,.12);
+#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: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;
+#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:14px}
-#sbc .sbc-pdot{width:34px;height:34px;border-radius:50%;cursor:pointer;
+#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;text-decoration:none;color:inherit;background:#fff;border:1px solid var(--line);
+#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">
@@ -177,10 +193,37 @@
   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,k:300})})
+      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||[]);
@@ -188,16 +231,21 @@
         grid.innerHTML='';
         res.forEach(function(p,i){
           if(!p.handle||!p.image)return;
-          var a=document.createElement('a');a.className='sbc-card';
+          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';
-          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();};
+          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);grid.appendChild(a);
+          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>';});

← 907a60d colorwheel: canonical /pages/shop-by-color body (new wheel,  ·  back to Dw Marketing Reels  ·  console: Video Studio — buttons to queue IG reel / URL→reel 1d2a1cb →