← 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 →