# DEV TASK: Тёмная тема карты (F-18) **Статус:** Ready for dev **Проект:** enduro-trails **Фаза:** 5.1 **BRD:** нет (простая фича) --- ## Цель > Карта MapLibre переключается между светлым и тёмным стилем синхронно с темой UI (auto/light/dark + SunCalc). ## Архитектура Сейчас `style.json` — это светлый стиль (фон `#f0ede6`, OSM raster desaturated). Нужно создать `style-dark.json` с тёмной палитрой и исправить логику в `switchMapStyle()`, которая сейчас инвертирована (считает `style.json` тёмным). Подход: тёмный стиль = тот же OSM raster, но с `brightness-max: 0.3`, `contrast: -0.2`, тёмный background. Цвета треков и POI адаптированы для тёмного фона. ## Стек / Зависимости - MapLibre GL JS (уже подключён) - Никаких новых зависимостей --- ## Инфраструктура | Параметр | Значение | |----------|----------| | Сервер | `slin@82.22.50.71` (пароль: `motoZ@yaz2010`) | | Рабочая директория | `/home/slin/enduro-trails/prototype/static/` | | Workspace | `/home/node/.openclaw/workspace/tasks/enduro-trails/prototype/static/` | | Деплой | SFTP + `docker cp` после рестарта | | Контейнер | `prototype-enduro-trails-1` | | URL | `https://openclaw.mva154.duckdns.org/enduro/` | --- ## Файловая карта | Действие | Файл | Ответственность | |----------|------|-----------------| | Создать | `static/style-dark.json` | Тёмный стиль карты | | Переименовать | `static/style.json` → оставить как есть | Светлый стиль (уже есть) | | Изменить | `static/app.js` (строки 86-105) | Исправить логику switchMapStyle | --- ## Задачи ### Task 1: Создать style-dark.json **Файлы:** - Создать: `static/style-dark.json` **Шаги:** - [ ] **1.1** Создать `style-dark.json` на основе `style.json` со следующими изменениями: ```json { "name": "Enduro Trails Dark", "layers": [ { "id": "background", "paint": { "background-color": "#1a1a2e" } }, { "id": "osm-base", "paint": { "raster-opacity": 1.0, "raster-saturation": -0.6, "raster-contrast": -0.1, "raster-brightness-min": 0, "raster-brightness-max": 0.35 } }, { "id": "trails-track", "paint": { "line-color": "...", "line-opacity": 0.95 } // Цвета треков: grade1/2 → #FFE066, grade3/4/5 → #FF6633, default → #FF6633 }, { "id": "trails-path-bridleway", "paint": { "line-color": "#ff4444", "line-opacity": 0.9 } }, { "id": "poi-circles", "paint": { "circle-stroke-color": "#333333" // Остальные цвета POI — ярче на 10-15% для контраста на тёмном фоне } }, { "id": "poi-labels", "paint": { "text-color": "#e0e0e0", "text-halo-color": "#1a1a2e", "text-halo-width": 2 } } ] } ``` Полная спецификация изменений относительно `style.json`: | Слой | Свойство | Светлый | Тёмный | |------|----------|---------|--------| | background | background-color | `#f0ede6` | `#1a1a2e` | | osm-base | raster-saturation | `-0.4` | `-0.6` | | osm-base | raster-contrast | `0.25` | `-0.1` | | osm-base | raster-brightness-max | `0.9` | `0.35` | | trails-track | line-color grade1/2 | `#FFD700` | `#FFE066` | | trails-track | line-color grade3/4/5/default | `#FF4400` | `#FF6633` | | trails-path-bridleway | line-color | `#cc0000` | `#ff4444` | | poi-circles | circle-stroke-color | `#ffffff` | `#333333` | | poi-labels | text-color | `#333333` | `#e0e0e0` | | poi-labels | text-halo-color | `#ffffff` | `#1a1a2e` | | poi-labels | text-halo-width | `1.5` | `2` | Все остальные свойства (sources, glyphs, filters, zoom levels, line-width) — копировать из `style.json` без изменений. - [ ] **1.2** Проверить валидность JSON ```bash cat static/style-dark.json | python3 -m json.tool > /dev/null && echo "OK" # Ожидаемый результат: OK ``` **Критерий готовности:** Файл `style-dark.json` существует, валидный JSON, содержит все слои из style.json с тёмной палитрой. --- ### Task 2: Исправить switchMapStyle() в app.js **Файлы:** - Изменить: `static/app.js` (строки 86-105) **Шаги:** - [ ] **2.1** Заменить функцию `switchMapStyle()` (строки 86-105): **Было:** ```javascript function switchMapStyle() { const map = window._map; if (!map) return; const dark = isDarkTheme(); const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; const styleUrl = dark ? basePath + '/style.json' : basePath + '/style-light.json'; // Check if style-light.json exists - if not, keep current fetch(styleUrl, { method: 'HEAD' }).then(r => { if (r.ok) { map.setStyle(styleUrl); } else { // No light style available, keep dark if (!dark) { console.log('Light map style not available, keeping dark'); } } }).catch(() => { // Network error, don't switch }); } ``` **Стало:** ```javascript function switchMapStyle() { const map = window._map; if (!map) return; const dark = isDarkTheme(); const basePath = window.location.pathname.replace(/\/[^/]*$/, '') || ''; const styleUrl = dark ? basePath + '/style-dark.json' : basePath + '/style.json'; fetch(styleUrl, { method: 'HEAD' }).then(r => { if (r.ok) { map.setStyle(styleUrl); } else { console.log('Map style not available:', styleUrl); } }).catch(() => { // Network error, don't switch }); } ``` Ключевое изменение: `dark → style-dark.json`, `light → style.json` (было инвертировано). - [ ] **2.2** Проверить что `switchMapStyle()` вызывается в `applyTheme()` (строка 26) — уже есть, не трогать. **Критерий готовности:** При `isDarkTheme() === true` загружается `style-dark.json`, при `false` — `style.json`. --- ### Task 3: Деплой и проверка **Шаги:** - [ ] **3.1** Загрузить файлы на сервер ```bash # Через Node.js ssh2 (deploy_static.js) или напрямую: scp static/style-dark.json slin@82.22.50.71:/home/slin/enduro-trails/prototype/static/ scp static/app.js slin@82.22.50.71:/home/slin/enduro-trails/prototype/static/ ``` - [ ] **3.2** Docker cp в контейнер ```bash ssh slin@82.22.50.71 << 'EOF' docker cp /home/slin/enduro-trails/prototype/static/style-dark.json prototype-enduro-trails-1:/app/static/style-dark.json docker cp /home/slin/enduro-trails/prototype/static/app.js prototype-enduro-trails-1:/app/static/app.js EOF ``` ⚠️ НЕ рестартовать контейнер! Просто cp — иначе придётся копировать ВСЕ файлы заново. - [ ] **3.3** Проверить доступность стилей ```bash curl -s -o /dev/null -w "%{http_code}" https://openclaw.mva154.duckdns.org/enduro/style.json # Ожидаемый результат: 200 curl -s -o /dev/null -w "%{http_code}" https://openclaw.mva154.duckdns.org/enduro/style-dark.json # Ожидаемый результат: 200 ``` - [ ] **3.4** Проверить содержимое тёмного стиля ```bash curl -s https://openclaw.mva154.duckdns.org/enduro/style-dark.json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['name'], d['layers'][0]['paint'])" # Ожидаемый результат: Enduro Trails Dark {'background-color': '#1a1a2e'} ``` **Критерий готовности:** Оба стиля доступны по URL, тёмный стиль содержит правильную палитру. --- ## Проверка (Acceptance) | # | Проверка | Действие | Ожидаемый результат | |---|----------|----------|---------------------| | 1 | Светлая тема | Открыть сайт, выбрать ☀️ | Карта светлая (бежевый фон) | | 2 | Тёмная тема | Переключить на 🌙 | Карта тёмная (тёмно-синий фон, приглушённый OSM) | | 3 | Auto (день) | Режим auto днём | Карта светлая | | 4 | Auto (ночь) | Режим auto ночью | Карта тёмная | | 5 | Overlay сохраняется | Включить terrain → переключить тему | Terrain слой остаётся видимым | | 6 | Маршрут сохраняется | Построить маршрут → переключить тему | Маршрут остаётся на карте | | 7 | style-dark.json 200 | `curl` | HTTP 200 | --- ## Ограничения и контекст - ⚠️ `docker cp` БЕЗ рестарта контейнера — рестарт перезапишет статику из образа - ⚠️ SSH через Node.js ssh2 модуль (бинарный ssh в контейнере OpenClaw не работает — glibc mismatch). Альтернатива: `deploy_static.js` - ⚠️ После `map.setStyle()` все кастомные слои (terrain, routes, markers) слетают — `onMapStyleLoad()` + `rebuildMapOverlays()` уже обрабатывают это через событие `style.load` - 🚫 Не трогать `style.json` — это рабочий светлый стиль - 🚫 Не менять логику SunCalc / applyAutoTheme — она работает корректно --- ## Деплой-чеклист - [ ] `style-dark.json` создан и валиден - [ ] `app.js` — логика `switchMapStyle` исправлена - [ ] Файлы загружены на сервер (`/home/slin/enduro-trails/prototype/static/`) - [ ] `docker cp` выполнен (без рестарта!) - [ ] `style-dark.json` доступен по URL (HTTP 200) - [ ] Переключение темы меняет стиль карты - [ ] Overlays (terrain, routes) сохраняются при смене стиля --- *Создано: 2026-05-12 | Автор ТЗ: Стрим | Исполнитель: Dev-агент*