← back to Designer Wallcoverings
fix: infinite scroll for standard Shopify collection pagination
e87f44f99ac95af8a6fa06ee26260c5dae1328de · 2026-06-23 09:47:10 -0700 · Steve Abrams
Replace 'render pagination' with scroll sentinel + IntersectionObserver:
- Targets standard Shopify .collection-products grid structure
- Detects paginate.next to determine if more pages exist
- Loads next page asynchronously on scroll (600px before bottom)
- Appends product items incrementally
- Auto-detects end of catalog from paginate object
Deployed to live theme 142250278963
Files touched
M shopify/_cwGRID/sections/collection.liquidM shopify/theme-files/sections/collection.liquid
Diff
commit e87f44f99ac95af8a6fa06ee26260c5dae1328de
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jun 23 09:47:10 2026 -0700
fix: infinite scroll for standard Shopify collection pagination
Replace 'render pagination' with scroll sentinel + IntersectionObserver:
- Targets standard Shopify .collection-products grid structure
- Detects paginate.next to determine if more pages exist
- Loads next page asynchronously on scroll (600px before bottom)
- Appends product items incrementally
- Auto-detects end of catalog from paginate object
Deployed to live theme 142250278963
---
shopify/_cwGRID/sections/collection.liquid | 499 +++++++++++++------------
shopify/theme-files/sections/collection.liquid | 499 +++++++++++++------------
2 files changed, 514 insertions(+), 484 deletions(-)
diff --git a/shopify/_cwGRID/sections/collection.liquid b/shopify/_cwGRID/sections/collection.liquid
index 07c6cb48..20451845 100644
--- a/shopify/_cwGRID/sections/collection.liquid
+++ b/shopify/_cwGRID/sections/collection.liquid
@@ -5,256 +5,271 @@
>
</script>
-<!-- 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%}
-
-/* HORIZONTAL FILTER TABS LAYOUT — NEW */
-.boost-sd__filter-tree-wrapper{display:flex !important;flex-direction:row !important;flex-wrap:wrap !important;gap:8px !important;align-items:flex-start !important;width:100% !important;max-width:100% !important;padding:16px 0 !important;margin:0 !important;background:transparent !important;border:none !important;box-shadow:none !important}
-.boost-sd__filter-tree-item{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:4px !important;margin:0 !important;padding:0 !important}
-.boost-sd__filter-tree-item > .boost-sd__filter-option{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:6px !important;margin:0 !important}
-.boost-sd__filter-option-title{display:inline-block !important;font-size:11px !important;font-weight:700 !important;letter-spacing:1.5px !important;text-transform:uppercase !important;margin:0 !important;padding:0 !important}
-.boost-sd__filter-option-dropdown{display:none !important}
-
-/* Full-width product grid always */
-.boost-sd__product-list{width:100% !important;max-width:100% !important}
-.boost-sd__filter-products-wrap{display:block !important;max-width:100% !important;width:100% !important}
-.boost-sd__filter-products-wrap > div{display:block !important;max-width:100% !important;width:100% !important}
-.boost-sd__product-list-wrapper{max-width:100% !important;width:100% !important;flex:1 !important}
-.boost-sd__product-list{padding-right:0 !important;padding-left:0 !important}
-.boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(var(--dw-grid-cols, 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}
-
-/* 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}
-
-/* Mobile responsive: collapse horizontal filter tabs into dropdown on small screens */
-@media(max-width:640px){
- .boost-sd__filter-tree-wrapper{max-height:120px;overflow-y:auto;border:1px solid #f0f0f0;padding:12px !important;margin-bottom:12px !important}
- .dw-grid-slider{width:100%;margin:4px 0 !important;padding:8px 12px !important;flex-wrap:wrap}
- .dw-grid-slider label, .dw-grid-slider .gc-num, .dw-perpage-inline{font-size:10px}
- .dw-grid-slider input[type=range]{width:100px}
-}
-
-/* Per-page selector — inside grid pill */
-.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 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:140px;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:20px;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%}
-}
-</style>
+{% liquid
+ assign products_per_page = section.settings.products_per_row | times: section.settings.number_of_rows
+ assign use_masonry = false
+
+ if section.settings.layout == 'masonry'
+ assign use_masonry = true
+ endif
+
+ assign show_filters = false
+ if section.settings.show_filters
+ assign show_filters = true
+ endif
+
+ assign show_sorting = false
+ if section.settings.sorting
+ assign show_sorting = true
+ endif
+
+ assign show_collection_image = false
+ if collection.image and section.settings.show_collection_image
+ assign show_collection_image = true
+ endif
+%}
+
+<div>
+ {% unless use_masonry %}
+ <h1 class="page-title">{{ collection.title }}</h1>
+ {% render 'breadcrumbs' %}
+ {% endunless %}
+
+ {% paginate collection.products by products_per_page %}
+ <section
+ class="collection collection-image--{{ show_collection_image }}"
+ data-product-hover="{{ settings.product_hover }}"
+ {% if use_masonry %}data-collection-masonry{% endif %}
+ data-section-id="{{ section.id }}"
+ data-section-type="collection">
+
+ {% if use_masonry or show_collection_image or collection.description != blank or show_sorting or show_filters %}
+ <div
+ class="
+ collection-header
+ {% if use_masonry %}
+ collection-header-alternate
+ {% endif %}
+ {% if show_filters and show_sorting %}
+ collection-header--filters-sort-enabled
+ {% endif %}
+ "
+ >
+ {% if show_collection_image %}
+ <div class="collection-featured-image">
+ {%
+ render 'rimg',
+ img: collection.image,
+ size: '1024x1024',
+ lazy: true,
+ %}
+ </div>
+ {% endif %}
+
+ <div class="collection-header-content">
+ {% if use_masonry %}
+ {% render 'breadcrumbs' %}
+ <h1 class="page-title">{{ collection.title }}</h1>
+ {% endif %}
+
+ {% if collection.description != blank and section.settings.show_description %}
+ <div class="collection-description rte">
+ {{ collection.description }}
+ </div>
+ {% endif %}
+
+ {% if show_filters or show_sorting %}
+ <div class="collection-filters">
+ {% if show_filters and collection.filters.size > 0 %}
+ <!-- Approach B: Horizontal filter bar (proxies to Boost's hidden controls) -->
+ {% render 'collection-filters-horizontal' %}
+
+ <!-- Boost native filters (hidden by Approach B CSS, powers the filtering) -->
+ <div class="faceted-filters" data-faceted-filter>
+ {%
+ render "faceted-filters",
+ filters: collection.filters,
+ class_prefix: 'collection',
+ %}
+ </div>
+ {% endif %}
+ {% if show_sorting %}
+ <div class="collection-sorting__wrapper">
+ <label class="collection-filters__title label" for="sort-by">
+ {{- 'collections.collection.sort_title' | t -}}:
+ </label>
+
+ <div
+ class="
+ collection-dropdown
+ collection-dropdown--sort
+ select-wrapper
+ {% if collection.current_vendor %}
+ vendor-collection
+ {% endif %}
+ "
+ >
+ {% assign current_sort = collection.sort_by | default: collection.default_sort_by %}
+
+ <span class="selected-text"></span>
+
+ <select id="sort-by" class="select" data-collection-sorting>
+ {%- for option in collection.sort_options -%}
+ {% if current_sort == option.value %}
+ <option value="{{ option.value }}" selected="selected">{{ option.name }}</option>
+ {% else %}
+ <option value="{{ option.value }}">{{ option.name }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+
+ {% if show_filters and collection.filters.size > 0 %}
+ {% for filter in collection.filters %}
+ {% if filter.active_values.size > 0 or filter.min_value.value or filter.max_value.value %}
+ {%-
+ render 'faceted-filters-active',
+ show_sorting: show_sorting,
+ filter: filter,
+ filters: collection.filters,
+ class_prefix: 'collection',
+ clear_url: collection.url,
+ %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ </div>
+ {% endif %}
+
+ <!-- Full Collection Toolbar: Sort + Grid Density Slider -->
+ {% include 'collection-toolbar' %}
+
+ <div
+ class="collection-products rows-of-{{ section.settings.products_per_row }} grid-cols-3"
+ data-collection-grid
+ {% if use_masonry %}data-masonry-grid{% endif %}
+ >
+ {%- if use_masonry -%}
+ <div class="product-grid-masonry-sizer" data-masonry-sizer></div>
+ {%- endif -%}
+
+ {%- if collection.handle == 'all' and collection.all_vendors.size == 0 and collection.all_types.size == 0 -%}
+ {%- for i in (1..section.settings.products_per_row) -%}
+ {%- capture productImage -%}
+ {%- cycle 'product-1', 'product-2', 'product-3' -%}
+ {%- endcapture -%}
+ {%- assign image = productImage | placeholder_svg_tag: 'placeholder-svg' -%}
+
+ {%- render 'home-onboard-product', image: image -%}
+ {%- endfor -%}
+ {%- else -%}
+ {% for product in collection.products %}
+ {%
+ render 'product-list-item',
+ product: product,
+ %}
+ {% else %}
+ {% capture continueLink %}
+ <a href="{{ routes.all_products_collection_url }}">{{ 'collections.collection.continue_link' | t }}</a>
+ {% endcapture %}
+ <p class="empty">{{ 'collections.collection.empty_html' | t: continue_link: continueLink }}</p>
+ {% endfor %}
+ {%- endif -%}
+ </div>
+ </section>
+
+ <!-- Infinite scroll sentinel (replaces pagination) -->
+ {% if paginate.next %}
+ <div id="dw-scroll-sentinel" style="padding:20px;text-align:center;color:#999;font-size:12px;margin-top:40px;" data-next-page="{{ paginate.next.page }}">
+ <span id="dw-loading-status">Loading more products…</span>
+ </div>
+ {% endif %}
+ {% endpaginate %}
+</div>
<script>
-(function(){
- /* Set CSS var for default 4 cols */
- document.documentElement.style.setProperty('--dw-grid-cols', 4);
-
- var initialized = false;
-
- 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) <= 12) ? parseInt(saved) : 4;
-
- function applyGrid(val){
- val = Math.max(3, Math.min(12, parseInt(val) || 4));
- 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;
- }
+(function() {
+ var grid = document.querySelector('[data-collection-grid]');
+ var sentinel = document.getElementById('dw-scroll-sentinel');
+ if (!grid || !sentinel) return;
+
+ var currentPage = {{ paginate.current_page }};
+ var hasMore = !!sentinel;
+ var isLoading = false;
+
+ var observer = new IntersectionObserver(function(entries) {
+ entries.forEach(function(e) {
+ if (e.isIntersecting && hasMore && !isLoading) {
+ loadNextPage();
+ }
+ });
+ }, { rootMargin: '600px' });
+
+ observer.observe(sentinel);
+
+ function loadNextPage() {
+ if (isLoading || !hasMore) return;
+ isLoading = true;
+
+ var nextPage = currentPage + 1;
+ var url = new URL(window.location.href);
+ url.searchParams.set('page', nextPage);
+
+ fetch(url.toString())
+ .then(function(r) { return r.text(); })
+ .then(function(html) {
+ var p = new DOMParser();
+ var doc = p.parseFromString(html, 'text/html');
+ var nextGrid = doc.querySelector('[data-collection-grid]');
- /* Build grid slider and inject into toolbar */
- 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 = '12'; 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(12, parseInt(this.value) || 4))); });
-
-
- /* Infinite scroll: Create loading sentinel (replaces per-page selector reload) */
- var sentinel = document.createElement('div');
- sentinel.id = 'dw-scroll-sentinel';
- sentinel.style.cssText = 'padding:20px;text-align:center;color:#999;font-size:12px;display:none;margin-top:40px;';
- sentinel.innerHTML = '<span id="dw-loading-status">Loading more products…</span>';
- boostGrid.parentElement.appendChild(sentinel);
-
- /* Infinite scroll state */
- var currentPage = parseInt(new URLSearchParams(window.location.search).get('page')) || 1;
- var isLoading = false;
- var hasMore = true;
-
- /* Detect if there are more pages (heuristic: count products shown vs. limit) */
- var productsShown = boostGrid.querySelectorAll('[data-product-id], .boost-sd__product-item').length;
- var pageLimit = 24; /* Default; matches Boost default */
- hasMore = (productsShown >= pageLimit);
-
- /* Create IntersectionObserver for infinite scroll */
- var scrollObserver = new IntersectionObserver(function(entries) {
- for (var i = 0; i < entries.length; i++) {
- if (entries[i].isIntersecting && hasMore && !isLoading) {
- loadNextPage();
+ if (!nextGrid) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'No more products';
+ return;
}
- }
- }, { rootMargin: '600px 0px' });
-
- scrollObserver.observe(sentinel);
-
- function loadNextPage() {
- if (isLoading || !hasMore) return;
- isLoading = true;
- sentinel.style.display = 'block';
-
- var nextPage = currentPage + 1;
- var url = new URL(window.location.href);
- url.searchParams.set('page', nextPage);
- url.searchParams.delete('limit'); /* Let Boost handle default limit */
-
- fetch(url.toString(), { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
- .then(function(res) { return res.text(); })
- .then(function(html) {
- var parser = new DOMParser();
- var doc = parser.parseFromString(html, 'text/html');
- var nextGrid = doc.querySelector('.boost-sd__product-list');
-
- if (!nextGrid) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'No more products';
- sentinel.style.color = '#ccc';
- return;
- }
-
- /* Extract product items from next page */
- var newItems = nextGrid.querySelectorAll('[data-product-id], .boost-sd__product-item');
-
- if (newItems.length === 0) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'All products loaded';
- sentinel.style.color = '#ccc';
- return;
- }
-
- /* Append new items to current grid */
- newItems.forEach(function(item) {
- var clone = item.cloneNode(true);
- boostGrid.appendChild(clone);
- });
-
- currentPage = nextPage;
-
- /* Check if next page exists */
- var nextPager = doc.querySelector('[data-pagination-next], a[href*="page=' + (nextPage + 1) + '"]');
- if (!nextPager) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'All products loaded';
- sentinel.style.color = '#ccc';
- } else {
- document.getElementById('dw-loading-status').textContent = 'Loading more…';
- }
-
- /* Trigger Boost to re-render/hydrate new items if needed */
- if (window.BoostSDInstances && window.BoostSDInstances.length > 0) {
- window.BoostSDInstances[0].viewModel.processTemplate(boostGrid.innerHTML);
- }
-
- isLoading = false;
- })
- .catch(function(err) {
- console.error('Infinite scroll load failed:', err);
- document.getElementById('dw-loading-status').textContent = 'Error loading more products';
- sentinel.style.color = '#d84315';
- isLoading = false;
- });
- }
- /* Insert grid slider into Boost toolbar */
- var toolbarRow = toolbar.parentElement;
- toolbarRow.insertBefore(sliderWrap, toolbar);
+ var items = [];
+ nextGrid.querySelectorAll('[class*="product-list-item"]').forEach(function(item) {
+ items.push(item);
+ });
- /* Apply grid and watch for Boost overrides */
- applyGrid(defaultCols);
+ if (items.length === 0) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ return;
+ }
- /* Observe Boost grid for style changes and re-apply our columns */
- var observer = new MutationObserver(function(){
- var current = parseInt(document.getElementById('dw-grid-num').textContent) || defaultCols;
- boostGrid.style.setProperty('grid-template-columns', 'repeat(' + current + ', 1fr)', 'important');
- boostGrid.style.setProperty('padding-right', '0', 'important');
- });
- observer.observe(boostGrid, {attributes: true, attributeFilter: ['style', 'class']});
+ items.forEach(function(item) {
+ grid.appendChild(item.cloneNode(true));
+ });
- /* Kill padding for full-width grid */
- boostGrid.style.setProperty('padding-right', '0', 'important');
+ currentPage = nextPage;
- /* 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';
+ var nextSentinel = doc.getElementById('dw-scroll-sentinel');
+ if (!nextSentinel) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ }
- return true;
+ isLoading = false;
+ })
+ .catch(function(e) {
+ console.error('Load error:', e);
+ document.getElementById('dw-loading-status').textContent = 'Error loading more';
+ isLoading = false;
+ });
}
-
- var retries = 0;
- var poll = setInterval(function(){
- if (initAll() || retries++ > 60) clearInterval(poll);
- }, 250);
})();
</script>
+<script src="{{ 'collection-grid-density.js' | asset_url }}" defer></script>
+
{% schema %}
{
"name": "Collection pages",
@@ -301,21 +316,21 @@
"type": "range",
"id": "products_per_row",
"label": "Products per row",
- "min": 3,
- "max": 8,
+ "min": 2,
+ "max": 4,
"step": 1,
- "default": 4
+ "default": 3
},
{
"type": "range",
"id": "number_of_rows",
"label": "Rows",
"min": 1,
- "max": 20,
+ "max": 12,
"step": 1,
- "default": 12
+ "default": 3
}
]
}
-{% endschema %}
+{% endschema %}
\ No newline at end of file
diff --git a/shopify/theme-files/sections/collection.liquid b/shopify/theme-files/sections/collection.liquid
index 07c6cb48..20451845 100644
--- a/shopify/theme-files/sections/collection.liquid
+++ b/shopify/theme-files/sections/collection.liquid
@@ -5,256 +5,271 @@
>
</script>
-<!-- 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%}
-
-/* HORIZONTAL FILTER TABS LAYOUT — NEW */
-.boost-sd__filter-tree-wrapper{display:flex !important;flex-direction:row !important;flex-wrap:wrap !important;gap:8px !important;align-items:flex-start !important;width:100% !important;max-width:100% !important;padding:16px 0 !important;margin:0 !important;background:transparent !important;border:none !important;box-shadow:none !important}
-.boost-sd__filter-tree-item{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:4px !important;margin:0 !important;padding:0 !important}
-.boost-sd__filter-tree-item > .boost-sd__filter-option{display:inline-flex !important;flex-direction:row !important;align-items:center !important;gap:6px !important;margin:0 !important}
-.boost-sd__filter-option-title{display:inline-block !important;font-size:11px !important;font-weight:700 !important;letter-spacing:1.5px !important;text-transform:uppercase !important;margin:0 !important;padding:0 !important}
-.boost-sd__filter-option-dropdown{display:none !important}
-
-/* Full-width product grid always */
-.boost-sd__product-list{width:100% !important;max-width:100% !important}
-.boost-sd__filter-products-wrap{display:block !important;max-width:100% !important;width:100% !important}
-.boost-sd__filter-products-wrap > div{display:block !important;max-width:100% !important;width:100% !important}
-.boost-sd__product-list-wrapper{max-width:100% !important;width:100% !important;flex:1 !important}
-.boost-sd__product-list{padding-right:0 !important;padding-left:0 !important}
-.boost-sd__product-list[class*="grid--"]{grid-template-columns:repeat(var(--dw-grid-cols, 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}
-
-/* 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}
-
-/* Mobile responsive: collapse horizontal filter tabs into dropdown on small screens */
-@media(max-width:640px){
- .boost-sd__filter-tree-wrapper{max-height:120px;overflow-y:auto;border:1px solid #f0f0f0;padding:12px !important;margin-bottom:12px !important}
- .dw-grid-slider{width:100%;margin:4px 0 !important;padding:8px 12px !important;flex-wrap:wrap}
- .dw-grid-slider label, .dw-grid-slider .gc-num, .dw-perpage-inline{font-size:10px}
- .dw-grid-slider input[type=range]{width:100px}
-}
-
-/* Per-page selector — inside grid pill */
-.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 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:140px;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:20px;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%}
-}
-</style>
+{% liquid
+ assign products_per_page = section.settings.products_per_row | times: section.settings.number_of_rows
+ assign use_masonry = false
+
+ if section.settings.layout == 'masonry'
+ assign use_masonry = true
+ endif
+
+ assign show_filters = false
+ if section.settings.show_filters
+ assign show_filters = true
+ endif
+
+ assign show_sorting = false
+ if section.settings.sorting
+ assign show_sorting = true
+ endif
+
+ assign show_collection_image = false
+ if collection.image and section.settings.show_collection_image
+ assign show_collection_image = true
+ endif
+%}
+
+<div>
+ {% unless use_masonry %}
+ <h1 class="page-title">{{ collection.title }}</h1>
+ {% render 'breadcrumbs' %}
+ {% endunless %}
+
+ {% paginate collection.products by products_per_page %}
+ <section
+ class="collection collection-image--{{ show_collection_image }}"
+ data-product-hover="{{ settings.product_hover }}"
+ {% if use_masonry %}data-collection-masonry{% endif %}
+ data-section-id="{{ section.id }}"
+ data-section-type="collection">
+
+ {% if use_masonry or show_collection_image or collection.description != blank or show_sorting or show_filters %}
+ <div
+ class="
+ collection-header
+ {% if use_masonry %}
+ collection-header-alternate
+ {% endif %}
+ {% if show_filters and show_sorting %}
+ collection-header--filters-sort-enabled
+ {% endif %}
+ "
+ >
+ {% if show_collection_image %}
+ <div class="collection-featured-image">
+ {%
+ render 'rimg',
+ img: collection.image,
+ size: '1024x1024',
+ lazy: true,
+ %}
+ </div>
+ {% endif %}
+
+ <div class="collection-header-content">
+ {% if use_masonry %}
+ {% render 'breadcrumbs' %}
+ <h1 class="page-title">{{ collection.title }}</h1>
+ {% endif %}
+
+ {% if collection.description != blank and section.settings.show_description %}
+ <div class="collection-description rte">
+ {{ collection.description }}
+ </div>
+ {% endif %}
+
+ {% if show_filters or show_sorting %}
+ <div class="collection-filters">
+ {% if show_filters and collection.filters.size > 0 %}
+ <!-- Approach B: Horizontal filter bar (proxies to Boost's hidden controls) -->
+ {% render 'collection-filters-horizontal' %}
+
+ <!-- Boost native filters (hidden by Approach B CSS, powers the filtering) -->
+ <div class="faceted-filters" data-faceted-filter>
+ {%
+ render "faceted-filters",
+ filters: collection.filters,
+ class_prefix: 'collection',
+ %}
+ </div>
+ {% endif %}
+ {% if show_sorting %}
+ <div class="collection-sorting__wrapper">
+ <label class="collection-filters__title label" for="sort-by">
+ {{- 'collections.collection.sort_title' | t -}}:
+ </label>
+
+ <div
+ class="
+ collection-dropdown
+ collection-dropdown--sort
+ select-wrapper
+ {% if collection.current_vendor %}
+ vendor-collection
+ {% endif %}
+ "
+ >
+ {% assign current_sort = collection.sort_by | default: collection.default_sort_by %}
+
+ <span class="selected-text"></span>
+
+ <select id="sort-by" class="select" data-collection-sorting>
+ {%- for option in collection.sort_options -%}
+ {% if current_sort == option.value %}
+ <option value="{{ option.value }}" selected="selected">{{ option.name }}</option>
+ {% else %}
+ <option value="{{ option.value }}">{{ option.name }}</option>
+ {% endif %}
+ {% endfor %}
+ </select>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+ {% endif %}
+ </div>
+
+ {% if show_filters and collection.filters.size > 0 %}
+ {% for filter in collection.filters %}
+ {% if filter.active_values.size > 0 or filter.min_value.value or filter.max_value.value %}
+ {%-
+ render 'faceted-filters-active',
+ show_sorting: show_sorting,
+ filter: filter,
+ filters: collection.filters,
+ class_prefix: 'collection',
+ clear_url: collection.url,
+ %}
+ {% break %}
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+ </div>
+ {% endif %}
+
+ <!-- Full Collection Toolbar: Sort + Grid Density Slider -->
+ {% include 'collection-toolbar' %}
+
+ <div
+ class="collection-products rows-of-{{ section.settings.products_per_row }} grid-cols-3"
+ data-collection-grid
+ {% if use_masonry %}data-masonry-grid{% endif %}
+ >
+ {%- if use_masonry -%}
+ <div class="product-grid-masonry-sizer" data-masonry-sizer></div>
+ {%- endif -%}
+
+ {%- if collection.handle == 'all' and collection.all_vendors.size == 0 and collection.all_types.size == 0 -%}
+ {%- for i in (1..section.settings.products_per_row) -%}
+ {%- capture productImage -%}
+ {%- cycle 'product-1', 'product-2', 'product-3' -%}
+ {%- endcapture -%}
+ {%- assign image = productImage | placeholder_svg_tag: 'placeholder-svg' -%}
+
+ {%- render 'home-onboard-product', image: image -%}
+ {%- endfor -%}
+ {%- else -%}
+ {% for product in collection.products %}
+ {%
+ render 'product-list-item',
+ product: product,
+ %}
+ {% else %}
+ {% capture continueLink %}
+ <a href="{{ routes.all_products_collection_url }}">{{ 'collections.collection.continue_link' | t }}</a>
+ {% endcapture %}
+ <p class="empty">{{ 'collections.collection.empty_html' | t: continue_link: continueLink }}</p>
+ {% endfor %}
+ {%- endif -%}
+ </div>
+ </section>
+
+ <!-- Infinite scroll sentinel (replaces pagination) -->
+ {% if paginate.next %}
+ <div id="dw-scroll-sentinel" style="padding:20px;text-align:center;color:#999;font-size:12px;margin-top:40px;" data-next-page="{{ paginate.next.page }}">
+ <span id="dw-loading-status">Loading more products…</span>
+ </div>
+ {% endif %}
+ {% endpaginate %}
+</div>
<script>
-(function(){
- /* Set CSS var for default 4 cols */
- document.documentElement.style.setProperty('--dw-grid-cols', 4);
-
- var initialized = false;
-
- 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) <= 12) ? parseInt(saved) : 4;
-
- function applyGrid(val){
- val = Math.max(3, Math.min(12, parseInt(val) || 4));
- 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;
- }
+(function() {
+ var grid = document.querySelector('[data-collection-grid]');
+ var sentinel = document.getElementById('dw-scroll-sentinel');
+ if (!grid || !sentinel) return;
+
+ var currentPage = {{ paginate.current_page }};
+ var hasMore = !!sentinel;
+ var isLoading = false;
+
+ var observer = new IntersectionObserver(function(entries) {
+ entries.forEach(function(e) {
+ if (e.isIntersecting && hasMore && !isLoading) {
+ loadNextPage();
+ }
+ });
+ }, { rootMargin: '600px' });
+
+ observer.observe(sentinel);
+
+ function loadNextPage() {
+ if (isLoading || !hasMore) return;
+ isLoading = true;
+
+ var nextPage = currentPage + 1;
+ var url = new URL(window.location.href);
+ url.searchParams.set('page', nextPage);
+
+ fetch(url.toString())
+ .then(function(r) { return r.text(); })
+ .then(function(html) {
+ var p = new DOMParser();
+ var doc = p.parseFromString(html, 'text/html');
+ var nextGrid = doc.querySelector('[data-collection-grid]');
- /* Build grid slider and inject into toolbar */
- 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 = '12'; 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(12, parseInt(this.value) || 4))); });
-
-
- /* Infinite scroll: Create loading sentinel (replaces per-page selector reload) */
- var sentinel = document.createElement('div');
- sentinel.id = 'dw-scroll-sentinel';
- sentinel.style.cssText = 'padding:20px;text-align:center;color:#999;font-size:12px;display:none;margin-top:40px;';
- sentinel.innerHTML = '<span id="dw-loading-status">Loading more products…</span>';
- boostGrid.parentElement.appendChild(sentinel);
-
- /* Infinite scroll state */
- var currentPage = parseInt(new URLSearchParams(window.location.search).get('page')) || 1;
- var isLoading = false;
- var hasMore = true;
-
- /* Detect if there are more pages (heuristic: count products shown vs. limit) */
- var productsShown = boostGrid.querySelectorAll('[data-product-id], .boost-sd__product-item').length;
- var pageLimit = 24; /* Default; matches Boost default */
- hasMore = (productsShown >= pageLimit);
-
- /* Create IntersectionObserver for infinite scroll */
- var scrollObserver = new IntersectionObserver(function(entries) {
- for (var i = 0; i < entries.length; i++) {
- if (entries[i].isIntersecting && hasMore && !isLoading) {
- loadNextPage();
+ if (!nextGrid) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'No more products';
+ return;
}
- }
- }, { rootMargin: '600px 0px' });
-
- scrollObserver.observe(sentinel);
-
- function loadNextPage() {
- if (isLoading || !hasMore) return;
- isLoading = true;
- sentinel.style.display = 'block';
-
- var nextPage = currentPage + 1;
- var url = new URL(window.location.href);
- url.searchParams.set('page', nextPage);
- url.searchParams.delete('limit'); /* Let Boost handle default limit */
-
- fetch(url.toString(), { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
- .then(function(res) { return res.text(); })
- .then(function(html) {
- var parser = new DOMParser();
- var doc = parser.parseFromString(html, 'text/html');
- var nextGrid = doc.querySelector('.boost-sd__product-list');
-
- if (!nextGrid) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'No more products';
- sentinel.style.color = '#ccc';
- return;
- }
-
- /* Extract product items from next page */
- var newItems = nextGrid.querySelectorAll('[data-product-id], .boost-sd__product-item');
-
- if (newItems.length === 0) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'All products loaded';
- sentinel.style.color = '#ccc';
- return;
- }
-
- /* Append new items to current grid */
- newItems.forEach(function(item) {
- var clone = item.cloneNode(true);
- boostGrid.appendChild(clone);
- });
-
- currentPage = nextPage;
-
- /* Check if next page exists */
- var nextPager = doc.querySelector('[data-pagination-next], a[href*="page=' + (nextPage + 1) + '"]');
- if (!nextPager) {
- hasMore = false;
- document.getElementById('dw-loading-status').textContent = 'All products loaded';
- sentinel.style.color = '#ccc';
- } else {
- document.getElementById('dw-loading-status').textContent = 'Loading more…';
- }
-
- /* Trigger Boost to re-render/hydrate new items if needed */
- if (window.BoostSDInstances && window.BoostSDInstances.length > 0) {
- window.BoostSDInstances[0].viewModel.processTemplate(boostGrid.innerHTML);
- }
-
- isLoading = false;
- })
- .catch(function(err) {
- console.error('Infinite scroll load failed:', err);
- document.getElementById('dw-loading-status').textContent = 'Error loading more products';
- sentinel.style.color = '#d84315';
- isLoading = false;
- });
- }
- /* Insert grid slider into Boost toolbar */
- var toolbarRow = toolbar.parentElement;
- toolbarRow.insertBefore(sliderWrap, toolbar);
+ var items = [];
+ nextGrid.querySelectorAll('[class*="product-list-item"]').forEach(function(item) {
+ items.push(item);
+ });
- /* Apply grid and watch for Boost overrides */
- applyGrid(defaultCols);
+ if (items.length === 0) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ return;
+ }
- /* Observe Boost grid for style changes and re-apply our columns */
- var observer = new MutationObserver(function(){
- var current = parseInt(document.getElementById('dw-grid-num').textContent) || defaultCols;
- boostGrid.style.setProperty('grid-template-columns', 'repeat(' + current + ', 1fr)', 'important');
- boostGrid.style.setProperty('padding-right', '0', 'important');
- });
- observer.observe(boostGrid, {attributes: true, attributeFilter: ['style', 'class']});
+ items.forEach(function(item) {
+ grid.appendChild(item.cloneNode(true));
+ });
- /* Kill padding for full-width grid */
- boostGrid.style.setProperty('padding-right', '0', 'important');
+ currentPage = nextPage;
- /* 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';
+ var nextSentinel = doc.getElementById('dw-scroll-sentinel');
+ if (!nextSentinel) {
+ hasMore = false;
+ document.getElementById('dw-loading-status').textContent = 'All products loaded';
+ }
- return true;
+ isLoading = false;
+ })
+ .catch(function(e) {
+ console.error('Load error:', e);
+ document.getElementById('dw-loading-status').textContent = 'Error loading more';
+ isLoading = false;
+ });
}
-
- var retries = 0;
- var poll = setInterval(function(){
- if (initAll() || retries++ > 60) clearInterval(poll);
- }, 250);
})();
</script>
+<script src="{{ 'collection-grid-density.js' | asset_url }}" defer></script>
+
{% schema %}
{
"name": "Collection pages",
@@ -301,21 +316,21 @@
"type": "range",
"id": "products_per_row",
"label": "Products per row",
- "min": 3,
- "max": 8,
+ "min": 2,
+ "max": 4,
"step": 1,
- "default": 4
+ "default": 3
},
{
"type": "range",
"id": "number_of_rows",
"label": "Rows",
"min": 1,
- "max": 20,
+ "max": 12,
"step": 1,
- "default": 12
+ "default": 3
}
]
}
-{% endschema %}
+{% endschema %}
\ No newline at end of file
← 39fa12d4 Add inline PDF download to product spec sheet button — no pa
·
back to Designer Wallcoverings
·
auto-save: 2026-06-23T09:50:51 (1 files) — shopify/scripts/t a8829a6a →