← back to Rentv
desk: open contact links in an inline right-dock iframe panel
43d1c96c49dfc1f3cb4227756d874c468f63c7d0 · 2026-08-12 16:15:49 -0700 · Steve
Website/DRE/Assessor/Listing render inline; Map auto-upgraded to a keyless
Google Maps embed; Google/LinkedIn/Firm/social hard-fall-back to open-in-tab.
Call/Email stay native; cmd/ctrl-click still opens a real new tab.
Files touched
Diff
commit 43d1c96c49dfc1f3cb4227756d874c468f63c7d0
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 12 16:15:49 2026 -0700
desk: open contact links in an inline right-dock iframe panel
Website/DRE/Assessor/Listing render inline; Map auto-upgraded to a keyless
Google Maps embed; Google/LinkedIn/Firm/social hard-fall-back to open-in-tab.
Call/Email stay native; cmd/ctrl-click still opens a real new tab.
---
public/desk.html | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 68 insertions(+), 1 deletion(-)
diff --git a/public/desk.html b/public/desk.html
index 3cbf3c6c..4eccaa11 100644
--- a/public/desk.html
+++ b/public/desk.html
@@ -57,6 +57,22 @@
.cmd{max-width:760px;margin:0 auto;padding:40px 20px}.cmd .box{background:#11161c;border:1px solid #2a333d;border-radius:14px;overflow:hidden;box-shadow:0 30px 80px rgba(0,0,0,.5)}.cmd .in{display:flex;align-items:center;gap:12px;padding:16px 18px;border-bottom:1px solid var(--line)}.cmd input{flex:1;background:none;border:0;color:#e7ecf1;font:600 18px 'Inter';outline:none}.cmd .results{max-height:56vh;overflow-y:auto}.res{padding:12px 16px;border-bottom:1px solid #171d24;cursor:pointer}.res:hover,.res.hot{background:#141b22}.res .nm{font-weight:700}.res .f{font-size:12px;color:#9aa4ad;margin-top:2px}.res .exp{display:none;margin-top:10px}.res.open .exp{display:block}
.fresh{color:#6b747d;font-size:12px;padding:10px 14px}
@media(max-width:900px){.shell{grid-template-columns:1fr;height:auto}.md,.mapv{grid-template-columns:1fr}.kpis{grid-template-columns:1fr 1fr}}
+ /* ── inline preview panel (right dock) ── opens web action links in an iframe instead of a new tab */
+ .pv-scrim{position:fixed;inset:0;background:rgba(6,9,12,.5);opacity:0;pointer-events:none;transition:.2s;z-index:970}.pv-scrim.open{opacity:1;pointer-events:auto}
+ .pv{position:fixed;top:0;right:0;bottom:0;width:46vw;min-width:380px;max-width:820px;background:#0b0e12;border-left:1px solid var(--line);transform:translateX(102%);transition:transform .26s cubic-bezier(.4,0,.2,1);z-index:971;display:flex;flex-direction:column;box-shadow:-2px 0 44px rgba(0,0,0,.5)}
+ .pv.open{transform:none}
+ .pv .pvh{display:flex;align-items:center;gap:9px;padding:10px 12px;border-bottom:1px solid var(--line);background:#0d1014}
+ .pv .pvh .t{font-weight:800;font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;min-width:60px}
+ .pv .pvh .u{font-size:11px;color:#6b747d;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:34%}
+ .pv .pvh a,.pv .pvh button{background:#12161c;border:1px solid #2a333d;border-radius:8px;color:#dbe1e7;font:700 12px 'Inter';padding:6px 10px;cursor:pointer;white-space:nowrap;text-decoration:none;line-height:1}.pv .pvh a:hover,.pv .pvh button:hover{border-color:var(--red);color:#fff}
+ .pv .pvbody{position:relative;flex:1;background:#fff;overflow:hidden}
+ .pv iframe{position:absolute;inset:0;width:100%;height:100%;border:0;background:#fff}
+ .pv .pvstate{position:absolute;inset:0;display:none;flex-direction:column;align-items:center;justify-content:center;gap:14px;text-align:center;padding:30px;background:#0d1014;color:#c3cbd3;z-index:2}
+ .pv .pvbody.loading .pvload,.pv .pvbody.blocked .pvblock{display:flex}
+ .pv .pvstate .big{font-size:16px;font-weight:800}.pv .pvstate .sm{font-size:12.5px;color:#8b939b;max-width:360px;line-height:1.55}
+ .pv .pvstate a.open{background:var(--red);border:0;border-radius:9px;color:#fff;font:800 13px 'Inter';padding:11px 18px;text-decoration:none}.pv .pvstate a.open:hover{filter:brightness(1.08)}
+ .pv .spin{width:26px;height:26px;border:3px solid #232a32;border-top-color:var(--red);border-radius:50%;animation:pvspin .8s linear infinite}@keyframes pvspin{to{transform:rotate(360deg)}}
+ @media(max-width:900px){.pv{width:100vw;max-width:none;min-width:0}.pv .pvh .u{display:none}}
</style></head>
<body>
<header class="top">
@@ -88,6 +104,25 @@
<div class="view" id="v-analytics"></div>
</div>
</div>
+<!-- inline preview: right-dock iframe panel -->
+<div class="pv-scrim" id="pvScrim"></div>
+<aside class="pv" id="pv" aria-hidden="true">
+ <div class="pvh">
+ <span class="t" id="pvTitle">Preview</span>
+ <span class="u" id="pvUrl"></span>
+ <a id="pvOpen" href="#" target="_blank" rel="noopener noreferrer">Open ↗</a>
+ <button id="pvClose" aria-label="Close preview">✕</button>
+ </div>
+ <div class="pvbody" id="pvBody">
+ <div class="pvstate pvload"><div class="spin"></div><div class="sm">Loading inline preview…</div></div>
+ <div class="pvstate pvblock">
+ <div class="big">🔒 This site blocks inline preview</div>
+ <div class="sm">Some sites (LinkedIn, Google, Facebook/X) refuse to load inside an embedded frame for security. Open it in a new tab instead — it'll still keep the desk here behind it.</div>
+ <a class="open" id="pvBlockOpen" href="#" target="_blank" rel="noopener noreferrer">Open in new tab ↗</a>
+ </div>
+ <iframe id="pvFrame" referrerpolicy="strict-origin-when-cross-origin" allow="fullscreen"></iframe>
+ </div>
+</aside>
<script>
const $=s=>document.getElementById(s), enc=encodeURIComponent;
const esc=s=>String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));
@@ -95,7 +130,7 @@ const shortUsd=n=>{n=Number(n)||0;return n>=1e9?'$'+(n/1e9).toFixed(2)+'B':n>=1e
const dstr=d=>d?new Date(d).toLocaleDateString(undefined,{month:'short',day:'numeric',year:'numeric'}):'—';
const loc=x=>[x.city||x.hq_city,x.state_code||x.hq_state].filter(Boolean).join(', ');
const LI_P=(n,f)=>`https://www.linkedin.com/search/results/people/?keywords=${enc([n,f].filter(Boolean).join(' '))}`,LI_C=f=>`https://www.linkedin.com/search/results/companies/?keywords=${enc(f)}`,WEB=f=>`https://www.google.com/search?q=${enc('"'+f+'" real estate official website')}`,MAPS=q=>`https://www.google.com/maps/search/?api=1&query=${enc(q)}`,DRE=l=>`https://www2.dre.ca.gov/PublicASP/pplinfo.asp?License_id=${enc(l)}`,GOOG=q=>`https://www.google.com/search?q=${enc(q)}`,ASSR=a=>`https://portal.assessor.lacounty.gov/parceldetail/${enc(a)}`;
-const A=(h,i,l)=>`<a class="act" href="${h}" target="_blank" rel="noopener noreferrer" onclick="event.stopPropagation()"><span class="i">${i}</span>${l}</a>`;
+const A=(h,i,l)=>`<a class="act" href="${h}" target="_blank" rel="noopener noreferrer"${/^https?:\/\//i.test(h)?' data-pv="1"':''} onclick="event.stopPropagation()"><span class="i">${i}</span>${l}</a>`;
function actions(k,x){
if(k==='firms')return [A(LI_C(x.name),'🏢','LinkedIn'),A(WEB(x.name),'🌐','Website'),A(MAPS(x.name+' '+loc(x)),'📍','HQ / Map'),x.license_no?A(DRE(x.license_no),'🪪','DRE'):'',A(GOOG(x.name+' brokers '+loc(x)),'🔎','Google')].filter(Boolean).join('');
if(k==='owners'){const ad=[x.address,x.city].filter(Boolean).join(', ');return [A(MAPS(ad),'📍','Map'),A(GOOG(ad+' '+(x.use_desc||'')+' owner'),'🔎','Owner / deal'),A('https://www.google.com/maps/@?api=1&map_action=pano&viewpoint='+enc(ad),'📸','Street View'),A(ASSR(x.ain),'🏛️','Assessor')].join('');}
@@ -136,6 +171,38 @@ let D={scope:'commercial',view:'directory',selDir:0,selMap:0};
$('burger').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
const cd=()=>{$('scrim').classList.remove('open');$('drawer').classList.remove('open');};$('dclose').onclick=cd;$('scrim').onclick=cd;
$('user').onclick=()=>{$('scrim').classList.add('open');$('drawer').classList.add('open');};
+// ── inline preview panel ── route web action links into a right-dock iframe instead of a new tab
+const PV={t:null};
+function pvEmbed(u){ // upgrade a plain Google Maps search into a keyless embeddable frame so Map/HQ render inline
+ try{const m=u.match(/[?&]query=([^&]+)/);if(/google\.[^/]+\/maps\/search/i.test(u)&&m)return 'https://maps.google.com/maps?q='+m[1]+'&z=14&output=embed';}catch(e){}
+ return u;
+}
+// domains that ALWAYS refuse framing (X-Frame-Options/CSP) — the 🔎 Google, 👤 LinkedIn, 🏢 Firm buttons.
+// Skip the iframe attempt entirely and show the "open in tab" fallback immediately (no blank frame, no wait).
+const PV_HARD=[/google\.[a-z.]+\/search/i,/linkedin\.com/i,/facebook\.com/i,/(^|\/\/)(www\.)?(x|twitter)\.com/i,/instagram\.com/i];
+function pvHardBlocked(u){return PV_HARD.some(re=>re.test(u));}
+function pvClose(){$('pv').classList.remove('open');$('pvScrim').classList.remove('open');$('pv').setAttribute('aria-hidden','true');$('pvFrame').src='about:blank';clearTimeout(PV.t);$('pvBody').classList.remove('loading','blocked');}
+function pvOpen(href,label){
+ const body=$('pvBody'),fr=$('pvFrame');
+ $('pvTitle').textContent=label||'Preview';
+ try{$('pvUrl').textContent=new URL(href).hostname.replace(/^www\./,'');}catch(e){$('pvUrl').textContent='';}
+ $('pvOpen').href=href;$('pvBlockOpen').href=href;
+ clearTimeout(PV.t);fr.onload=null;body.classList.remove('loading','blocked'); // drop any stale onload from a prior open
+ $('pv').classList.add('open');$('pvScrim').classList.add('open');$('pv').setAttribute('aria-hidden','false');
+ if(pvHardBlocked(href)){fr.src='about:blank';body.classList.add('blocked');return;} // known frame-hostile → straight to fallback
+ body.classList.add('loading');
+ fr.onload=()=>{clearTimeout(PV.t);body.classList.remove('loading','blocked');}; // real load (even a slow one) wins over the timeout
+ fr.src=pvEmbed(href);
+ // some sites silently fail to ever fire load when blocked → time out to the "open in tab" fallback
+ PV.t=setTimeout(()=>{if(body.classList.contains('loading')){body.classList.remove('loading');body.classList.add('blocked');}},4200);
+}
+$('pvClose').onclick=pvClose;$('pvScrim').onclick=pvClose;
+document.addEventListener('keydown',e=>{if(e.key==='Escape'&&$('pv').classList.contains('open'))pvClose();});
+document.addEventListener('click',e=>{ // plain left-click → inline panel; cmd/ctrl/middle-click still opens a native new tab
+ const a=e.target.closest('a.act[data-pv]');if(!a)return;
+ if(e.metaKey||e.ctrlKey||e.shiftKey||e.altKey||e.button!==0)return;
+ e.preventDefault();pvOpen(a.getAttribute('href'),(a.textContent||'').trim());
+},true);
async function jget(u,fb){try{const r=await fetch(u,{credentials:'same-origin'});if(!r.ok)return fb;return await r.json();}catch(e){return fb;}}
(async()=>{
const [b,f,la,sl,cb,cs]=await Promise.all([
← 91b6dc90 auto-data-snapshot: 2026-08-12T16:02:38 (669 data files) — d
·
back to Rentv
·
CRM: import LABJ2026 Book-of-Lists into rentv directory (1,4 efb96961 →