12 KiB
Dev Task: Enduro Trails — Фаза 5 «Редизайн»
Приоритет: HIGH
Проект: enduro-trails
BRD: BRD_PHASE5.md
Дата: 2026-05-05
Контекст
Фазы 3–4 реализованы: роутинг A→B, альтернативы, Разведка, Связка, Красивый маршрут, Линейка, Метки.
UI уже частично обновлён (bottom sheets, toolbar, SVG, тёмная тема), но BRD Phase 5 требует довести до продакшн-качества — мобильный first, две темы с авто-режимом по восходу/закату, адаптив, анимации.
⚠️ Бэкенд (app.py) НЕ ТРОГАТЬ. Только фронтенд: index.html, app.css, app.js.
Деплой
SSH бинарник не работает в контейнере (glibc 2.36 < 2.38). Используй Node.js ssh2:
const { Client } = require('/tmp/node_modules/ssh2');
Шаблон: /tmp/deploy_app2.js.
Статику заливаем через SFTP в /home/slin/enduro-trails/prototype/static/, потом docker cp в контейнер + docker restart.
Что уже есть vs что нужно
| Компонент | Сейчас (Phase 4) | Надо (Phase 5) |
|---|---|---|
| Тема | Только тёмная, кнопка ☀️/🌙 но не работает | Три режима: Авто (SunCalc восход/закат), Светлая, Тёмная |
| Цвета | CSS vars есть, но только тёмные | Два набора CSS vars, переключение через body.theme-dark/body.theme-light |
| Кнопка темы | Есть #btn-theme в search-bar |
Циклическое переключение Авто→Светлая→Тёмная→Авто, подпись режима |
| Search bar | Есть, position:fixed |
Остается, но цвета под тему |
| Bottom toolbar | 6 кнопок, SVG, #toolbar |
Остается, но: активная кнопка = оранжевый фон (не только цвет текста), label появляется под иконкой |
| Bottom sheets | 4 sheets (route/recon/scenic/link) | Остаются, но: цвета под тему, skeleton loading, свайп вниз для закрытия |
| Map buttons | #map-controls-r (2 кнопки) |
Остаются, цвета под тему |
| Адаптив | Mobile only, без десктопа | Десктоп (≥768px): боковая панель 320px, кнопки слева |
| Анимации | Есть sheet slide | + skeleton loading, + кнопка scale(0.94) при tap, + маркер scale появление |
| Иконки | SVG inline (lucide-style) | ОК, остаются. НЕ emoji |
| Карта style | Один style.json | Переключение dark/light style при смене темы |
Задача 1: Дизайн-система — Цвета и Тема
1.1 CSS Custom Properties — две темы
На :root определить ВСЕ переменные для тёмной (по умолчанию). На body.theme-light — переопределить.
Тёмная (default):
:root {
--bg: #0D1117;
--surface: #161B22;
--surface2: #21262D;
--border: #30363D;
--text: #E6EDF3;
--text2: #8B949E;
--text3: #484F58;
--accent: #FF6B00;
--accent-hover:#FF8A33;
--gold: #FFD700;
--red: #FF3B1F;
--success: #2EA043;
--shadow: rgba(0,0,0,0.5);
--overlay: rgba(0,0,0,0.6);
}
Светлая:
body.theme-light {
--bg: #F5F5F0;
--surface: #FFFFFF;
--surface2: #F0F0EA;
--border: #D0CFC8;
--text: #1A1A1A;
--text2: #6B6B6B;
--text3: #9A9A9A;
--accent: #E55A00;
--accent-hover:#CC4F00;
--gold: #C89B00;
--red: #CC2200;
--success: #1A7A30;
--shadow: rgba(0,0,0,0.15);
--overlay: rgba(0,0,0,0.3);
}
Все компоненты (search-bar, toolbar, bottom-sheet, map-btn, cards, pills, ruler-info, marker-dialog, search-results) должны использовать ТОЛЬКО CSS vars — никаких хардкоженных цветов.
1.2 Переключатель темы — три режима
Состояния:
themeMode = 'auto'— по умолчанию, тема определяется по восходу/закатуthemeMode = 'light'— принудительно светлаяthemeMode = 'dark'— принудительно тёмная
Циклическое переключение: тап на #btn-theme → auto → light → dark → auto
UI кнопки:
- В авто-режиме: иконка динамическая (☀️ если день, 🌙 если ночь)
- В ручном светлом: всегда ☀️
- В ручном тёмном: всегда 🌙
- Подпись под иконкой (или рядом, маленький текст): «Авто» / «День» / «Ночь»
Сохранение: localStorage.setItem('enduro-theme-mode', themeMode) — при загрузке читать оттуда.
1.3 SunCalc — авто-режим по восходу/закату
Подключить в index.html:
<script src="https://unpkg.com/suncalc@1.9.0/suncalc.min.js"></script>
(до app.js)
Логика:
function applyAutoTheme() {
if (themeMode !== 'auto') return;
const now = new Date();
// Геолокация: если есть — используем, иначе Москва
const lat = userLat || 55.75;
const lon = userLon || 37.62;
const times = SunCalc.getTimes(now, lat, lon);
const isDay = now >= times.sunrise && now < times.sunset;
document.body.className = isDay ? 'theme-light' : 'theme-dark';
updateThemeButtonIcon();
}
// Проверять раз в минуту
setInterval(applyAutoTheme, 60000);
При получении геолокации (кнопка 🎯): обновить userLat/userLon и пересчитать тему.
При переключении карты: MapLibre.setStyle() на соответствующий style.json (тёмный/светлый).
1.4 Карта — стиль под тему
Уже есть два style.json: тёмный и светлый. При смене темы:
map.setStyle(isDark ? 'style-dark.json' : 'style-light.json');
Или если стили инлайн — переключить map.setStyle() с нужным объектом.
⚠️ Важно: после setStyle нужно пересоздать все слои (routes, markers, circles). Использовать событие map.on('style.load', ...).
Задача 2: Компоненты — все на CSS vars
2.1 Search bar
- Фон:
var(--surface), бордер:var(--border), тень сvar(--shadow) - Placeholder:
var(--text3) - Кнопка темы: иконка + подпись
2.2 Toolbar
- Фон:
var(--surface), бордер:var(--border) - Неактивная кнопка:
var(--text2)цвет - Активная кнопка:
var(--accent)фон, белый цвет текста/иконки,border-radius: 10px - Активная кнопка: подпись под иконкой видна
2.3 Bottom sheets
- Фон:
var(--surface), бордер:var(--border) - Handle:
var(--border)цвет - Заголовок:
var(--text), подзаголовок/label:var(--text2) - Close button:
var(--text2)→ hovervar(--text) - Waypoint chips:
var(--surface2)фон,var(--border)бордер - Route cards:
var(--surface2)фон,var(--border)бордер - Stat pills:
var(--bg)фон,var(--border)бордер - Hint text:
var(--text3)
2.4 Map buttons
- Фон:
var(--surface), бордер:var(--border), тень сvar(--shadow) - Active:
var(--accent)фон
2.5 Search results
- Фон:
var(--surface), бордер:var(--border) - Hover:
var(--surface2)
2.6 Marker dialog
- Фон:
var(--surface), внутренностиvar(--surface2), бордерvar(--border)
Задача 3: Skeleton Loading
При загрузке маршрутов/разведки показывать skeleton вместо «Строю маршрут...»:
.skeleton {
background: linear-gradient(90deg, var(--surface2) 25%, var(--border) 50%, var(--surface2) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
height: 64px;
margin-bottom: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
Использовать в #route-cards, #scenic-cards, #link-cards пока идёт запрос к API.
Задача 4: Свайп вниз для закрытия sheet
Touch-обработка:
// На каждом .bottom-sheet
let startY = 0;
sheet.addEventListener('touchstart', e => {
if (e.target.closest('.sheet-handle') || e.touches[0].clientY < sheet.getBoundingClientRect().top + 40) {
startY = e.touches[0].clientY;
}
});
sheet.addEventListener('touchmove', e => {
const dy = e.touches[0].clientY - startY;
if (dy > 0) sheet.style.transform = `translateY(${dy}px)`;
});
sheet.addEventListener('touchend', e => {
const dy = e.changedTouches[0].clientY - startY;
if (dy > 80) closeSheet(sheet.id);
else sheet.style.transform = '';
});
Задача 5: Адаптив — Десктоп
При width >= 768px:
@media (min-width: 768px) {
#toolbar {
position: fixed;
bottom: auto; left: 0; top: 60px;
width: 72px; height: auto;
flex-direction: column;
border-top: none;
border-right: 1px solid var(--border);
}
.bottom-sheet {
left: 72px;
max-width: 400px;
border-radius: 20px 20px 0 0;
}
#map-controls-r {
left: 72px;
}
#search-bar {
left: 84px;
}
}
Задача 6: Микро-анимации
- Кнопки toolbar при tap:
transform: scale(0.94), 100ms - Маркеры на карте при появлении:
transform: scale(0) → scale(1), 200ms (MapLibre marker animation) - Карточки при появлении:
opacity: 0 → 1, 200ms stagger
Порядок реализации
- Задача 1 — Дизайн-система: CSS vars для двух тем + SunCalc + переключатель
- Задача 2 — Все компоненты на CSS vars (замена хардкода)
- Задача 3 — Skeleton loading
- Задача 4 — Свайп для закрытия sheets
- Задача 5 — Десктоп-адаптив
- Задача 6 — Микро-анимации
- Деплой + проверка
Проверка после деплоя
- Открыть приложение → проверить тему (если день — светлая, если ночь — тёмная, подпись «Авто»)
- Тапнуть кнопку темы 3 раза → Авто→Светлая→Тёмная→Авто
- В светлом режиме — бежевый фон, тёмный текст, нет белых вспышек
- В тёмном режиме — тёмный фон, светлый текст
- Открыть Маршрут → sheet, карточки, всё в цветах текущей темы
- Переключить тему с открытым sheet → цвета обновились
- Десктоп (широкий экран) → toolbar слева вертикально, sheet с max-width
- Skeleton при построении маршрута
- Свайп вниз по sheet → закрывается
- Геолокация → тема пересчитывается по реальным координатам
Технические ограничения
- Бэкенд (app.py) НЕ ТРОГАТЬ — только
index.html,app.css,app.js - MapLibre GL остаётся
- Без npm-зависимостей (inline CSS/JS)
- CDN:
https://unpkg.com/suncalc@1.9.0/suncalc.min.js(добавить в index.html) - CDN:
https://unpkg.com/maplibre-gl@4.7.0/...(уже есть) - Деплой через ssh2 (шаблон
/tmp/deploy_app2.js)