[object Object]

← back to Commercialrealestate

CRCP firm-IG: render inline & LINK-FREE under agent-contact cards (honor no-outside-links); unify leads strip to same global renderer

50009ed8d481ad1575aa41f02e1ee13e6ae0c518 · 2026-07-17 10:02:16 -0700 · Steve Abrams

Files touched

Diff

commit 50009ed8d481ad1575aa41f02e1ee13e6ae0c518
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Fri Jul 17 10:02:16 2026 -0700

    CRCP firm-IG: render inline & LINK-FREE under agent-contact cards (honor no-outside-links); unify leads strip to same global renderer
---
 public/index.html | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/public/index.html b/public/index.html
index 2dc1ba1..1443da6 100644
--- a/public/index.html
+++ b/public/index.html
@@ -488,6 +488,7 @@
     .fighd{font-size:11px;color:var(--mut);letter-spacing:.02em;margin-bottom:1px;}
     .figli{display:flex;gap:9px;align-items:center;padding:5px 7px;border:1px solid var(--line);border-radius:8px;background:rgba(255,255,255,.02);text-decoration:none;color:inherit;}
     .figli:hover{border-color:var(--blue);}
+    .figli-static{cursor:default;} .figli-static:hover{border-color:var(--line);}
     .figt{width:38px;height:38px;flex:0 0 38px;border-radius:6px;object-fit:cover;background:var(--line);display:inline-block;}
     .figb{display:flex;flex-direction:column;min-width:0;gap:1px;}
     .figc{font-size:12px;line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:min(70vw,560px);}
@@ -609,7 +610,7 @@
           +'<span class="fit">fit '+l.fit_score+'</span><span class="meta">'+l.listings+' listings · '+moneyK(l.total_value)+'</span><span class="car">▸</span></div>'
           +'<div class="body"><div class="acts"><button class="bkbtn" data-act="contact">✉ Contact + pitch</button>'
           +'<button class="bkbtn" data-act="sold">🔴 Sold / area history</button></div>'
-          +firmIgStrip(l.firm)
+          +window.firmIgInline(l.firm)
           +'<table class="mls"><thead><tr><th>Address</th><th>Type</th><th>Price</th><th>Units</th><th>Cap</th><th></th></tr></thead><tbody>'+rows+'</tbody></table>'
           +'<div class="soldbox"></div></div></div>';
       }).join('');
@@ -992,6 +993,28 @@ function firmLabel(p){
 // without a reload. Sending a letter is NOT done here — letters are SAVED only.
 window.CONTACTS = window.CONTACTS || {};
 fetch('/api/contacts').then(r=>r.json()).then(j=>{ window.CONTACTS=j.contacts||{}; if(window.DATA_READY) render(); }).catch(()=>{});
+
+// ── Firm Instagram (global, link-free) ── each brokerage firm's last 3 IG posts,
+// rendered INLINE under the agent-contact card (no outside links — Business
+// Discovery, thumbnails cached same-origin). Loads once at boot, re-renders on ready.
+window.FIRM_IG = window.FIRM_IG || {}; window.FIRM_IG_IDX = window.FIRM_IG_IDX || {};
+Promise.all([
+  fetch('data/firm-ig-cache.json').then(r=>r.json()).catch(()=>({})),
+  fetch('data/firm-ig-map.json').then(r=>r.json()).catch(()=>({handles:{}}))
+]).then(([cache,map])=>{ window.FIRM_IG=cache||{}; const idx={};
+  for(const [h,al] of Object.entries((map.handles)||{})) for(const a of al) idx[a]=h; window.FIRM_IG_IDX=idx;
+  if(window.DATA_READY) render(); }).catch(()=>{});
+window.firmHandleOf=function(firm){ const n=String(firm||'').toLowerCase().replace(/[^a-z0-9]/g,''); if(!n) return null;
+  if(window.FIRM_IG_IDX[n]) return window.FIRM_IG_IDX[n];
+  for(const [a,h] of Object.entries(window.FIRM_IG_IDX)){ if(n===a||(a.length>=5&&n.includes(a))||(a.length>=3&&n.startsWith(a))) return h; } return null; };
+window.firmIgInline=function(firm){ const h=window.firmHandleOf(firm); if(!h) return '';
+  const c=window.FIRM_IG[h]; if(!c||!c.posts||!c.posts.length) return '';
+  const e=s=>String(s==null?'':s).replace(/[&<>"]/g,m=>({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[m]));
+  const items=c.posts.slice(0,3).map(p=>{ const cap=e((p.caption||'').replace(/\s+/g,' ').slice(0,90))||'(no caption)';
+    const when=p.timestamp?String(p.timestamp).slice(0,10):''; const vid=p.media_type==='VIDEO'?'▶ ':'';
+    const thumb=p.image?'<img class="figt" loading="lazy" src="'+e(p.image)+'" alt="">':'<span class="figt"></span>';
+    return '<div class="figli figli-static">'+thumb+'<span class="figb"><span class="figc">'+vid+cap+'</span><span class="figm">'+(when?when+' · ':'')+'♥ '+(p.likes||0)+' · 💬 '+(p.comments||0)+'</span></span></div>'; }).join('');
+  return '<div class="figwrap"><div class="fighd">📷 @'+e(h)+' · firm’s latest 3</div>'+items+'</div>'; };
 // Best-known contact info for a listing: the saved CRM record wins, else what the scrape gave us.
 function agentInfo(p){
   const c=window.CONTACTS[p.id]||{};
@@ -1118,6 +1141,7 @@ function cardAgentBlock(p){
   return `<div class="cardagent" id="cardagent-${id}">`
     +`<div class="ca-top">🧑‍💼 ${nm} ${chips}</div>`
     +contactBits(a)
+    +window.firmIgInline(a.firm)
     +`<div class="acts"><button class="abtn contactbtn" data-contact="${id}" title="Open the inline agent window — notes, letters, draft">✎ Open</button>`
     +`<button class="abtn markbtn" data-mark="${id}" title="${a.contacted_at?'Update contacted date':'Mark contacted (saves the date)'}">${a.contacted_at?'↻':'✓'} Contacted</button></div>`
     +`</div>`;

← 3db44ca chore: refactor (drop dead toggleExpand), lint pass, v0.10.0  ·  back to Commercialrealestate  ·  CRCP License Directory: agents/firms/title/escrow from cre l d438cb4 →