# 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.)