[object Object]

← back to Commercialrealestate

CRCP root: center the top chat bar (constrained width, centered chips)

884b8b77a58592d5e62138ab3af5f4339d5374ac · 2026-08-18 16:29:50 -0700 · Steve Abrams

Files touched

Diff

commit 884b8b77a58592d5e62138ab3af5f4339d5374ac
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue Aug 18 16:29:50 2026 -0700

    CRCP root: center the top chat bar (constrained width, centered chips)
---
 public/index.html | 35 +++++++++++++++++++++++++----------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/public/index.html b/public/index.html
index 3fb2b6b..f1c2785 100644
--- a/public/index.html
+++ b/public/index.html
@@ -416,16 +416,16 @@
 
 <!-- Real natural-language chat bar at the TOP — "4 units in los angeles" → drives filters + re-renders (client-side, no server) -->
 <div id="chatbar" style="padding:9px 18px 10px;border-bottom:1px solid var(--line);background:var(--bg2,var(--card));position:sticky;top:57px;z-index:20;">
-  <div style="display:flex;align-items:center;gap:9px;">
+  <div style="display:flex;align-items:center;gap:9px;max-width:860px;margin:0 auto;width:100%;">
     <span style="font-size:16px" title="Ask for properties in plain English">💬</span>
     <input id="chatq" type="text" autocomplete="off" spellcheck="false"
       placeholder="Ask for what you want — e.g.  “4 units in Los Angeles”  ·  “multifamily under $2M”  ·  “retail 6% cap”  ·  “all cash”  (Enter)"
       style="flex:1;min-width:0;background:var(--card);border:1px solid var(--line);color:var(--ink);border-radius:9px;padding:9px 13px;font-size:14px;">
     <button id="chatgo" class="ex" title="Apply this request">Ask ▸</button>
     <button id="chatclear" class="ex" title="Clear the chat filters">Clear</button>
-    <span id="chatmsg" style="font-size:12px;color:var(--mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:26%"></span>
+    <span id="chatmsg" style="font-size:12px;color:var(--mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:22%"></span>
   </div>
-  <div id="chatex" style="display:flex;flex-wrap:wrap;gap:6px;margin-top:7px;padding-left:26px;font-size:11px;color:var(--mut);align-items:center;">
+  <div id="chatex" style="display:flex;flex-wrap:wrap;gap:6px;margin:7px auto 0;max-width:860px;justify-content:center;font-size:11px;color:var(--mut);align-items:center;">
     <span style="opacity:.7">Try:</span>
     <button class="chatchip" data-q="4 units in Los Angeles">4 units in Los Angeles</button>
     <button class="chatchip" data-q="unwarrantable condos">unwarrantable condos</button>
@@ -484,6 +484,9 @@
       <span class="chip" data-st="active">Active only</span>
     </div>
     <label class="toggle" style="margin-top:10px"><input type="checkbox" id="fAfford"> In budget ($400k down) only</label>
+    <!-- Broker/firm contactability filters (Steve 2026-08-18): narrow to listings whose resolved broker/firm contact has a phone / email — so a reachable contact exists before working the lead. -->
+    <label class="toggle" style="margin-top:10px"><input type="checkbox" id="fHasPhone"> Has broker phone</label>
+    <label class="toggle" style="margin-top:6px"><input type="checkbox" id="fHasEmail"> Has broker email</label>
   </div>
   <div class="fsec"><h4>Sort &amp; density</h4>
     <select id="sort">
@@ -1034,7 +1037,7 @@ function loopnetLink(p){
 }
 // Rich filter state
 const F = { types:new Set(), firms:new Set(), cityQuery:'', priceMin:null, priceMax:null,
-            capMin:0, unitsMin:null, unitsMax:null, yearMin:null, yearMax:null, status:'all', afford:false, unwarrantable:false, nonqm:false, shortlist:false };
+            capMin:0, unitsMin:null, unitsMax:null, yearMin:null, yearMax:null, status:'all', afford:false, unwarrantable:false, nonqm:false, shortlist:false, hasPhone:false, hasEmail:false };
 
 // ── SEARCH ALL DATA (Steve list-build rule 7) ──────────────────────────────────
 // Free-text search across EVERY field on a listing — not just the visible/defined columns,
@@ -1331,6 +1334,7 @@ function applyFilters(list){
     if(F.afford && !p.finance.affordable) return false;
     if(F.unwarrantable && !(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved')) return false;
     if(F.nonqm && !isNonQM(p)) return false;
+    if(F.hasPhone||F.hasEmail){ const ci=agentInfo(p); if(F.hasPhone && !ci.phone) return false; if(F.hasEmail && !ci.email) return false; }
     if(F.shortlist && !SHORT.has(p.id)) return false;
     return true;
   });
@@ -1352,6 +1356,7 @@ function passExcept(p, skip){
   if(F.afford && !p.finance.affordable) return false;
   if(F.unwarrantable && !(p.isCondo && p.warrantable_status && p.warrantable_status!=='fha_approved')) return false;
   if(F.nonqm && !isNonQM(p)) return false;
+  if(F.hasPhone||F.hasEmail){ const ci=agentInfo(p); if(F.hasPhone && !ci.phone) return false; if(F.hasEmail && !ci.email) return false; }
   if(F.shortlist && !SHORT.has(p.id)) return false;
   return true;
 }
@@ -1370,7 +1375,7 @@ function activeFilterCount(){
   if(F.types.size) n++; if(F.firms.size) n++; if(F.cityQuery) n++;
   if(F.priceMin!=null||F.priceMax!=null) n++; if(F.capMin>0) n++;
   if(F.unitsMin!=null) n++; if(F.unitsMax!=null) n++; if(F.yearMin!=null) n++; if(F.yearMax!=null) n++;
-  if(F.status!=='all') n++; if(F.afford) n++; if(F.unwarrantable) n++; if(F.nonqm) n++; if(F.shortlist) n++;
+  if(F.status!=='all') n++; if(F.afford) n++; if(F.unwarrantable) n++; if(F.nonqm) n++; if(F.shortlist) n++; if(F.hasPhone) n++; if(F.hasEmail) n++;
   return n;
 }
 
@@ -1400,6 +1405,8 @@ function renderActiveBar(){
   if(F.afford) chips.push(['afford', 'in budget']);
   if(F.unwarrantable) chips.push(['unwarrantable', '⚑ unwarrantable']);
   if(F.nonqm) chips.push(['nonqm', '◆ non-QM']);
+  if(F.hasPhone) chips.push(['hasPhone', '☎ has phone']);
+  if(F.hasEmail) chips.push(['hasEmail', '✉ has email']);
   if(F.shortlist) chips.push(['shortlist', '⭐ shortlist']);
   bar.innerHTML = chips.length
     ? '<span class="lbl">Filtering</span>'+chips.map(([k,l])=>`<span class="afchip" data-clear="${k.replace(/"/g,'&quot;')}">${esc(l)} <span class="xx">✕</span></span>`).join('')+'<button class="afreset" id="afreset">Reset all ✕</button>'
@@ -1420,6 +1427,8 @@ function clearFilter(spec){
   else if(spec==='afford'){ F.afford=false; if($('#fAfford')) $('#fAfford').checked=false; }
   else if(spec==='unwarrantable'){ F.unwarrantable=false; }
   else if(spec==='nonqm'){ F.nonqm=false; }
+  else if(spec==='hasPhone'){ F.hasPhone=false; if($('#fHasPhone')) $('#fHasPhone').checked=false; }
+  else if(spec==='hasEmail'){ F.hasEmail=false; if($('#fHasEmail')) $('#fHasEmail').checked=false; }
   else if(spec==='shortlist'){ F.shortlist=false; }
   syncChips(); render();
 }
@@ -1440,6 +1449,8 @@ function stateToURL(){
   if(F.afford) p.set('aff','1');
   if(F.unwarrantable) p.set('unw','1');
   if(F.nonqm) p.set('nq','1');
+  if(F.hasPhone) p.set('hp','1');
+  if(F.hasEmail) p.set('he','1');
   if(scenario!=='25') p.set('sc',scenario);
   const so=$('#sort')&&$('#sort').value; if(so && so!=='deal') p.set('sort',so);
   if(colSort) p.set('cs',colSort.key+':'+(colSort.dir>0?'a':'d'));
@@ -1465,6 +1476,8 @@ function urlToState(){
   if(p.get('aff')==='1'){ F.afford=true; if($('#fAfford'))$('#fAfford').checked=true; any=true; }
   if(p.get('unw')==='1'){ F.unwarrantable=true; any=true; }
   if(p.get('nq')==='1'){ F.nonqm=true; any=true; }
+  if(p.get('hp')==='1'){ F.hasPhone=true; if($('#fHasPhone'))$('#fHasPhone').checked=true; any=true; }
+  if(p.get('he')==='1'){ F.hasEmail=true; if($('#fHasEmail'))$('#fHasEmail').checked=true; any=true; }
   if(p.get('sc')){ scenario=p.get('sc'); }
   if(p.get('sort')&&$('#sort')){ $('#sort').value=p.get('sort'); if($('#sortTop'))$('#sortTop').value=p.get('sort'); }
   if(p.get('v')){ view=p.get('v'); }
@@ -1751,17 +1764,17 @@ function applyChat(raw){
 // ---- persistence ----
 function saveState(){ try{ localStorage.setItem('cre_filters', JSON.stringify({
   types:[...F.types],firms:[...F.firms],cityQuery:F.cityQuery,priceMin:F.priceMin,priceMax:F.priceMax,
-  capMin:F.capMin,unitsMin:F.unitsMin,unitsMax:F.unitsMax,yearMin:F.yearMin,yearMax:F.yearMax,status:F.status,afford:F.afford,sort:$('#sort').value })); }catch(e){} }
+  capMin:F.capMin,unitsMin:F.unitsMin,unitsMax:F.unitsMax,yearMin:F.yearMin,yearMax:F.yearMax,status:F.status,afford:F.afford,hasPhone:F.hasPhone,hasEmail:F.hasEmail,sort:$('#sort').value })); }catch(e){} }
 function loadState(){ try{ const s=JSON.parse(localStorage.getItem('cre_filters')||'{}');
   if(s.types) F.types=new Set(s.types); if(s.firms) F.firms=new Set(s.firms);
   F.cityQuery=s.cityQuery||''; F.priceMin=s.priceMin??null; F.priceMax=s.priceMax??null;
-  F.capMin=s.capMin||0; F.unitsMin=s.unitsMin??null; F.unitsMax=s.unitsMax??null; F.yearMin=s.yearMin??null; F.yearMax=s.yearMax??null; F.status=s.status||'all'; F.afford=!!s.afford;
+  F.capMin=s.capMin||0; F.unitsMin=s.unitsMin??null; F.unitsMax=s.unitsMax??null; F.yearMin=s.yearMin??null; F.yearMax=s.yearMax??null; F.status=s.status||'all'; F.afford=!!s.afford; F.hasPhone=!!s.hasPhone; F.hasEmail=!!s.hasEmail;
   if(s.cityQuery) $('#fCity').value=s.cityQuery; if(s.priceMin) $('#fPriceMin').value=s.priceMin; if(s.priceMax) $('#fPriceMax').value=s.priceMax;
   if(s.capMin){ $('#fCap').value=s.capMin; $('#capVal').textContent=s.capMin+'%+'; } if(s.unitsMin) $('#fUnits').value=s.unitsMin; if(s.unitsMax) $('#fUnitsMax').value=s.unitsMax; if(s.yearMin) $('#fYear').value=s.yearMin; if(s.yearMax) $('#fYearMax').value=s.yearMax;
-  $('#fAfford').checked=F.afford; if(s.sort){ $('#sort').value=s.sort; }
+  $('#fAfford').checked=F.afford; if($('#fHasPhone'))$('#fHasPhone').checked=F.hasPhone; if($('#fHasEmail'))$('#fHasEmail').checked=F.hasEmail; if(s.sort){ $('#sort').value=s.sort; }
 }catch(e){} }
-function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.unitsMax=F.yearMin=F.yearMax=null; F.capMin=0; F.status='all'; F.afford=false; F.unwarrantable=false; F.nonqm=false;
-  ['fCity','fPriceMin','fPriceMax','fUnits','fUnitsMax','fYear','fYearMax'].forEach(id=>$('#'+id).value=''); $('#fCap').value=0; $('#capVal').textContent='any'; $('#fAfford').checked=false;
+function resetAll(){ F.types.clear(); F.firms.clear(); F.cityQuery=''; F.priceMin=F.priceMax=F.unitsMin=F.unitsMax=F.yearMin=F.yearMax=null; F.capMin=0; F.status='all'; F.afford=false; F.unwarrantable=false; F.nonqm=false; F.hasPhone=false; F.hasEmail=false;
+  ['fCity','fPriceMin','fPriceMax','fUnits','fUnitsMax','fYear','fYearMax'].forEach(id=>$('#'+id).value=''); $('#fCap').value=0; $('#capVal').textContent='any'; $('#fAfford').checked=false; if($('#fHasPhone'))$('#fHasPhone').checked=false; if($('#fHasEmail'))$('#fHasEmail').checked=false;
   syncChips(); render(); }
 
 // ---- chat ----
@@ -2067,6 +2080,8 @@ function loadRanked(_retry){ return fetch('data/ranked.json').then(r=>{ if(!r.ok
   $('#fYear').oninput=()=>{ F.yearMin=$('#fYear').value?+$('#fYear').value:null; render(); };
   $('#fYearMax').oninput=()=>{ F.yearMax=$('#fYearMax').value?+$('#fYearMax').value:null; render(); };
   $('#fAfford').onchange=()=>{ F.afford=$('#fAfford').checked; render(); };
+  $('#fHasPhone').onchange=()=>{ F.hasPhone=$('#fHasPhone').checked; render(); };
+  $('#fHasEmail').onchange=()=>{ F.hasEmail=$('#fHasEmail').checked; render(); };
   $('#scenario').onclick=e=>{ if(!e.target.dataset.s) return; setScenario(e.target.dataset.s); render(); };
 
   // sort (drawer + top bar mirror each other)

← 376087d CRCP root: add working client-side NL chat bar at top + remo  ·  back to Commercialrealestate  ·  CRCP: real phone reveal — resolve broker/firm phone+email+we 8215dbe →