← 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,'&quot;') +'">' : '') +
        '</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>