[object Object]

← back to Domain Landings

wall: thumbnail-primary (screenshot) with click-to-live-iframe + /thumbs route

93a827bb19748d29ff156a112599b3ada520313e · 2026-08-17 15:55:36 -0700 · Steve Abrams

Files touched

Diff

commit 93a827bb19748d29ff156a112599b3ada520313e
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Mon Aug 17 15:55:36 2026 -0700

    wall: thumbnail-primary (screenshot) with click-to-live-iframe + /thumbs route
---
 server.js | 35 +++++++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/server.js b/server.js
index 760cfb8..9dfa7aa 100644
--- a/server.js
+++ b/server.js
@@ -198,9 +198,12 @@ input[type=range]{width:150px}
 .filter{background:#161b22;color:#e6edf3;border:1px solid #30363d;border-radius:7px;padding:6px 10px}
 .grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:14px;padding:18px}
 .card{background:#161b22;border:1px solid #21262d;border-radius:12px;overflow:hidden;display:flex;flex-direction:column}
-.frame{position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;background:#0d1117;border-bottom:1px solid #21262d}
+.frame{position:relative;width:100%;aspect-ratio:4/3;overflow:hidden;background:#0d1117;border-bottom:1px solid #21262d;cursor:pointer}
+.frame img{width:100%;height:100%;object-fit:cover;object-position:top center;display:block}
 .frame iframe{position:absolute;top:0;left:0;width:1280px;height:960px;border:0;transform-origin:0 0;pointer-events:none;background:#fff}
-.ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#484f58;font-size:12px}
+.ph{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;color:#484f58;font-size:12px;text-align:center;padding:8px}
+.live{position:absolute;top:6px;right:6px;background:#1f6febcc;color:#fff;font-size:9px;padding:2px 7px;border-radius:999px;opacity:0;transition:.15s}
+.frame:hover .live{opacity:1}
 .meta{padding:9px 11px;display:flex;justify-content:space-between;align-items:center;gap:8px}
 .dom{font-weight:600;font-size:12px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
 .dom a{color:#e6edf3;text-decoration:none}.dom a:hover{color:#58a6ff}
@@ -228,14 +231,13 @@ const grid=document.getElementById('grid'), sortEl=document.getElementById('sort
 const LS=(k,d)=>localStorage.getItem('wall_'+k)||d;
 sortEl.value=LS('sort','name'); typeEl.value=LS('type','all'); densEl.value=LS('dens','4');
 function applyDens(){document.documentElement.style.setProperty('--cols',densEl.value);
-  // scale iframe (1280 wide) to card width
   const cw=grid.querySelector('.frame'); if(cw){const s=cw.clientWidth/1280;
     document.querySelectorAll('.frame iframe').forEach(f=>f.style.transform='scale('+s+')');}}
-const io=new IntersectionObserver((es)=>{es.forEach(e=>{if(e.isIntersecting){const f=e.target;
-  if(!f.dataset.loaded){f.dataset.loaded=1;const if_=document.createElement('iframe');
-    if_.loading='lazy';if_.src='https://'+f.dataset.dom+'/';if_.sandbox='allow-scripts allow-same-origin';
-    const s=f.clientWidth/1280;if_.style.transform='scale('+s+')';
-    if_.onload=()=>{const ph=f.querySelector('.ph');if(ph)ph.style.display='none'};f.appendChild(if_);}io.unobserve(f);}})},{rootMargin:'300px'});
+// click a tile -> swap the thumbnail for the LIVE iframe
+function goLive(f){ if(f.dataset.live)return; f.dataset.live=1;
+  const if_=document.createElement('iframe'); if_.src='https://'+f.dataset.dom+'/';
+  if_.sandbox='allow-scripts allow-same-origin'; if_.style.transform='scale('+(f.clientWidth/1280)+')';
+  f.innerHTML=''; f.appendChild(if_); }
 function render(){
   const t=typeEl.value; let list=SITES.filter(s=>t==='all'||s.type===t);
   const sv=sortEl.value;
@@ -243,10 +245,13 @@ function render(){
   else if(sv==='type')list.sort((a,b)=>a.type.localeCompare(b.type)||a.domain.localeCompare(b.domain));
   else list.sort((a,b)=>(b.type===sv)-(a.type===sv)||a.domain.localeCompare(b.domain));
   countEl.textContent=list.length+' shown';
-  grid.innerHTML=list.map(s=>'<div class=card><div class=frame data-dom="'+s.domain+'"><div class=ph>'+s.domain+'</div></div>'+
+  grid.innerHTML=list.map(s=>'<div class=card><div class=frame data-dom="'+s.domain+'" title="click for live view">'+
+    '<img loading=lazy src="/thumbs/'+s.domain+'.jpg" onerror="this.style.display=\\'none\\';this.nextElementSibling.style.display=\\'flex\\'">'+
+    '<div class=ph style=display:none>'+s.domain+'<br><small>(no preview — click for live)</small></div>'+
+    '<span class=live>▶ live</span></div>'+
     '<div class=meta><span class=dom><a href="https://'+s.domain+'/" target=_blank rel=noopener>'+s.domain+'</a></span>'+
     '<span class="badge b-'+s.type+'">'+s.type+'</span></div></div>').join('');
-  grid.querySelectorAll('.frame').forEach(f=>io.observe(f)); applyDens();
+  grid.querySelectorAll('.frame').forEach(f=>f.addEventListener('click',()=>goLive(f))); applyDens();
 }
 sortEl.onchange=()=>{localStorage.setItem('wall_sort',sortEl.value);render()};
 typeEl.onchange=()=>{localStorage.setItem('wall_type',typeEl.value);render()};
@@ -260,6 +265,16 @@ const server = http.createServer((req, res) => {
   if (req.method === 'GET' && req.url.split('?')[0] === '/admin/wall') {
     return adminWall(req, res);
   }
+  if (req.method === 'GET' && req.url.startsWith('/thumbs/')) {
+    const name = path.basename(req.url.split('?')[0]);           // <domain>.jpg
+    if (!/^[a-z0-9.\-]+\.jpg$/i.test(name)) { res.writeHead(400).end(); return; }
+    const fp = path.join(__dirname, 'data/thumbs', name);
+    fs.readFile(fp, (e, buf) => {
+      if (e) { res.writeHead(404).end(); return; }
+      res.writeHead(200, { 'content-type': 'image/jpeg', 'cache-control': 'public, max-age=86400' }).end(buf);
+    });
+    return;
+  }
   if (req.method === 'GET' && req.url.split('?')[0] === '/admin/inquiries') {
     return adminInquiries(req, res);
   }

← ee0d788 admin: portfolio wall (/admin/wall) — iframe grid of all 290  ·  back to Domain Landings  ·  wall: gitignore thumbs dir (285 screenshots served from Kama 92cd673 →