# Dev Task: Enduro Trails — Маркеры и редизайн панели маршрута **Файлы:** `/home/node/.openclaw/workspace/tasks/enduro-trails/prototype/static/` **Деплой:** `node /tmp/deploy_static.js` **Бэкенд не трогать.** --- ## Часть 1: Маркеры точек на карте ### Текущее состояние - Старт: зелёный круг «A» - Финиш: красный круг «B» - Промежуточные: белый круг с синей рамкой, цифра ### Новый дизайн маркеров **Старт (index === 0):** флаг-пин зелёный с буквой «S» **Финиш (index === total-1):** флаг-пин красный с буквой «F» **Промежуточные:** синий пин с цифрой 1, 2, 3... Форма пина (капля/слеза, как у Google Maps): ``` ╭───╮ │ S │ ╰─┬─╯ │ ``` Реализация через SVG в innerHTML маркера: ```js function createWaypointMarkerEl(index, total) { const el = document.createElement('div'); el.className = 'route-waypoint-marker marker-anim'; el.style.cssText = 'cursor: grab; width: 28px; height: 36px; position: relative;'; let bg, label; if (index === 0) { bg = '#2EA043'; label = 'S'; } else if (index === total - 1) { bg = '#FF3B1F'; label = 'F'; } else { bg = '#0066ff'; label = String(index); } el.innerHTML = ` ${label} `; return el; } ``` CSS для маркера (добавить в app.css): ```css .route-waypoint-marker { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4)); } .route-waypoint-marker:active { cursor: grabbing; } ``` --- ## Часть 2: Список точек в панели — редизайн ### Текущее состояние ``` ● 55.723, 37.612 [×] ● 56.123, 38.234 [×] ``` ### Новый дизайн ``` [S] Москва, Тверская ул. [×] [1] Клин [×] [F] Тверь, центр [×] ``` Иконка слева — мини-версия пина (SVG, 20×26px), цвет соответствует маркеру на карте. ```js function waypointPinSvg(label, color) { const fs = label.length > 1 ? '7' : '9'; return ` ${label} `; } ``` Изменить `renderWaypointsList()`: ```js async function renderWaypointsList() { const list = document.getElementById('waypoints-list'); if (!routeWaypoints.length) { list.innerHTML = ''; return; } list.innerHTML = routeWaypoints.map((wp, i) => { const isStart = i === 0; const isEnd = i === routeWaypoints.length - 1; const label = isStart ? 'S' : isEnd ? 'F' : String(i); const color = isStart ? '#2EA043' : isEnd ? '#FF3B1F' : '#0066ff'; const coordText = `${wp.lat.toFixed(3)}, ${wp.lon.toFixed(3)}`; return `
${waypointPinSvg(label, color)}
${coordText}
`; }).join(''); // Async geocode routeWaypoints.forEach(async (wp, i) => { const name = await reverseGeocode(wp.lat, wp.lon); const el = document.getElementById(`wl-label-${i}`); if (el) el.textContent = name; }); } ``` CSS для списка точек (обновить): ```css .wl-item { display: flex; align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--border); } .wl-item:last-child { border-bottom: none; } .wl-pin { flex-shrink: 0; display: flex; align-items: center; } .wl-label { flex: 1; font-size: 13px; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .wl-remove { width: 28px; height: 28px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; background: none; border: none; color: var(--text3); cursor: pointer; border-radius: 6px; -webkit-tap-highlight-color: transparent; } .wl-remove:active { background: var(--red-bg); color: var(--red); } .wl-remove svg { width: 14px; height: 14px; } ``` --- ## Часть 3: Карточки маршрутов — редизайн ### Новый дизайн карточки ``` ┌─────────────────────────────────────┐ │ ● Вариант 1 1013 км 14ч │ ← header │ ████████████████░░░░ 82% грунт │ ← полоска + подпись └─────────────────────────────────────┘ ``` - Полоска грунт/асфальт — высота 6px, скруглённая, без отдельных pill-бейджей - Под полоской: «82% грунт · 18% асфальт» одной строкой мелким текстом - Активная карточка: оранжевый левый бордер (4px) вместо полного бордера - Компактнее: padding 10px 12px вместо 12px 14px ```js function renderRouteCards(routes) { const container = document.getElementById('route-cards'); container.innerHTML = routes.map((route, i) => { const color = ROUTE_COLORS[i] || '#888888'; const distKm = (route.distance_m / 1000).toFixed(0); const timeStr = formatDuration(route.duration_s); const isActive = i === activeRouteIdx; const s = route.stats || {}; const dirtPct = s.dirt_total_pct || 0; const asphPct = s.asphalt_pct || 0; return `
Вариант ${i + 1} ${distKm} км · ${timeStr}
${dirtPct}% грунт${asphPct ? ` · ${asphPct}% асфальт` : ''}
`; }).join(''); } ``` CSS карточек (обновить/добавить): ```css .route-card { background: var(--surface2); border: 1.5px solid var(--border); border-left: 4px solid transparent; border-radius: 10px; padding: 10px 12px; margin-bottom: 6px; cursor: pointer; transition: border-color 0.15s, background 0.15s; } .route-card:active { background: var(--surface3, var(--surface2)); } .route-card.active { border-color: var(--border); border-left-color: var(--accent); background: var(--accent-bg); } .rc-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; } .rc-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } .rc-title { font-size: 13px; font-weight: 700; color: var(--text); flex: 1; } .rc-meta { font-size: 12px; color: var(--text2); white-space: nowrap; } .rc-bar-wrap { margin-bottom: 4px; } .rc-bar { height: 6px; border-radius: 3px; background: var(--border); display: flex; overflow: hidden; } .rc-bar-dirt { background: var(--gold); border-radius: 3px 0 0 3px; } .rc-bar-asphalt { background: var(--text3); } .rc-bar-label { font-size: 11px; color: var(--text2); } ``` --- ## Часть 4: Кнопки действий — компактнее Текущие кнопки «+ Точка», «GPX», «Сброс» — сделать компактнее, иконки без текста на мобиле (или иконка + короткий текст). ```html ``` CSS: ```css .route-actions { display: flex; gap: 6px; margin: 8px 0; } .btn-action { flex: 1; height: 36px; display: flex; align-items: center; justify-content: center; gap: 5px; background: var(--surface2); border: 1px solid var(--border); border-radius: 10px; color: var(--text2); font-size: 12px; font-weight: 600; cursor: pointer; -webkit-tap-highlight-color: transparent; transition: background 0.15s, color 0.15s; } .btn-action svg { width: 14px; height: 14px; flex-shrink: 0; } .btn-action:active { background: var(--surface3, var(--border)); } .btn-action.primary { background: var(--accent); border-color: var(--accent); color: #fff; } .btn-action.primary:active { opacity: 0.85; } .btn-action.danger:active { background: var(--red-bg); color: var(--red); border-color: var(--red); } ``` --- ## Порядок реализации 1. Часть 1: новые SVG-маркеры (createWaypointMarkerEl) 2. Часть 2: waypointPinSvg + renderWaypointsList + CSS 3. Часть 3: renderRouteCards + CSS карточек 4. Часть 4: CSS кнопок действий 5. Деплой + проверка ## Проверка 1. Поставить старт → зелёный пин «S» на карте 2. Поставить финиш → красный пин «F» 3. Добавить промежуточную → синий пин «1» 4. В списке точек: иконки пинов + названия мест 5. Карточки маршрутов: компактные, полоска, левый оранжевый бордер у активной