[object Object]

← back to Glitterwalls

hero: hi-res only (>=1600px wide) carousel; 2x2 grid fallback if not enough hi-res

8ccb35250d5dee1173a55a02233a1fb95e65b1d2 · 2026-05-08 08:21:23 -0700 · Steve Abrams

Files touched

Diff

commit 8ccb35250d5dee1173a55a02233a1fb95e65b1d2
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri May 8 08:21:23 2026 -0700

    hero: hi-res only (>=1600px wide) carousel; 2x2 grid fallback if not enough hi-res
---
 public/index.html | 108 +++++++++++++++++++++++++++++++-----------------------
 1 file changed, 63 insertions(+), 45 deletions(-)

diff --git a/public/index.html b/public/index.html
index e082a38..f0720be 100644
--- a/public/index.html
+++ b/public/index.html
@@ -921,63 +921,81 @@ loadGridPage();
       });
     }
   }, true);
-// DW STANDARD: hero crossfade carousel — cycles through top-12 newest product images
-// every 5 seconds with a CSS opacity crossfade. Falls back to /hero-bg.jpg.
-(async function heroCarousel() {
+// HERO: only true high-res Shopify images (>=1600px wide). If <4 hi-res candidates,
+// fall back to a 2x2 grid of the best-resolution images we have. Locked 2026-05-08.
+(async function heroHiResOrGrid() {
+  const bg = document.getElementById('heroBg');
+  if (!bg) return;
+  const HIRES_MIN = 1600;
+  function strip(u){ return (u||'').replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4'); }
+  function bumpWidth(u, w){
+    if (!u) return u;
+    const sized = u.replace(/(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '_'+w+'x$1$3');
+    return sized;
+  }
+  function probe(url) {
+    return new Promise((resolve) => {
+      const im = new Image();
+      im.onload = () => resolve({ url, w: im.naturalWidth, h: im.naturalHeight });
+      im.onerror = () => resolve(null);
+      im.src = url;
+    });
+  }
   try {
-    const r = await fetch('/api/products?limit=100');
+    const r = await fetch('/api/products?limit=200');
     if (!r.ok) return;
     const d = await r.json();
-    const items = (d.items || [])
+    const cands = (d.items || [])
       .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
-      .sort((a,b) => String(b.created_at || '').localeCompare(String(a.created_at || '')))
-      .slice(0, 12);
-    if (items.length < 2) {
-      // single item: just set it
-      if (items.length === 1) {
-        const url = items[0].image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
-        const bg = document.getElementById('heroBg');
-        if (bg) bg.style.backgroundImage = "url('" + url + "')";
-      }
-      return;
-    }
-    // Build two background layers for crossfade
-    const bg = document.getElementById('heroBg');
-    if (!bg) return;
-    // Layer A is current (bg itself), Layer B is the next (::after via a clone overlay)
-    const overlay = document.createElement('div');
-    overlay.style.cssText = bg.style.cssText + ';position:absolute;inset:0;background-size:cover;background-position:center;opacity:0;transition:opacity 1.8s ease;z-index:1;';
-    bg.style.position = 'absolute';
-    bg.style.inset = '0';
-    bg.style.transition = 'opacity 1.8s ease';
-    bg.style.zIndex = '0';
-    bg.parentNode.insertBefore(overlay, bg.nextSibling);
-
-    // Start with a random offset so not all sites look the same on load
-    let idx = Math.floor(Math.random() * items.length);
-    function getUrl(i) {
-      return items[i % items.length].image_url.replace(/(_\d+x\d*)(\.(jpg|jpeg|png|webp|gif))(\?|$)/i, '$2$4');
-    }
-    // Set initial bg
-    bg.style.backgroundImage = "url('" + getUrl(idx) + "')";
-
-    let useOverlay = false;
-    setInterval(() => {
-      idx = (idx + 1) % items.length;
-      const nextUrl = getUrl(idx);
-      if (!useOverlay) {
-        // Preload into overlay, then fade it in
+      .map(p => ({ title: p.title, url: strip(p.image_url) }))
+      .slice(0, 24);
+    if (cands.length === 0) return;
+
+    // Probe original (un-resized) URLs in parallel — Shopify returns the master file.
+    const probed = (await Promise.all(cands.map(c => probe(c.url)))).filter(Boolean);
+    probed.sort((a,b) => b.w - a.w);
+    const hires = probed.filter(p => p.w >= HIRES_MIN).slice(0, 8);
+
+    if (hires.length >= 4) {
+      // Single-image full-bleed crossfade carousel of hi-res only
+      const overlay = document.createElement('div');
+      overlay.style.cssText = 'position:absolute;inset:0;background-size:cover;background-position:center;opacity:0;transition:opacity 1.8s ease;z-index:1';
+      bg.style.position = 'absolute'; bg.style.inset = '0';
+      bg.style.transition = 'opacity 1.8s ease'; bg.style.zIndex = '0';
+      bg.parentNode.insertBefore(overlay, bg.nextSibling);
+      let idx = Math.floor(Math.random() * hires.length);
+      bg.style.backgroundImage = "url('" + hires[idx].url + "')";
+      setInterval(() => {
+        idx = (idx + 1) % hires.length;
+        const nextUrl = hires[idx].url;
         overlay.style.backgroundImage = "url('" + nextUrl + "')";
         overlay.style.opacity = '1';
         setTimeout(() => {
-          // Once overlay is visible, swap bg to match, then hide overlay
           bg.style.backgroundImage = "url('" + nextUrl + "')";
           overlay.style.transition = 'none';
           overlay.style.opacity = '0';
           setTimeout(() => { overlay.style.transition = 'opacity 1.8s ease'; }, 50);
         }, 1900);
-      }
-    }, 6000);
+      }, 6000);
+      return;
+    }
+
+    // Fallback: 2x2 grid of the 4 best-resolution images (no fake hero)
+    const top4 = probed.slice(0, 4);
+    if (top4.length < 1) return;
+    while (top4.length < 4) top4.push(top4[top4.length % Math.max(1, top4.length)]);
+    bg.style.backgroundImage = 'none';
+    bg.style.background = '#000';
+    bg.style.position = 'absolute'; bg.style.inset = '0';
+    bg.style.display = 'grid';
+    bg.style.gridTemplateColumns = '1fr 1fr';
+    bg.style.gridTemplateRows = '1fr 1fr';
+    bg.style.gap = '2px';
+    for (const tile of top4) {
+      const cell = document.createElement('div');
+      cell.style.cssText = "background-size:cover;background-position:center;background-image:url('"+tile.url+"');";
+      bg.appendChild(cell);
+    }
   } catch(e) {}
 })();
 

← 5ffb6b3 lock storefront scope to GLS/GLM SKU series + sequin/glitter  ·  back to Glitterwalls  ·  hero: shuffle hi-res pool every page load — both carousel or 1d62ecd →