chore: checkpoint initial — Zektyc dataset, avant humanisation
- site vitrine + fingerprint test + zmap + z-panel - remplacement duckdns -> riricdev.tail5ea5cd.ts.net (canonical/og/alternates/docs) - suppression du token DuckDNS (plus utilisé)
This commit is contained in:
commit
d7ec32a458
92 changed files with 17476 additions and 0 deletions
188
public/zmap/web/app.js
Normal file
188
public/zmap/web/app.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
(function(){
|
||||
"use strict";
|
||||
var API = location.origin;
|
||||
|
||||
var map = L.map("map",{zoomControl:false,attributionControl:false}).setView([48.85,2.35],13);
|
||||
var osm = L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png",{maxZoom:19,attribution:"OSM"});
|
||||
var esri = L.tileLayer("https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}",{maxZoom:19,attribution:"Esri"});
|
||||
osm.addTo(map);
|
||||
L.control.attribution({position:"bottomleft",prefix:false}).addTo(map);
|
||||
|
||||
var fromPt=null, toPt=null, routeLayer=null, stationLayer=null, gpsWatchId=null, gpsMarker=null;
|
||||
var selectingFrom=true;
|
||||
|
||||
var $=function(id){return document.getElementById(id)};
|
||||
var $from=$("from"), $to=$("to"), $btnRoute=$("btn-route"), $btnClear=$("btn-clear");
|
||||
var $btnSat=$("btn-sat"), $btnGps=$("btn-gps"), $panel=$("panel");
|
||||
var $routeInfo=$("route-info"), $legend=$("speed-legend"), $loading=$("loading");
|
||||
var $searchIn=$("search-input"), $searchRes=$("search-results");
|
||||
|
||||
function esc(s){var d=document.createElement("div");d.textContent=s;return d.innerHTML}
|
||||
|
||||
$btnSat.onclick=function(){
|
||||
if(map.hasLayer(esri)){map.removeLayer(esri);osm.addTo(map);$btnSat.className="icon-btn"}
|
||||
else{map.removeLayer(osm);esri.addTo(map);$btnSat.className="icon-btn active"}
|
||||
};
|
||||
|
||||
$btnGps.onclick=function(){
|
||||
if(gpsWatchId!=null){stopGps();return}
|
||||
if(!navigator.geolocation){alert("Géolocalisation non supportée");return}
|
||||
gpsWatchId=navigator.geolocation.watchPosition(function(pos){
|
||||
var lat=pos.coords.latitude,lng=pos.coords.longitude;
|
||||
if(!gpsMarker){
|
||||
var icon=L.divIcon({className:"",html:"<div class='gps-dot'></div>",iconSize:[14,14],iconAnchor:[7,7]});
|
||||
gpsMarker=L.marker([lat,lng],{icon:icon,zIndexOffset:1000}).addTo(map);
|
||||
} else gpsMarker.setLatLng([lat,lng]);
|
||||
},function(e){alert("Erreur GPS: "+e.message)},{enableHighAccuracy:true,timeout:10000});
|
||||
$btnGps.className="icon-btn active gps-active";
|
||||
};
|
||||
function stopGps(){
|
||||
if(gpsWatchId!=null){navigator.geolocation.clearWatch(gpsWatchId);gpsWatchId=null}
|
||||
if(gpsMarker){map.removeLayer(gpsMarker);gpsMarker=null}
|
||||
$btnGps.className="icon-btn";
|
||||
}
|
||||
|
||||
var searchTimer=null;
|
||||
$searchIn.oninput=function(){
|
||||
clearTimeout(searchTimer);
|
||||
var q=$searchIn.value.trim();
|
||||
if(q.length<2){$searchRes.className="search-results";return}
|
||||
searchTimer=setTimeout(function(){doSearch(q)},300);
|
||||
};
|
||||
$searchIn.onkeydown=function(e){if(e.key==="Escape"){$searchRes.className="search-results";$searchIn.blur()}};
|
||||
document.addEventListener("click",function(e){if(!e.target.closest(".search-wrap"))$searchRes.className="search-results"});
|
||||
|
||||
function doSearch(q){
|
||||
fetch(API+"/api/zmap/search?q="+encodeURIComponent(q))
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if(!d.results||!d.results.length){$searchRes.innerHTML='<div class="sr-item" style="color:var(--dim)">Aucun résultat</div>';return}
|
||||
$searchRes.innerHTML="";
|
||||
for(var i=0;i<d.results.length;i++){
|
||||
var r=d.results[i];
|
||||
var el=document.createElement("div");
|
||||
el.className="sr-item";
|
||||
el.innerHTML="<div>"+esc(r.display_name)+"</div><div class='sr-type'>"+esc(r.type)+" · "+esc(r.category)+"</div>";
|
||||
el.dataset.lat=r.lat;el.dataset.lng=r.lon;
|
||||
el.onclick=function(){
|
||||
var lat=parseFloat(this.dataset.lat),lng=parseFloat(this.dataset.lng);
|
||||
map.setView([lat,lng],16);
|
||||
if(selectingFrom){setFrom(lat,lng);selectingFrom=false}
|
||||
else{setTo(lat,lng);selectingFrom=true}
|
||||
$searchRes.className="search-results";$searchIn.value="";
|
||||
};
|
||||
$searchRes.appendChild(el);
|
||||
}
|
||||
$searchRes.className="search-results open";
|
||||
}).catch(function(){});
|
||||
}
|
||||
|
||||
map.on("click",function(e){
|
||||
var lat=+e.latlng.lat.toFixed(5),lng=+e.latlng.lng.toFixed(5);
|
||||
if(selectingFrom){setFrom(lat,lng);selectingFrom=false}
|
||||
else{setTo(lat,lng);selectingFrom=true}
|
||||
});
|
||||
|
||||
function setFrom(lat,lng){fromPt={lat:lat,lng:lng};$from.value=lat+","+lng;if(fromPt&&toPt)$btnRoute.disabled=false}
|
||||
function setTo(lat,lng){toPt={lat:lat,lng:lng};$to.value=lat+","+lng;if(fromPt&&toPt)$btnRoute.disabled=false}
|
||||
|
||||
$btnClear.onclick=function(){
|
||||
fromPt=toPt=null;$from.value="";$to.value="";$btnRoute.disabled=true;selectingFrom=true;
|
||||
$routeInfo.className="route-info hidden";$legend.innerHTML="";
|
||||
if(routeLayer){map.removeLayer(routeLayer);routeLayer=null}
|
||||
if(stationLayer){map.removeLayer(stationLayer);stationLayer=null}
|
||||
};
|
||||
|
||||
$btnRoute.onclick=function(){
|
||||
if(!fromPt||!toPt)return;
|
||||
$loading.className="loading show";
|
||||
fetch(API+"/api/zmap/route?from="+fromPt.lat+","+fromPt.lng+"&to="+toPt.lat+","+toPt.lng)
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){if(d.error){alert(d.error);return}renderRoute(d)})
|
||||
.catch(function(e){alert("Erreur: "+e.message)})
|
||||
.finally(function(){$loading.className="loading"});
|
||||
};
|
||||
|
||||
function speedColor(k){
|
||||
if(k==null)return"#555";
|
||||
if(k<=20)return"#ef4444";if(k<=40)return"#f59e0b";if(k<=60)return"#eab308";
|
||||
if(k<=80)return"#22c55e";if(k<=100)return"#06b6d4";if(k<=120)return"#3b82f6";return"#8b5cf6";
|
||||
}
|
||||
function speedCls(k){
|
||||
if(k==null)return"sl-unk";
|
||||
if(k<=20)return"sl-20";if(k<=40)return"sl-40";if(k<=60)return"sl-60";
|
||||
if(k<=80)return"sl-80";if(k<=100)return"sl-100";if(k<=120)return"sl-120";return"sl-max";
|
||||
}
|
||||
|
||||
function renderRoute(d){
|
||||
if(routeLayer)map.removeLayer(routeLayer);
|
||||
var coords=d.geometry&&d.geometry.coordinates||[];
|
||||
var speeds=d.speeds||[];
|
||||
var segs=[];
|
||||
for(var i=0;i<coords.length-1;i++){
|
||||
var kmh=speeds[i]!=null?speeds[i]:null;
|
||||
var line=L.polyline([[coords[i][1],coords[i][0]],[coords[i+1][1],coords[i+1][0]]],
|
||||
{color:speedColor(kmh),weight:5,opacity:.9,lineCap:"round",lineJoin:"round"});
|
||||
if(kmh!=null)line.bindPopup("<b>"+kmh+" km/h</b>");
|
||||
segs.push(line);
|
||||
}
|
||||
routeLayer=L.layerGroup(segs).addTo(map);
|
||||
map.fitBounds(L.polyline(coords.map(function(c){return[c[1],c[0]]})).getBounds().pad(.1));
|
||||
|
||||
var covered=speeds.filter(function(s){return s!=null}).length;
|
||||
var total=speeds.length;
|
||||
var pct=total?Math.round(covered/total*100):0;
|
||||
$routeInfo.className="route-info";
|
||||
var dist={};
|
||||
for(var i=0;i<speeds.length;i++){
|
||||
var k=speeds[i]==null?"?":speeds[i]<=20?"0-20":speeds[i]<=40?"21-40":speeds[i]<=60?"41-60":speeds[i]<=80?"61-80":speeds[i]<=100?"81-100":speeds[i]<=120?"101-120":">120";
|
||||
dist[k]=(dist[k]||0)+1;
|
||||
}
|
||||
var colors={"?":"#555","0-20":"#ef4444","21-40":"#f59e0b","41-60":"#eab308","61-80":"#22c55e","81-100":"#06b6d4","101-120":"#3b82f6",">120":"#8b5cf6"};
|
||||
var order=["?","0-20","21-40","41-60","61-80","81-100","101-120",">120"];
|
||||
var barHtml="<div class='speed-bar'>";
|
||||
var distribHtml="<div class='speed-distrib'>";
|
||||
for(var j=0;j<order.length;j++){
|
||||
var cnt=dist[order[j]]||0;
|
||||
if(cnt===0)continue;
|
||||
var bpct=Math.round(cnt/speeds.length*100);
|
||||
barHtml+="<div style='width:"+bpct+"%;background:"+colors[order[j]]+"'></div>";
|
||||
distribHtml+="<span><span class='sd-dot' style='background:"+colors[order[j]]+"'></span>"+order[j]+" km/h: "+cnt+"</span>";
|
||||
}
|
||||
barHtml+="</div>";distribHtml+="</div>";
|
||||
$routeInfo.innerHTML='<div class="ri-main">'+d.distance_km+" km</div>"+
|
||||
'<div class="ri-sub">'+d.duration_min+" min · "+covered+"/"+total+" segments · <b>"+pct+"%</b> couverts</div>"+barHtml+distribHtml;
|
||||
|
||||
var leg=[["≤20","sl-20"],["21-40","sl-40"],["41-60","sl-60"],
|
||||
["61-80","sl-80"],["81-100","sl-100"],["101-120","sl-120"],[">120","sl-max"],["?","sl-unk"]];
|
||||
$legend.innerHTML=leg.map(function(l){return'<span class="'+l[1]+'">'+l[0]+" km/h</span>"}).join("");
|
||||
loadStations(coords);
|
||||
}
|
||||
|
||||
function loadStations(coords){
|
||||
if(stationLayer)map.removeLayer(stationLayer);
|
||||
if(!coords.length)return;
|
||||
var mnLa=90,mxLa=-90,mnLo=180,mxLo=-180;
|
||||
for(var i=0;i<coords.length;i++){
|
||||
if(coords[i][1]<mnLa)mnLa=coords[i][1];if(coords[i][1]>mxLa)mxLa=coords[i][1];
|
||||
if(coords[i][0]<mnLo)mnLo=coords[i][0];if(coords[i][0]>mxLo)mxLo=coords[i][0];
|
||||
}
|
||||
var p=.02;
|
||||
fetch(API+"/api/zmap/stations?south="+(mnLa-p)+"&west="+(mnLo-p)+"&north="+(mxLa+p)+"&east="+(mxLo+p))
|
||||
.then(function(r){return r.json()})
|
||||
.then(function(d){
|
||||
if(!d.stations||!d.stations.length)return;
|
||||
stationLayer=L.layerGroup().addTo(map);
|
||||
for(var i=0;i<d.stations.length;i++){
|
||||
var s=d.stations[i];
|
||||
var icon=L.divIcon({className:"",html:"<div class='station-dot'></div>",iconSize:[10,10],iconAnchor:[5,5]});
|
||||
var pw=0;
|
||||
if(s.connectors)for(var j=0;j<s.connectors.length;j++){var v=s.connectors[j].powerKw||0;if(v>pw)pw=v}
|
||||
L.marker([s.lat,s.lon],{icon:icon}).bindPopup(
|
||||
"<b>"+esc((s.name||"").slice(0,40)||"Borne")+"</b><br>"+
|
||||
(pw?pw+" kW":"")+" "+esc(s.network||"")+"<br>"+esc(s.status||"")
|
||||
).addTo(stationLayer);
|
||||
}
|
||||
}).catch(function(){});
|
||||
}
|
||||
})();
|
||||
56
public/zmap/web/index.html
Normal file
56
public/zmap/web/index.html
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<title>ZMap — Web</title>
|
||||
<link rel="stylesheet" href="leaflet/leaflet.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="map"></div>
|
||||
|
||||
<div class="topbar">
|
||||
<div class="search-wrap">
|
||||
<svg class="icon" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
<input id="search-input" type="text" placeholder="Rechercher un lieu..." autocomplete="off">
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</div>
|
||||
<button id="btn-gps" class="icon-btn" title="Ma position">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="3"/><path d="M12 2v4m0 12v4M2 12h4m12 0h4"/></svg>
|
||||
</button>
|
||||
<button id="btn-sat" class="icon-btn" title="Vue satellite">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10A15.3 15.3 0 0 1 12 2z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="panel" class="panel open">
|
||||
<div class="panel-head">
|
||||
<h2>Itinéraire</h2>
|
||||
<button class="panel-close" id="panel-close">×</button>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="point-row">
|
||||
<div class="point-dot from"></div>
|
||||
<input id="from" placeholder="Départ (clic ou recherche)" readonly>
|
||||
</div>
|
||||
<div class="point-row">
|
||||
<div class="point-dot to"></div>
|
||||
<input id="to" placeholder="Arrivée (clic ou recherche)" readonly>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<button class="btn" id="btn-route" disabled>Calculer</button>
|
||||
<button class="btn btn-outline btn-danger" id="btn-clear">Effacer</button>
|
||||
</div>
|
||||
<div id="route-info" class="route-info hidden"></div>
|
||||
<div id="speed-legend" class="speed-legend"></div>
|
||||
<p class="hint">Cliquez sur la carte ou utilisez la barre de recherche</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="loading"><div class="spinner"></div> Calcul de l'itinéraire...</div>
|
||||
|
||||
<script src="leaflet/leaflet.js"></script>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
public/zmap/web/leaflet/images/layers-2x.png
Normal file
BIN
public/zmap/web/leaflet/images/layers-2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
BIN
public/zmap/web/leaflet/images/layers.png
Normal file
BIN
public/zmap/web/leaflet/images/layers.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 696 B |
661
public/zmap/web/leaflet/leaflet.css
Normal file
661
public/zmap/web/leaflet/leaflet.css
Normal file
|
|
@ -0,0 +1,661 @@
|
|||
/* required styles */
|
||||
|
||||
.leaflet-pane,
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-tile-container,
|
||||
.leaflet-pane > svg,
|
||||
.leaflet-pane > canvas,
|
||||
.leaflet-zoom-box,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-layer {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
.leaflet-tile,
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
}
|
||||
/* Prevents IE11 from highlighting tiles in blue */
|
||||
.leaflet-tile::selection {
|
||||
background: transparent;
|
||||
}
|
||||
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
|
||||
.leaflet-safari .leaflet-tile {
|
||||
image-rendering: -webkit-optimize-contrast;
|
||||
}
|
||||
/* hack that prevents hw layers "stretching" when loading new tiles */
|
||||
.leaflet-safari .leaflet-tile-container {
|
||||
width: 1600px;
|
||||
height: 1600px;
|
||||
-webkit-transform-origin: 0 0;
|
||||
}
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow {
|
||||
display: block;
|
||||
}
|
||||
/* .leaflet-container svg: reset svg max-width decleration shipped in Joomla! (joomla.org) 3.x */
|
||||
/* .leaflet-container img: map is broken in FF if you have max-width: 100% on tiles */
|
||||
.leaflet-container .leaflet-overlay-pane svg {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
.leaflet-container .leaflet-marker-pane img,
|
||||
.leaflet-container .leaflet-shadow-pane img,
|
||||
.leaflet-container .leaflet-tile-pane img,
|
||||
.leaflet-container img.leaflet-image-layer,
|
||||
.leaflet-container .leaflet-tile {
|
||||
max-width: none !important;
|
||||
max-height: none !important;
|
||||
width: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.leaflet-container img.leaflet-tile {
|
||||
/* See: https://bugs.chromium.org/p/chromium/issues/detail?id=600120 */
|
||||
mix-blend-mode: plus-lighter;
|
||||
}
|
||||
|
||||
.leaflet-container.leaflet-touch-zoom {
|
||||
-ms-touch-action: pan-x pan-y;
|
||||
touch-action: pan-x pan-y;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag {
|
||||
-ms-touch-action: pinch-zoom;
|
||||
/* Fallback for FF which doesn't support pinch-zoom */
|
||||
touch-action: none;
|
||||
touch-action: pinch-zoom;
|
||||
}
|
||||
.leaflet-container.leaflet-touch-drag.leaflet-touch-zoom {
|
||||
-ms-touch-action: none;
|
||||
touch-action: none;
|
||||
}
|
||||
.leaflet-container {
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
.leaflet-container a {
|
||||
-webkit-tap-highlight-color: rgba(51, 181, 229, 0.4);
|
||||
}
|
||||
.leaflet-tile {
|
||||
filter: inherit;
|
||||
visibility: hidden;
|
||||
}
|
||||
.leaflet-tile-loaded {
|
||||
visibility: inherit;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
width: 0;
|
||||
height: 0;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
z-index: 800;
|
||||
}
|
||||
/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */
|
||||
.leaflet-overlay-pane svg {
|
||||
-moz-user-select: none;
|
||||
}
|
||||
|
||||
.leaflet-pane { z-index: 400; }
|
||||
|
||||
.leaflet-tile-pane { z-index: 200; }
|
||||
.leaflet-overlay-pane { z-index: 400; }
|
||||
.leaflet-shadow-pane { z-index: 500; }
|
||||
.leaflet-marker-pane { z-index: 600; }
|
||||
.leaflet-tooltip-pane { z-index: 650; }
|
||||
.leaflet-popup-pane { z-index: 700; }
|
||||
|
||||
.leaflet-map-pane canvas { z-index: 100; }
|
||||
.leaflet-map-pane svg { z-index: 200; }
|
||||
|
||||
.leaflet-vml-shape {
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
}
|
||||
.lvml {
|
||||
behavior: url(#default#VML);
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
|
||||
/* control positioning */
|
||||
|
||||
.leaflet-control {
|
||||
position: relative;
|
||||
z-index: 800;
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-top,
|
||||
.leaflet-bottom {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-top {
|
||||
top: 0;
|
||||
}
|
||||
.leaflet-right {
|
||||
right: 0;
|
||||
}
|
||||
.leaflet-bottom {
|
||||
bottom: 0;
|
||||
}
|
||||
.leaflet-left {
|
||||
left: 0;
|
||||
}
|
||||
.leaflet-control {
|
||||
float: left;
|
||||
clear: both;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
float: right;
|
||||
}
|
||||
.leaflet-top .leaflet-control {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.leaflet-left .leaflet-control {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.leaflet-right .leaflet-control {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/* zoom and fade animations */
|
||||
|
||||
.leaflet-fade-anim .leaflet-popup {
|
||||
opacity: 0;
|
||||
-webkit-transition: opacity 0.2s linear;
|
||||
-moz-transition: opacity 0.2s linear;
|
||||
transition: opacity 0.2s linear;
|
||||
}
|
||||
.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {
|
||||
opacity: 1;
|
||||
}
|
||||
.leaflet-zoom-animated {
|
||||
-webkit-transform-origin: 0 0;
|
||||
-ms-transform-origin: 0 0;
|
||||
transform-origin: 0 0;
|
||||
}
|
||||
svg.leaflet-zoom-animated {
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-animated {
|
||||
-webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
-moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
transition: transform 0.25s cubic-bezier(0,0,0.25,1);
|
||||
}
|
||||
.leaflet-zoom-anim .leaflet-tile,
|
||||
.leaflet-pan-anim .leaflet-tile {
|
||||
-webkit-transition: none;
|
||||
-moz-transition: none;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
.leaflet-zoom-anim .leaflet-zoom-hide {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
|
||||
/* cursors */
|
||||
|
||||
.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
}
|
||||
.leaflet-grab {
|
||||
cursor: -webkit-grab;
|
||||
cursor: -moz-grab;
|
||||
cursor: grab;
|
||||
}
|
||||
.leaflet-crosshair,
|
||||
.leaflet-crosshair .leaflet-interactive {
|
||||
cursor: crosshair;
|
||||
}
|
||||
.leaflet-popup-pane,
|
||||
.leaflet-control {
|
||||
cursor: auto;
|
||||
}
|
||||
.leaflet-dragging .leaflet-grab,
|
||||
.leaflet-dragging .leaflet-grab .leaflet-interactive,
|
||||
.leaflet-dragging .leaflet-marker-draggable {
|
||||
cursor: move;
|
||||
cursor: -webkit-grabbing;
|
||||
cursor: -moz-grabbing;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
/* marker & overlays interactivity */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
.leaflet-image-layer,
|
||||
.leaflet-pane > svg path,
|
||||
.leaflet-tile-container {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.leaflet-marker-icon.leaflet-interactive,
|
||||
.leaflet-image-layer.leaflet-interactive,
|
||||
.leaflet-pane > svg path.leaflet-interactive,
|
||||
svg.leaflet-image-layer.leaflet-interactive path {
|
||||
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
/* visual tweaks */
|
||||
|
||||
.leaflet-container {
|
||||
background: #ddd;
|
||||
outline-offset: 1px;
|
||||
}
|
||||
.leaflet-container a {
|
||||
color: #0078A8;
|
||||
}
|
||||
.leaflet-zoom-box {
|
||||
border: 2px dotted #38f;
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
|
||||
|
||||
/* general typography */
|
||||
.leaflet-container {
|
||||
font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
|
||||
font-size: 12px;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
|
||||
/* general toolbar styles */
|
||||
|
||||
.leaflet-bar {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
|
||||
border-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a {
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ccc;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
display: block;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: black;
|
||||
}
|
||||
.leaflet-bar a,
|
||||
.leaflet-control-layers-toggle {
|
||||
background-position: 50% 50%;
|
||||
background-repeat: no-repeat;
|
||||
display: block;
|
||||
}
|
||||
.leaflet-bar a:hover,
|
||||
.leaflet-bar a:focus {
|
||||
background-color: #f4f4f4;
|
||||
}
|
||||
.leaflet-bar a:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
border-top-right-radius: 4px;
|
||||
}
|
||||
.leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaflet-bar a.leaflet-disabled {
|
||||
cursor: default;
|
||||
background-color: #f4f4f4;
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-bar a {
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:first-child {
|
||||
border-top-left-radius: 2px;
|
||||
border-top-right-radius: 2px;
|
||||
}
|
||||
.leaflet-touch .leaflet-bar a:last-child {
|
||||
border-bottom-left-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
|
||||
/* zoom control */
|
||||
|
||||
.leaflet-control-zoom-in,
|
||||
.leaflet-control-zoom-out {
|
||||
font: bold 18px 'Lucida Console', Monaco, monospace;
|
||||
text-indent: 1px;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-zoom-in, .leaflet-touch .leaflet-control-zoom-out {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
||||
/* layers control */
|
||||
|
||||
.leaflet-control-layers {
|
||||
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers.png);
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
}
|
||||
.leaflet-retina .leaflet-control-layers-toggle {
|
||||
background-image: url(images/layers-2x.png);
|
||||
background-size: 26px 26px;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers-toggle {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
}
|
||||
.leaflet-control-layers .leaflet-control-layers-list,
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-toggle {
|
||||
display: none;
|
||||
}
|
||||
.leaflet-control-layers-expanded .leaflet-control-layers-list {
|
||||
display: block;
|
||||
position: relative;
|
||||
}
|
||||
.leaflet-control-layers-expanded {
|
||||
padding: 6px 10px 6px 6px;
|
||||
color: #333;
|
||||
background: #fff;
|
||||
}
|
||||
.leaflet-control-layers-scrollbar {
|
||||
overflow-y: scroll;
|
||||
overflow-x: hidden;
|
||||
padding-right: 5px;
|
||||
}
|
||||
.leaflet-control-layers-selector {
|
||||
margin-top: 2px;
|
||||
position: relative;
|
||||
top: 1px;
|
||||
}
|
||||
.leaflet-control-layers label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
}
|
||||
.leaflet-control-layers-separator {
|
||||
height: 0;
|
||||
border-top: 1px solid #ddd;
|
||||
margin: 5px -10px 5px -6px;
|
||||
}
|
||||
|
||||
/* Default icon URLs */
|
||||
.leaflet-default-icon-path { /* used only in path-guessing heuristic, see L.Icon.Default */
|
||||
background-image: url(images/marker-icon.png);
|
||||
}
|
||||
|
||||
|
||||
/* attribution and scale controls */
|
||||
|
||||
.leaflet-container .leaflet-control-attribution {
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
margin: 0;
|
||||
}
|
||||
.leaflet-control-attribution,
|
||||
.leaflet-control-scale-line {
|
||||
padding: 0 5px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.leaflet-control-attribution a {
|
||||
text-decoration: none;
|
||||
}
|
||||
.leaflet-control-attribution a:hover,
|
||||
.leaflet-control-attribution a:focus {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.leaflet-attribution-flag {
|
||||
display: inline !important;
|
||||
vertical-align: baseline !important;
|
||||
width: 1em;
|
||||
height: 0.6669em;
|
||||
}
|
||||
.leaflet-left .leaflet-control-scale {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.leaflet-bottom .leaflet-control-scale {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.leaflet-control-scale-line {
|
||||
border: 2px solid #777;
|
||||
border-top: none;
|
||||
line-height: 1.1;
|
||||
padding: 2px 5px 1px;
|
||||
white-space: nowrap;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
text-shadow: 1px 1px #fff;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child) {
|
||||
border-top: 2px solid #777;
|
||||
border-bottom: none;
|
||||
margin-top: -2px;
|
||||
}
|
||||
.leaflet-control-scale-line:not(:first-child):not(:last-child) {
|
||||
border-bottom: 2px solid #777;
|
||||
}
|
||||
|
||||
.leaflet-touch .leaflet-control-attribution,
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
box-shadow: none;
|
||||
}
|
||||
.leaflet-touch .leaflet-control-layers,
|
||||
.leaflet-touch .leaflet-bar {
|
||||
border: 2px solid rgba(0,0,0,0.2);
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
|
||||
/* popup */
|
||||
|
||||
.leaflet-popup {
|
||||
position: absolute;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.leaflet-popup-content-wrapper {
|
||||
padding: 1px;
|
||||
text-align: left;
|
||||
border-radius: 12px;
|
||||
}
|
||||
.leaflet-popup-content {
|
||||
margin: 13px 24px 13px 20px;
|
||||
line-height: 1.3;
|
||||
font-size: 13px;
|
||||
font-size: 1.08333em;
|
||||
min-height: 1px;
|
||||
}
|
||||
.leaflet-popup-content p {
|
||||
margin: 17px 0;
|
||||
margin: 1.3em 0;
|
||||
}
|
||||
.leaflet-popup-tip-container {
|
||||
width: 40px;
|
||||
height: 20px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-top: -1px;
|
||||
margin-left: -20px;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
.leaflet-popup-tip {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
|
||||
margin: -10px auto 0;
|
||||
pointer-events: auto;
|
||||
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.leaflet-popup-content-wrapper,
|
||||
.leaflet-popup-tip {
|
||||
background: white;
|
||||
color: #333;
|
||||
box-shadow: 0 3px 14px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
border: none;
|
||||
text-align: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
font: 16px/24px Tahoma, Verdana, sans-serif;
|
||||
color: #757575;
|
||||
text-decoration: none;
|
||||
background: transparent;
|
||||
}
|
||||
.leaflet-container a.leaflet-popup-close-button:hover,
|
||||
.leaflet-container a.leaflet-popup-close-button:focus {
|
||||
color: #585858;
|
||||
}
|
||||
.leaflet-popup-scrolled {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper {
|
||||
-ms-zoom: 1;
|
||||
}
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
width: 24px;
|
||||
margin: 0 auto;
|
||||
|
||||
-ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";
|
||||
filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);
|
||||
}
|
||||
|
||||
.leaflet-oldie .leaflet-control-zoom,
|
||||
.leaflet-oldie .leaflet-control-layers,
|
||||
.leaflet-oldie .leaflet-popup-content-wrapper,
|
||||
.leaflet-oldie .leaflet-popup-tip {
|
||||
border: 1px solid #999;
|
||||
}
|
||||
|
||||
|
||||
/* div icon */
|
||||
|
||||
.leaflet-div-icon {
|
||||
background: #fff;
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
||||
/* Tooltip */
|
||||
/* Base styles for the element that has a tooltip */
|
||||
.leaflet-tooltip {
|
||||
position: absolute;
|
||||
padding: 6px;
|
||||
background-color: #fff;
|
||||
border: 1px solid #fff;
|
||||
border-radius: 3px;
|
||||
color: #222;
|
||||
white-space: nowrap;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.4);
|
||||
}
|
||||
.leaflet-tooltip.leaflet-interactive {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
}
|
||||
.leaflet-tooltip-top:before,
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
border: 6px solid transparent;
|
||||
background: transparent;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* Directions */
|
||||
|
||||
.leaflet-tooltip-bottom {
|
||||
margin-top: 6px;
|
||||
}
|
||||
.leaflet-tooltip-top {
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before,
|
||||
.leaflet-tooltip-top:before {
|
||||
left: 50%;
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-top:before {
|
||||
bottom: 0;
|
||||
margin-bottom: -12px;
|
||||
border-top-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-bottom:before {
|
||||
top: 0;
|
||||
margin-top: -12px;
|
||||
margin-left: -6px;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-left {
|
||||
margin-left: -6px;
|
||||
}
|
||||
.leaflet-tooltip-right {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before,
|
||||
.leaflet-tooltip-right:before {
|
||||
top: 50%;
|
||||
margin-top: -6px;
|
||||
}
|
||||
.leaflet-tooltip-left:before {
|
||||
right: 0;
|
||||
margin-right: -12px;
|
||||
border-left-color: #fff;
|
||||
}
|
||||
.leaflet-tooltip-right:before {
|
||||
left: 0;
|
||||
margin-left: -12px;
|
||||
border-right-color: #fff;
|
||||
}
|
||||
|
||||
/* Printing */
|
||||
|
||||
@media print {
|
||||
/* Prevent printers from removing background-images of controls. */
|
||||
.leaflet-control {
|
||||
-webkit-print-color-adjust: exact;
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
}
|
||||
6
public/zmap/web/leaflet/leaflet.js
Normal file
6
public/zmap/web/leaflet/leaflet.js
Normal file
File diff suppressed because one or more lines are too long
BIN
public/zmap/web/leaflet/marker-icon-2x.png
Normal file
BIN
public/zmap/web/leaflet/marker-icon-2x.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
BIN
public/zmap/web/leaflet/marker-icon.png
Normal file
BIN
public/zmap/web/leaflet/marker-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/zmap/web/leaflet/marker-shadow.png
Normal file
BIN
public/zmap/web/leaflet/marker-shadow.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 618 B |
99
public/zmap/web/style.css
Normal file
99
public/zmap/web/style.css
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{--bg:#0c0c0f;--surface:#16161a;--surface2:#1e1e24;--border:#2a2a32;--text:#e8e8ed;--dim:#8888a0;--accent:#3b82f6;--accent2:#2563eb;--green:#22c55e;--red:#ef4444;--orange:#f59e0b;--radius:12px;--radius-sm:8px}
|
||||
html,body{height:100%;overflow:hidden}
|
||||
body{font-family:system-ui,-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}
|
||||
#map{position:fixed;top:0;left:0;right:0;bottom:0;z-index:0}
|
||||
|
||||
/* --- Top bar --- */
|
||||
.topbar{position:fixed;top:12px;left:12px;right:12px;z-index:1000;display:flex;gap:8px;align-items:center}
|
||||
.search-wrap{flex:1;position:relative}
|
||||
.search-wrap input{width:100%;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:12px 14px 12px 40px;color:var(--text);font-size:14px;outline:none;transition:border-color .2s}
|
||||
.search-wrap input:focus{border-color:var(--accent)}
|
||||
.search-wrap input::placeholder{color:var(--dim)}
|
||||
.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--dim);pointer-events:none}
|
||||
.search-results{position:absolute;top:calc(100% + 4px);left:0;right:0;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);overflow:hidden;display:none;max-height:320px;overflow-y:auto;box-shadow:0 8px 32px rgba(0,0,0,.5);z-index:2000}
|
||||
.search-results.open{display:block}
|
||||
.search-results .sr-item{padding:10px 14px;cursor:pointer;font-size:13px;border-bottom:1px solid var(--border);transition:background .15s}
|
||||
.search-results .sr-item:last-child{border-bottom:none}
|
||||
.search-results .sr-item:hover{background:var(--surface2)}
|
||||
.search-results .sr-item .sr-type{font-size:11px;color:var(--dim)}
|
||||
.icon-btn{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius-sm);width:44px;height:44px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:var(--text);transition:all .2s;flex-shrink:0}
|
||||
.icon-btn:hover{background:var(--surface2);border-color:var(--accent)}
|
||||
.icon-btn.active{background:var(--accent);border-color:var(--accent);color:#fff}
|
||||
|
||||
/* --- Side panel --- */
|
||||
.panel{position:fixed;top:68px;left:12px;z-index:1000;background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:0;width:340px;max-height:calc(100vh - 92px);overflow:hidden;display:none;box-shadow:0 8px 32px rgba(0,0,0,.4)}
|
||||
.panel.open{display:block}
|
||||
.panel-head{padding:16px 16px 0;display:flex;align-items:center;justify-content:space-between}
|
||||
.panel-head h2{font-size:16px;font-weight:700}
|
||||
.panel-close{background:none;border:none;color:var(--dim);cursor:pointer;padding:4px;font-size:20px}
|
||||
.panel-close:hover{color:var(--text)}
|
||||
.panel-body{padding:12px 16px 16px}
|
||||
|
||||
.point-row{display:flex;align-items:center;gap:8px;margin-bottom:8px}
|
||||
.point-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}
|
||||
.point-dot.from{background:var(--green)}
|
||||
.point-dot.to{background:var(--red)}
|
||||
.point-row input{flex:1;background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:8px 10px;color:var(--text);font-size:13px;outline:none}
|
||||
.point-row input:focus{border-color:var(--accent)}
|
||||
|
||||
.actions{display:flex;gap:6px;margin-top:4px}
|
||||
.btn{background:var(--accent);color:#fff;border:none;border-radius:var(--radius-sm);padding:10px 16px;font-size:13px;font-weight:600;cursor:pointer;transition:background .2s}
|
||||
.btn:hover{background:var(--accent2)}
|
||||
.btn:disabled{opacity:.35;cursor:default}
|
||||
.btn-outline{background:transparent;border:1px solid var(--border);color:var(--text)}
|
||||
.btn-outline:hover{background:var(--surface2);border-color:var(--accent)}
|
||||
.btn-danger{background:var(--red)}
|
||||
.btn-danger:hover{background:#dc2626}
|
||||
|
||||
/* Route info */
|
||||
.route-info{background:var(--bg);border-radius:var(--radius-sm);padding:12px;margin-top:10px;font-size:13px;line-height:1.7}
|
||||
.route-info .ri-main{font-size:18px;font-weight:700;margin-bottom:2px}
|
||||
.route-info .ri-sub{color:var(--dim);font-size:12px}
|
||||
.route-info .ri-stat{display:inline-block;margin-right:12px}
|
||||
.route-info .ri-stat b{color:var(--accent)}
|
||||
|
||||
/* Speed legend */
|
||||
.speed-legend{display:flex;gap:4px;margin-top:10px;flex-wrap:wrap}
|
||||
.speed-legend span{padding:4px 8px;border-radius:4px;font-size:11px;font-weight:700;letter-spacing:.3px}
|
||||
.sl-20{background:#ef4444;color:#fff}.sl-40{background:#f59e0b;color:#000}.sl-60{background:#eab308;color:#000}
|
||||
.sl-80{background:#22c55e;color:#000}.sl-100{background:#06b6d4;color:#000}.sl-120{background:#3b82f6;color:#fff}
|
||||
.sl-max{background:#8b5cf6;color:#fff}.sl-unk{background:#555;color:#aaa}
|
||||
.speed-bar{display:flex;height:8px;border-radius:4px;overflow:hidden;margin-top:6px}
|
||||
.speed-bar div{height:100%;transition:width .3s}
|
||||
.speed-distrib{display:flex;gap:8px;margin-top:4px;flex-wrap:wrap;font-size:11px;color:var(--dim)}
|
||||
.speed-distrib span{display:flex;align-items:center;gap:3px}
|
||||
.speed-distrib .sd-dot{width:8px;height:8px;border-radius:2px;flex-shrink:0}
|
||||
|
||||
.hint{margin-top:12px;font-size:11px;color:var(--dim);text-align:center}
|
||||
|
||||
/* --- GPS button --- */
|
||||
.gps-active .gps-dot{animation:pulse 1.5s infinite}
|
||||
@keyframes pulse{0%,100%{box-shadow:0 0 0 0 rgba(59,130,246,.5)}50%{box-shadow:0 0 0 8px rgba(59,130,246,0)}}
|
||||
|
||||
/* --- Loading --- */
|
||||
/* --- Leaflet overrides --- */
|
||||
.leaflet-control-zoom{border:none!important}
|
||||
.leaflet-control-zoom a{background:var(--surface)!important;color:var(--text)!important;border:1px solid var(--border)!important;border-radius:var(--radius-sm)!important;width:36px!important;height:36px!important;line-height:36px!important;font-size:18px!important}
|
||||
.leaflet-control-zoom a:hover{background:var(--surface2)!important}
|
||||
.leaflet-control-zoom{gap:0;margin-right:54px!important}
|
||||
|
||||
/* --- Marker popups --- */
|
||||
.leaflet-popup-content-wrapper{background:var(--surface)!important;color:var(--text)!important;border-radius:var(--radius-sm)!important;border:1px solid var(--border)!important;box-shadow:0 4px 16px rgba(0,0,0,.4)!important}
|
||||
.leaflet-popup-tip{background:var(--surface)!important;border:1px solid var(--border)!important}
|
||||
.leaflet-popup-content{font-size:13px;margin:8px 12px!important}
|
||||
|
||||
.gps-dot{width:14px;height:14px;background:var(--accent);border:3px solid #fff;border-radius:50%;box-shadow:0 0 8px rgba(59,130,246,.6)}
|
||||
.station-dot{width:10px;height:10px;background:var(--green);border:2px solid #fff;border-radius:50%}
|
||||
.loading{display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:2000;background:var(--surface);padding:14px 22px;border-radius:var(--radius);font-size:13px;border:1px solid var(--border);box-shadow:0 8px 32px rgba(0,0,0,.5);align-items:center;gap:8px}
|
||||
.loading.show{display:flex}
|
||||
.spinner{width:16px;height:16px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:spin .6s linear infinite}
|
||||
@keyframes spin{to{transform:rotate(360deg)}}
|
||||
|
||||
/* --- Responsive --- */
|
||||
@media(max-width:600px){
|
||||
.topbar{top:8px;left:8px;right:8px}
|
||||
.panel{left:8px;right:8px;width:auto;top:60px;max-height:55vh}
|
||||
.map-ctrls.top{top:8px}
|
||||
.map-ctrls.bottom{bottom:8px}
|
||||
}
|
||||
Loading…
Reference in a new issue