  /* ==========================================================
     9. КАРТА
     Высота рамки и вся машинерия жестов не меняются — только цвета
     и хром вокруг.
     ========================================================== */

  .map-toolbar {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: flex-end;
  }

  .map-toolbar .field { min-width: 180px; }
  .map-panel-toggle { margin-left: auto; }

  .floor-switch { display: flex; flex-wrap: wrap; gap: 3px; }

  /* У Ледокола этажей 16 — в одной строке с выпадашкой карт они выжимают
     кнопку «квесты» за край. Начиная с семи этажей отдаём им целую строку. */
  .floor-switch--many { flex: 1 1 100%; }

  /* Все органы управления картой — одной высоты (--ctrl-h): выпадашка
     карт, этажи, стороны выходов и кнопка панели квестов стоят в одном
     ряду, и разнобой 26 против 34 пикселей читался как случайность, а не
     как замысел. Кегль у этажей остаётся мельче — их бывает 16, и 12px
     увели бы ряд на лишнюю строку. */
  .floor-switch .toggle-btn { height: var(--ctrl-h); font-size: 11px; }

  /* Кнопка стороны выхода — тот же toggle-btn, но с образцом цвета, и
     поэтому ей нужен flex, иначе образец сдвигает текст по базовой линии.
     Кнопки мест устроены так же (у «замков» образец — ромб). */
  .exit-switch .toggle-btn,
  .place-switch .toggle-btn {
    display: inline-flex;
    align-items: center;
    gap: 5px;
  }

  /* Выключенная сторона не должна выглядеть «стороной без выходов»:
     образец гаснет вместе с текстом, а не остаётся цветным. */
  .exit-switch .toggle-btn:not(.active) .exit-chip__swatch {
    background: transparent;
    border-color: var(--line-strong);
  }

  .map-layout {
    display: flex;
    gap: var(--sp-3);
    align-items: flex-start;
    /* Единая высота рамки карты — не пропорция схемы: у Таможни
       соотношение сторон ~2:1, у Маяка ~1:1.6, общая пропорция
       раздавила бы одних и оставила пустоту у других. Один токен на
       карту и на панель квестов — они колонки одной высоты. */
    --map-frame-h: min(72vh, 760px);
  }

  .map-main { flex: 1 1 auto; min-width: 0; }

  .map-canvas-wrap {
    position: relative;
    margin-top: var(--sp-3);
    height: var(--map-frame-h);
    background: #0d0f0c;
    border: 1px solid var(--line-strong);
    padding: 6px;
    cursor: grab;
    /* Жесты внутри рамки — свои (пан мышью/пальцем, щипок, Ctrl+колесо),
       поэтому браузеру тут нечего делать: pan-x/pan-y отдали бы вертикальный
       свайп странице ДО того, как до него доберётся наш обработчик, а
       pinch-zoom дал бы нативный зум ВСЕЙ страницы поверх нашего. */
    touch-action: none;
  }

  /* Уголки-засечки у рамки карты — та же маркировка, что у панелей. */
  .map-canvas-wrap::before, .map-canvas-wrap::after {
    content: "";
    position: absolute;
    width: 10px;
    height: 10px;
    border: 2px solid var(--accent);
    pointer-events: none;
    z-index: 2;
    opacity: .7;
  }

  .map-canvas-wrap::before { top: -1px; left: -1px; border-right: 0; border-bottom: 0; }
  .map-canvas-wrap::after  { bottom: -1px; right: -1px; border-left: 0; border-top: 0; }

  /* Схемы карт нарисованы чужой бирюзово-зелёной гаммой и рядом с
     хаки-палитрой интерфейса выглядели вторым, несвязанным экраном.
     Лёгкое приглушение насыщенности сближает их, не трогая читаемость
     (метки поверх — янтарные, они остаются янтарными). */
  .map-canvas { width: 100%; height: 100%; filter: saturate(.78); }
  /* height ставит и JS (captureViewBox) — здесь только на случай, если
     скрипт ещё не отработал на первом кадре. Схема вписывается в рамку
     целиком через свой preserveAspectRatio, поля вокруг — эта подложка. */
  .map-canvas svg { width: 100%; height: 100%; display: block; }
  .map-canvas.is-panning { cursor: grabbing; }

  .map-fit-btn {
    position: absolute;
    top: 12px;
    right: 12px;
    z-index: 3;
    background: rgba(11, 12, 10, .85);
    border: 1px solid var(--line-strong);
    color: var(--text-dim);
    font-family: var(--font-display);
    font-size: 11px;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: 5px 9px;
    cursor: pointer;
  }

  .map-fit-btn:hover { border-color: var(--accent); color: var(--accent); }

  /* чужой этаж притемняется, а не прячется */
  .map-layer--dim { opacity: .28; transition: opacity .15s ease; }

  /* Тайлы — только подложка: клик по метке не должен доставаться картинке
     под ней (метки лежат выше по порядку, но у <image> своя область
     попадания на всю клетку, и промах по кружку гасился бы об неё). */
  .map-tile { pointer-events: none; }

  .quest-marker__dot {
    fill: var(--accent);
    stroke: #0b0c0a;
    stroke-width: 1.6;
    transition: opacity .15s ease;
  }

  .quest-marker--dim .quest-marker__dot { opacity: .3; }
  .quest-marker--dim .quest-marker__count { opacity: .55; }

  /* Возможное место квестового предмета: предмет лежит в ОДНОМ из таких
     кружков, а не в каждом. Полая пунктирная обводка вместо заливки —
     тот же язык, что у меток плана и у полых квадратов чужих выходов:
     сплошное = известно, пунктир = «может быть». Цвет тот же янтарный,
     это всё ещё цель квеста. */
  .quest-marker--maybe .quest-marker__dot {
    fill: none;
    stroke: var(--accent);
    stroke-dasharray: 2.6 2;
  }

  .quest-marker--maybe .quest-marker__count { fill: var(--accent); }

  .quest-marker--selected .quest-marker__dot {
    fill: var(--text);
    stroke: var(--accent);
    stroke-width: 2.4;
  }

  .quest-marker__count {
    fill: #0b0c0a;
    font-weight: 600;
    pointer-events: none;
  }

  /* ---------- карта: слой выходов ---------- */

  /* Квадрат против кружка цели квеста: форма различает слои даже там,
     где цвета сливаются (притемнённый чужой этаж, мелкий зум), и прямой
     угол — родная геометрия интерфейса. Цвет несёт сторону выхода. */
  /* Цвет стороны приходит из JS одной переменной --side: правило класса
     всегда перебивает presentation-атрибут fill/stroke на самом элементе,
     поэтому «пустой» вариант иначе не смог бы забрать цвет себе в обводку. */
  .exit-marker__box {
    fill: var(--side, var(--text-dim));
    stroke: #0b0c0a;
    stroke-width: 1.4;
    transition: opacity .15s ease;
  }

  /* Заливка — «условия известны», пустой контур — «данных об условиях
     нет». У выходов дикого, общих и секретных серверной записи в базе
     локации нет вовсе, и залить их так же, как выходы ЧВК, значило бы
     соврать: неизвестное должно выглядеть неизвестным и на карте, а не
     только в карточке. */
  .exit-marker--void .exit-marker__box {
    fill: rgba(11, 12, 10, .8);
    stroke: var(--side, var(--text-dim));
    stroke-width: 2;
  }

  .exit-marker--active .exit-marker__box { stroke: var(--text); stroke-width: 2.6; }

  /* ---------- карта: легенда-фильтр выходов ---------- */

  .exit-chip__swatch {
    width: 9px;
    height: 9px;
    flex-shrink: 0;
    border: 1px solid #0b0c0a;
    background: var(--swatch, var(--text-dim));
  }

  .exit-chip__count { color: inherit; opacity: .7; font-variant-numeric: tabular-nums; }

  .exit-legend-note { color: var(--text-faint); font-size: 11px; }

  .exit-legend-note b { color: var(--text-dim); font-weight: 600; }

  /* ---------- карта: слой мест (надписи и замки) ----------
     Третий слой поверх схемы — и единственный, который НЕ приносит на
     карту нового цвета. Цветом на этой карте уже сказаны две вещи:
     янтарь = цель квеста, четыре тона = сторона выхода. Место и замок —
     не состояние и не выбор, а постоянная черта локации, поэтому им
     достаётся костяной --text-dim, тот же, каким набран второстепенный
     текст интерфейса. Различает их форма и подача: надпись — это
     типографика без значка, замок — ромб (ни круг цели, ни квадрат
     выхода). */

  .place-label {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: .05em;
    fill: var(--text-dim);
    /* Тёмный ореол вокруг буквы — единственный способ читать подпись и
       над светлым ангаром, и над чёрным лесом одной картой. paint-order
       кладёт обводку ПОД заливку, иначе она съедала бы тонкие штрихи. */
    stroke: #0b0c0a;
    stroke-width: 3;
    stroke-linejoin: round;
    paint-order: stroke fill;
    pointer-events: none;
  }

  /* Названия мест приходят по-английски (перевода нет ни у нас, ни у
     источника — часть из них и в игре латиницей). Отдельного признака
     «чужая строка» на карте не рисуем: подпись и так набрана иначе,
     чем весь русский интерфейс, а пунктир под каждым словом превратил
     бы схему в кашу. Оговорка живёт одной строкой под картой. */

  .lock-mark {
    fill: var(--text-dim);
    stroke: #0b0c0a;
    stroke-width: 1.4;
    cursor: pointer;
  }

  /* Багажник машины против двери: полый ромб против залитого. Тот же
     приём, что у выходов (заливка = «известно»), и здесь он тоже про
     содержание — вскрыть машину и открыть дверь это разные действия. */
  .lock-mark--trunk {
    fill: rgba(11, 12, 10, .8);
    stroke: var(--text-dim);
    stroke-width: 1.8;
  }

  .lock-name {
    font-family: var(--font-display);
    fill: var(--text);
    stroke: #0b0c0a;
    stroke-width: 3;
    stroke-linejoin: round;
    paint-order: stroke fill;
    pointer-events: none;
  }

  .place-chip__mark { font-size: 11px; line-height: 1; }

  /* ---------- карта: карточка выхода ---------- */

  .exit-side-tag {
    display: inline-block;
    padding: 1px 7px;
    border: 1px solid var(--tag, var(--line-strong));
    color: var(--tag, var(--text-dim));
    font-family: var(--font-display);
    font-size: 11px;
    letter-spacing: .08em;
    text-transform: uppercase;
  }

  /* Служебный идентификатор из файлов игры показываем моноширинным —
     чтобы его нельзя было спутать с названием, которого у выхода нет. */
  /* Оба класса в селекторе — .map-popup__task объявлен ниже по файлу и
     при равной силе перебил бы одиночный .exit-code. */
  .map-popup__task.exit-code {
    font-family: ui-monospace, 'Cascadia Mono', Consolas, monospace;
    font-size: 15px;
    color: var(--text-dim);
    word-break: break-all;
  }

  .exit-name-note { color: var(--text-faint); font-size: 11px; margin: 2px 26px 0 0; }

  .exit-facts {
    display: grid;
    grid-template-columns: minmax(0, auto) minmax(0, 1fr);
    gap: 5px var(--sp-3);
    margin: var(--sp-3) 0 0;
    font-size: 13px;
  }

  .exit-facts dt { color: var(--text-faint); }
  .exit-facts dd { margin: 0; color: var(--text); }
  .exit-facts dd.is-none { color: var(--text-faint); }
  .exit-facts dd.is-zero { color: var(--danger); }

  /* Сырой ключ из файлов игры рядом с человеческой расшифровкой: перевод
     виден, но проверить его по источнику можно не отходя от карточки. */
  .exit-raw { color: var(--text-faint); font-size: 11px; font-family: ui-monospace, Consolas, monospace; }

  /* Та же штриховка, что у «не проверить» в полосе прогресса: в этом
     интерфейсе фактура уже означает «данных нет». */
  .exit-unknown {
    margin: var(--sp-3) 0 0;
    padding: var(--sp-2) var(--sp-3);
    border: 1px solid var(--line);
    background-color: var(--surface-2);
    background-image: repeating-linear-gradient(135deg,
      transparent 0 3px, rgba(216, 161, 42, .28) 3px 5px);
    color: var(--text-dim);
    font-size: 12px;
    line-height: 1.45;
  }

  .exit-unknown b { color: var(--text); font-weight: 600; }

  .map-hint, .map-attribution {
    margin: var(--sp-2) 0 0;
    font-size: 11px;
    color: var(--text-faint);
  }

  .map-attribution { margin-top: 2px; }
  .map-attribution a { color: var(--text-faint); }
  .map-attribution a:hover { color: var(--accent); }

  /* ---------- карта: панель квестов ---------- */

  .map-sidebar {
    flex: 0 0 280px;
    width: 280px;
    background: var(--surface-1);
    border: 1px solid var(--line);
    padding: var(--sp-3);
    margin-top: var(--sp-3);
    /* Та же высота, что у рамки карты (--map-frame-h) — карта и панель
       квестов идут колонками одной высоты, а не «карта + хвост списка». */
    height: var(--map-frame-h);
    overflow-y: auto;
    transition: flex-basis .15s ease, width .15s ease;
  }

  /* свёрнутая на широком экране — не выдвижная шторка, а нулевая ширина
     рядом с картой, чтобы карта заняла освободившееся место */
  .map-sidebar:not(.is-open) {
    flex-basis: 0;
    width: 0;
    padding: 0;
    border: none;
    overflow: hidden;
  }

  .map-sidebar__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sp-2);
    padding-bottom: var(--sp-2);
    border-bottom: 1px solid var(--line);
  }

  .map-sidebar__title {
    font-family: var(--font-display);
    font-size: 13px;
    font-weight: 600;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--text);
  }

  .map-sidebar__count { color: var(--accent); font-variant-numeric: tabular-nums; }

  .map-sidebar__collapse {
    background: transparent;
    border: none;
    color: var(--text-faint);
    font-size: 15px;
    cursor: pointer;
    padding: 2px 4px;
  }

  .map-sidebar__collapse:hover { color: var(--danger); }

  .map-sidebar__filters { display: flex; gap: 3px; margin-top: var(--sp-2); }
  .map-sidebar__filters .toggle-btn { height: var(--chip-h); padding: 0 8px; font-size: 11px; }

  .map-quest-list { display: flex; flex-direction: column; margin-top: var(--sp-2); }

  .map-quest-row {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    width: 100%;
    background: transparent;
    border: none;
    border-bottom: 1px solid var(--line);
    border-left: 3px solid transparent;
    color: var(--text);
    text-align: left;
    font-size: 12px;
    padding: 7px 4px;
    cursor: pointer;
  }

  .map-quest-row:hover { background: var(--surface-2); }
  .map-quest-row.is-dim { color: var(--text-faint); }
  .map-quest-row.is-selected { background: var(--surface-3); border-left-color: var(--accent); color: var(--accent); }

  /* Кружок с ОДНОЙ буквой расшифровать было нельзя (P — Prapor или
     Peacekeeper?). Три буквы различают всех торговцев каталога, полное
     имя остаётся в title. */
  .map-quest-row__trader {
    flex-shrink: 0;
    min-width: 30px;
    padding: 1px 3px;
    background: var(--surface-3);
    border: 1px solid var(--line);
    color: var(--text-dim);
    font-family: var(--font-display);
    font-size: 10px;
    font-weight: 600;
    letter-spacing: .04em;
    text-align: center;
  }

  .map-quest-row__name { flex: 1 1 auto; min-width: 0; }

  .map-quest-row__hint {
    flex-shrink: 0;
    width: 16px;
    height: 16px;
    border: 1px solid var(--line-strong);
    color: var(--text-dim);
    font-size: 10px;
    text-decoration: none;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .map-quest-row__hint:hover { border-color: var(--accent); color: var(--accent); }

  .map-quest-row__count { flex-shrink: 0; color: var(--text-faint); font-size: 11px; font-variant-numeric: tabular-nums; }

  .map-sidebar-backdrop { display: none; }

  .map-popup-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, .66);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--sp-4);
    z-index: 1000;
  }

  .map-popup {
    position: relative;
    background: var(--surface-1);
    border: 1px solid var(--line-strong);
    padding: var(--sp-4);
    width: min(92vw, 440px);
    max-height: calc(100vh - 32px);
    overflow-y: auto;
  }

  .map-popup__close {
    position: absolute;
    top: 4px;
    right: 4px;
    background: transparent;
    border: none;
    color: var(--text-faint);
    font-size: 18px;
    line-height: 1;
    cursor: pointer;
    padding: 6px 10px;
  }

  .map-popup__close:hover { color: var(--danger); }

  .map-popup__task {
    color: var(--text);
    font-family: var(--font-display);
    font-size: 17px;
    font-weight: 600;
    margin: 0 26px 4px 0;
  }

  .map-popup__desc { color: var(--text-dim); font-size: 13px; line-height: 1.45; margin: 0 0 var(--sp-3); }

  .map-popup__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2);
    align-items: center;
    margin-top: var(--sp-3);
  }

  .cluster-list { display: flex; flex-direction: column; gap: 4px; margin-top: var(--sp-2); }

  .cluster-item {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 2px;
    text-align: left;
    background: var(--surface-2);
    border: 1px solid var(--line);
    border-left: 3px solid var(--line-strong);
    padding: 7px 9px;
    cursor: pointer;
  }

  .cluster-item:hover { border-color: var(--line-strong); }
  .cluster-item.is-highlight { border-left-color: var(--accent); }

  .cluster-item__task { color: var(--text); font-size: 13px; font-family: var(--font-display); font-weight: 600; }
  .cluster-item__desc { color: var(--text-dim); font-size: 12px; }

  .map-obj-link { flex-shrink: 0; }

