Files
wiki/tasks/enduro-trails/DEV_TASK_PHASE5.md
2026-05-05 08:10:01 +03:00

311 lines
12 KiB
Markdown
Raw Permalink 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 — Фаза 5 «Редизайн»
**Приоритет:** HIGH
**Проект:** enduro-trails
**BRD:** `BRD_PHASE5.md`
**Дата:** 2026-05-05
---
## Контекст
Фазы 34 реализованы: роутинг 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:
```js
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):**
```css
: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);
}
```
**Светлая:**
```css
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`:
```html
<script src="https://unpkg.com/suncalc@1.9.0/suncalc.min.js"></script>
```
(до `app.js`)
**Логика:**
```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: тёмный и светлый. При смене темы:
```js
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)` → hover `var(--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 вместо «Строю маршрут...»:
```css
.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-обработка:**
```js
// На каждом .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`:
```css
@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: Микро-анимации
1. **Кнопки toolbar** при tap: `transform: scale(0.94)`, 100ms
2. **Маркеры на карте** при появлении: `transform: scale(0) → scale(1)`, 200ms (MapLibre marker animation)
3. **Карточки** при появлении: `opacity: 0 → 1`, 200ms stagger
---
## Порядок реализации
1. **Задача 1** — Дизайн-система: CSS vars для двух тем + SunCalc + переключатель
2. **Задача 2**Все компоненты на CSS vars (замена хардкода)
3. **Задача 3** — Skeleton loading
4. **Задача 4** — Свайп для закрытия sheets
5. **Задача 5** — Десктоп-адаптив
6. **Задача 6** — Микро-анимации
7. Деплой + проверка
---
## Проверка после деплоя
1. Открыть приложение → проверить тему (если день — светлая, если ночь — тёмная, подпись «Авто»)
2. Тапнуть кнопку темы 3 раза → Авто→Светлая→Тёмная→Авто
3. В светлом режиме — бежевый фон, тёмный текст, нет белых вспышек
4. В тёмном режиме — тёмный фон, светлый текст
5. Открыть Маршрут → sheet, карточки, всё в цветах текущей темы
6. Переключить тему с открытым sheet → цвета обновились
7. Десктоп (широкий экран) → toolbar слева вертикально, sheet с max-width
8. Skeleton при построении маршрута
9. Свайп вниз по sheet → закрывается
10. Геолокация → тема пересчитывается по реальным координатам
---
## Технические ограничения
- **Бэкенд (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`)