← back to Designer Wallcoverings
Refine PDP color-palette: fix invisible density slider, dedup near-duplicate dots, premium slider + color-index grid chrome
7fa268bff96e8ae231a40cee2c828a3d86948910 · 2026-07-09 16:02:07 -0700 · Steve (DW)
- CRITICAL live bug fix: wireDensity() multi-color path never cleared the markup's
'hidden' attr, so the density slider was invisible on every >1-color product.
Now unhides in the multi-color branch.
- Distribution: ΔE76 (CIELAB) adjacent-dedup of interpolated dots (JND ~2.3) — a
4-grey palette at slider 64 now renders 22 meaningful swatches, not 64 near-dupes.
Base (real) colors are never deduped.
- Slider visuals: caption-under-title layout, hairline track, smaller ink thumb w/
hairline ring, letter-spaced Lora label, focus-visible rings (keyboard a11y).
- Swatches: softer hairline base ring (was heavy dark hoop), material inner-shadow,
larger radius, 3px gaps.
- Color-index grid: hairline-ruled header, Lora title at palette scale, soft reveal
animation, even gutters, hairline media, Esc-to-close + focus mgmt.
- All prior behavior preserved: localStorage persistence, 80ms debounce, single-color
disabled+hint, 10% tolerance, CLIP strip, family fallback href.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
A scripts/wallquest-refresh/cp-refine-capture-local.cjsA scripts/wallquest-refresh/cp-scrape-data.cjsM shopify/theme-refine-colorpalette-20260709/snippets/color-palette.liquid
Diff
commit 7fa268bff96e8ae231a40cee2c828a3d86948910
Author: Steve (DW) <steve@designerwallcoverings.com>
Date: Thu Jul 9 16:02:07 2026 -0700
Refine PDP color-palette: fix invisible density slider, dedup near-duplicate dots, premium slider + color-index grid chrome
- CRITICAL live bug fix: wireDensity() multi-color path never cleared the markup's
'hidden' attr, so the density slider was invisible on every >1-color product.
Now unhides in the multi-color branch.
- Distribution: ΔE76 (CIELAB) adjacent-dedup of interpolated dots (JND ~2.3) — a
4-grey palette at slider 64 now renders 22 meaningful swatches, not 64 near-dupes.
Base (real) colors are never deduped.
- Slider visuals: caption-under-title layout, hairline track, smaller ink thumb w/
hairline ring, letter-spaced Lora label, focus-visible rings (keyboard a11y).
- Swatches: softer hairline base ring (was heavy dark hoop), material inner-shadow,
larger radius, 3px gaps.
- Color-index grid: hairline-ruled header, Lora title at palette scale, soft reveal
animation, even gutters, hairline media, Esc-to-close + focus mgmt.
- All prior behavior preserved: localStorage persistence, 80ms debounce, single-color
disabled+hint, 10% tolerance, CLIP strip, family fallback href.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
.../wallquest-refresh/cp-refine-capture-local.cjs | 44 +++++++
scripts/wallquest-refresh/cp-scrape-data.cjs | 18 +++
.../snippets/color-palette.liquid | 131 +++++++++++++++------
3 files changed, 156 insertions(+), 37 deletions(-)
diff --git a/scripts/wallquest-refresh/cp-refine-capture-local.cjs b/scripts/wallquest-refresh/cp-refine-capture-local.cjs
new file mode 100644
index 00000000..f85c2794
--- /dev/null
+++ b/scripts/wallquest-refresh/cp-refine-capture-local.cjs
@@ -0,0 +1,44 @@
+const puppeteer=require('/Users/macstudio3/Projects/Designer-Wallcoverings/scripts/wallquest-refresh/node_modules/puppeteer');
+const path=require('path');
+const OUT='/Users/macstudio3/Projects/Designer-Wallcoverings/scripts/wallquest-refresh/cp-refine-shots';
+const HARNESS='file://'+path.join('/Users/macstudio3/Projects/Designer-Wallcoverings/scripts/wallquest-refresh','cp-harness.html');
+const TAG='after';
+(async()=>{
+ const b=await puppeteer.launch({headless:'new',args:['--no-sandbox','--disable-web-security']});
+ const p=await b.newPage();
+ await p.setViewport({width:820,height:1600,deviceScaleFactor:2});
+ p.on('console',m=>{const t=m.text();if(/error|fail/i.test(t))console.log(' [c]',t.slice(0,140));});
+ await p.goto(HARNESS,{waitUntil:'networkidle2',timeout:30000});
+ await new Promise(r=>setTimeout(r,2500));
+ const pal='[data-dw-color-palette]';
+ const el=await p.$(pal);
+ if(!el){console.log('NO PALETTE');await b.close();process.exit(2);}
+ const st=await p.evaluate(()=>{const r=document.querySelector('[data-dw-color-palette]');const c=r.querySelector('[data-dw-color-palette-cards]');const rg=r.querySelector('[data-dw-cp-range]');const d=r.querySelector('[data-dw-palette-density]');return{dots:c?c.children.length:0,base:c?c.querySelectorAll('.dw-swatch--base').length:0,sliderVal:rg?rg.value:null,sliderMin:rg?rg.min:null,sliderMax:rg?rg.max:null,densityHidden:d?d.hasAttribute('hidden'):null,densityVisible:d?(getComputedStyle(d).display!=='none'):null};});
+ console.log('REST:',JSON.stringify(st));
+ await el.screenshot({path:path.join(OUT,`${TAG}-01-rest.png`)});
+ // low
+ await p.evaluate(()=>{const r=document.querySelector('[data-dw-cp-range]');if(r&&!r.disabled){r.value=r.min;r.dispatchEvent(new Event('input',{bubbles:true}));r.dispatchEvent(new Event('change',{bubbles:true}));}});
+ await new Promise(r=>setTimeout(r,300));
+ const lowN=await p.evaluate(()=>document.querySelector('[data-dw-color-palette-cards]').children.length);
+ await el.screenshot({path:path.join(OUT,`${TAG}-02-low.png`)});
+ // high
+ await p.evaluate(()=>{const r=document.querySelector('[data-dw-cp-range]');if(r&&!r.disabled){r.value=r.max;r.dispatchEvent(new Event('input',{bubbles:true}));r.dispatchEvent(new Event('change',{bubbles:true}));}});
+ await new Promise(r=>setTimeout(r,300));
+ const highN=await p.evaluate(()=>document.querySelector('[data-dw-color-palette-cards]').children.length);
+ await el.screenshot({path:path.join(OUT,`${TAG}-03-high.png`)});
+ console.log('low dots:',lowN,'high dots(after dedup):',highN);
+ // persistence check: reload, read localStorage-driven value
+ const persisted=await p.evaluate(()=>{try{return localStorage.getItem('dw_palette_dot_count');}catch(e){return 'ERR';}});
+ console.log('persisted dw_palette_dot_count:',persisted);
+ // click base dot -> index
+ const clicked=await p.evaluate(()=>{const b=document.querySelector('[data-dw-color-palette-cards] .dw-swatch--base');if(b){b.click();return true;}return false;});
+ await new Promise(r=>setTimeout(r,4500));
+ const ci=await p.evaluate(()=>{const c=document.querySelector('[data-dw-color-index]');if(!c)return{present:false};const g=c.querySelector('[data-dw-ci-grid]');return{present:true,hidden:c.hasAttribute('hidden'),isOpen:c.classList.contains('is-open'),cards:g?g.querySelectorAll('.dw-color-index__card').length:0,empty:g?!!g.querySelector('.dw-color-index__empty'):false,loading:g?!!g.querySelector('.dw-color-index__loading'):false};});
+ console.log('clicked:',clicked,'INDEX:',JSON.stringify(ci));
+ await p.evaluate(()=>document.querySelector('[data-dw-color-index]').scrollIntoView({block:'start'}));
+ await new Promise(r=>setTimeout(r,400));
+ const ciEl=await p.$('[data-dw-color-index]');
+ if(ciEl) await ciEl.screenshot({path:path.join(OUT,`${TAG}-04-index-open.png`)});
+ await b.close();
+ console.log('DONE');
+})().catch(e=>{console.error('ERR',e.message);process.exit(1);});
diff --git a/scripts/wallquest-refresh/cp-scrape-data.cjs b/scripts/wallquest-refresh/cp-scrape-data.cjs
new file mode 100644
index 00000000..7f36ed97
--- /dev/null
+++ b/scripts/wallquest-refresh/cp-scrape-data.cjs
@@ -0,0 +1,18 @@
+const puppeteer=require('/Users/macstudio3/Projects/Designer-Wallcoverings/scripts/wallquest-refresh/node_modules/puppeteer');
+(async()=>{
+ const b=await puppeteer.launch({headless:'new',args:['--no-sandbox']});
+ const p=await b.newPage();
+ await p.goto('https://www.designerwallcoverings.com/products/crayford-paisley-porcelain',{waitUntil:'networkidle2',timeout:60000});
+ await p.waitForSelector('[data-dw-color-palette]',{timeout:20000}).catch(()=>{});
+ const data=await p.evaluate(()=>{
+ const r=document.querySelector('[data-dw-color-palette]');
+ const o={};
+ if(r){ for(const a of r.attributes){ o[a.name]=a.value; } }
+ // grab a gallery img src for canvas fallback
+ const g=document.querySelector('.product-gallery img, .product__media img, img[src*="cdn/shop"]');
+ o._galleryImg = g?g.src:'';
+ return o;
+ });
+ console.log(JSON.stringify(data,null,1));
+ await b.close();
+})();
diff --git a/shopify/theme-refine-colorpalette-20260709/snippets/color-palette.liquid b/shopify/theme-refine-colorpalette-20260709/snippets/color-palette.liquid
index 6525af08..8ee04b1f 100644
--- a/shopify/theme-refine-colorpalette-20260709/snippets/color-palette.liquid
+++ b/shopify/theme-refine-colorpalette-20260709/snippets/color-palette.liquid
@@ -100,19 +100,23 @@
</div>
<style>
- .dw-color-palette{padding:16px 0 10px;text-align:center;min-height:10px;}
- .dw-color-palette__title{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;margin:0 0 12px;letter-spacing:-0.01em;}
- /* ── DENSITY SLIDER (expand/contract dot count) ── */
- .dw-color-palette__density{display:flex;align-items:center;justify-content:center;gap:8px;max-width:660px;margin:0 auto 14px;}
+ .dw-color-palette{padding:18px 0 12px;text-align:center;min-height:10px;}
+ .dw-color-palette__title{font-family:Lora,serif;font-size:15px;font-weight:500;color:#3D4246;margin:0 0 4px;letter-spacing:.01em;}
+ /* ── DENSITY SLIDER (expand/contract dot count) — refined to read as a quiet
+ caption UNDER the title, not a utilitarian widget row (refine 2026-07-09). ── */
+ .dw-color-palette__density{display:flex;align-items:center;justify-content:center;gap:10px;max-width:660px;margin:0 auto 15px;}
.dw-color-palette__density[hidden]{display:none;}
- .dw-cp-dense__wrap{display:inline-flex;align-items:center;gap:8px;}
- .dw-cp-dense__lbl{font-family:Lora,serif;font-size:11px;color:#8a8577;letter-spacing:.02em;text-transform:uppercase;white-space:nowrap;}
- .dw-cp-dense__range{-webkit-appearance:none;appearance:none;width:150px;max-width:38vw;height:3px;border-radius:3px;background:#e0ded9;outline:none;cursor:pointer;margin:0;}
- .dw-cp-dense__range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:14px;height:14px;border-radius:50%;background:#3D4246;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.18);cursor:pointer;}
- .dw-cp-dense__range::-moz-range-thumb{width:14px;height:14px;border-radius:50%;background:#3D4246;border:2px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.18);cursor:pointer;}
- .dw-cp-dense__out{font-family:Lora,serif;font-size:12px;font-weight:600;color:#3D4246;min-width:18px;text-align:right;font-variant-numeric:tabular-nums;}
- .dw-cp-dense__step{font-family:Lora,serif;font-size:16px;line-height:1;color:#6b6b6b;background:none;border:1px solid #e0ded9;border-radius:50%;width:24px;height:24px;padding:0;cursor:pointer;flex:0 0 auto;}
- .dw-cp-dense__step:hover{color:#000;border-color:#b8b3a8;}
+ .dw-cp-dense__wrap{display:inline-flex;align-items:center;gap:9px;}
+ .dw-cp-dense__lbl{font-family:Lora,serif;font-size:10px;color:#8a8577;letter-spacing:.14em;text-transform:uppercase;white-space:nowrap;}
+ .dw-cp-dense__range{-webkit-appearance:none;appearance:none;width:150px;max-width:38vw;height:2px;border-radius:2px;background:linear-gradient(#d8d4cc,#d8d4cc) no-repeat;background-size:100% 2px;background-position:0 center;outline:none;cursor:pointer;margin:0;}
+ .dw-cp-dense__range:focus-visible{outline:2px solid #b8b3a8;outline-offset:4px;border-radius:3px;}
+ .dw-cp-dense__range::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:12px;height:12px;border-radius:50%;background:#3D4246;border:2px solid #fff;box-shadow:0 0 0 1px rgba(61,66,70,.35),0 1px 2px rgba(0,0,0,.12);cursor:pointer;transition:transform .1s ease;}
+ .dw-cp-dense__range::-webkit-slider-thumb:hover{transform:scale(1.15);}
+ .dw-cp-dense__range::-moz-range-thumb{width:12px;height:12px;border-radius:50%;background:#3D4246;border:2px solid #fff;box-shadow:0 0 0 1px rgba(61,66,70,.35),0 1px 2px rgba(0,0,0,.12);cursor:pointer;}
+ .dw-cp-dense__out{font-family:Lora,serif;font-size:11px;font-weight:600;color:#3D4246;min-width:16px;text-align:right;font-variant-numeric:tabular-nums;letter-spacing:.01em;}
+ .dw-cp-dense__step{font-family:Lora,serif;font-size:15px;line-height:1;color:#8a8577;background:none;border:1px solid #e0ded9;border-radius:50%;width:22px;height:22px;padding:0;cursor:pointer;flex:0 0 auto;transition:color .12s ease,border-color .12s ease;}
+ .dw-cp-dense__step:hover{color:#3D4246;border-color:#b8b3a8;}
+ .dw-cp-dense__step:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
/* single-dominant-color state: control stays visible but reads as intentionally
inert (Steve 2026-07-09 fix #2) — dimmed, non-interactive, hinted via title/aria. */
.dw-color-palette__density.is-single{opacity:.55;}
@@ -127,19 +131,22 @@
}
/* Centered EVEN ROWS of uniform swatches: flex-wrap + justify-content:center
centers every row, including a partial last row. (Steve 2026-07-08) */
- .dw-color-palette__cards{display:flex;flex-wrap:wrap;justify-content:center;align-content:center;gap:2px;max-width:660px;margin:0 auto;}
- .dw-swatch{display:block;width:18px;height:18px;border-radius:3px;text-decoration:none;box-sizing:border-box;border:1px solid rgba(0,0,0,.06);transition:transform .12s ease,box-shadow .12s ease,z-index .12s;position:relative;}
+ .dw-color-palette__cards{display:flex;flex-wrap:wrap;justify-content:center;align-content:center;gap:3px;max-width:660px;margin:0 auto;}
+ /* Swatches read as cut material tiles: faint inner shadow + slightly larger radius,
+ hairline edge (not a heavy border). (refine 2026-07-09) */
+ .dw-swatch{display:block;width:18px;height:18px;border-radius:4px;text-decoration:none;box-sizing:border-box;box-shadow:inset 0 0 0 1px rgba(0,0,0,.05),inset 0 -3px 5px rgba(0,0,0,.045);transition:transform .12s ease,box-shadow .12s ease,z-index .12s;position:relative;}
a.dw-swatch{cursor:pointer;}
- .dw-swatch:hover{transform:scale(1.55);box-shadow:0 3px 10px rgba(0,0,0,.22);z-index:3;border-color:rgba(0,0,0,.25);}
- /* the pattern's REAL colors get a ring + a touch more size so they stand out
- from the interpolated in-between steps, while rows stay even. */
- .dw-swatch--base{width:22px;height:22px;border-radius:4px;border:1px solid #fff;box-shadow:0 0 0 1.5px #3D4246,0 1px 3px rgba(0,0,0,.14);}
- .dw-swatch--base:hover{transform:scale(1.4);}
- .dw-color-palette__bar{display:flex;height:4px;border-radius:3px;overflow:hidden;max-width:660px;margin:14px auto 0;box-shadow:inset 0 0 0 1px rgba(0,0,0,.04);}
+ .dw-swatch:hover{transform:scale(1.55);box-shadow:0 3px 10px rgba(0,0,0,.20),inset 0 0 0 1px rgba(0,0,0,.10);z-index:3;}
+ /* the pattern's REAL colors read as anchors: a refined HAIRLINE ink ring + a
+ slightly larger tile — no longer a heavy dark hoop that orphans them at high
+ dot counts. (refine 2026-07-09) */
+ .dw-swatch--base{width:22px;height:22px;border-radius:5px;box-shadow:0 0 0 1px #fff,0 0 0 1.5px rgba(61,66,70,.55),0 1px 3px rgba(0,0,0,.12),inset 0 -3px 6px rgba(0,0,0,.05);}
+ .dw-swatch--base:hover{transform:scale(1.4);box-shadow:0 0 0 1px #fff,0 0 0 1.5px rgba(61,66,70,.85),0 3px 10px rgba(0,0,0,.20);}
+ .dw-color-palette__bar{display:flex;height:4px;border-radius:3px;overflow:hidden;max-width:660px;margin:16px auto 0;box-shadow:inset 0 0 0 1px rgba(0,0,0,.04);}
.dw-color-palette__bar span{display:block;height:100%;}
- .dw-color-palette__legend{display:flex;flex-wrap:wrap;justify-content:center;gap:6px 14px;max-width:660px;margin:12px auto 0;}
- .dw-color-palette__legend a,.dw-color-palette__legend span{display:inline-flex;align-items:center;gap:5px;font-family:Lora,serif;font-size:11px;color:#3D4246;text-decoration:none;text-transform:capitalize;}
- .dw-color-palette__legend i{width:11px;height:11px;border-radius:2px;box-shadow:0 0 0 1px rgba(0,0,0,.12);}
+ .dw-color-palette__legend{display:flex;flex-wrap:wrap;justify-content:center;gap:6px 16px;max-width:660px;margin:13px auto 0;}
+ .dw-color-palette__legend a,.dw-color-palette__legend span{display:inline-flex;align-items:center;gap:6px;font-family:Lora,serif;font-size:11px;color:#3D4246;text-decoration:none;text-transform:capitalize;letter-spacing:.01em;}
+ .dw-color-palette__legend i{width:11px;height:11px;border-radius:3px;box-shadow:0 0 0 1px rgba(0,0,0,.12),inset 0 -2px 3px rgba(0,0,0,.05);}
.dw-color-palette__legend a:hover{color:#000;text-decoration:underline;}
.dw-color-palette:empty{display:none;}
@media (max-width:600px){
@@ -147,20 +154,27 @@
.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;}
+ /* ── In-page color-index grid the dot brings up — refined to read as a curated
+ "shop this colorway" drawer: hairline-ruled header, Lora title at the palette
+ scale, soft reveal, even gutters, hairline media. (refine 2026-07-09) ── */
+ .dw-color-index{max-width:660px;margin:20px auto 6px;text-align:left;border-top:1px solid #ece9e3;padding-top:16px;}
.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;}
+ @keyframes dwCiReveal{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:none;}}
+ .dw-color-index.is-open{animation:dwCiReveal .28s ease both;}
+ .dw-color-index__head{display:flex;align-items:center;gap:10px;margin:0 0 14px;}
+ .dw-color-index__swatch{width:22px;height:22px;border-radius:5px;flex:0 0 auto;box-shadow:0 0 0 1px rgba(0,0,0,.14),inset 0 -3px 5px rgba(0,0,0,.06);}
+ .dw-color-index__label{font-family:Lora,serif;font-size:14px;color:#3D4246;flex:1 1 auto;letter-spacing:.01em;}
+ .dw-color-index__label b{font-weight:600;text-transform:capitalize;}
+ .dw-color-index__close{flex:0 0 auto;background:none;border:1px solid #e0ded9;border-radius:50%;width:26px;height:26px;line-height:1;font-size:15px;color:#8a8577;cursor:pointer;padding:0;transition:color .12s ease,border-color .12s ease;}
+ .dw-color-index__close:hover{color:#3D4246;border-color:#b8b3a8;}
+ .dw-color-index__close:focus-visible{outline:2px solid #b8b3a8;outline-offset:2px;}
+ .dw-color-index__grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(122px,1fr));gap:16px 14px;}
.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__card img{width:100%;aspect-ratio:1;object-fit:cover;box-shadow:0 0 0 1px #ece9e3;border-radius:3px;background:#f4f2ee;transition:box-shadow .14s ease;}
+ .dw-color-index__card:hover img{box-shadow:0 0 0 1px #b8b3a8,0 3px 12px rgba(0,0,0,.10);}
+ .dw-color-index__cap{display:block;font-family:Lora,serif;font-size:11px;color:#3D4246;margin-top:7px;line-height:1.3;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;text-transform:capitalize;}
+ .dw-color-index__card:hover .dw-color-index__cap{text-decoration:underline;text-decoration-color:#d8d4cc;}
+ .dw-color-index__cap i{display:block;font-style:normal;color:#8a8577;font-size:10px;text-transform:none;margin-top:1px;letter-spacing:.02em;}
.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;}
@@ -285,10 +299,12 @@
return _ciCache[hex];
}
function ciEl(sel){ return CI ? CI.querySelector(sel) : null; }
- function closeIndex(){ if(CI){ CI.hidden=true; } }
+ function closeIndex(){ if(CI){ CI.classList.remove('is-open'); CI.hidden=true; } }
if(CI){
var _cx=ciEl('[data-dw-ci-close]');
if(_cx) _cx.addEventListener('click', closeIndex);
+ // Esc closes the drawer when it (or something inside it) is focused (a11y).
+ CI.addEventListener('keydown', function(ev){ if(ev.key==='Escape'){ closeIndex(); } });
}
/* Open the in-page index for a clicked hex. */
function openIndex(hex, name){
@@ -299,7 +315,10 @@
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;
+ // retrigger the soft reveal even on a re-click (refine 2026-07-09)
+ CI.classList.remove('is-open'); void CI.offsetWidth; CI.classList.add('is-open');
CI.scrollIntoView({behavior:'smooth', block:'nearest'});
+ var _cl=ciEl('[data-dw-ci-close]'); if(_cl){ try{ _cl.focus({preventScroll:true}); }catch(e){} }
fetchColorIndex(hex).then(function(d){
if(!grid) return;
var res=(d&&d.results)||[];
@@ -347,6 +366,30 @@
var r=Math.round(ca[0]+(cb[0]-ca[0])*t),g=Math.round(ca[1]+(cb[1]-ca[1])*t),bl=Math.round(ca[2]+(cb[2]-ca[2])*t);
return '#'+[r,g,bl].map(function(x){return x.toString(16).padStart(2,'0');}).join('');
}
+ /* ── perceptual ΔE (CIELAB, ΔE76) for VISUAL dedup of adjacent interpolated dots.
+ On a low-base, low-chroma pattern (e.g. an all-grey paisley) cranking the slider
+ high produces long runs of visually-identical adjacent swatches — meaningless
+ and it cheapens the curated feel. We skip an INTERPOLATED dot when it is within
+ ΔE < DEDUP_DE of the previously-placed swatch. Base (real) colors are NEVER
+ deduped — they're the pattern's actual colors. (refine 2026-07-09) */
+ function _rgb2lab(rgb){
+ var r=rgb[0]/255,g=rgb[1]/255,b=rgb[2]/255;
+ r=r>0.04045?Math.pow((r+0.055)/1.055,2.4):r/12.92;
+ g=g>0.04045?Math.pow((g+0.055)/1.055,2.4):g/12.92;
+ b=b>0.04045?Math.pow((b+0.055)/1.055,2.4):b/12.92;
+ var x=(r*0.4124+g*0.3576+b*0.1805)/0.95047,
+ y=(r*0.2126+g*0.7152+b*0.0722)/1.0,
+ z=(r*0.0193+g*0.1192+b*0.9505)/1.08883;
+ var f=function(t){return t>0.008856?Math.pow(t,1/3):(7.787*t)+16/116;};
+ x=f(x);y=f(y);z=f(z);
+ return [(116*y)-16, 500*(x-y), 200*(y-z)];
+ }
+ function deltaE(hexA,hexB){
+ var a=hexToRgb(hexA),b=hexToRgb(hexB); if(!a||!b) return 999;
+ var la=_rgb2lab(a),lb=_rgb2lab(b);
+ return Math.sqrt(Math.pow(la[0]-lb[0],2)+Math.pow(la[1]-lb[1],2)+Math.pow(la[2]-lb[2],2));
+ }
+ var DEDUP_DE = 2.3; // ~1 JND: below this, two swatches are indistinguishable
/* TOTAL rendered dots are driven by a user DENSITY SLIDER (Steve 2026-07-09d:
"allow the user to grid slide the amount of dots that they can see..
expand.. contract"). The slider value is the target TOTAL dot count. We keep
@@ -425,13 +468,22 @@
var list = _baseList;
var stepsPerGap = buildSteps(list.length, dotTarget);
var frag=document.createDocumentFragment();
+ var _lastHex=null; // last hex actually appended, for adjacent visual dedup
for(var i=0;i<list.length;i++){
+ // BASE colors are always shown (real pattern colors, never deduped).
frag.appendChild(makeSwatch(list[i].hex,{base:true,name:list[i].name,pct:list[i].pct}));
+ _lastHex=list[i].hex;
if(i<list.length-1){
var steps = stepsPerGap[i] || 0;
for(var k=1;k<=steps;k++){
var t=k/(steps+1);
- frag.appendChild(makeSwatch(mix(list[i].hex,list[i+1].hex,t),{base:false}));
+ var ih=mix(list[i].hex,list[i+1].hex,t);
+ // skip an interpolated dot that's visually identical to the previous one
+ // (dedup near-duplicates on low-chroma / low-base palettes). We still
+ // guarantee the NEXT base color renders because bases bypass this check.
+ if(_lastHex && deltaE(ih,_lastHex) < DEDUP_DE) continue;
+ frag.appendChild(makeSwatch(ih,{base:false}));
+ _lastHex=ih;
}
}
}
@@ -471,6 +523,11 @@
if(plus) plus.disabled = true;
return;
}
+ // Multi-color path: REVEAL the control (refine 2026-07-09 — critical live bug:
+ // the density slider markup ships with the `hidden` attr and this branch never
+ // cleared it, so the whole expand/contract control was invisible on every
+ // normal >1-color product). Unhide it here.
+ wrap.hidden = false;
wrap.classList.remove('is-single');
wrap.removeAttribute('title');
range.disabled = false;
← 5718e91a fix(sync): use global fetch in sync-shopify-products — daily
·
back to Designer Wallcoverings
·
auto-save: 2026-07-09T16:11:12 (9 files) — pending-approval/ ed16c8e2 →