# Dev Task: Enduro Trails — Мини-бар маршрута **Файлы:** `/home/node/.openclaw/workspace/tasks/enduro-trails/prototype/static/` **Деплой:** `/tmp/deploy_static.js` --- ## Три состояния sheet-route | Состояние | Как попасть | Что происходит | |-----------|-------------|----------------| | **Полная панель** | Тап «Маршрут» в toolbar | Открыта sheet-route | | **Мини-бар** | Свайп вниз по handle полной панели | Панель свёрнута, маршрут на карте остаётся | | **Закрыто** | Крестик (X) в полной панели | Панель закрыта + clearRoute() | Мини-бар → тап или свайп вверх → полная панель. Мини-бар → свайп влево/вправо → переключение вариантов маршрута. --- ## HTML — добавить в index.html перед `` ```html
Вариант 1
— км · —% грунт
``` --- ## CSS — добавить в app.css ```css /* ── Mini Route Bar ───────────────────────── */ #sheet-route-mini { position: fixed; bottom: 72px; left: 0; right: 0; height: 64px; background: var(--surface); border-top: 1px solid var(--border); border-radius: 14px 14px 0 0; z-index: 350; display: none; flex-direction: column; align-items: center; box-shadow: 0 -4px 16px var(--shadow); } #sheet-route-mini.visible { display: flex; } #sheet-route-mini .mini-handle { width: 32px; height: 4px; background: var(--border2, var(--border)); border-radius: 2px; margin: 7px auto 0; flex-shrink: 0; } .mini-route-info { display: flex; align-items: center; gap: 10px; padding: 0 16px; flex: 1; width: 100%; } .mini-route-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; } .mini-route-text { flex: 1; min-width: 0; } .mini-route-label { font-size: 13px; font-weight: 700; color: var(--text); } .mini-route-stats { font-size: 11px; color: var(--text2); } .mini-route-arrows { display: flex; gap: 4px; flex-shrink: 0; } .mini-arrow { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; font-size: 18px; color: var(--text2); cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; } .mini-arrow:active { background: var(--accent); color: #fff; border-color: var(--accent); } @media (min-width: 768px) { #sheet-route-mini { left: 72px; width: 380px; right: auto; border-radius: 0 14px 0 0; } } ``` --- ## JS — добавить функции в app.js ```js // ─── Mini Route Bar ──────────────────────────────────────────────── const ROUTE_COLORS = ['#0066ff','#ff6600','#00aa44','#aa00ff','#ff0044']; function showMiniRouteSheet() { if (!routeResults || routeResults.length === 0) return; updateMiniRouteCard(); document.getElementById('sheet-route-mini').classList.add('visible'); initMiniRouteInteraction(); } function hideMiniRouteSheet() { const el = document.getElementById('sheet-route-mini'); if (el) el.classList.remove('visible'); } function updateMiniRouteCard() { const r = routeResults[activeRouteIdx]; if (!r) return; const km = (r.distance_m / 1000).toFixed(0); const dirt = r.stats?.dirt_total_pct ?? '?'; document.getElementById('mini-dot').style.background = ROUTE_COLORS[activeRouteIdx % ROUTE_COLORS.length]; document.getElementById('mini-label').textContent = `Вариант ${activeRouteIdx + 1} из ${routeResults.length}`; document.getElementById('mini-stats').textContent = `${km} км · ${dirt}% грунт`; document.getElementById('mini-prev').style.opacity = activeRouteIdx > 0 ? '1' : '0.3'; document.getElementById('mini-next').style.opacity = activeRouteIdx < routeResults.length - 1 ? '1' : '0.3'; } function selectMiniRoute(idx) { if (idx < 0 || idx >= routeResults.length) return; activeRouteIdx = idx; const map = window._map; for (let i = 0; i < routeResults.length; i++) { const op = i === idx ? 1 : 0.35; if (map.getLayer('route-line-' + i)) map.setPaintProperty('route-line-' + i, 'line-opacity', op); if (map.getLayer('route-line-' + i + '-outline')) map.setPaintProperty('route-line-' + i + '-outline', 'line-opacity', op); } updateMiniRouteCard(); } function initMiniRouteInteraction() { const mini = document.getElementById('sheet-route-mini'); if (!mini) return; // Re-bind arrow buttons document.getElementById('mini-prev').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx - 1); }; document.getElementById('mini-next').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx + 1); }; // Remove old touch listeners by replacing element const newMini = mini.cloneNode(true); mini.parentNode.replaceChild(newMini, mini); document.getElementById('mini-prev').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx - 1); }; document.getElementById('mini-next').onclick = (e) => { e.stopPropagation(); selectMiniRoute(activeRouteIdx + 1); }; let startX = 0, startY = 0; newMini.addEventListener('touchstart', e => { startX = e.touches[0].clientX; startY = e.touches[0].clientY; }, { passive: true }); newMini.addEventListener('touchend', e => { const dx = e.changedTouches[0].clientX - startX; const dy = e.changedTouches[0].clientY - startY; if (Math.abs(dy) > Math.abs(dx)) { if (dy < -40) { hideMiniRouteSheet(); openSheet('sheet-route'); } } else { if (dx < -40) selectMiniRoute(activeRouteIdx + 1); if (dx > 40) selectMiniRoute(activeRouteIdx - 1); } }); newMini.addEventListener('click', e => { if (e.target.classList.contains('mini-arrow')) return; hideMiniRouteSheet(); openSheet('sheet-route'); }); } ``` --- ## Изменить существующие функции ### minimizeSheet(id) ```js function minimizeSheet(id) { closeSheet(id); if (id === 'sheet-route' && routeResults && routeResults.length > 0) { showMiniRouteSheet(); } } ``` ### clearRoute() — в самое начало добавить: ```js hideMiniRouteSheet(); ``` ### toggleRouteMode() — при входе в режим добавить перед openSheet: ```js hideMiniRouteSheet(); ``` ### initSheetSwipe() — найти обработчик touchend и изменить логику закрытия: Найти строку вида `if (dy > 80) closeSheet(sheet.id)` или `if (dy > 80) { closeSheet(...) }` и заменить на: ```js if (dy > 80) { if (sheet.id === 'sheet-route' && routeResults && routeResults.length > 0) { minimizeSheet(sheet.id); } else { closeSheet(sheet.id); } sheet.style.transform = ''; } ``` ### Крестик (X) в sheet-route — уже вызывает minimizeSheet('sheet-route'). Нужно изменить на toggleRouteMode() чтобы крестик стирал маршрут: В index.html найти: ```html