Files
enduro-trails/src/web/app.css
claude-bot eea6c846c2
Some checks failed
CI / lint (push) Failing after 5s
CI / test (push) Failing after 6s
CI / build (push) Has been skipped
CI / lint (pull_request) Failing after 4s
CI / test (pull_request) Failing after 5s
CI / build (pull_request) Has been skipped
feat(gps-tracks): GPX download from public track popup
Реализация ET-011: кнопка «Скачать GPX» в popup публичного GPS-трека и
новый эндпоинт GET /api/gps-tracks/{track_id}/download (GPX 1.1 +
Content-Disposition с UTF-8 именем по RFC 5987). Реэкспорт защищён
per-source флагом `download_allowed` в `config/gps_sources.yaml`
(default-deny, MVP whitelist = `osm`).

Backend:
- `src/api/gps_tracks/export.py` — чистый stdlib-builder GPX 1.1
  (`build_gpx`) + санитизация имени файла (`safe_filename`, RFC 5987).
- `src/api/gps_tracks/endpoint.py` — новый route с проверками
  400 / 403 / 404 / 413; cap 200 000 точек (REQ-NF-02).
- `src/api/gps_tracks/config.py` — `load_download_allowed_sources()`
  читает YAML, default-deny при отсутствии поля; fallback на `{"osm"}`
  при отсутствии конфига.
- `src/api/main.py` — пробрасывает `GPS_SOURCES_CONFIG_PATH` в router.

Frontend:
- `src/web/gps_tracks.js` — кнопка в `_renderTrackPopupHtml`,
  обработчик `_downloadPublicTrack` (fetch + Blob + a.download — тот же
  паттерн, что в `app.js::downloadGPX`, R-1 митигирован), парсер
  `_parseFilenameFromCD` для RFC 5987, маппинг ошибок
  `_handleDownloadError` (403/404/413/5xx → showToast).
- `src/web/app.css` — стиль кнопки, 32×32 CSS px (REQ-NF-04).

Тесты:
- 13 unit для GPX-builder (UT-01/02/03/05; XSD-валидация против
  `tests/fixtures/gpx-1.1/gpx.xsd`).
- 10 unit для `safe_filename` (UT-04).
- 11 integration для download-эндпоинта (IT-01..08 +
  ANY-rule license check + default-deny без конфига).

ADR-014 (gpx-download-endpoint), ADR-015 (source-redistribution-policy).
Refs: ET-011

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-03 20:59:53 +00:00

1344 lines
48 KiB
CSS
Raw 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.
/* ═══════════════════════════════════════════════════════════════════
Enduro Trails — Design System v5.0
Phase 5: Dual themes, skeleton, swipe, desktop, animations
═══════════════════════════════════════════════════════════════════ */
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
/* ── Dark Theme (default) ───── */
body.theme-dark {
--bg: #0D1117;
--surface: #161B22;
--surface2: #21262D;
--surface3: #2D333B;
--border: #30363D;
--border2: #444C56;
--text: #E6EDF3;
--text2: #8B949E;
--text3: #484F58;
--accent: #FF6B00;
--accent-h: #FF8C2A;
--accent-bg: rgba(255,107,0,0.12);
--gold: #FFD700;
--gold-bg: rgba(255,215,0,0.12);
--red: #FF3B1F;
--red-bg: rgba(255,59,31,0.12);
--success: #2EA043;
--shadow: 0 4px 24px rgba(0,0,0,0.6);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.4);
--overlay: rgba(0,0,0,0.6);
}
/* ── Light Theme ────────────── */
body.theme-light {
--bg: #F0EFE8;
--surface: #FFFFFF;
--surface2: #F5F4EE;
--surface3: #ECEAE2;
--border: #D4D0C8;
--border2: #B8B4AA;
--text: #1C1C1A;
--text2: #6B6760;
--text3: #9C9890;
--accent: #D95200;
--accent-h: #BF4800;
--accent-bg: rgba(217,82,0,0.1);
--gold: #A07800;
--gold-bg: rgba(160,120,0,0.1);
--red: #B82200;
--red-bg: rgba(184,34,0,0.1);
--success: #1A6B2A;
--shadow: 0 4px 24px rgba(0,0,0,0.15);
--shadow-sm: 0 2px 8px rgba(0,0,0,0.1);
--overlay: rgba(0,0,0,0.3);
}
/* ── Base ─────────────────────────────────────── */
html, body {
height: 100%;
font-family: -apple-system, 'SF Pro Text', 'Segoe UI', system-ui, sans-serif;
font-size: 14px;
background: var(--bg);
color: var(--text);
overflow: hidden;
-webkit-font-smoothing: antialiased;
transition: background 0.3s, color 0.3s;
}
#map { position: fixed; inset: 0; z-index: 0; }
/* ── MapLibre nav controls position ──────────── */
.maplibregl-ctrl-top-left {
top: calc(max(env(safe-area-inset-top, 0px), 12px) + 8px) !important;
left: 12px !important;
}
/* ── Waypoint inline search ───────────────────── */
.wl-search-btn {
background: none;
border: none;
color: var(--text3);
cursor: pointer;
padding: 4px;
border-radius: 6px;
display: flex;
align-items: center;
flex-shrink: 0;
transition: color 0.15s;
}
.wl-search-btn:hover, .wl-search-btn:active { color: var(--accent); }
.wl-search-panel {
padding: 6px 8px 4px 8px;
border-top: 1px solid var(--border);
}
.wl-search-input {
width: 100%;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 13px;
padding: 7px 10px;
outline: none;
box-sizing: border-box;
}
.wl-search-input:focus { border-color: var(--accent); }
.wl-search-results {
margin-top: 4px;
max-height: 180px;
overflow-y: auto;
}
.wl-search-result-item {
padding: 8px 10px;
cursor: pointer;
border-radius: 6px;
font-size: 13px;
color: var(--text);
}
.wl-search-result-item:hover, .wl-search-result-item:active {
background: var(--surface2);
}
.wl-search-result-name { font-weight: 500; }
.wl-search-result-sub { font-size: 11px; color: var(--text2); margin-top: 1px; }
/* ── Map Control Buttons ──────────────────────── */
#map-controls-r {
position: fixed; right: 12px;
bottom: calc(80px + env(safe-area-inset-bottom, 0px) + 12px);
display: flex; flex-direction: column; gap: 8px; z-index: 400;
transition: bottom 0.2s ease;
}
.map-btn {
width: 48px; height: 48px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 12px;
color: var(--text2);
display: flex; align-items: center; justify-content: center;
cursor: pointer; box-shadow: var(--shadow-sm);
transition: all 0.15s;
-webkit-tap-highlight-color: transparent;
position: relative;
}
.map-btn svg { width: 20px; height: 20px; }
.map-btn:active { transform: scale(0.94); background: var(--surface2); }
.map-btn.active { background: var(--accent); color: #fff; border-color: var(--accent); }
/* ── Bottom Toolbar ───────────────────────────── */
#toolbar {
position: fixed; bottom: 0; left: 0; right: 0;
height: calc(68px + env(safe-area-inset-bottom, 0px));
padding-bottom: env(safe-area-inset-bottom, 0px);
background: var(--surface);
border-top: 1px solid var(--border);
display: flex; align-items: center; justify-content: space-around;
z-index: 300;
box-shadow: 0 -4px 20px rgba(0,0,0,0.2);
transition: background 0.3s, border-color 0.3s;
}
.tb-btn {
flex: 1;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
gap: 3px; height: 56px;
border: none; background: none;
color: var(--text3);
font-size: 9px; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.06em;
border-radius: 10px; cursor: pointer;
transition: color 0.15s, background 0.15s, transform 0.1s;
-webkit-tap-highlight-color: transparent;
padding: 0 4px;
}
.tb-btn svg { width: 22px; height: 22px; margin-bottom: 1px; transition: transform 0.1s; }
.tb-btn:active { background: var(--surface2); transform: scale(0.94); }
.tb-btn.active {
color: #fff; background: var(--accent); border-radius: 10px;
}
.tb-btn.active svg { stroke: #fff; }
.tb-btn span { line-height: 1; }
/* ── Bottom Sheet ─────────────────────────────── */
.bottom-sheet {
position: fixed; bottom: 0; left: 0; right: 0;
background: var(--surface);
border-radius: 20px 20px 0 0;
border-top: 1px solid var(--border);
z-index: 400; max-height: 78vh;
overflow-y: auto; overscroll-behavior: contain;
-webkit-overflow-scrolling: touch;
transform: translateY(100%);
transition: transform 0.3s cubic-bezier(0.32, 0, 0.15, 1);
padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
touch-action: pan-y;
}
.bottom-sheet.open { transform: translateY(0); }
.bottom-sheet.swiping { transition: none; }
.sheet-handle {
width: 36px; height: 4px;
background: var(--border2);
border-radius: 2px; margin: 12px auto 0; cursor: grab;
}
.sheet-header {
display: flex; align-items: center;
padding: 14px 16px 12px; gap: 10px;
border-bottom: 1px solid var(--border);
}
.sheet-header svg { width: 20px; height: 20px; stroke: var(--accent); flex-shrink: 0; }
.sheet-header h2 { flex: 1; font-size: 15px; font-weight: 700; color: var(--text); letter-spacing: 0.02em; }
.sheet-close {
width: 32px; height: 32px; border-radius: 8px;
background: var(--surface2); border: 1px solid var(--border);
color: var(--text2);
display: flex; align-items: center; justify-content: center;
cursor: pointer; flex-shrink: 0; transition: all 0.15s;
}
.sheet-close svg { width: 16px; height: 16px; }
.sheet-close:active { background: var(--surface3); color: var(--text); }
.sheet-body { padding: 14px 16px; }
.sheet-hint { font-size: 13px; color: var(--text2); text-align: center; padding: 16px 0 8px; line-height: 1.5; }
#sheet-backdrop {
position: fixed; inset: 0;
background: var(--overlay);
z-index: 390; opacity: 0; pointer-events: none;
transition: opacity 0.3s;
}
#sheet-backdrop.visible { opacity: 1; pointer-events: auto; }
/* Allow map clicks through backdrop when route/ruler/marker/recon/link/scenic mode is active */
body.has-map-mode #sheet-backdrop.visible { pointer-events: none; }
/* ── Section Label ────────────────────────────── */
.section-label { font-size: 10px; font-weight: 800; color: var(--text3); text-transform: uppercase; letter-spacing: 0.12em; margin-bottom: 8px; margin-top: 4px; }
/* ── Waypoints Row ────────────────────────────── */
.waypoints-row { display: flex; align-items: center; gap: 4px; overflow-x: auto; padding: 0 0 4px; scrollbar-width: none; }
.waypoints-row::-webkit-scrollbar { display: none; }
.wp-chip { display: flex; align-items: center; gap: 6px; background: var(--surface2); border: 1px solid var(--border); border-radius: 10px; padding: 7px 10px; flex-shrink: 0; max-width: 140px; cursor: pointer; transition: border-color 0.15s; }
.wp-chip:active { border-color: var(--accent); }
.wp-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.wp-label { font-size: 12px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wp-arrow { color: var(--text3); font-size: 18px; flex-shrink: 0; padding: 0 1px; }
.wp-add { display: flex; align-items: center; gap: 6px; background: none; border: 1.5px dashed var(--border2); border-radius: 10px; padding: 7px 12px; font-size: 12px; font-weight: 600; color: var(--text2); flex-shrink: 0; cursor: pointer; transition: border-color 0.15s, color 0.15s; }
.wp-add:active { border-color: var(--accent); color: var(--accent); }
/* ── Waypoints List ───────────────────────────── */
#waypoints-list { display: flex; flex-direction: column; margin-bottom: 10px; }
.wl-item {
display: flex; align-items: center; gap: 8px;
padding: 8px 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
border-bottom: 1px solid var(--border);
position: relative;
}
.wl-item:last-child { border-bottom: none; }
.wl-drag-handle {
width: 20px; height: 28px;
display: flex; align-items: center; justify-content: center;
color: var(--text3); cursor: grab; flex-shrink: 0;
touch-action: none;
-webkit-tap-highlight-color: transparent;
}
.wl-drag-handle svg { width: 16px; height: 16px; }
.wl-item.dragging {
opacity: 0.4;
background: var(--surface);
border-radius: 4px;
}
.wl-item.drag-over-top { border-top: 2px solid var(--accent); }
.wl-item.drag-over-bottom { border-bottom: 2px solid var(--accent); }
.wl-pin { flex-shrink: 0; display: flex; align-items: center; }
.wl-info { display: flex; flex-direction: column; flex: 1; min-width: 0; }
.wl-label {
font-size: 13px; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.wl-dist { font-size: 11px; color: var(--text3); margin-top: 1px; }
.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; }
/* Sheet icon buttons (header) */
.sheet-icon-btn {
width: 32px; height: 32px;
display: flex; align-items: center; justify-content: center;
background: none; border: none; color: var(--text3);
border-radius: 8px; cursor: pointer; padding: 0;
flex-shrink: 0;
transition: background 0.15s, color 0.15s;
-webkit-tap-highlight-color: transparent;
}
.sheet-icon-btn svg { width: 18px; height: 18px; }
.sheet-icon-btn:active { background: var(--surface2); }
.sheet-icon-btn.danger { color: var(--red); }
.sheet-icon-btn.danger:active { background: var(--red); color: #fff; }
/* Add waypoint row */
.wl-add { cursor: pointer; }
.wl-add:active { background: var(--surface); }
.wl-add .wl-pin svg path { fill: var(--text3) !important; }
.wl-add .wl-label { color: var(--text3); }
/* ── Route Status ─────────────────────────────── */
#route-status { font-size: 13px; color: var(--text2); padding: 8px 0; display: flex; align-items: center; gap: 6px; min-height: 20px; }
/* ── Route Cards ──────────────────────────────── */
#route-cards, #link-cards, #scenic-cards { display: flex; flex-direction: column; gap: 8px; margin-top: 4px; }
.route-card {
background: var(--surface2);
border: 1.5px solid var(--border);
border-left: 4px solid transparent;
border-radius: 10px;
padding: 10px 12px;
margin-bottom: 0;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
-webkit-tap-highlight-color: transparent;
animation: cardFadeIn 0.2s ease-out both;
}
.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; font-variant-numeric: tabular-nums; }
.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; transition: width 0.4s; }
.rc-bar-asphalt { background: var(--text3); }
.rc-bar-label { font-size: 11px; color: var(--text2); }
.rc-stats { display: flex; flex-wrap: wrap; gap: 5px; }
/* Stat pills */
.stat-pill { display: inline-flex; align-items: center; gap: 4px; border-radius: 20px; padding: 3px 9px; font-size: 11px; font-weight: 700; letter-spacing: 0.02em; }
.stat-pill.dirt { background: var(--gold-bg); color: var(--gold); }
.stat-pill.asphalt { background: var(--surface3); color: var(--text2); }
.stat-pill.path { background: var(--red-bg); color: var(--red); }
/* ── Primary Button ───────────────────────────── */
.btn-primary { width: 100%; height: 48px; background: var(--accent); color: #fff; border: none; border-radius: 14px; font-size: 15px; font-weight: 700; display: flex; align-items: center; justify-content: center; gap: 8px; cursor: pointer; transition: background 0.15s, transform 0.1s; letter-spacing: 0.02em; margin-top: 12px; }
.btn-primary svg { width: 18px; height: 18px; }
.btn-primary:active { background: var(--accent-h); transform: scale(0.98); }
.btn-primary:disabled { opacity: 0.5; pointer-events: none; }
/* ── Segment Control ──────────────────────────── */
.seg-control { display: flex; gap: 4px; background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 4px; margin-bottom: 12px; }
.seg-btn { flex: 1; height: 34px; background: none; border: none; border-radius: 9px; font-size: 13px; font-weight: 600; color: var(--text2); cursor: pointer; transition: all 0.15s; }
.seg-btn.active { background: var(--accent); color: #fff; box-shadow: 0 2px 8px rgba(255,107,0,0.35); }
.seg-btn:not(.active):active { background: var(--surface3); }
.dist-custom { height: 34px; width: 70px; background: var(--surface2); border: 1px solid var(--border); border-radius: 9px; color: var(--text); font-size: 13px; font-weight: 600; text-align: center; outline: none; flex-shrink: 0; }
.dist-custom:focus { border-color: var(--accent); }
/* ── Recon Results ────────────────────────────── */
.recon-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-bottom: 14px; }
.recon-stat { background: var(--surface2); border: 1px solid var(--border); border-radius: 12px; padding: 10px 12px; }
.rs-value { font-size: 22px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; line-height: 1; margin-bottom: 3px; }
.rs-value.gold { color: var(--gold); }
.rs-value.red { color: var(--red); }
.rs-label { font-size: 11px; color: var(--text2); font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; }
.poi-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid var(--border); }
.poi-row:last-child { border-bottom: none; }
.poi-row-label { font-size: 13px; color: var(--text); display: flex; align-items: center; gap: 8px; }
.poi-row-count { font-size: 16px; font-weight: 800; color: var(--accent); font-variant-numeric: tabular-nums; }
.poi-icon { width: 28px; height: 28px; border-radius: 8px; background: var(--surface2); display: flex; align-items: center; justify-content: center; font-size: 14px; }
/* ── Scenic POI ───────────────────────────────── */
.scenic-poi-item { display: flex; align-items: center; gap: 8px; font-size: 12px; color: var(--text2); padding: 3px 0; }
.scenic-score-bar { height: 4px; border-radius: 2px; background: var(--surface3); overflow: hidden; margin: 6px 0; }
.scenic-score-fill { height: 100%; background: var(--gold); border-radius: 2px; }
/* ── Link Points ──────────────────────────────── */
.link-points { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; }
.link-pt { display: flex; align-items: center; gap: 8px; background: var(--surface2); border: 1.5px solid var(--border); border-radius: 10px; padding: 10px 12px; }
.link-pt-num { width: 24px; height: 24px; border-radius: 50%; background: var(--accent); color: #fff; font-size: 12px; font-weight: 800; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
.link-pt-label { font-size: 13px; color: var(--text); flex: 1; }
.link-pt.empty .link-pt-num { background: var(--surface3); color: var(--text3); }
.link-pt.empty .link-pt-label { color: var(--text3); }
#link-status { font-size: 13px; color: var(--text2); padding: 4px 0 10px; }
/* ── Scenic Config ───────────────────────────── */
#scenic-status { font-size: 13px; color: var(--text2); padding: 6px 0; display: flex; align-items: center; gap: 6px; }
.dist-row { display: flex; gap: 4px; align-items: center; margin-bottom: 4px; }
/* ── Marker Dialog ────────────────────────────── */
#marker-dialog { position: fixed; inset: 0; z-index: 500; display: flex; align-items: flex-end; justify-content: center; padding-bottom: env(safe-area-inset-bottom, 0px); pointer-events: none; opacity: 0; transition: opacity 0.2s; }
#marker-dialog.open { pointer-events: auto; opacity: 1; }
.marker-dialog-inner { background: var(--surface); border-radius: 20px 20px 0 0; border-top: 1px solid var(--border); padding: 0 16px 20px; width: 100%; transform: translateY(30px); transition: transform 0.25s cubic-bezier(0.32, 0, 0.15, 1); }
#marker-dialog.open .marker-dialog-inner { transform: translateY(0); }
.marker-type-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 12px 0; }
.marker-type-btn { background: var(--surface2); border: 1.5px solid var(--border); border-radius: 12px; padding: 12px 8px; cursor: pointer; transition: all 0.15s; display: flex; flex-direction: column; align-items: center; gap: 5px; -webkit-tap-highlight-color: transparent; }
.marker-type-btn:active { border-color: var(--accent); background: var(--accent-bg); }
.marker-type-btn .mt-icon { font-size: 24px; }
.marker-type-btn .mt-label { font-size: 11px; font-weight: 600; color: var(--text2); text-transform: uppercase; letter-spacing: 0.06em; }
/* ── No Data Warning ─────────────────────────── */
#no-data-warning { display: none; position: fixed; bottom: 80px; left: 12px; right: 12px; background: var(--red-bg); border: 1px solid var(--red); border-radius: 12px; padding: 10px 14px; font-size: 13px; color: var(--red); z-index: 200; }
#no-data-warning.visible { display: block; }
/* ── Skeleton Loading ────────────────────────── */
.skeleton {
background: linear-gradient(90deg, var(--surface2) 0%, var(--surface3) 50%, var(--surface2) 100%);
background-size: 200% 100%;
animation: shimmer 1.4s infinite;
border-radius: 8px;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-card {
background: var(--surface2);
border: 1.5px solid var(--border);
border-radius: 14px;
padding: 14px;
margin-bottom: 8px;
}
.skeleton-line {
height: 14px;
margin-bottom: 8px;
border-radius: 4px;
}
.skeleton-line.w60 { width: 60%; }
.skeleton-line.w40 { width: 40%; }
.skeleton-line.w80 { width: 80%; }
.skeleton-line.h20 { height: 20px; }
/* ── Ruler ───────────────────────────────────── */
#ruler-info {
position: fixed;
top: calc(max(env(safe-area-inset-top,0px),12px) + 58px);
left: 50%;
transform: translateX(-50%);
width: fit-content;
max-width: 320px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 8px;
padding: 5px 10px;
font-size: 13px; color: var(--text);
font-weight: 600; z-index: 200;
display: none; box-shadow: var(--shadow-sm);
}
#ruler-info.visible { display: flex; align-items: center; gap: 6px; }
#ruler-info #ruler-dist { flex: 1; }
.ruler-action-btn {
flex-shrink: 0;
height: 32px;
min-width: 32px;
padding: 4px 10px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 13px;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
display: flex;
align-items: center;
justify-content: center;
-webkit-tap-highlight-color: transparent;
}
.ruler-action-btn--danger {
color: var(--danger, #e05252);
border-color: var(--danger, #e05252);
font-size: 16px;
padding: 4px 8px;
}
/* ── Ruler toast hint ────────────────────────── */
#ruler-toast {
position: fixed;
top: calc(max(env(safe-area-inset-top,0px),12px) + 100px);
left: 50%;
transform: translateX(-50%);
background: rgba(20,20,20,0.82);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 8px 16px;
border-radius: 20px;
z-index: 210;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
white-space: nowrap;
}
#ruler-toast.visible { opacity: 1; }
/* ── Fix: MapLibre markers must stay absolute ────── */
.maplibregl-marker {
position: absolute !important;
}
/* ── Waypoint Markers ─────────────────────────── */
.route-waypoint-marker { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4)); width: 28px; height: 36px; cursor: grab; display: block; }
.route-waypoint-marker:active { cursor: grabbing; }
.named-marker-el { font-size: 22px; cursor: pointer; filter: drop-shadow(0 2px 4px rgba(0,0,0,0.5)); user-select: none; line-height: 1; display: block; width: 28px; height: 28px; text-align: center; }
/* ═══════════════════════════════════════════════════
TASK 5: Desktop Layout (≥768px)
═══════════════════════════════════════════════════ */
@media (min-width: 768px) {
#toolbar {
flex-direction: column;
width: 72px; height: auto;
right: auto; left: 0;
top: 0; bottom: 0;
border-right: 1px solid var(--border);
border-top: none;
padding: 80px 0 20px;
justify-content: flex-start;
gap: 4px;
}
.tb-btn { width: 64px; height: 56px; flex: none; }
.bottom-sheet {
left: 72px; right: auto;
width: 380px; max-width: 400px;
max-height: 100vh;
border-radius: 0 20px 0 0;
border-top: none;
border-right: 1px solid var(--border);
top: 0; bottom: 0;
transform: translateX(-120%);
}
.bottom-sheet.open { transform: translateX(0); }
.bottom-sheet.swiping { transition: none; }
#map-controls-r { right: 12px; bottom: 12px; }
#sheet-backdrop { display: none; }
#ruler-info { max-width: 320px; }
}
/* ═══════════════════════════════════════════════════
TASK 6: Micro-animations
═══════════════════════════════════════════════════ */
@keyframes cardFadeIn {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.route-card:nth-child(1) { animation-delay: 0ms; }
.route-card:nth-child(2) { animation-delay: 60ms; }
.route-card:nth-child(3) { animation-delay: 120ms; }
.route-card:nth-child(4) { animation-delay: 180ms; }
.route-card:nth-child(5) { animation-delay: 240ms; }
/* Marker pop-in animation */
@keyframes markerPopIn {
from { transform: scale(0); opacity: 0; }
to { transform: scale(1); opacity: 1; }
}
/* marker-anim НЕ применять к элементам-обёрткам MapLibre — только к внутренним элементам */
.marker-anim-inner { animation: markerPopIn 0.2s cubic-bezier(0.18, 0.89, 0.32, 1.28) both; }
/* ── Onboarding (empty waypoints state) ─────────── */
.wl-onboarding {
padding: 4px 0;
}
.wl-onboard-field {
display: flex;
align-items: flex-start;
gap: 10px;
padding: 8px 12px 8px 0;
}
.wl-onboard-input {
flex: 1;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
color: var(--text);
font-size: 14px;
padding: 8px 12px;
outline: none;
box-sizing: border-box;
}
.wl-onboard-input:focus { border-color: var(--accent); }
.wl-onboard-hint {
text-align: center;
font-size: 12px;
color: var(--text3);
padding: 4px 0 8px;
}
/* ── Misc ────────────────────────────────────── */
.text-accent { color: var(--accent); }
.text-gold { color: var(--gold); }
.text-red { color: var(--red); }
.text-muted { color: var(--text2); }
.mt-8 { margin-top: 8px; }
.mt-12 { margin-top: 12px; }
.mb-8 { margin-bottom: 8px; }
.cursor-crosshair .maplibregl-canvas { cursor: crosshair !important; }
/* ── My Location Marker ──────────────────────── */
.my-location-marker { position: relative; width: 20px; height: 20px; }
.my-location-dot {
position: absolute; top: 50%; left: 50%;
width: 12px; height: 12px;
background: #4285f4; border: 2px solid #fff;
border-radius: 50%;
transform: translate(-50%, -50%);
box-shadow: 0 2px 6px rgba(66,133,244,0.6);
}
.my-location-pulse {
position: absolute; top: 50%; left: 50%;
width: 30px; height: 30px;
background: rgba(66,133,244,0.3);
border-radius: 50%;
transform: translate(-50%, -50%);
animation: pulse-ring 2s ease-out infinite;
}
@keyframes pulse-ring {
0% { transform: translate(-50%, -50%) scale(0.5); opacity: 1; }
100% { transform: translate(-50%, -50%) scale(2); opacity: 0; }
}
/* ── MapLibre popup theme overrides ──────────── */
.maplibregl-popup-content {
background: var(--surface) !important;
color: var(--text) !important;
border: 1px solid var(--border) !important;
border-radius: 12px !important;
padding: 12px !important;
font-size: 13px;
box-shadow: var(--shadow) !important;
}
.maplibregl-popup-tip {
border-top-color: var(--surface) !important;
}
.maplibregl-popup-close-button {
color: var(--text2) !important;
font-size: 18px !important;
right: 6px !important; top: 4px !important;
}
.popup-title { font-size: 14px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.popup-row { display: flex; justify-content: space-between; padding: 2px 0; font-size: 12px; }
.popup-key { color: var(--text2); }
.popup-val { color: var(--text); font-weight: 600; }
/* Route card legacy styles (compat) */
.route-card-header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
.route-color-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.route-card-title { flex: 1; font-size: 13px; font-weight: 700; color: var(--text); }
.route-card-dist { font-size: 14px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.route-card-time { font-size: 12px; color: var(--text2); font-variant-numeric: tabular-nums; }
.route-coverage-bar { height: 5px; border-radius: 3px; background: var(--surface3); overflow: hidden; margin-bottom: 8px; display: flex; }
.route-coverage-bar > div { height: 100%; transition: width 0.4s; }
.route-card-summary { font-size: 12px; color: var(--text2); margin-bottom: 6px; }
.route-card-details { margin-top: 6px; border-top: 1px solid var(--border); padding-top: 6px; }
.route-stat-row { font-size: 12px; color: var(--text2); padding: 2px 0; }
.route-details-toggle { width: 100%; background: none; border: none; color: var(--accent); font-size: 12px; font-weight: 600; cursor: pointer; padding: 6px 0 0; text-align: left; }
.waypoint-row { display: flex; align-items: center; gap: 8px; padding: 6px 8px; background: var(--surface2); border: 1px solid var(--border); border-radius: 8px; margin-bottom: 4px; transition: border-color 0.15s; }
.waypoint-row.drag-over { border-color: var(--accent); }
.waypoint-label { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border-radius: 50%; font-size: 11px; font-weight: 700; color: #fff; flex-shrink: 0; }
.waypoint-label.start { background: var(--success); }
.waypoint-label.end { background: var(--red); }
.waypoint-label.mid { background: #0066ff; }
.waypoint-coords { flex: 1; font-size: 12px; color: var(--text2); font-variant-numeric: tabular-nums; }
.waypoint-remove { width: 24px; height: 24px; border: none; background: none; color: var(--text3); cursor: pointer; font-size: 14px; border-radius: 4px; display: flex; align-items: center; justify-content: center; }
.waypoint-remove:hover { background: var(--red-bg); color: var(--red); }
#btn-add-waypoint { width: 100%; height: 36px; background: var(--surface2); border: 1.5px dashed var(--border2); border-radius: 10px; color: var(--text2); font-size: 12px; font-weight: 600; cursor: pointer; margin-top: 4px; display: flex; align-items: center; justify-content: center; gap: 6px; transition: border-color 0.15s; }
#btn-add-waypoint:hover { border-color: var(--accent); color: var(--accent); }
#btn-build-route { width: 100%; height: 42px; background: var(--accent); color: #fff; border: none; border-radius: 10px; font-size: 14px; font-weight: 700; cursor: pointer; margin-top: 8px; transition: background 0.15s; }
#btn-build-route:active { background: var(--accent-h); }
/* ── Mini Route Bar ───────────────────────── */
#sheet-route-mini {
position: fixed;
bottom: 72px; left: 0; right: 0;
height: 64px;
background: var(--surface);
border-top: 1px solid var(--border);
border-radius: 14px 14px 0 0;
z-index: 350;
display: none;
flex-direction: column;
align-items: center;
box-shadow: 0 -4px 16px var(--shadow);
}
#sheet-route-mini.visible { display: flex; }
#sheet-route-mini .mini-handle {
width: 32px; height: 4px;
background: var(--border2, var(--border));
border-radius: 2px;
margin: 7px auto 0;
flex-shrink: 0;
}
.mini-route-info {
display: flex; align-items: center;
gap: 10px; padding: 0 16px;
flex: 1; width: 100%;
}
.mini-route-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
.mini-route-text { flex: 1; min-width: 0; }
.mini-route-label { font-size: 13px; font-weight: 700; color: var(--text); }
.mini-route-stats { font-size: 11px; color: var(--text2); }
.mini-route-arrows { display: flex; gap: 6px; flex-shrink: 0; margin-left: 8px; }
.mini-arrow {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
background: var(--surface2); border: 1px solid var(--border);
border-radius: 10px; font-size: 22px; color: var(--text2);
cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent;
}
.mini-arrow:active { background: var(--accent); color: #fff; border-color: var(--accent); }
.mini-add-btn {
width: 36px; height: 36px;
display: flex; align-items: center; justify-content: center;
background: var(--accent); border: none;
border-radius: 10px; color: #fff;
cursor: pointer; flex-shrink: 0;
margin-left: 4px;
-webkit-tap-highlight-color: transparent;
}
.mini-add-btn:active { opacity: 0.8; transform: scale(0.94); }
/* ── Route onboarding mini-bar ───────────────── */
#mini-onboard-pin svg {
width: 22px;
height: 28px;
}
@media (min-width: 768px) {
#sheet-route-mini { left: 72px; width: 380px; right: auto; border-radius: 0 14px 0 0; }
}
/* ── Route Loading Spinner ───────────────────── */
.route-loading {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 32px 16px;
}
.route-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--border);
border-top-color: var(--accent);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
/* ── Moto Wheel Loading Indicator ────────────── */
.moto-wheel {
width: 32px; height: 32px;
flex-shrink: 0;
display: none;
transform-origin: center;
}
.moto-wheel.spinning {
display: block;
animation: wheelSpin 0.8s linear infinite;
}
@keyframes wheelSpin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* ═══════════════════════════════════════════
Terrain Layer (Phase 5.4)
═══════════════════════════════════════════ */
/* Terrain toggle button active state */
#terrain-toggle.active {
color: var(--accent, #4CAF50);
background: rgba(76, 175, 80, 0.15);
}
/* Terrain popup */
.terrain-popup {
position: fixed;
z-index: 500;
background: var(--surface, #1e1e1e);
border: 1px solid var(--border, rgba(255,255,255,0.12));
border-radius: 12px;
padding: 12px 14px;
min-width: 160px;
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
user-select: none;
}
.terrain-popup-title {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--text2, rgba(255,255,255,0.5));
margin-bottom: 10px;
}
.terrain-checkbox {
display: flex;
align-items: center;
gap: 10px;
padding: 8px 4px;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
cursor: pointer;
font-size: 15px;
color: var(--text, #fff);
border-radius: 6px;
}
.terrain-checkbox span {
font-size: 15px;
line-height: 1.3;
}
.terrain-checkbox:hover {
color: var(--accent, #4CAF50);
}
.terrain-checkbox input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: var(--accent, #4CAF50);
cursor: pointer;
flex-shrink: 0;
}
/* Light theme overrides */
.theme-light .terrain-popup {
background: var(--surface, #fff);
border-color: var(--border, rgba(0,0,0,0.12));
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
}
.theme-light .terrain-popup-title {
color: var(--text2, rgba(0,0,0,0.5));
}
.theme-light .terrain-checkbox {
color: var(--text, #111);
}
/* Terrain hillshade hint & disabled state */
.terrain-hint {
display: block;
font-size: 11px;
color: var(--accent, #4CAF50);
font-style: italic;
padding: 4px 0 2px 28px;
line-height: 1.2;
}
.terrain-checkbox.disabled {
opacity: 0.45;
pointer-events: none;
cursor: not-allowed;
}
.terrain-checkbox.disabled input[type="checkbox"] {
cursor: not-allowed;
}
/* ── ET-007: переключатель подложки (Схема/Спутник) в попапе рельефа ── */
.terrain-base-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0 2px;
}
.terrain-base-label {
font-size: 12px;
color: var(--text2);
flex-shrink: 0;
}
.terrain-base-row .seg-control {
flex: 1;
margin-bottom: 0;
}
.base-seg .seg-btn {
font-size: 12px;
height: 34px;
}
/* ET-007 P1-5 / ADR-004 §8: пока активен «Спутник», скрыть UI-кнопку
«Базовая карта» (#btn-basemap) — гибридный режим (схема поверх
спутника) out of scope BRD §3. JS добавляет/снимает класс
.satellite-active на <body> в applyBaseLayer(). На «Схеме» — кнопка
снова видна (если она присутствует в текущей вёрстке). */
body.satellite-active #btn-basemap {
display: none !important;
}
/* ── ET-005: переключатель единиц измерения (км/мили) в попапе рельефа ── */
.terrain-unit-row {
padding: 8px 4px 2px;
}
.terrain-unit-label {
display: block;
font-size: 15px;
line-height: 1.3;
color: var(--text, #fff);
margin-bottom: 8px;
}
.theme-light .terrain-unit-label {
color: var(--text, #111);
}
/* Сегментированный переключатель внутри попапа — без нижнего отступа,
он последний элемент (см. .seg-control в блоке Segment Control). */
.terrain-unit-row .seg-control {
margin-bottom: 0;
}
/* ── Scale + Zoom bar (one line, top-right) ───────── */
#scale-zoom-bar {
position: absolute;
top: calc(max(env(safe-area-inset-top, 0px), 8px) + 4px);
right: 12px;
display: flex;
align-items: center;
gap: 6px;
z-index: 10;
pointer-events: none;
}
.szb-scale {
height: 16px;
border: 1.5px solid rgba(255,255,255,0.8);
border-top: none;
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
}
.szb-label {
font-size: 10px;
font-weight: 500;
color: rgba(255,255,255,0.9);
text-shadow: 0 0 3px rgba(0,0,0,0.8), 0 1px 2px rgba(0,0,0,0.6);
white-space: nowrap;
padding: 0 4px;
}
.szb-zoom {
font-size: 11px;
font-weight: 600;
color: rgba(255,255,255,0.85);
text-shadow: 0 0 3px rgba(0,0,0,0.8), 0 1px 2px rgba(0,0,0,0.6);
white-space: nowrap;
}
/* ── Search panel ───────────────────────────── */
#search-panel {
position: fixed;
bottom: calc(68px + env(safe-area-inset-bottom, 0px));
left: 0; right: 0;
background: var(--surface);
border-top: 1px solid var(--border);
z-index: 350;
padding: 12px 16px;
box-shadow: 0 -4px 20px rgba(0,0,0,0.15);
}
.search-panel-inner {
display: flex;
gap: 8px;
align-items: center;
}
#standalone-search-input {
flex: 1;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
padding: 10px 14px;
font-size: 15px;
color: var(--text1);
outline: none;
}
#standalone-search-input:focus {
border-color: var(--accent);
}
#search-close-btn {
background: none;
border: none;
color: var(--text3);
font-size: 20px;
cursor: pointer;
padding: 4px 8px;
}
#standalone-search-results {
max-height: 240px;
overflow-y: auto;
margin-top: 8px;
}
#standalone-search-results .search-result-item {
padding: 10px 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.15s;
}
#standalone-search-results .search-result-item:hover,
#standalone-search-results .search-result-item:active {
background: var(--surface2);
}
#standalone-search-results .search-result-name {
font-size: 14px;
font-weight: 500;
color: var(--text1);
}
#standalone-search-results .search-result-sub {
font-size: 12px;
color: var(--text3);
margin-top: 2px;
}
/* ─── Zoom controls ──────────────────────────────────────────────────────── */
#zoom-controls {
position: fixed;
left: 12px;
top: 12px;
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
z-index: 400;
}
#zoom-controls .map-btn {
width: 40px;
height: 40px;
font-size: 20px;
font-weight: 700;
line-height: 1;
}
#zoom-level {
background: var(--surface, #1e1e1e);
color: var(--text, #fff);
border-radius: 6px;
padding: 4px 8px;
font-size: 13px;
font-weight: 600;
min-width: 32px;
text-align: center;
border: 1px solid rgba(255,255,255,0.1);
}
/* ─── Scale bar ──────────────────────────────────────────────────────────── */
#scale-bar {
position: fixed;
bottom: calc(80px + env(safe-area-inset-bottom, 0px) + 16px);
left: 12px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 3px;
z-index: 400;
pointer-events: none;
}
#scale-line {
height: 4px;
width: 100px;
background: #fff;
border: 1px solid rgba(0,0,0,0.6);
border-top: none;
border-left: 2px solid #fff;
border-right: 2px solid #fff;
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
#scale-label {
font-size: 11px;
color: #fff;
text-shadow: 0 1px 3px rgba(0,0,0,0.9), 0 0 4px rgba(0,0,0,0.7);
font-weight: 700;
letter-spacing: 0.3px;
}
/* ═══════════════════════════════════════════════════
ET-006: Загрузка и визуализация GPX-треков
═══════════════════════════════════════════════════ */
/* ── Toast-уведомления (TRZ §3.4) ───────────────── */
#app-toast {
position: fixed;
top: calc(max(env(safe-area-inset-top,0px),12px) + 60px);
left: 50%;
transform: translateX(-50%);
max-width: 86vw;
background: rgba(20,20,20,0.92);
color: #fff;
font-size: 13px;
font-weight: 600;
text-align: center;
padding: 10px 18px;
border-radius: 20px;
z-index: 600;
pointer-events: none;
opacity: 0;
transition: opacity 0.3s;
}
#app-toast.visible { opacity: 1; }
/* ── Индикатор парсинга GPX (TRZ REQ-NF-01, AC-11) ─ */
#gpx-loading {
position: fixed;
top: 50%; left: 50%;
transform: translate(-50%, -50%);
display: none;
flex-direction: column;
align-items: center;
gap: 10px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 14px;
padding: 20px 28px;
box-shadow: var(--shadow);
z-index: 600;
}
#gpx-loading.visible { display: flex; }
#gpx-loading span { font-size: 13px; color: var(--text2); font-weight: 600; }
.gpx-spinner {
width: 32px; height: 32px;
border: 3px solid var(--surface3);
border-top-color: var(--accent);
border-radius: 50%;
animation: gpx-spin 0.8s linear infinite;
}
@keyframes gpx-spin {
to { transform: rotate(360deg); }
}
/* ── Список загруженных треков (TRZ §3.3) ───────── */
#gpx-list { display: flex; flex-direction: column; gap: 6px; }
.gpx-row {
display: flex; align-items: center; gap: 10px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 12px;
padding: 10px 12px;
cursor: pointer;
transition: border-color 0.15s, background 0.15s;
}
.gpx-row:active { background: var(--surface3); }
.gpx-row.active { border-color: var(--accent); background: var(--accent-bg); }
.gpx-dot {
width: 14px; height: 14px; border-radius: 50%;
flex-shrink: 0; box-shadow: 0 0 0 2px var(--surface);
}
.gpx-name {
flex: 1; font-size: 13px; font-weight: 600; color: var(--text);
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.gpx-check { color: var(--accent); font-weight: 800; font-size: 14px; }
.gpx-del {
width: 28px; height: 28px; flex-shrink: 0;
border: 1px solid var(--border);
background: var(--surface);
color: var(--text2);
border-radius: 8px;
font-size: 13px; line-height: 1; cursor: pointer;
transition: all 0.15s;
}
.gpx-del:active { background: var(--red); color: #fff; border-color: var(--red); }
/* ── Сетка статистики трека (TRZ REQ-F-11) ──────── */
.gpx-stats-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 6px;
margin-bottom: 14px;
}
.gpx-stat {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
padding: 8px 4px;
text-align: center;
}
.gpx-stat-val {
font-size: 14px; font-weight: 800; color: var(--text);
font-variant-numeric: tabular-nums; line-height: 1.1;
}
.gpx-stat-lbl {
font-size: 9px; color: var(--text2); font-weight: 600;
text-transform: uppercase; letter-spacing: 0.04em; margin-top: 3px;
}
/* ── Профиль высот (TRZ REQ-F-10) ───────────────── */
#gpx-elevation-wrap { position: relative; width: 100%; }
#gpx-elevation-canvas {
display: block; width: 100%; height: 120px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 10px;
touch-action: none;
}
#gpx-elevation-empty {
font-size: 13px; color: var(--text2); text-align: center; padding: 38px 0;
}
#gpx-elevation-tip {
position: absolute; top: 4px;
display: none;
background: rgba(20,20,20,0.92);
color: #fff;
font-size: 11px; font-weight: 600;
padding: 3px 8px;
border-radius: 8px;
white-space: nowrap;
pointer-events: none;
}
#gpx-elevation-axis {
display: flex; justify-content: space-between;
margin-top: 4px;
font-size: 10px; color: var(--text3); font-weight: 600;
}
/* ── Маркер-курсор профиля на карте ─────────────── */
.gpx-cursor-marker {
width: 12px; height: 12px;
border-radius: 50%;
background: var(--accent);
border: 2px solid #fff;
box-shadow: 0 1px 4px rgba(0,0,0,0.5);
}
/* ─── ET-008: GPS-треки ──────────────────────────── */
.terrain-link-btn {
display: block;
margin: 4px 0 0 24px;
background: none;
border: none;
color: var(--accent, #ff8c1a);
font-size: 12px;
cursor: pointer;
padding: 2px 0;
text-decoration: underline;
}
.gps-filter-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 6px;
margin-bottom: 12px;
}
.gps-filter-chip {
display: flex;
align-items: center;
gap: 6px;
cursor: pointer;
font-size: 13px;
color: var(--text);
}
.gps-filter-chip input[type=checkbox] {
accent-color: var(--accent, #ff8c1a);
width: 14px;
height: 14px;
}
.gps-stats-row {
font-size: 12px;
color: var(--text2);
margin-top: 8px;
}
/* Track popup */
.track-popup {
font-size: 13px;
color: var(--text, #fff);
min-width: 220px;
}
.track-popup-name {
font-weight: 700;
font-size: 14px;
margin-bottom: 6px;
}
.track-popup-row {
margin: 3px 0;
color: var(--text2, #ccc);
}
.track-popup-sources {
margin-top: 8px;
font-size: 12px;
}
.track-popup-sources a {
color: var(--accent, #ff8c1a);
text-decoration: none;
}
.track-popup-sources a:hover {
text-decoration: underline;
}
/* ET-011: кнопка «Скачать GPX» в popup публичного трека (REQ-NF-04) */
.track-popup-actions {
margin-top: 8px;
display: flex;
gap: 8px;
}
.track-popup-download-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: none;
border-radius: 6px;
cursor: pointer;
background: var(--accent, #ff8c1a);
color: #fff;
padding: 0;
transition: opacity 0.15s ease;
}
.track-popup-download-btn:hover {
opacity: 0.9;
}
.track-popup-download-btn:focus {
outline: 2px solid var(--accent, #ff8c1a);
outline-offset: 2px;
}
.track-popup-download-btn svg {
width: 18px;
height: 18px;
}
.track-popup-download-btn.is-loading {
opacity: 0.6;
pointer-events: none;
}