[object Object]

← back to Designer Wallcoverings

card-hover v3: hide vendor on touch (mobile shows pattern name only)

1cde76665c4cf1901f90aaf69a6a4d7aeca27ad5 · 2026-06-22 19:27:22 -0700 · Steve Abrams

Files touched

Diff

commit 1cde76665c4cf1901f90aaf69a6a4d7aeca27ad5
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Jun 22 19:27:22 2026 -0700

    card-hover v3: hide vendor on touch (mobile shows pattern name only)
---
 .../theme-5.0-duplicate/assets/dw-card-hover.js    | 139 ++++++++++++++++-----
 1 file changed, 109 insertions(+), 30 deletions(-)

diff --git a/shopify/theme-5.0-duplicate/assets/dw-card-hover.js b/shopify/theme-5.0-duplicate/assets/dw-card-hover.js
index c9d5ca46..1a2adcd3 100644
--- a/shopify/theme-5.0-duplicate/assets/dw-card-hover.js
+++ b/shopify/theme-5.0-duplicate/assets/dw-card-hover.js
@@ -1,54 +1,133 @@
+/* ============================================================================
+   DW card hover overlay  v3  (2026-06-22) — collection / search product grids.
 
+   Clean image-ONLY grid: the native text band below every card image is removed
+   (kept in DOM for SEO/a11y via the clip technique). On HOVER / focus, a refined
+   overlay rises over the image bottom:
+        Pattern name     Title-Cased, scales with grid density
+        ── hairline
+        VENDOR           uppercase, wide tracking
 
-/* ============================================================================
-   DW card hover label (2026-06-22) — Boost product grid cards.
-   Default: clean image-only grid (full SEO title hidden).
-   On hover: reveal "Pattern, Color" (full title minus "wallcovering(s) | vendor")
-   + the vendor on a smaller line. Full title stays in DOM for SEO.
-   Re-applies to infinite-scroll-appended / Boost-rerendered cards via observer.
+   Touch devices (no hover) get a compact persistent label so phone shoppers
+   still see names. Honors prefers-reduced-motion. Works on the theme markup
+   (.product-list-item*) AND Boost AI Search 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 =
-      '.boost-sd__product-item .boost-sd__product-title,' +
-      '.boost-sd__product-item .boost-sd__product-vendor{opacity:0;transition:opacity .15s;}' +
-      '.boost-sd__product-item:hover .dw-hover-label{opacity:1;}' +
-      '.dw-hover-label{opacity:0;transition:opacity .15s;padding:6px 2px 0;pointer-events:none;}' +
-      '.dw-hover-label .pat{font-size:13px;font-weight:600;color:#1a1a1a;line-height:1.2;}' +
-      '.dw-hover-label .ven{font-size:11px;letter-spacing:.04em;text-transform:uppercase;color:#888;margin-top:2px;}' +
-      /* Mobile: no real hover -> always show, and ONLY the pattern name (hide vendor). */
-      '@media(max-width:768px){.boost-sd__product-item .dw-hover-label{opacity:1 !important;}.dw-hover-label .ven{display:none !important;}}';
+    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;}',
+
+      /* the overlay — rises over the image bottom on reveal */
+      '.dw-hover-overlay{position:absolute;left:0;right:0;bottom:0;z-index:2;',
+        'padding:22px 14px 13px;pointer-events:none;opacity:0;transform:translateY(7px);',
+        'transition:opacity .28s cubic-bezier(.4,0,.2,1),transform .28s cubic-bezier(.4,0,.2,1);',
+        'background:linear-gradient(to top,rgba(16,16,18,.80) 0%,rgba(16,16,18,.46) 46%,rgba(16,16,18,0) 100%);',
+        'font-family:-apple-system,system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;}',
+      '.product-list-item:hover .dw-hover-overlay,',
+      '.product-list-item:focus-within .dw-hover-overlay,',
+      '.boost-sd__product-item:hover .dw-hover-overlay,',
+      '.boost-sd__product-item:focus-within .dw-hover-overlay{opacity:1;transform:translateY(0);}',
+      '.dw-hover-overlay .pat{font-size:clamp(12px,1.02vw,14px);font-weight:600;line-height:1.22;',
+        'color:#fff;letter-spacing:.004em;text-shadow:0 1px 3px rgba(0,0,0,.55);}',
+      '.dw-hover-overlay .ven{font-size:10px;letter-spacing:.16em;text-transform:uppercase;',
+        'color:rgba(255,255,255,.86);margin-top:6px;text-shadow:0 1px 3px rgba(0,0,0,.55);}',
+      '.dw-hover-overlay .ven::before{content:"";display:block;width:18px;height:1px;',
+        'background:rgba(255,255,255,.5);margin:0 0 6px;}',
+
+      /* touch / no-hover: show a compact persistent label */
+      '@media (hover:none){.dw-hover-overlay{opacity:1;transform:none;padding:18px 12px 11px;}.dw-hover-overlay .ven{display:none;}}',
+      /* reduced motion: no slide */
+      '@media (prefers-reduced-motion:reduce){.dw-hover-overlay{transition:opacity .12s linear;transform: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) {
-    return s.replace(/\w\S*/g, function (w) { return w.charAt(0).toUpperCase() + w.slice(1); });
+    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-label')) return;
-    var tEl = card.querySelector('.boost-sd__product-title');
-    var vEl = card.querySelector('.boost-sd__product-vendor');
+    if (card.querySelector('.dw-hover-overlay')) return;
+    var tEl = card.querySelector(TITLE);
     if (!tEl) return;
-    var vendor = (vEl ? vEl.textContent : '').trim();
-    var name = tEl.textContent.split('|')[0].trim()        // drop "| vendor"
-      .replace(/\s*wallcoverings?\b/ig, '')                // drop "wallcovering(s)"
-      .replace(/[,\s]+$/, '').trim();
-    var lab = document.createElement('div');
-    lab.className = 'dw-hover-label';
-    lab.innerHTML = '<div class="pat">' + titleCase(name) + '</div>' +
-      (vendor ? '<div class="ven">' + titleCase(vendor) + '</div>' : '');
-    tEl.parentElement.appendChild(lab);
+    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;
+    if (getComputedStyle(imgWrap).position === 'static') imgWrap.style.position = 'relative';
+
+    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>' : '');
+    imgWrap.appendChild(ov);
   }
 
   function run() {
-    var cards = document.querySelectorAll('.boost-sd__product-item');
+    var cards = document.querySelectorAll(CARD);
     for (var i = 0; i < cards.length; i++) labelCard(cards[i]);
   }
 
@@ -58,7 +137,7 @@
     run();
   }
 
-  // Re-apply when Boost renders a grid or infinite scroll appends more cards.
+  // 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++) {

← 7df3fcf8 Bucket-B PR + Hollywood sample-only roll-variant scripts (DT  ·  back to Designer Wallcoverings  ·  Fix grid slider: force setProperty with !important to overri 24ae0051 →