← 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>