  /* ==========================================================
     10. ПЛАН РЕЙДА

     Экран отвечает не на вопрос «где это», а на вопрос «что реально
     закрыть за один заход и что взять с собой».

     ⚠️ Порядок обхода тут ПО УМОЛЧАНИЮ ОТСУТСТВУЕТ, и это решение, а не
     недоделка: ЧВК высаживает спавн, спавнов на карте несколько, и какой
     достанется — неизвестно заранее. Пока начало пути неизвестно, любая
     нумерация «точка 1 → точка 2» врёт: список мест НЕУПОРЯДОЧЕН, линии
     нет, а номер метки — это ключ «метка на схеме ↔ карточка в списке»,
     как на бумажной карте с легендой.

     Порядок появляется РОВНО ТОГДА, когда известно настоящее начало:
     позиция игрока из последнего скриншота игры (переключатель «маршрут
     от моей позиции» в шапке). Тогда список пересортирован в порядок
     обхода, между метками идёт пунктирная линия, а тексты легенды и
     оговорки внизу меняются — иначе экран отрицал бы то, что рисует.
     ⚠️ Оба состояния оговариваются честно: путь считается ПО ПРЯМОЙ,
     стен, лестниц и заборов данные игры не знают. Метров на экране нет
     ни в одном состоянии (решение владельца, api/raid_plan.py).

     Ни одного НОВОГО цвета: янтарь (--accent) — цели квестов и линия
     обхода между ними, --exit-pmc — выход ЧВК, --exit-scav — сборы и
     корзина «без места», костяной --text — места и точка игрока. Все
     смыслы уже заняты этими же тонами на вкладке «Карта», и заводить
     ещё один было бы ложью о том, что это другой язык.
     ========================================================== */

  .plan-lede {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3) var(--sp-6);
    margin: var(--sp-4) 0;
  }

  .plan-lede h1 {
    font-size: clamp(22px, 3.4vw, 32px);
    font-weight: 600;
    letter-spacing: .01em;
    margin: 0;
    text-wrap: balance;
  }

  .plan-lede p { margin: 4px 0 0; max-width: 54ch; color: var(--text-dim); font-size: 13px; }

  /* Сводка .plan-tally (с .is-aside) и двухколоночная .plan-board
     удалены 22.09.2026: владелец снял сводку 20.09.2026, а .plan-board
     заменила трёхколоночная .plan-rail 21.09.2026. Разметки у них не
     осталось ни строки. */

  /* Схема прилипает к верху окна: список мест с 51 целью уходит на две
     тысячи пикселей вниз, и без этого к пятой карточке схема, ради
     которой всё затевалось, давно бы уехала за край экрана. */
  @media (min-width: 861px) {
    .plan-mapwrap { position: sticky; top: var(--sp-3); }
  }

  .plan-mapwrap { padding: var(--sp-3); }

  .plan-map {
    position: relative;
    height: min(58vh, 560px);
    background: #0d0f0c;
    border: 1px solid var(--line-strong);
    /* ⚠️ ЖЕСТЫ ОТБИРАЮТСЯ У БРАУЗЕРА ТОЛЬКО У ПРИБЛИЖЕННОЙ СХЕМЫ, и
       это не мелочь. Коробка занимает по высоте почти весь экран и
       залипает у его верха; поставь тут безусловный touch-action: none
       (как у .map-canvas-wrap, где рамка невысокая и страница
       короткая) — и свайпом по схеме нельзя было бы пролистать
       страницу вообще, а список работы уходит на тысячи пикселей вниз.
       В покое двигать всё равно нечего: окно просмотра совпадает со
       всей схемой, перетаскивание никуда её не сдвинет. Поэтому
       pan-y — вертикальный свайп листает страницу, — и только щипок
       остаётся нашим (pinch-zoom в разрешённые не входит, иначе
       браузер зумил бы ВСЮ страницу поверх нашего зума).
       Приблизили — коробка становится картой, и жесты её. */
    touch-action: pan-y;
  }

  .plan-map.is-zoomed { cursor: grab; touch-action: none; }
  .plan-map.is-panning { cursor: grabbing; }

  /* Два наложенных SVG, а не один с дорисованными узлами: слой плана
     описан прямо в шаблоне и обновляется реактивностью Vue.
     ⚠️ Оба вписываются в общий бокс своим preserveAspectRatio =
     xMidYMid meet и совпадают точка в точку РОВНО ПОТОМУ, что viewBox
     у них одинаковый — и при зуме тоже: схеме его ставит атрибутом
     planApplyView, слою меток — привязка к planViewBox. Разойдись эти
     два значения хоть на шаг колеса, и метки поедут относительно
     схемы. */
  .plan-map__base, .plan-map__over { position: absolute; inset: 0; }
  /* Та же приглушка насыщенности, что у большой карты: чужая
     бирюзово-зелёная схема иначе выглядит вторым, несвязанным экраном. */
  .plan-map__base { filter: saturate(.7) brightness(.86); }
  .plan-map__base svg { width: 100%; height: 100%; display: block; }
  .plan-map__over { width: 100%; height: 100%; }

  .plan-mark { cursor: pointer; }

  /* Невидимое кольцо вокруг кружка — цель нажатия. Сам кружок на
     четырёхсотпиксельном экране ужимается до семи пикселей: пальцем в
     него не попасть, а WCAG требует 24. Приём тот же, что у обводки
     меток на большой карте: vector-effect делает stroke-width ЭКРАННЫМИ
     пикселями, поэтому кольцо не зависит ни от размера схемы, ни от
     единиц её viewBox. pointer-events: all обязателен — прозрачную
     заливку и обводку браузер иначе кликом не считает. */
  .plan-mark-hit {
    fill: transparent;
    stroke: transparent;
    stroke-width: 24;
    pointer-events: all;
  }
  .plan-mark-dot { fill: var(--surface-3); stroke: var(--accent); stroke-width: 2; }
  .plan-mark-key { fill: var(--accent); font-family: var(--font-display); font-weight: 700; pointer-events: none; }
  .plan-mark.is-active .plan-mark-dot { fill: var(--accent); stroke: var(--text); stroke-width: 2.6; }
  .plan-mark.is-active .plan-mark-key { fill: #0b0c0a; }
  .plan-mark:focus-visible { outline: none; }
  .plan-mark:focus-visible .plan-mark-dot { stroke: var(--text); stroke-width: 3; }

  /* Возможное место квестового предмета. Пунктирная незалитая обводка —
     тот же язык, что у ключа .plan-key--maybe и у полых квадратов выходов:
     сплошное и залитое = известно точно, пунктир и пустота = «может
     быть». Шесть таких меток под ОДНИМ номером читаются как «одна
     вещь, шесть мест», а не как шесть добыч. Цвет тот же янтарный: это всё
     ещё цель квеста, менять тон значило бы заводить пятый смысл. */
  .plan-mark--maybe .plan-mark-dot {
    fill: none;
    stroke-dasharray: 3 2.4;
    opacity: .85;
  }
  /* Выбранная группа заливается так же, как выбранное место, — иначе
     подсветка шестнадцати кружков была бы незаметна. Пунктир обводки
     при этом остаётся: «выбрано» не отменяет «возможное». */
  .plan-mark--maybe.is-active .plan-mark-dot { fill: var(--accent); opacity: 1; }
  .plan-mark--maybe.is-active .plan-mark-key { fill: #0b0c0a; }

  /* Квадрат против кружка — та же система знаков, что на большой
     карте: форма различает слой (выход против цели), цвет несёт сторону
     выхода. Цвет приходит из JS переменной --side, как и там. */
  .plan-exit-box { fill: #16200f; stroke: var(--side, var(--exit-pmc)); stroke-width: 2; }

  .plan-legend {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sp-2) var(--sp-3);
    margin-top: var(--sp-3);
    font-family: var(--font-display);
    font-size: 11px;
    letter-spacing: .05em;
    color: var(--text-faint);
  }

  .plan-legend i {
    display: inline-block;
    width: 9px;
    height: 9px;
    margin-right: 5px;
    vertical-align: -1px;
    border: 1px solid var(--swatch, var(--text-dim));
    background: var(--fill, var(--swatch, var(--text-dim)));
  }

  .plan-legend i.is-dot { border-radius: 50%; }

  /* Образцы маршрута. Кольцо без заливки — точка игрока, штрих —
     линия обхода: ровно те же две фигуры, что на схеме. */
  .plan-legend i.is-you {
    border-radius: 50%;
    border-color: var(--you);
    background: transparent;
  }

  .plan-legend i.is-line {
    width: 15px;
    height: 0;
    border: 0;
    border-top: 1px dashed var(--accent);
    background: none;
    vertical-align: 4px;
  }

  /* ---------- маршрут от позиции игрока ---------- */

  /* Ломаная «игрок → место → место». Пунктир и полупрозрачность — не
     украшение, а оговорка в графике: путь посчитан ПО ПРЯМОЙ, стен и
     лестниц данные игры не знают, и сплошная линия читалась бы как
     проложенная дорога. Цвет тот же янтарный — она соединяет цели
     квестов, а не заводит новый смысл. non-scaling-stroke в разметке:
     толщина не должна зависеть от единиц viewBox чужой схемы. */
  .plan-route {
    fill: none;
    stroke: var(--accent);
    stroke-width: 1.8;
    stroke-dasharray: 7 5;
    /* .6, а не .5: в средней колонке на 1280 схема Улиц — 448 CSS-пикселей,
       и на этой ширине половинной непрозрачности линия почти не видна
       поверх серо-зелёной подложки (смотрел глазами, не по числам).
       Выше .6 не поднимаем: линия начинает спорить с самими метками. */
    opacity: .6;
    pointer-events: none;
  }

  /* «Вы здесь». Кольцо с ядром, без цифры — и формой, и тоном отличается
     и от янтарного кружка места, и от квадрата выхода. Костяной --text:
     самый светлый тон палитры, единственный, который читается поверх и
     светлой, и тёмной части чужой схемы. Нажимать не на что —
     pointer-events снят, чтобы кольцо не перехватывало клик у метки,
     на которой игрок стоит.

     ⚠️ КОНТРАСТ ДЕРЖИТСЯ ДВУМЯ РАЗНЫМИ СРЕДСТВАМИ, И ОБА НУЖНЫ.

     Первое — ЦВЕТ. Кольцо было костяным (--text), и владелец 22.09.2026
     попросил «не белую, а какую-то контрастную, а то сливается со всем
     остальным»: костяной — это же тон текста, подписей и светлых
     построек схемы, а стоит метка среди янтарных кружков мест. Теперь
     кольцо и ядро пурпурные (--you) — единственный незанятый участок
     круга на этом экране, разбор в 01-tokens.css.

     Второе — ТЁМНАЯ ПОДКЛАДКА, и её снимать нельзя даже с ярким тоном.
     Схемы источника местами почти белые, и на белом пурпур 62 %
     светлоты даёт около 3:1 — читается, но не «выпрыгивает». Поэтому
     под меткой остаётся тёмный диск, у кольца остаётся тёмный подбой,
     и на любом фоне метка остаётся «яркое на тёмном». Плюс мягкая тень
     по контуру всей группы — она же разводит метку с соседним янтарным
     кружком, если игрок стоит прямо на месте.

     Мигание не берём намеренно: движение на статичной сводке кричит
     громче, чем нужно, и спорит с пунктирной линией обхода. */
  .plan-you {
    pointer-events: none;
    filter: drop-shadow(0 0 2px rgba(0, 0, 0, .85));
  }

  /* Подложка. Тон — фон самой коробки схемы (#0d0f0c), не новый цвет;
     полупрозрачность оставляет схему под меткой читаемой. */
  .plan-you-halo { fill: rgba(13, 15, 12, .62); }

  /* Тёмный подбой кольца. Шире самого кольца ровно настолько, чтобы
     дать ему кайму в один пиксель с каждой стороны. */
  .plan-you-edge { fill: none; stroke: #0b0c0a; stroke-width: 5.6; }

  .plan-you-ring { fill: none; stroke: var(--you); stroke-width: 3.4; }
  .plan-you-dot { fill: var(--you); }

  /* Куда игрок смотрел в момент снимка. Кусок пирога из центра метки,
     раствор и длина — в planYouCone (plan.js), там же довод, почему
     сектор, а не стрелка.

     Того же пурпура, что кольцо: это одна метка, а не второй смысл.
     Полупрозрачный и БЕЗ ОБВОДКИ намеренно — клин ложится поверх
     схемы и запросто накрывает соседний янтарный кружок, и сплошная
     заливка или контур сделали бы его фигурой, спорящей с номерными
     метками. В таком виде он читается как подсветка, а не как объект.

     Границу клина слегка растворяем градиентом? Нет: градиент в SVG
     требует <defs> с собственным id, а слой схемы рисуется в общем
     документе с картой — второй такой же id рано или поздно приедет
     оттуда. Ровная заливка своего дела не портит.

     ⚠️ Направление берётся из позиции, которая живёт 10 минут, и
     показывает МОМЕНТ СНИМКА, а не «сейчас»: игрок давно повернулся.
     Сказано об этом в <title> метки — рисовать поверх схемы нечего. */
  .plan-you-cone {
    fill: var(--you);
    opacity: .45;
  }

  /* Правил .plan-route-hint здесь больше НЕТ: блок «Позиции нет —
     строить порядок не от чего» снят владельцем 21.09.2026 вместе с
     разметкой. Снята подсказка, а не состояние: маршрут без свежей
     позиции работает как работал, объяснение осталось внизу экрана
     (.plan-caveat, вторая ветка planCaveat). */

  /* ---------- список мест ---------- */

  /* Именно СПИСОК, а не лента: вертикальной линии между карточками тут
     больше нет. Она рисовала связь «сначала это, потом то» всегда — в
     том числе когда начало пути неизвестно (см. шапку раздела).
     Карточки разделяет только воздух. При включённом маршруте порядок
     между ними значение имеет, но говорит об этом текст («в порядке
     обхода» у заголовка) и линия НА СХЕМЕ, а не лента в списке: линия
     показывает, что путь идёт по карте, а не сверху вниз по колонке. */
  .plan-spots { display: flex; flex-direction: column; }

  /* Размер ключа — переменная, потому что от неё зависят ДВЕ вещи сразу:
     ширина колонки и сама кнопка. На сенсорной ширине она вырастает до
     цели нажатия (--tap = 44px), и обе едут вместе, без пересчёта. */
  .plan-spot {
    --pin: 29px;
    display: grid;
    grid-template-columns: var(--pin) minmax(0, 1fr);
    column-gap: var(--sp-3);
    padding-bottom: var(--sp-4);
    position: relative;
  }

  @media (max-width: 900px) {
    .plan-spot { --pin: var(--tap); }
  }

  /* Номер-ключ. Тот же знак стоит в кружке на схеме — это единственное,
     что связывает карточку с меткой, поэтому шрифт, вес и размер у них
     намеренно одни и те же. */
  .plan-key {
    grid-row: 1 / span 4;
    width: var(--pin);
    height: var(--pin);
    display: grid;
    place-items: center;
    background: var(--surface-3);
    border: 1px solid var(--accent);
    color: var(--accent);
    font-family: var(--font-display);
    font-weight: 700;
    font-size: 14px;
    z-index: 1;
    cursor: pointer;
    padding: 0;
  }

  .plan-spot.is-active .plan-key { background: var(--accent); color: #0b0c0a; }
  /* Пунктирная рамка ключа = пунктирная обводка его меток на схеме: у
     карточки «может лежать в одном из N» и у её кружков должен быть
     один и тот же признак неточности, иначе связь читается как разная. */
  .plan-key--maybe { border-style: dashed; }

  /* Название места и подпись под ним — в одну строку (решение
     владельца 21.09.2026). baseline, а не center: у заголовка 15px, у
     подписи 11.5px, и по центру подпись всплыла бы над строкой текста.
     wrap оставлен: у мест с длинным названием («Здание общежития,
     3 этаж») на узком экране строка честно переносится, а не вылезает
     за карточку. */
  .plan-spot__head {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    column-gap: var(--sp-2);
  }

  .plan-spot h3 {
    margin: 4px 0 2px;
    font-size: 15px;
    font-weight: 600;
    letter-spacing: .03em;
    text-transform: uppercase;
  }

  .plan-where {
    margin: 0 0 var(--sp-2);
    font-family: ui-monospace, Consolas, monospace;
    font-size: 11.5px;
    color: var(--text-faint);
  }

  .plan-obj { display: flex; flex-direction: column; gap: 6px; margin: 0; padding: 0; list-style: none; }

  .plan-obj li {
    display: grid;
    grid-template-columns: 13px minmax(0, 1fr) auto;
    gap: var(--sp-2);
    align-items: baseline;
    font-size: 13.5px;
    line-height: 1.45;
  }

  .plan-obj li::before {
    content: "";
    width: 11px;
    height: 11px;
    border: 1px solid var(--line-strong);
    background: var(--surface-2);
    align-self: center;
  }

  /* Выполненную цель гасим, но не прячем: «уже закрыто» — часть ответа
     на вопрос «что осталось за заход», а пропавшая строка выглядела бы
     потерей данных. */
  .plan-obj li.is-done { opacity: .5; }
  .plan-obj li.is-done::before { background: var(--done); border-color: var(--done); }

  /* Селектор не привязан к .plan-obj: то же имя квеста стоит над целью
     и в корзине «без места», где никакого .plan-obj нет. */
  /* Имя квеста над группой его целей. Стало рядом строки, а не одним
     текстом: слева от имени живёт кнопка «✓» (отметить выполненным),
     справа — флажок «не сохранилось». Сам вид имени прежний. */
  .plan-q {
    display: flex;
    align-items: center;
    gap: 5px;
    font-family: var(--font-display);
    font-size: 10.5px;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--accent);
    margin-bottom: 1px;
  }

  /* Имя переносится, кнопка — нет: длинные имена вроде «Сетевой
     провайдер. Часть 1» в средней колонке на 1280 не помещаются в
     строку, и без этого кнопку выдавливало бы за край карточки. */
  .plan-q__name { min-width: 0; }

  /* «Отметить выполненным» — ПОСЛЕ имени квеста и СО СЛОВОМ, а не
     галочкой в квадратике. Оба решения вынужденные, и оба из-за
     соседей по колонке.

     ⚠️ Квадратная кнопка с «✓» вставала ровно в тот же столбец, что
     чекбоксы целей строкой ниже, и читалась как «цель уже отмечена» —
     то есть противоположно тому, что делает. Слово снимает вопрос
     целиком, а место после имени уводит кнопку из чужого столбца.

     Контур без заливки и мелкий кегль: таких строк на одно место
     бывает до восемнадцати, и восемнадцать залитых кнопок перебили бы
     сами цели, ради которых экран и открывают. Тон зелёный «сделано»
     (--done), а не янтарь имени: кнопка не часть названия.

     Регистр и трекинг сбрасываются намеренно — от .plan-q кнопка
     унаследовала бы капс с разрядкой, в котором «✓ ВЫПОЛНЕН» вдвое
     шире нужного.

     ⚠️ Настоящая цель нажатия доведена до 24 px по высоте (порог
     WCAG 2.2 AA) прозрачным ::after: сама кнопка ниже, иначе она
     переросла бы строку имени и разогнала бы группы по вертикали. */
  .plan-q-done {
    position: relative;
    flex: none;
    padding: 1px 5px;
    line-height: 1.3;
    font-size: 10px;
    font-family: var(--font-body);
    text-transform: none;
    letter-spacing: normal;
    white-space: nowrap;
    cursor: pointer;
    color: var(--done);
    background: transparent;
    border: 1px solid var(--line-strong);
    border-radius: 0;
  }

  .plan-q-done::after { content: ""; position: absolute; inset: -4px 0; }

  .plan-q-done:hover:not(:disabled) {
    color: var(--bg);
    background: var(--done);
    border-color: var(--done);
  }

  /* Отправка идёт — кнопка гаснет и перестаёт нажиматься. Второй клик
     по той же кнопке послал бы второй PUT с тем же статусом: вреда нет,
     но и смысла тоже, а мигание ответа выглядело бы сбоем. */
  .plan-q-done:disabled { opacity: .45; cursor: default; }

  .plan-count {
    font-family: ui-monospace, Consolas, monospace;
    font-size: 11.5px;
    color: var(--text-faint);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  /* Метки состояний — тот же язык, что во всём приложении: сплошная
     рамка это условие, пунктир — пометка. */
  .plan-tag {
    display: inline-block;
    font-family: var(--font-display);
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: .1em;
    text-transform: uppercase;
    padding: 1px 5px;
    margin-left: 6px;
    white-space: nowrap;
  }

  .plan-tag--fir { border: 1px solid var(--accent); color: var(--accent); }
  .plan-tag--buy { border: 1px dashed var(--text-faint); color: var(--text-faint); }
  /* Плашки «НЕОБЯЗАТЕЛЬНО» (.plan-tag--opt) больше нет — решение
     владельца 21.09.2026. Она стояла у каждой такой цели: на Таможне
     это десять ярлыков из двадцати строк, и одинаковый повтор
     перебивал редкие «в рейде» и «можно купить», которые сообщают
     важное. Признак переехал в начертание (правило ниже), объяснение —
     в сноску под списком. */

  /* Необязательная цель — курсивом. Курсив здесь честнее плашки ещё и
     потому, что ничего не занимает по ширине: в колонке работы на 1280
     описание цели и так идёт в две-три строки. */
  .plan-obj li.is-optional > div,
  .plan-unplaced-list li.is-optional > div { font-style: italic; }

  /* Имя квеста над целью, счётчик и плашки прямыми и остаются. Имя
     стоит над ГРУППОЙ целей одного квеста, среди которых бывают и
     обязательные, — наклонить его значило бы сказать неправду про
     соседей; счётчик — число, а плашка «в рейде» — свой знак, и
     курсив смешал бы два признака в один. */
  .plan-obj li.is-optional .plan-q,
  .plan-unplaced-list li.is-optional .plan-q,
  li.is-optional .plan-tag { font-style: normal; }

  .plan-need {
    margin-top: var(--sp-2);
    padding: var(--sp-2) var(--sp-3);
    background: var(--surface-2);
    border-left: 2px solid var(--exit-scav);
    font-size: 12.5px;
    color: var(--text-dim);
  }

  .plan-need b {
    display: block;
    font-family: var(--font-display);
    font-size: 9.5px;
    font-weight: 600;
    letter-spacing: .13em;
    text-transform: uppercase;
    color: var(--exit-scav);
    margin-bottom: 2px;
  }

  /* ---------- «может лежать в одном из N» ---------- */

  /* Главная строка карточки поиска, и она обязана быть заметнее самих
     мест: список возможностей, поданный как набор к сбору, — ровно та
     ошибка, на которой уже обожглись с вариантами предметов. Поэтому
     формулировка стоит отдельной строкой, а не сноской у списка. */
  .plan-maybe {
    margin: 0 0 var(--sp-2);
    font-size: 13px;
    color: var(--text-dim);
  }

  .plan-maybe b { color: var(--accent); font-weight: 600; }

  /* Места-кандидаты — чипами, а не нумерованным списком: своя нумерация
     сказала бы «сначала сюда, потом туда» про места, которые перебирают
     в любом порядке (предмет лежит в ОДНОМ из них). Номера в чипах —
     чужие, они ссылаются на метки схемы. Пунктирная рамка у каждого —
     тот же признак неточности, что у метки на схеме. */
  .plan-places {
    display: flex;
    flex-wrap: wrap;
    gap: 5px;
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .plan-places li {
    border: 1px dashed var(--line-strong);
    color: var(--text-dim);
    font-size: 12px;
    padding: 2px 7px;
    max-width: 100%;
    overflow-wrap: anywhere;
  }

  /* Строка предмета: иконка + имя. Та же клетка 22×22, что в «собрать
     до рейда», — предмет один и тот же, и разный вид одного понятия на
     одном экране читается как два разных. */
  .plan-item {
    display: flex;
    align-items: center;
    gap: var(--sp-2);
    margin: 0 0 var(--sp-2);
    font-size: 13.5px;
  }

  /* Клетка предмета — ОДНО правило на весь экран, без предка в
     селекторе (сведено 22.09.2026). До этого один и тот же блок лежал
     трижды: `.plan-item .plan-slot`, `.plan-kit .plan-slot` и
     `.rail-list .plan-slot`, а `.plan-slot img` — четырежды. Третья
     копия появилась ровно потому, что вторая осталась привязана
     к `.plan-kit`, которого в разметке давно нет: иконки ключей
     рисовались в натуральную величину и наезжали на подписи.
     Предмет на экране один, и вид у него обязан быть один.

     flex: none нужен в обоих местах, где клетка стоит: в строке
     .plan-item (flex) она иначе сжимается, в ячейке грида .rail-list
     шириной 22px — растягивает колонку под картинку. */
  .plan-slot {
    flex: none;
    width: 22px;
    height: 22px;
    background: var(--surface-3);
    border: 1px solid var(--line-strong);
    display: grid;
    place-items: center;
    font-family: ui-monospace, Consolas, monospace;
    font-size: 11px;
    color: var(--text-faint);
  }

  /* Иконка предмета живёт внутри той же клетки, что и знак-заменитель:
     не загрузилась одна (чужой CDN) — гасим картинку, клетка остаётся и
     держит строку ровной (тот же приём, что у .item-icon). */
  .plan-slot img { width: 100%; height: 100%; object-fit: contain; padding: 1px; }

  .plan-slot--warn { border-color: var(--exit-scav); color: var(--exit-scav); }

  /* ---------- корзина «без точки на карте» ---------- */

  /* 69 % целей в игре координат не имеют вовсе («устранить любую цель»,
     «передать предмет торговцу»). На части карт эта корзина будет
     БОЛЬШЕ списка мест, поэтому она — полноразмерная панель во всю
     ширину, а не сноска под ним: иначе план выглядел бы короче, чем
     работа, которую он описывает. */
  .plan-unplaced { border-left: 2px solid var(--exit-scav); }

  /* Своя строка пояснения, а не .status-text: у того margin: 0 auto —
     он центрирует абзац по панели, и в сплошном блоке текста это
     выглядит как сбой вёрстки, а не как пояснение к списку. */
  .plan-note {
    color: var(--text-dim);
    font-size: 13px;
    line-height: 1.5;
    max-width: 78ch;
    margin: 0 0 var(--sp-3);
  }

  /* 340px, а не 260: в узкой колонке описание цели («Найти и передать
     Механику 2 микроконтроллера») ломается на три строки, и список из
     пяти пунктов выглядит вдвое длиннее, чем он есть. */
  .plan-unplaced-list {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(340px, 1fr));
    gap: var(--sp-2) var(--sp-4);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .plan-unplaced-list li {
    display: grid;
    grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--sp-2);
    align-items: baseline;
    font-size: 13.5px;
    padding: 3px 0;
  }

  /* Ветка .plan-kit (8 правил, «собрать до рейда») удалена 22.09.2026.
     Её разметка переехала в рейл ещё 21.09.2026 — про смерть знали,
     она прямо описана в комментарии у .rail-list, — но правила
     остались и продолжали спорить за клетку предмета. */

  /* ---------- оговорка ---------- */

  .plan-caveat {
    display: grid;
    grid-template-columns: 18px minmax(0, 1fr);
    gap: var(--sp-2);
    font-size: 12.5px;
    color: var(--text-faint);
    max-width: 82ch;
    margin: var(--sp-4) 0 0;
  }

  .plan-caveat > span[aria-hidden] {
    width: 16px;
    height: 16px;
    display: grid;
    place-items: center;
    border: 1px dashed var(--line-strong);
    font-family: var(--font-display);
    font-size: 10px;
  }

  .plan-caveat b { color: var(--text-dim); font-weight: 600; }

  /* ---------- раскладка «рейл / схема / работа» ---------- */

  /* Три колонки вместо двух. Прежняя сетка .plan-board ставила рядом
     схему и ВСЮ работу сразу — сборы (ключи, место в рюкзаке, выходы,
     предупреждения) попадали в ту же колонку, что и цели, и «что взять
     до рейда» перемешивалось с «что делать в рейде». Здесь это разные
     колонки: слева то, что собирают ДО, справа то, что закрывают В.

     Доли, а не пиксели: рейл должен тянуться вместе с окном, иначе
     «Ключ от кабинета стоматолога» ломается пополам.

     1 : 1.5 : 1.3, а НЕ 1 : 1.9 : 1 из макета. Макет рисовался на
     заглушечных данных; на живых Улицах (46 целей, длинные русские
     описания) при доле 1.9 у схемы колонка работы получалась 308px на
     1280 и 431px даже на 1760 — цель занимала три строки на нижней
     границе поддержки и две на верхней. Замерено по медианной высоте
     строки цели:

         доли          1280        1440        1760
         1 : 1.9 : 1   75px        60px        55px
         1 : 1.6 : 1.2 55px        55px        40px
         1 : 1.5 : 1.3 55px        55px        40px
         1 : 1.4 : 1.4 55px        40px        37px

     Дальше 1.5 идти не стали: выигрыш у работы уже незаметен (обе
     версии дают две строки на 1280), а схема на 1280 падает с 448 до
     416px, и номера меток на Улицах с их 21 меткой читаются плохо —
     проверено глазами, а не по числам. Нижняя граница поддержки —
     1280 (решение владельца 20.09.2026), поэтому баланс подбирался
     по ней, а не по 1920.

     ⚠️ 21.09.2026 доли стали 0.75 : 1.75 : 1.3 — владелец: «схема
     мелкая, может столбец правый сделать поуже». Правый оставлен как
     был, и это не спор с заданием, а замер: при 1.2 у работы шесть
     описаний из двенадцати ломаются с двух строк на три (померено на
     Таможне, 1280: медиана строки 48px → 65px, колонка +7 % высоты) —
     ровно то, за что доля 1.9 у схемы была отвергнута в прошлый раз.
     Ту же ширину схеме отдал рейл, и он теряет меньше всех: в нём
     короткие подписи под иконкой, которым перенос не вредит.

         доли              1280 (рейл / схема / работа)   1760
         1   : 1.5 : 1.3   316 / 474 / 411                442 / 664 / 575
         0.75: 1.75: 1.3   237 / 553 / 411                332 / 774 / 575

     Схема получила +17 % ширины, работа — ноль изменений. Главный
     выигрыш схемы, впрочем, не здесь, а в пропорции коробки — см.
     .plan-map--ratio. */
  .plan-rail {
    display: grid;
    grid-template-columns: minmax(0, 0.75fr) minmax(0, 1.75fr) minmax(0, 1.3fr);
    gap: var(--sp-4);
    align-items: start;
  }

  /* Липнут обе боковые колонки, а не только схема: без этого при
     прокрутке списка целей рейл уезжал бы за край вместе с ключами, за
     которыми в него и смотрят.

     ⚠️ У РЕЙЛА ОБЯЗАТЕЛЬНА СВОЯ ПРОКРУТКА. В задании сказано «рейл
     короткий» — это верно для макета на заглушечных данных, но не для
     живых: на Улицах он 1920px при окне 800px. А липкий элемент выше
     окна — ловушка: он прижимается верхом к top и держится так всю
     высоту гридовой ячейки, то есть все 8822px колонки работы. Нижние
     1120px рейла (выходы, «на что смотреть») при этом недостижимы — до
     них можно доехать, только прокрутив страницу до самого конца.
     Ровно на это и пожаловался владелец: «первый столбец скроллится,
     только когда третий доехал вниз».

     max-height по высоте окна + overflow-y снимают ловушку: рейл
     остаётся на виду и при этом прокручивается сам. Вложенная полоса
     прокрутки здесь меньшее зло, чем недостижимое содержимое. */
  @media (min-width: 861px) {
    .plan-rail__side, .plan-rail__map { position: sticky; top: var(--sp-3); }
    /* Потолок высоты схемы. 230px — не запас «на глазок», а замер
       худшего случая: всё, что стоит в той же залипающей панели над
       картинкой и под ней. На Улицах это отступы панели, легенда в три
       строки, строка про жесты, приписка про метки за рамкой и подпись
       автора. Четыре строки подсказки «позиции нет» из этого замера
       ушли вместе с самой подсказкой (21.09.2026), строка про жесты
       пришла — запас только вырос, и трогать число незачем.
       Панель обязана целиком помещаться в окно, иначе её
       низ недостижим: залипший элемент выше окна прижимается верхом и
       держится так всю высоту ячейки (та же ловушка, что у рейла).
       С 180px панель Улиц выходила за край окна 900px на 37px. */
    .plan-map--ratio { max-height: calc(100vh - 230px); }
    .plan-rail__side {
      max-height: calc(100vh - var(--sp-3) * 2);
      overflow-y: auto;
      /* Полоса не должна прижиматься вплотную к тексту. */
      padding-right: var(--sp-2);
      /* Прокрутка рейла не должна «протекать» на страницу, когда
         дошла до своего края: иначе колесо в рейле внезапно уносит
         всю страницу вниз. */
      overscroll-behavior: contain;
    }
  }

  .plan-rail__side { display: flex; flex-direction: column; gap: var(--sp-5); }

  /* Схема держит пропорцию, а не долю экрана: в средней колонке её
     ширина задана сеткой, и height: min(58vh, 560px) от .plan-map
     оставлял бы под ней пустую полосу либо резал бы её по высоте.

     ⚠️ Пропорция — СВОЯ У КАЖДОЙ КАРТЫ, из viewBox её схемы
     (--map-ar ставит разметка, см. plan.js). Общие 1000/620 = 1.61
     остались умолчанием на время, пока схема не приехала, и только.
     Замеренные пропорции источника: Маяк 0.62, Улицы 0.73, Полигон
     0.71, Терминал 0.85, Завод 0.93, Лес 1.04, Резерв 1.09, тайловые
     1.00, Развязка 1.19, Лаборатория 1.23, Берег 1.51, Таможня 1.99.
     То есть общая пропорция не подходила НИ ОДНОЙ карте, а картинка
     вписывается в коробку целиком: у девяти карт из тринадцати она
     упиралась в высоту и занимала треть ширины (Улицы на 1280 —
     202px картинки в коробке шириной 448), у Таможни наоборот
     оставляла 70px пустоты сверху и снизу. Пропорция по картинке
     убирает и то, и другое разом: на Улицах схема стала в 2.7 раза
     крупнее по стороне, и ни один сосед не подвинулся.

     max-height — против ловушки залипания, той же, что у рейла: при
     пропорции 0.62 коробка на 1280 запросила бы 890px, а элемент выше
     окна прижимается верхом и держится так всю высоту ячейки — легенда
     и подпись автора под схемой стали бы недостижимы. Потолок по окну
     возвращает часть пустоты по бокам, но это меньшее зло, и он живёт
     в той же медиазапросной ветке, что само залипание: на узком экране
     высоту задаёт 15-narrow.css, и второй потолок там только мешал бы. */
  .plan-map--ratio { height: auto; aspect-ratio: var(--map-ar, 1000 / 620); }

  /* Общий список рейла: ключи, место в рюкзаке, предупреждения.
     Клетка 22×22 — общая .plan-slot выше: предмет один и тот же, и
     разный вид одного понятия на одном экране читается как два
     разных. */
  .rail-list {
    display: flex;
    flex-direction: column;
    gap: 9px;
    margin: var(--sp-3) 0 0;
    padding: 0;
    list-style: none;
    font-size: 13px;
  }

  .rail-list li {
    display: grid;
    grid-template-columns: 22px minmax(0, 1fr);
    gap: var(--sp-2);
    align-items: start;
  }

  /* Своих правил у клетки здесь больше нет: и размер, и flex: none,
     и вписывание картинки, и красная рамка предупреждения приехали
     с общей .plan-slot выше. */

  /* Ниже 1240 колонка работы не ужимается до одной цели в строку, а
     уходит под схему во всю ширину. Ниже 860 всё в одну колонку и
     липкость снимается — там её берёт на себя 15-narrow.css. */
  @media (max-width: 1240px) {
    .plan-rail { grid-template-columns: minmax(0, 1fr) minmax(0, 1.6fr); }
    .plan-rail__work { grid-column: 1 / -1; }
  }

  @media (max-width: 860px) {
    .plan-rail { grid-template-columns: minmax(0, 1fr); }
  }

  /* Шапка раздела одной строкой: заголовок, выбор карты, переход на
     карту. В макете эти три правила стояли инлайном на <header>, в
     присланном CSS их не было — здесь они классом, потому что инлайн
     в planView нельзя ни переопределить с узкого экрана, ни найти
     поиском по стилям. */
  .plan-head {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    gap: var(--sp-3) var(--sp-5);
  }

  /* Заголовок забирает свободное место, но ужимается: 420px — порог,
     ниже которого выбор карты и кнопка переносятся на свою строку, а
     не режут название карты. */
  .plan-head__title { flex: 1 1 420px; min-width: 0; }
  .plan-head__title .marker { margin: 0 0 4px; }
  .plan-head__title h1 {
    margin: 0;
    font-size: clamp(22px, 3vw, 30px);
    font-weight: 600;
    letter-spacing: .01em;
  }
  .plan-head .field { flex: 0 0 auto; }
  .plan-head .profile-select { max-width: none; width: 220px; }

  /* В рейле подпись переносится, а в карточках — нет, и это не
     разнобой. `.plan-count` задуман под короткое: «0 / 5», «2 × 1»,
     где перенос разорвал бы одно число пополам, — отсюда nowrap. В
     рейле в тот же знак попадают строки выходов вида «ЧВК · шанс
     100 % · без условий · подземка», и nowrap там даёт не красивую
     строку, а горизонтальную прокрутку ВСЕЙ страницы на 400px
     (замерено: шесть подписей шириной до 398px в колонке на 365px).
     Смысл знака один и тот же — мелкая приписка к строке; разной
     должна быть только переносимость. */
  .rail-list .plan-count {
    display: block;
    white-space: normal;
    overflow-wrap: anywhere;
  }

  /* Колонка работы — стопка панелей. Тоже было инлайном в макете. */
  .plan-rail__work { display: flex; flex-direction: column; gap: var(--sp-4); }

  /* Список «без места» в узкой колонке — в один столбец: его штатная
     сетка рассчитана на всю ширину экрана и здесь давала бы по два
     слова в строке. Правилом, а не инлайном в разметке, по той же
     причине, что и шапка. */
  .plan-rail__work .plan-unplaced-list { grid-template-columns: minmax(0, 1fr); }

  /* Сноска про курсив. Ниже всех трёх списков и вне панелей: она
     объясняет знак, общий для них, и внутри любой из панелей читалась
     бы как примечание только к ней. Тон --text-faint — самый тихий:
     это подпись к легенде, а не ещё одна строка работы. Появляется
     только вместе с самим курсивом (v-if="planHasOptional"). */
  .plan-optnote {
    margin: 0;
    color: var(--text-faint);
    font-size: 12.5px;
    line-height: 1.5;
  }

  .plan-optnote em { font-style: italic; color: var(--text-dim); }

  /* Ниже 860 липкость снимается: колонки идут друг за другом, и
     прилипший рейл перекрывал бы то, к чему пролистывают. */
  @media (max-width: 860px) {
    .plan-rail__side, .plan-rail__map { position: static; }
  }

  /* ---------- управление зумом схемы ---------- */

  /* Кнопки поверх схемы, в её правом верхнем углу — там же, где
     «вписать в экран» у большой карты (.map-fit-btn). Своё место под
     ними в панели отнимало бы у картинки строку, ради которой
     21.09.2026 переделывали пропорцию коробки.

     ⚠️ Кнопки живут ВНУТРИ коробки, на которой висят жесты, поэтому
     нажатие на них иначе начало бы и перетаскивание схемы. Самого
     конфликта нет: перетаскивание считается только после порога в 4
     пикселя, а клик по кнопке его не набирает, — но курсор-«ладонь»
     на кнопке выглядел бы ошибкой, отсюда свой cursor. */
  .plan-zoom {
    position: absolute;
    top: 8px;
    right: 8px;
    z-index: 3;
    display: flex;
    gap: 4px;
  }

  .plan-zoom button {
    background: rgba(11, 12, 10, .85);
    border: 1px solid var(--line-strong);
    color: var(--text-dim);
    font-family: var(--font-display);
    font-size: 13px;
    line-height: 1;
    /* Квадрат в 26px: цель нажатия у «+» и «−» и так на грани, а
       меньше делать нельзя — попасть мышью в плюс шириной в символ
       невозможно. */
    min-width: 26px;
    height: 26px;
    padding: 0 6px;
    cursor: pointer;
  }

  .plan-zoom button:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
  /* Предел приближения — не поломка, а край шкалы: кнопка гаснет, но
     остаётся на месте. Пропади она — на максимуме зума ряд кнопок
     перестраивался бы под курсором. */
  .plan-zoom button:disabled { opacity: .35; cursor: default; }

  .plan-zoom__fit {
    font-size: 10.5px;
    letter-spacing: .06em;
    text-transform: uppercase;
  }

  /* ---------- широкая раскладка ---------- */

  /* Боковые колонки к самым краям окна, схема в середине забирает всё
     освободившееся (просьба владельца 21.09.2026: «1 и 3 колонки
     прилепить по краям экрана, а карту оставить в центре»).

     ⚠️ ВСЁ ЛЕЖИТ В ОДНОМ БЛОКЕ ПРАВИЛ НАМЕРЕННО. Разлёт по краям
     делается через :has() — если браузер его не знает, не должно
     примениться и переопределение колонок, иначе получилась бы
     раскладка «широкие доли внутри узкой оболочки», то есть схема
     ЕЩЁ уже обычной. Поэтому обе части висят на одном и том же
     селекторе-предке.

     ⚠️ И весь блок — внутри min-width: 1241px. Ниже этого порога
     раскладка штатно схлопывается в две колонки (правило ниже по
     файлу), а `.app-shell:has(…)` по специфичности сильнее него и
     молча бы его перебил.

     Почему оболочка, а не отрицательные поля у самой сетки: обычный
     приём «margin-inline: calc(50% - 50vw)» считает 50vw ВМЕСТЕ с
     полосой прокрутки, и правый край колонки уезжал бы под неё
     (html { overflow-x: clip } спрятал бы это молча). Потолок ширины
     снимается с .app-shell, а шапке приложения он возвращается
     отдельно: вкладки и выбор профиля, разъехавшиеся на 2560 точек,
     отношения к плану не имеют. */
  @media (min-width: 1241px) {
    .app-shell:has(.plan-rail.is-wide) { max-width: none; }
    .app-shell:has(.plan-rail.is-wide) > .app-header {
      max-width: var(--shell);
      margin-inline: auto;
    }

    /* Доли заменяются на «проценты с потолком»: боковые колонки растут
       вместе с окном ровно до той ширины, которую имеют в обычной
       раскладке на 1760 (332 и 575 точек), и дальше стоят — всё
       остальное достаётся схеме.

       Проценты, а не фиксированные пиксели: на 1280 фиксированные
       332 + 575 оставили бы схеме 309 точек вместо нынешних 553, то
       есть «широкая» раскладка оказалась бы УЖЕ обычной. Сами 19.2 и
       33.3 — не круглые числа и не подгонка: это те же доли
       0.75 : 1.75 : 1.3, пересчитанные в процент от всей сетки
       (0.75/3.8 и 1.3/3.8 от ширины за вычетом двух зазоров).
       Поэтому на нижней границе поддержки колонки совпадают с
       обычной раскладкой точка в точку, и «широкий» режим там ничего
       не портит — он просто нечего ещё расширять.

           окно    рейл / схема / работа   (обычная раскладка)
           1280    237 / 554 / 411         (237 / 553 / 411)
           1760    329 / 780 / 571         (332 / 774 / 575)
           2560    340 / 1561 / 580        (341 / 796 / 591 внутри 1760)
    */
    .plan-rail.is-wide {
      grid-template-columns:
        minmax(0, clamp(200px, 19.2%, 340px))
        minmax(0, 1fr)
        minmax(0, clamp(330px, 33.3%, 580px));
    }

    /* Схема забирает освободившуюся середину целиком — и по высоте
       тоже, иначе «шире» означало бы только «шире поля вокруг
       картинки».

       ⚠️ max-width здесь — пересчитанный потолок ВЫСОТЫ. Коробка
       держит пропорцию картинки, ширину ей задаёт колонка, и в
       широкой раскладке колонка почти всегда шире, чем позволяет
       высота окна: без этой строки высокая схема (Маяк, 0.62)
       упиралась бы в max-height и оставляла по бокам по трети
       колонки пустоты. Высота × пропорция и есть та ширина, при
       которой картинка ровно вписывается; --map-arn ставит разметка
       (см. plan.js), 1.61 — умолчание на время, пока схема не
       приехала. */
    .plan-rail.is-wide .plan-map--ratio {
      max-width: calc((100vh - 230px) * var(--map-arn, 1.61));
      margin-inline: auto;
    }
  }
