Speedometer / index.html
dawn28's picture
Update index.html
487c7aa verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<title>GPS Dashboard Pro</title>
<style>
:root{
--bg:#0a0b0d;
--panel:#16181c;
--good:#00ff9d;
--mid:#ffaa00;
--bad:#ff4444;
}
body{
margin:0;
background:var(--bg);
color:#fff;
font-family:system-ui;
display:flex;
flex-direction:column;
height:100dvh;
}
/* MAIN SPEED */
.main{
flex:2;
display:flex;
flex-direction:column;
justify-content:center;
align-items:center;
}
#speed{
font-size:clamp(90px,30vw,180px);
font-weight:900;
margin:0;
letter-spacing:-2px;
}
.unit{
color:#777;
letter-spacing:6px;
}
/* STATUS BAR */
.status{
text-align:center;
padding:10px;
font-size:12px;
letter-spacing:2px;
background:rgba(255,255,255,0.05);
}
/* ADDRESS */
.address{
text-align:center;
padding:10px;
font-size:14px;
color:#ccc;
border-bottom:1px solid #222;
}
/* GRID */
.panel{
background:var(--panel);
padding:12px;
}
.grid{
display:grid;
grid-template-columns:repeat(3,1fr);
gap:12px;
font-size:11px;
}
.item{
text-align:center;
}
.label{
color:#666;
font-size:10px;
}
.value{
font-family:monospace;
font-size:13px;
}
</style>
</head>
<body>
<div class="main">
<p id="speed">0.0</p>
<p class="unit">KM/H</p>
</div>
<div id="status" class="status">INITIALIZING GPS...</div>
<div id="address" class="address">Detecting address...</div>
<div class="panel">
<div class="grid">
<div class="item"><div class="label">Heading</div><div id="head" class="value">---</div></div>
<div class="item"><div class="label">Accuracy</div><div id="acc" class="value">---</div></div>
<div class="item"><div class="label">Altitude</div><div id="alt" class="value">---</div></div>
<div class="item"><div class="label">Lat</div><div id="lat" class="value">---</div></div>
<div class="item"><div class="label">Lon</div><div id="lon" class="value">---</div></div>
<div class="item"><div class="label">Confidence</div><div id="conf" class="value">---</div></div>
</div>
</div>
<script>
// --- ELEMENTS ---
const speedEl = document.getElementById("speed");
const statusEl = document.getElementById("status");
const addressEl = document.getElementById("address");
const headEl = document.getElementById("head");
const accEl = document.getElementById("acc");
const altEl = document.getElementById("alt");
const latEl = document.getElementById("lat");
const lonEl = document.getElementById("lon");
const confEl = document.getElementById("conf");
// --- STATE ---
let state={
lastFix:null,
emaSpeed:null,
firstGood:false,
lastHeading:null,
lastGeoKey:"",
lastGeoTime:0
};
// --- HAVERSINE ---
function dist(a,b){
const R=6371000;
const toRad=x=>x*Math.PI/180;
const dLat=toRad(b[0]-a[0]);
const dLon=toRad(b[1]-a[1]);
const x=Math.sin(dLat/2)**2+
Math.cos(toRad(a[0]))*Math.cos(toRad(b[0]))*
Math.sin(dLon/2)**2;
return 2*R*Math.asin(Math.sqrt(x));
}
// --- SAFE DERIVED ---
function safeDerived(c,ts){
if(!state.lastFix) return null;
const dt=(ts-state.lastFix.ts)/1000;
if(dt<0.8||dt>5) return null;
const d=dist(
[state.lastFix.lat,state.lastFix.lon],
[c.latitude,c.longitude]
);
if(d<3) return 0;
const s=d/dt;
if(s>55) return null;
return s;
}
// --- BEST SPEED ---
function bestSpeed(c,ts){
if(c.speed && c.speed>0.5) return c.speed;
const d=safeDerived(c,ts);
return d ?? 0;
}
// --- SMOOTH ---
function smooth(v){
if(state.emaSpeed===null){
state.emaSpeed=v;
return v;
}
const a=v>state.emaSpeed?0.35:0.2;
state.emaSpeed=a*v+(1-a)*state.emaSpeed;
if(state.emaSpeed<0.2) state.emaSpeed=0;
return state.emaSpeed;
}
// --- HEADING ---
function stableHeading(h,s){
if(s<1.5) return state.lastHeading;
if(h==null) return state.lastHeading;
if(state.lastHeading==null){
state.lastHeading=h;
return h;
}
let diff=h-state.lastHeading;
if(diff>180) diff-=360;
if(diff<-180) diff+=360;
state.lastHeading+=diff*0.25;
return state.lastHeading;
}
function headingText(h){
if(h==null) return "---";
const d=['N','NE','E','SE','S','SW','W','NW'];
return d[Math.round(h/45)%8]+" "+Math.round(h)+"°";
}
// --- CONF ---
function conf(acc){
if(acc<=10) return ["HIGH","#00ff9d"];
if(acc<=30) return ["MED","#ffaa00"];
return ["LOW","#ff4444"];
}
// --- ADDRESS ---
async function updateAddress(lat,lon){
const key=lat.toFixed(3)+","+lon.toFixed(3);
const now=Date.now();
if(key===state.lastGeoKey) return;
if(now-state.lastGeoTime<20000) return;
state.lastGeoKey=key;
state.lastGeoTime=now;
try{
const r=await fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}`);
const d=await r.json();
addressEl.textContent=d.display_name?.split(",").slice(0,3).join(",") || "Unknown";
}catch{}
}
// --- MAIN ---
navigator.geolocation.watchPosition((pos)=>{
const c=pos.coords;
const ts=Date.now();
let v=bestSpeed(c,ts);
let final;
if(!state.firstGood){
if(v>1.5 && v<40){
state.firstGood=true;
final=v;
}else final=0;
}else{
final=smooth(v);
}
const kmh=(final*3.6).toFixed(1);
const [label,color]=conf(c.accuracy);
speedEl.textContent=kmh;
speedEl.style.color=color;
statusEl.textContent=`CONFIDENCE: ${label}`;
statusEl.style.color=color;
accEl.textContent=Math.round(c.accuracy)+"m";
altEl.textContent=c.altitude!=null?Math.round(c.altitude)+"m":"N/A";
latEl.textContent=c.latitude.toFixed(4);
lonEl.textContent=c.longitude.toFixed(4);
const h=stableHeading(c.heading,kmh);
headEl.textContent=headingText(h);
confEl.textContent=label;
updateAddress(c.latitude,c.longitude);
state.lastFix={lat:c.latitude,lon:c.longitude,ts};
},()=>{},{
enableHighAccuracy:true,
maximumAge:5000,
timeout:10000
});
</script>
</body>
</html>