Files
macker/server/index.html
T
2026-10-28 10:54:15 -05:00

35 lines
3.7 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Macker · Work car</title>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css">
<style>
*{box-sizing:border-box}body{margin:0;background:#f3f5f2;color:#182d26;font:16px system-ui,sans-serif}header{padding:24px 5vw;display:flex;justify-content:space-between;align-items:center}h1{font-size:24px;margin:0}header p{margin:4px 0;color:#607269}main{margin:0 5vw 28px}.stats{display:flex;gap:16px;margin-bottom:20px}.card{background:white;border:1px solid #dce4dd;border-radius:12px;padding:20px;flex:1}.label{font-size:12px;text-transform:uppercase;letter-spacing:.1em;color:#607269}.value{display:block;font-size:32px;margin:8px 0}small{color:#607269}#map{height:65vh;min-height:320px;border-radius:14px;border:1px solid #cbd8cd}button{background:#1c593f;color:white;border:0;border-radius:8px;padding:12px;cursor:pointer}#status{margin:12px 0;min-height:24px}@media(max-width:540px){.stats{flex-direction:column;gap:10px}.card{padding:14px}.value{font-size:26px}header{gap:12px}#map{height:50vh}}
</style>
</head>
<body>
<header><div><h1>Macker</h1><p>Work car · Live location</p></div><button id="follow" type="button">Follow car: on</button></header>
<main><div class="stats"><section class="card"><span class="label">Current speed</span><strong class="value" id="speed">—</strong><small id="metric">Waiting for GPS</small></section><section class="card"><span class="label">Last update</span><strong class="value" id="age">—</strong><small id="time">No location received yet</small></section></div><p id="status" role="status">Waiting for the car’s first GPS fix…</p><div id="map" aria-label="Car location map"></div></main>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
<script>
const el=id=>document.getElementById(id);
let map, marker, latest, follow=true, failed=false;
if(window.L){map=L.map('map').setView([20,0],2);L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png',{maxZoom:19,attribution:'&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);}
el('follow').onclick=()=>{follow=!follow;el('follow').textContent=`Follow car: ${follow?'on':'off'}`;if(follow&&marker)map.panTo(marker.getLatLng());};
function render(){
if(!latest){el('status').textContent=failed?'Cannot reach server. Retrying…':'Waiting for the car’s first GPS fix…';return;}
const seconds=Math.max(0,Math.floor((Date.now()-Date.parse(latest.updated_at))/1000));
const stale=seconds>30,p=latest.point;
el('age').textContent=seconds<60?`${seconds} s ago`:seconds<3600?`${Math.floor(seconds/60)} min ago`:`${Math.floor(seconds/3600)} h ago`;
el('time').textContent=new Date(latest.updated_at).toLocaleString();
el('speed').textContent=stale||p.Speed==null?'—':`${(p.Speed*2.236936).toFixed(1)} mph`;
el('metric').textContent=stale?'Last fix is stale':p.Speed==null?'Speed unavailable':`${(p.Speed*3.6).toFixed(1)} km/h`;
el('status').textContent=failed?'Connection lost · showing last known location':stale?'No recent GPS fix · showing last known location':`GPS active · ${p.Lat.toFixed(5)}, ${p.Long.toFixed(5)}`;
if(!map)el('status').textContent+=' · Map failed to load; reload to retry';
}
async function poll(){try{const res=await fetch('/api/location',{cache:'no-store',signal:AbortSignal.timeout(8000)});if(!res.ok)throw Error(res.status);latest=await res.json();failed=false;if(latest&&map){const pos=[latest.point.Lat,latest.point.Long];if(!marker){marker=L.marker(pos).addTo(map);map.setView(pos,15);}else{marker.setLatLng(pos);if(follow)map.panTo(pos);}}}catch(e){failed=true;}render();setTimeout(poll,2000);}
setInterval(render,1000);poll();
</script>
</body></html>