[object Object]

← back to Designer Wallcoverings

Collection hero fix: harden image/no-image switch via body.dw-collection-has-hero; F3 mobile gap verified (branding 118->74px); confirm JS errors owned by companion grid.js memo

fff83e00444faa6041a3fce36cd17ac7939d810e · 2026-06-23 15:16:53 -0700 · Steve Studio

Files touched

Diff

commit fff83e00444faa6041a3fce36cd17ac7939d810e
Author: Steve Studio <steve@designerwallcoverings.com>
Date:   Tue Jun 23 15:16:53 2026 -0700

    Collection hero fix: harden image/no-image switch via body.dw-collection-has-hero; F3 mobile gap verified (branding 118->74px); confirm JS errors owned by companion grid.js memo
---
 shopify/collection-hero-fix/console-probe.js       | 13 ++++
 shopify/collection-hero-fix/dw-collection-hero.css |  4 +-
 .../probe-collection-defects.js                    | 74 ++++++++++++++++++
 .../verify-collection-hero-fix.js                  | 91 ++++++++++++++++++++++
 4 files changed, 180 insertions(+), 2 deletions(-)

diff --git a/shopify/collection-hero-fix/console-probe.js b/shopify/collection-hero-fix/console-probe.js
new file mode 100644
index 00000000..33a6ab93
--- /dev/null
+++ b/shopify/collection-hero-fix/console-probe.js
@@ -0,0 +1,13 @@
+const { chromium } = require('playwright');
+(async () => {
+  const b = await chromium.launch();
+  const p = await (await b.newContext({viewport:{width:1440,height:900}})).newPage();
+  const errs = [];
+  p.on('console', m => { if (m.type()==='error') errs.push(m.text()); });
+  p.on('pageerror', e => errs.push('PAGEERROR: '+e.message));
+  await p.goto('https://www.designerwallcoverings.com/collections/all',{waitUntil:'domcontentloaded',timeout:60000});
+  await p.waitForTimeout(4000);
+  console.log('CONSOLE ERRORS ('+errs.length+'):');
+  [...new Set(errs)].slice(0,15).forEach(e=>console.log(' -', e.slice(0,170)));
+  await b.close();
+})();
diff --git a/shopify/collection-hero-fix/dw-collection-hero.css b/shopify/collection-hero-fix/dw-collection-hero.css
index 5cd61ab6..006e73cd 100644
--- a/shopify/collection-hero-fix/dw-collection-hero.css
+++ b/shopify/collection-hero-fix/dw-collection-hero.css
@@ -83,7 +83,7 @@ body.template-collection .boost-sd__product-filter-fallback .page-title {
    (set only when collection.image OR settings.collection_default_hero exists)
    AND Boost rendered no native image-inner. Excludes .dw-has-hero so the
    image case (painted by the snippet's inline <style>) is never stripped. */
-.boost-sd__collection-header:not(.dw-has-hero):not(:has(.boost-sd__header-image-inner)) .boost-sd__header-title {
+body:not(.dw-collection-has-hero) .boost-sd__collection-header:not(:has(.boost-sd__header-image-inner)) .boost-sd__header-title {
   background: transparent !important;
   color: #1a1a1a !important;
   border-radius: 0 !important;
@@ -93,7 +93,7 @@ body.template-collection .boost-sd__product-filter-fallback .page-title {
   text-transform: uppercase;
   font-weight: 300;
 }
-.boost-sd__collection-header:not(.dw-has-hero):not(:has(.boost-sd__header-image-inner)) {
+body:not(.dw-collection-has-hero) .boost-sd__collection-header:not(:has(.boost-sd__header-image-inner)) {
   background: #f4f2ee;
   min-height: 200px;
   display: flex;
diff --git a/shopify/collection-hero-fix/probe-collection-defects.js b/shopify/collection-hero-fix/probe-collection-defects.js
new file mode 100644
index 00000000..083c1c5e
--- /dev/null
+++ b/shopify/collection-hero-fix/probe-collection-defects.js
@@ -0,0 +1,74 @@
+// Probe live /collections/all to identify the grey-pill hero element + mobile header gap.
+// Read-only: no clicks, no writes. $0 (local).
+const { chromium } = require('playwright');
+
+(async () => {
+  const url = 'https://www.designerwallcoverings.com/collections/all';
+  const browser = await chromium.launch();
+
+  // ---- DESKTOP: find the grey pill behind "Products" ----
+  const ctxD = await browser.newContext({ viewport: { width: 1440, height: 900 } });
+  const pD = await ctxD.newPage();
+  await pD.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
+  await pD.waitForTimeout(3500);
+
+  const titleInfo = await pD.evaluate(() => {
+    const out = [];
+    // find any element whose text is exactly the collection title
+    const candidates = [...document.querySelectorAll('h1,.page-title,.collection-title,.boost-sd__header-title,[class*="header-title"],[class*="page-title"]')];
+    for (const el of candidates) {
+      const t = (el.textContent || '').trim();
+      if (!t || t.length > 40) continue;
+      const cs = getComputedStyle(el);
+      const r = el.getBoundingClientRect();
+      out.push({
+        text: t, tag: el.tagName, cls: el.className,
+        bg: cs.backgroundColor, color: cs.color, radius: cs.borderRadius,
+        pad: cs.padding, display: cs.display,
+        w: Math.round(r.width), h: Math.round(r.height),
+        parentCls: el.parentElement ? el.parentElement.className : '',
+        parentBg: el.parentElement ? getComputedStyle(el.parentElement).backgroundColor : '',
+      });
+    }
+    // also the collection-header / boost header wrapper
+    const wraps = [];
+    for (const sel of ['.collection-header', '.boost-sd__collection-header', '.boost-sd__header-main', '.collection-header-content']) {
+      const el = document.querySelector(sel);
+      if (el) {
+        const cs = getComputedStyle(el); const r = el.getBoundingClientRect();
+        wraps.push({ sel, bg: cs.backgroundColor, w: Math.round(r.width), h: Math.round(r.height), radius: cs.borderRadius, pad: cs.padding });
+      }
+    }
+    // does a collection.image exist on the page?
+    const featured = document.querySelector('.collection-featured-image img, .boost-sd__header-image-inner img');
+    return { titles: out, wraps, hasFeaturedImage: !!featured, featuredSrc: featured ? featured.src : null };
+  });
+  console.log('=== DESKTOP TITLE / HEADER ===');
+  console.log(JSON.stringify(titleInfo, null, 2));
+  await ctxD.close();
+
+  // ---- MOBILE: measure the header gap ----
+  const iPhone = { viewport: { width: 390, height: 844 }, deviceScaleFactor: 3, isMobile: true, hasTouch: true,
+    userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1' };
+  const ctxM = await browser.newContext(iPhone);
+  const pM = await ctxM.newPage();
+  await pM.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 });
+  await pM.waitForTimeout(3500);
+
+  const headerInfo = await pM.evaluate(() => {
+    const rect = (sel) => { const el = document.querySelector(sel); if (!el) return null; const r = el.getBoundingClientRect(); const cs = getComputedStyle(el); return { sel, top: Math.round(r.top), bottom: Math.round(r.bottom), h: Math.round(r.height), pad: cs.padding, margin: cs.margin, minH: cs.minHeight, display: cs.display }; };
+    const sels = ['.header-tools-wrapper','.header-tools','.aligned-right','.mini-cart-wrapper','.cart-count','.header-main-content','.header-branding','.main-header','[data-section-header]'];
+    const measured = sels.map(rect).filter(Boolean);
+    // gap = top of branding minus bottom of cart-count
+    const cart = document.querySelector('.cart-count');
+    const brand = document.querySelector('.header-branding-desktop, .header-branding');
+    let gap = null;
+    if (cart && brand) gap = Math.round(brand.getBoundingClientRect().top - cart.getBoundingClientRect().bottom);
+    return { measured, cartToBrandGap: gap, windowH: window.innerHeight };
+  });
+  console.log('\n=== MOBILE HEADER ===');
+  console.log(JSON.stringify(headerInfo, null, 2));
+  await ctxM.close();
+
+  await browser.close();
+})();
diff --git a/shopify/collection-hero-fix/verify-collection-hero-fix.js b/shopify/collection-hero-fix/verify-collection-hero-fix.js
new file mode 100644
index 00000000..fddcddc1
--- /dev/null
+++ b/shopify/collection-hero-fix/verify-collection-hero-fix.js
@@ -0,0 +1,91 @@
+// Verify the staged collection-hero fix by applying the EXACT staged CSS + the
+// snippet's inline <style> output to the real live /collections/all DOM, both
+// viewports, BEFORE/AFTER screenshots. Read-only network; $0 local.
+const { chromium } = require('playwright');
+const fs = require('fs');
+
+const CSS = fs.readFileSync(__dirname + '/shopify/collection-hero-fix/dw-collection-hero.css', 'utf8');
+const URL = 'https://www.designerwallcoverings.com/collections/all';
+const SHOTS = '/tmp/dwshots';
+
+// Simulate the snippet output for the NO-IMAGE case (collection.image blank,
+// settings.collection_default_hero blank): the snippet emits only the wrapper
+// flag, no --dw-hero-img -> soft band + clean dark type. We also test the
+// IMAGE-PRESENT case by injecting a sample hero url.
+const SNIPPET_NOIMG = `
+  .boost-sd__collection-header,.template-collection .collection-header{--dw-hero-applied:1;}
+`;
+const SAMPLE_IMG = 'https://www.designerwallcoverings.com/cdn/shop/files/marble.jpg';
+const SNIPPET_IMG = `
+  body.dw-collection-has-hero .boost-sd__collection-header{
+    background-image:linear-gradient(180deg,rgba(0,0,0,.18),rgba(0,0,0,.34)),url("${SAMPLE_IMG}") !important;
+    background-size:cover !important;background-position:center !important;min-height:240px;
+    display:flex;align-items:center;justify-content:center;text-align:center;}
+  body.dw-collection-has-hero .boost-sd__collection-header .boost-sd__header-title{color:#fff !important;background:rgba(0,0,0,.42) !important;border-radius:4px !important;padding:12px 28px !important;display:inline-block !important;}
+`;
+
+async function probeTitle(page) {
+  return page.evaluate(() => {
+    const pick = (sel) => { const el = document.querySelector(sel); if (!el) return null; const cs = getComputedStyle(el); const r = el.getBoundingClientRect(); return { sel, bg: cs.backgroundColor, color: cs.color, radius: cs.borderRadius, w: Math.round(r.width), h: Math.round(r.height), visible: cs.display !== 'none' && r.height > 0 }; };
+    return {
+      boostTitle: pick('.boost-sd__header-title'),
+      themeTitle: pick('.page-title'),
+      boostHeader: pick('.boost-sd__collection-header'),
+    };
+  });
+}
+
+(async () => {
+  const browser = await chromium.launch();
+
+  for (const [name, vp, mobile] of [
+    ['desktop', { width: 1440, height: 900 }, false],
+    ['mobile', { width: 390, height: 844 }, true],
+  ]) {
+    const ctx = await browser.newContext({ viewport: vp, isMobile: mobile, deviceScaleFactor: mobile ? 3 : 1,
+      userAgent: mobile ? 'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.0 Mobile/15E148 Safari/604.1' : undefined });
+    const page = await ctx.newPage();
+    await page.goto(URL, { waitUntil: 'domcontentloaded', timeout: 60000 });
+    await page.waitForTimeout(3500);
+
+    // BEFORE
+    await page.screenshot({ path: `${SHOTS}/dev-${name}-BEFORE.png`, fullPage: false });
+    const before = await probeTitle(page);
+
+    // APPLY staged CSS + snippet (no-image case = the real /collections/all state)
+    await page.addStyleTag({ content: CSS });
+    await page.addStyleTag({ content: SNIPPET_NOIMG });
+    // tag wrapper class the snippet/JS would add for has-image detection (none here)
+    await page.waitForTimeout(400);
+    await page.screenshot({ path: `${SHOTS}/dev-${name}-AFTER-noimage.png`, fullPage: false });
+    const afterNoImg = await probeTitle(page);
+
+    // Now simulate IMAGE-PRESENT case (snippet sets body.dw-collection-has-hero)
+    await page.addStyleTag({ content: SNIPPET_IMG });
+    await page.evaluate(() => { document.body.classList.add('dw-collection-has-hero'); document.documentElement.classList.add('dw-coll-hero'); });
+    await page.waitForTimeout(400);
+    await page.screenshot({ path: `${SHOTS}/dev-${name}-AFTER-withimage.png`, fullPage: false });
+    const afterImg = await probeTitle(page);
+
+    console.log(`\n=== ${name.toUpperCase()} ===`);
+    console.log('BEFORE  boostTitle bg:', before.boostTitle && before.boostTitle.bg, '| themeTitle visible:', before.themeTitle && before.themeTitle.visible);
+    console.log('AFTER-noimg boostTitle bg:', afterNoImg.boostTitle && afterNoImg.boostTitle.bg, 'color:', afterNoImg.boostTitle && afterNoImg.boostTitle.color, '| themeTitle visible:', afterNoImg.themeTitle && afterNoImg.themeTitle.visible);
+    console.log('AFTER-img  boostTitle bg:', afterImg.boostTitle && afterImg.boostTitle.bg, 'color:', afterImg.boostTitle && afterImg.boostTitle.color, '| header bg-image applied:', afterImg.boostHeader && afterImg.boostHeader.bg);
+
+    if (mobile) {
+      const gap = await page.evaluate(() => {
+        const cart = document.querySelector('.cart-count');
+        const brand = document.querySelector('.header-branding-desktop, .header-branding');
+        const g = (cart && brand) ? Math.round(brand.getBoundingClientRect().top - cart.getBoundingClientRect().bottom) : null;
+        const bH = brand ? Math.round(brand.getBoundingClientRect().height) : null;
+        const mh = document.querySelector('.main-header');
+        return { cartToBrandGap: g, brandingHeight: bH, headerHeight: mh ? Math.round(mh.getBoundingClientRect().height) : null };
+      });
+      console.log('MOBILE F3 after-fix:', JSON.stringify(gap), '(BEFORE was gap~11, brandingH 118, headerH 160)');
+    }
+
+    await ctx.close();
+  }
+  await browser.close();
+  console.log('\nScreenshots in', SHOTS);
+})();

← 06ab4e14 Designtex: comprehensive gated memo + live-77 UOM flip scrip  ·  back to Designer Wallcoverings  ·  Draft gated live-push memo + before/after screenshots for co a3d6742a →