[object Object]

← back to Designer Wallcoverings

Collection grid UX: hue/color sort modes + compact pill under image + Design Coordinate dw_sku->handle fix

f68c7287c31fdc380e6b390e441f86a218535acd · 2026-06-27 11:04:36 -0700 · Steve Abrams

Files touched

Diff

commit f68c7287c31fdc380e6b390e441f86a218535acd
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Sat Jun 27 11:04:36 2026 -0700

    Collection grid UX: hue/color sort modes + compact pill under image + Design Coordinate dw_sku->handle fix
---
 shopify/theme-files/assets/dw-card-hover.js        | 166 +++++
 shopify/theme-files/sections/product.liquid        | 718 +++++++++++++++++++++
 .../theme-files/snippets/dw-boost-overrides.liquid | 702 +++++++++++++++++++-
 3 files changed, 1552 insertions(+), 34 deletions(-)

diff --git a/shopify/theme-files/assets/dw-card-hover.js b/shopify/theme-files/assets/dw-card-hover.js
new file mode 100644
index 00000000..9e61401b
--- /dev/null
+++ b/shopify/theme-files/assets/dw-card-hover.js
@@ -0,0 +1,166 @@
+/* ============================================================================
+   DW card label  v5  (2026-06-27) — collection / search product grids.
+   v5 (Steve): the name/vendor label was a DARK absolute OVERLAY riding the
+       bottom of the image (a "pill" on top of the thumbnail). It is now a
+       SMALLER, light, in-flow pill placed DIRECTLY UNDER the image — no
+       overlap, no dark gradient, always visible (better for trade shoppers
+       scanning a dense grid). Smaller type + tighter padding than v4.
+   v4: also hides the on-image quick-add / quick-view hover buttons (DTD B).
+
+   The native Boost text band stays clipped (SEO/a11y in DOM). Works on the
+   theme markup (.product-list-item*) AND Boost AI markup
+   (.boost-sd__product-item), re-applying to lazy / infinite-scroll /
+   re-rendered cards via a MutationObserver.
+
+   Source asset: assets/dw-card-hover.js  (loaded by layout/theme.liquid).
+   v1 backup: assets/dw-card-hover.js.v1-bak
+   ========================================================================== */
+(function () {
+  if (window.__dwCardHover) return;
+  window.__dwCardHover = true;
+
+  var CARD   = '.product-list-item, .boost-sd__product-item';
+  var TITLE  = '.product-list-item-title, .boost-sd__product-title';
+  var VENDOR = '.product-list-item-vendor, .boost-sd__product-vendor';
+  var THUMB  = '.product-list-item-thumbnail, .boost-sd__product-image-wrapper, [class*="boost-sd__product-image"]:not(img)';
+
+  var STYLE_ID = 'dw-card-hover-style';
+  if (!document.getElementById(STYLE_ID)) {
+    var st = document.createElement('style');
+    st.id = STYLE_ID;
+    st.textContent = [
+      /* image wrapper = positioning context for the overlay */
+      '.product-list-item .product-list-item-thumbnail,',
+      '.boost-sd__product-item .boost-sd__product-image-wrapper,',
+      '.boost-sd__product-item [class*="boost-sd__product-image"]:not(img){position:relative;}',
+
+      /* collapse the native text band to zero height. Boost keeps vendor/title/
+         price as siblings, so each is clipped directly. Kept in DOM for SEO/a11y. */
+      '.product-list-item .product-list-item-details,',
+      '.boost-sd__product-item .boost-sd__product-info,',
+      '.boost-sd__product-item .boost-sd__product-title,',
+      '.boost-sd__product-item .boost-sd__product-vendor,',
+      '.boost-sd__product-item [class*="boost-sd__product-price"],',
+      '.boost-sd__product-item [class*="boost-sd__format-price"]{',
+        'position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;',
+        'overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0;}',
+
+      /* hide the on-image quick-add / quick-view hover buttons (DTD verdict B,
+         2026-06-22): DW is trade/luxe — variant + MOQ selection happens on the
+         PDP and the card image already links there, so these hover CTAs are
+         redundant clutter against the pure image + name overlay. */
+      '.boost-sd__product-item .boost-sd__button--show-on-hover,',
+      '.boost-sd__product-item [class*="boost-sd__btn-quick"],',
+      '.boost-sd__product-item [class*="boost-sd__product-action"]{display:none!important;}',
+
+      /* === DW pill v5 (2026-06-27): the name/vendor label was an absolute dark
+         overlay riding the bottom of the IMAGE. Per Steve it is now a COMPACT
+         pill placed DIRECTLY UNDER the image, in normal flow (no overlap, no
+         dark gradient). Smaller type + tighter padding than the v4 overlay. === */
+      '.dw-hover-overlay{display:block;width:100%;box-sizing:border-box;z-index:2;',
+        'padding:6px 8px 8px;text-align:center;pointer-events:none;',
+        'font-family:-apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}',
+      /* the compact pat pill: small, rounded, sits just below the image */
+      '.dw-hover-overlay .pat{display:inline-block;max-width:100%;',
+        'font-size:clamp(10px,0.78vw,12px);font-weight:600;line-height:1.2;',
+        'color:#1a1a1a;letter-spacing:.01em;',
+        'white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
+      '.dw-hover-overlay .ven{font-size:9px;letter-spacing:.14em;text-transform:uppercase;',
+        'color:#8a8278;margin-top:2px;line-height:1.2;}',
+      '.dw-hover-overlay .ven::before{content:none;}',
+      /* reduced motion: nothing to animate now (static pill) */
+      '@media (prefers-reduced-motion:reduce){.dw-hover-overlay{transition:none;}}'
+    ].join('');
+    document.head.appendChild(st);
+  }
+
+  function esc(s) {
+    return s.replace(/[&<>"']/g, function (c) {
+      return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c];
+    });
+  }
+
+  // Boost renders titles lowercase; Title-Case for a luxe look. Small stop-words
+  // (and/or/of/on/in/the/a/an/to/with) stay lowercase unless they lead the line.
+  var STOP = { and:1, or:1, of:1, on:1, in:1, the:1, a:1, an:1, to:1, with:1 };
+  function titleCase(s) {
+    var i = 0;
+    return s.replace(/\w[\w']*/g, function (w) {
+      i++;
+      var lower = w.toLowerCase();
+      if (i > 1 && STOP[lower]) return lower;
+      return w.charAt(0).toUpperCase() + w.slice(1);
+    });
+  }
+
+  function patternName(title) {
+    var t = title;
+    if (t.indexOf('|') !== -1) t = t.split('|')[0];          // "Name | Vendor"
+    else {                                                    // "Name By Designer"
+      var by = t.split(/\s+by\s+/i);
+      if (by.length > 1) t = by[0];
+    }
+    t = t.replace(/\s*wallcoverings?\b/ig, '')                // drop "wallcovering(s)"
+         .replace(/[,\s]+$/, '').trim();
+    return titleCase(t);
+  }
+
+  function labelCard(card) {
+    if (card.querySelector('.dw-hover-overlay')) return;
+    var tEl = card.querySelector(TITLE);
+    if (!tEl) return;
+    var name = patternName(tEl.textContent || '');
+    if (!name) return;
+    var vEl = card.querySelector(VENDOR);
+    var vendor = (vEl ? vEl.textContent : '').replace(/\s+/g, ' ').trim();
+
+    var imgWrap = card.querySelector(THUMB);
+    if (!imgWrap) {
+      var img = card.querySelector('img');
+      imgWrap = img ? img.parentElement : null;
+    }
+    if (!imgWrap) return;
+
+    var ov = document.createElement('div');
+    ov.className = 'dw-hover-overlay';
+    ov.innerHTML = '<div class="pat">' + esc(name) + '</div>' +
+      (vendor ? '<div class="ven">' + esc(vendor) + '</div>' : '');
+
+    /* v5: place the compact pill DIRECTLY UNDER the image, in normal flow.
+       Insert it as a sibling right after the image-wrapper's nearest block
+       container so it never overlaps the thumbnail. Walk up from the image
+       wrapper to the card-level image column, then insert after that column. */
+    var imageCol = imgWrap.closest(
+      '.boost-sd__product-item-grid-view-layout-image, .product-list-item-thumbnail'
+    ) || imgWrap;
+    if (imageCol.parentNode) {
+      imageCol.parentNode.insertBefore(ov, imageCol.nextSibling);
+    } else {
+      imgWrap.appendChild(ov);
+    }
+  }
+
+  function run() {
+    var cards = document.querySelectorAll(CARD);
+    for (var i = 0; i < cards.length; i++) labelCard(cards[i]);
+  }
+
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', run);
+  } else {
+    run();
+  }
+
+  // Re-apply when the grid re-renders or lazy/infinite scroll appends cards.
+  var t;
+  var obs = new MutationObserver(function (muts) {
+    for (var i = 0; i < muts.length; i++) {
+      if (muts[i].addedNodes && muts[i].addedNodes.length) {
+        clearTimeout(t);
+        t = setTimeout(run, 80);
+        return;
+      }
+    }
+  });
+  obs.observe(document.body, { childList: true, subtree: true });
+})();
diff --git a/shopify/theme-files/sections/product.liquid b/shopify/theme-files/sections/product.liquid
new file mode 100644
index 00000000..9c9626c9
--- /dev/null
+++ b/shopify/theme-files/sections/product.liquid
@@ -0,0 +1,718 @@
+{% render 'breadcrumbs' %}
+
+{% liquid
+  assign enable_zoom = section.settings.enable_zoom
+  assign enable_cart_redirection = section.settings.enable_cart_redirection
+  assign images_layout = section.settings.images_layout
+  assign enable_video_autoplay = section.settings.enable_video_autoplay
+  assign enable_video_looping = section.settings.enable_video_looping
+  assign enable_linked_options = true
+  assign show_vendor = settings.show_vendor
+  assign show_social_media_icons = section.settings.show_social_media_icons
+  assign show_payment_button = section.settings.show_payment_button
+
+  for block in section.blocks
+    if block.type == 'complementary_products'
+      assign product_recommendation_limit = block.settings.product_recommendation_limit
+      break
+    endif
+  endfor
+%}
+
+{% if images_layout == 'masonry' %}
+  {% comment %}Related products in masonry grid must be below product{% endcomment %}
+  {% assign related_products_position_right = false %}
+{% endif %}
+
+<script
+  type="application/json"
+  data-section-type="product"
+  data-section-id="{{ section.id }}"
+  data-section-data
+>
+  {
+    "product": {{ product | json }},
+    "product_settings": {
+      "addToCartText": {{ 'products.product.add_to_cart' | t | json }},
+      "enableHistory": true,
+      "processingText": {{ 'products.product.processing' | t | json }},
+      "setQuantityText": {{ 'products.product.set_quantity' | t | json }},
+      "soldOutText": {{ 'products.product.sold_out' | t | json }},
+      "unavailableText": {{ 'products.product.unavailable' | t | json }}
+    },
+    "images_layout": {{ images_layout | json }},
+    "enable_zoom": {{ enable_zoom | json }},
+    "enable_video_autoplay": {{ enable_video_autoplay | json }},
+    "enable_video_looping": {{ enable_video_looping | json }},
+    "enable_cart_redirection": {{ enable_cart_redirection | json }},
+    "enable_fixed_positioning": true,
+    "product_recommendation_limit": {{ product_recommendation_limit | json }}
+  }
+</script>
+
+{% render 'product-success-labels' %}
+<section class="product-container">
+  {%
+    render 'product',
+    product: product,
+    enable_zoom: enable_zoom,
+    images_layout: images_layout,
+    enable_linked_options: enable_linked_options,
+    show_vendor: show_vendor,
+    show_social_media_icons: show_social_media_icons,
+    show_payment_button: show_payment_button,
+  %}
+
+  {%- comment -%}
+      Design Coordinate v6 — Steve's spec:
+        • Catalog grid: 24 stripe/plaid/check coordinates that use the source palette
+        • Density slider 3-12 cols (default 8), localStorage-persisted
+        • AI Generated Coordinates: 24 SVG-poster placeholders seeded from palette
+        • Vendor picker: SW / DE / BM / F&B (default on) + Behr / PPG / RAL (opt-in)
+        • Each card shows palette dots + selected vendors' paint chips
+      Service: https://pairs.designerwallcoverings.com  (commit history at ~/Projects/dw-pairs-well)
+      CSS namespace kept as .pairs-well__* — only the button TEXT is "Design Coordinate".
+    {%- endcomment -%}
+    <section
+      id="pairs-well-with"
+      class="dc-section"
+      data-pairs-endpoint="https://pairs.designerwallcoverings.com/api/pairs"
+      data-ai-endpoint="https://pairs.designerwallcoverings.com/api/ai-coordinates"
+      data-product-handle="{{ product.handle }}"
+      {%- comment -%}
+        2026-06-27 (vp-dw-commerce): data-product-sku must be the DW sku (the
+        shopify_products.dw_sku key), NOT custom.manufacturer_sku — the MFR sku
+        never matches the pairs DB and was returning "source not found",
+        breaking the Design Coordinate panel. Use the variant's DW sku (strip the
+        -Sample suffix); the pairs API also resolves by handle as a fallback.
+      {%- endcomment -%}
+      {%- assign dc_sku = product.selected_or_first_available_variant.sku | default: product.variants.first.sku | replace: '-Sample', '' | replace: '-SAMPLE', '' -%}
+      data-product-sku="{{ dc_sku | escape }}"
+      style="margin: 1.5em 0 2em; clear: both;"
+      hidden
+    >
+      <button type="button" class="pairs-well__cta" aria-expanded="false">
+        <span class="pairs-well__cta-text">Design Coordinate</span>
+        <span class="pairs-well__cta-icon" aria-hidden="true">+</span>
+      </button>
+
+      <div class="pairs-well__source-palette" id="pairs-well-source-palette" hidden></div>
+
+      <div class="pairs-well__vendor-picker" id="pairs-well-vendor-picker" hidden>
+        <h4>Paint brands</h4>
+        <div class="chips">
+          <label><input type="checkbox" value="sw"   checked> Sherwin-Williams</label>
+          <label><input type="checkbox" value="de"   checked> Dunn-Edwards</label>
+          <label><input type="checkbox" value="bm"   checked> Benjamin Moore</label>
+          <label><input type="checkbox" value="fb"   checked> Farrow &amp; Ball</label>
+          <label><input type="checkbox" value="behr"        > Behr</label>
+          <label><input type="checkbox" value="ppg"         > PPG</label>
+          <label><input type="checkbox" value="ral"         > RAL</label>
+        </div>
+      </div>
+
+      <div class="pairs-well__catalog" id="pairs-well-catalog" hidden>
+        <div class="pairs-well__panel-head">
+          <h3>Stripes &amp; Plaids from our catalog <span class="count" id="pairs-well-catalog-count"></span></h3>
+          <div class="pairs-well__controls">
+            <label for="pairs-well-cols-catalog">Columns</label>
+            <input id="pairs-well-cols-catalog" type="range" min="3" max="12" value="8" step="1">
+            <span class="pairs-well__cols-val" id="pairs-well-cols-catalog-val">8</span>
+          </div>
+        </div>
+        <div id="pairs-well-grid" class="pairs-well__grid"></div>
+      </div>
+
+      <div class="pairs-well__ai" id="pairs-well-ai" hidden>
+        <div class="pairs-well__panel-head">
+          <h3>AI Generated Coordinates <span class="ai-badge">Preview · 24 options</span></h3>
+          <div class="pairs-well__controls">
+            <label for="pairs-well-cols-ai">Columns</label>
+            <input id="pairs-well-cols-ai" type="range" min="3" max="12" value="8" step="1">
+            <span class="pairs-well__cols-val" id="pairs-well-cols-ai-val">8</span>
+          </div>
+        </div>
+        <div id="pairs-well-ai-grid" class="pairs-well__grid pairs-well__grid--ai"></div>
+      </div>
+    </section>
+
+    <style>
+      #pairs-well-with { font-family: inherit; --cols-catalog: 8; --cols-ai: 8; }
+      .pairs-well__cta {
+        display: flex; align-items: center; justify-content: space-between;
+        width: 100%; padding: 18px 24px; margin: 0;
+        background: #111; color: #fff;
+        border: 0; border-radius: 999px;
+        font-size: 15px; font-weight: 500; letter-spacing: 0.06em;
+        text-transform: uppercase; cursor: pointer;
+        transition: background 160ms ease, transform 120ms ease;
+      }
+      .pairs-well__cta:hover  { background: #2a2a2a; }
+      .pairs-well__cta:active { transform: scale(0.995); }
+      .pairs-well__cta[aria-expanded="true"] .pairs-well__cta-icon { transform: rotate(45deg); }
+      .pairs-well__cta-icon { display: inline-block; font-size: 22px; line-height: 1; transition: transform 200ms ease; }
+
+      .pairs-well__source-palette { margin: 18px 0 0; }
+      .pairs-well__source-palette h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #777; }
+      .pairs-well__source-palette table { border-collapse: collapse; width: 100%; font-size: 12px; }
+      .pairs-well__source-palette th, .pairs-well__source-palette td { text-align: left; padding: 6px 8px; border-bottom: 1px solid #f0ebe2; }
+      .pairs-well__source-palette th { font-weight: 500; color: #777; font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; }
+      .pairs-well__source-palette td.swatch { width: 28px; }
+
+      .pw-dot    { display: inline-block; width: 18px; height: 18px; border-radius: 50%; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; }
+      .pw-square { display: inline-block; width: 13px; height: 13px; border-radius: 3px; border: 1px solid rgba(0,0,0,0.12); vertical-align: middle; margin-right: 4px; }
+      .pw-code   { color: #888; font-variant-numeric: tabular-nums; font-size: 11px; }
+
+      .pairs-well__vendor-picker { margin: 18px 0 0; }
+      .pairs-well__vendor-picker h4 { margin: 0 0 8px; font-size: 11px; letter-spacing: 0.08em; text-transform: uppercase; color: #777; }
+      .pairs-well__vendor-picker .chips { display: flex; flex-wrap: wrap; gap: 6px; }
+      .pairs-well__vendor-picker label {
+        font-size: 12px; padding: 5px 10px; border: 1px solid #e7e7e7; border-radius: 999px;
+        cursor: pointer; background: #fff; user-select: none;
+      }
+      .pairs-well__vendor-picker label:has(input:checked) { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
+      .pairs-well__vendor-picker input { display: none; }
+
+      .pairs-well__panel-head {
+        display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap;
+        gap: 12px; margin: 24px 0 12px;
+      }
+      .pairs-well__panel-head h3 { margin: 0; font: 500 18px/1.2 ui-serif, Georgia, serif; }
+      .pairs-well__panel-head .count { color: #888; font-size: 12px; margin-left: 6px; font-family: -apple-system, sans-serif; font-weight: 400; }
+      .pairs-well__panel-head .ai-badge {
+        display: inline-block; padding: 3px 8px; background: #2a2a2a; color: #fff;
+        border-radius: 999px; font-size: 10px; letter-spacing: 0.08em; text-transform: uppercase;
+        margin-left: 8px; font-family: -apple-system, sans-serif; font-weight: 600;
+      }
+      .pairs-well__controls {
+        display: flex; align-items: center; gap: 10px;
+        font-size: 11px; color: #777; text-transform: uppercase; letter-spacing: 0.06em;
+      }
+      .pairs-well__controls input[type=range] { width: 160px; accent-color: #1a1a1a; }
+      .pairs-well__cols-val { font-variant-numeric: tabular-nums; color: #1a1a1a; min-width: 1.5em; text-align: right; font-weight: 600; }
+
+      .pairs-well__grid {
+        display: grid; gap: 14px; margin-top: 6px;
+        grid-template-columns: repeat(var(--cols-catalog), minmax(0, 1fr));
+      }
+      .pairs-well__grid--ai { grid-template-columns: repeat(var(--cols-ai), minmax(0, 1fr)); }
+      @media (max-width: 1100px) { .pairs-well__grid { grid-template-columns: repeat(min(6, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(6, var(--cols-ai)), minmax(0, 1fr)); } }
+      @media (max-width: 900px)  { .pairs-well__grid { grid-template-columns: repeat(min(4, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(4, var(--cols-ai)), minmax(0, 1fr)); } }
+      @media (max-width: 600px)  { .pairs-well__grid { grid-template-columns: repeat(min(2, var(--cols-catalog)), minmax(0, 1fr)); } .pairs-well__grid--ai { grid-template-columns: repeat(min(2, var(--cols-ai)), minmax(0, 1fr)); } }
+
+      .pairs-well__card {
+        display: block; text-decoration: none; color: inherit;
+        background: #fff; border: 1px solid #e7e7e7; border-radius: 6px;
+        overflow: hidden; transition: box-shadow 160ms ease, transform 160ms ease;
+      }
+      .pairs-well__card:hover { box-shadow: 0 6px 20px rgba(0,0,0,0.08); transform: translateY(-2px); }
+      .pairs-well__card--ai { border-style: dashed; border-color: #d9c7a8; }
+      .pairs-well__img-wrap { aspect-ratio: 1 / 1; background: #f4f4f4; overflow: hidden; }
+      .pairs-well__img { width: 100%; height: 100%; object-fit: cover; display: block; }
+      .pairs-well__meta { padding: 10px 12px 12px; }
+      .pairs-well__title { margin: 0 0 4px; font-size: 13px; font-weight: 600; line-height: 1.3; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
+      .pairs-well__vendor { margin: 0 0 6px; font-size: 10px; color: #777; letter-spacing: 0.04em; text-transform: uppercase; }
+      .pairs-well__why { display: inline-block; padding: 2px 7px; background: #f4ede4; color: #5a4628; border-radius: 999px; font-size: 10px; line-height: 1.4; margin-bottom: 8px; }
+      .pairs-well__card--ai .pairs-well__why { background: #efe6f5; color: #5b3478; }
+      .pairs-well__palette { display: flex; gap: 3px; margin: 0 0 8px; flex-wrap: wrap; }
+      .pairs-well__palette .pw-dot { width: 12px; height: 12px; }
+      .pairs-well__paints { border-top: 1px dashed #ebe4d8; margin-top: 4px; padding-top: 6px; font-size: 10px; line-height: 1.5; color: #444; }
+      .pairs-well__paints .label { color: #888; font-weight: 600; margin-right: 4px; letter-spacing: 0.04em; }
+      .pairs-well__paints .row { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+
+      .pairs-well__skeleton { background: linear-gradient(90deg, #eee 0%, #f6f6f6 50%, #eee 100%); background-size: 200% 100%; animation: pwShim 1.2s linear infinite; aspect-ratio: 1 / 1; border-radius: 6px; }
+      @keyframes pwShim { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }
+      .pairs-well__empty { padding: 18px; color: #777; font-size: 14px; grid-column: 1 / -1; }
+    </style>
+
+    <script>
+      (function () {
+        var root = document.getElementById('pairs-well-with');
+        if (!root) return;
+
+        var endpointPairs = root.getAttribute('data-pairs-endpoint');
+        var endpointAi    = root.getAttribute('data-ai-endpoint');
+        var handle        = root.getAttribute('data-product-handle');
+        var sku           = root.getAttribute('data-product-sku');
+        if (!(sku && sku.length) && !(handle && handle.length)) return;
+        root.hidden = false;
+
+        var cta            = root.querySelector('.pairs-well__cta');
+        var catalogPanel   = document.getElementById('pairs-well-catalog');
+        var catalogGrid    = document.getElementById('pairs-well-grid');
+        var catalogCols    = document.getElementById('pairs-well-cols-catalog');
+        var catalogColsVal = document.getElementById('pairs-well-cols-catalog-val');
+        var catalogCount   = document.getElementById('pairs-well-catalog-count');
+        var aiPanel        = document.getElementById('pairs-well-ai');
+        var aiGrid         = document.getElementById('pairs-well-ai-grid');
+        var aiCols         = document.getElementById('pairs-well-cols-ai');
+        var aiColsVal      = document.getElementById('pairs-well-cols-ai-val');
+        var sourcePalette  = document.getElementById('pairs-well-source-palette');
+        var vendorPicker   = document.getElementById('pairs-well-vendor-picker');
+        var vendorChecks   = vendorPicker.querySelectorAll('input[type=checkbox]');
+        var loaded = false;
+        var cachedPairs = null, cachedAi = null, cachedSource = null;
+
+        var VENDOR_LABEL = { sw:'Sherwin-Williams', de:'Dunn-Edwards', bm:'Benjamin Moore', fb:'Farrow & Ball', behr:'Behr', ppg:'PPG', ral:'RAL' };
+        var VENDOR_SHORT = { sw:'SW', de:'DE', bm:'BM', fb:'F&B', behr:'Behr', ppg:'PPG', ral:'RAL' };
+        var DEFAULT_VENDORS = ['sw','de','bm','fb'];
+
+        function selectedVendors() {
+          var out = [];
+          vendorChecks.forEach(function (cb) { if (cb.checked) out.push(cb.value); });
+          return out.length ? out : DEFAULT_VENDORS.slice();
+        }
+        try {
+          var stored = JSON.parse(localStorage.getItem('pw.vendors') || 'null');
+          if (Array.isArray(stored) && stored.length) {
+            vendorChecks.forEach(function (cb) { cb.checked = stored.indexOf(cb.value) !== -1; });
+          }
+        } catch (_) {}
+        function persistVendors() {
+          try { localStorage.setItem('pw.vendors', JSON.stringify(selectedVendors())); } catch (_) {}
+        }
+        vendorChecks.forEach(function (cb) {
+          cb.addEventListener('change', function () {
+            persistVendors();
+            if (cachedSource) renderSourcePalette(cachedSource.palette);
+            if (cachedPairs && !catalogPanel.hidden) renderGrid(catalogGrid, cachedPairs, false);
+          });
+        });
+
+        function wireSlider(input, valEl, gridEl, varName, storageKey) {
+          function apply() {
+            var n = Math.max(3, Math.min(12, parseInt(input.value, 10) || 8));
+            valEl.textContent = n;
+            gridEl.parentNode.parentNode.style.setProperty('--' + varName, n);
+            root.style.setProperty('--' + varName, n);
+            try { localStorage.setItem(storageKey, n); } catch (_) {}
+          }
+          try {
+            var s = parseInt(localStorage.getItem(storageKey), 10);
+            if (!isNaN(s) && s >= 3 && s <= 12) input.value = s;
+          } catch (_) {}
+          input.addEventListener('input', apply);
+          apply();
+        }
+        wireSlider(catalogCols, catalogColsVal, catalogGrid, 'cols-catalog', 'pw.cols.catalog');
+        wireSlider(aiCols,      aiColsVal,      aiGrid,      'cols-ai',      'pw.cols.ai');
+
+        function esc(s) {
+          return String(s == null ? '' : s)
+            .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
+            .replace(/"/g, '&quot;').replace(/'/g, '&#39;');
+        }
+        function renderSkeletons(grid, n) {
+          var html = '';
+          for (var i = 0; i < n; i++) html += '<div class="pairs-well__skeleton"></div>';
+          grid.innerHTML = html;
+        }
+
+        function renderSourcePalette(palette) {
+          if (!palette || !palette.length) { sourcePalette.hidden = true; vendorPicker.hidden = true; return; }
+          var vendors = selectedVendors();
+          var head = '<th></th>' + vendors.map(function (v) { return '<th>' + esc(VENDOR_LABEL[v]) + '</th>'; }).join('');
+          var body = palette.map(function (c) {
+            var hex = typeof c === 'string' ? c : c.hex;
+            var cells = '<td class="swatch"><span class="pw-dot" style="background:' + esc(hex) + '"></span></td>';
+            vendors.forEach(function (v) {
+              var p = c && c.paints && c.paints[v];
+              cells += '<td>' + (p
+                ? '<span class="pw-square" style="background:' + esc(p.hex) + '"></span>' + esc(p.name) + ' <span class="pw-code">' + esc(p.code) + '</span>'
+                : '—') + '</td>';
+            });
+            return '<tr>' + cells + '</tr>';
+          }).join('');
+          sourcePalette.innerHTML = '<h4>Color story</h4><table><thead><tr>' + head + '</tr></thead><tbody>' + body + '</tbody></table>';
+          sourcePalette.hidden = false;
+          vendorPicker.hidden = false;
+        }
+
+        function renderCard(p, isAi) {
+          var img = p.image_url ? esc(p.image_url) : '';
+          var why = (p.why && p.why[0]) ? esc(p.why[0]) : (p.motif ? 'AI ' + esc(p.motif) : '');
+          var palette = (p.palette || []).map(function (c) {
+            var hex = typeof c === 'string' ? c : c.hex;
+            var sw = c && c.paints && c.paints.sw, de = c && c.paints && c.paints.de;
+            var tip = (sw ? 'SW ' + sw.name + ' ' + sw.code : '') + (de ? ' · DE ' + de.name + ' ' + de.code : '');
+            return '<span class="pw-dot" style="background:' + esc(hex) + '" title="' + esc(tip) + '"></span>';
+          }).join('');
+          var dom = (p.palette && p.palette[0]) || null;
+          var paintsBlock = '';
+          if (dom && dom.paints) {
+            var vendors = selectedVendors();
+            var rows = vendors.map(function (v) {
+              var paint = dom.paints[v];
+              if (!paint) return '';
+              return '<span class="row"><span class="label">' + esc(VENDOR_SHORT[v]) + '</span>'
+                + '<span class="pw-square" style="background:' + esc(paint.hex) + '"></span>'
+                + esc(paint.name) + ' <span class="pw-code">' + esc(paint.code) + '</span></span>';
+            }).filter(Boolean).join('');
+            if (rows) paintsBlock = '<div class="pairs-well__paints">' + rows + '</div>';
+          }
+          var href = p.url ? esc(p.url) : (p.handle ? '/products/' + esc(p.handle) : '#');
+          return '<a class="pairs-well__card' + (isAi ? ' pairs-well__card--ai' : '') + '" href="' + href + '" title="' + esc((p.why || []).join(' · ')) + '">'
+            +   '<div class="pairs-well__img-wrap">' + (img ? '<img class="pairs-well__img" src="' + img + '" alt="' + esc(p.title) + '" loading="lazy">' : '') + '</div>'
+            +   '<div class="pairs-well__meta">'
+            +     '<p class="pairs-well__title">' + esc(p.title || '') + '</p>'
+            +     '<p class="pairs-well__vendor">' + esc(p.vendor || (isAi ? 'AI-Generated' : '')) + '</p>'
+            +     (why ? '<span class="pairs-well__why">' + why + '</span>' : '')
+            +     (palette ? '<div class="pairs-well__palette">' + palette + '</div>' : '')
+            +     paintsBlock
+            +   '</div>'
+            + '</a>';
+        }
+        function renderGrid(grid, items, isAi) {
+          if (!items || !items.length) { grid.innerHTML = '<div class="pairs-well__empty">No coordinates found for this pattern.</div>'; return; }
+          grid.innerHTML = items.map(function (p) { return renderCard(p, isAi); }).join('');
+        }
+
+        function buildUrl(base, limit) {
+          var qs = [];
+          if (sku)    qs.push('dw_sku=' + encodeURIComponent(sku));
+          if (handle) qs.push('handle=' + encodeURIComponent(handle));
+          if (limit)  qs.push('limit=' + limit);
+          return base + '?' + qs.join('&');
+        }
+
+        function load() {
+          if (loaded) return Promise.resolve();
+          loaded = true;
+          renderSkeletons(catalogGrid, 12);
+          renderSkeletons(aiGrid, 12);
+          catalogPanel.hidden = false;
+          aiPanel.hidden = false;
+
+          var pairsPromise = fetch(buildUrl(endpointPairs, 24), { credentials: 'omit' })
+            .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
+            .then(function (d) {
+              if (!d || d.ok === false) throw new Error((d && d.error) || 'bad payload');
+              cachedSource = d.source || null;
+              cachedPairs  = d.pairs  || [];
+              if (cachedSource) renderSourcePalette(cachedSource.palette || []);
+              renderGrid(catalogGrid, cachedPairs, false);
+              catalogCount.textContent = '(' + cachedPairs.length + ')';
+              if (!cachedPairs.length && (!cachedSource || !cachedSource.palette || !cachedSource.palette.length)) {
+                root.hidden = true;
+              }
+            })
+            .catch(function () { catalogGrid.innerHTML = '<div class="pairs-well__empty">Could not load coordinates right now.</div>'; });
+
+          var aiPromise = fetch(buildUrl(endpointAi, 24), { credentials: 'omit' })
+            .then(function (r) { return r.ok ? r.json() : Promise.reject(new Error('http ' + r.status)); })
+            .then(function (d) {
+              if (!d || d.ok === false) throw new Error('bad payload');
+              cachedAi = d.items || [];
+              renderGrid(aiGrid, cachedAi, true);
+            })
+            .catch(function () { aiGrid.innerHTML = '<div class="pairs-well__empty">AI generator not ready.</div>'; });
+
+          return Promise.all([pairsPromise, aiPromise]);
+        }
+
+        cta.addEventListener('click', function () {
+          var open = cta.getAttribute('aria-expanded') === 'true';
+          if (open) {
+            cta.setAttribute('aria-expanded', 'false');
+            sourcePalette.hidden = true;
+            vendorPicker.hidden = true;
+            catalogPanel.hidden = true;
+            aiPanel.hidden = true;
+            return;
+          }
+          cta.setAttribute('aria-expanded', 'true');
+          load();
+        });
+      })();
+    </script>
+
+  <div
+    class="product-recommendations-wrapper--right"
+    data-product-recommendations-right
+  >
+  </div>
+</section>
+
+{% schema %}
+{
+  "name": "Product pages",
+  "settings": [
+    {
+      "type": "checkbox",
+      "id": "enable_cart_redirection",
+      "label": "Enable cart redirection",
+      "info": "Automatically sends users to the Cart page after adding a product.",
+      "default": false
+    },
+    {
+      "type": "checkbox",
+      "id": "show_payment_button",
+      "label": "Show dynamic checkout button",
+      "info": "Each customer will see their preferred payment method from those available on your store, such as PayPal or Apple Pay. [Learn more](https://help.shopify.com/manual/using-themes/change-the-layout/dynamic-checkout)",
+      "default": true
+    },
+    {
+      "type": "header",
+      "content": "Media"
+    },
+    {
+      "type": "paragraph",
+      "content": "Learn more about [media types](https://help.shopify.com/en/manual/products/product-media/product-media-types)"
+    },
+    {
+      "type": "select",
+      "id": "images_layout",
+      "label": "Layout",
+      "options": [
+        {
+          "label": "Slideshow",
+          "value": "slideshow"
+        },
+        {
+          "label": "List",
+          "value": "list"
+        },
+        {
+          "label": "Masonry",
+          "value": "masonry"
+        }
+      ],
+      "default": "slideshow"
+    },
+    {
+      "type": "checkbox",
+      "id": "enable_zoom",
+      "label": "Enable image zoom",
+      "info": "Zoom only works with the slideshow image layout"
+    },
+    {
+      "type": "checkbox",
+      "id": "enable_video_autoplay",
+      "label": "Enable video autoplay",
+      "default": true
+    },
+    {
+      "type": "checkbox",
+      "id": "enable_video_looping",
+      "label": "Enable video looping"
+    }
+  ],
+  "blocks": [
+    {
+      "type": "@app"
+    },
+    {
+      "type": "collapsible-tab",
+      "name": "Collapsible tab",
+      "settings": [
+        {
+          "type": "text",
+          "id": "collapsible_tab_heading",
+          "label": "Heading",
+          "default": "Collapsible tab"
+        },
+        {
+          "type": "richtext",
+          "id": "collapsible_tab_text",
+          "label": "Text",
+          "default": "<p>Use this text to share information about your product.</p>"
+        }
+      ]
+    },
+    {
+      "type": "custom-liquid",
+      "name": "Custom liquid",
+      "settings": [
+        {
+          "type": "liquid",
+          "id": "custom_liquid",
+          "label": "Custom liquid",
+          "info": "Add app snippets or other Liquid code to create advanced customizations."
+        }
+      ]
+    },
+    {
+      "type": "tabs",
+      "name": "Tabs",
+      "limit": 1,
+      "settings": [
+        {
+          "type": "checkbox",
+          "id": "show_product_description",
+          "label": "Show product description",
+          "default": false
+        },
+        {
+          "type": "header",
+          "content": "Tab"
+        },
+        {
+          "type": "text",
+          "id": "tab_heading_1",
+          "label": "Heading",
+          "default": "Tab 1"
+        },
+        {
+          "type": "richtext",
+          "id": "tab_text_1",
+          "label": "Text",
+          "default": "<p>Tab 1 content goes here.</p>"
+        },
+        {
+          "type": "header",
+          "content": "Tab"
+        },
+        {
+          "type": "text",
+          "id": "tab_heading_2",
+          "label": "Heading",
+          "default": "Tab 2"
+        },
+        {
+          "type": "richtext",
+          "id": "tab_text_2",
+          "label": "Text",
+          "default": "<p>Tab 2 content goes here.</p>"
+        },
+        {
+          "type": "header",
+          "content": "Tab"
+        },
+        {
+          "type": "text",
+          "id": "tab_heading_3",
+          "label": "Heading",
+          "default": "Tab 3"
+        },
+        {
+          "type": "richtext",
+          "id": "tab_text_3",
+          "label": "Text",
+          "default": "<p>Tab 3 content goes here.</p>"
+        }
+      ]
+    },
+    {
+      "type": "title",
+      "name": "Title",
+      "limit": 1
+    },
+    {
+      "type": "vendor",
+      "name": "Vendor",
+      "limit": 1
+    },
+    {
+      "type": "social",
+      "name": "Social",
+      "limit": 1
+    },
+    {
+      "type": "description",
+      "name": "Description",
+      "limit": 1
+    },
+    {
+      "type": "price",
+      "name": "Price",
+      "limit": 1
+    },
+    {
+      "type": "form",
+      "name": "Form",
+      "limit": 1,
+      "settings": [
+        {
+          "type": "paragraph",
+          "content": "Customize form features for the product in the products portion of the theme settings."
+        },
+        {
+          "type": "checkbox",
+          "id": "show_gift_card_recipient_form",
+          "label": "t:sections.product.blocks.form.show_gift_card_recipient_form.label",
+          "info": "t:sections.product.blocks.form.show_gift_card_recipient_form.info",
+          "default": false
+        }
+      ]
+    },
+    {
+      "type": "rating",
+      "name": "Product rating",
+      "limit": 1,
+      "settings": [
+        {
+          "type": "paragraph",
+          "content": "To display a rating, add a product rating app. [Learn more](https://apps.shopify.com/product-reviews)"
+        }
+      ]
+    },
+    {
+      "type": "complementary_products",
+      "name": "Complementary products",
+      "limit": 1,
+      "settings": [
+        {
+          "type": "paragraph",
+          "content": "To select complementary products, add the Search & Discovery app. [Learn more](https:\/\/shopify.dev\/themes\/product-merchandising\/recommendations)"
+        },
+        {
+          "type": "text",
+          "id": "heading",
+          "label": "Heading",
+          "default": "Pairs well with"
+        },
+        {
+          "type": "range",
+          "id": "product_recommendation_limit",
+          "label": "Maximum products to show",
+          "min": 1,
+          "max": 10,
+          "default": 5
+        },
+        {
+          "type": "range",
+          "id": "products_per_slide",
+          "label": "Number of products per page",
+          "min": 1,
+          "max": 3,
+          "default": 2
+        }
+      ]
+    }
+  ]
+}
+
+{% endschema %}
+
+<div class="keywords-desktop" style="padding-left: 0ch; max-width: 80ch; line-height: 1.6;">
+  <h4 style="padding-left: 3ch; margin-bottom: 0;">KEYWORDS:</h4>
+  <div style="padding-left: 3ch;">
+    {% assign line = "" %}
+    {% assign char_limit = 96 %}
+    {% assign separator = ", " %}
+    {% assign plain_line = "" %}
+    {% for tag in product.tags %}
+      {% unless tag == "display_variant" %}
+        {% assign tag_link = '<a href="/search?q=' | append: tag | append: '">' | append: tag | append: '</a>' %}
+        {% assign tag_text = tag | append: separator %}
+        {% assign test_line = plain_line | append: tag_text %}
+        {% assign test_size = test_line | size %}
+        {% if test_size > char_limit %}
+          {{ line }}<br>
+          {% assign line = tag_link | append: separator %}
+          {% assign plain_line = tag_text %}
+        {% else %}
+          {% assign line = line | append: tag_link | append: separator %}
+          {% assign plain_line = test_line %}
+        {% endif %}
+      {% endunless %}
+    {% endfor %}
+    {{ line }}
+  </div>
+</div>
+
+{% comment %} Recently Viewed Products — tracks current product + shows carousel {% endcomment %}
+{% if settings.dw_enable_recently_viewed %}
+
+  {% render 'recently-viewed' %}
+{% endif %}
diff --git a/shopify/theme-files/snippets/dw-boost-overrides.liquid b/shopify/theme-files/snippets/dw-boost-overrides.liquid
index ce9d31a7..a8cedab4 100644
--- a/shopify/theme-files/snippets/dw-boost-overrides.liquid
+++ b/shopify/theme-files/snippets/dw-boost-overrides.liquid
@@ -15,24 +15,127 @@
 
 /* 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, 4), 1fr) !important}
-.boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(4, 1fr) !important}
-.boost-sd__product-list{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
-.boost-sd__product-list-grid--3-col{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !important}
-.boost-sd__product-list-grid--4-col{grid-template-columns:repeat(var(--dw-grid-cols, 4), 1fr) !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}
+.boost-sd__product-list{overflow:hidden !important;display:grid !important;gap:0 !important;grid-gap:0 !important;column-gap:0 !important;row-gap:0 !important}
+/* DW close all space between products */
+.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:2px 14px;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{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}
 
@@ -41,11 +144,52 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
 /* 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:8px;padding:2px 16px;margin:4px 0 4px 16px;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{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}
@@ -72,13 +216,37 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
 
 <script>
 (function(){
-  /* Collapse filters immediately */
-  document.body.classList.add('dw-filters-collapsed');
-  /* Set CSS var for default 4 cols */
-  document.documentElement.style.setProperty('--dw-grid-cols', 4);
+  /* 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);
 
   var initialized = false;
 
+  /* Auto-apply Wallcoverings filter on first visit if no type filter set */
+  (function(){
+    var url = new URL(window.location.href);
+    var hasTypeFilter = url.searchParams.has('pf_t_product_type');
+    var isCollectionOrSearch = /\/(collections|search)/.test(url.pathname);
+    if (!hasTypeFilter && isCollectionOrSearch) {
+      var saved = sessionStorage.getItem('dw-ptype-filter');
+      var types = saved ? JSON.parse(saved) : ['Wallcovering'];
+      if (types.length > 0 && types[0] !== '') {
+        types.forEach(function(v) { url.searchParams.append('pf_t_product_type', v); });
+        window.location.replace(url.toString());
+      }
+    }
+  })();
+
   function initAll(){
     if (initialized) return true;
     var boostGrid = document.querySelector('.boost-sd__product-list');
@@ -88,10 +256,12 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
 
     /* === GRID SLIDER === */
     var saved = localStorage.getItem('dw-grid-cols');
-    var defaultCols = (saved && parseInt(saved) >= 3 && parseInt(saved) <= 8) ? parseInt(saved) : 4;
+    var defaultCols = (saved && parseInt(saved) >= 3 && parseInt(saved) <= 8) ? parseInt(saved) : 6;
 
     function applyGrid(val){
-      val = Math.max(3, Math.min(8, parseInt(val) || 4));
+      /* 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');
@@ -100,20 +270,31 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
       if (slider) slider.value = val;
     }
 
-    /* Build grid slider and inject into toolbar */
+    /* === 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';
-    var lbl = document.createElement('label');
-    lbl.textContent = 'Grid';
-    var input = document.createElement('input');
-    input.type = 'range'; input.min = '3'; input.max = '8'; input.value = defaultCols; input.id = 'dw-grid-range';
-    var numSpan = document.createElement('span');
-    numSpan.className = 'gc-num'; numSpan.id = 'dw-grid-num'; numSpan.textContent = defaultCols;
-    sliderWrap.appendChild(lbl);
-    sliderWrap.appendChild(input);
-    sliderWrap.appendChild(numSpan);
-    input.addEventListener('input', function(){ applyGrid(this.value); });
-    input.addEventListener('change', function(){ localStorage.setItem('dw-grid-cols', Math.max(3, Math.min(8, parseInt(this.value) || 4))); });
+    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');
@@ -136,39 +317,134 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
     btn.appendChild(svg);
     var filterLabel = document.createElement('span');
     filterLabel.id = 'dw-filter-label';
-    filterLabel.textContent = 'Filters';
+    /* 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(){
-      document.body.classList.toggle('dw-filters-collapsed');
-      filterLabel.textContent = document.body.classList.contains('dw-filters-collapsed') ? 'Filters' : 'Hide Filters';
+      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: true},
+      {label: 'Fabrics', value: 'Fabric', defaultOn: false},
+      {label: 'More', value: '', defaultOn: false}
+    ];
 
-    /* Per-page "Show N" selector removed per Steve 2026-06-24 — Boost handles
-       pagination; the Grid density slider stays in this pill (density hard rule). */
+    // Load from sessionStorage or use defaults
+    var savedTypes = sessionStorage.getItem('dw-ptype-filter');
+    var activeTypes = savedTypes ? JSON.parse(savedTypes) : ['Wallcovering'];
+
+    function applyTypeFilter() {
+      var checked = [];
+      ptypeBar.querySelectorAll('input[type=checkbox]').forEach(function(cb) {
+        if (cb.checked && cb.value) checked.push(cb.value);
+      });
+      sessionStorage.setItem('dw-ptype-filter', JSON.stringify(checked));
+
+      // 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 checked if no specific types are active (show all)
+        cb.checked = activeTypes.length === 0 || activeTypes.indexOf('') >= 0;
+      }
+      if (cb.checked) lbl.classList.add('dw-ptype-on');   /* fill active chip */
+      cb.addEventListener('change', function(){
+        /* 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);
 
     /* 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(){
-      var current = parseInt(document.getElementById('dw-grid-num').textContent) || defaultCols;
+      /* 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')) {
+      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')) {
+    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 */
@@ -184,3 +460,361 @@ body.dw-filters-collapsed .boost-sd__product-list[class*="grid--"]{grid-template
   }, 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;margin:0;
+  -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;
+    if (!rgb) return mode==='wheel' ? 999 : (mode==='dark' ? -1 : 9999);
+    if (mode==='light' || mode==='dark'){ var L=rgb2lab(rgb)[0]; return mode==='dark' ? -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 next to the grid-density pill (same toolbar row) */
+  var mountTries=0;
+  var mt=setInterval(function(){
+    var slider=document.querySelector('.dw-grid-slider');
+    var count=document.querySelector('.boost-sd__toolbar-item--product-count, .boost-sd__product-count');
+    var row = slider ? slider.parentElement : (count ? count.parentElement : null);
+    if(row){ clearInterval(mt); buildControl(row, count||slider); if(currentMode()!=='default') apply(); }
+    else if(mountTries++>60) 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>

← 8a2aa39f auto-save: 2026-06-27T10:08:38 (5 files) — pending-approval/  ·  back to Designer Wallcoverings  ·  Hue sort: mount into VISIBLE Boost toolbar (skip display:non 9b14ef65 →