[object Object]

← back to Glitterwalls

hero: static hero-bg.jpg back as position 1, then glitter carousel (headers cropped)

733a126a4186bac31d78a35c9bfa9d8b8732c774 · 2026-07-30 11:46:49 -0700 · Steve Abrams

- hero-bg.jpg painted immediately on load = position 1, before /api/products resolves
- glitter product images crossfade after it and loop back to the static image
- crossfade overlay now gets the center-12% crop (it previously bypassed the
  anti-header rule), so vendor banner/header chrome is lopped off every frame
- retired the <4-hi-res 2x2 grid fallback; static-only when no product images

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 733a126a4186bac31d78a35c9bfa9d8b8732c774
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 30 11:46:49 2026 -0700

    hero: static hero-bg.jpg back as position 1, then glitter carousel (headers cropped)
    
    - hero-bg.jpg painted immediately on load = position 1, before /api/products resolves
    - glitter product images crossfade after it and loop back to the static image
    - crossfade overlay now gets the center-12% crop (it previously bypassed the
      anti-header rule), so vendor banner/header chrome is lopped off every frame
    - retired the <4-hi-res 2x2 grid fallback; static-only when no product images
    
    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
 public/index.html | 97 +++++++++++++++++++++----------------------------------
 1 file changed, 36 insertions(+), 61 deletions(-)

diff --git a/public/index.html b/public/index.html
index e5b7049..555e78b 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1237,18 +1237,18 @@ loadGridPage();
       });
     }
   }, true);
-// 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() {
+// HERO: static hero-bg.jpg is POSITION 1 (painted immediately on load), then the
+// glitter product images crossfade after it and loop back to it. The scraped
+// product images carry vendor banner/header chrome, so every crossfade frame is
+// cropped center 12% to lop the headers off. (Static-first-then-carousel — Steve 2026-07-30.)
+(async function heroStaticFirstCarousel() {
   const bg = document.getElementById('heroBg');
   if (!bg) return;
+  const STATIC_HERO = '/hero-bg.jpg';
   const HIRES_MIN = 1600;
+  // Position 1 — clean static image up first, before /api/products resolves.
+  bg.style.backgroundImage = "url('" + STATIC_HERO + "')";
   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();
@@ -1257,6 +1257,7 @@ loadGridPage();
       im.src = url;
     });
   }
+  function shuffle(a){ for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];} return a; }
   try {
     const r = await fetch('/api/products?limit=200');
     if (!r.ok) return;
@@ -1265,64 +1266,38 @@ loadGridPage();
       .filter(p => p && p.image_url && /^https:\/\/(?:cdn\.shopify\.com|designerwallcoverings\.com)\//.test(p.image_url))
       .map(p => ({ title: p.title, url: strip(p.image_url) }))
       .slice(0, 24);
-    if (cands.length === 0) return;
+    if (cands.length === 0) return; // no product images → static-only hero
 
     // 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);
-    // Hi-res pool: only images that are genuinely high-end, high-res.
-    const hiresPool = probed.filter(p => p.w >= HIRES_MIN);
-    // Fisher-Yates shuffle so every page load picks a different set/order.
-    function shuffle(a){ for(let i=a.length-1;i>0;i--){const j=Math.floor(Math.random()*(i+1));[a[i],a[j]]=[a[j],a[i]];} return a; }
-    shuffle(hiresPool);
-
-    if (hiresPool.length >= 4) {
-      // Single-image full-bleed crossfade carousel of hi-res images only,
-      // randomized each visit.
-      const cycle = hiresPool.slice(0, Math.min(8, hiresPool.length));
-      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 = 0;
-      bg.style.backgroundImage = "url('" + cycle[idx].url + "')";
-      setInterval(() => {
-        idx = (idx + 1) % cycle.length;
-        const nextUrl = cycle[idx].url;
-        overlay.style.backgroundImage = "url('" + nextUrl + "')";
-        overlay.style.opacity = '1';
-        setTimeout(() => {
-          bg.style.backgroundImage = "url('" + nextUrl + "')";
-          overlay.style.transition = 'none';
-          overlay.style.opacity = '0';
-          setTimeout(() => { overlay.style.transition = 'opacity 1.8s ease'; }, 50);
-        }, 1900);
-      }, 6000);
-      return;
-    }
-
-    // 2×2 grid mode — pick 4 fresh tiles each visit. Prefer hi-res, only
-    // fall back to the next-best resolution if hi-res pool can't fill 4.
+    // Prefer genuinely hi-res; fall back to next-best so the carousel still runs.
     const ranked = probed.slice().sort((a,b) => b.w - a.w);
-    const tier1 = ranked.filter(p => p.w >= HIRES_MIN);
-    const tier2 = ranked.filter(p => p.w >= 1000 && p.w < HIRES_MIN);
-    let pool = tier1.length >= 4 ? tier1 : tier1.concat(tier2);
-    shuffle(pool);
-    const tiles = pool.slice(0, 4);
-    if (tiles.length < 1) return;
-    while (tiles.length < 4) tiles.push(tiles[tiles.length % Math.max(1, tiles.length)]);
-    bg.style.backgroundImage = 'none';
-    bg.style.background = '#000';
+    let productPool = ranked.filter(p => p.w >= HIRES_MIN);
+    if (productPool.length < 4) productPool = productPool.concat(ranked.filter(p => p.w >= 1000 && p.w < HIRES_MIN));
+    shuffle(productPool);
+    if (productPool.length === 0) return; // nothing usable → static-only hero
+
+    // cycle[0] = static hero (position 1); the rest are glitter product images.
+    const cycle = [{ url: STATIC_HERO }].concat(productPool.slice(0, 7));
+    const overlay = document.createElement('div');
+    // center 12% crop lops any banner/header chrome off the incoming crossfade frame.
+    overlay.style.cssText = 'position:absolute;inset:0;background-size:cover;background-position:center 12%;opacity:0;transition:opacity 1.8s ease;z-index:1';
     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 tiles) {
-      const cell = document.createElement('div');
-      cell.style.cssText = "background-size:cover;background-position:center;background-image:url('"+tile.url+"');";
-      bg.appendChild(cell);
-    }
+    bg.style.transition = 'opacity 1.8s ease'; bg.style.zIndex = '0';
+    bg.parentNode.insertBefore(overlay, bg.nextSibling);
+    let idx = 0; // already showing cycle[0] = static hero
+    setInterval(() => {
+      idx = (idx + 1) % cycle.length;
+      const nextUrl = cycle[idx].url;
+      overlay.style.backgroundImage = "url('" + nextUrl + "')";
+      overlay.style.opacity = '1';
+      setTimeout(() => {
+        bg.style.backgroundImage = "url('" + nextUrl + "')";
+        overlay.style.transition = 'none';
+        overlay.style.opacity = '0';
+        setTimeout(() => { overlay.style.transition = 'opacity 1.8s ease'; }, 50);
+      }, 1900);
+    }, 6000);
   } catch(e) {}
 })();
 

← 86cd45d search: scroll results into view on mobile (grid was below t  ·  back to Glitterwalls  ·  sync repo to live: preserve prod hero-bg.jpg + products.json 405d229 →