← back to Dw Fleet Registry
fleet viewer: downsize grid thumbnails (Shopify width param) — saved hero stays full-res
389f62143c5bc09133581357d00f9b840e3ee1ee · 2026-06-01 15:37:00 -0700 · SteveStudio2
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit 389f62143c5bc09133581357d00f9b840e3ee1ee
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Mon Jun 1 15:37:00 2026 -0700
fleet viewer: downsize grid thumbnails (Shopify width param) — saved hero stays full-res
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
viewer/index.html | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/viewer/index.html b/viewer/index.html
index 6cfdda7..a5b68d0 100644
--- a/viewer/index.html
+++ b/viewer/index.html
@@ -143,6 +143,15 @@ const $ = s => document.querySelector(s);
// Route cross-origin hero images through the same-origin proxy (fleet sites send
// CORP same-origin, which blocks direct <img> hotlinking). Local /static & /img pass through.
const heroSrc = u => !u ? '' : (u.charAt(0)==='/' ? u : '/img?u='+encodeURIComponent(u));
+// Downsized variant for GRID THUMBNAILS only — ask Shopify for a small render so a tile
+// pulls ~30KB instead of the multi-MB master. The actual saved hero always keeps the
+// full-res URL; this only rewrites the displayed src. Non-Shopify URLs fall back to full.
+const thumbSrc = (u, w) => {
+ if(!u) return '';
+ if(u.charAt(0)==='/') return u; // local /static, /img — already small
+ if(/cdn\.shopify\.com/i.test(u)){ const [base,qs]=u.split('?'); const ps=new URLSearchParams(qs||''); ps.set('width', String(w||400)); u=base+'?'+ps.toString(); }
+ return '/img?u='+encodeURIComponent(u);
+};
let SITES = [];
const hueOf = hex => { if(!/^#?[0-9a-f]{6}$/i.test(hex||''))return 999; const n=parseInt(hex.replace('#',''),16); const r=(n>>16&255)/255,g=(n>>8&255)/255,b=(n&255)/255; const mx=Math.max(r,g,b),mn=Math.min(r,g,b),d=mx-mn; let h=0; if(d){ if(mx===r)h=((g-b)/d)%6; else if(mx===g)h=(b-r)/d+2; else h=(r-g)/d+4; } return Math.round(h*60+360)%360; };
const accentOf = s => (s.local&&s.local.port?null:null) || (s.manifest&&s.manifest.theme&&s.manifest.theme.accent) || (s.local&&s.local.theme&&s.local.theme.accent) || '#9c7a4d';
@@ -178,7 +187,7 @@ function render(){
a.innerHTML =
'<div class="accentbar" style="background:'+s._accent+'"></div>'+
'<div class="thumb" data-domain="'+s.domain+'" style="background-color:'+s._accent+'22">'+
- (s.heroImage?'<img class="heroimg" loading="lazy" src="'+heroSrc(s.heroImage)+'" alt="" onerror="this.remove()">':'')+
+ (s.heroImage?'<img class="heroimg" loading="lazy" src="'+thumbSrc(s.heroImage,800)+'" alt="" onerror="this.remove()">':'')+
'<button class="heroref" type="button" title="Refresh to a new image — or drag an image here to set it" aria-label="refresh hero">↻</button>'+
'<button class="heropool" type="button" title="Manage hero pool — pick high-res images to rotate" aria-label="manage hero pool">⊞</button>'+
((s.heroImages&&s.heroImages.length>1)?'<span class="poolcount">'+s.heroImages.length+' heroes</span>':'')+
@@ -205,7 +214,7 @@ function lazyHeroes(){
if(el.querySelector('img.heroimg')) continue; // baked hero already painted
const d=el.dataset.domain;
fetch('/api/hero?domain='+encodeURIComponent(d)).then(r=>r.json()).then(j=>{
- if(j.image){ const u=heroSrc(j.image); const img=new Image(); img.onload=()=>{ el.style.backgroundImage='url('+u+')'; const ph=el.querySelector('.ph'); if(ph)ph.style.display='none'; }; img.src=u; }
+ if(j.image){ const u=thumbSrc(j.image,800); const img=new Image(); img.onload=()=>{ el.style.backgroundImage='url('+u+')'; const ph=el.querySelector('.ph'); if(ph)ph.style.display='none'; }; img.src=u; }
}).catch(()=>{});
}
},{rootMargin:'300px'});
@@ -306,7 +315,7 @@ function renderPool(){
if(!poolArr.length){ host.innerHTML='<div class="poolempty">No heroes yet — click images on the right to add. With an empty pool the live site keeps its sharp static hero.</div>'; }
else host.innerHTML = poolArr.map((u,i)=>
'<div class="poolitem" draggable="true" data-i="'+i+'">'+
- '<img src="'+heroSrc(u)+'" alt="" onerror="this.style.visibility=\'hidden\'">'+
+ '<img src="'+thumbSrc(u,200)+'" alt="" onerror="this.style.visibility=\'hidden\'">'+
'<div class="meta">'+(i===0?'<span class="primary">PRIMARY · </span>':'')+(resOf(u)||'')+'<br>'+u.split('/').pop().split('?')[0].slice(0,42)+'</div>'+
'<button class="rm" data-rm="'+i+'" title="remove">×</button>'+
'</div>').join('');
@@ -319,7 +328,7 @@ function renderLibrary(q){
const items=(LIB||[]).filter(im=>!ql||((im.title||'')+' '+im.src).toLowerCase().includes(ql)).slice(0,300);
grid.innerHTML = items.map(im=>
'<div class="libcell'+(poolArr.includes(im.src)?' added':'')+'" data-src="'+im.src.replace(/"/g,'"')+'" title="'+(im.title||'').replace(/"/g,'"')+'">'+
- '<img loading="lazy" src="'+heroSrc(im.src)+'" alt="">'+
+ '<img loading="lazy" src="'+thumbSrc(im.src,400)+'" alt="">'+
'<div class="added-tick">✓</div>'+
'<div class="res"><span>'+im.w+'×'+im.h+'</span></div>'+
'</div>').join('');
← e53dc97 viewer: deploy curated hero pools live (hero-pool.json rsync
·
back to Dw Fleet Registry
·
library: harvest full Shopify image arrays + room/pattern/sw c4ca891 →