← back to Commercialrealestate
data/marketing-exec-leads/index.html
153 lines
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>CRE Marketing Execs — Lead Desk</title>
<style>
:root{--cols:3;--bg:#0f1115;--card:#171a21;--line:#252a34;--ink:#e7eaf0;--dim:#9aa3b2;--teal:#39c2b8;--gold:#c9a24a;--red:#e0605e;--grn:#4bbf73}
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif}
header{padding:14px 18px;border-bottom:1px solid var(--line);display:flex;flex-wrap:wrap;gap:12px;align-items:center;position:sticky;top:0;background:var(--bg);z-index:5}
h1{font-size:16px;margin:0;font-weight:600;letter-spacing:.02em}
.spacer{flex:1}
input,select{background:var(--card);color:var(--ink);border:1px solid var(--line);border-radius:8px;padding:7px 10px;font:inherit}
input[type=search]{min-width:220px}
label.ctl{display:flex;align-items:center;gap:6px;color:var(--dim);font-size:12px}
.chips{display:flex;flex-wrap:wrap;gap:6px;padding:10px 18px;border-bottom:1px solid var(--line)}
.chip{cursor:pointer;border:1px solid var(--line);border-radius:999px;padding:4px 11px;font-size:12px;color:var(--dim);user-select:none}
.chip.on{background:var(--teal);border-color:var(--teal);color:#04110f;font-weight:600}
.count{color:var(--dim);font-size:12px;padding:8px 18px}
.grid{display:grid;grid-template-columns:repeat(var(--cols),1fr);gap:12px;padding:0 18px 40px}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:13px 14px;cursor:pointer;transition:border-color .15s}
.card:hover{border-color:var(--teal)}
.card .nm{font-weight:600;font-size:15px}
.card .ti{color:var(--dim);font-size:12.5px;margin:2px 0 8px}
.card .fm{font-size:13px;color:var(--ink)}
.row{display:flex;gap:7px;align-items:center;margin-top:6px;font-size:12.5px}
.row a{color:var(--teal);text-decoration:none}.row a:hover{text-decoration:underline}
.k{color:var(--dim);min-width:52px}
.badge{font-size:10.5px;padding:2px 7px;border-radius:999px;border:1px solid var(--line);color:var(--dim)}
.b-conf{color:var(--grn);border-color:var(--grn)}.b-inf{color:var(--gold);border-color:var(--gold)}.b-none{color:var(--dim)}
.b-t1{color:var(--grn);border-color:var(--grn)}.b-t2{color:var(--teal);border-color:var(--teal)}.b-t3{color:var(--gold);border-color:var(--gold)}
.when{color:var(--dim);font-size:11px;margin-top:8px;display:flex;align-items:center;gap:5px}
#detail{position:fixed;inset:0;background:rgba(0,0,0,.55);display:none;align-items:center;justify-content:center;z-index:20}
#detail .panel{background:var(--card);border:1px solid var(--line);border-radius:14px;max-width:560px;width:92%;max-height:86vh;overflow:auto;padding:22px}
#detail h2{margin:0 0 2px}#detail .x{float:right;cursor:pointer;color:var(--dim);font-size:20px}
#detail table{width:100%;border-collapse:collapse;margin-top:12px}
#detail td{padding:7px 6px;border-bottom:1px solid var(--line);vertical-align:top}
#detail td.k{color:var(--dim);width:120px}
#detail a{color:var(--teal)}
</style></head>
<body>
<header>
<h1>CRE Marketing Execs · Lead Desk</h1>
<span class="badge" id="hdrcount"></span>
<div class="spacer"></div>
<input type="search" id="q" placeholder="Search all fields (space = AND)">
<label class="ctl">Sort
<select id="sort">
<option value="firm">Firm A→Z</option>
<option value="name">Contact A→Z</option>
<option value="tier">Tier</option>
<option value="email">Email status</option>
<option value="conf">Confidence</option>
</select>
</label>
<label class="ctl">Density <input type="range" id="dens" min="1" max="6" value="3"></label>
</header>
<div class="chips" id="chips"></div>
<div class="count" id="count"></div>
<div class="grid" id="grid"></div>
<div id="detail"><div class="panel"><span class="x" onclick="closeD()">✕</span><div id="dbody"></div></div></div>
<script>
const IMPORTED = "2026-08-07"; // list import date (admin card rule)
let ROWS=[], HEADERS=[], FILT={tier:new Set(),channel:new Set(),email:new Set()};
function parseCSV(t){
const rows=[];let i=0,f='',row=[],q=false;
while(i<t.length){const c=t[i];
if(q){if(c=='"'){if(t[i+1]=='"'){f+='"';i++}else q=false}else f+=c}
else{if(c=='"')q=true;else if(c==','){row.push(f);f=''}else if(c=='\n'||c=='\r'){if(c=='\r'&&t[i+1]=='\n')i++;if(f!==''||row.length){row.push(f);rows.push(row);row=[];f=''}}else f+=c}
i++}
if(f!==''||row.length){row.push(f);rows.push(row)}
return rows;
}
function tierClass(t){return t=='1'?'b-t1':t=='2'?'b-t2':'b-t3'}
function emClass(e){return /CONFIRMED/.test(e)?'b-conf':/INFERRED/.test(e)?'b-inf':'b-none'}
function esc(s){return (s||'').replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]))}
function render(){
const q=document.getElementById('q').value.toLowerCase().split(/\s+/).filter(Boolean);
let out=ROWS.filter(r=>{
if(FILT.tier.size && !FILT.tier.has(r.Tier)) return false;
if(FILT.email.size){const es=/CONFIRMED/.test(r['Email Status'])?'CONFIRMED':/INFERRED/.test(r['Email Status'])?'INFERRED':'NONE';if(!FILT.email.has(es))return false}
const hay=Object.values(r).join(' ').toLowerCase();
return q.every(t=>hay.includes(t));
});
const s=document.getElementById('sort').value;
out.sort((a,b)=>{
if(s=='firm')return (a.Firm||'').localeCompare(b.Firm||'');
if(s=='name')return (a['Contact Name']||'').localeCompare(b['Contact Name']||'');
if(s=='tier')return (a.Tier||'9').localeCompare(b.Tier||'9');
if(s=='email')return (a['Email Status']||'z').localeCompare(b['Email Status']||'z');
if(s=='conf'){const o={high:0,medium:1,low:2};return (o[a.Confidence]??3)-(o[b.Confidence]??3)}
return 0;
});
const g=document.getElementById('grid');g.innerHTML='';
out.forEach((r,idx)=>{
const named=r['Contact Name']&&!r['Contact Name'].startsWith('(');
const tier=r.Tier||'—';
const div=document.createElement('div');div.className='card';div.onclick=()=>openD(r);
div.innerHTML=`
<div class="nm">${esc(r['Contact Name'])||'<span style="color:var(--dim)">— unresolved —</span>'}</div>
<div class="ti">${esc(r.Title)||''}</div>
<div class="fm">${esc(r.Firm)}</div>
${r.Phone?`<div class="row"><span class="k">Call</span><a href="tel:${esc(r.Phone.replace(/[^0-9x]/g,''))}">${esc(r.Phone)}</a>${r.Mobile?` · <a href="tel:${esc(r.Mobile.replace(/[^0-9]/g,''))}">m ${esc(r.Mobile)}</a>`:''}</div>`:''}
${r.Email?`<div class="row"><span class="k">Email</span><a href="mailto:${esc(r.Email)}">${esc(r.Email)}</a></div>`:''}
<div class="row" style="margin-top:9px">
${tier!=='—'?`<span class="badge ${tierClass(tier)}">Tier ${tier}</span>`:''}
${r['Email Status']?`<span class="badge ${emClass(r['Email Status'])}">${/CONFIRMED/.test(r['Email Status'])?'Email ✓':/INFERRED/.test(r['Email Status'])?'Email ~inferred':''}</span>`:''}
${r.Confidence?`<span class="badge">${esc(r.Confidence)}</span>`:''}
</div>
<div class="when" title="List imported ${IMPORTED}">🕓 imported ${IMPORTED}</div>`;
g.appendChild(div);
});
document.getElementById('count').textContent=`${out.length} of ${ROWS.length} contacts`;
document.getElementById('hdrcount').textContent=`${ROWS.filter(r=>r['Contact Name']&&!r['Contact Name'].startsWith('(')).length} named leads`;
}
function openD(r){
const b=document.getElementById('dbody');
b.innerHTML=`<h2>${esc(r['Contact Name'])}</h2><div style="color:var(--dim)">${esc(r.Title)} · ${esc(r.Firm)}</div><table>${
HEADERS.map(h=>r[h]?`<tr><td class="k">${esc(h)}</td><td>${
h=='Phone'||h=='Mobile'?`<a href="tel:${esc(r[h].replace(/[^0-9x]/g,''))}">${esc(r[h])}</a>`:
h=='Email'?`<a href="mailto:${esc(r[h])}">${esc(r[h])}</a>`:
h=='LinkedIn'?`<a href="${esc(r[h])}" target="_blank" rel="noopener noreferrer">${esc(r[h])}</a>`:
h=='Firm Website'?`<a href="https://${esc(r[h])}" target="_blank" rel="noopener noreferrer">${esc(r[h])}</a>`:
esc(r[h])}</td></tr>`:'').join('')}</table>`;
document.getElementById('detail').style.display='flex';
}
function closeD(){document.getElementById('detail').style.display='none'}
document.getElementById('detail').onclick=e=>{if(e.target.id=='detail')closeD()};
function buildChips(){
const c=document.getElementById('chips');
const add=(label,group,val)=>{const s=document.createElement('span');s.className='chip';s.textContent=label;
s.onclick=()=>{FILT[group].has(val)?FILT[group].delete(val):FILT[group].add(val);s.classList.toggle('on');render()};c.appendChild(s)};
c.insertAdjacentHTML('beforeend','<span class="k" style="align-self:center">Tier:</span>');
['1','2','3'].forEach(t=>add('Tier '+t,'tier',t));
c.insertAdjacentHTML('beforeend','<span class="k" style="align-self:center;margin-left:10px">Email:</span>');
add('Confirmed','email','CONFIRMED');add('Inferred','email','INFERRED');add('None','email','NONE');
}
document.getElementById('dens').oninput=e=>document.documentElement.style.setProperty('--cols',e.target.value);
['q','sort'].forEach(id=>document.getElementById(id).addEventListener('input',render));
fetch('cre-marketing-execs-MASTER.csv').then(r=>r.text()).then(t=>{
const rows=parseCSV(t);HEADERS=rows[0];
ROWS=rows.slice(1).filter(r=>r.length>1).map(r=>Object.fromEntries(HEADERS.map((h,i)=>[h,r[i]||''])));
buildChips();render();
}).catch(e=>{document.getElementById('grid').innerHTML='<p style="color:var(--red);padding:18px">Could not load CSV: '+e+'</p>'});
</script>
</body></html>