From 246c4f36cefdf391164c1297fc4c4a31a3b14e63 Mon Sep 17 00:00:00 2001 From: Stream Date: Wed, 6 May 2026 00:40:01 +0300 Subject: [PATCH] auto-sync: 2026-05-06 00:40:01 --- tasks/enduro-trails/BRD_PHASE5.md | 31 ++++- tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md | 115 ++++++++++++++++++ tasks/enduro-trails/PROJECT.md | 42 ++++++- tasks/enduro-trails/prototype/static/app.css | 109 +++++++---------- tasks/enduro-trails/prototype/static/app.js | 75 +++++++++++- .../enduro-trails/prototype/static/index.html | 16 +-- 6 files changed, 304 insertions(+), 84 deletions(-) create mode 100644 tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md diff --git a/tasks/enduro-trails/BRD_PHASE5.md b/tasks/enduro-trails/BRD_PHASE5.md index 1bdb227..5253283 100644 --- a/tasks/enduro-trails/BRD_PHASE5.md +++ b/tasks/enduro-trails/BRD_PHASE5.md @@ -66,7 +66,15 @@ - Заменена на прямую кнопку «Скачать GPX» с download-иконкой (стрелка вниз) - `onclick="downloadGPX()"` — без промежуточных диалогов -### ✅ Мини-бар маршрута +### ✅ Расстояние по маршруту между точками (добавлено 05.05.2026) + +- Функция `getRouteSegmentDistances()` — snap каждого waypoint к ближайшей точке геометрии OSRM +- Первая точка → `snapIdx[0] = 0`, последняя → `snapIdx[n-1]` — гарантирует покрытие всей геометрии +- Сумма сегментов масштабируется к `route.distance_m` — точное совпадение (diff=0) +- Обновляется при смене варианта (`selectRoute`, `selectMiniRoute`) +- Обновляется после построения маршрута (`drawRouteResults` → `renderWaypointsList`) +- Fallback на haversine по прямой если маршрут ещё не построен +- Форматирование `toFixed(1)` везде (сегменты, карточки вариантов, мини-бар) - `#mini-route-bar` — компактная полоска поверх карты когда sheet свёрнут - Показывает активный маршрут (дистанция, % грунт) @@ -83,6 +91,9 @@ | 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 restart` → `docker cp` (порядок важен!) | --- @@ -100,6 +111,8 @@ | Десктоп: боковая панель, не ломается layout | ✅ | | GPX скачивание работает | ✅ | | Drag-and-drop точек маршрута (touch + mouse) | ✅ | +| Расстояние по маршруту между точками (сумма = route.distance_m) | ✅ | +| Обновление расстояний при смене варианта маршрута | ✅ | | Деплой + health check OK | ✅ | --- @@ -109,7 +122,21 @@ - **MapLibre GL JS** 4.7.0 (CDN) - **SunCalc** 1.9.0 (CDN) — авто-тема по восходу/закату - Без npm-зависимостей, всё inline CSS/JS -- Деплой: Node.js ssh2 → SFTP → docker cp → docker restart +- Деплой: Node.js ssh2 → SFTP на сервер → `docker restart` → `docker 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 нужно делать вручную после рестарта. --- diff --git a/tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md b/tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md new file mode 100644 index 0000000..e5d8eff --- /dev/null +++ b/tasks/enduro-trails/BRD_WAYPOINT_SEARCH.md @@ -0,0 +1,115 @@ +# BRD: Enduro Trails — Поиск точек маршрута + +**Версия:** 1.0 +**Дата:** 2026-05-05 +**Автор:** Стрим 🌊 +**Статус:** 🔄 В разработке + +--- + +## 1. Контекст и проблема + +Сейчас добавить точку маршрута можно только тапом на карту. Если нужно попасть в конкретное место (деревня, перекрёсток, POI) — нужно сначала найти его на карте, потом тапнуть. Это неудобно, особенно на мобиле. + +Верхний search bar решал задачу навигации по карте, но не интегрирован с маршрутом — найденное место просто центрировало карту, не добавляя точку. Плюс bar занимал место и мешал карте. + +--- + +## 2. Цель + +Убрать верхний search bar. Добавить поиск прямо в список точек маршрута — inline, для каждой точки отдельно. + +--- + +## 3. Что меняется + +### 3.1 Убрать верхний search bar + +- Удалить `#search-bar` и `#search-results` из HTML +- Удалить все CSS стили (`#search-bar`, `#search-input`, `#search-results`, `.search-result-item`, `.sri-*`) +- Удалить вызов `initSearch()` из JS +- Кнопку темы (`#btn-theme`) перенести в `#map-controls-r` (рядом с компасом и геолокацией) + +**Почему:** bar занимал ~52px сверху, перекрывал карту, дублировал функционал который теперь встроен в waypoints. + +### 3.2 Inline поиск в каждой точке маршрута + +В каждом `wl-item` добавить кнопку-лупу. Тап → разворачивается inline панель поиска прямо под точкой. + +**UX flow:** +1. Пользователь видит список точек маршрута (S → 1 → F) +2. Тапает иконку 🔍 рядом с нужной точкой +3. Под точкой появляется поле ввода с автофокусом +4. Вводит название места → через 400ms debounce → Nominatim поиск +5. Выбирает результат → точка обновляется, карта летит к ней, маршрут перестраивается +6. Панель закрывается + +**Детали:** +- Одновременно открыта только одна панель поиска (остальные закрываются) +- Поиск через Nominatim (`countrycodes=ru`, `accept-language=ru`, limit=5) +- Результат: название + подзаголовок (область/район) +- После выбора: `routeWaypoints[idx]` обновляется, `rebuildWaypointMarkers()` + `renderWaypointsList()` + `debounceBuildRoute()` +- `map.flyTo()` к выбранному месту (zoom 13, duration 600ms) + +--- + +## 4. UI компоненты + +### Кнопка лупы в wl-item +- Иконка: Lucide `search` SVG (15×15) +- Цвет: `var(--text3)` → hover `var(--accent)` +- Позиция: между `.wl-info` и `.wl-drag-handle` + +### Inline панель поиска +- Фон: `var(--surface)`, бордер сверху `var(--border)` +- Input: `var(--surface2)` фон, `var(--border)` бордер, focus → `var(--accent)` бордер +- Результаты: max-height 180px, scroll, hover `var(--surface2)` +- Каждый результат: название (bold) + подзаголовок (мелкий, `var(--text2)`) + +### Кнопка темы (перенос) +- Переносится в `#map-controls-r` как `.map-btn` +- `updateThemeButtonIcon()` продолжает работать — находит `#btn-theme` по id + +--- + +## 5. Технические детали + +| Аспект | Решение | +|--------|---------| +| Поиск API | Nominatim `/search` (уже используется в проекте) | +| Debounce | 400ms (как в старом search bar) | +| Состояние | `wpSearchTimeout` — глобальная переменная для debounce | +| Закрытие панели | Тап на лупу повторно — закрывает; открытие другой — закрывает текущую | +| После выбора | `routeWaypoints[idx]` = `{lat, lon}`, rebuild + route | +| Кнопка «Добавить точку» | `wl-add` остаётся без изменений | + +--- + +## 6. Definition of Done + +| Критерий | Статус | +|----------|--------| +| Верхний search bar отсутствует | ⬜ | +| Кнопка темы работает в map-controls-r | ⬜ | +| Иконка лупы в каждом wl-item | ⬜ | +| Тап на лупу → inline поиск открывается | ⬜ | +| Поиск находит места через Nominatim | ⬜ | +| Выбор результата → waypoint обновляется | ⬜ | +| Маршрут перестраивается после выбора | ⬜ | +| Карта летит к выбранному месту | ⬜ | +| Одновременно открыта только одна панель | ⬜ | +| Деплой + health check OK | ⬜ | + +--- + +## 7. Файлы + +| Файл | Изменения | +|------|-----------| +| `prototype/static/index.html` | Удалить `#search-bar`, `#search-results`; перенести `#btn-theme` в `#map-controls-r` | +| `prototype/static/app.css` | Удалить стили search bar; добавить `.wl-search-btn`, `.wl-search-panel`, `.wl-search-input`, `.wl-search-results`, `.wl-search-result-item` | +| `prototype/static/app.js` | Удалить `initSearch()`; добавить `openWaypointSearch()`, `doWaypointSearch()`, `selectWaypointSearchResult()` | + +--- + +*Следующая фаза после реализации: тестирование на мобиле.* diff --git a/tasks/enduro-trails/PROJECT.md b/tasks/enduro-trails/PROJECT.md index 864aea2..66c4578 100644 --- a/tasks/enduro-trails/PROJECT.md +++ b/tasks/enduro-trails/PROJECT.md @@ -45,10 +45,22 @@ ### Деплой ```bash -# SFTP через ssh2: /tmp/deploy_app.js -# docker cp + restart (файлы запечены в образ) +# ВАЖНО: docker cp нужно делать ПОСЛЕ рестарта контейнера! +# Образ перезаписывает статику при рестарте. + +# 1. Загрузить файлы на сервер через SFTP (deploy_static.js) +# 2. docker restart prototype-enduro-trails-1 +# 3. Подождать 8 сек +# 4. docker cp /home/slin/enduro-trails/prototype/static/app.js prototype-enduro-trails-1:/app/static/app.js +# 5. docker cp /home/slin/enduro-trails/prototype/static/app.css prototype-enduro-trails-1:/app/static/app.css +# 6. docker cp /home/slin/enduro-trails/prototype/static/index.html prototype-enduro-trails-1:/app/static/index.html + +# Для бэкенда (app.py): docker cp /home/slin/enduro-trails/prototype/app.py prototype-enduro-trails-1:/app/app.py docker restart prototype-enduro-trails-1 + +# deploy_app2.js — только для app.py! +# deploy_static.js — SFTP статики, но cp делать вручную после рестарта ``` --- @@ -84,7 +96,7 @@ docker restart prototype-enduro-trails-1 | F-13 | "Связка" | Соединить два трека грунтовками | ⏳ Бэклог | 4 | | F-14 | "Разведка" | Грунтовки вокруг точки, статистика по типам, POI | ⏳ Бэклог | 4 | | F-15 | "Народные треки" | OSM Traces, Wikiloc, Komoot, 4x4travel | ⏳ Бэклог | 8 | -| F-16 | Тёмная тема + редизайн | Две темы (авто/светлая/тёмная), SunCalc, мобильный UI, drag-and-drop точек | ✅ Готово | 5 | +| F-16 | Тёмная тема + редизайн | Две темы (авто/светлая/тёмная), SunCalc, мобильный UI, drag-and-drop точек, расстояние по маршруту | ✅ Готово | 5 | | F-17 | PWA + офлайн | Service Worker, MBTiles, GPS-трекинг | ⏳ Бэклог | 7 | --- @@ -156,6 +168,23 @@ docker restart prototype-enduro-trails-1 - Кнопка «Скачать GPX» (share dialog убран) - Фикс: «Добавить точку» из мини-бара - Десктоп-адаптив (toolbar слева, sheet max-width 400px) +- Расстояние по маршруту между точками (по геометрии OSRM, масштабирование к route.distance_m) +- Обновление расстояний при смене варианта маршрута + +**Баги исправлены (05.05):** +- «Добавить точку» не работала — `addWaypointMode()` не устанавливала `routeMode = true` +- Drag-and-drop не работал на десктопе — добавлены mouse-события +- Расстояние в списке точек показывало haversine по прямой вместо маршрута +- При смене варианта маршрута расстояния не обновлялись (`selectRoute`/`selectMiniRoute` не вызывали `renderWaypointsList`) +- `renderWaypointsList` вызывался до построения маршрута → добавлен вызов после `drawRouteResults` +- Деплой статики через `deploy_app2.js` не работал (копировал только `app.py`) → нужен `deploy_static.js` + `docker cp` **после** рестарта контейнера (образ перезаписывает статику при рестарте) + +**Технические решения:** +- `getRouteSegmentDistances()` — snap waypoints к геометрии маршрута, суммирование haversine по точкам, масштабирование к `route.distance_m` +- `snapIdx[0] = 0`, `snapIdx[last] = n-1` — принудительный snap первой/последней точки +- Деплой статики: SFTP → сервер → `docker restart` → `docker cp` (порядок важен!) +- `streaming.mode: "off"` в Telegram канале — убраны дублированные сообщения +- `send_voice.sh` — убрана отправка через `openclaw message send`, только генерация OGG + `MEDIA:` директива ### ⏳ Фаза 6 — SRTM рельеф - F-12 «Горка» — макс набор высоты, мин дистанция @@ -185,8 +214,11 @@ docker restart prototype-enduro-trails-1 | Retry TooBig: 5→3→1 | OSRM не даёт 5 альтернатив на длинных маршрутах | | Retry NoSegment: radiuses=10000 | Точки далеко от дорог — нужен широкий snap | | Timeout 60s для retry | Длинные маршруты строятся >30с | -| Node.js ssh2 для деплоя | SSH бинарник в контейнере требует glibc 2.38 | -| docker cp вместо compose build | Файлы запечены в образ, cp в running container быстрее | +| `docker cp` после рестарта (не до) | Образ перезаписывает статику при рестарте — cp нужен после того как контейнер поднялся | +| `deploy_app2.js` только для app.py | Скрипт не копирует статику — для фронтенда использовать `deploy_static.js` + ручной cp после рестарта | +| Масштабирование сегментов к `route.distance_m` | OSRM геометрия упрощена — haversine по точкам даёт ~0.2% погрешность; масштабирование даёт точное совпадение | +| `renderWaypointsList()` после `drawRouteResults()` | Список рендерится до построения маршрута — нужен повторный вызов когда `routeResults` заполнен | +| `streaming.mode: "off"` для Telegram | `partial` и `progress` шлют промежуточные сообщения — `off` даёт одно финальное | | Сэмплирование каждые ~500м для stats | Без этого расчёт >30с на длинных маршрутах | | Grid cache для calc_route_stats | Убирает повторные SQL запросы для близких точек | diff --git a/tasks/enduro-trails/prototype/static/app.css b/tasks/enduro-trails/prototype/static/app.css index 119ec92..b6582e6 100644 --- a/tasks/enduro-trails/prototype/static/app.css +++ b/tasks/enduro-trails/prototype/static/app.css @@ -67,78 +67,60 @@ html, body { #map { position: fixed; inset: 0; z-index: 0; } -/* ── Search Bar ──────────────────────────────── */ -#search-bar { - position: fixed; - top: max(env(safe-area-inset-top, 0px), 12px); - left: 12px; right: 12px; - height: 50px; - /* Push MapLibre nav controls below search bar */ -} +/* ── MapLibre nav controls position ──────────── */ .maplibregl-ctrl-top-left { - top: calc(max(env(safe-area-inset-top, 0px), 12px) + 58px) !important; + top: calc(max(env(safe-area-inset-top, 0px), 12px) + 8px) !important; left: 12px !important; } - background: var(--surface); - border: 1px solid var(--border); - border-radius: 14px; - display: flex; align-items: center; - padding: 0 6px 0 14px; - gap: 8px; z-index: 300; - box-shadow: var(--shadow); - transition: background 0.3s, border-color 0.3s; + +/* ── Waypoint inline search ───────────────────── */ +.wl-search-btn { + background: none; + border: none; + color: var(--text3); + cursor: pointer; + padding: 4px; + border-radius: 6px; + display: flex; + align-items: center; + flex-shrink: 0; + transition: color 0.15s; } -#search-bar:focus-within { border-color: var(--accent); } -#search-bar .sb-icon { color: var(--text3); flex-shrink: 0; width: 18px; height: 18px; } -#search-input { - flex: 1; background: none; border: none; - color: var(--text); font-size: 15px; outline: none; min-width: 0; - transition: color 0.3s; +.wl-search-btn:hover, .wl-search-btn:active { color: var(--accent); } + +.wl-search-panel { + padding: 6px 8px 4px 8px; + border-top: 1px solid var(--border); } -#search-input::placeholder { color: var(--text3); } -#btn-theme { - width: auto; min-width: 38px; height: 38px; - border-radius: 10px; +.wl-search-input { + width: 100%; background: var(--surface2); border: 1px solid var(--border); - color: var(--text2); - display: flex; align-items: center; justify-content: center; - gap: 3px; cursor: pointer; flex-shrink: 0; - transition: all 0.15s; padding: 0 8px; + border-radius: 8px; + color: var(--text); + font-size: 13px; + padding: 7px 10px; + outline: none; + box-sizing: border-box; } -#btn-theme:active { transform: scale(0.94); background: var(--surface3); } -#btn-theme svg { width: 16px; height: 16px; } -#theme-label { - font-size: 9px; font-weight: 700; - text-transform: uppercase; letter-spacing: 0.04em; - color: var(--text3); line-height: 1; +.wl-search-input:focus { border-color: var(--accent); } +.wl-search-results { + margin-top: 4px; + max-height: 180px; + overflow-y: auto; } - -/* Search results */ -#search-results { - position: fixed; - top: calc(max(env(safe-area-inset-top, 0px), 12px) + 58px); - left: 12px; right: 12px; - background: var(--surface); - border: 1px solid var(--border); - border-radius: 14px; z-index: 300; - box-shadow: var(--shadow); - overflow: hidden; display: none; - transition: background 0.3s, border-color 0.3s; -} -.search-result-item { - padding: 12px 16px; - border-bottom: 1px solid var(--border); +.wl-search-result-item { + padding: 8px 10px; cursor: pointer; - display: flex; align-items: center; gap: 10px; - transition: background 0.1s; + border-radius: 6px; + font-size: 13px; + color: var(--text); } -.search-result-item:last-child { border-bottom: none; } -.search-result-item:active { background: var(--surface2); } -.search-result-item:hover { background: var(--surface2); } -.sri-icon { color: var(--text3); flex-shrink: 0; } -.sri-name { font-size: 14px; font-weight: 500; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } -.sri-sub { font-size: 12px; color: var(--text2); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } +.wl-search-result-item:hover, .wl-search-result-item:active { + background: var(--surface2); +} +.wl-search-result-name { font-weight: 500; } +.wl-search-result-sub { font-size: 11px; color: var(--text2); margin-top: 1px; } /* ── Map Control Buttons ──────────────────────── */ #map-controls-r { @@ -502,11 +484,9 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; } } .bottom-sheet.open { transform: translateX(0); } .bottom-sheet.swiping { transition: none; } - #search-bar { left: 84px; right: 12px; max-width: 400px; } #map-controls-r { right: 12px; bottom: 12px; } #sheet-backdrop { display: none; } #ruler-info { left: 84px; } - #search-results { left: 84px; right: 12px; max-width: 400px; } } /* ═══════════════════════════════════════════════════ @@ -610,8 +590,7 @@ body.has-map-mode #sheet-backdrop.visible { pointer-events: none; } #btn-add-waypoint:hover { border-color: var(--accent); color: var(--accent); } #btn-build-route { width: 100%; height: 42px; background: var(--accent); color: #fff; border: none; border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer; margin-top: 8px; transition: background 0.15s; } #btn-build-route:active { background: var(--accent-h); } -.search-result-name { font-size: 14px; font-weight: 500; color: var(--text); } -.search-result-detail { font-size: 12px; color: var(--text2); margin-top: 2px; } + /* ── Mini Route Bar ───────────────────────── */ #sheet-route-mini { diff --git a/tasks/enduro-trails/prototype/static/app.js b/tasks/enduro-trails/prototype/static/app.js index 573c5e5..aacd326 100644 --- a/tasks/enduro-trails/prototype/static/app.js +++ b/tasks/enduro-trails/prototype/static/app.js @@ -627,10 +627,17 @@ async function renderWaypointsList() { ${coordText} ${distStr ? `${distStr}` : ''} +
${gripSvg}
+ + `; }).join(''); @@ -1175,7 +1182,6 @@ async function initMap() { checkDataAvailability(); initRouteClicks(map); initRulerClicks(map); - initSearch(); renderMarkers(); // Apply theme on load applyTheme(); @@ -1332,6 +1338,73 @@ function initRouteClicks(map) { // ─── Поиск (Nominatim) ───────────────────────────────────────────── let searchTimeout = null; +// ─── Waypoint inline search ──────────────────────────────────────── +let wpSearchTimeout = null; + +function openWaypointSearch(idx) { + // Close all other open panels + document.querySelectorAll('.wl-search-panel').forEach(p => { + if (p.id !== `wl-search-panel-${idx}`) p.style.display = 'none'; + }); + const panel = document.getElementById(`wl-search-panel-${idx}`); + if (!panel) return; + const isOpen = panel.style.display !== 'none'; + panel.style.display = isOpen ? 'none' : 'block'; + if (!isOpen) { + const input = document.getElementById(`wl-search-input-${idx}`); + if (input) { + input.value = ''; + input.focus(); + input.addEventListener('input', () => { + clearTimeout(wpSearchTimeout); + const q = input.value.trim(); + if (q.length < 2) { + document.getElementById(`wl-search-results-${idx}`).innerHTML = ''; + return; + } + wpSearchTimeout = setTimeout(() => doWaypointSearch(idx, q), 400); + }); + } + } +} + +async function doWaypointSearch(idx, query) { + const resultsEl = document.getElementById(`wl-search-results-${idx}`); + if (!resultsEl) return; + resultsEl.innerHTML = '
Поиск...
'; + try { + const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(query)}&format=json&limit=5&countrycodes=ru&accept-language=ru`; + const resp = await fetch(url); + const data = await resp.json(); + if (!data.length) { + resultsEl.innerHTML = '
Ничего не найдено
'; + return; + } + resultsEl.innerHTML = data.map(item => { + const parts = (item.display_name || '').split(', '); + const name = parts[0]; + const sub = parts.slice(1, 3).join(', '); + return `
+
${name}
+ ${sub ? `
${sub}
` : ''} +
`; + }).join(''); + } catch(e) { + resultsEl.innerHTML = '
Ошибка поиска
'; + } +} + +function selectWaypointSearchResult(idx, lat, lon, name) { + routeWaypoints[idx] = { lat: parseFloat(lat), lon: parseFloat(lon) }; + const panel = document.getElementById(`wl-search-panel-${idx}`); + if (panel) panel.style.display = 'none'; + rebuildWaypointMarkers(); + renderWaypointsList(); + window._map.flyTo({ center: [parseFloat(lon), parseFloat(lat)], zoom: 13, duration: 600 }); + if (routeWaypoints.length >= 2) debounceBuildRoute(); + updateMiniRouteCard(); +} + function initSearch() { const input = document.getElementById('search-input'); const results = document.getElementById('search-results'); diff --git a/tasks/enduro-trails/prototype/static/index.html b/tasks/enduro-trails/prototype/static/index.html index 7f23cc1..0ffcbf4 100644 --- a/tasks/enduro-trails/prototype/static/index.html +++ b/tasks/enduro-trails/prototype/static/index.html @@ -18,17 +18,7 @@
- - -
+
@@ -48,6 +38,10 @@ +