# Dev Task: Enduro Trails — UX маршрута + индикатор загрузки
**Приоритет:** HIGH
**Проект:** enduro-trails
**Дата:** 2026-05-05
---
## Контекст
Текущий UX маршрута неудобен:
1. После выбора двух точек автоматически открывается основной sheet — карта скрывается
2. «Добавить точку» в основном sheet не скрывает sheet — карта не видна для тапа
3. Основной sheet открывается сразу, хотя достаточно мини-бара
4. Нет индикатора что маршрут строится — пользователь не понимает что происходит
**Файлы:** ТОЛЬКО `app.js`, `app.css`, `index.html` (бэкенд НЕ трогать)
---
## Задача 1: Не открывать основной sheet автоматически
### Текущее поведение
В `initRouteClicks` при добавлении второй точки вызывается `buildRoute()` — который внутри ничего не открывает, НО `toggleRouteMode()` при входе в режим уже открыл sheet через `openSheet('sheet-route')`.
### Нужное поведение
- Вход в режим маршрута (`toggleRouteMode()`) → **НЕ открывать** `sheet-route` автоматически
- Sheet открывается ТОЛЬКО по явному тапу на мини-бар (уже работает через `showMiniRouteSheet`)
- Маршрут строится в фоне, карта остаётся видна, мини-бар показывает прогресс
### Изменения в `toggleRouteMode()`:
```js
function toggleRouteMode() {
const btn = document.getElementById('tb-route');
if (routeMode) {
// Если sheet открыт — закрыть sheet (но не выходить из режима)
const sheet = document.getElementById('sheet-route');
if (sheet && sheet.classList.contains('open')) {
closeSheet('sheet-route');
return;
}
// Если sheet закрыт — выйти из режима и сбросить маршрут
routeMode = false;
btn.classList.remove('active');
clearRoute();
window._map.getCanvas().style.cursor = '';
} else {
// Войти в режим маршрута — НЕ открывать sheet
deactivateAllModes();
routeMode = true;
btn.classList.add('active');
clearRoute();
window._map.getCanvas().style.cursor = 'crosshair';
// sheet НЕ открываем — пользователь сам тапнет на мини-бар
}
updateMapModeClass();
}
```
---
## Задача 2: «Добавить точку» скрывает sheet
### В `addWaypointMode()`:
```js
function addWaypointMode() {
if (routeWaypoints.length >= 10) return;
if (!routeMode) {
routeMode = true;
document.getElementById('tb-route').classList.add('active');
updateMapModeClass();
}
addingWaypoint = true;
window._map.getCanvas().style.cursor = 'crosshair';
// Скрыть основной sheet чтобы карта была видна
closeSheet('sheet-route');
// Показать подсказку в мини-баре
const statsEl = document.getElementById('mini-stats');
if (statsEl) statsEl.textContent = 'Тапни на карте для добавления точки';
document.getElementById('sheet-route-mini').classList.add('visible');
}
```
---
## Задача 3: После построения маршрута — только мини-бар
### В `buildRoute()` — убрать автоматическое открытие sheet, показывать только мини-бар:
```js
async function buildRoute() {
if (routeWaypoints.length < 2) return;
// Показать мини-бар с индикатором загрузки
showMiniRouteLoading();
// Закрыть основной sheet если открыт
closeSheet('sheet-route');
document.getElementById('route-status').textContent = 'Строю маршрут...';
try {
const resp = await fetch(...);
// ... существующая логика ...
drawRouteResults(routeResults, 0);
// После успеха — показать мини-бар с результатом (НЕ открывать sheet)
showMiniRouteSheet();
hideMiniRouteLoading();
document.getElementById('route-status').textContent = `${routeResults.length} маршрут(ов)`;
} catch(e) {
hideMiniRouteLoading();
document.getElementById('route-status').textContent = '❌ ' + e.message;
// Показать ошибку в мини-баре
const statsEl = document.getElementById('mini-stats');
if (statsEl) statsEl.textContent = '❌ ' + e.message;
}
}
```
---
## Задача 4: Индикатор загрузки — колесо мотоцикла
### Анимация
В мини-баре (`#sheet-route-mini`) показывать SVG колесо мотоцикла которое крутится пока строится маршрут.
**SVG колесо (кросс/эндуро стиль — спицы, грунтозацепы):**
```svg
```
**CSS анимация:**
```css
.moto-wheel {
width: 32px; height: 32px;
animation: wheelSpin 0.8s linear infinite;
display: none;
flex-shrink: 0;
}
.moto-wheel.spinning {
display: block;
}
@keyframes wheelSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
```
### Где показывать
В `#sheet-route-mini` добавить элемент колеса рядом с `#mini-stats`:
```html
```
**Функции управления:**
```js
function showMiniRouteLoading() {
const wheel = document.getElementById('mini-wheel');
const statsEl = document.getElementById('mini-stats');
if (wheel) wheel.classList.add('spinning');
if (statsEl) statsEl.textContent = 'Строю маршрут...';
document.getElementById('sheet-route-mini').classList.add('visible');
}
function hideMiniRouteLoading() {
const wheel = document.getElementById('mini-wheel');
if (wheel) wheel.classList.remove('spinning');
}
```
---
## Итоговый UX-флоу
```
Тап на 🗺 → routeMode=true, cursor=crosshair, sheet НЕ открывается
Тап на карте (точка A) → маркер A, cursor=crosshair
Тап на карте (точка B) → маркер B, мини-бар появляется с крутящимся колесом
Маршрут построен → колесо исчезает, мини-бар показывает "120 км · 87% грунт"
Тап на мини-бар → открывается основной sheet с деталями
Тап «Добавить точку» в sheet → sheet скрывается, карта видна, cursor=crosshair
Тап на карте → точка добавлена, маршрут перестраивается (колесо в мини-баре)
```
---
## Деплой
```
/tmp/deploy_static.js
SSH: host=82.22.50.71, port=22, user=slin, pass=motoZ@yaz2010
Health: curl -s http://mva154:5558/api/health
```
## Ограничения
- НЕ трогать `app.py`
- Без npm-зависимостей
- Сохранить всю существующую логику (drag-and-drop, geocoding, GPX, etc.)