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