/* =============================================================================
   redesign.css — ПРОДУКТОВЫЙ слой daymilo.com поверх Ant Design.

   Подключается ПОСЛЕДНИМ, после assets/antd/antd.css и assets/theme.css.

   Здесь только то, чего в дизайн-системе нет и быть не может: карта, маркеры
   маршрута, липкая панель действий на смартфоне, жесты, полноэкранный режим,
   онбординг, виджет поддержки. Всё остальное — кнопки, поля, модалки, списки,
   шаги, теги, алерты, статистика — рисует сам antd, и переопределять его здесь
   НЕ НУЖНО: если что-то выглядит не так, почти всегда дело в неверной разметке,
   а не в нехватке правила.

   Цвета берутся ТОЛЬКО из переменных: --ant-* (дизайн-система) и --route-*
   (роли точек, assets/theme.css). Хардкод числом сломает тёмную тему —
   она переключается подменой значений этих же переменных.

   При переводе на antd (2026-08-14) сюда переехало нужное из удалённых
   легаси-слоёв (main.css, common.css, header/footer/navigation/responsive.css,
   overlay-and-popover.css, global-common.css, bootstrap-partial). Часть из них
   была в CP1251 и правилась только «сверху» — теперь этой проблемы нет.
   ========================================================================== */

/* ============================ БАЗА ============================ */

body {
  margin: 0;
  font-family: var(--ant-font-family);
  font-size: var(--ant-font-size);
  line-height: var(--ant-line-height);
  color: var(--ant-color-text);
  background: var(--ant-color-bg-layout);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;               /* страховка от горизонтальной прокрутки */
}

a { text-decoration: none; }
li { list-style: none; }
ol, ul { padding-left: 0; margin: 0; }
p { margin-top: 0; }

.clickable { cursor: pointer; }

/* нужно, чтобы иметь вложенные элементы с position:absolute (выпадающие списки) */
.anchor-for-absolute-child {
  position: relative;
  width: 100%;
  height: 0;
  padding: 0;
  margin: 0;
}

.pos-fixed-center {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.pos-fixed-bottom-right { position: fixed; right: 0; bottom: 0; }
.pos-absolute-stretch { position: absolute; inset: 0; }

.page-wrapper {
  min-height: 100vh;
  background: var(--ant-color-bg-layout);
  padding: 0 clamp(12px, 3vw, 32px);
}

/* ============================ ШАПКА ============================ */
/* antd Layout.Header по умолчанию тёмный (#001529) и высотой 64px с полями
   0 50px — это раскладка админки. Здесь шапка светлая и по ширине страницы:
   продукт публичный, а не панель управления. */
.site-header {
  display: flex;
  align-items: center;
  gap: 14px;
  height: auto;
  padding: 12px 0;
  line-height: normal;
  background: transparent;
}

.site-brand { display: flex; align-items: center; flex: 0 0 auto; }
#logo { display: block; width: auto; height: 34px; }

.site-tagline {
  margin: 0;
  font-size: var(--ant-font-size);
  font-weight: 600;
  color: var(--ant-color-text-secondary);
  line-height: 1.25;
}

/* переключатель темы — antd Switch, крайним справа (у .nav-wrapper свой
   margin-left:auto, который забирает всё свободное место) */
.site-theme-switch { order: 99; flex: 0 0 auto; position: relative; }
.site-theme-switch .anticon { font-size: 12px; }

/* ⚠️ Switch у antd — 22px высотой, а в этом продукте действует норма тач-цели
   44px (аудит на iPhone 13 / Pixel 7). Растянуть саму кнопку нельзя: у Switch
   ползунок позиционируется абсолютно относительно неё, и любая правка размеров
   ломает геометрию. Поэтому зону нажатия расширяет невидимый псевдоэлемент —
   он не участвует в раскладке и потому не может вызвать переполнение шапки.
   (У .ant-switch::after своих объявлений, кроме box-sizing, нет — проверено.) */
.site-theme-switch::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 44px;
  height: 44px;
  transform: translate(-50%, -50%);
}

/* ---- навигация ----
   Гамбургер и fly-out отключены (brk.small = 0 в nav-ondemand.js): меню из двух
   коротких пунктов всегда помещается инлайн. Разметка якорей оставлена — модуль
   кладёт #nav-wrapper в parentNode кнопки-гамбургера. */
#nav-show-btn { display: none !important; }
.nav-anchor { display: none; }
.nav-wrapper { margin-left: auto; }

.nav-responsive > ul {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 4px;
  margin: 0;
}
.nav-responsive > ul > li > a {
  display: inline-flex;
  align-items: center;
  min-height: 36px;
  padding: 6px 12px;
  border-radius: var(--ant-border-radius);
  color: var(--ant-color-text-secondary);
  font-weight: 500;
  white-space: nowrap;
  transition: background-color var(--ant-motion-duration-mid), color var(--ant-motion-duration-mid);
}
.nav-responsive > ul > li > a:hover {
  color: var(--ant-color-primary);
  background: var(--ant-color-primary-bg);
}
.nav-responsive > ul > li > a:focus-visible {
  outline: 2px solid var(--ant-color-primary);
  outline-offset: 2px;
}

/* ============================ РАСКЛАДКА ============================ */

.ant-layout-content { padding-bottom: 8px; }

/* сетка antd (ant-row / ant-col) заменила bootstrap. Промежуток между колонками
   даём здесь, а не gutter'ом: gutter у antd — это инлайновые отрицательные поля
   на .ant-row и padding на каждой .ant-col, и считать их в статической разметке
   значит вписывать числа руками в двух местах. */
.content-row {
  position: relative;
  gap: 20px 24px;
  align-items: flex-start;
}
/* .ant-col в antd — flex-элементы с процентной шириной; при собственном gap
   у строки ширины 41.7% + 58.3% в сумме дают ровно 100% и перенос происходил бы
   всегда. Вычитаем промежуток из колонок. */
@media (min-width: 576px) {
  .content-row > .ant-col-sm-10 { max-width: calc(41.66666667% - 12px); flex: 0 0 calc(41.66666667% - 12px); }
  .content-row > .ant-col-sm-14 { max-width: calc(58.33333333% - 12px); flex: 0 0 calc(58.33333333% - 12px); }
}

.route-card { box-shadow: var(--ant-box-shadow-tertiary); }
.route-card .ant-card-head-title { display: flex; align-items: center; gap: 8px; }

/* ============================ ПОЛЯ АДРЕСОВ ============================ */

/* Form.Item по умолчанию отбивает 24px снизу — для трёх полей подряд плюс
   список между ними это слишком воздушно на смартфоне */
.route-card .ant-form-item { margin-bottom: 12px; }

/* ⚠️ Отступ полем, а не пробелом в разметке: подпись у antd — флекс-контейнер,
   и пробел между текстовым узлом и <span> схлопывается («Финиш— необязательно») */
.label-optional {
  margin-left: .35em;
  color: var(--ant-color-text-tertiary);
  font-weight: 400;
}

/* Роль точки видно по цвету иконки в поле — той же, что у маркера на карте.
   Раньше роль отмечалась цветной полосой слева на самом поле; в antd поле имеет
   свою рамку и состояния (hover/focus/error), и полоса с ними конфликтовала. */
.role-start .ant-input-prefix  { color: var(--route-start-bg); }
.role-finish .ant-input-prefix { color: var(--route-finish-bg); }
.address-wrapper:not(.role-field) .ant-input-prefix { color: var(--ant-color-text-tertiary); }

/* подсказка «Финиш не заполнен -> маршрут кольцевой».
   .is-active = точки уже есть, расчёт возможен (до этого подсказка приглушена) */
.role-hint { display: flex; align-items: center; gap: 6px; }
.role-hint.is-active { color: var(--ant-color-text-secondary); }

.bulk-import-row { margin-top: 8px; }

/* ============================ ПОДСКАЗКИ АДРЕСОВ ============================ */
/* Разметка — выпадающий список antd Select. Показом управляет
   search-with-suggestons.js атрибутом data-is-visible. */

.address-suggestions {
  position: absolute;
  top: 4px;
  left: 0;
  width: 100%;
  /* Выше липкой панели действий (см. --z-sticky-actions): на смартфоне список
     поля Финиш уезжал ПОД панель, и первая подсказка была физически
     некликабельна — elementFromPoint возвращал кнопку «Оптимизировать». */
  z-index: var(--z-suggestions);
  opacity: 0;
  transform: scaleY(.85);
  transform-origin: top;
  pointer-events: none;
  transition: opacity var(--ant-motion-duration-mid) var(--ant-motion-ease-in-out),
              transform var(--ant-motion-duration-mid) var(--ant-motion-ease-in-out);
}
.address-suggestions[data-is-visible] {
  opacity: 1;
  transform: scaleY(1);
  pointer-events: auto;
}

/* Не длиннее половины экрана, со своей прокруткой. Было 10 позиций высотой
   414px: на iPhone под клавиатурой оставалось видно 4 из 10. */
.address-suggestions .ant-select-dropdown-list {
  max-height: 44vh;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  padding: 4px;
  margin: 0;
}
/* тач-цель по норме iOS HIG */
.address-suggestions .ant-select-item { min-height: 44px; align-items: center; }

/* адреса длинные — переносим по словам, а не режем многоточием, как делает
   antd в обычном Select: обрезанный адрес выбрать вслепую нельзя */
.address-suggestions .ant-select-item-option-content { white-space: normal; }

/* подсветка стрелками с клавиатуры. Состояние помечено атрибутом
   (search-with-suggestons.js), а не классом antd: на атрибуте держится поиск
   текущего пункта по селектору. Вид берём антдшный — переменную его же Select. */
.address-suggestions .ant-select-item-option[data-is-hovered] {
  background-color: var(--ant-select-option-active-bg, var(--ant-color-fill-tertiary));
}

/* ============================ СПИСОК ТОЧЕК ============================ */

.address-list { margin-bottom: 12px; }

.address-list-box { border-radius: var(--ant-border-radius); }
/* список пуст — показываем antd Empty вместо пустой рамки.
   Класс ставит route-list-view.js (AddressList_EmptyRefresh) */
.address-list-empty { display: none; padding: 8px 0 4px; }
.address-list.is-empty .address-list-empty { display: block; }
.address-list.is-empty .address-list-box { display: none; }

/* Строка списка. Разметку antd List (.ant-list-item) строит
   route-list-view.js — здесь только то, что list компонентом не покрыто:
   перетаскивание, выделение, отметка «далеко». */
#address-list > li.address {
  display: flex;
  align-items: center;
  /* ⚠️ nowrap обязателен: у .ant-list-item из antd стоит flex-wrap:wrap, и на
     смартфоне длинный адрес сталкивал кнопку удаления на вторую строку.
     Сжимается вместо этого сам заголовок (.addr-title, min-width:0). */
  flex-wrap: nowrap;
  gap: 8px;
  /* тач-цель: строку саму по себе нужно уметь тапнуть */
  min-height: 44px;
  padding: 6px 8px;
  border-radius: var(--ant-border-radius);
  cursor: pointer;
  color: var(--ant-color-text);
  transition: background-color var(--ant-motion-duration-mid);
}
#address-list > li.address:hover { background: var(--ant-color-fill-tertiary); }
#address-list > li.address.is-selected { background: var(--ant-color-primary-bg); }

.addr-drag-handle {
  flex: 0 0 auto;
  color: var(--ant-color-text-quaternary);
  cursor: grab;
  font-size: var(--ant-font-size);
  /* ⚠️ На тач-экране ручка — единственный захват для переноса строки, поэтому
     жест над НЕЙ браузеру не отдаём (иначе он уедет в прокрутку страницы, и
     перетащить точку пальцем станет нечем). За остальную часть строки скролл,
     наоборот, обязан работать — там touch-action не трогаем. */
  touch-action: none;
}
#address-list > li.address:hover .addr-drag-handle { color: var(--ant-color-text-tertiary); }

.addr-label {
  flex: 0 0 auto;
  min-width: 1.4em;
  color: var(--ant-color-text-tertiary);
  font-variant-numeric: tabular-nums;
}
.addr-title {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: break-word;
}
.addr-delete,
.addr-edit { flex: 0 0 auto; }

/* Кнопки строки — приглушённые: список читают ради адресов, а не ради кнопок.
   Корзина намеренно НЕ красная (просьба заказчика 2026-08-17): danger-вариант
   antd делал её самым ярким пятном в списке. Опасность действия закрыта
   отменой удаления, а не цветом. */
.addr-delete .anticon,
.addr-edit .anticon { color: var(--ant-color-text-quaternary); transition: color .15s; }
#address-list > li.address:hover .addr-delete .anticon,
#address-list > li.address:hover .addr-edit .anticon { color: var(--ant-color-text-tertiary); }
.addr-delete:hover .anticon { color: var(--ant-color-text-secondary); }
.addr-edit:hover .anticon { color: var(--ant-color-primary); }

/* точка выпадает из маршрута — предупреждение, не запрет */
#address-list > li.address.is-far {
  box-shadow: inset 3px 0 0 var(--route-far-bg);
}
.addr-far-mark { flex: 0 0 auto; color: var(--ant-color-warning); cursor: help; }

/* место под перетаскиваемую строку + сама «карточка» в полёте */
.address-list .placeholder {
  background: var(--ant-color-fill-secondary);
  border-radius: var(--ant-border-radius);
}
.dragged {
  cursor: grabbing;
  z-index: 1000;
  background: var(--ant-color-bg-elevated);
  border-radius: var(--ant-border-radius);
  box-shadow: var(--ant-box-shadow);
  transform: rotate(2deg);
}

@keyframes active-fadeout {
  from { background-color: var(--route-active-bg); }
  to   { background-color: transparent; }
}
.item-active-anim { animation: active-fadeout 2s; }

/* ============================ ДЕЙСТВИЯ ============================ */

.address-list-buttons-wrapper {
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 8px;
  margin-top: 4px;
}
/* ⚠️ width:auto гасит ant-btn-block (width:100%) у кнопки расчёта: с ним она
   занимала всю строку и выталкивала «Поделиться» на вторую. Обе кнопки должны
   стоять в один ряд — на смартфоне это липкая панель, и каждая её лишняя
   строка съедает высоту списка. */
.address-list-buttons-wrapper #route-optimize-btn { flex: 1 1 auto; width: auto; }
.address-list-buttons-wrapper #share-link-btn { flex: 0 0 auto; }

.route-points-counter { font-weight: 400; }

/* подсказки-алерты под кнопками: компактнее обычного antd Alert —
   это реплика на действие, а не сообщение об аварии */
.route-hint-alert,
.geo-outlier-hint { margin-top: 10px; padding: 6px 10px; }
.route-hint-alert .ant-alert-title,
.geo-outlier-hint .ant-alert-title { font-size: var(--ant-font-size-sm); }

/* ============================ ПРОБЕГ ============================ */

.route-summary {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 4px 24px;
  margin-top: 12px;
  padding: 10px 14px;
  border: 1px solid var(--ant-color-border-secondary);
  border-radius: var(--ant-border-radius);
  background: var(--ant-color-fill-quaternary);
}
.route-summary .ant-statistic-title {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-bottom: 0;
  font-size: var(--ant-font-size-sm);
}
/* 24px у antd Statistic — для дашборда; в карточке маршрута это две строки
   цифр рядом с картой, им хватает 20px */
.route-summary .ant-statistic-content { font-size: 20px; }
.route-summary-note { align-self: center; }

/* ============================ УЧАСТКИ МАРШРУТА ============================ */

.route-legs { margin-top: 4px; }
.route-legs .ant-collapse-header { padding-left: 0 !important; }
/* стрелка разворота — antd анимирует её через is-active у item */
.route-legs.is-open .ant-collapse-arrow { transform: rotate(90deg); }
.route-legs .ant-collapse-content-box { padding: 8px 0 0; }

.route-legs-list .leg-label {
  color: var(--ant-color-text-tertiary);
  font-variant-numeric: tabular-nums;
  margin-right: 8px;
}
.route-legs-list .leg-title { color: var(--ant-color-text-secondary); }
.route-legs-list .leg-dist {
  float: right;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* ============================ КАРТА ============================ */

/* ---- Перетаскиваемая граница «список ↔ карта» (assets/split-resize.js) ----
   Повторяет разделитель кабинета frontend-24: полоса во всю высоту с
   «рёбрышками», курсор col-resize. Ширину колонок ставит JS инлайново. */
.col-resizer {
  flex: 0 0 auto;
  align-self: stretch;
  width: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: col-resize;
  touch-action: none;
  user-select: none;
  /* ⚠️ Полоса — ТРЕТИЙ элемент флекс-ряда, и появление третьего элемента
     добавляет ВТОРОЙ column-gap (24px). Колонки же посчитаны ровно под один:
     41.66% - 12px и 58.33% - 12px, то есть вместе W - 24px. Без компенсации
     сумма перестаёт влезать в ряд, у .ant-row стоит flex-wrap:wrap — и карта
     уезжает ПОД список (поймано гейтом: карта начиналась на y=708).
     Отрицательные поля возвращают footprint полосы к -24px: 12 - 18 - 18 = -24,
     плюс два гэпа 48 = 24 итого. Просвет между колонками остаётся прежним
     24px, а сама полоса встаёт ровно посередине него.
     Считать это же число умеет rowInnerWidth() в split-resize.js — там оно
     измеряется, а не зашито. */
  margin: 0 -18px;
}
.col-resizer-grip {
  width: 2px;
  height: 24px;
  border-radius: 1px;
  background: var(--ant-color-text-quaternary);
  box-shadow: 5px 0 0 var(--ant-color-text-quaternary);
  transform: translateX(-2.5px);
  transition: background 0.15s, box-shadow 0.15s;
}
.col-resizer:hover .col-resizer-grip,
.col-resizer.is-active .col-resizer-grip {
  background: var(--ant-color-primary);
  box-shadow: 5px 0 0 var(--ant-color-primary);
}
/* во время перетаскивания курсор обязан оставаться col-resize над ВСЕМ окном,
   включая холст карты — его накрывает прозрачный оверлей из JS */
.col-resize-overlay {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  cursor: col-resize;
}
:root.col-resizing { cursor: col-resize; }
:root.col-resizing * { user-select: none !important; }

/* на смартфоне колонки идут в столбик — двигать нечего */
@media (max-width: 575px) {
  .col-resizer { display: none; }
}

.map-section { padding: 0; }
.map-section .map,
#map.map {
  width: 100%;
  height: 100%;
  min-height: 420px;
  border-radius: var(--ant-border-radius-lg);
  overflow: hidden;
  border: 1px solid var(--ant-color-border-secondary);
  background: var(--map-canvas-bg);
}
/* ---- на десктопе карта занимает экран целиком за вычетом шапки ----
   ⚠️ Высота колонки должна быть ОПРЕДЕЛЁННОЙ (height, а не min-height).
   Раньше здесь стояло min-height: 78vh — и это была тихая поломка: процентная
   высота разрешается только относительно определённой высоты родителя, поэтому
   height:100% у #map откатывался в auto, и карта схлопывалась в собственный
   min-height: 420px внутри 702-пиксельной колонки. Полэкрана под ней пустовало.

   Величины публикует assets/layout-metrics.js: высота шапки не константа
   (на 576-699px тэглайн переносится на три строки, 60 -> 94px), а меню в неё
   кладёт nav-ondemand.js уже после загрузки. Запасные значения в var() —
   замеры широкого десктопа: без JS раскладка остаётся рабочей.
   8px — тот самый padding-bottom у .ant-layout-content, иначе страница
   получила бы вертикальную прокрутку ровно на эту величину. */
@media (min-width: 576px) {
  .map-section {
    height: calc(100dvh
                 - var(--layout-header-h, 60px)
                 - var(--layout-footer-h, 0px)
                 - 8px);
    min-height: 420px;
  }

  /* Подвал пуст: меню из него переносит в шапку nav-ondemand.js (brk.small = 0),
     а 24px полей у antd Layout.Footer оставляли 48px мёртвой полосы, на которую
     карта и не досчитывалась. Поля отдаём самой навигации — если JS не
     отработал и меню осталось внизу, отбивка вернётся вместе с ним. */
  .site-footer { padding: 0; }
  .site-footer .nav-wrapper { padding: 12px 0; }
}

.map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  height: 100%;
  min-height: 420px;
  color: var(--ant-color-text-tertiary);
}
.map-placeholder .ant-spin-dot { font-size: 24px; color: var(--ant-color-primary); }

/* ---- фон холста карты, пока не отрисовался вектор ----
   С 27.08.2026 подложка векторная (OpenFreeMap), и первые ~полсекунды человек
   видит именно этот фон. Цвет РАВЕН фону стиля подложки (см. bm.contrast в
   route-basemap.js), чтобы появление карты не давало ступеньки, и намеренно не
   равен leaflet'овскому #ddd: серый прямоугольник читается как поломка, а фон
   карты — как «карта грузится». */
.leaflet-container { background: #eef0f2; }
:root[data-theme="dark"] .leaflet-container { background: var(--map-canvas-bg); }

/* ---- контролы Leaflet под antd ---- */
.leaflet-bar,
.leaflet-bar a {
  border: 0 !important;
}
.leaflet-bar {
  border-radius: var(--ant-border-radius) !important;
  box-shadow: var(--ant-box-shadow-tertiary) !important;
  overflow: hidden;
}
.leaflet-bar a,
.leaflet-control-locate a,
.leaflet-control-theme a,
.leaflet-control-fit a,
.leaflet-control-fullscreen a {
  display: flex !important;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  background: var(--ant-color-bg-elevated) !important;
  color: var(--ant-color-text-secondary) !important;
  font-size: var(--ant-font-size-lg);
  line-height: 1 !important;
  cursor: pointer;
  transition: background-color var(--ant-motion-duration-mid), color var(--ant-motion-duration-mid);
}
.leaflet-control-locate a,
.leaflet-control-theme a,
.leaflet-control-fit a,
.leaflet-control-fullscreen a {
  border-radius: var(--ant-border-radius);
  box-shadow: var(--ant-box-shadow-tertiary);
}
.leaflet-bar a:hover,
.leaflet-control-locate a:hover,
.leaflet-control-theme a:hover,
.leaflet-control-fit a:hover,
.leaflet-control-fullscreen a:hover {
  background: var(--ant-color-primary-bg) !important;
  color: var(--ant-color-primary) !important;
}
.leaflet-bar a.leaflet-disabled {
  background: var(--ant-color-bg-container-disabled) !important;
  color: var(--ant-color-text-quaternary) !important;
}
.leaflet-control-locate.is-busy a { opacity: .6; pointer-events: none; }

/* атрибуция и масштабная линейка */
.leaflet-control-attribution {
  background: var(--ant-color-bg-elevated) !important;
  color: var(--ant-color-text-tertiary) !important;
  border-radius: var(--ant-border-radius) 0 0 0;
}
.leaflet-control-attribution a { color: var(--ant-color-text-secondary) !important; }

:root[data-theme="dark"] .leaflet-popup-content-wrapper,
:root[data-theme="dark"] .leaflet-popup-tip {
  background: var(--ant-color-bg-elevated);
  color: var(--ant-color-text);
}

/* ---- контрастность РАСТРОВОЙ подложки (CARTO light_all/dark_all) ----
   Растром карта стартует у всех и остаётся насовсем у англоязычных, без WebGL2,
   при таймауте и потере WebGL-контекста. Тайлы — чужие картинки, поднять
   контраст можно только фильтром. Векторная подложка правится иначе
   (Basemap_StyleContrast), поэтому класс висит на контейнере ИМЕННО растрового
   слоя: фильтровать .leaflet-tile-pane целиком нельзя — вектор лежит в том же
   pane (pane:'tilePane') и получил бы коррекцию поверх своей собственной.

   ⚠️ Порядок функций принципиален. Голый contrast() сделал бы ХУЖЕ: он
   поворачивает шкалу вокруг средне-серого, а вся палитра Positron лежит в
   яркости 0.85–1.0 — её прижало бы к белому и разница «полотно vs обводка»
   схлопнулась. Поэтому сначала brightness(b<1) опускает диапазон через точку
   поворота, и только потом contrast(k) при k = 0.5/(b−0.5): при таком k чистый
   белый остаётся ровно белым, а всё более тёмное расходится. Местами не менять —
   CSS клампит результат после каждой функции, и белый бы «сгорел».

   Числа подобраны по кадрам (Москва, зум 14): при b=.88 обводка #ddd темнеет
   всего на .02 и картинка визуально не меняется, при b=.67 карта уже выглядит
   грязной. b=.72 -> k = 0.5/.22 = 2.27. */
.map-theme-light .basemap-raster { filter: brightness(.72) contrast(2.27) saturate(1.15); }

/* ⚠️ В тёмной теме формула ДРУГАЯ, и это не небрежность. Там фон почти чёрный
   (0.055), а дороги серые (~0.28), то есть НИЖЕ точки поворота: contrast()
   потянул бы их к фону, ровно против задачи. Контраст здесь набирается
   яркостью — она разводит дорогу и фон мультипликативно, а фон при этом почти
   не всплывает. Выше 1.3 тёмная тема начинает сереть и терять характер. */
.map-theme-dark .basemap-raster { filter: brightness(1.3) contrast(1.08) saturate(1.15); }

/* ---- маркеры-бейджи ----
   divIcon-пузырёк с хвостиком. Точка привязки (iconAnchor [0,0], см.
   Map_makeBadgeIcon) = левый верхний угол обёртки Leaflet, т.е. РОВНО координата
   адреса. Геометрия детерминированная: кончик хвостика приходится точно на
   координату при любом масштабе и в любом браузере.
     .mrk-badge  — контейнер строго 0x0 в точке привязки (раньше это был пустой
                   INLINE-бокс, его высота зависела от метрик шрифта);
     .mrk-bubble — bottom:6px => низ пузырька на 6px выше координаты (место под
                   хвостик). Раньше здесь было bottom:0 + translateY(-100%) —
                   двойной сдвиг, из-за которого маркер стоял на ~22px выше;
     .mrk-tail   — CSS-треугольник РОВНО 6px высотой (повёрнутый на 45° квадрат
                   давал дробную вершину 5.66px). */
.mrk-badge { position: absolute; left: 0; top: 0; width: 0; height: 0; }
.mrk-bubble {
  position: absolute; left: 0; bottom: 6px;
  transform: translateX(-50%);
  min-width: 24px; height: 22px; box-sizing: border-box; padding: 0 6px;
  display: flex; align-items: center; justify-content: center;
  border-radius: var(--ant-border-radius);
  white-space: nowrap;
  font-family: var(--ant-font-family);
  font-weight: 600; font-size: var(--ant-font-size-sm); line-height: 1;
  box-shadow: var(--ant-box-shadow-tertiary);
}
.mrk-tail {
  position: absolute; left: 50%; top: 100%;
  width: 0; height: 0; margin-left: -5px;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 6px solid transparent;
}
.mrk-start  .mrk-bubble { background: var(--route-start-bg);  color: var(--route-start-fg); }
.mrk-start  .mrk-tail   { border-top-color: var(--route-start-bg); }
.mrk-finish .mrk-bubble { background: var(--route-finish-bg); color: var(--route-finish-fg); }
.mrk-finish .mrk-tail   { border-top-color: var(--route-finish-bg); }
.mrk-mid    .mrk-bubble { background: var(--route-mid-bg);    color: var(--route-mid-fg); }
.mrk-mid    .mrk-tail   { border-top-color: var(--route-mid-bg); }
.mrk-far    .mrk-bubble { background: var(--route-far-bg);    color: var(--route-far-fg); }
.mrk-far    .mrk-tail   { border-top-color: var(--route-far-bg); }
/* подсветка выбранной точки важнее отметки «далеко» — правило идёт после,
   иначе выбранный выброс не отличался бы от невыбранного */
.mrk-active .mrk-bubble,
.mrk-far.mrk-active .mrk-bubble { background: var(--route-active-bg); color: var(--route-active-fg); }
.mrk-active .mrk-tail,
.mrk-far.mrk-active .mrk-tail   { border-top-color: var(--route-active-bg); }

/* ---- подсказка «двумя пальцами» (см. Map_GestureHandling_Init) ---- */
.map-gesture-hint {
  position: absolute;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  text-align: center;
  color: #fff;
  background: var(--ant-color-bg-mask);
  font-size: var(--ant-font-size-lg);
  font-weight: 500;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--ant-motion-duration-mid) ease;
}
.map-gesture-hint.is-visible { opacity: 1; }

/* ---- «Карта загружается…» (см. Basemap_LoadingShow) ---- */
/* Промежуток, когда подложки нет ни векторной, ни растровой: карта — ровная
   заливка цветом фона стиля. Молчаливая заливка читается как поломка, а на
   медленном мобильном она длится десяток секунд, пока едет maplibre и стиль.
   ⚠️ Раньше это место занимал аварийный растр CARTO — то есть карта с надписью
   «API KEY REQUIRED» поперёк. Она и была тем, на что жаловался владелец:
   «загружается» читается как ожидание, «нужен ключ» — как сломанный продукт. */
.basemap-loading {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  /* выше тайлов (200) и оверлеев (400), ниже контролов Leaflet (800) */
  z-index: 450;
  /* карту можно тащить и зумить прямо сквозь подпись */
  pointer-events: none;
  padding: 8px 16px;
  border-radius: var(--ant-border-radius-lg);
  background: var(--ant-color-bg-elevated);
  color: var(--ant-color-text-secondary);
  font-size: var(--ant-font-size);
  box-shadow: var(--ant-box-shadow-secondary);
  white-space: nowrap;
}

/* ---- режим «поставить точку кликом» (см. Map_addPickControl) ---- */
/* Включённый режим обязан читаться с одного взгляда: подсвеченная кнопка,
   перекрестье вместо ладони и плашка с объяснением. Иначе следующий клик по
   карте (например, чтобы её подвинуть) станет для человека неожиданностью. */
.leaflet-control-pick a.is-active {
  background: var(--ant-color-primary);
  color: #fff;
}
:root.map-pick-active .leaflet-container,
:root.map-pick-active .leaflet-grab,
:root.map-pick-active .leaflet-interactive { cursor: crosshair; }

.map-pick-hint {
  position: absolute;
  left: 50%;
  bottom: 16px;
  transform: translateX(-50%);
  z-index: 900;
  max-width: calc(100% - 24px);
  padding: 6px 14px;
  border-radius: var(--ant-border-radius-lg);
  background: var(--ant-color-bg-elevated);
  box-shadow: var(--ant-box-shadow);
  color: var(--ant-color-text);
  font-size: var(--ant-font-size);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  pointer-events: none;
}
.map-pick-hint[hidden] { display: none; }

/* ---- контейнер всплывающих сообщений (Antd.message) ----
   🐛 ⚠️ БЕЗ ЭТИХ ДВУХ СТРОК ПЕРВЫЙ ЖЕ ТОСТ ГАСИТ КЛИКИ ПО ВСЕЙ СТРАНИЦЕ.
   У antd `.ant-message` — это `position:fixed; width:100%; height:100vh`, то есть
   коробка во всё окно, а `pointer-events:none` висит у него на классе
   `.ant-message-list`, которого наш хост (antd-ui.js) не носит. Хост создаётся
   один раз и из DOM больше не удаляется — сам тост исчезает через 3 секунды, а
   прозрачная пустая плёнка остаётся навсегда и перехватывает всё.
   Дефект лежал спящим с перехода на antd: `Antd.message` до 18.08 никто не звал.
   Ровно та же болезнь была у контейнера уведомлений — см. `.notificaitons-wrapper`
   выше. Поймано гейтом: Playwright не смог нажать карандаш в строке. */
.antd-message-host { pointer-events: none; }
.antd-message-host .ant-message-notice { pointer-events: auto; }

/* ---- третий способ добавить точку: подсказка в пустом списке ---- */
.address-list-empty-hint {
  margin-top: 8px;
  color: var(--ant-color-text-tertiary);
  font-size: var(--ant-font-size-sm);
  line-height: var(--ant-line-height-sm);
}
.address-list-empty-tag {
  margin-inline: 2px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

/* ---- карта на весь экран ---- */
:root.map-is-fullscreen body { overflow: hidden; }
:root.map-is-fullscreen .map-section {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  /* ⚠️ height: auto обязателен: на десктопе у колонки задана точная высота
     (экран минус шапка), а при inset:0 она пересилила бы bottom — полный
     экран оказался бы короче экрана на высоту шапки. */
  height: auto;
  min-height: 0;
  padding: 0;
  margin: 0;
  /* ⚠️ !important обязателен из-за разделителя колонок: split-resize.js задаёт
     ширину карты ИНЛАЙНОВЫМ стилем (`max-width: 940px`), а инлайн сильнее любого
     класса. Без !important полноэкранная карта оставалась бы шириной с колонку —
     то есть кнопка «на весь экран» после перетаскивания границы переставала бы
     работать. Ширину здесь задаём тоже принудительно, на случай если инлайновых
     свойств станет больше. */
  max-width: none !important;
  width: 100vw !important;
  flex: 0 0 auto !important;
}
:root.map-is-fullscreen #map.map {
  height: 100% !important;
  width: 100% !important;
  border-radius: 0;
  border: 0;
}

/* ============================ МОДАЛЬНЫЕ ОКНА ============================ */
/* Разметка — анатомия antd Modal, но показом управляет PopoverEngine
   (assets/popover.js): атрибут hidden на окне + общий #overlay в роли маски.
   Поэтому позиционирование задаём здесь, а не через .ant-modal-wrap. */

.overlay {
  position: fixed;
  inset: 0;
  z-index: calc(var(--z-modal) - 1);
  background: var(--ant-color-bg-mask);
  animation: antFadeIn var(--ant-motion-duration-mid) var(--ant-motion-ease-in-out);
}
.overlay[hidden] { display: none; }

.popover.overlaid,
.splash.overlaid {
  z-index: var(--z-modal);
  width: min(32rem, calc(100vw - 24px));
  max-width: calc(100vw - 24px);
  /* низкое окно (ноутбук 1280x620, смартфон в альбомной): окно не должно
     вылезать за экран — шапка и футер остаются, тело прокручивается.
     dvh, а не vh: на смартфоне адресная строка схлопывается */
  max-height: calc(100dvh - 24px);
  display: flex;
  flex-direction: column;
  animation: mini-zoom-in-center var(--ant-motion-duration-mid) var(--ant-motion-ease-in-out);
}

/* ⚠️ Своя копия antZoomIn, а не antd'шная — иначе окно уезжает от центра.
   Центрирование здесь держится на `transform: translate(-50%,-50%)`
   (.pos-fixed-center), а @keyframes antZoomIn анимирует ТУ ЖЕ `transform`
   (scale .2 → 1). Пока идёт анимация, ключевой кадр ПЕРЕБИВАЕТ центрирующий
   сдвиг: окно 200 мс растёт из центра экрана вправо-вниз своим левым верхним
   углом и лишь на последнем кадре прыгает на место. Заметнее всего это было на
   сплэше «Расчёт маршрута» и на смартфоне. Поэтому сдвиг повторён в КАЖДОМ
   кадре — трогать нельзя, иначе дефект вернётся (правка 2026-08-17). */
@keyframes mini-zoom-in-center {
  0%   { transform: translate(-50%, -50%) scale(0.88); opacity: 0; }
  100% { transform: translate(-50%, -50%) scale(1);    opacity: 1; }
}
.popover[hidden],
.splash[hidden] { display: none; }

.popover .ant-modal-container {
  display: flex;
  flex-direction: column;
  min-height: 0;
  max-height: calc(100dvh - 24px);
}
/* min-height:0 — иначе флекс-элемент не сжимается ниже своего содержимого
   и прокрутка внутри окна не включается */
.popover .ant-modal-body { min-height: 0; overflow-y: auto; }

/* ---- поповер «Поделиться» ----
   Шире стандартного: ссылка вида https://daymilo.com/p/xxxxxxx должна
   влезать целиком, её читают глазами перед отправкой */
#popover-link-share { width: min(34rem, calc(100vw - 24px)); }

.share-explain { display: flex; align-items: center; gap: 14px; }
.share-explain-text { margin: 0; color: var(--ant-color-text-secondary); }

.link-to-share { margin-top: 16px; }
.link-to-share-url {
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--ant-color-border);
  border-radius: var(--ant-border-radius);
  background: var(--ant-color-fill-quaternary);
  color: var(--ant-color-text);
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: var(--ant-font-size-sm);
  /* ссылка должна быть видна ЦЕЛИКОМ */
  white-space: normal;
  overflow-wrap: break-word;
}
.link-to-share-url:focus-visible {
  outline: 0;
  border-color: var(--ant-color-primary);
}

.copy-status { margin: 8px 0 0; font-size: var(--ant-font-size-sm); font-weight: 500; }
.copy-status[hidden] { display: none; }
.copy-status.is-ok   { color: var(--ant-color-success); }
.copy-status.is-warn { color: var(--ant-color-warning); }

.popover-disable { background: var(--ant-color-bg-container); opacity: .5; }

/* --- пояснительная анимация: карточка-ссылка уезжает в телефон курьера,
       на экране проявляется маршрут A→B. Чистый CSS, без картинок. --- */
.share-anim { position: relative; flex: 0 0 auto; width: 84px; height: 66px; }
.share-anim-phone {
  position: absolute; right: 0; top: 0;
  width: 38px; height: 66px; box-sizing: border-box;
  border: 2px solid var(--ant-color-border);
  border-radius: 8px;
  background: var(--ant-color-bg-container);
}
.share-anim-phone:before {                 /* «динамик» */
  content: ""; position: absolute; left: 50%; top: 4px;
  width: 12px; height: 2px; margin-left: -6px;
  border-radius: 2px; background: var(--ant-color-border);
}
.share-anim-link {                         /* летящая карточка со ссылкой */
  position: absolute; left: 0; top: 24px;
  width: 34px; height: 14px;
  border-radius: 4px;
  background: var(--ant-color-primary);
  animation: share-fly 3.2s ease-in-out infinite;
}
.share-anim-link:before {                  /* «текст» внутри карточки */
  content: ""; position: absolute; left: 5px; top: 6px;
  width: 20px; height: 2px; border-radius: 2px;
  background: rgba(255,255,255,.85);
}
.share-anim-route {
  position: absolute; left: 8px; top: 12px; right: 8px; bottom: 10px;
  opacity: 0;
  animation: share-route-in 3.2s ease-in-out infinite;
}
.share-anim-dot { position: absolute; width: 8px; height: 8px; border-radius: 50%; }
.share-anim-dot-a { left: 0; top: 0; background: var(--route-start-bg); }
.share-anim-dot-b { right: 0; bottom: 0; background: var(--route-finish-bg); }
.share-anim-line {
  position: absolute; left: 4px; top: 4px;
  width: 2px; height: calc(100% - 8px);
  background: repeating-linear-gradient(180deg, var(--route-line) 0 4px, transparent 4px 7px);
}
/* 50px = от левого края до середины «телефона» (он справа, шириной 38px) */
@keyframes share-fly {
  0%,  8%  { transform: translate(0, 0) scale(1);      opacity: 1; }
  45%      { transform: translate(50px, 0) scale(.72); opacity: 1; }
  55%,100% { transform: translate(50px, 0) scale(.5);  opacity: 0; }
}
@keyframes share-route-in {
  0%,  48%  { opacity: 0; }
  62%, 100% { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .share-anim-link { animation: none; }
  .share-anim-route { animation: none; opacity: 1; }
}

/* ============================ ЗАГРУЗКА АДРЕСОВ ============================ */

#popover-bulk-import { width: min(40rem, calc(100vw - 24px)); }

.bulk-import-steps { padding: 12px 24px 0; }
.bulk-import-lead { margin-bottom: 12px; color: var(--ant-color-text-secondary); }

/* Три варианта в столбик на широком окне = три пустые полосы во всю ширину.
   От 640px ставим их в ряд. Порог по ОКНУ, а не по поповеру: при 640px окну
   достаётся ~38rem, на вариант приходится ~190px. */
.bulk-import-options { display: grid; gap: 10px; }
@media (min-width: 640px) {
  .bulk-import-options { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
}

/* вариант загрузки — antd Button, но с содержимым в несколько строк */
.bulk-import-option {
  display: block !important;
  height: auto !important;
  width: 100%;
  text-align: left;
  white-space: normal;
  padding: 12px 14px !important;
  line-height: 1.35;
}
.bulk-import-option-icon {
  display: block;
  margin-bottom: 6px;
  font-size: 20px;
  color: var(--ant-color-primary);
}
.bulk-import-option > b { display: block; margin-bottom: 2px; }
.bulk-import-option > span:not(.bulk-import-option-icon) {
  display: block;
  font-size: var(--ant-font-size-sm);
  font-weight: 400;
  color: var(--ant-color-text-tertiary);
}
.bulk-import-soon { margin-left: 6px; vertical-align: 1px; }

.bulk-import-textarea {
  /* < 16px => iOS зумит страницу при фокусе */
  font-size: 16px !important;
  line-height: 1.45;
  resize: vertical;
}
.bulk-import-count { margin: 8px 0 0; font-size: var(--ant-font-size-sm); color: var(--ant-color-text-tertiary); }

.bulk-import-footer { display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap; }
.bulk-import-footer > button[hidden] { display: none; }

/* экран «Уточните»: по строке на неоднозначный адрес */
.bulk-clarify-list {
  margin: 0;
  /* вариантов бывает по 4 на строку — список прокручивается внутри окна,
     чтобы кнопки действий оставались на виду */
  max-height: 46vh;
  overflow-y: auto;
}
.bulk-clarify-list > li {
  padding: 10px 0;
  border-bottom: 1px solid var(--ant-color-border-secondary);
}
.bulk-clarify-list > li:last-child { border-bottom: 0; }
.bulk-clarify-query {
  margin: 0 0 6px;
  font-weight: 600;
  color: var(--ant-color-text);
  overflow-wrap: break-word;
}
.bulk-clarify-options { display: flex; flex-direction: column; gap: 2px; }
/* тач-цель по норме iOS HIG. Одной строкой вариант набирал ~35px —
   задаём высоту явно, а не надеемся на padding + line-height */
.bulk-clarify-options .ant-radio-wrapper {
  align-items: flex-start;
  min-height: 44px;
  padding: 9px 4px;
  margin: 0;
  border-radius: var(--ant-border-radius);
  white-space: normal;
}
.bulk-clarify-options .ant-radio-wrapper:hover { background: var(--ant-color-fill-tertiary); }
.bulk-clarify-skip { color: var(--ant-color-text-tertiary); font-style: italic; }

.bulk-import-fail-list { margin: 8px 0 0; max-height: 30vh; overflow-y: auto; }
.bulk-import-fail-list > li {
  padding: 6px 10px;
  margin-bottom: 4px;
  border-radius: var(--ant-border-radius);
  background: var(--ant-color-fill-tertiary);
  color: var(--ant-color-text-secondary);
  font-size: var(--ant-font-size-sm);
  word-break: break-word;
}
.bulk-import-result-box { padding: 8px 0; }
#bulk-import-result-far { color: var(--ant-color-warning-text); }
#bulk-import-result-far[hidden] { display: none; }

/* ============================ РАСЧЁТ МАРШРУТА ============================ */
/* Индикатор — SVG-маршрут, который прокладывается сам: бегущий отрезок по
   трассе + подсвечивающиеся по очереди точки. Чрома вокруг — antd Spin. */

#splash-route-optimization {
  width: auto;
  /* коробка считается от индикатора (148px) + поля, а не от абстрактных 15rem:
     раньше min-width жил своей жизнью и подпись с картинкой стояли вразнобой */
  min-width: calc(148px + 2 * 28px);
  max-width: calc(100vw - 24px);
  padding: 28px;
  border-radius: var(--ant-border-radius-lg);
  background: var(--ant-color-bg-elevated);
  box-shadow: var(--ant-box-shadow);
  align-items: center;
  text-align: center;
}
.route-loader {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  width: 100%;
}
/* ⚠️ Обёртке индикатора нужен СВОЙ размер, равный размеру SVG.
   Пока на ней висел класс `ant-spin-dot`, antd держал её 32×32 (`width:1em`
   при `font-size:32px`) — SVG 148×76 вылезал за карточку вправо и вниз, ложился
   на подпись, а flex-колонка мерила высоту по 32px, из-за чего gap и
   выравнивание считались от неверной коробки. Класс убран из разметки, размер
   задаётся здесь. Числа согласованы с viewBox="0 0 148 76". */
.route-loader__dot {
  display: block;
  width: 148px;
  height: 76px;
  max-width: 100%;
}
.route-loader__svg { display: block; width: 100%; height: 100%; overflow: visible; }

.rl-grad-from { stop-color: var(--ant-color-primary); }
.rl-grad-to   { stop-color: var(--route-line); }

.rl-track, .rl-snake { fill: none; stroke-width: 5; stroke-linecap: round; }
.rl-track { stroke: var(--ant-color-fill-secondary); }
.rl-snake {
  stroke: url(#rl-grad);
  /* сумма штрих+пробел = pathLength(100) → цикл бесшовный */
  stroke-dasharray: 30 70;
  animation: rl-run 1.9s linear infinite;
}
@keyframes rl-run {
  from { stroke-dashoffset: 100; }
  to   { stroke-dashoffset: 0; }
}
.rl-node {
  fill: var(--ant-color-bg-elevated);
  stroke: var(--ant-color-primary);
  stroke-width: 3;
  transform-box: fill-box;
  transform-origin: center;
  animation: rl-pop 1.9s ease-in-out infinite;
}
/* задержки = моменту, когда мимо точки проходит бегущий отрезок */
.rl-node--2 { animation-delay: 0.6s; }
.rl-node--3 { animation-delay: 1.2s; }
@keyframes rl-pop {
  0%, 55%, 100% { transform: scale(1);    opacity: 0.75; }
  18%           { transform: scale(1.45); opacity: 1; }
}
.route-loader__text {
  margin: 0;
  font-weight: 500;
  font-size: var(--ant-font-size-lg);
  line-height: var(--ant-line-height-lg);
  color: var(--ant-color-text);
  /* подпись и многоточие — одной строкой по центру: точки дорисовываются
     справа от текста и не должны утаскивать его влево */
  white-space: nowrap;
}
.rl-dots i {
  display: inline-block;
  width: 3px; height: 3px; margin-left: 3px;
  border-radius: 50%;
  background: currentColor;
  vertical-align: 0.15em;
  opacity: 0.2;
  animation: rl-blink 1.4s ease-in-out infinite;
}
.rl-dots i:nth-child(2) { animation-delay: 0.18s; }
.rl-dots i:nth-child(3) { animation-delay: 0.36s; }
@keyframes rl-blink {
  0%, 70%, 100% { opacity: 0.2; }
  35%           { opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .rl-snake { animation-duration: 4s; }
  .rl-node, .rl-dots i { animation: none; opacity: 1; }
}

/* ============================ УВЕДОМЛЕНИЯ И ТОСТ ============================ */

.notificaitons-wrapper {
  position: fixed;
  top: 16px;
  right: 16px;
  /* ⚠️ bottom/left гасим явно: у antd контейнер уведомлений — растянутый на всё
     окно список со своей прокруткой, и без этого он занимал 384×(вся высота).
     Тогда он накрывал правый край шапки, и пункты меню переставали нажиматься —
     поймано автотестом (клик по «О проекте» уходил в пустой контейнер). */
  bottom: auto;
  left: auto;
  height: auto;
  max-height: calc(100vh - 32px);
  z-index: var(--z-notification);
  display: flex;
  flex-direction: column;
  gap: 12px;
  max-width: min(384px, calc(100vw - 32px));
  /* и вторая половина той же защиты, ровно как делает antd: сквозь пустое место
     контейнера клики проходят, ловят их только сами уведомления */
  pointer-events: none;
}
.notificaitons-wrapper .error-message,
.undo-toast .ant-message-notice { pointer-events: auto; }
.undo-toast { pointer-events: none; }
.notificaitons-wrapper .error-message[hidden] { display: none; }
/* ⚠️ antd раскладывает уведомления стопкой и потому ставит каждому
   position:absolute с посчитанным сдвигом. Стопкой управляет его React-слой,
   которого здесь нет: у нас обычный поток из NotificationEngine. Возвращаем
   элементы в поток, иначе все уведомления легли бы друг на друга. */
.notificaitons-wrapper .ant-notification-notice,
.undo-toast .ant-message-notice { position: relative; }

/* тост «Отменить удаление» — antd message, но снизу: сверху на смартфоне его
   перекрывала бы клавиатура, а действие нужно в пределах большого пальца */
.undo-toast {
  position: fixed;
  left: 50%;
  bottom: calc(16px + env(safe-area-inset-bottom, 0px));
  transform: translateX(-50%);
  z-index: var(--z-undo-toast);
  max-width: min(calc(100vw - 24px), 440px);
}
.undo-toast[hidden] { display: none; }
.undo-toast .ant-message-notice-content { display: inline-flex; align-items: center; }
.undo-toast .ant-message-custom-content { display: flex; align-items: center; gap: 8px; }
.undo-toast-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.undo-toast-btn { flex: 0 0 auto; }

/* ============================ ОНБОРДИНГ ============================ */
/* Четыре подсказки по интерфейсу тем, кто здесь впервые (кому и когда — в
   assets/onboarding.js). Карточка — antd Tour; подсветка своя: затемнение
   рисует не отдельный слой, а огромная тень «дырки» .onb-hole, поэтому
   подсвеченный элемент виден в своём естественном виде и его z-index не трогаем.

   Слой ниже модалок и уведомлений: если тур совпал бы с открытым окном,
   окно и должно быть сверху. */
.onboarding { position: fixed; inset: 0; z-index: var(--z-onboarding); }
.onboarding[hidden] { display: none; }

/* прозрачный слой: затемняет тень .onb-hole, а этот ловит клики — страница под
   подсказками не должна «жить». touch-action: свайп по нему не таскает страницу */
.onb-backdrop { position: absolute; inset: 0; touch-action: none; cursor: pointer; }

.onb-hole {
  position: fixed;
  border-radius: var(--ant-border-radius-lg);
  pointer-events: none;                 /* сама «дырка» кликов не ловит */
  box-shadow: 0 0 0 3px var(--ant-color-primary), 0 0 0 9999px var(--ant-color-bg-mask);
  transition: top .22s ease, left .22s ease, width .22s ease, height .22s ease;
}
/* шаг без подсветки (элемент не найден) — затемняем экран целиком */
.onb-no-hole .onb-backdrop { background: var(--ant-color-bg-mask); }

.onb-card {
  position: fixed;
  width: min(23rem, calc(100vw - 24px));
  /* низкое окно (смартфон в альбомной) — текст прокручивается внутри карточки */
  max-height: 70vh;
  overflow-y: auto;
}
.onb-card .ant-tour-actions { display: flex; align-items: center; gap: 4px; }

/* Пузырь Jivo (z-index 2147483647) перекрыть слоем невозможно — на время тура
   прячем его целиком. Правило идёт ПОСЛЕ блока .jivo-allowed (специфичность
   одинаковая, решает порядок в файле) — см. ниже. */

/* ============================ СМАРТФОН ============================ */

@media (max-width: 575px) {
  .page-wrapper { padding: 0 12px; }

  /* тэглайн прячем: на узком экране шапке хватает логотипа и меню */
  .site-tagline { display: none; }
  .site-header { gap: 8px; padding: 10px 0; }

  /* ⚠️ Ширину логотипа задаём ЧИСЛОМ, а не через высоту. Пропорции у него
     458×81, и height:30px давали ширину 170px — вместе с меню и переключателем
     шапка вылезала за 390px и роняла всю страницу в горизонтальную прокрутку
     (а с ней уезжали вправо модалки: они центрируются по ширине документа).
     Поймано автотестом: scrollWidth 395 при окне 390. */
  #logo { width: 130px; height: auto; }
  .nav-responsive > ul > li > a { padding: 6px 8px; font-size: 13px; }

  /* --- Липкая панель действий ---
     При 11 точках кнопка «Оптимизировать» уезжала на 811px при экране 664px,
     то есть за экран, и её приходилось искать скроллом. */
  .address-list-buttons-wrapper {
    position: sticky;
    bottom: 0;
    z-index: var(--z-sticky-actions);
    /* ⚠️ nowrap, а не wrap: обе кнопки обязаны стоять в одну строку. Панель
       липкая и перекрывает список точек — каждая её лишняя строка отнимает
       высоту у того, ради чего пользователь сюда пришёл. Место под подписи
       выкраивается сжатием кнопки расчёта (min-width:0 ниже), а не переносом. */
    flex-wrap: nowrap;
    padding: 8px 0 calc(8px + env(safe-area-inset-bottom, 0px));
    margin-top: 4px;
    background: var(--ant-color-bg-container);
    box-shadow: 0 -8px 20px -12px rgba(0, 0, 0, .35);
  }
  /* подпись в одну строку: перенос делал бы липкую панель выше,
     а она и так перекрывает список */
  .address-list-buttons-wrapper .ant-btn { white-space: nowrap; }

  /* --- тач-цели по норме 44px (аудит на iPhone 13 / Pixel 7) ---
     Только на смартфоне: на десктопе мышь точная, и раздутые до 44px кнопки
     ломали бы плотность, заданную Ant Design (у него контрол = 32px).

     ⚠️ Кнопке удаления даём РЕАЛЬНЫЙ размер, а не невидимое расширение зоны,
     как у переключателя темы. Рядом с ней кликабельна сама строка («показать
     точку на карте»), и невидимый нахлёст означал бы, что промах по краю
     удаляет адрес вместо того, чтобы его показать. Для разрушительного
     действия это недопустимо. */
  #address-list > li.address .addr-delete,
  #address-list > li.address .addr-edit { width: 44px; height: 44px; }

  /* ⚠️ Ручка на смартфоне — тоже настоящая тач-цель: с 2026-08-17 пальцем
     строка переносится ТОЛЬКО за неё, и иконка шириной в 14px этого не
     выдерживает. Здесь невидимое расширение зоны безопасно (в отличие от
     кнопки удаления): промах даёт максимум лишний захват, а не потерю адреса. */
  #address-list > li.address .addr-drag-handle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 44px;
    margin-left: -8px;
    margin-right: -6px;
    font-size: 18px;
  }

  /* строка стала плотнее: ручка 44px + номер + адрес + карандаш + корзина */
  #address-list > li.address { gap: 4px; padding: 4px 6px; }

  .nav-responsive > ul > li > a { min-height: 44px; }
  /* большая кнопка antd = 40px; на смартфоне добираем до нормы.
     Шрифт и поля при этом ужимаем: обе кнопки обязаны встать в ОДНУ строку —
     панель липкая, и каждая лишняя её строка съедает высоту списка точек. */
  .address-list-buttons-wrapper .ant-btn {
    height: 44px;
    /* 13px подобран так, чтобы ОБЕ подписи влезали целиком: у большой кнопки
       antd шрифт 16px, и при нём «Оптимизировать маршрут» обрезалось
       многоточием — обрезать подпись главного действия недопустимо */
    font-size: 13px;
    padding-inline: 8px;
  }

  /* кнопке расчёта разрешаем сжиматься, подпись при нехватке места
     обрезается многоточием — но обе кнопки остаются в строке */
  .address-list-buttons-wrapper #route-optimize-btn { min-width: 0; }
  .route-optimize-caption { overflow: hidden; text-overflow: ellipsis; }

  /* поля карточки у antd — 24px; на 390px это 48px из 366 доступных,
     то есть восьмая часть экрана под воздух */
  .route-card .ant-card-body { padding: 16px; }
  .route-card .ant-card-head { padding-inline: 16px; }

  /* --- Карта: ниже, чтобы не быть ловушкой скролла на весь экран ---
     Было 70% экрана; свайп по ней панорамировал карту вместо прокрутки страницы */
  .map-section { min-height: 0; }
  #map.map { height: var(--map-height-mobile); min-height: 220px; }
  .map-placeholder { min-height: 0; height: var(--map-height-mobile); }

  /* --- Карта над формой ---
     Порядок задаётся ТОЛЬКО раскладкой: в разметке карта остаётся после формы,
     поэтому ни порядок табуляции, ни чтение с экрана не меняются - меняется
     лишь то, что человек видит первым.

     ⚠️ Это стало возможно после того, как карта на телефоне уменьшилась с 52dvh
     до 34dvh. При прежней высоте своп уводил поле старта под сгиб - а его и так
     не заполняет половина сессий. Замер при 390x844 после уменьшения:
     карта 64-351, поле старта 481, поле адреса 592, кнопка расчёта 792 -
     всё помещается на первый экран. На 360x640 старт и адрес тоже видны,
     а кнопки расчёта держит липкая панель внизу. */
  .content-row > .map-section { order: -1; }

  /* --- Пустое состояние списка: на телефоне не показываем вовсе ---
     Картинка antd Empty с подписью «Добавьте точки маршрута» и подсказкой про
     клик по карте занимала целый экран между полем адреса и кнопкой расчёта,
     то есть отодвигала вниз ровно то, ради чего человек пришёл. На телефоне
     режим «точка кликом» и так доступен кнопкой на самой карте, которая теперь
     стоит первой. Селектор перебивает `.address-list.is-empty
     .address-list-empty { display: block }` выше по файлу. */
  .address-list.is-empty .address-list-empty { display: none; }

  /* маршрут посчитан - смотреть теперь есть на что, и карта разворачивается.
     Класс route-ready ставит optimal-route-app.js (RouteReady_Update). */
  :root.route-ready #map.map,
  :root.route-ready .map-placeholder { height: var(--map-height-mobile-result); }

  /* --- Виджет поддержки Jivo ---
     85×85 с z-index 2147483647 в правом нижнем углу: перекрывал крестики
     удаления нижних адресов, угол карты и липкую панель. Прячем, пока
     пользователь работает с формой; возвращаем, когда он доскроллил до подвала
     (класс ставит JS). Если разметка Jivo изменится и селектор перестанет
     совпадать — виджет просто останется видимым, как раньше. */
  jdiv { display: none !important; }
  :root.jivo-allowed jdiv { display: block !important; }

  .bulk-import-steps { padding: 12px 16px 0; }
}

/* на планшете виджет тоже не должен лезть на карту */
@media (min-width: 576px) and (max-width: 767px) {
  jdiv { opacity: .85; }
}

/* ⚠️ ПОСЛЕ .jivo-allowed: на время тура пузырь прячется в любом случае */
:root.onb-running jdiv { display: none !important; }

/* ============================ УЗКИЙ ЭКРАН (<=380px) ============================
   Шапке нужно место под логотип, два пункта меню и переключатель темы. На всём,
   что уже (iPhone SE/8 = 375, старые Android = 320), страница получала
   горизонтальную прокрутку — а вместе с ней съезжали вправо модалки: они
   центрируются по ширине ДОКУМЕНТА, а она была больше окна. Аудит 2026-08-04
   этого не поймал: он шёл на 390 и 412, где шапка впритык помещается. */
@media (max-width: 380px) {
  .site-header { gap: 6px; }
  #logo { width: 110px; height: auto; }
  .nav-responsive > ul { gap: 2px; }
  .nav-responsive > ul > li > a { padding: 6px 6px; font-size: var(--ant-font-size-sm); }
}

/* ============================ О ПРОЕКТЕ ============================
   Текстовый блок под картой. Раньше жил внутри модалки #popover-about и был
   скрыт от поисковых роботов — а других осмысленных слов на странице нет,
   только подписи интерфейса (см. комментарий в index.php).

   Ширину держим по мере читаемости, а не по ширине экрана: сплошной текст
   на 1400px строкой не читается. Цвета — только через токены antd, поэтому
   блок перекрашивается вместе с тёмной темой и ничего не знает про неё. */
.about-section {
  margin-top: 8px;
  padding: 28px 0 40px;
  border-top: 1px solid var(--ant-color-border-secondary);
  /* якорь из меню: пункт «О проекте» — обычная ссылка на #about */
  scroll-margin-top: 16px;
}

.about-content {
  max-width: 68ch;
  margin: 0 auto;
  color: var(--ant-color-text);
}

.about-content h2 {
  margin: 0 0 14px;
  font-size: var(--ant-font-size-heading-3);
  line-height: 1.3;
  font-weight: 600;
  color: var(--ant-color-text-heading);
}

.about-content h3 {
  margin: 26px 0 10px;
  font-size: var(--ant-font-size-heading-5);
  line-height: 1.4;
  font-weight: 600;
  color: var(--ant-color-text-heading);
}

.about-content p {
  margin: 0 0 12px;
  line-height: 1.7;
  color: var(--ant-color-text-secondary);
}

.about-content p:last-child { margin-bottom: 0; }
.about-content b { font-weight: 600; color: var(--ant-color-text); }

/* Ссылки внутри текстовых секций: подчёркивание тонкой линией, а не сплошным
   border-bottom — иначе строки в плотном абзаце визуально слипаются. */
.about-content a {
  color: var(--ant-color-link);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, var(--ant-color-link) 35%, transparent);
  text-underline-offset: 2px;
}
.about-content a:hover { color: var(--ant-color-link-hover); text-decoration-color: currentColor; }

/* ЧАСТЫЕ ВОПРОСЫ. Верхней границы нет: секция идёт сразу за «О проекте», и
   вторая линия подряд читалась бы как разрыв страницы, а не как её продолжение. */
.faq-section { border-top: none; padding-top: 4px; }

/* Вопрос и ответ — пара, поэтому расстояние между ними меньше, чем до
   следующего вопроса. Иначе список читается как ровная лента абзацев. */
.faq-section .about-content h3 { margin: 22px 0 6px; }
.faq-section .about-content h3:first-of-type { margin-top: 14px; }

@media (max-width: 575px) {
  .about-section { padding: 20px 0 28px; }
  .faq-section { padding-top: 0; }
}

/* ============================ БЫСТРЫЙ СТАРТ И ВЫВОД МАРШРУТА ============================
   Элементы, добавленные по недельной статистике 2026-W34: кнопка геолокации
   у поля Старт, нажимаемый чип «на карте» и передача готового маршрута
   в навигатор. */

/* «Моё местоположение» - ссылка-кнопка под полем старта. Не внутри поля:
   там уже живёт свой крестик очистки (antd allowClear), и два значка подряд
   в одном поле на смартфоне неразличимы пальцем. */
.start-locate-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  margin-top: 4px;
  padding: 4px 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: var(--ant-font-size-sm);
  line-height: 1.4;
  color: var(--ant-color-primary);
  cursor: pointer;
}
.start-locate-btn:hover { color: var(--ant-color-primary-hover, var(--ant-color-primary)); text-decoration: underline; }
.start-locate-btn:disabled { cursor: default; text-decoration: none; }
.start-locate-btn.is-busy { opacity: .6; pointer-events: none; }

/* Чип «на карте» стал <button>: кнопке нужно вернуть типографику окружения,
   иначе браузер подставит свой системный шрифт и размер. */
button.address-list-empty-tag {
  font: inherit;
  line-height: inherit;
  cursor: pointer;
  appearance: none;
}
button.address-list-empty-tag:hover { color: var(--ant-color-primary); border-color: var(--ant-color-primary); }

/* Передача маршрута в навигатор. Появляется только вместе с готовым маршрутом
   (route-export[hidden] снимает RouteReady_Update). */
.route-export { margin-top: 8px; }
.route-export-btn { width: 100%; }
.route-export-note {
  margin-top: 6px;
  font-size: var(--ant-font-size-sm);
  color: var(--ant-color-text-secondary);
}

/* на десктопе кнопка по содержимому: там она стоит в колонке шириной 41%
   и растянутая на всю ширину выглядит как основное действие, а основное
   действие здесь - «Оптимизировать» */
@media (min-width: 576px) {
  .route-export-btn { width: auto; }
}
