diff --git a/tasks/enduro-trails/PROJECT.md b/tasks/enduro-trails/PROJECT.md index e280351..21581a6 100644 --- a/tasks/enduro-trails/PROJECT.md +++ b/tasks/enduro-trails/PROJECT.md @@ -100,6 +100,8 @@ docker restart prototype-enduro-trails-1 | F-17 | PWA + офлайн | Service Worker, MBTiles, GPS-трекинг | ⏳ Бэклог | 7 | | F-18 | Светлая карта | Создать `style-light.json` — светлый стиль карты для светлой темы. Сейчас при светлой теме карта остаётся тёмной (`style-light.json` отсутствует) | ⏳ Бэклог | 5.1 | | F-19 | Иконка колеса + спиннер | Заменить иконку колеса в мини-баре на нормальную мотокросс (спицы, кноблинг). Спиннер в основном листе маршрута пока строится | ⏳ Бэклог | 5.1 | +| F-20 | Линейка: перетаскивание точек | Возможность перетащить уже поставленную точку линейки на новое место | ⏳ Бэклог | 5.x | +| F-21 | Линейка: сохранение между сессиями | Сохранять точки линейки в localStorage, восстанавливать при перезагрузке | ⏳ Бэклог | 5.x | --- diff --git a/tasks/enduro-trails/prototype/static/app.css b/tasks/enduro-trails/prototype/static/app.css index 56367fd..87017cf 100644 --- a/tasks/enduro-trails/prototype/static/app.css +++ b/tasks/enduro-trails/prototype/static/app.css @@ -441,7 +441,9 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; } #ruler-info { position: fixed; top: calc(max(env(safe-area-inset-top,0px),12px) + 58px); - left: 12px; right: 12px; + left: 12px; + width: fit-content; + max-width: 320px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; @@ -454,22 +456,49 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; } #ruler-info #ruler-dist { flex: 1; } .ruler-action-btn { flex-shrink: 0; - height: 28px; - padding: 0 10px; + height: 32px; + min-width: 32px; + padding: 4px 10px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; color: var(--text); - font-size: 12px; + font-size: 13px; font-weight: 600; cursor: pointer; white-space: nowrap; + display: flex; + align-items: center; + justify-content: center; + -webkit-tap-highlight-color: transparent; } .ruler-action-btn--danger { color: var(--danger, #e05252); border-color: var(--danger, #e05252); + font-size: 16px; + padding: 4px 8px; } +/* ── Ruler toast hint ────────────────────────── */ +#ruler-toast { + position: fixed; + top: calc(max(env(safe-area-inset-top,0px),12px) + 100px); + left: 50%; + transform: translateX(-50%); + background: rgba(20,20,20,0.82); + color: #fff; + font-size: 13px; + font-weight: 600; + padding: 8px 16px; + border-radius: 20px; + z-index: 210; + pointer-events: none; + opacity: 0; + transition: opacity 0.3s; + white-space: nowrap; +} +#ruler-toast.visible { opacity: 1; } + /* ── Fix: MapLibre markers must stay absolute ────── */ .maplibregl-marker { position: absolute !important; @@ -510,7 +539,7 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; } .bottom-sheet.swiping { transition: none; } #map-controls-r { right: 12px; bottom: 12px; } #sheet-backdrop { display: none; } - #ruler-info { left: 84px; } + #ruler-info { left: 84px; max-width: 320px; } } /* ═══════════════════════════════════════════════════ diff --git a/tasks/enduro-trails/prototype/static/app.js b/tasks/enduro-trails/prototype/static/app.js index 29bf77a..41608f2 100644 --- a/tasks/enduro-trails/prototype/static/app.js +++ b/tasks/enduro-trails/prototype/static/app.js @@ -1631,6 +1631,8 @@ function toggleRuler() { window._map.getCanvas().style.cursor = 'crosshair'; clearRuler(); document.getElementById('ruler-info').classList.add('visible'); + // Fix 4: Show toast hint on activation + showRulerToast(); } else { btn.classList.remove('active'); window._map.getCanvas().style.cursor = ''; @@ -1640,6 +1642,14 @@ function toggleRuler() { updateMapModeClass(); } +// Fix 4: Toast hint helper +function showRulerToast() { + const toast = document.getElementById('ruler-toast'); + if (!toast) return; + toast.classList.add('visible'); + setTimeout(() => toast.classList.remove('visible'), 3000); +} + // Exit ruler mode without clearing points/markers ("Завершить") function exitRulerMode() { if (!rulerMode) return; @@ -1702,11 +1712,19 @@ function updateRulerLabels() { rulerTotal = 0; for (let i = 0; i < rulerMarkers.length; i++) { const markerEl = rulerMarkers[i].getElement(); + const dot = markerEl.querySelector('.ruler-dot'); const label = markerEl.querySelector('.ruler-label'); const btn = markerEl.querySelector('.ruler-remove-btn'); const labelText = label ? label.querySelector('span') : null; + + // Fix 5: Update dot color for first point + if (dot) { + const dotColor = i === 0 ? '#2EA043' : '#0088ff'; + dot.style.background = dotColor; + } + if (i === 0) { - if (labelText) labelText.textContent = ''; + if (labelText) labelText.textContent = 'Старт'; } else { const segDist = haversineKm(rulerPoints[i - 1], rulerPoints[i]); rulerTotal += segDist; @@ -1738,34 +1756,41 @@ function addRulerPoint(lngLat) { rulerTotal += segDist; } + // Hide toast on first tap + if (idx === 0) { + const toast = document.getElementById('ruler-toast'); + if (toast) toast.classList.remove('visible'); + } + // Wrapper element for dot + label row const wrapper = document.createElement('div'); wrapper.style.cssText = 'position:relative;display:flex;flex-direction:column;align-items:center;'; - // Dot + // Dot - Fix 5: first point green const dot = document.createElement('div'); dot.className = 'ruler-dot'; - dot.style.cssText = 'width:10px;height:10px;background:#0088ff;border:2px solid #fff;border-radius:50%;box-shadow:0 0 4px rgba(0,0,0,0.3);display:block;flex-shrink:0;'; + const dotColor = idx === 0 ? '#2EA043' : '#0088ff'; + dot.style.cssText = `width:10px;height:10px;background:${dotColor};border:2px solid #fff;border-radius:50%;box-shadow:0 0 4px rgba(0,0,0,0.3);display:block;flex-shrink:0;`; // Label row: [distance text][× button] in one line const label = document.createElement('div'); label.className = 'ruler-label'; - label.style.cssText = 'margin-top:3px;display:flex;align-items:center;gap:2px;background:rgba(20,20,20,0.75);color:#fff;font-size:10px;padding:1px 4px;border-radius:3px;white-space:nowrap;'; + label.style.cssText = 'margin-top:3px;display:flex;align-items:center;gap:4px;background:rgba(20,20,20,0.75);color:#fff;font-size:10px;padding:2px 6px;border-radius:3px;white-space:nowrap;'; const labelText = document.createElement('span'); if (idx === 0) { - labelText.textContent = ''; + labelText.textContent = 'Старт'; } else { labelText.textContent = segDist >= 1 ? segDist.toFixed(1) + ' км' : Math.round(segDist * 1000) + ' м'; } - // Remove button × inline with label + // Remove button × inline with label - Fix 1: bigger tap target const btn = document.createElement('span'); btn.className = 'ruler-remove-btn'; btn.textContent = '×'; - btn.style.cssText = 'cursor:pointer;font-size:11px;line-height:1;opacity:0.85;padding-left:2px;'; + btn.style.cssText = 'cursor:pointer;font-size:16px;line-height:1;opacity:0.85;padding:4px 8px;min-width:32px;min-height:32px;display:flex;align-items:center;justify-content:center;margin:-2px -6px -2px 0;'; btn.onclick = (e) => { e.stopPropagation(); removeRulerPoint(idx); }; label.appendChild(labelText); @@ -1791,12 +1816,19 @@ function initRulerClicks(map) { e.preventDefault(); exitRulerMode(); }); - // Fix 3: tap on ruler line restores panel + // Fix 2 & 6: tap on ruler line shows panel AND resumes ruler mode map.on('click', 'ruler-line', (e) => { - if (rulerMode) return; // already active + e.originalEvent.stopPropagation(); if (rulerPoints.length > 0) { + // Fix 6: Resume ruler mode + if (!rulerMode) { + rulerMode = true; + const btn = document.getElementById('tb-ruler'); + btn.classList.add('active'); + window._map.getCanvas().style.cursor = 'crosshair'; + updateMapModeClass(); + } document.getElementById('ruler-info').classList.add('visible'); - e.preventDefault(); } }); } diff --git a/tasks/enduro-trails/prototype/static/index.html b/tasks/enduro-trails/prototype/static/index.html index 88e0b10..0313058 100644 --- a/tasks/enduro-trails/prototype/static/index.html +++ b/tasks/enduro-trails/prototype/static/index.html @@ -26,6 +26,8 @@ + +
Тапни на карту чтобы добавить точку
⚠️ База данных недоступна