[object Object]

← back to Commercialrealestate

Deal Intelligence dashboard: 2026 liquid-glass UI (animated gradient mesh + frosted panels) with 6 dynamic Chart.js charts bound to live deal data; nav links across pages

d5d29c913ea310630613d166fab97ac13f95c7c0 · 2026-06-28 20:39:36 -0700 · Steve

Files touched

Diff

commit d5d29c913ea310630613d166fab97ac13f95c7c0
Author: Steve <steve@designerwallcoverings.com>
Date:   Sun Jun 28 20:39:36 2026 -0700

    Deal Intelligence dashboard: 2026 liquid-glass UI (animated gradient mesh + frosted panels) with 6 dynamic Chart.js charts bound to live deal data; nav links across pages
---
 public/crcp.html  |   2 +-
 public/deals.html | 197 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 public/index.html |   1 +
 public/map.html   |   2 +-
 4 files changed, 200 insertions(+), 2 deletions(-)

diff --git a/public/crcp.html b/public/crcp.html
index a13dfb2..edd3f16 100644
--- a/public/crcp.html
+++ b/public/crcp.html
@@ -75,7 +75,7 @@
   <h1><b>CRCP</b> · Commercial Real Estate Control Panel</h1>
   <span class="live"><span class="pulse"></span><span id="livetxt">live</span></span>
   <nav class="nav">
-    <a href="/">Properties</a><a href="/map.html">Map</a><a href="/brokers.html">Mind-map</a><a href="/graphics.html">Graphics</a><a href="/condos.html">Condos</a>
+    <a href="/">Properties</a><a href="/map.html">Map</a><a href="/deals.html">Deals</a><a href="/brokers.html">Mind-map</a><a href="/graphics.html">Graphics</a><a href="/condos.html">Condos</a>
     <span class="denslider" title="chip / tile density">⊞ <input type="range" id="dens" min="120" max="300" value="150"></span>
     <button class="expandall" id="expandall">Expand all ▾</button>
   </nav>
diff --git a/public/deals.html b/public/deals.html
new file mode 100644
index 0000000..8b517cc
--- /dev/null
+++ b/public/deals.html
@@ -0,0 +1,197 @@
+<!doctype html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='11' text-anchor='middle' fill='%23c8a24b'%3E%24%3C/text%3E%3C/svg%3E">
+<title>Deal Intelligence — LA County CRE</title>
+<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
+<style>
+  :root{
+    --gold:#e9c46a; --g2:#c8a24b; --violet:#8b7cf6; --teal:#2dd4bf; --blue:#60a5fa; --pink:#f472b6; --orange:#fb923c;
+    --ink:#eef1f7; --muted:#9aa3b4; --glass:rgba(255,255,255,.055); --glassb:rgba(255,255,255,.12);
+  }
+  *{box-sizing:border-box;margin:0;padding:0;}
+  html,body{min-height:100%;background:#070a10;color:var(--ink);font:15px/1.5 -apple-system,'SF Pro Display',Segoe UI,Roboto,sans-serif;-webkit-font-smoothing:antialiased;}
+  /* ---- 2026 liquid-glass background: drifting gradient-mesh orbs behind frosted glass ---- */
+  body::before{content:'';position:fixed;inset:-25%;z-index:-2;
+    background:
+      radial-gradient(38vw 38vw at 18% 22%, rgba(139,124,246,.40), transparent 60%),
+      radial-gradient(34vw 34vw at 82% 18%, rgba(45,212,191,.34), transparent 60%),
+      radial-gradient(40vw 40vw at 72% 86%, rgba(233,196,106,.30), transparent 62%),
+      radial-gradient(36vw 36vw at 24% 88%, rgba(96,165,250,.30), transparent 60%);
+    filter:saturate(1.25);animation:drift 26s ease-in-out infinite alternate;}
+  body::after{content:'';position:fixed;inset:0;z-index:-1;backdrop-filter:blur(70px);background:rgba(7,10,16,.34);}
+  @keyframes drift{0%{transform:translate3d(0,0,0) scale(1) rotate(0deg);}
+    50%{transform:translate3d(2%,-2%,0) scale(1.08) rotate(4deg);}100%{transform:translate3d(-2%,2%,0) scale(1.04) rotate(-3deg);}}
+  .wrap{max-width:1280px;margin:0 auto;padding:22px 22px 60px;}
+  /* ---- header ---- */
+  header{display:flex;align-items:center;gap:16px;margin-bottom:22px;flex-wrap:wrap;}
+  .title h1{font-size:26px;font-weight:800;letter-spacing:-.5px;background:linear-gradient(92deg,#fff,var(--gold) 60%,var(--teal));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
+  .title p{font-size:12.5px;color:var(--muted);margin-top:2px;}
+  nav{margin-left:auto;display:flex;gap:8px;flex-wrap:wrap;}
+  nav a{font-size:12.5px;color:var(--muted);text-decoration:none;padding:7px 13px;border-radius:13px;
+    background:var(--glass);border:1px solid var(--glassb);backdrop-filter:blur(14px);transition:.2s;}
+  nav a:hover{color:#fff;border-color:rgba(233,196,106,.5);transform:translateY(-1px);}
+  /* ---- glass primitives ---- */
+  .glass{position:relative;background:var(--glass);border:1px solid var(--glassb);border-radius:22px;
+    backdrop-filter:blur(22px) saturate(1.5);-webkit-backdrop-filter:blur(22px) saturate(1.5);
+    box-shadow:0 12px 40px rgba(0,0,0,.40), inset 0 1px 0 rgba(255,255,255,.18), inset 0 -1px 0 rgba(0,0,0,.25);
+    overflow:hidden;opacity:0;transform:translateY(14px);animation:rise .7s cubic-bezier(.2,.7,.2,1) forwards;}
+  .glass::before{content:'';position:absolute;inset:0;border-radius:22px;pointer-events:none;
+    background:linear-gradient(120deg, rgba(255,255,255,.14), transparent 32%);}
+  @keyframes rise{to{opacity:1;transform:none;}}
+  /* ---- hero stat tiles ---- */
+  .tiles{display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:15px;margin-bottom:18px;}
+  .tile{padding:18px 19px;}
+  .tile .k{font-size:11px;text-transform:uppercase;letter-spacing:.7px;color:var(--muted);}
+  .tile .v{font-size:33px;font-weight:800;letter-spacing:-.5px;margin-top:6px;font-variant-numeric:tabular-nums;line-height:1;}
+  .tile .s{font-size:11.5px;color:var(--muted);margin-top:6px;}
+  .tile .ic{position:absolute;top:15px;right:16px;font-size:19px;opacity:.85;filter:drop-shadow(0 2px 8px rgba(0,0,0,.4));}
+  .v.gold{background:linear-gradient(90deg,#fff,var(--gold));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
+  .v.teal{background:linear-gradient(90deg,#fff,var(--teal));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
+  .v.violet{background:linear-gradient(90deg,#fff,var(--violet));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
+  .v.blue{background:linear-gradient(90deg,#fff,var(--blue));-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;}
+  /* ---- chart grid ---- */
+  .grid{display:grid;grid-template-columns:repeat(12,1fr);gap:15px;}
+  .card{padding:18px 19px 16px;}
+  .card h3{font-size:13px;font-weight:700;letter-spacing:.2px;display:flex;align-items:center;gap:8px;margin-bottom:3px;}
+  .card .sub{font-size:11.5px;color:var(--muted);margin-bottom:12px;}
+  .card .cw{position:relative;height:260px;}
+  .c12{grid-column:span 12;} .c8{grid-column:span 8;} .c6{grid-column:span 6;} .c4{grid-column:span 4;} .c7{grid-column:span 7;} .c5{grid-column:span 5;}
+  @media(max-width:900px){.c8,.c6,.c4,.c7,.c5{grid-column:span 12;}}
+  .dot{width:9px;height:9px;border-radius:50%;box-shadow:0 0 10px currentColor;}
+  .foot{margin-top:22px;font-size:11px;color:var(--muted);text-align:center;line-height:1.7;}
+  .pill{display:inline-block;padding:2px 9px;border-radius:11px;background:var(--glass);border:1px solid var(--glassb);margin:0 3px;}
+</style>
+</head>
+<body>
+<div class="wrap">
+  <header>
+    <div class="title">
+      <h1>◆ Deal Intelligence</h1>
+      <p id="asof">LA County commercial + residential pipeline · live</p>
+    </div>
+    <nav>
+      <a href="/">Properties</a><a href="/map.html">Map</a><a href="/crcp.html">CRCP</a>
+      <a href="/brokers.html">Mind-map</a><a href="/sales.html">Sales</a>
+    </nav>
+  </header>
+
+  <div class="tiles" id="tiles"></div>
+
+  <div class="grid">
+    <div class="glass card c7" style="animation-delay:.05s"><h3><span class="dot" style="color:var(--gold)"></span>Deal Pipeline by Loan Product</h3>
+      <div class="sub">How many screened properties fit each Arcstone loan product</div><div class="cw"><canvas id="cPipe"></canvas></div></div>
+    <div class="glass card c5" style="animation-delay:.1s"><h3><span class="dot" style="color:var(--teal)"></span>Portfolio by Property Type</h3>
+      <div class="sub">Commercial listings by asset class</div><div class="cw"><canvas id="cType"></canvas></div></div>
+
+    <div class="glass card c5" style="animation-delay:.15s"><h3><span class="dot" style="color:var(--violet)"></span>Cap-Rate Distribution</h3>
+      <div class="sub">Where the yield lives across priced deals</div><div class="cw"><canvas id="cCap"></canvas></div></div>
+    <div class="glass card c7" style="animation-delay:.2s"><h3><span class="dot" style="color:var(--blue)"></span>Median Deal Size by Product</h3>
+      <div class="sub">Typical ticket per loan product — what you're financing</div><div class="cw"><canvas id="cMed"></canvas></div></div>
+
+    <div class="glass card c7" style="animation-delay:.25s"><h3><span class="dot" style="color:var(--orange)"></span>Geographic Concentration</h3>
+      <div class="sub">Top markets by property count (geocoded)</div><div class="cw"><canvas id="cGeo"></canvas></div></div>
+    <div class="glass card c5" style="animation-delay:.3s"><h3><span class="dot" style="color:var(--pink)"></span>Price Band by Product</h3>
+      <div class="sub">p10–median–p90 range — the deal-size spread</div><div class="cw"><canvas id="cBand"></canvas></div></div>
+  </div>
+
+  <div class="foot">
+    <span class="pill">Liquid-glass UI · 2026</span><span class="pill">Chart.js</span><span class="pill">live data</span><br>
+    Estimates labeled where used (assumable = heuristic; price bands = p10/p90; rent/ROI = Census proxy). Not an appraisal.
+  </div>
+</div>
+
+<script>
+const F = { money:n=>n==null?'—':'$'+(n>=1e9?(n/1e9).toFixed(1)+'B':n>=1e6?(n/1e6).toFixed(2)+'M':n>=1e3?Math.round(n/1e3)+'k':n),
+  num:n=>Number(n||0).toLocaleString() };
+const PAL = ['#e9c46a','#2dd4bf','#8b7cf6','#60a5fa','#f472b6','#fb923c','#34d399','#f87171','#a78bfa','#fbbf24'];
+// translucent gradient fill for glass-matched bars
+function grad(ctx, c, vertical=true){ const a=ctx.chart.chartArea; if(!a) return c+'cc';
+  const g=ctx.chart.ctx.createLinearGradient(vertical?0:a.left,vertical?a.bottom:0,vertical?0:a.right,vertical?a.top:0);
+  g.addColorStop(0,c+'33'); g.addColorStop(1,c+'ee'); return g; }
+Chart.defaults.color='#9aa3b4'; Chart.defaults.borderColor='rgba(255,255,255,.07)';
+Chart.defaults.font.family="-apple-system,'SF Pro Display',Segoe UI,Roboto,sans-serif";
+const glassTip={backgroundColor:'rgba(16,20,28,.92)',borderColor:'rgba(255,255,255,.14)',borderWidth:1,padding:11,cornerRadius:12,titleColor:'#fff',bodyColor:'#cfd6e4',displayColors:true};
+
+function countUp(el,to,fmt){ const dur=900,t0=performance.now();
+  (function step(t){ const k=Math.min(1,(t-t0)/dur), e=1-Math.pow(1-k,3); el.textContent=fmt(Math.round(to*e)); if(k<1)requestAnimationFrame(step); })(t0); }
+
+Promise.all([
+  fetch('/api/segments').then(r=>r.json()).catch(()=>({segments:[]})),
+  fetch('/api/crcp/stats').then(r=>r.json()).catch(()=>({})),
+  fetch('/data/ranked.json').then(r=>r.json()).catch(()=>({ranked:[]})),
+  fetch('/data/map-points.json').then(r=>r.json()).catch(()=>({points:[]})),
+]).then(([seg, stats, ranked, mapd]) => {
+  const segs=(seg.segments||[]).map(s=>({...s,props:+s.props||0,agents:+s.agents||0,med:+s.priceMed||0,lo:+s.priceMin||0,hi:+s.priceMax||0}))
+    .filter(s=>s.props>0).sort((a,b)=>b.props-a.props);
+  const rows=(ranked.ranked||ranked||[]);
+  const pts=(mapd.points||[]);
+
+  // ---- hero tiles ----
+  const totalProps=(stats.listings||0)+(stats.sfr||rows.length*0||0)+ (pts.filter(p=>p.kind==='condo').length||0) || pts.length;
+  const screened = pts.length || (segs.reduce((s,x)=>s+x.props,0));
+  const agents=(stats.brokers||stats.agents_commercial||0);
+  const medAll = segs.length? Math.round(segs.reduce((s,x)=>s+x.med,0)/segs.filter(x=>x.med).length):0;
+  const tiles=[
+    {k:'Properties screened',v:screened,cls:'gold',ic:'🏙️',s:'commercial · condo · SFR, geocoded'},
+    {k:'Loan products live',v:segs.length,cls:'teal',ic:'◆',s:'Arcstone call segments'},
+    {k:'Agents in rolodex',v:agents,cls:'violet',ic:'👤',s:(stats.brokers_phone||0)+' w/ phone · '+(stats.brokers_email||0)+' w/ email'},
+    {k:'Median deal size',v:medAll,cls:'blue',ic:'💰',s:'across all products',fmt:F.money},
+  ];
+  document.getElementById('tiles').innerHTML=tiles.map((t,i)=>
+    `<div class="glass tile" style="animation-delay:${i*.05}s"><span class="ic">${t.ic}</span><div class="k">${t.k}</div>`+
+    `<div class="v ${t.cls}" id="tile${i}">0</div><div class="s">${t.s}</div></div>`).join('');
+  tiles.forEach((t,i)=>countUp(document.getElementById('tile'+i),t.v,t.fmt||F.num));
+
+  // ---- 1. Deal pipeline by loan product (horizontal bars) ----
+  new Chart(cPipe,{type:'bar',data:{labels:segs.map(s=>s.label),datasets:[{data:segs.map(s=>s.props),
+    backgroundColor:c=>grad(c,PAL[c.dataIndex%PAL.length],false),borderRadius:9,borderSkipped:false,maxBarThickness:26}]},
+    options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{...glassTip,
+      callbacks:{label:c=>` ${F.num(c.raw)} properties · med ${F.money(segs[c.dataIndex].med)}`}}},
+      scales:{x:{grid:{color:'rgba(255,255,255,.06)'},ticks:{callback:v=>F.num(v)}},y:{grid:{display:false},ticks:{font:{size:11}}}},
+      animation:{duration:1100,easing:'easeOutQuart'}}});
+
+  // ---- 2. Portfolio by property type (doughnut) ----
+  const bt=stats.byType||{}; const tl=Object.keys(bt), tv=tl.map(k=>bt[k]);
+  new Chart(cType,{type:'doughnut',data:{labels:tl,datasets:[{data:tv,backgroundColor:PAL.map(c=>c+'dd'),
+    borderColor:'rgba(10,14,20,.7)',borderWidth:2,hoverOffset:8}]},
+    options:{responsive:true,maintainAspectRatio:false,cutout:'62%',plugins:{legend:{position:'right',labels:{boxWidth:11,boxHeight:11,padding:12,font:{size:11.5}}},tooltip:{...glassTip,callbacks:{label:c=>` ${c.label}: ${F.num(c.raw)}`}}},animation:{animateRotate:true,duration:1100}}});
+
+  // ---- 3. Cap-rate distribution (histogram from ranked) ----
+  const caps=rows.map(p=>+p.cap_rate).filter(c=>c>0&&c<20);
+  const buckets=['0–2','2–4','4–6','6–8','8–10','10+']; const bc=[0,0,0,0,0,0];
+  caps.forEach(c=>{ const i=Math.min(5,Math.floor(c/2)); bc[i]++; });
+  new Chart(cCap,{type:'bar',data:{labels:buckets,datasets:[{data:bc,backgroundColor:c=>grad(c,'#8b7cf6'),borderRadius:9,borderSkipped:false}]},
+    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{...glassTip,callbacks:{title:c=>`Cap rate ${c[0].label}%`,label:c=>` ${F.num(c.raw)} deals`}}},
+      scales:{x:{grid:{display:false},title:{display:true,text:'cap rate %',color:'#9aa3b4',font:{size:10}}},y:{grid:{color:'rgba(255,255,255,.06)'}}},animation:{duration:1000}}});
+
+  // ---- 4. Median deal size by product (area line) ----
+  const ms=[...segs].sort((a,b)=>a.med-b.med);
+  new Chart(cMed,{type:'line',data:{labels:ms.map(s=>s.label),datasets:[{data:ms.map(s=>s.med),
+    borderColor:'#60a5fa',backgroundColor:c=>grad(c,'#60a5fa'),fill:true,tension:.4,pointRadius:4,pointBackgroundColor:'#60a5fa',pointBorderColor:'#fff',pointBorderWidth:1.5}]},
+    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{...glassTip,callbacks:{label:c=>' median '+F.money(c.raw)}}},
+      scales:{x:{grid:{display:false},ticks:{font:{size:9.5},maxRotation:32,minRotation:18}},y:{grid:{color:'rgba(255,255,255,.06)'},ticks:{callback:v=>F.money(v)}}},animation:{duration:1100}}});
+
+  // ---- 5. Geographic concentration (top cities) ----
+  const cc={}; pts.forEach(p=>{ if(p.city)cc[p.city]=(cc[p.city]||0)+1; });
+  const top=Object.entries(cc).sort((a,b)=>b[1]-a[1]).slice(0,10);
+  new Chart(cGeo,{type:'bar',data:{labels:top.map(t=>t[0]),datasets:[{data:top.map(t=>t[1]),
+    backgroundColor:c=>grad(c,'#fb923c'),borderRadius:8,borderSkipped:false,maxBarThickness:30}]},
+    options:{responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{...glassTip,callbacks:{label:c=>` ${F.num(c.raw)} properties`}}},
+      scales:{x:{grid:{display:false},ticks:{font:{size:10},maxRotation:36,minRotation:24}},y:{grid:{color:'rgba(255,255,255,.06)'}}},animation:{duration:1000}}});
+
+  // ---- 6. Price band by product (floating bars: p10→p90, median marker) ----
+  const sb=segs.filter(s=>s.hi>s.lo);
+  new Chart(cBand,{type:'bar',data:{labels:sb.map(s=>s.label),datasets:[
+    {label:'p10–p90',data:sb.map(s=>[s.lo,s.hi]),backgroundColor:c=>grad(c,PAL[c.dataIndex%PAL.length],false),borderRadius:7,borderSkipped:false,maxBarThickness:20},
+  ]},options:{indexAxis:'y',responsive:true,maintainAspectRatio:false,plugins:{legend:{display:false},tooltip:{...glassTip,
+    callbacks:{label:c=>{const s=sb[c.dataIndex];return [' p10 '+F.money(s.lo),' med '+F.money(s.med),' p90 '+F.money(s.hi)];}}}},
+    scales:{x:{grid:{color:'rgba(255,255,255,.06)'},ticks:{callback:v=>F.money(v)}},y:{grid:{display:false},ticks:{font:{size:10}}}},animation:{duration:1100}}});
+
+  document.getElementById('asof').textContent=`LA County pipeline · ${F.num(screened)} properties · ${segs.length} loan products · live`;
+}).catch(e=>{ document.getElementById('asof').textContent='data load error: '+e.message; });
+</script>
+</body>
+</html>
diff --git a/public/index.html b/public/index.html
index 71ee8cb..f2670dd 100644
--- a/public/index.html
+++ b/public/index.html
@@ -118,6 +118,7 @@
   <div class="spacer"></div>
   <a href="/crcp.html" style="color:var(--warn);text-decoration:none;font-size:13px;margin-right:12px" title="Commercial Real Estate Control Panel (live)">🛰️ CRCP</a>
   <a href="/map.html" style="color:var(--blue);text-decoration:none;font-size:13px;margin-right:12px" title="All properties on a map, colored by type, with overhead aerial photos">🗺️ Map</a>
+  <a href="/deals.html" style="color:var(--blue);text-decoration:none;font-size:13px;margin-right:12px" title="Liquid-glass deal-intelligence charts">◆ Deals</a>
   <a href="/brokers.html" style="color:var(--blue);text-decoration:none;font-size:13px;margin-right:12px" title="Broker / agent relationship mind-map">🕸️ Broker map</a>
   <a href="/graphics.html" style="color:var(--blue);text-decoration:none;font-size:13px;margin-right:12px" title="Broker / firm / warrantability charts">📊 Graphics</a>
   <a href="/condos.html" style="color:var(--blue);text-decoration:none;font-size:13px;margin-right:12px" title="LA condos — FHA warrantability">🏢 Condos</a>
diff --git a/public/map.html b/public/map.html
index c5f402d..715541a 100644
--- a/public/map.html
+++ b/public/map.html
@@ -37,7 +37,7 @@
 <body>
 <div class="topbar">
   <b>🗺️ Property Map</b> · LA County <span id="count">loading…</span>
-  <nav class="nav"><a href="/">Properties</a><a href="/crcp.html">CRCP</a><a href="/brokers.html">Mind-map</a><a href="/sales.html">Sales</a></nav>
+  <nav class="nav"><a href="/">Properties</a><a href="/crcp.html">CRCP</a><a href="/deals.html">Deals</a><a href="/brokers.html">Mind-map</a><a href="/sales.html">Sales</a></nav>
 </div>
 <div id="map"></div>
 <div class="legend"><h4>Property type</h4><div id="legend"></div></div>

← f4cd532 Property map: all 10,875 geocoded properties on Leaflet, col  ·  back to Commercialrealestate  ·  5x sweep 1: add live Refresh + pipeline metric toggle (Deals 7a4f457 →