Files
wiki/tasks/enduro-trails/BRD_PHASE5.md
2026-05-06 10:10:01 +03:00

15 KiB
Raw Blame History

BRD: Enduro Trails — Фаза 5 «Редизайн»

Версия: 2.1
Дата: 2026-05-06
Автор: Стрим 🌊
Статус: Реализовано (с дополнениями)


1. Контекст и проблема

Фазы 34 дали полный функционал (роутинг, разведка, связка, красивый маршрут), но UI был dev-прототипом: белый фон, emoji-иконки, панели перекрывали карту, не работало на мобиле в перчатках.

2. Цель

Мобильный UI уровня onX Offroad / Locus Map — тёмный, эндуро-стильный, thumb-friendly. Карта — главный герой. UI — минималистичный HUD.

3. Что реализовано в Фазе 5

Дизайн-система

  • Две темы: тёмная (body.theme-dark) и светлая (body.theme-light)
  • Авто-режим: SunCalc (CDN) определяет восход/закат по геолокации (fallback: Москва 55.75°N)
  • Три режима переключателя: Авто → Светлая → Тёмная → Авто (циклически)
  • Сохранение: localStorage — режим темы сохраняется между сессиями
  • CSS vars: все компоненты используют var(--bg), var(--surface), var(--accent) и т.д.
  • Карта: map.setStyle() переключает dark/light style.json при смене темы; слои пересоздаются через map.on('style.load', onMapStyleLoad)

Layout и компоненты

  • Search barposition: fixed, top, safe-area, все цвета через CSS vars
  • Bottom toolbar — 6 режимов (Маршрут, Связка, Красивый, Разведка, Линейка, Метка), SVG Lucide-иконки, активная кнопка = оранжевый фон + белый текст
  • Bottom sheets — 4 шита (route/recon/scenic/link), slide-up анимация, drag handle, safe-area padding
  • Map controls — компас + геолокация, справа
  • Skeleton loading — shimmer-анимация в карточках маршрутов пока идёт запрос

Анимации

  • Sheet slide: transform: translateY + cubic-bezier(0.32, 0, 0.15, 1), 300ms
  • Кнопки: scale(0.94) при tap
  • Карточки маршрутов: cardFadeIn stagger (0/60/120/180/240ms)
  • Маркеры: markerPopIn scale появление

Свайп вниз для закрытия sheet

  • initSheetSwipe() — touch-обработка на всех .bottom-sheet
  • Свайп > 80px → closeSheet()
  • Во время свайпа: translateY(dy) для визуального feedback

Адаптив — десктоп

  • @media (min-width: 768px) — toolbar вертикально слева, sheet с max-width 400px

Промежуточные точки — drag-and-drop (добавлено в ходе фазы)

  • Grip-иконка (6 кружков, Lucide-style) в каждом wl-item
  • _initWaypointDragHandles(list) — единая логика для touch и mouse
  • Touch (мобиль): touchstart/touchmove/touchend
  • Mouse (десктоп): mousedown → document mousemove/mouseup
  • Визуальный feedback: dragging (opacity 0.4), drag-over-top/drag-over-bottom (border accent)
  • После drop: rebuildWaypointMarkers() + renderWaypointsList() + debounceBuildRoute()

Кнопка «Скачать GPX» (изменение в ходе фазы)

  • Кнопка «Поделиться» (share dialog с Telegram/WhatsApp) убрана по запросу Славы
  • Заменена на прямую кнопку «Скачать GPX» с download-иконкой (стрелка вниз)
  • onclick="downloadGPX()" — без промежуточных диалогов

Расстояние по маршруту между точками (добавлено 05.05.2026)

  • Функция getRouteSegmentDistances() — snap каждого waypoint к ближайшей точке геометрии OSRM

  • Первая точка → snapIdx[0] = 0, последняя → snapIdx[n-1] — гарантирует покрытие всей геометрии

  • Сумма сегментов масштабируется к route.distance_m — точное совпадение (diff=0)

  • Обновляется при смене варианта (selectRoute, selectMiniRoute)

  • Обновляется после построения маршрута (drawRouteResultsrenderWaypointsList)

  • Fallback на haversine по прямой если маршрут ещё не построен

  • Форматирование toFixed(1) везде (сегменты, карточки вариантов, мини-бар)

  • #mini-route-bar — компактная полоска поверх карты когда sheet свёрнут

  • Показывает активный маршрут (дистанция, % грунт)

  • Кнопки: добавить точку, развернуть sheet

  • miniAddWaypoint() — теперь корректно устанавливает routeMode = true перед addingWaypoint = true

Поиск точек маршрута (добавлено 05.05.2026)

  • Убран верхний search bar, поиск перенесён прямо в список waypoints
  • Кнопка-лупа в каждом wl-item → inline Nominatim поиск
  • btn-theme перенесён в map-controls-r

Метки (named markers, добавлено 05.05.2026)

  • 6 типов меток: 🚩 Флаг, 🏕 Лагерь, 🔧 Ремонт, Заправка, 💧 Вода, 📍 Точка
  • Сохраняются в localStorage (лимит 50)
  • Попап с координатами и кнопками: → Точка A, → Точка B, 🗑 Удалить
  • Баг исправлен: removeMarker() теперь явно закрывает попап перед удалением маркера

Линейка — полный UX редизайн (06.05.2026)

Маркеры:

  • Первая точка — зелёная (#2EA043) с подписью «Старт»
  • Остальные точки — синие с расстоянием сегмента от предыдущей точки
  • Крестик × (кнопка button) справа от расстояния в одной строке — удаляет точку
  • anchor: 'center' — маркер точно привязан к точке тапа
  • Label абсолютно позиционирован ниже dot

Панель общего расстояния (#ruler-info):

  • Компактная: width: fit-content, max-width: 320px, прижата к левому краю
  • Показывается только после добавления первой точки
  • Кнопка ✓ Завершить — выход из режима рисования, линейка остаётся на карте
  • Кнопка (deleteRuler()) — удаляет всю линейку

Логика кнопки тулбара (tb-ruler):

  • Нет линейки → войти в режим рисования + показать toast-подсказку
  • Режим активен → выйти из режима, скрыть линейку (точки сохраняются в памяти)
  • Линейка скрыта → показать линейку и войти в режим рисования

Toast-подсказка:

  • «Тапни на карту чтобы добавить точку» — появляется при входе в режим, исчезает через 3 сек или при первом тапе

Восстановление панели:

  • Тап на линию (ruler-line) → показать панель + возобновить режим рисования
  • Тап на маркер линейки → то же самое

updateRulerLabels() — пересчитывает все расстояния и цвета точек с нуля после удаления любой точки


4. Баги исправленные в ходе фазы (включая 06.05.2026)

Баг Причина Фикс
Кнопка «Добавить точку» не работала addWaypointMode() не устанавливала routeMode = true Добавлена проверка и установка routeMode
Web Share API не работал на HTTP API требует HTTPS Убран share dialog, оставлено только скачивание GPX
Drag-and-drop не работал на десктопе Только touch-события Добавлены mouse-события (mousedown/mousemove/mouseup)
CSS классы диалога не совпадали с JS Dev добавил JS с одними классами, CSS с другими Унифицированы, затем диалог полностью убран
Расстояние между точками по прямой renderWaypointsList вызывался до построения маршрута, routeResults был пуст Добавлен renderWaypointsList() после drawRouteResults()
Расстояние не обновлялось при смене варианта selectRoute/selectMiniRoute не вызывали renderWaypointsList Добавлен renderWaypointsList() в обе функции
Деплой статики не работал deploy_app2.js копирует только app.py; образ перезаписывает статику при рестарте SFTP → docker restartdocker cp (порядок важен!)
Крестик × линейки не срабатывал Слишком маленькая тапабельная зона (span 11px) Заменён на button с min-width/height: 32px, font-size: 16px
Тап на линию не показывал панель Общий map.on('click') перехватывал событие раньше e.originalEvent.stopPropagation() на ruler-line click
Кнопка ✕ в панели не удаляла линейку toggleRuler() при rulerMode=false + точки есть только показывал панель Создана отдельная функция deleteRuler()
Попап метки зависал при удалении removeMarker() не закрывал попап перед удалением Добавлен явный popup.remove() перед marker.remove()
#ruler-info показывался до добавления точек Панель открывалась при входе в режим Панель показывается только после первой точки

5. Definition of Done — статус

Критерий Статус
Все P0 тест-кейсы на iPhone SE (375px)
Bottom sheet плавно открывается/закрывается
Toolbar: все 6 режимов, min 48px tap target
Тема: авто (SunCalc) + ручной переключатель
SVG иконки (Lucide), никаких emoji в UI
Карта видна при активных панелях
Safe area корректная
Десктоп: боковая панель, не ломается layout
GPX скачивание работает
Drag-and-drop точек маршрута (touch + mouse)
Расстояние по маршруту между точками (сумма = route.distance_m)
Обновление расстояний при смене варианта маршрута
Деплой + health check OK
Линейка: расстояние сегмента под каждым маркером
Линейка: крестик удаления точки с нормальной тапабельной зоной
Линейка: панель компактная, fit-content
Линейка: кнопки Завершить / Удалить всё
Линейка: toast-подсказка при входе в режим
Линейка: первая точка зелёная «Старт»
Линейка: тап на линию/маркер возобновляет режим
Линейка: логика кнопки тулбара (скрыть/показать/рисовать)
Метки: 6 типов, сохранение в localStorage
Метки: попап с кнопками A/B/Удалить
Поиск точек маршрута inline (Nominatim)

6. Технический стек фронтенда

  • MapLibre GL JS 4.7.0 (CDN)
  • SunCalc 1.9.0 (CDN) — авто-тема по восходу/закату
  • Без npm-зависимостей, всё inline CSS/JS
  • Деплой: Node.js ssh2 → SFTP на сервер → docker restartdocker cp после рестарта

⚠️ Критически важно: порядок деплоя статики

Образ Docker запекает статику при сборке. При docker restart образ перезаписывает /app/static/. Поэтому:

  1. SFTP загрузить файлы на сервер (/home/slin/enduro-trails/prototype/static/)
  2. docker restart prototype-enduro-trails-1
  3. Подождать 8 секунд
  4. docker cp /home/slin/.../app.js prototype-enduro-trails-1:/app/static/app.js
  5. docker cp /home/slin/.../app.css prototype-enduro-trails-1:/app/static/app.css
  6. docker cp /home/slin/.../index.html prototype-enduro-trails-1:/app/static/index.html

deploy_app2.jsтолько для app.py (бэкенд).
deploy_static.js — SFTP статики, но cp нужно делать вручную после рестарта.


7. Файлы

Файл Описание
prototype/static/index.html HTML, CDN подключения, структура DOM
prototype/static/app.css Все стили, CSS vars, темы, компоненты
prototype/static/app.js Вся логика: карта, роутинг, UI, drag-and-drop
prototype/app.py Бэкенд FastAPI (не трогать)

Фаза 5 завершена. Следующая: Фаза 6 — SRTM рельеф.