auto-sync: 2026-05-03 13:50:01
This commit is contained in:
@@ -332,4 +332,4 @@ if os.path.exists(STATIC_DIR):
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"==> Enduro Trails API DB={DATA_PATH} Port={PORT}")
|
||||
uvicorn.run(app, host="0.0.0.0", port=PORT)
|
||||
uvicorn.run("app:app", host="0.0.0.0", port=PORT, workers=4)
|
||||
|
||||
@@ -211,3 +211,70 @@ body {
|
||||
font-family: monospace;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* ─── Кастомные кнопки управления ────────────────────────────────────────── */
|
||||
.custom-map-ctrl {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
#map-controls-br {
|
||||
bottom: 40px;
|
||||
}
|
||||
|
||||
.map-ctrl-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: #fff;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.15);
|
||||
transition: background 0.15s;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.map-ctrl-btn:hover { background: #f5f5f5; }
|
||||
.map-ctrl-btn.active { background: #ff6600; border-color: #ff6600; }
|
||||
|
||||
/* ─── Маркер текущего местоположения ─────────────────────────────────────── */
|
||||
.my-location-marker {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.my-location-dot {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 14px; height: 14px;
|
||||
background: #ff6600;
|
||||
border: 2px solid #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 4px rgba(0,0,0,0.3);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.my-location-pulse {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 30px; height: 30px;
|
||||
background: rgba(255, 102, 0, 0.25);
|
||||
border-radius: 50%;
|
||||
animation: location-pulse 1.5s ease-out infinite;
|
||||
}
|
||||
|
||||
@keyframes location-pulse {
|
||||
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 1; }
|
||||
100% { transform: translate(-50%, -50%) scale(1.5); opacity: 0; }
|
||||
}
|
||||
|
||||
59
tasks/enduro-trails/prototype/static/app.js
vendored
59
tasks/enduro-trails/prototype/static/app.js
vendored
@@ -1,3 +1,62 @@
|
||||
// ─── Компас ───────────────────────────────────────────────────────────────────
|
||||
let compassLocked = false;
|
||||
|
||||
function toggleCompass() {
|
||||
const map = window._map;
|
||||
if (!map) return;
|
||||
const btn = document.getElementById('btn-compass');
|
||||
compassLocked = !compassLocked;
|
||||
if (compassLocked) {
|
||||
map.rotateTo(0, { duration: 300 });
|
||||
map.dragRotate.disable();
|
||||
map.touchZoomRotate.disableRotation();
|
||||
btn.textContent = '⬆️';
|
||||
btn.title = 'Север вверху (нажми для свободного вращения)';
|
||||
btn.classList.add('active');
|
||||
} else {
|
||||
map.dragRotate.enable();
|
||||
map.touchZoomRotate.enableRotation();
|
||||
btn.textContent = '🧭';
|
||||
btn.title = 'Свободное вращение';
|
||||
btn.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Геолокация ───────────────────────────────────────────────────────────────
|
||||
let locationMarker = null;
|
||||
|
||||
function locateMe() {
|
||||
if (!navigator.geolocation) {
|
||||
alert('Геолокация недоступна в этом браузере');
|
||||
return;
|
||||
}
|
||||
const btn = document.getElementById('btn-locate');
|
||||
btn.textContent = '⏳';
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
const { longitude, latitude } = pos.coords;
|
||||
const map = window._map;
|
||||
btn.textContent = '📍';
|
||||
map.flyTo({ center: [longitude, latitude], zoom: 13, duration: 800 });
|
||||
if (locationMarker) {
|
||||
locationMarker.setLngLat([longitude, latitude]);
|
||||
} else {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'my-location-marker';
|
||||
el.innerHTML = '<div class="my-location-dot"></div><div class="my-location-pulse"></div>';
|
||||
locationMarker = new maplibregl.Marker({ element: el, anchor: 'center' })
|
||||
.setLngLat([longitude, latitude])
|
||||
.addTo(map);
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
btn.textContent = '📍';
|
||||
alert('Не удалось определить местоположение: ' + err.message);
|
||||
},
|
||||
{ enableHighAccuracy: true, timeout: 10000 }
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Layer visibility state ───────────────────────────────────────────────────
|
||||
const layerState = {
|
||||
tracks: true,
|
||||
|
||||
@@ -93,6 +93,11 @@
|
||||
</div>
|
||||
|
||||
<div id="stats">Zoom: <span id="zoom-val">7</span> | Координаты: <span id="coords-val">—</span></div>
|
||||
|
||||
<div id="map-controls-br" class="custom-map-ctrl">
|
||||
<button id="btn-compass" class="map-ctrl-btn" title="Свободное вращение" onclick="toggleCompass()">🧭</button>
|
||||
<button id="btn-locate" class="map-ctrl-btn" title="Моё местоположение" onclick="locateMe()">📍</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/app.js" defer></script>
|
||||
|
||||
Reference in New Issue
Block a user