From d7dda1e2f57512290fb1e40cfb29d03fec3428be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9=20=D0=A1=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=D1=8C=D0=B5=D0=B2?= Date: Sun, 31 May 2026 16:08:30 +0300 Subject: [PATCH] feat(tgu-ops-ui): add pass selector styles and update architecture docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tgu-editor.css: add .map-pass-selector styles — floating panel with semi-transparent background, range slider track, fill, tick marks, draggable thumbs, and per-handle labels showing orbit number + UTC time. docs: update PCP_ARCHITECTURE.md to reflect pcp-tgu-ops-ui is now a real SPA (not a placeholder); add PCP_TGU_OPS_UI_CAPABILITY_MAP.md describing feature areas and data sources for the TGU ops frontend. Co-Authored-By: Claude Sonnet 4.6 --- docs/architect/PCP_ARCHITECTURE.md | 2 +- .../PCP_TGU_OPS_UI_CAPABILITY_MAP.md | 493 ++++++++++++++++++ .../pcp-tgu-ops-ui/src/styles/tgu-editor.css | 160 ++++++ 3 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 docs/architect/PCP_TGU_OPS_UI_CAPABILITY_MAP.md diff --git a/docs/architect/PCP_ARCHITECTURE.md b/docs/architect/PCP_ARCHITECTURE.md index 49b97b2..3f96ce4 100644 --- a/docs/architect/PCP_ARCHITECTURE.md +++ b/docs/architect/PCP_ARCHITECTURE.md @@ -36,7 +36,7 @@ |---|---| | **spring-cloud-config-server** | Раздаёт конфигурацию из `config-repo` всем сервисам | | **pcp-srpring-boot-admin-server** | Spring Boot Admin — мониторинг/health всех сервисов (опечатка в имени: `srpring`) | -| **pcp-tgu-ops-ui** | Плейсхолдер фронта Mission Ops (пустой, прототипы в `docs/ui-prototypes`) | +| **pcp-tgu-ops-ui** | Отдельная SPA Mission Ops/TGU; карта возможностей и источников данных: [`PCP_TGU_OPS_UI_CAPABILITY_MAP.md`](PCP_TGU_OPS_UI_CAPABILITY_MAP.md) | | **libs/pcp-types-lib** | Общие DTO и конверт `KafkaMessage`, перечисление `PcpKafkaEvent` | | **libs/ballistics-lib** | Общие баллистические расчёты | diff --git a/docs/architect/PCP_TGU_OPS_UI_CAPABILITY_MAP.md b/docs/architect/PCP_TGU_OPS_UI_CAPABILITY_MAP.md new file mode 100644 index 0000000..a88c1f4 --- /dev/null +++ b/docs/architect/PCP_TGU_OPS_UI_CAPABILITY_MAP.md @@ -0,0 +1,493 @@ +# pcp-tgu-ops-ui — карта возможностей и источников данных + +Дата анализа: 2026-05-31. + +Документ фиксирует, что сейчас умеет `pcp-tgu-ops-ui`: что отображает, чем управляет, какие backend endpoints вызывает и откуда реально приходят данные. Анализ выполнен по исходному коду frontend и по execution path backend endpoints до service/repository/external source. + +## Кратко + +`pcp-tgu-ops-ui` — отдельная Vite/React SPA для Mission Ops/TGU. Приложение рендерит `TguPlanningPage` и имеет три вкладки: + +- `Планы` — timeline persisted TGU-планов по КА. +- `Карта 2D` — карта с полигонами заявок на съемку. +- `Редактор` — локальный редактор включений КА с черновиком, проверками и заглушкой сохранения. + +Реально изменять состояние backend UI умеет только через отправку решения по плану: `ACCEPTED` или `REJECTED`. Редакторские правки пока не сохраняются во внешний сервис. + +## Общая схема + +```text +Browser + | + v +pcp-tgu-ops-ui SPA + | + |-- /api/tgu-planning/* + | | + | v + | pcp-ui-service :7008 + | | + | v + | pcp-tgu-service :7011 + | | + | |-- pcp_tgu DB + | | + | `-- Ordinis NSI spacecraft classifier + | + |-- /api/current-plans/* + | | + | v + | pcp-ui-service :7008 + | | + | v + | pcp-mission-planing-service :7010 + | | + | `-- mission planning DB + | + |-- /api/pcp-request/v1/* + | | + | v + | pcp-request-service :7005 + | | + | `-- request DB + | + `-- https://*.basemaps.cartocdn.com + | + `-- CARTO basemap tiles +``` + +Dev proxy: + +- `/api/pcp-request` -> `http://localhost:7005`, prefix `/api/pcp-request` stripped. +- `/api` -> `http://localhost:7008`. + +Production nginx: + +- `/api/pcp-request/` -> `http://host.docker.internal:7005/`. +- `/api/` -> `http://host.docker.internal:7008/api/`. + +## Endpoint inventory + +| UI endpoint | Метод | Использование в UI | Backend execution path | Источник | +|---|---:|---|---|---| +| `/api/tgu-planning/platforms` | GET | список КА, подписи, поиск, редактор | `pcp-ui-service` `TguPlanningRestController.platforms()` -> `TguPlanningService.platforms()` -> `pcp-tgu-service /api/platforms` -> `PlatformService.loadPlatforms()` | in-memory cache, Ordinis NSI | +| `/api/tgu-planning/plans?historyDays=N` | GET | загрузка всех TGU-планов для timeline/map/editor context | `pcp-ui-service` -> `pcp-tgu-service /api/plans` -> `PlanQueryService.getAllPlans()` -> `PlannedPlanRepository.findAllByOrderByStartTime()` | `planned_plan` | +| `/api/tgu-planning/spacecraft/{spacecraftId}/plans?historyDays=N` | GET | есть в API-клиенте, текущая страница в основном грузит все планы | `pcp-ui-service` -> `pcp-tgu-service /api/plans/{spacecraftId}` -> `PlanQueryService.getPlansBySpacecraftId()` | `planned_plan` | +| `/api/tgu-planning/plans/{planId}/decision?decision=...&reason=...` | POST | кнопки принять/отклонить для `WAITING_DECISION` | `pcp-ui-service` -> `pcp-tgu-service /test/tgu/plans/{planId}/decision` -> `TestPlanDecisionService.generateDecision()` -> `HandlePlanDecisionUseCase.handle()` | `planned_plan`, `plan_issue_attempt`, `spacecraft_planning_state`, `processed_plan_decision_event` | +| `/api/pcp-request/v1/requests/map?...` | GET | полигоны заявок на карте 2D и в редакторе | напрямую в `pcp-request-service /v1/requests/map` -> `RequestController.listRequestsForMap()` -> `RequestService.listRequestsForMap()` -> `RequestRepository.findRequests()` | `requests` | +| `/api/current-plans/missions/{missionId}/modes` | GET | контуры существующих включений в редакторе | `pcp-ui-service` -> `pcp-mission-planing-service /api/missions/{missionId}/modes` -> `MissionPlaningService.modesByMission()` | `missions`, `plans`, `modes`, `survey_modes`, `drop_modes`, `drop_mode_survey`, `mode_booking` | + +## Вкладка "Планы" + +### Что отображает + +- Список КА: `name`, `NORAD`, `mission`, `status`. +- Количество планов по КА в выбранном интервале. +- Индикатор проблемных статусов: `REJECTED`, `EXPIRED`, `START_AMBIGUOUS`. +- Timeline persisted TGU-планов по КА. +- Стрелки между последовательными планами одного КА. +- Линию текущего времени, если `now` попадает в выбранный диапазон. +- Детали выбранного плана: + - `planId`; + - `spacecraftId`; + - КА; + - `startTime`; + - `endTime`; + - duration; + - `kppId`; + - `status`. + +### Чем управляет + +- Фильтр диапазона дат. +- Быстрые диапазоны: 24 часа, 3 суток, 7 суток. +- Refresh данных. +- Поиск по КА. +- Выбор конкретного КА. +- Выбор плана. +- Отправка решения `ACCEPTED`/`REJECTED`, но только для плана в статусе `WAITING_DECISION`. + +### Execution path решения по плану + +```text +User + | + v +TguPlanDetails + | + |-- Принять -> decision=ACCEPTED, reason=UI_TEST_ACCEPTED + | + `-- Отклонить -> decision=REJECTED, reason from prompt + | + v +sendPlanDecision() + | + v +POST /api/tgu-planning/plans/{planId}/decision + | + v +pcp-ui-service + | + v +TguPlanningService.sendDecision() + | + v +POST pcp-tgu-service /test/tgu/plans/{planId}/decision + | + v +TestPlanDecisionService.generateDecision() + | + |-- checks plan exists + |-- checks spacecraft_planning_state exists + |-- checks activePlanId == planId + |-- checks activeAttemptId exists and belongs to plan + |-- checks attempt status STARTING/STARTED + | + v +HandlePlanDecisionUseCase.handle() + | + |-- insert processed_plan_decision_event, dedup by eventId + |-- lock plan_issue_attempt by attemptId + |-- lock planned_plan by planId + |-- lock/create spacecraft_planning_state + | + |-- ACCEPTED: + | |-- attempt=ACCEPTED + | |-- plan=ACCEPTED + | |-- currentBasePlanId=planId + | |-- activePlanId/activeAttemptId cleared + | |-- enqueue plan_recalculation_job + | `-- enqueue accepted_plan_slots_update_job + | + `-- REJECTED: + |-- attempt=REJECTED + |-- activePlanId/activeAttemptId cleared + |-- if enough lead time: plan=PLANNED, issueNotBefore=now + `-- else: plan=EXPIRED, promote next plan or enqueue rebuild +``` + +Важный caveat: UI отправляет решение через test-only controller `pcp-tgu-service /test/tgu/...`. Если `tgu.test-controller.enabled=false`, эта команда перестанет работать. + +## Вкладка "Карта 2D" + +### Что отображает + +- CARTO dark basemap tiles. +- Lat/lon grid поверх тайлов. +- Полигоны заявок на съемку из `pcp-request-service`. +- Цвета заявок по `surveyType`: + - `OPTICS`; + - `RSA`; + - `COMBINED`. +- Hover tooltip по полигону. +- Выбор полигона заявки на карте. +- Детальную панель с выбранным планом и выбранным объектом карты. + +### Чем управляет + +- Панорамирование карты drag-ом. +- Zoom колесом и кнопками `+`/`-`. +- Reset view. +- Включение/выключение слоев. +- Загрузка заявок за отдельный интервал. +- Быстрые интервалы заявок: сегодня, 7 дней, 30 дней. +- Скрытие/показ КА в sidebar. + +### Что сейчас заявлено в UI, но не имеет данных + +В главной карте 2D есть переключатели слоев: + +- трасса КА; +- полоса обзора; +- работы плана; +- станции; +- маркеры КА; +- заявки на съемку. + +Но текущий `buildTgu2DMapScene()` наполняет только `request` polygons. Для остальных слоев рисовалки существуют, но данные в scene не передаются. UI показывает предупреждение: `Данные слоя недоступны в текущем API.` + +### Execution path карты 2D + +```text +User + | + v +Tgu2DMapTab + | + |-- selectedPlan / range / layers / hiddenSpacecraftIds + | + |-- load requests + | | + | v + | fetchRequestsForMap() + | | + | |-- page size = 500 + | |-- max pages = 10 + | |-- max loaded requests = 5000 + | |-- sort = beginDateTime,asc + | | + | v + | GET /api/pcp-request/v1/requests/map + | | + | v + | pcp-request-service + | | + | v + | RequestController.listRequestsForMap() + | | + | v + | RequestService.listRequestsForMap() + | | + | v + | RequestRepository.findRequests() + | | + | |-- status filter, optional + | |-- surveyType filter, optional + | |-- kpp filter, supported by backend but not passed by current UI + | |-- highPriorityTransmit filter, supported by backend but not passed by current UI + | |-- beginFrom/beginTo/endFrom/endTo + | |-- exclude DELETED by default + | | + | v + | requests table + | + v +buildTgu2DMapScene() + | + |-- parse WKT from request.geometry + |-- supports POLYGON and MULTIPOLYGON + |-- build MapPolygon objects with layer=requests + | + v +Tgu2DMapView + | + |-- drawTiles() + | | + | `-- https://{a|b|c|d}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}.png + | + |-- drawBaseMap() + | | + | `-- 30 degree lat/lon grid + | + |-- drawRequests() + | | + | `-- request polygons on canvas + | + |-- createPolygonSpatialIndex() + |-- hitTestPolygons() + | + v +Canvas + hover tooltip + selected object details +``` + +### Фильтр заявок на карте + +Для выбранного интервала `[from, to]` UI передает: + +```text +endFrom = from +beginTo = to +``` + +То есть выбираются заявки, которые пересекают интервал карты. + +## Вкладка "Редактор" + +### Что отображает + +- Выбранный КА и выбранный TGU-план. +- Черновик включений: + - `shoot` — съемка; + - `downlink` — сброс. +- Timeline черновика. +- Окна видимости цели/станции. +- Синтетическую трассу КА на карте редактора. +- Полигоны заявок из `pcp-request-service`. +- Контуры `SURVEY` из `pcp-mission-planing-service`, если endpoint `/current-plans/missions/{id}/modes` вернул режимы. +- Счетчики: + - всего включений; + - съемок; + - сбросов; + - конфликтов. + +### Что умеет делать локально + +- Добавить съемку через клик точки на карте. +- Добавить сброс по станции и окну связи. +- Перетаскивать включения по timeline. +- Менять начало/конец включения drag resize. +- Менять время через stepper. +- Дублировать включение. +- Удалять включение. +- Undo/redo. +- Проверять пересечения включений по времени. +- Проверять видимость цели/станции по синтетической орбитальной модели `passes.ts`. +- Показывать контекст других КА в rail, но сейчас `contextLanes.works` пустой. + +### Что не умеет сохранять + +Кнопка `Применить` вызывает `saveEditedPlan()`, но функция является заглушкой: + +```text +Сохранение правок плана во внешний сервис еще не реализовано в backend. +``` + +Backend endpoint для сохранения отредактированного набора включений отсутствует. + +### Execution path редактора + +```text +User + | + v +TguEditorTab + | + |-- selectedSpacecraftId / selectedPlan from TguPlanningPage + | + |-- initialize draft + | | + | v + | seedDraft(selectedPlan) + | | + | |-- if selectedPlan has embedded works: + | | `-- use embedded works + | | + | `-- else: + | `-- create fallback downlink work: + | id = {planId}:plan-window + | kind = downlink + | start/end = selected plan start/end + | stationId = selectedPlan.kppId + | + |-- load request context + | | + | v + | fetchRequestsForMap() + | | + | v + | GET /api/pcp-request/v1/requests/map + | | + | v + | pcp-request-service + | | + | v + | requests table + | + |-- load plan modes + | | + | v + | fetchPlanModes(selectedPlan.planId) + | | + | v + | GET /api/current-plans/missions/{selectedPlan.planId}/modes + | | + | v + | pcp-ui-service + | | + | v + | pcp-mission-planing-service + | | + | v + | MissionPlaningService.modesByMission() + | | + | |-- check mission exists + | |-- find modes by mission id + | |-- map SurveyModeEntity -> SurveyModeResponseDTO + | |-- map DropModeEntity -> DropModeResponseDTO + | | + | v + | mission planning DB + | + |-- editor map scene + | | + | |-- groundTrack(spacecraftId) from frontend synthetic orbit + | |-- station markers only if station lat/lon exists + | |-- plan mode SURVEY contourWkt -> planWorks polygons + | `-- request.geometry WKT -> request polygons + | + |-- local draft operations + | | + | |-- addShoot() + | |-- addDownlink() + | |-- updateWork() + | |-- deleteWork() + | |-- duplicateWork() + | |-- undo() + | |-- redo() + | |-- overlaps() + | `-- checkVisibility() + | + `-- apply draft + | + v + saveEditedPlan() + | + v + stub result only, no backend mutation +``` + +### Важные caveats редактора + +- `fetchPlanModes()` использует `selectedPlan.planId` как `missionId` для `/api/current-plans/missions/{missionId}/modes`. +- Если UUID TGU-плана не совпадает с UUID mission в `pcp-mission-planing-service`, endpoint вернет 404, а редактор просто останется без режимов плана. +- Станции строятся из `selectedPlan.kppId`, но координаты станции из backend не загружаются. Поэтому проверка видимости downlink часто возвращает конфликт `Нет координат станции для проверки видимости`. +- Орбитальная модель в редакторе frontend-only и синтетическая: параметры выводятся из hash `spacecraftId`, а не из реальной баллистики/TLE. + +## Capability matrix + +| Область | Сейчас умеет | Ограничение | +|---|---|---| +| Платформы КА | читать из TGU/NSI, показывать name/NORAD/mission/status | при падении NSI `pcp-tgu-service` может использовать stale cache, если есть | +| TGU-планы | читать persisted plans, строить timeline | UI грузит все планы и фильтрует на клиенте | +| Решение по плану | отправить `ACCEPTED`/`REJECTED` для `WAITING_DECISION` | через test-only controller; нужен active plan + active attempt | +| Карта заявок | загрузить и показать WKT-полигоны заявок | до 5000 заявок, без server-side фильтров kpp/highPriority в UI | +| Слои карты | UI-переключатели есть | кроме заявок, основные слои главной карты не получают данных | +| Редактор включений | локальный draft, drag/resize, add/delete/duplicate, undo/redo, checks | сохранения в backend нет | +| Режимы плана в редакторе | пытается читать `/current-plans/missions/{id}/modes` | возможен mismatch `planId` vs `missionId` | +| Станции | может показывать station markers только при наличии lat/lon | lat/lon станций сейчас не загружаются | +| Тайлы карты | CARTO CDN | внешний network dependency браузера | + +## Проверенные исходники + +Frontend: + +- `services/pcp-tgu-ops-ui/src/App.tsx` +- `services/pcp-tgu-ops-ui/src/api/tguApi.ts` +- `services/pcp-tgu-ops-ui/src/api/requestApi.ts` +- `services/pcp-tgu-ops-ui/src/features/tgu-planning/*` +- `services/pcp-tgu-ops-ui/src/features/tgu-map-2d/*` +- `services/pcp-tgu-ops-ui/src/features/tgu-editor/*` +- `services/pcp-tgu-ops-ui/vite.config.ts` +- `services/pcp-tgu-ops-ui/nginx.conf` + +Backend/BFF: + +- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/TguPlanningController.kt` +- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/TguPlanningService.kt` +- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/controller/CurrentPlansController.kt` +- `services/pcp-ui-service/src/main/kotlin/space/nstart/pcp/slots_service/service/MissionService.kt` + +TGU: + +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlatformController.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/PlanController.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/controller/TestTguController.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlatformService.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/PlanQueryService.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/TestPlanDecisionService.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/service/HandlePlanDecisionUseCase.kt` +- `services/pcp-tgu-service/src/main/kotlin/space/nstart/pcp_tgu_service/repository/*` + +Requests: + +- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/controller/RequestController.kt` +- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/service/RequestService.kt` +- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/repository/RequestRepository.kt` +- `services/pcp-request-service/src/main/kotlin/org/nstart/dep265/requestservice/dto/RequestApiDtos.kt` + +Mission planning: + +- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/controller/MissionController.kt` +- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/service/MissionPlaningService.kt` +- `services/pcp-mission-planing-service/src/main/kotlin/space/nstart/pcp/pcp_mission_planing_service/repository/*` +- `libs/pcp-types-lib/src/main/kotlin/space/nstart/pcp/pcp_types_lib/dto/satellite/mission/*` diff --git a/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css b/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css index e70c928..ab4094a 100644 --- a/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css +++ b/services/pcp-tgu-ops-ui/src/styles/tgu-editor.css @@ -236,6 +236,166 @@ background: #0b0d0e; } +.map-pass-selector { + position: absolute; + top: 0.55rem; + left: 0.55rem; + z-index: 20; + width: 28rem; + border: 1px solid var(--line); + border-radius: 8px; + background: rgba(23, 26, 28, 0.93); + backdrop-filter: blur(6px); + font-size: 0.78rem; + user-select: none; +} + +.map-pass-selector__header { + display: flex; + align-items: center; + gap: 0.45rem; + padding: 0.4rem 0.65rem; +} + +.map-pass-selector__title { + font-weight: 600; + color: var(--text); + flex-shrink: 0; +} + +.map-pass-selector__nums { + color: var(--accent); + font-family: var(--mono); + font-size: 0.72rem; + font-weight: 600; + flex-shrink: 0; +} + +.map-pass-selector__times { + color: var(--text-dim); + font-family: var(--mono); + font-size: 0.66rem; + flex: 1; + min-width: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.map-pass-selector__reset { + flex-shrink: 0; + border: 1px solid var(--line); + border-radius: 4px; + padding: 0.06rem 0.4rem; + background: transparent; + color: var(--text-muted); + font-size: 0.66rem; + line-height: 1.4; +} + +.map-pass-selector__reset:hover { + background: var(--bg-elevated); + color: var(--text); +} + +.map-pass-selector__body { + padding: 0.1rem 1rem 0.55rem; + cursor: pointer; +} + +/* Track line */ +.map-pass-selector__track { + position: relative; + height: 4px; + border-radius: 2px; + background: var(--line); + margin: 0.85rem 0 0; +} + +.map-pass-selector__fill { + position: absolute; + top: 0; + height: 100%; + background: var(--accent); + border-radius: 2px; + pointer-events: none; +} + +/* Orbit tick marks */ +.map-pass-selector__tick { + position: absolute; + top: -3px; + width: 2px; + height: 10px; + margin-left: -1px; + border-radius: 1px; + background: var(--line-soft); + pointer-events: none; + transition: background 0.1s; +} + +.map-pass-selector__tick.is-active { + background: rgba(104, 195, 189, 0.4); +} + +/* Draggable handles */ +.map-pass-selector__thumb { + position: absolute; + top: 50%; + width: 14px; + height: 14px; + margin-left: -7px; + margin-top: -7px; + border-radius: 50%; + background: var(--accent); + border: 2px solid var(--bg-panel); + box-shadow: 0 0 0 1px var(--accent); + cursor: grab; + z-index: 2; + transition: transform 0.08s; +} + +.map-pass-selector__thumb:active { + cursor: grabbing; + transform: scale(1.18); +} + +/* Handle labels */ +.map-pass-selector__labels { + position: relative; + height: 2.2rem; + margin-top: 0.25rem; + pointer-events: none; +} + +.map-pass-selector__label { + position: absolute; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 0.05rem; +} + +.map-pass-selector__label--right { + transform: translateX(-50%); +} + +.map-pass-selector__label-num { + font-weight: 700; + font-size: 0.72rem; + color: var(--accent); + line-height: 1; +} + +.map-pass-selector__label-time { + font-family: var(--mono); + font-size: 0.6rem; + color: var(--text-dim); + white-space: nowrap; + line-height: 1; +} + .tgu-editor-map-stage .tgu-2d-map { min-height: 6rem; }