← back to Homesonspec

apps/mobile/scripts/vshots/shot-map.html

110 lines

<!DOCTYPE html><html><head><meta charset="utf-8">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"/>
<link rel="stylesheet" href="common.css"/>
<style>
  #map{ position:absolute; top:262px; bottom:252px; left:0; right:0; background:#e8edf2; }
  .leaflet-control-attribution, .leaflet-control-zoom{ display:none!important; }
  /* filter chip strip under the header */
  .filterbar{ position:absolute; top:262px; left:0; right:0; z-index:400; background:#fff;
    border-bottom:2px solid var(--line); padding:26px 30px; display:flex; gap:16px; flex-wrap:wrap; }
  .chip{ padding:16px 30px; border-radius:40px; border:4px solid #d1d5db; background:#fff;
    font-size:31px; font-weight:700; color:#374151; }
  .chip.g{ border-color:var(--green);} .chip.o{ border-color:var(--orange);} .chip.i{ border-color:var(--indigo);}
  .chip.active{ background:var(--brand); border-color:var(--brand); color:#fff; }
  /* pins */
  .pin{ width:34px; height:34px; border-radius:50%; border:5px solid #fff; box-shadow:0 3px 8px rgba(0,0,0,.35); }
  .pin.g{ background:var(--green);} .pin.o{ background:var(--orange);} .pin.i{ background:var(--indigo);}
  .cl{ background:var(--brand); color:#fff; font-weight:800; border:6px solid #fff;
    border-radius:999px; display:flex; align-items:center; justify-content:center;
    box-shadow:0 4px 10px rgba(0,0,0,.35); font-size:38px; }
  /* selected callout (hero) */
  .callout{ position:absolute; z-index:500; background:#fff; border-radius:22px; padding:22px 26px;
    box-shadow:0 10px 30px rgba(0,0,0,.25); min-width:300px; }
  .callout .p{ font-size:48px; } .callout .c{ font-size:33px; color:var(--muted); margin-top:6px; }
  .callout .s{ font-size:30px; margin-top:6px; font-weight:700; }
  /* detail bottom sheet */
  .sheet{ position:absolute; left:0; right:0; bottom:252px; z-index:500; background:#fff;
    border-radius:44px 44px 0 0; padding:52px 56px 64px; box-shadow:0 -6px 24px rgba(0,0,0,.14);
    display:flex; align-items:flex-start; }
  .sheet .l{ flex:1; } .sheet .p{ font-size:66px; } .sheet .city{ font-size:40px; color:#374151; margin-top:8px; }
  .sheet .st{ font-size:38px; font-weight:700; margin-top:12px; } .sheet .bd{ font-size:36px; color:var(--muted); margin-top:8px; }
  .sheet .view{ background:var(--accent); color:#fff; font-weight:800; font-size:40px; padding:28px 44px; border-radius:22px; }
</style></head>
<body>
<div class="frame">
  <div id="map"></div>
  <div class="appbar"><div class="title">Map</div></div>
  <div class="filterbar" id="fb"></div>
  <div id="overlay"></div>
</div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script src="data.js"></script>
<script src="chrome.js"></script>
<script>
const mode = new URLSearchParams(location.search).get('mode') || 'hero';
const COL = {MOVE_IN_READY:'g', UNDER_CONSTRUCTION:'o', PLANNED:'i'};
const LBL = {MOVE_IN_READY:'Move-In Ready', UNDER_CONSTRUCTION:'Under Construction', PLANNED:'Planned'};
const HEX = {g:'#16a34a',o:'#e07b39',i:'#6366f1'};
const fmt = p => p==null?'—' : (p>=1e6? '$'+(p/1e6).toFixed(1)+'M' : '$'+Math.round(p/1e3)+'k');

// filter chips (mode 'filter' highlights Move-In Ready)
const chips = [
  ['All ('+HOS.total.toLocaleString()+')', mode!=='filter', ''],
  ['Move-In Ready', mode==='filter', 'g'],
  ['Under Construction', false, 'o'],
  ['Planned', false, 'i'],
];
document.getElementById('fb').innerHTML = chips.map(([t,a,c])=>
  `<div class="chip ${c} ${a?'active':''}">${t}</div>`).join('');

const map = L.map('map',{zoomControl:false, attributionControl:false, fadeAnimation:false, zoomAnimation:false})
  .setView([32.86,-96.90], 10);
L.tileLayer('https://a.basemaps.cartocdn.com/light_all/{z}/{x}/{y}@2x.png',{maxZoom:19}).addTo(map);

// plot markers (filter mode → only move-in-ready)
let pts = HOS.dfw.filter(m=>m.la&&m.lo);
if(mode==='filter') pts = pts.filter(m=>m.s==='MOVE_IN_READY');

// grid-cluster like the app (coarser so we get a few count bubbles + many pins)
const b = map.getBounds(), cells = 12;
const dLat=(b.getNorth()-b.getSouth())/cells, dLng=(b.getEast()-b.getWest())/cells;
const grid = {};
pts.forEach(m=>{ const gx=Math.floor(m.lo/dLng), gy=Math.floor(m.la/dLat); const k=gx+':'+gy;
  (grid[k]=grid[k]||[]).push(m); });
Object.values(grid).forEach(cell=>{
  const la=cell.reduce((s,m)=>s+m.la,0)/cell.length, lo=cell.reduce((s,m)=>s+m.lo,0)/cell.length;
  if(cell.length>=6){
    const n=cell.length, s=n>=1000?Math.round(n/1000)+'k':String(n);
    const sz = Math.min(122, 66+s.length*14);
    L.marker([la,lo],{icon:L.divIcon({className:'',html:`<div class="cl" style="width:${sz}px;height:${sz}px">${s}</div>`,iconSize:[sz,sz],iconAnchor:[sz/2,sz/2]})}).addTo(map);
  } else {
    cell.forEach(m=>{ const c=COL[m.s]||'o';
      L.marker([m.la,m.lo],{icon:L.divIcon({className:'',html:`<div class="pin ${c}"></div>`,iconSize:[34,34],iconAnchor:[17,17]})}).addTo(map);
    });
  }
});

// overlays per mode
const ov = document.getElementById('overlay');
if(mode==='hero'){
  // a selected callout near center
  const sel = pts.find(m=>m.p) || pts[0];
  ov.innerHTML = `<div class="callout" style="left:150px; top:1050px">
     <div class="p price">${fmt(sel.p)}</div>
     <div class="c">${sel.c||'Dallas–Fort Worth, TX'}</div>
     <div class="s" style="color:${HEX[COL[sel.s]||'o']}">${LBL[sel.s]||'Under Construction'}</div></div>`;
}
if(mode==='detail'){
  const sel = pts.find(m=>m.p && m.b) || pts[0];
  ov.innerHTML = `<div class="sheet"><div class="l">
     <div class="p price">${fmt(sel.p)}</div>
     <div class="city">${sel.c||'Frisco, TX'}</div>
     <div class="st" style="color:${HEX[COL[sel.s]||'o']}">${LBL[sel.s]||'Under Construction'}</div>
     <div class="bd">${sel.b||4} bed</div></div>
     <div class="view">View Details</div></div>`;
}
injectChrome('map', true);
window.__READY__ = true;
</script>
</body></html>