← 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
M shopify/theme-LIVE-591-freshpull-20260709/snippets/color-palette.liquid
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">×</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 →