diff --git a/memory/2026-05-03.md b/memory/2026-05-03.md index cc56d16..7e28b2c 100644 --- a/memory/2026-05-03.md +++ b/memory/2026-05-03.md @@ -78,3 +78,15 @@ **openclaw.json изменения:** - `agents.dev.model.primary` = `vibecode/claude-opus-4.7` + +## Линейка — финальный фикс (21:45 UTC) + +Проблема: плашки смещены влево от кружков на скрине Славы. +Причина: `anchor: 'bottom'` для labelMarker крепит нижний-левый угол, не центр. + +Решение (задеплоено): +- Кружок: отдельный маркер, `anchor: 'center'`, строго 10×10px → линия проходит точно через него +- Плашка: отдельный маркер, `anchor: 'center'`, `offset: [0, -20]` → висит ровно над кружком по центру +- Два маркера на точку: `dotMarker` + `labelMarker`, оба в `rulerMarkers[]` + +Деплой: `docker compose up -d --build` на сервере 82.22.50.71, контейнер `prototype-enduro-trails-1` diff --git a/tasks/enduro-trails/prototype/static/app.js b/tasks/enduro-trails/prototype/static/app.js index 3925fe5..eec0c57 100644 --- a/tasks/enduro-trails/prototype/static/app.js +++ b/tasks/enduro-trails/prototype/static/app.js @@ -86,7 +86,6 @@ function toggleLayer(group) { // ─── Map init ───────────────────────────────────────────────────────────────── async function initMap() { - // Определяем base path для работы как на корне, так и под /enduro/ const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; const tileBase = window.location.origin + basePath; const style = await fetch(basePath + '/style.json').then(r => r.json()); @@ -107,13 +106,12 @@ async function initMap() { map.addControl(new maplibregl.ScaleControl({ unit: 'metric' }), 'bottom-right'); map.addControl(new maplibregl.FullscreenControl(), 'top-left'); - // ─── Loading state ──────────────────────────────────────────────────────── map.on('load', () => { document.getElementById('loading').classList.remove('visible'); checkDataAvailability(); initRouteClicks(map); - initSearch(); initRulerClicks(map); + initSearch(); }); map.on('error', (e) => { @@ -125,7 +123,6 @@ async function initMap() { document.getElementById('loading').classList.remove('visible'); }, 15000); - // ─── Stats bar ──────────────────────────────────────────────────────────── map.on('zoom', () => { document.getElementById('zoom-val').textContent = map.getZoom().toFixed(1); }); @@ -136,7 +133,6 @@ async function initMap() { `${lat.toFixed(4)}, ${lng.toFixed(4)}`; }); - // ─── Popups ─────────────────────────────────────────────────────────────── const popup = new maplibregl.Popup({ closeButton: true, closeOnClick: false, @@ -161,7 +157,6 @@ async function initMap() { return labels[t] || t; } - // Клик по грунтовкам ['trails-track', 'trails-path-bridleway', 'trails-asphalt'].forEach(layerId => { map.on('click', layerId, (e) => { const props = e.features[0].properties; @@ -179,7 +174,6 @@ async function initMap() { map.on('mouseleave', layerId, () => { map.getCanvas().style.cursor = ''; }); }); - // Клик по POI map.on('click', 'poi-circles', (e) => { const props = e.features[0].properties; const html = ` @@ -191,7 +185,6 @@ async function initMap() { map.on('mouseenter', 'poi-circles', () => { map.getCanvas().style.cursor = 'pointer'; }); map.on('mouseleave', 'poi-circles', () => { map.getCanvas().style.cursor = ''; }); - // Закрыть popup при клике на пустое место map.on('click', (e) => { const features = map.queryRenderedFeatures(e.point, { layers: ['trails-track', 'trails-path-bridleway', 'trails-asphalt', 'poi-circles'], @@ -202,6 +195,7 @@ async function initMap() { async function checkDataAvailability() { try { + const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; const resp = await fetch(basePath + '/api/health'); const data = await resp.json(); if (!data.db_exists) { @@ -212,12 +206,11 @@ async function checkDataAvailability() { } } -// ─── Роутинг ────────────────────────────────────────────────────────────────────────────── +// ─── Роутинг ────────────────────────────────────────────────────────────────── let routeMode = false; let routeStart = null; let routeEnd = null; let routeMarkers = []; -let routeLayer = null; function toggleRouteMode() { routeMode = !routeMode; @@ -252,7 +245,6 @@ function clearRoute() { async function buildRoute() { const map = window._map; document.getElementById('route-status').textContent = '⏳ Строю маршрут...'; - const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; try { const resp = await fetch( @@ -260,8 +252,6 @@ async function buildRoute() { ); if (!resp.ok) throw new Error('Маршрут не найден'); const data = await resp.json(); - - // Рисуем линию if (map.getSource('route')) { map.getSource('route').setData(data); } else { @@ -270,16 +260,10 @@ async function buildRoute() { id: 'route-line', type: 'line', source: 'route', - paint: { - 'line-color': '#0066ff', - 'line-width': 4, - 'line-opacity': 0.85, - }, + paint: { 'line-color': '#0066ff', 'line-width': 4, 'line-opacity': 0.85 }, layout: { 'line-cap': 'round', 'line-join': 'round' } }); } - - // Показываем статистику const p = data.properties; document.getElementById('route-distance').textContent = `${p.distance_km} км`; document.getElementById('route-duration').textContent = `~${p.duration_min} мин`; @@ -290,12 +274,10 @@ async function buildRoute() { } } -// Клик на карте в режиме роутинга function initRouteClicks(map) { map.on('click', (e) => { if (!routeMode) return; const { lng, lat } = e.lngLat; - if (!routeStart) { routeStart = [lng, lat]; const el = document.createElement('div'); @@ -312,34 +294,23 @@ function initRouteClicks(map) { }); } -// ─── Поиск (Nominatim) ──────────────────────────────────────────────────────────────── +// ─── Поиск (Nominatim) ──────────────────────────────────────────────────────── let searchTimeout = null; function initSearch() { const input = document.getElementById('search-input'); const results = document.getElementById('search-results'); - input.addEventListener('input', () => { clearTimeout(searchTimeout); const q = input.value.trim(); - if (q.length < 2) { - results.style.display = 'none'; - return; - } + if (q.length < 2) { results.style.display = 'none'; return; } searchTimeout = setTimeout(() => doSearch(q), 400); }); - input.addEventListener('keydown', (e) => { - if (e.key === 'Escape') { - results.style.display = 'none'; - input.blur(); - } + if (e.key === 'Escape') { results.style.display = 'none'; input.blur(); } }); - document.addEventListener('click', (e) => { - if (!e.target.closest('#search-box')) { - results.style.display = 'none'; - } + if (!e.target.closest('#search-box')) results.style.display = 'none'; }); } @@ -347,21 +318,18 @@ async function doSearch(query) { const results = document.getElementById('search-results'); results.innerHTML = '