← 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, '"') + '" loading="lazy">' +
+ '<img onerror="imgFail(this)" src="' + url + '" alt="' + (p.title || '').replace(/"/g, '"') + '" 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 →