[object Object]

← back to Wallpapercanada

wallpapercanada: never show non-loading images (hide card/rail/modal on img onerror)

a5322e009db9b2f57571b52c49d6d5183e52e85a · 2026-09-22 10:40:47 -0700 · Steve Abrams

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FZB4e67U2DRKnZu3SVFk1m

Files touched

Diff

commit a5322e009db9b2f57571b52c49d6d5183e52e85a
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Sep 22 10:40:47 2026 -0700

    wallpapercanada: never show non-loading images (hide card/rail/modal on img onerror)
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
    Claude-Session: https://claude.ai/code/session_01FZB4e67U2DRKnZu3SVFk1m
---
 public/index.html | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/public/index.html b/public/index.html
index fbf1daa..9b39bbf 100644
--- a/public/index.html
+++ b/public/index.html
@@ -790,6 +790,9 @@ function cleanSku(s){
 // DW Shopify CDN + designerwallcoverings.com (defends against XSS via crafted products.json).
 // /img/ is server-controlled same-origin (not protocol-relative, not a javascript: URI) so it's safe.
 function safeImg(u) { u = u || ''; return (/^\/img\//.test(u) || /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(u)) ? u : '/hero-bg.jpg'; }
+// Steve rule: never show a non-loading image. On <img> load failure, hide the
+// whole card/rail-tile/modal-image-wrap so a broken-image box never renders.
+function imgFail(el) { var w = el && el.closest && el.closest('.card, .rail-card, .d-img-wrap'); if (w) w.style.display = 'none'; else if (el) el.style.display = 'none'; }
 // USD → CAD conversion (cosmetic; final price confirmed at checkout)
 const USD_CAD = 1.36;
 function cadPrice(usd) {
@@ -800,7 +803,7 @@ function cadPrice(usd) {
 function cardHTML(p) {
   const eager = state.page === 1;
   const cad = cadPrice(p.max_price);
-  return '<img data-f="image" loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+  return '<img onerror="imgFail(this)" data-f="image" loading="' + (eager ? 'eager' : 'lazy') + '"' + (eager ? ' fetchpriority="high"' : '') + ' src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
     + '<div class="overlay">'
     + '<div class="pat" data-f="pattern">' + escAttr(p.pattern_name || p.title) + '</div>'
     + '<div class="ven" data-f="vendor">Designer Wallcoverings' + (cad ? ' · <span class="cad-price">from ' + cad + ' CAD</span>' : '') + '</div>'
@@ -815,7 +818,7 @@ function rowHTML(p) {
   const mp = Number(p.max_price);
   const price = (p.max_price != null && !isNaN(mp) && mp > 0) ? '$' + mp.toFixed(2) : '—';
   const aes = LABELS[p.aesthetic] || p.aesthetic || '—';
-  return '<img data-f="image" loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
+  return '<img onerror="imgFail(this)" data-f="image" loading="lazy" src="' + escAttr(safeImg(p.image_url)) + '" alt="' + escAttr(p.title) + '">'
     + '<div class="r-pat" data-f="pattern">' + escAttr(p.pattern_name || p.title) + '</div>'
     + '<div class="r-col r-sku" data-f="vendor">' + escAttr(cleanSku(p.sku || p.handle_display || p.handle) || '—') + '</div>'
     + '<div class="r-col r-aes" data-f="color">' + (p.aesthetic ? sfDrill(p.aesthetic, String(aes).replace(/-/g, ' '), 'span') : escAttr(String(aes).replace(/-/g, ' '))) + '</div>'
@@ -839,8 +842,11 @@ function openDetails(p) {
   };
   const m = document.getElementById('dwmDetails');
   if (!m) return; // safety
-  m.querySelector('[data-d-img]').src = safeP.image_url;
-  m.querySelector('[data-d-img]').alt = safeP.title;
+  const dImg = m.querySelector('[data-d-img]');
+  const dWrap = dImg.closest('.d-img-wrap'); if (dWrap) dWrap.style.display = ''; // reset (element is reused across opens)
+  dImg.onerror = function () { imgFail(this); };
+  dImg.src = safeP.image_url;
+  dImg.alt = safeP.title;
   m.querySelector('[data-d-pat]').textContent = safeP.pattern;
   m.querySelector('[data-d-brand]').textContent = 'Designer Wallcoverings' + (cleanSku(safeP.sku) ? ' · ' + cleanSku(safeP.sku) : '');
   const priceEl = m.querySelector('[data-d-price]');
@@ -1516,7 +1522,7 @@ document.addEventListener('DOMContentLoaded', function(){ try { renderRailSectio
       const url = (p.image_url || '').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
       const cad = (typeof cadPrice === 'function') ? cadPrice(p.max_price) : '';
       return '<a class="rail-card" href="/sample/' + encodeURIComponent(p.handle_display || p.handle || p.sku) + '">' +
-        '<img src="' + url + '" alt="' + (p.title || '').replace(/"/g, '&quot;') + '" loading="lazy">' +
+        '<img onerror="imgFail(this)" src="' + url + '" alt="' + (p.title || '').replace(/"/g, '&quot;') + '" loading="lazy">' +
         '<div class="rc-title">' + ((p.title || '').split('|')[0] || '').trim() + '</div>' +
         '<div class="rc-meta">Designer Wallcoverings' + (cad ? ' · ' + cad + ' CAD' : '') + '</div>' +
       '</a>';

← befff70 wallpapercanada: remove all 'free sample delivery' copy (EN  ·  back to Wallpapercanada  ·  wallpapercanada: also hide cards that would fall back to the 34669bb →