[object Object]

← back to Domain Landings

wall: add per-chip open-in-new-window control (↗ open button + meta ↗ link)

e79003b9ff3db423d68f1447f5d5650f6fecc310 · 2026-08-17 17:22:27 -0700 · Steve

Files touched

Diff

commit e79003b9ff3db423d68f1447f5d5650f6fecc310
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 17 17:22:27 2026 -0700

    wall: add per-chip open-in-new-window control (↗ open button + meta ↗ link)
---
 server.js | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/server.js b/server.js
index ad240e0..0533709 100644
--- a/server.js
+++ b/server.js
@@ -214,6 +214,10 @@ input[type=range]{width:150px}
 .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}
+.openbtn{position:absolute;top:6px;left:6px;background:#238636dd;color:#fff;border:0;font-size:9px;font-family:inherit;padding:2px 8px;border-radius:999px;cursor:pointer;opacity:.9;transition:.15s;z-index:3}
+.openbtn:hover{background:#2ea043;opacity:1}
+.meta a.open{color:#8b949e;text-decoration:none;font-size:12px;padding:0 2px;flex:0 0 auto}
+.meta a.open:hover{color:#7ee787}
 .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}
@@ -266,11 +270,15 @@ function render(){
   grid.innerHTML=list.map(s=>'<div class=card><div class=frame data-dom="'+s.domain+'" data-fr="'+s.fr+'" title="'+(s.fr?'click for live view':'this site blocks embedding — click to open')+'">'+
     (s.th?'<img loading=lazy src="/thumbs/'+s.domain+'.jpg" onerror="this.style.display=\\'none\\';this.nextElementSibling.style.display=\\'flex\\'">':'')+
     '<div class=ph'+(s.th?' style=display:none':'')+'>'+s.domain+'<br><small>(no preview — click for live)</small></div>'+
-    '<span class=live>▶ live</span></div>'+
+    '<span class=live>▶ live</span>'+
+    '<button class=openbtn data-url="https://'+s.domain+'/" title="open '+s.domain+' in a new window">↗ open</button></div>'+
     '<div class=meta><span class=dom><a href="https://'+s.domain+'/" target=_blank rel=noopener>'+s.domain+'</a></span>'+
+    '<a class=open href="https://'+s.domain+'/" target=_blank rel=noopener title="open in a new window">↗</a>'+
     '<span class="badge b-'+s.type+'">'+s.type+'</span></div></div>').join('');
   grid.querySelectorAll('.frame').forEach(f=>{f.addEventListener('click',()=>goLive(f));
     if(LIVE.has(f.dataset.dom)){f.dataset.live='';goLive(f);}}); // restore live iframes after re-render
+  // explicit open-in-new-window per chip (bypasses the click-to-iframe hijack; the reliable escape hatch for sites that refuse embedding)
+  grid.querySelectorAll('.openbtn').forEach(b=>b.addEventListener('click',e=>{e.stopPropagation();window.open(b.dataset.url,'_blank','noopener');}));
   applyDens();
 }
 sortEl.onchange=()=>{localStorage.setItem('wall_sort',sortEl.value);render()};

← df81e89 chore: refactor (checkBasicAuth helper), v0.2.0 (session clo  ·  back to Domain Landings  ·  wall: add liveness-probe report (cert-mismatch = the 'dead s 11a4a85 →