← back to Dw Theme Boost Fix
snippets/dw-boost-overrides.liquid
984 lines
<!-- DW Grid Slider — CSS + Control + JS only (products rendered by Boost AI) -->
<style>
/* Override Boost color filter dots to match shop-by-color page palette */
.boost-sd__filter-option-swatch-item-img[style*="background-color: pink"]{background-color:#C9899A !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: red"]{background-color:#9B3B3B !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: orange"]{background-color:#C8784A !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: yellow"]{background-color:#D4B96A !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: green"]{background-color:#6B8E6B !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: blue"]{background-color:#4A6FA5 !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: cyan"]{background-color:#5F9EA0 !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: aqua"]{background-color:#5F9EA0 !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: purple"]{background-color:#7A6B8A !important}
.boost-sd__filter-option-swatch-item-img[style*="background-color: brown"]{background-color:#8B6F4E !important}
.boost-sd__filter-option-swatch-item-img{border-radius:50%}
/* Collapsed filter panel on load — full-width product grid */
body.dw-filters-collapsed .boost-sd__filter-tree-wrapper{display:none !important}
body.dw-filters-collapsed .boost-sd__filter-tree-vertical{display:none !important}
body.dw-filters-collapsed .boost-sd__product-list{width:100% !important;max-width:100% !important}
body.dw-filters-collapsed .boost-sd__filter-products-wrap{display:block !important;max-width:100% !important;width:100% !important}
body.dw-filters-collapsed .boost-sd__filter-products-wrap > div{display:block !important;max-width:100% !important;width:100% !important}
body.dw-filters-collapsed .boost-sd__product-list-wrapper{max-width:100% !important;width:100% !important;flex:1 !important}
body.dw-filters-collapsed .boost-sd__product-list{padding-right:0 !important;padding-left:0 !important}
body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(var(--dw-grid-cols, 6), 1fr) !important}
.boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(6, 1fr) !important}
.boost-sd__product-list{grid-template-columns:repeat(var(--dw-grid-cols, 6), 1fr) !important}
.boost-sd__product-list-grid--3-col{grid-template-columns:repeat(var(--dw-grid-cols, 6), 1fr) !important}
.boost-sd__product-list-grid--4-col{grid-template-columns:repeat(var(--dw-grid-cols, 6), 1fr) !important}
/* Force product items to fit grid columns */
.boost-sd__product-item,.boost-sd__product-item *{min-width:0 !important;max-width:100% !important;box-sizing:border-box !important}
.boost-sd__product-item{width:auto !important;overflow:hidden !important}
.boost-sd__product-item img{max-width:100% !important;height:auto !important;width:100% !important}
.boost-sd__product-list{overflow:hidden !important;display:grid !important;gap:16px !important;grid-gap:16px !important;column-gap:16px !important;row-gap:16px !important}
/* DW grid breathing room between products (Steve 2026-07-06); card internals stay tight */
.boost-sd__product-item{padding:0 !important;margin:0 !important}
.boost-sd__product-item-image,.boost-sd__product-item-inner,.boost-sd__product-item > *{margin:0 !important}
/* === MOBILE: 2 products per row + full-width index images (DW 2026-06-23) === */
@media (max-width:768px){
/* full-bleed: kill Boost's reserved side padding + width cap so the 2 columns
span the entire viewport (mobile never gets .dw-filters-collapsed, which is
what strips this padding on desktop) */
.boost-sd-right,
.boost-sd__product-list-wrapper,
.boost-sd__product-list{
width:100% !important;max-width:100% !important;
padding-left:0 !important;padding-right:0 !important;
}
.boost-sd__product-list,
.boost-sd__product-list[class*="grid--"],
body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{
grid-template-columns:repeat(2,1fr) !important;
}
/* === Unique mobile card template (officer-refined 2026-06-23) ===
Fix "images cut off": show the WHOLE pattern, never crop.
- object-fit:contain (was cover) on a paper-white ground, so square
swatches fill the cell and only odd-ratio images get a subtle margin.
- title + vendor move BELOW the image (was an absolute overlay covering
the pattern). Card becomes a clean vertical stack: image -> name -> vendor. */
.boost-sd__product-item{
display:flex !important;flex-direction:column !important;
}
.boost-sd__product-image-wrapper,
[class*="product-image-wrap"]{
aspect-ratio:1/1 !important;overflow:hidden !important;
background:#FAFAF8 !important; /* paper-white ground, never black bars */
width:100% !important;height:auto !important;
}
.boost-sd__product-item img,
.boost-sd__product-image-wrapper img,
[class*="product-image-wrap"] img,
.product-list-item img,
.product-list-item-thumbnail img{
width:100% !important;height:100% !important;display:block !important;
object-fit:contain !important; /* contain = full image, nothing cut off */
object-position:center !important;
}
/* Title + vendor: out of the overlay, in normal flow below the image */
.boost-sd__product-title,
.boost-sd__product-vendor,
.boost-sd__product-info{
position:static !important;width:100% !important;
background:none !important;text-align:left !important;opacity:1 !important;
inset:auto !important;
}
.boost-sd__product-title,.boost-sd__product-title a{
font-size:13px !important;font-weight:500 !important;
color:#1A1A1A !important;line-height:1.3 !important;padding:6px 4px 0 !important;
}
.boost-sd__product-vendor{
font-size:11px !important;letter-spacing:.06em !important;
text-transform:uppercase !important;color:#8A8A85 !important;padding:2px 4px 2px !important;
}
/* === Appended infinite-scroll cards (neutral .dw-if-* classes so no Boost/theme
script strips the <img>). Styled to MATCH the page-1 cards: contained image
on a paper ground with the name/vendor overlaid at the bottom. === */
.dw-if-card{position:relative !important;display:block !important;}
.dw-if-card > a{display:block !important;text-decoration:none !important;color:#fff !important;}
.dw-if-img{aspect-ratio:1/1 !important;width:100% !important;background:#FAFAF8 !important;overflow:hidden !important;}
.dw-if-img img{width:100% !important;height:100% !important;object-fit:contain !important;display:block !important;}
.dw-if-card .dw-if-name,
.dw-if-card .dw-if-ven{position:absolute !important;left:0;right:0;padding:0 8px !important;
text-align:center !important;color:#fff !important;text-shadow:0 1px 5px rgba(0,0,0,.65) !important;
pointer-events:none !important;}
.dw-if-card .dw-if-name{bottom:26px !important;font-size:13px !important;font-weight:600 !important;line-height:1.2 !important;}
.dw-if-card .dw-if-ven{bottom:8px !important;font-size:10px !important;letter-spacing:.08em !important;text-transform:uppercase !important;opacity:.92 !important;}
/* === Uniform toolbar pills (DW 2026-06-23): every control the same size, simple ===
Sort, Refine By, Filters, Show, and Wallcoverings/Fabrics/More -> one pill shape. */
.dw-filter-toggle,
.dw-grid-slider,
.dw-ptype-bar,
.boost-sd__toolbar-item--sorting,
.boost-sd__filter-tree-toggle-button{
box-sizing:border-box !important;
height:34px !important;min-height:34px !important;
display:inline-flex !important;align-items:center !important;justify-content:center !important;
padding:0 14px !important;margin:5px 6px 0 0 !important;
border:1.5px solid #1A1A1A !important;border-radius:999px !important;
background:#fff !important;color:#1A1A1A !important;
font-size:11px !important;font-weight:600 !important;letter-spacing:.06em !important;
line-height:1 !important;white-space:nowrap !important;
}
/* normalize inner text/controls of the native Boost sort + refine so they match */
.boost-sd__toolbar-item--sorting *,
.boost-sd__filter-tree-toggle-button,
.boost-sd__filter-tree-toggle-button *{
font-size:11px !important;padding-bottom:0 !important;line-height:1 !important;
text-transform:uppercase !important;letter-spacing:.06em !important;
}
.dw-grid-slider select,.dw-grid-slider label,
.dw-ptype-bar label{ font-size:11px !important;font-weight:600 !important; }
}
/* Filter toggle button */
.dw-filter-toggle{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;margin:4px 0 4px 8px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;user-select:none;background:#fff;border:1.5px solid #000;border-radius:999px;cursor:pointer;vertical-align:middle}
.dw-filter-toggle:hover{background:#f5f5f5}
.dw-filter-toggle svg{width:14px;height:14px}
/* Hide static grid slider from main section — dynamic one is injected into Boost toolbar */
.shopify-section > .dw-grid-slider{display:none !important}
/* Hide old dw-gc grid control — replaced by toolbar grid slider */
.dw-gc{display:none !important}
/* Per-page selector — inside grid pill */
/* === Product type — INDIVIDUAL pills (DW 2026-06-25 consolidation) ===
The container is now a transparent flex wrapper (NO border / NO pill).
Each Wallcoverings / Fabrics / More <label> is its OWN bordered rounded
chip, uniform height with the FILTERS / SORT pills (see .dw-pill-uniform
group rule below). Active (checked) chip fills black. */
.dw-ptype-bar{display:inline-flex;align-items:center;gap:6px;margin:5px 0 0 0;padding:0;background:none;border:0;font-family:-apple-system,BlinkMacSystemFont,sans-serif;user-select:none}
.dw-ptype-bar label{
box-sizing:border-box;display:inline-flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap;
height:34px;min-height:34px;padding:0 14px;margin:0;
border:1.5px solid #1A1A1A;border-radius:999px;background:#fff;color:#1A1A1A;
font-size:11px;font-weight:600;letter-spacing:.06em;text-transform:uppercase;line-height:1;
}
.dw-ptype-bar label:hover{background:#f5f5f5}
/* checked chip = filled (set via .dw-ptype-on class by JS so it works without :has) */
.dw-ptype-bar label.dw-ptype-on{background:#1A1A1A;color:#fff}
.dw-ptype-bar input[type=checkbox]{width:13px;height:13px;margin:0;cursor:pointer;accent-color:#3D4246}
.dw-ptype-bar .dw-ptype-sep{display:none !important} /* separators removed — pills stand alone */
/* === SINGLE uniform-height rule for ALL toolbar pills (DW 2026-06-25) ===
Applies on DESKTOP too (the old rule was inside @media max-width:768 only,
so live desktop pills were inconsistent). Every control — the FILTERS toggle,
the grid/Show pill, Boost's native SORT + Refine-By, and each product-type
chip — shares one 34px height + radius. This is the "same height" guarantee. */
.dw-filter-toggle,
.dw-grid-slider,
.dw-ptype-bar label,
.boost-sd__toolbar-item--sorting,
.boost-sd__filter-tree-toggle-button{
box-sizing:border-box !important;
height:34px !important;min-height:34px !important;
display:inline-flex !important;align-items:center !important;
padding:0 14px !important;margin:5px 6px 0 0 !important;
border:1.5px solid #1A1A1A !important;border-radius:999px !important;
background:#fff;color:#1A1A1A;
font-size:11px;font-weight:600;letter-spacing:.06em;line-height:1;white-space:nowrap;
}
.dw-ptype-bar label.dw-ptype-on{background:#1A1A1A !important;color:#fff !important}
.dw-perpage-inline{display:inline-flex;align-items:baseline;gap:4px;margin-left:10px;padding-left:10px;border-left:1px solid #ccc}
.dw-perpage-inline select{border:none;background:transparent;font-size:14px;font-weight:700;cursor:pointer;padding:0;margin:0;-webkit-appearance:none;appearance:none}
.dw-perpage-inline label{color:#000;font-size:14px;font-weight:700;text-transform:uppercase;letter-spacing:1px}
/* Fix doubled product titles in Boost cards */
.boost-sd__product-title a{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
</style>
<style>
.dw-grid-slider{display:inline-flex;align-items:center;gap:6px;padding:4px 12px;margin:4px 0 4px 10px;font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;user-select:none;background:#fff;border:1.5px solid #000;border-radius:999px}
.dw-grid-slider label{color:#000;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px}
.dw-grid-slider input[type=range]{-webkit-appearance:none;width:120px;height:3px;background:#ccc;border-radius:2px;outline:none;cursor:pointer}
.dw-grid-slider input[type=range]::-webkit-slider-thumb{-webkit-appearance:none;width:16px;height:16px;background:#000;border-radius:50%;cursor:pointer}
.dw-grid-slider input[type=range]::-moz-range-thumb{width:16px;height:16px;background:#000;border-radius:50%;cursor:pointer;border:none}
.dw-grid-slider .gc-num{color:#000;font-weight:700;font-size:14px;min-width:14px;text-align:center}
.collection-products.rows-of-3 .product-list-item,.collection-products.rows-of-3 .product-grid-masonry-sizer{width:33.333%}
.collection-products.rows-of-4 .product-list-item,.collection-products.rows-of-4 .product-grid-masonry-sizer{width:25%}
.collection-products.rows-of-5 .product-list-item,.collection-products.rows-of-5 .product-grid-masonry-sizer{width:20%}
.collection-products.rows-of-6 .product-list-item,.collection-products.rows-of-6 .product-grid-masonry-sizer{width:16.666%}
.collection-products.rows-of-7 .product-list-item,.collection-products.rows-of-7 .product-grid-masonry-sizer{width:14.285%}
.collection-products.rows-of-8 .product-list-item,.collection-products.rows-of-8 .product-grid-masonry-sizer{width:12.5%}
.collection-products.rows-of-5 .product-list-item .product-vendor,.collection-products.rows-of-5 .product-list-item .product-title,
.collection-products.rows-of-6 .product-list-item .product-vendor,.collection-products.rows-of-6 .product-list-item .product-title,
.collection-products.rows-of-7 .product-list-item .product-vendor,.collection-products.rows-of-7 .product-list-item .product-title,
.collection-products.rows-of-8 .product-list-item .product-vendor,.collection-products.rows-of-8 .product-list-item .product-title{display:none}
.collection-products.rows-of-5 .product-list-item .product-price,
.collection-products.rows-of-6 .product-list-item .product-price,
.collection-products.rows-of-7 .product-list-item .product-price,
.collection-products.rows-of-8 .product-list-item .product-price{font-size:11px}
@media(max-width:768px){
.collection-products.rows-of-5 .product-list-item,.collection-products.rows-of-6 .product-list-item,.collection-products.rows-of-7 .product-list-item,.collection-products.rows-of-8 .product-list-item{width:50%}
}
/* ===== Amazon-style LEFT filter rail (desktop) — DW 2026-07-14 =====
Un-suppress Boost's native vertical tree that .dw-filters-collapsed hides
(rules ~17-24 above) and lay the collection out as a 2-column rail + grid.
Desktop only (>=769px); mobile keeps Boost's native filter drawer untouched.
Placed AFTER the collapse rules so it wins at equal specificity — no arms race. */
@media (min-width:769px){
body.dw-filters-collapsed .boost-sd__filter-tree-wrapper,
body.dw-filters-collapsed .boost-sd__filter-tree-vertical,
body.dw-filters-collapsed .boost-sd__filter-tree-vertical-content,
body.dw-filters-collapsed .boost-sd__filter-tree-vertical-refine-by{display:block !important;width:auto !important;visibility:visible !important}
.boost-sd-layout--has-filter-vertical{display:flex !important;flex-direction:row !important;align-items:flex-start !important;gap:30px !important}
.boost-sd-left{display:block !important;width:264px !important;flex:0 0 264px !important;max-width:264px !important;min-width:264px !important;position:sticky !important;top:14px !important;padding-right:22px !important;border-right:1px solid rgba(0,0,0,.09) !important;overflow:visible !important}
body.dw-filters-collapsed .boost-sd-right,
.boost-sd-right{flex:1 1 auto !important;width:auto !important;min-width:0 !important;max-width:none !important}
body.dw-filters-collapsed .boost-sd__product-list{width:100% !important;max-width:100% !important}
.boost-sd-left .dw-rail-dept{margin:2px 0 18px}
.boost-sd-left .dw-ptype-bar{display:flex !important;flex-wrap:wrap;gap:8px}
.dw-hfilter-bar{display:none !important}
}
</style>
<script>
(function(){
/* DESKTOP ONLY: hide Boost's native vertical tree + run the horizontal bar.
On mobile (<=768) Boost renders its own Filter-button drawer and lazy-loads
the filter options only when opened — so we must NOT hide it or try to read
it; leave the native mobile filter completely alone. */
if (window.innerWidth > 768) {
document.body.classList.add('dw-filters-collapsed');
/* Horizontal filter bar scope hook (Approach B — dw-hfilter.*); dw-filters-collapsed stays the always-on state engine that keeps Boost's native tree hidden */
document.body.classList.add('dw-filters-horizontal');
/* Restore the horizontal-bar collapsed state (our own bar, NOT the native tree) */
try { if (localStorage.getItem('dw-hfilter-collapsed') === '1') document.body.classList.add('dw-hbar-collapsed'); } catch(e){}
}
/* Set CSS var for default 6 cols */
document.documentElement.style.setProperty('--dw-grid-cols', 6);
/* === Amazon-style LEFT rail (desktop): move the product-type pill into Boost's
native left column and drop the now-redundant Hide-Filters toggle. The CSS
above un-hides the native tree + lays out the 2 columns; this only relocates
our custom controls. Self-healing poller so it works regardless of Boost's
async render timing. Mobile (<=768) is left entirely alone. */
if (window.innerWidth > 768) {
/* Boost injects its own CSS at RUNTIME (after any static inline <style>), so a
static override loses the source-order tie. Instead: drop the collapse class
(so Boost's native left/vertical layout is no longer suppressed) and append
our rail CSS at runtime so it lands LAST and wins. Mirrors the verified path. */
document.body.classList.remove('dw-filters-collapsed', 'dw-filters-horizontal', 'dw-hbar-collapsed');
var __railCss = document.createElement('style');
__railCss.textContent =
'@media (min-width:769px){' +
'.boost-sd-layout--has-filter-vertical{display:flex !important;flex-direction:row !important;align-items:flex-start !important;gap:30px !important}' +
'.boost-sd-left{display:block !important;width:264px !important;flex:0 0 264px !important;max-width:264px !important;min-width:264px !important;position:sticky !important;top:14px !important;padding-right:22px !important;border-right:1px solid rgba(0,0,0,.09) !important;overflow:visible !important}' +
'.boost-sd__filter-tree-wrapper,.boost-sd__filter-tree-vertical,.boost-sd__filter-tree-vertical-content,.boost-sd__filter-tree-vertical-refine-by{display:block !important;width:auto !important;visibility:visible !important}' +
'.boost-sd-right{flex:1 1 auto !important;width:auto !important;min-width:0 !important;max-width:none !important}' +
'.boost-sd__product-list{width:100% !important;max-width:100% !important}' +
'.boost-sd-left .dw-rail-dept{margin:2px 0 18px}' +
'.boost-sd-left .dw-ptype-bar{display:flex !important;flex-wrap:wrap;gap:8px}' +
'.dw-hfilter-bar{display:none !important}' +
'.dw-filter-toggle{display:none !important}' + /* redundant Hide/Show-Filters toggle: rail is always visible on desktop */
'}';
document.head.appendChild(__railCss);
var __railTries = 0;
var __railTimer = setInterval(function(){
__railTries++;
/* Boost may re-add the collapse class on re-render — keep it off on desktop */
document.body.classList.remove('dw-filters-collapsed');
/* Boost injects CSS at runtime; re-append ours so it stays LAST and wins */
document.head.appendChild(__railCss);
var left = document.querySelector('.boost-sd-left');
var ptype = document.querySelector('.dw-ptype-bar');
if (left && ptype && !left.contains(ptype)) {
var dept = left.querySelector('.dw-rail-dept');
if (!dept) { dept = document.createElement('div'); dept.className = 'dw-rail-dept'; left.insertBefore(dept, left.firstChild); }
dept.appendChild(ptype);
}
var btns = document.querySelectorAll('button, a, label');
for (var i = 0; i < btns.length; i++) {
var t = (btns[i].textContent || '').trim().toLowerCase();
if (t === 'hide filters' || t === 'show filters') btns[i].style.display = 'none';
}
if ((left && ptype && left.contains(ptype)) || __railTries > 40) clearInterval(__railTimer);
}, 250);
}
var initialized = false;
/* NO product-type persistence: every load starts with nothing selected so ALL
products show, until the user toggles a pill. Product-type state is driven
purely by the URL's pf_t_product_type params (set on click), never re-applied
from a saved choice on load. (Deliberately no auto-apply block here.) */
function initAll(){
if (initialized) return true;
var boostGrid = document.querySelector('.boost-sd__product-list');
var toolbar = document.querySelector('.boost-sd__toolbar-item--product-count, .boost-sd__product-count');
if (!boostGrid || !toolbar) return false;
initialized = true;
/* === GRID SLIDER === */
var saved = localStorage.getItem('dw-grid-cols');
var defaultCols = (saved && parseInt(saved) >= 3 && parseInt(saved) <= 8) ? parseInt(saved) : 6;
function applyGrid(val){
/* Mobile is locked to 2 columns; desktop keeps the 3–8 range. */
var isMobile = window.innerWidth <= 768;
val = isMobile ? 2 : Math.max(3, Math.min(8, parseInt(val) || 6));
document.documentElement.style.setProperty('--dw-grid-cols', val);
boostGrid.style.setProperty('grid-template-columns', 'repeat(' + val + ', 1fr)', 'important');
var num = document.getElementById('dw-grid-num');
var slider = document.getElementById('dw-grid-range');
if (num) num.textContent = val;
if (slider) slider.value = val;
}
/* === GRID DENSITY SLIDER (DW 2026-06-25 consolidation: RE-ADDED) ===
Drives Boost's REAL grid via applyGrid() -> --dw-grid-cols +
boostGrid.grid-template-columns; persists to localStorage('dw-grid-cols').
Desktop only (mobile is locked to 2-col + infinite scroll). The pill also
hosts the per-page "Show" selector below. */
var sliderWrap = document.createElement('div');
sliderWrap.className = 'dw-grid-slider';
if (window.innerWidth > 768) {
var gLbl = document.createElement('label');
gLbl.textContent = 'Grid';
var gRange = document.createElement('input');
gRange.type = 'range'; gRange.id = 'dw-grid-range';
gRange.min = '3'; gRange.max = '8'; gRange.step = '1';
gRange.value = String(defaultCols);
var gNum = document.createElement('span');
gNum.className = 'gc-num'; gNum.id = 'dw-grid-num';
gNum.textContent = String(defaultCols);
gRange.addEventListener('input', function(){
applyGrid(this.value); /* live re-flow */
try { localStorage.setItem('dw-grid-cols', String(this.value)); } catch(e){}
});
sliderWrap.appendChild(gLbl);
sliderWrap.appendChild(gRange);
sliderWrap.appendChild(gNum);
}
/* === FILTER TOGGLE === */
var btn = document.createElement('button');
btn.id = 'dw-filter-toggle';
btn.className = 'dw-filter-toggle';
btn.setAttribute('aria-label', 'Toggle filters');
var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('viewBox', '0 0 24 24');
svg.setAttribute('fill', 'none');
svg.setAttribute('stroke', 'currentColor');
svg.setAttribute('stroke-width', '2');
svg.style.cssText = 'width:14px;height:14px';
var ys = ['6','12','18'];
for (var i = 0; i < ys.length; i++) {
var l = document.createElementNS('http://www.w3.org/2000/svg', 'line');
l.setAttribute('x1','4'); l.setAttribute('y1',ys[i]);
l.setAttribute('x2','20'); l.setAttribute('y2',ys[i]);
svg.appendChild(l);
}
btn.appendChild(svg);
var filterLabel = document.createElement('span');
filterLabel.id = 'dw-filter-label';
/* Initial label reflects our horizontal-bar state (default = bar visible -> 'Hide Filters') */
filterLabel.textContent = document.body.classList.contains('dw-hbar-collapsed') ? 'Filters' : 'Hide Filters';
btn.appendChild(filterLabel);
btn.addEventListener('click', function(){
var collapsed = document.body.classList.toggle('dw-hbar-collapsed');
filterLabel.textContent = collapsed ? 'Filters' : 'Hide Filters';
try { localStorage.setItem('dw-hfilter-collapsed', collapsed ? '1' : '0'); } catch(e){}
});
/* Build per-page selector INSIDE the grid slider pill */
var perpageInline = document.createElement('span');
perpageInline.className = 'dw-perpage-inline';
var perpageLbl = document.createElement('label');
perpageLbl.textContent = 'Show';
var perpageSelect = document.createElement('select');
perpageSelect.id = 'dw-perpage';
var options = [24, 25, 50, 100];
var currentLimit = parseInt(new URLSearchParams(window.location.search).get('limit')) || 24;
options.forEach(function(val) {
var opt = document.createElement('option');
opt.value = val;
opt.textContent = val;
if (val === currentLimit) opt.selected = true;
perpageSelect.appendChild(opt);
});
perpageSelect.addEventListener('change', function() {
var url = new URL(window.location.href);
url.searchParams.set('limit', this.value);
url.searchParams.delete('page');
window.location.href = url.toString();
});
perpageInline.appendChild(perpageLbl);
perpageInline.appendChild(perpageSelect);
sliderWrap.appendChild(perpageInline);
/* === PRODUCT TYPE CHECKBOXES === */
var ptypeBar = document.createElement('div');
ptypeBar.className = 'dw-ptype-bar';
var types = [
{label: 'Wallcoverings', value: 'Wallcovering', defaultOn: false},
{label: 'Fabrics', value: 'Fabric', defaultOn: false},
{label: 'More', value: '', defaultOn: false}
];
// Pills reflect the URL ONLY (no persistence): a fresh load with no
// pf_t_product_type shows everything ("More"/All active); a filtered URL
// checks the matching pills. So state never sticks across loads.
var activeTypes = new URL(window.location.href).searchParams.getAll('pf_t_product_type');
function applyTypeFilter() {
var checked = [];
ptypeBar.querySelectorAll('input[type=checkbox]').forEach(function(cb) {
if (cb.checked && cb.value) checked.push(cb.value);
});
// (no sessionStorage — selection lives only in the URL, so it never persists across loads)
// Apply via URL parameter for Boost
var url = new URL(window.location.href);
url.searchParams.delete('pf_t_product_type');
if (checked.length > 0 && checked.length < types.filter(function(t){return t.value;}).length) {
checked.forEach(function(v) { url.searchParams.append('pf_t_product_type', v); });
}
// Remove page param to reset
url.searchParams.delete('page');
window.location.href = url.toString();
}
types.forEach(function(t, i) {
if (i > 0) {
var sep = document.createElement('span');
sep.className = 'dw-ptype-sep';
ptypeBar.appendChild(sep);
}
var lbl = document.createElement('label');
var cb = document.createElement('input');
cb.type = 'checkbox';
cb.value = t.value;
// "More" = everything else (no value, acts as "all others")
if (t.value) {
cb.checked = activeTypes.indexOf(t.value) >= 0;
} else {
// "More" is NEVER auto-lit on load — it's a show-all/reset action, not a
// persistent state. Empty selection = nothing highlighted (all products show).
cb.checked = false;
}
if (cb.checked) lbl.classList.add('dw-ptype-on'); /* fill active chip */
cb.addEventListener('change', function(){
/* Segmented semantics: "More"/All is mutually exclusive with the
specific types. Selecting All clears the specific types; selecting a
specific type clears All — so clicking All actually resets to all
products instead of leaving a stale type filter applied. */
if (this.checked) {
var selectingAll = !this.value;
ptypeBar.querySelectorAll('input[type=checkbox]').forEach(function(o){
if (o === cb) return;
if (selectingAll ? !!o.value : !o.value) {
o.checked = false;
var ol = o.closest('label'); if (ol) ol.classList.remove('dw-ptype-on');
}
});
}
/* reflect filled/unfilled immediately, then navigate */
var l = this.closest('label'); if (l) l.classList.toggle('dw-ptype-on', this.checked);
applyTypeFilter();
});
lbl.appendChild(cb);
var txt = document.createTextNode(' ' + t.label);
lbl.appendChild(txt);
ptypeBar.appendChild(lbl);
});
/* Insert controls into Boost toolbar */
var toolbarRow = toolbar.parentElement;
toolbarRow.insertBefore(btn, toolbar);
toolbarRow.insertBefore(sliderWrap, toolbar);
toolbarRow.insertBefore(ptypeBar, toolbar);
/* === VISIBLE-TOOLBAR RE-MOUNT (DW 2026-06-27, vp-dw-commerce) ===
Boost renders SEVERAL toolbar variants and hides all but one via
display:none (live = .boost-sd__toolbar--3_1 is hidden). The product-count
item we anchor to (toolbar.parentElement) can therefore be inside a HIDDEN
variant, so the density slider + filter toggle + product-type bar never
reach real users — only the color-sort pill survives because IT already
has a fallback into the visible .boost-sd__toolbar-inner.
Fix (mirrors the proven color-sort mount EXACTLY): only when our anchored
controls land in a HIDDEN toolbar, MOVE them into the same visible
.boost-sd__toolbar-inner / .boost-sd__toolbar-content container the color
pill uses. When --3_1 (or whatever variant) IS visible, this no-ops, so the
existing multi-variant robustness is preserved — we never regress the
already-working case, we only rescue the hidden one. Deferred so Boost has
finished sizing the variants (offsetParent is reliable post-layout). */
function isShownEl(el){ return !!(el && el.offsetParent !== null); }
(function remountIfHidden(tries){
tries = tries || 0;
/* If our slider is already visible to the user, nothing to do. */
if (isShownEl(sliderWrap)) return;
/* The slider/toggle/ptype were anchored next to the product-count item,
which lives inside the HIDDEN .boost-sd__toolbar--3_1 row. The VISIBLE
container Boost actually paints is .boost-sd__toolbar-inner (the same one
the color-sort pill resolves to and mounts as a DIRECT child of — proven
live). Note: that inner is an ANCESTOR of the hidden --3_1, so a naive
.contains(slider) check is true even though the slider isn't visible — do
NOT reject the target on contains(); instead append our controls as DIRECT
children of the visible inner, which LIFTS them out of the hidden --3_1. */
var inners = [].slice.call(document.querySelectorAll(
'.boost-sd__toolbar-inner, .boost-sd__toolbar-content'));
var target = null;
for (var i = 0; i < inners.length; i++){
/* skip an inner whose DIRECT child is already our slider (== it's visible,
we'd have returned above) — but accept a visible inner that only
transitively contains the slider via the hidden --3_1 row. */
if (isShownEl(inners[i]) && sliderWrap.parentElement !== inners[i]) { target = inners[i]; break; }
}
if (target){
/* Move (not clone) so all wired event listeners + state come along.
appendChild relocates them out of the hidden --3_1 into the visible inner. */
target.appendChild(btn);
target.appendChild(sliderWrap);
target.appendChild(ptypeBar);
return;
}
/* Boost may still be laying out / swapping the visible variant — retry a
few times, then give up (worst case = current pre-fix behavior, no regression). */
if (tries < 40) setTimeout(function(){ remountIfHidden(tries + 1); }, 250);
})(0);
/* Apply grid and watch for Boost overrides */
applyGrid(defaultCols);
/* Re-apply when crossing the mobile breakpoint (rotate / resize) */
window.addEventListener('resize', function(){ applyGrid(defaultCols); });
/* Observe Boost grid for style changes and re-apply our columns + kill padding */
var observer = new MutationObserver(function(){
/* Mobile = 2; desktop = whatever applyGrid last wrote to --dw-grid-cols */
var current = window.innerWidth <= 768
? 2
: (parseInt(getComputedStyle(document.documentElement).getPropertyValue('--dw-grid-cols')) || defaultCols);
boostGrid.style.setProperty('display', 'grid', 'important');
boostGrid.style.setProperty('grid-template-columns', 'repeat(' + current + ', 1fr)', 'important');
if (document.body.classList.contains('dw-filters-collapsed') || window.innerWidth <= 768) {
boostGrid.style.setProperty('padding-right', '0', 'important');
boostGrid.style.setProperty('padding-left', '0', 'important');
}
});
observer.observe(boostGrid, {attributes: true, attributeFilter: ['style', 'class']});
/* Force display:grid + kill padding immediately */
boostGrid.style.setProperty('display', 'grid', 'important');
if (document.body.classList.contains('dw-filters-collapsed') || window.innerWidth <= 768) {
boostGrid.style.setProperty('padding-right', '0', 'important');
boostGrid.style.setProperty('padding-left', '0', 'important');
}
/* Hide any leftover static grid slider */
var old = document.querySelectorAll('div.dw-grid-slider');
for (var j = 1; j < old.length; j++) old[j].style.display = 'none';
return true;
}
var retries = 0;
var poll = setInterval(function(){
if (initAll() || retries++ > 60) clearInterval(poll);
}, 250);
})();
</script>
<script>
/* === MOBILE INFINITE SCROLL (DW 2026-06-23) ===
Boost runs numbered ("default") pagination + renders client-side. On mobile we
replace the pager with TRUE infinite scroll by replaying Boost's own product
API (services.mybcapps.com/bc-sf-filter/filter) one page at a time and
appending rendered cards. Mobile only; top window only; desktop untouched. */
(function(){
if (window.top !== window.self) return;
function isMobile(){ return window.innerWidth <= 768; }
if (window.__dwInfScroll) return; window.__dwInfScroll = true;
var LIMIT = 24;
var capturedUrl = null;
var loadedPage = parseInt(new URLSearchParams(location.search).get('page') || '1', 10) || 1;
var total = Infinity, loading = false, done = false, started = false;
/* Capture Boost's exact filter request so sort + filters + session stay correct */
var _fetch = window.fetch.bind(window);
try {
window.fetch = function(input){
try {
var u = (typeof input === 'string') ? input : (input && input.url);
if (u && u.indexOf('bc-sf-filter/filter') !== -1) capturedUrl = u;
} catch(e){}
return _fetch.apply(this, arguments);
};
} catch(e){}
function buildUrl(page){
var u;
if (capturedUrl) { u = new URL(capturedUrl); }
else {
u = new URL('https://services.mybcapps.com/bc-sf-filter/filter');
u.searchParams.set('shop','designer-laboratory-sandbox.myshopify.com');
u.searchParams.set('sort', new URLSearchParams(location.search).get('sort_by') || 'title-ascending');
u.searchParams.set('event_type','filter');
u.searchParams.set('pg','collection_page');
u.searchParams.set('build_filter_tree','true');
u.searchParams.set('collection_scope','0');
u.searchParams.set('product_available','false');
u.searchParams.set('variant_available','false');
u.searchParams.set('urlScheme','2');
new URLSearchParams(location.search).forEach(function(v,k){ if(/^pf_/.test(k)) u.searchParams.append(k,v); });
}
u.searchParams.set('page', page);
u.searchParams.set('limit', LIMIT);
u.searchParams.set('t', Date.now());
return u.toString();
}
function nameOf(t){ return (t||'').split('|')[0].replace(/\s*wallcoverings?\b/ig,'').replace(/[,\s]+$/,'').trim(); }
function imgOf(p){
if (p.images_info && p.images_info[0] && p.images_info[0].src) return p.images_info[0].src;
if (p.images && typeof p.images === 'object'){
var k = Object.keys(p.images)[0];
if (k){ var v = p.images[k]; return (v && v.src) || (typeof v === 'string' ? v : '') || ''; }
}
return p.image || '';
}
function cardHtml(p){
var img = imgOf(p);
if (img) img += (img.indexOf('?') > -1 ? '&' : '?') + 'width=600';
var nm = nameOf(p.title) || (p.title||'');
/* NEUTRAL classes (dw-if-*) so Boost/theme card scripts never re-process and
strip our <img>. Styled by our own .dw-if-card CSS in the mobile block. */
return '<div class="dw-if-card">' +
'<a href="/products/'+ p.handle +'">' +
'<div class="dw-if-img">' +
(img ? '<img loading="lazy" src="'+ img +'" alt="'+ nm.replace(/"/g,'"') +'">' : '') +
'</div>' +
'<div class="dw-if-name">'+ nm +'</div>' +
'<div class="dw-if-ven">'+ (p.vendor || '') +'</div>' +
'</a></div>';
}
function grid(){ return document.querySelector('.boost-sd__product-list'); }
function spinner(g, show){
var sp = document.getElementById('dw-if-spin');
if (show && !sp){
sp = document.createElement('div'); sp.id = 'dw-if-spin';
sp.textContent = 'Loading more…';
sp.style.cssText = 'grid-column:1/-1;text-align:center;padding:22px 0;font:600 11px/1 -apple-system,sans-serif;letter-spacing:.14em;text-transform:uppercase;color:#8A8A85';
g.parentNode.insertBefore(sp, g.nextSibling);
} else if (!show && sp) { sp.remove(); }
}
function loadMore(){
if (loading || done || !isMobile()) return;
var g = grid(); if (!g) return;
if (loadedPage * LIMIT >= total) { done = true; return; }
loading = true; spinner(g, true);
_fetch(buildUrl(loadedPage + 1), { headers: { 'Accept':'application/json' } })
.then(function(r){ return r.json(); })
.then(function(j){
var t = j.total_product != null ? j.total_product : j.total;
if (typeof t === 'number') total = t;
var prods = j.products || [];
if (!prods.length) { done = true; spinner(g, false); loading = false; return; }
var frag = document.createElement('div');
frag.innerHTML = prods.map(cardHtml).join('');
while (frag.firstChild) g.appendChild(frag.firstChild);
loadedPage++; loading = false; spinner(g, false);
if (loadedPage * LIMIT >= total) done = true;
setTimeout(onScroll, 60);
})
.catch(function(){ loading = false; spinner(g, false); });
}
function onScroll(){
if (loading || done || !isMobile()) return;
if (window.innerHeight + window.scrollY > document.documentElement.scrollHeight - 1200) loadMore();
}
function hidePager(){
if (document.getElementById('dw-if-css')) return;
var s = document.createElement('style'); s.id = 'dw-if-css';
s.textContent = '@media(max-width:768px){.boost-sd__pagination{display:none !important}}';
document.head.appendChild(s);
}
function start(){
if (started || !isMobile()) return;
var tries = 0;
var c = setInterval(function(){
tries++;
if (grid()) { clearInterval(c); started = true; hidePager(); window.addEventListener('scroll', onScroll, { passive:true }); window.addEventListener('resize', onScroll, { passive:true }); onScroll(); }
else if (tries > 50) clearInterval(c);
}, 400);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
else start();
})();
</script>
<!-- DW REC GRID FIX — You-May-Also-Like: 3-up+text on mobile, 4-up image-only on desktop (2026-06-23) -->
<style id="dw-rec-grid-fix">
/* MOBILE: 3 fixed columns, keep the title/vendor overlay text */
@media (max-width:768px){
.product-recommendations-wrapper--section .product-recommendations{
display:grid !important;
grid-template-columns:repeat(3,1fr) !important;
gap:10px 8px !important;
position:static !important;
height:auto !important;
}
.product-recommendations-wrapper--section .product-recommendations .product-list-item{
width:auto !important;float:none !important;position:static !important;
left:auto !important;top:auto !important;right:auto !important;margin:0 !important;
}
.product-recommendations-wrapper--section .product-recommendations .product-grid-masonry-sizer{display:none !important}
}
/* DESKTOP (4-up rows-of-4): image-only — hide all title/vendor/hover text */
@media (min-width:769px){
.product-recommendations-wrapper--section .product-recommendations .dw-hover-overlay,
.product-recommendations-wrapper--section .product-recommendations .product-list-item-details{display:none !important}
}
</style>
<!-- DW SHOP-BY-COLOR ONE-ROW — all color dots on a single row (2026-06-23) -->
<style id="dw-sbc-onerow">
#sbc-circles.sbc-circles, .sbc-circles{
display:flex !important; flex-wrap:nowrap !important;
justify-content:safe center !important; align-items:flex-start !important;
gap:clamp(4px,0.9vw,14px) !important; width:100% !important; max-width:100% !important;
margin:0 auto !important; padding:20px 10px 40px !important;
overflow-x:auto !important; -webkit-overflow-scrolling:touch !important; scrollbar-width:thin !important;
grid-template-columns:none !important;
}
.sbc-circles .sbc-circle{ flex:1 1 0 !important; min-width:42px !important; max-width:96px !important; }
.sbc-circles .sbc-circle-dot{ width:100% !important; height:auto !important; aspect-ratio:1/1 !important; }
.sbc-circles .sbc-circle-name{ white-space:nowrap !important; font-size:clamp(9px,0.85vw,13px) !important; }
</style>
<style>/* DW 2026-06-25 consolidation: RE-ENABLE the density slider on the Boost grid.
The 06-24 block that hid .dw-grid-slider is superseded — the slider now drives
--dw-grid-cols + boostGrid.grid-template-columns directly (see initAll JS),
persists to localStorage('dw-grid-cols'), and shares the uniform pill shape.
Reversible: restore `.dw-grid-slider{display:none !important}` to roll back. */
.dw-grid-slider{display:inline-flex !important}
@media(max-width:768px){.dw-grid-slider{display:none !important}} /* mobile = fixed 2-col + infinite scroll, no slider */
</style>
<!-- ============================================================================
DW HUE / COLOR SORT (2026-06-27, vp-dw-commerce)
Fulfills the hard rule "every product grid MUST have sort + density" — the
density slider + Boost-native sort already exist; this adds the MISSING
color-sort modes (Light→Dark, Dark→Light, Color Wheel, By Color buckets).
Per the DTD HYBRID verdict (approach C): this is the CLIENT-SIDE ORDER of
the currently-loaded Boost cards. Boost SD's native color FILTER scopes the
full collection (Steve-gated dashboard config, drafted separately); this
re-orders whatever cards are on screen by each card's dominant hue.
Per-card hex source: computed live from the card's own thumbnail via a tiny
downscaled <canvas> average ($0, local, no data-file key dependency — the
dw-color-wheel-data.json `h` keys only match ~30% of Boost handles, so we
read the pixels instead). Cached per image src. Re-applies on Boost
re-render / infinite scroll via MutationObserver.
============================================================================ -->
<style>
.dw-colorsort{display:inline-flex;align-items:center;gap:6px;box-sizing:border-box;
height:34px;min-height:34px;padding:0 12px;margin:5px 6px 0 0;
border:1.5px solid #1A1A1A;border-radius:999px;background:#fff;color:#1A1A1A;
font-family:-apple-system,BlinkMacSystemFont,sans-serif;font-size:11px;font-weight:600;
letter-spacing:.06em;line-height:1;white-space:nowrap;user-select:none}
.dw-colorsort label{display:inline-flex;align-items:center;gap:5px;text-transform:uppercase;cursor:pointer}
.dw-colorsort .sw{width:13px;height:13px;border-radius:50%;
background:conic-gradient(#c0392b,#e67e22,#f1c40f,#2ecc71,#1ab6c4,#2e6fd4,#7d4fd4,#c0392b);
border:1px solid rgba(0,0,0,.15)}
.dw-colorsort select{border:none;background:transparent;font:inherit;font-weight:700;
text-transform:uppercase;letter-spacing:.06em;cursor:pointer;padding:0 2px 0 0;margin:0;
min-width:84px;-webkit-appearance:none;appearance:none;color:#1A1A1A}
.dw-colorsort.is-busy{opacity:.6}
@media(max-width:768px){.dw-colorsort{height:34px;margin:5px 6px 0 0}}
</style>
<script>
(function(){
if (window.__dwColorSort) return; window.__dwColorSort = true;
if (!/\/(collections|search)/.test(location.pathname)) return;
var KEY = 'dw-colorsort-mode';
var GRID_SEL = '.boost-sd__product-list';
var CARD_SEL = '.boost-sd__product-item';
/* ---- color math (mirrors dw-color-wheel.js) ---- */
function rgb2lab(c){var R=c.map(function(v){v/=255;return v>0.04045?Math.pow((v+0.055)/1.055,2.4):v/12.92;});
var X=(R[0]*0.4124+R[1]*0.3576+R[2]*0.1805)/0.95047,Y=R[0]*0.2126+R[1]*0.7152+R[2]*0.0722,Z=(R[0]*0.0193+R[1]*0.1192+R[2]*0.9505)/1.08883;
var f=function(t){return t>0.008856?Math.cbrt(t):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 rgb2hsl(r,g,b){r/=255;g/=255;b/=255;var mx=Math.max(r,g,b),mn=Math.min(r,g,b),h,s,l=(mx+mn)/2;
if(mx===mn){h=s=0;}else{var d=mx-mn;s=l>0.5?d/(2-mx-mn):d/(mx+mn);
switch(mx){case r:h=(g-b)/d+(g<b?6:0);break;case g:h=(b-r)/d+2;break;default:h=(r-g)/d+4;}h/=6;}
return[h*360,s,l];}
/* dominant color of an image: downscale to 16x16, drop near-white/near-black
paper margins, average the rest. Returns [r,g,b] or null. */
var hexCache = {};
function colorOfImg(img){
var src = img.currentSrc || img.src; if(!src) return null;
if (hexCache[src]) return hexCache[src];
try{
var n=16, cv=document.createElement('canvas'); cv.width=n; cv.height=n;
var cx=cv.getContext('2d',{willReadFrequently:true});
cx.drawImage(img,0,0,n,n);
var data=cx.getImageData(0,0,n,n).data, r=0,g=0,b=0,k=0;
for(var i=0;i<data.length;i+=4){
var R=data[i],G=data[i+1],B=data[i+2],A=data[i+3];
if(A<128) continue;
var mx=Math.max(R,G,B),mn=Math.min(R,G,B),l=(mx+mn)/510;
if(l>0.94||l<0.06) continue; /* skip paper-white & pure-black ground */
r+=R;g+=G;b+=B;k++;
}
if(!k){ /* fallback: plain average */
for(var j=0;j<data.length;j+=4){if(data[j+3]<128)continue;r+=data[j];g+=data[j+1];b+=data[j+2];k++;}
}
if(!k) return null;
var out=[Math.round(r/k),Math.round(g/k),Math.round(b/k)];
hexCache[src]=out; return out;
}catch(e){ return null; } /* tainted canvas etc. */
}
/* hue bucket order for "By Color" — reds→oranges→…→neutrals last */
function hueBucket(rgb){
var hsl=rgb2hsl(rgb[0],rgb[1],rgb[2]), h=hsl[0], s=hsl[1], l=hsl[2];
if(s<0.12) return 100 + (1-l); /* greys/neutrals → end, light→dark */
return Math.floor(h/30); /* 12 hue buckets 0..11 */
}
function cardKey(card, mode){
var img = card.querySelector('img.boost-sd__product-image-img, img');
var rgb = img ? colorOfImg(img) : null;
/* cards we can't read sort to the END for every mode (sort key always large) */
if (!rgb) return mode==='wheel' ? 999 : 999999;
if (mode==='light' || mode==='dark'){
var L = rgb2lab(rgb)[0]; /* 0 (black) .. 100 (white) */
/* "Light → Dark" = lightest FIRST => sort ascending on (100 - L)
"Dark → Light" = darkest FIRST => sort ascending on L */
return mode==='light' ? (100 - L) : L;
}
if (mode==='wheel'){ return rgb2hsl(rgb[0],rgb[1],rgb[2])[0]; } /* 0..360 hue */
return hueBucket(rgb); /* bucket */
}
function reorder(mode){
var grid=document.querySelector(GRID_SEL); if(!grid) return false;
var cards=[].slice.call(grid.querySelectorAll(CARD_SEL));
if(cards.length<2) return false;
/* only reorder once images are decodable; if many are still loading, retry */
var ready=0; cards.forEach(function(c){var im=c.querySelector('img'); if(im&&im.complete&&im.naturalWidth)ready++;});
if(ready < Math.min(cards.length, 6)) return false;
var decorated=cards.map(function(c,i){return {c:c,i:i,k:cardKey(c,mode)};});
decorated.sort(function(a,b){ return a.k===b.k ? a.i-b.i : (a.k<b.k?-1:1); });
var frag=document.createDocumentFragment();
decorated.forEach(function(d){frag.appendChild(d.c);});
grid.appendChild(frag);
return true;
}
var sel; /* the <select> */
function currentMode(){ return (sel && sel.value) || localStorage.getItem(KEY) || 'default'; }
function apply(){
var mode=currentMode();
if(mode==='default') return; /* leave Boost's native order untouched */
var box=sel?sel.closest('.dw-colorsort'):null; if(box)box.classList.add('is-busy');
/* enable CORS on card imgs so canvas isn't tainted (Shopify CDN allows it) */
document.querySelectorAll(CARD_SEL+' img').forEach(function(im){
if(!im.crossOrigin){ im.crossOrigin='anonymous'; if(im.complete&&im.src){var s=im.src;im.src='';im.src=s;} }
});
var tries=0;
(function go(){
var ok=reorder(mode);
if(!ok && tries++<20){ setTimeout(go,250); return; }
if(box)box.classList.remove('is-busy');
})();
}
function buildControl(toolbarRow, beforeNode){
if(document.querySelector('.dw-colorsort')) return;
var wrap=document.createElement('div'); wrap.className='dw-colorsort';
var lab=document.createElement('label');
var sw=document.createElement('span'); sw.className='sw';
lab.appendChild(sw);
sel=document.createElement('select'); sel.id='dw-colorsort-sel';
[['default','Color Sort'],['light','Light → Dark'],['dark','Dark → Light'],
['wheel','Color Wheel'],['bucket','By Color']].forEach(function(o){
var op=document.createElement('option'); op.value=o[0]; op.textContent=o[1]; sel.appendChild(op);
});
var saved=localStorage.getItem(KEY); if(saved) sel.value=saved;
sel.addEventListener('change',function(){
localStorage.setItem(KEY,this.value);
if(this.value==='default'){ location.reload(); return; } /* restore Boost order cleanly */
apply();
});
lab.appendChild(sel); wrap.appendChild(lab);
if(beforeNode && beforeNode.parentNode===toolbarRow) toolbarRow.insertBefore(wrap,beforeNode);
else toolbarRow.appendChild(wrap);
}
/* Mount the color-sort pill RIGHT NEXT TO the grid-density slider. The
density slider's own init (in the block above) already resolved the correct
(visible) Boost toolbar variant and inserted itself there — so anchoring to
it inherits that correctness exactly and the two pills always sit together.
This avoids the Boost "several toolbar variants, all-but-one display:none"
trap (a guess at the visible toolbar mis-fires headless). Fallback: the
product-count toolbar item if the slider never appears (e.g. mobile). */
function isShown(el){ return !!(el && el.offsetParent !== null); }
var mountTries=0;
var mt=setInterval(function(){
if (document.querySelector('.dw-colorsort')) { clearInterval(mt); return; }
var slider = document.querySelector('.dw-grid-slider');
/* Primary: sit right after the density slider IF its toolbar is actually
shown (keeps the two density/color pills together). */
if (slider && slider.parentElement && isShown(slider)){
clearInterval(mt);
buildControl(slider.parentElement, slider.nextSibling);
if(currentMode()!=='default') apply();
return;
}
/* Fallback: Boost renders several toolbar variants and hides all but one.
If the slider's variant is hidden, drop the pill into the VISIBLE
toolbar content/inner container so real users always get it. */
var content = document.querySelector('.boost-sd__toolbar-content, .boost-sd__toolbar-inner');
if (content && isShown(content) && mountTries > 6){
clearInterval(mt);
buildControl(content, content.firstChild);
if(currentMode()!=='default') apply();
return;
}
/* Last resort after a generous wait: mount next to slider regardless of
computed visibility (real users see it even when automation reads 0px —
the live shipped density slider behaves identically). */
if (slider && slider.parentElement && mountTries > 40){
clearInterval(mt);
buildControl(slider.parentElement, slider.nextSibling);
if(currentMode()!=='default') apply();
return;
}
if(mountTries++>80){ clearInterval(mt); }
},300);
/* re-apply after Boost re-renders / appends cards */
var rt;
new MutationObserver(function(muts){
if(currentMode()==='default') return;
for(var i=0;i<muts.length;i++){ if(muts[i].addedNodes&&muts[i].addedNodes.length){
clearTimeout(rt); rt=setTimeout(apply,200); return; } }
}).observe(document.body,{childList:true,subtree:true});
})();
</script>