[object Object]

← back to Designer Wallcoverings

auto-save: 2026-07-09T09:09:49 (3 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid vendor-scrapers/china-seas-refresh

c7633ec44a576dec0cd63d081b3105c651a9ce40 · 2026-07-09 09:09:53 -0700 · Steve Abrams

Files touched

Diff

commit c7633ec44a576dec0cd63d081b3105c651a9ce40
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 9 09:09:53 2026 -0700

    auto-save: 2026-07-09T09:09:49 (3 files) — pending-approval/boost-filter-consolidation-2026-06-25 shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid vendor-scrapers/china-seas-refresh
---
 .../snippets/color-palette.liquid                  | 213 ++++++++++++---------
 1 file changed, 118 insertions(+), 95 deletions(-)

diff --git a/shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid b/shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid
index 356e6aff..1ac4d6b5 100644
--- a/shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid
+++ b/shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid
@@ -3,14 +3,18 @@
   Shows the pattern's real colors AND ~20 interpolated colors between each pair,
   laid out as uniform swatches centered in even rows.
 
-  DOT CLICK TARGET (Steve 2026-07-09): a swatch click resolves to the ONE
-  nearest SPECIFIC product+variant of that hue and navigates to
-    /collections/<family>/products/<handle>?variant=<rollVariantId>
-  (collection kept in the path for breadcrumb; roll variant, never the Sample) —
-  NOT the general color-family collection listing. The family collection remains
-  the swatch's plain href (middle-click / crawler fallback) and the graceful
-  floor if a single specific product can't be resolved. (was: Steve 2026-07-08,
-  every swatch linked straight to the family collection.)
+  DOT CLICK TARGET (Steve 2026-07-09b): "bring up an index of many images, not
+  just that exact hex. tolerance 10% for color to pull more." A swatch click now
+  opens an IN-PAGE expanding grid of MANY products whose dominant color is within
+  a perceptual 10% tolerance (CIELAB ΔE76) of the clicked hex — CATALOG-WIDE, not
+  one collection. The grid is served by the color-index endpoint
+    POST https://photo.designerwallcoverings.com/apps/color-index  {hex, k}
+  (dwphoto → data/color-index.json, one dominant color per ACTIVE product with
+  precomputed LAB). Cards link to /products/<handle> (the roll variant, never the
+  $4.25 Sample). The family-collection href stays as the plain href so
+  middle-click / new-tab / crawlers still get a valid destination, and is the
+  graceful fallback if the endpoint is unreachable.
+  (was: Steve 2026-07-09a, a click deep-linked to the ONE nearest product+variant.)
 
   Data tiers (mirrors color-dots.liquid, kept in sync):
     Tier 1  custom.color_dots_json / custom.color_details  (enriched JSON: [{hex,name,pct}])
@@ -46,6 +50,19 @@
 >
   <p class="dw-color-palette__title">Colors In This Pattern</p>
   <div class="dw-color-palette__cards" data-dw-color-palette-cards aria-live="polite"></div>
+
+  {%- comment -%} In-page color-index grid the dot brings up (Steve 2026-07-09b).
+    Hidden until a swatch is clicked; then filled with the catalog-wide products
+    within 10% (CIELAB ΔE76) of the clicked hex. {%- endcomment -%}
+  <div class="dw-color-index" data-dw-color-index hidden
+       data-endpoint="https://photo.designerwallcoverings.com/apps/color-index">
+    <div class="dw-color-index__head">
+      <span class="dw-color-index__swatch" data-dw-ci-swatch></span>
+      <span class="dw-color-index__label" data-dw-ci-label aria-live="polite"></span>
+      <button type="button" class="dw-color-index__close" data-dw-ci-close aria-label="Close color index">&times;</button>
+    </div>
+    <div class="dw-color-index__grid" data-dw-ci-grid aria-live="polite"></div>
+  </div>
 </div>
 
 <style>
@@ -73,6 +90,24 @@
     .dw-swatch{width:15px;height:15px;}
     .dw-swatch--base{width:19px;height:19px;}
   }
+  /* ── In-page color-index grid the dot brings up ── */
+  .dw-color-index{max-width:660px;margin:16px auto 4px;text-align:left;}
+  .dw-color-index[hidden]{display:none;}
+  .dw-color-index__head{display:flex;align-items:center;gap:9px;margin:0 0 12px;}
+  .dw-color-index__swatch{width:20px;height:20px;border-radius:4px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14);}
+  .dw-color-index__label{font-family:Lora,serif;font-size:13px;color:#3D4246;flex:1 1 auto;}
+  .dw-color-index__label b{font-weight:600;}
+  .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:24px;height:24px;line-height:1;font-size:16px;color:#6b6b6b;cursor:pointer;padding:0;}
+  .dw-color-index__close:hover{color:#000;border-color:#b8b3a8;}
+  .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(120px,1fr));gap:12px;}
+  .dw-color-index__card{display:block;text-decoration:none;color:inherit;}
+  .dw-color-index__card img{width:100%;aspect-ratio:1;object-fit:cover;border:1px solid #e5e5e5;border-radius:3px;background:#f4f2ee;}
+  .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:5px;line-height:1.25;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+  .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;}
+  .dw-color-index__empty,.dw-color-index__loading{font-family:Lora,serif;font-size:12px;color:#8a8577;padding:8px 0;}
+  @media (max-width:600px){
+    .dw-color-index__grid{grid-template-columns:repeat(auto-fill,minmax(96px,1fr));gap:8px;}
+  }
 </style>
 
 <script>
@@ -172,88 +207,77 @@
       return url;
     }
 
-    /* ── hex → ONE SPECIFIC product+variant of that color ─────────────────────
-       Steve 2026-07-09: "on click dots should go to that color and not the
-       general collection." A dot is a palette-extracted hue with no single owning
-       product, so "that color" = the nearest specific product OF that hue. We
-       search the hex's own family collection (public products.json), sample each
-       product's dominant color from its thumbnail, pick the nearest to the clicked
-       hex, and return a Steve-shaped URL:
-         /collections/<family>/products/<handle>?...&variant=<rollVariantId>
-       (collection kept in the path for breadcrumb context; roll variant, never
-       the $4.25 Sample). Resolution is cached per family handle so repeated dot
-       clicks in the same family don't re-fetch. */
-    var _famCache = {};      // familyHandle -> Promise<[{handle,variantId,rgb}]>
-    function _thumbRgb(url){
-      return new Promise(function(resolve){
-        var img=new Image(); img.crossOrigin='anonymous';
-        img.onload=function(){
-          try{
-            var s=32,cv=document.createElement('canvas');cv.width=s;cv.height=s;
-            var cx=cv.getContext('2d');cx.drawImage(img,0,0,s,s);
-            var px=cx.getImageData(0,0,s,s).data,r=0,g=0,b=0,n=0;
-            for(var i=0;i<px.length;i+=4){ if(px[i+3]<200) continue; r+=px[i];g+=px[i+1];b+=px[i+2];n++; }
-            resolve(n?[Math.round(r/n),Math.round(g/n),Math.round(b/n)]:null);
-          }catch(e){ resolve(null); }
-        };
-        img.onerror=function(){ resolve(null); };
-        img.src=url;
-      });
+    /* ── hex → IN-PAGE INDEX of MANY products within 10% color tolerance ───────
+       Steve 2026-07-09b: "bring up an index of many images, not just that exact
+       hex. tolerance 10% for color to pull more." A dot is a palette-extracted
+       hue; clicking it opens the in-page grid (below the palette) of products
+       whose dominant color is within a perceptual 10% tolerance (CIELAB ΔE76) of
+       the clicked hex, drawn CATALOG-WIDE from the color-index endpoint. Results
+       are cached per hex so re-clicking a swatch is instant. */
+    var CI = root.querySelector('[data-dw-color-index]');
+    var CI_ENDPOINT = (CI && CI.dataset.endpoint) || 'https://photo.designerwallcoverings.com/apps/color-index';
+    var CI_K = 36;                 // cards to show (endpoint caps at 60)
+    var _ciCache = {};             // hex -> Promise<{results,total,tolerance_pct}>
+    function fetchColorIndex(hex){
+      if(_ciCache[hex]) return _ciCache[hex];
+      _ciCache[hex]=fetch(CI_ENDPOINT,{
+        method:'POST', mode:'cors', headers:{'Content-Type':'application/json'},
+        body:JSON.stringify({hex:hex, k:CI_K})
+      }).then(function(r){ return r.ok?r.json():{results:[]}; })
+        .catch(function(){ return {results:[], _err:true}; });
+      return _ciCache[hex];
     }
-    function _loadFamily(handle){
-      if(_famCache[handle]) return _famCache[handle];
-      var url='/collections/'+handle+'/products.json?limit=40';
-      _famCache[handle]=fetch(url,{credentials:'same-origin'})
-        .then(function(r){ return r.ok?r.json():{products:[]}; })
-        .then(function(d){
-          var prods=(d&&d.products)||[];
-          return Promise.all(prods.map(function(p){
-            var vs=p.variants||[];
-            // the sellable ROLL variant — never the $4.25 Sample
-            var roll=vs.filter(function(v){
-              var sku=(v.sku||'').toLowerCase(), ti=(v.title||'').toLowerCase();
-              return sku.indexOf('-sample')===-1 && ti!=='sample';
-            })[0] || vs[0];
-            var img=(p.images&&p.images[0]&&p.images[0].src)||'';
-            if(!p.handle || !roll || !img) return null;
-            var thumb=img.indexOf('?')===-1 ? img+'?width=80' : img;
-            return _thumbRgb(thumb).then(function(rgb){
-              return rgb ? {handle:p.handle, variantId:roll.id, rgb:rgb} : null;
-            });
-          })).then(function(arr){ return arr.filter(Boolean); });
-        })
-        .catch(function(){ return []; });
-      return _famCache[handle];
+    function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
+    function closeIndex(){ if(CI){ CI.hidden=true; } }
+    if(CI){
+      var _cx=ciEl('[data-dw-ci-close]');
+      if(_cx) _cx.addEventListener('click', closeIndex);
     }
-    /* Resolve the clicked hex to a specific product+variant URL (or null). */
-    function resolveSpecificHref(hex){
-      var handle=familyHandle(hex); if(!handle) return Promise.resolve(null);
-      var target=hexToRgb(hex); if(!target) return Promise.resolve(null);
-      return _loadFamily(handle).then(function(items){
-        if(!items.length) return null;
-        var best=null,bd=1e18;
-        items.forEach(function(it){
-          var d=Math.pow(target[0]-it.rgb[0],2)+Math.pow(target[1]-it.rgb[1],2)+Math.pow(target[2]-it.rgb[2],2);
-          if(d<bd){ bd=d; best=it; }
+    /* Open the in-page index for a clicked hex. */
+    function openIndex(hex, name){
+      if(!CI) return;
+      var sw=ciEl('[data-dw-ci-swatch]'), lbl=ciEl('[data-dw-ci-label]'), grid=ciEl('[data-dw-ci-grid]');
+      var fam=familyFromHex(hex)||'';
+      if(sw) sw.style.background=hex;
+      if(lbl) lbl.innerHTML='Wallcoverings in this color — <b>'+(name||fam||hex)+'</b>';
+      if(grid) grid.innerHTML='<div class="dw-color-index__loading">Finding matches…</div>';
+      CI.hidden=false;
+      CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+      fetchColorIndex(hex).then(function(d){
+        if(!grid) return;
+        var res=(d&&d.results)||[];
+        if(!res.length){
+          grid.innerHTML='';
+          var empty=document.createElement('div'); empty.className='dw-color-index__empty';
+          empty.textContent = d && d._err
+            ? 'Could not load matches right now — try another color.'
+            : 'No close matches for this color yet.';
+          grid.appendChild(empty);
+          return;
+        }
+        grid.innerHTML='';
+        var frag=document.createDocumentFragment();
+        res.forEach(function(p){
+          if(!p || !p.handle) return;
+          var a=document.createElement('a'); a.className='dw-color-index__card'; a.rel='nofollow';
+          // link straight to the PDP handle — the roll/first-available variant,
+          // never the $4.25 Sample (Sample is never the default variant).
+          a.href='/products/'+encodeURIComponent(p.handle);
+          var img=document.createElement('img'); img.loading='lazy';
+          img.src=p.image||''; img.alt=p.title||'';
+          var cap=document.createElement('span'); cap.className='dw-color-index__cap';
+          cap.textContent=(p.title||'').replace(/\s*\|\s*.*$/,'');   // pattern part
+          if(p.vendor){ var v=document.createElement('i'); v.textContent=p.vendor; cap.appendChild(v); }
+          a.appendChild(img); a.appendChild(cap); frag.appendChild(a);
         });
-        if(!best) return null;
-        var url='/collections/'+handle+'/products/'+encodeURIComponent(best.handle)+'?variant='+encodeURIComponent(best.variantId);
-        if(PRODUCT_STYLE){ url+='&filter.p.m.custom.style='+encodeURIComponent(PRODUCT_STYLE); }
-        return url;
+        grid.appendChild(frag);
       });
     }
-    /* Click handler shared by every swatch: navigate to the SPECIFIC
-       product+variant of that hue; fall back to the family collection only if
-       resolution genuinely fails. */
-    function onSwatchClick(hex, ev){
+    /* Click handler shared by every swatch: open the in-page index for that hue.
+       Modified clicks (new tab) fall through to the plain family-collection href. */
+    function onSwatchClick(hex, ev, name){
       ev.preventDefault();
-      var el=ev.currentTarget;
-      if(el.dataset.resolving) return; el.dataset.resolving='1';
-      resolveSpecificHref(hex).then(function(specific){
-        window.location.href = specific || familyHref(hex) || ('/collections/'+(familyHandle(hex)||''));
-      }).catch(function(){
-        window.location.href = familyHref(hex) || '/';
-      });
+      openIndex(hex, name);
     }
 
     /* ── color math: normalize + interpolate ── */
@@ -268,11 +292,10 @@
     }
     var STEPS = 20; /* interpolated colors served BETWEEN each pair (Steve 2026-07-08) */
 
-    /* Build one swatch. Left-click resolves to the SPECIFIC product+variant of
-       that hue (onSwatchClick); the href is the family collection so
-       middle-click / new-tab / crawlers still get a valid destination.
-       (Steve 2026-07-09: "on click dots should go to that color, not the
-       general collection.") */
+    /* Build one swatch. Left-click opens the IN-PAGE index of many products
+       within 10% of that hue (onSwatchClick → openIndex); the href is the family
+       collection so middle-click / new-tab / crawlers still get a valid
+       destination. (Steve 2026-07-09b: "bring up an index of many images.") */
     function makeSwatch(hex, opts){
       opts=opts||{};
       var href=familyHref(hex);
@@ -281,11 +304,11 @@
       el.style.background=hex;
       if(href){
         el.href=href;
-        el.addEventListener('click', function(ev){
+        el.addEventListener('click', (function(nm){ return function(ev){
           // let modified clicks (new tab / new window) use the plain-collection href
           if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
-          onSwatchClick(hex, ev);
-        });
+          onSwatchClick(hex, ev, nm);
+        }; })(opts.name));
       }
       var fam=familyFromHex(hex);
       if(opts.base){
@@ -335,10 +358,10 @@
           var it=document.createElement(href?'a':'span');
           if(href){
             it.href=href;
-            (function(hx){ it.addEventListener('click', function(ev){
+            (function(hx, nm){ it.addEventListener('click', function(ev){
               if(ev.metaKey||ev.ctrlKey||ev.shiftKey||ev.altKey||ev.button!==0) return;
-              onSwatchClick(hx, ev);
-            }); })(c.hex);
+              onSwatchClick(hx, ev, nm);
+            }); })(c.hex, c.name);
           }
           it.innerHTML='<i style="background:'+c.hex+'"></i>'+c.name+(c.pct!=null?' <b style="font-weight:600;color:#8a8577">'+c.pct+'%</b>':'');
           leg.appendChild(it);

← 78f9bfbe cadence: sample-only importer titles/describes Fabric lines  ·  back to Designer Wallcoverings  ·  pending-approval: color-dot index-of-many 10% tolerance memo 3baf80b0 →