[object Object]

← 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,'&quot;')+'" title="'+(im.title||'').replace(/"/g,'&quot;')+'">'+
-      '<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 →