Files
wiki/tasks/enduro-trails/DEV_TASK_PHASE5_MINIBAR.md
2026-05-05 15:50:01 +03:00

231 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 перед `</body>`
```html
<div id="sheet-route-mini">
<div class="mini-handle" id="mini-route-handle"></div>
<div class="mini-route-info">
<div class="mini-route-dot" id="mini-dot"></div>
<div class="mini-route-text">
<div class="mini-route-label" id="mini-label">Вариант 1</div>
<div class="mini-route-stats" id="mini-stats">— км · —% грунт</div>
</div>
<div class="mini-route-arrows">
<span class="mini-arrow" id="mini-prev"></span>
<span class="mini-arrow" id="mini-next"></span>
</div>
</div>
</div>
```
---
## 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
<button class="sheet-close" onclick="minimizeSheet('sheet-route')">
```
Заменить на:
```html
<button class="sheet-close" onclick="toggleRouteMode()">
```
(toggleRouteMode при routeMode=true → выходит из режима + clearRoute)
---
## Проверка после деплоя
1. Построить маршрут → свайп вниз по handle → появляется мини-бар
2. Свайп влево/вправо на мини-баре → переключение вариантов (если >1)
3. Тап на мини-бар → раскрывается полная панель
4. Крестик в полной панели → всё закрывается, маршрут стирается с карты
5. Мини-бар не появляется если маршрут не построен