/**
 * Public surface — Kartivi Shell 2.0 (/m/{cc}/{slug}).
 * Layout: nav-rail | sidebar | map-stage (≥55% width).
 * Brand Kartivi — not a pixel copy of a commercial map.
 */

/* RU: Палитра (--k-bg, --k-text, --k-brand и остальные) объявлена один раз в
   public/css/kartivi.css — там же тёмная и контрастная темы, системная настройка
   prefers-color-scheme и шкалы отступов, радиусов и z-index. Три копии, что были
   здесь, расходились между собой: тёмная тема кабинета отличалась от тёмной темы карты.
   EN: The palette (--k-bg, --k-text, --k-brand and the rest) is declared once in
   public/css/kartivi.css, along with the dark and contrast themes, the
   prefers-color-scheme system setting and the spacing, radius and z-index scales.
   The three copies that lived here had drifted apart: the cabinet's dark theme
   differed from the map's. */

/* RU: Фоны и цвета состояний в этом файле — только через токены kartivi.css (--k-surface*, --k-brand-soft,
   --k-ok/warn/danger/info и их -soft). Зашитый светлый фон при тексте из токена ломает ночную тему:
   текст светлеет, фон нет. 13.09.2026 так нашлись 34 правила — рубрики, чипы, бейджи «закрыто»,
   панель мощности, паспорт дома — с контрастом до 1,1:1. Новое правило пишем сразу на токенах.
   EN: Backgrounds and status colours in this file go through kartivi.css tokens only (--k-surface*,
   --k-brand-soft, --k-ok/warn/danger/info and their -soft). A hardcoded light background under token text
   breaks the night theme: the text lightens, the background does not. On 2026-09-13 that turned up 34
   rules — rubrics, chips, "closed" badges, the capacity panel, the building passport — at down to 1.1:1.
   New rules are written on tokens from the start. */

/* ── Ф2.9: иконки интерфейса из спрайта /icons.svg ── */

/* RU: Общий класс для всех иконок набора. Раньше значки были эмодзи в тексте кнопок, и
   размер задавался их font-size; отсюда и здесь размер выражен в `em` — иконка наследует
   масштаб от места, куда вставлена, и старые пропорции (рельс 1.15rem, кнопки карты 1rem)
   сохраняются без правки каждого правила.

   `stroke: currentColor` — то, ради чего всё делалось: цвет берётся от текста кнопки,
   поэтому тёмная и контрастная темы работают сами, без отдельных правил под каждый режим.
   `fill: none` задаёт обводочный стиль всему набору; фигуры, которым нужна заливка
   (залитая звезда, половина круга у значка темы), объявляют fill на себе прямо в спрайте —
   атрибут на элементе сильнее унаследованного значения.

   `stroke-width` в единицах viewBox (24): 1.7 при отрисовке в 20 px даёт примерно 1.4 px —
   та же весомость, что у подписей рядом. Толщина одна на весь набор: разнобой заметнее
   всего именно в столбце кнопок карты.

   `vertical-align` нужен там, где иконка стоит в строке текста (кнопка «Избранное»):
   инлайновый svg иначе садится на базовую линию и подпись едет вверх.
   EN: One shared class for every icon in the set. The glyphs used to be emoji inside button
   text, sized by their font-size; hence the size here is expressed in `em` — an icon inherits
   the scale of wherever it is placed, and the old proportions (1.15rem in the rail, 1rem in the
   map buttons) survive without editing every rule.

   `stroke: currentColor` is the whole point: the colour comes from the button's text, so the
   dark and contrast themes work on their own with no per-mode rules. `fill: none` gives the
   whole set an outline style; the shapes that need a fill (the filled star, the half circle of
   the theme glyph) declare fill on themselves inside the sprite — an attribute on the element
   beats an inherited value.

   `stroke-width` is in viewBox units (24): 1.7 rendered at 20 px is about 1.4 px — the same
   weight as the captions beside it. One weight for the whole set: mismatched weights show up
   most in the column of map buttons.

   `vertical-align` is needed where an icon sits in a line of text (the "Избранное" button):
   an inline svg otherwise rests on the baseline and drags the caption upwards. */
.ic {
  width: 1em;
  height: 1em;
  flex: 0 0 auto;
  display: inline-block;
  vertical-align: -0.14em;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* RU: Кнопки карты и рельса центрируют иконку сами. У кнопки с текстом это делал
   text-align, у пустой кнопки с одним svg внутри — уже нет: инлайновый элемент сел бы на
   базовую линию и значок оказался бы ниже центра. Размер иконки здесь 1.25rem, а не 1rem
   от font-size кнопки: 16 px внутри кнопки 44 px выглядели потерянными.
   EN: The map and rail buttons centre their icon themselves. For a button with text that was
   text-align's job; for an empty button holding a single svg it is not: an inline element would
   rest on the baseline and the glyph would sit below centre. The icon size here is 1.25rem
   rather than the button's 1rem font-size: 16 px inside a 44 px button looked lost. */
.map-tool {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* RU: Атрибут hidden обязан побеждать правило выше. `display: none` для [hidden] живёт в
   стилях самого браузера, и любое авторское правило с `display` его перебивает: без этой
   строки кнопка пробок (#tool-traffic стоит в разметке с hidden и показывается только при
   meta.traffic_enabled) появилась бы в каждом городе. Тот же приём применён в этом файле
   к .offline-indicator — ошибка типовая.
   EN: The hidden attribute must beat the rule above. `display: none` for [hidden] lives in the
   browser's own stylesheet, and any author rule with `display` overrides it: without this line
   the traffic button (#tool-traffic ships with hidden in the markup and appears only when
   meta.traffic_enabled) would show up in every city. The same trick is used in this file for
   .offline-indicator — the mistake is a common one. */
.map-tool[hidden] {
  display: none;
}

.map-tool .ic {
  width: 1.25rem;
  height: 1.25rem;
}

.rail-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* RU: Значок в подсказке лежит на цветной плашке 1.8rem — иконка в 0.85rem (старый
   font-size) на ней теряется, поэтому у неё свой размер.
   EN: A suggestion's glyph sits on a 1.8rem tinted plate — an icon at 0.85rem (the old
   font-size) is lost on it, so it gets a size of its own. */
.mobile-suggest-icon .ic {
  width: 1.05rem;
  height: 1.05rem;
}

/* RU: Кнопки-иконки в строке выдачи: «Маршрут сюда» и «Позвонить». Подписи у них нет
   (имя даёт aria-label), поэтому центрируем содержимое и задаём размер явно.
   EN: The icon buttons in a results row: "route here" and "call". They have no caption (the name
   comes from aria-label), so the content is centred and the size set explicitly. */
.result-route .ic,
.result-call-btn .ic {
  width: 1.15rem;
  height: 1.15rem;
}

/* RU: Обёртке `.result-route-icon` своего правила не заводим намеренно: ниже в этом же
   файле у неё уже есть `display: block; line-height: 1`, и парное правило здесь молча
   проигрывало бы по порядку — читающий решил бы, что центрирование задано тут.
   Горизонтально значок центрирует сама кнопка (у button по умолчанию text-align: center).
   EN: No rule of our own for the `.result-route-icon` wrapper, deliberately: further down this
   same file it already has `display: block; line-height: 1`, and a twin rule here would silently
   lose on order — a reader would think the centring is set here. Horizontally the glyph is
   centred by the button itself (a button defaults to text-align: center). */

/* RU: Ночь рисуется собственным стилем подложки (basemap/styles/dark.json), а не
   фильтром поверх светлой карты, поэтому обратная инверсия картинок больше не
   нужна: она искажала фотографии и логотипы атрибуции. Явное `filter: none` на
   холсте оставлено — оно защищает от фильтра, случайно унаследованного от контейнера.
   EN: Night is drawn with its own basemap style (basemap/styles/dark.json) rather
   than a filter over the light map, so counter-inverting images is no longer needed:
   it distorted photos and attribution logos. The explicit `filter: none` on the
   canvas stays — it guards against a filter accidentally inherited from a container. */
.is-dark .map-stage canvas {
  filter: none !important;
}

/* Contrast is a reversible treatment of the vector canvas; it keeps camera,
 * sources, selected features and building hit layers intact. */
.is-contrast .map-stage canvas {
  filter: contrast(1.18) saturate(0.92);
}

/* Dark map loading indicator */
.is-dark #map.map-loading::after {
  background-color: #1a1a1a;
}

/* ── U3: Offline indicator banner ── */

/* RU: Атрибут hidden должен побеждать display: flex ниже — иначе баннер, однажды
   показанный, не спрячется уже никогда.
   EN: The hidden attribute must beat the display: flex below — otherwise the banner, once
   shown, never disappears again. */
.offline-indicator[hidden] {
  display: none;
}

.offline-indicator {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  background: #ff7043;
  color: var(--k-text-on-accent);
  font-size: 0.85rem;
  font-weight: 600;
  text-align: center;
  box-shadow: 0 2px 8px rgba(0,0,0,0.25);
  animation: offline-slide-in 0.3s ease-out;
}
@keyframes offline-slide-in {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0); }
}
.offline-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--k-surface);
  animation: offline-pulse 1.5s ease-in-out infinite;
}
@keyframes offline-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.3; }
}

/* Dark navbar */
.is-dark .nav-rail {
  background: var(--k-surface);
}
.is-dark .sidebar {
  background: var(--k-bg);
  color: var(--k-text);
}
.is-dark .firm-card-v2 {
  background: var(--k-card-bg);
  box-shadow: 0 2px 12px var(--k-card-shadow);
}
.is-dark .firm-card-name {
  color: var(--k-text);
}
.is-dark .firm-hours {
  background: var(--k-surface);
}
.is-dark .firm-action-bar {
  border-top-color: var(--k-border);
}
.is-dark .firm-action-btn {
  background: var(--k-surface);
  color: var(--k-text);
  border-color: var(--k-border);
}
.is-dark .firm-action-btn:hover {
  background: var(--k-surface-2);
}
.is-dark .hours-table tr.is-today th,
.is-dark .hours-table tr.is-today td {
  background: var(--k-brand-soft);
  color: var(--k-brand-ink);
}
.is-dark .hours-raw {
  color: var(--k-text-muted);
}
.is-dark .desc-text,
.is-dark .address-text {
  color: var(--k-text-muted);
}
.is-dark .reviews-section {
  border-top-color: var(--k-border);
}
.is-dark .review-form {
  background: var(--k-surface);
}
.is-dark .firm-public-section {
  background: var(--k-info-soft);
}
.is-dark .public-sector-badge {
  color: var(--k-info);
}
.is-dark .public-hint {
  color: var(--k-text-muted);
}

/* ——— Shell grid ——— */
body.public.shell-v2 {
  overflow: hidden;
}

body.public.shell-v2 #app.shell {
  height: 100%;
  display: grid;
  grid-template-columns: 56px minmax(300px, 380px) minmax(0, 1fr);
  grid-template-rows: 1fr auto;
  grid-template-areas:
    "rail sidebar map"
    "attrib attrib attrib";
}

body.public.shell-v2 .nav-rail {
  grid-area: rail;
}

body.public.shell-v2 .sidebar {
  grid-area: sidebar;
}

body.public.shell-v2 .map-stage {
  grid-area: map;
  min-width: 0;
  min-height: 240px;
}

/* Map loading indicator — shown while MapLibre initialises tiles */
#map.map-loading::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #1a2a20 url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 50"><circle cx="25" cy="25" r="20" fill="none" stroke="%234caf50" stroke-width="3" stroke-dasharray="31.4 31.4" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" from="0 25 25" to="360 25 25" dur="1.5s" repeatCount="indefinite"/></circle></svg>') center / 50px 50px no-repeat;
  z-index: 2;
  pointer-events: none;
  transition: opacity 0.4s;
}
#map.map-loading {
  /* Ensure the container is visible while loading — prevents zero-height flash */
  min-height: 240px;
  background: #1a2a20;
}

body.public.shell-v2 .attrib {
  grid-area: attrib;
}

/* ——— Nav rail ——— */
.nav-rail {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  padding: 0.65rem 0.35rem;
  background: linear-gradient(180deg, #1a3d2e 0%, #143328 100%);
  color: #e8f5ee;
  z-index: 5;
  border-right: 1px solid rgba(255, 255, 255, 0.08);
}

.rail-brand {
  display: grid;
  place-items: center;
  width: 2.4rem;
  height: 2.4rem;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.1);
  text-decoration: none;
  margin-bottom: 0.35rem;
}

/* RU: С 19.09.2026 знак — inline SVG «сложенной карты» OneMaps вместо буквы «K»; размер задаёт width/height в разметке,
       display: block убирает зазор строки под SVG, из-за которого знак смещался вверх в квадрате 2.4rem.
   EN: Since 2026-09-19 the mark is the inline OneMaps "folded map" SVG instead of the letter "K"; width/height in the
       markup set its size, display: block removes the inline-baseline gap that nudged it up inside the 2.4rem square. */
.rail-brand-mark {
  display: block;
}

.rail-btn {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.1rem;
  width: 100%;
  border: 0;
  background: transparent;
  color: inherit;
  font: inherit;
  font-size: 0.62rem;
  font-weight: 600;
  padding: 0.45rem 0.2rem;
  border-radius: 10px;
  cursor: pointer;
  text-decoration: none;
}

/* RU: `display: flex` выше сильнее встроенного `[hidden] { display: none }` браузера, поэтому без этого правила
       скрытая кнопка рельса оставалась на экране: атрибут стоит, а пункт виден. Так «Доска» показывалась при
       закрытой доске (Ф6.23), а пуш-кнопка — там, где push-subscribe.js её прячет без поддержки уведомлений.
   EN: `display: flex` above outranks the browser's built-in `[hidden] { display: none }`, so without this rule a
       hidden rail button stayed on screen: the attribute is set yet the item shows. That is how "Доска" appeared with
       the board closed (Ф6.23), and the push button where push-subscribe.js hides it without notification support. */
.rail-btn[hidden] {
  display: none;
}

/* RU: Рельс тёмный во всех темах (днём — зелёный градиент, ночью — --k-surface), поэтому подпись
       активной кнопки — белая, а не --k-text-on-accent: ночью этот токен тёмный (#04150d, он для
       светлой акцентной заливки), и «Поиск» на тёмном рельсе давал 1,1:1.
   EN: The rail is dark in every theme (green gradient by day, --k-surface at night), so the active
       label is white rather than --k-text-on-accent: at night that token is dark (#04150d, meant for
       the light accent fill), and "Search" on the dark rail gave 1.1:1. */
.rail-btn:hover,
.rail-btn.is-active {
  background: rgba(255, 255, 255, 0.12);
  color: #ffffff;
}

.rail-btn.is-active {
  box-shadow: inset 3px 0 0 #6ee7b7;
}

.rail-icon {
  font-size: 1.15rem;
  line-height: 1;
}

.rail-spacer {
  flex: 1;
}

.rail-link {
  opacity: 0.85;
  font-size: 0.68rem;
}

.rail-link.is-signed-in {
  opacity: 1;
  color: var(--accent, var(--k-brand));
  font-weight: 700;
}

/* U2.5 — Push subscription button states */
.rail-btn-push.is-subscribed .rail-icon {
  filter: grayscale(0);
  opacity: 1;
}
.rail-btn-push:not(.is-subscribed) .rail-icon {
  opacity: 0.6;
}

/* ——— Sidebar ——— */
.sidebar {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: linear-gradient(180deg, #e7f0ea 0%, #f3f6f2 48%, #f7faf7 100%);
  border-right: 1px solid var(--line);
  z-index: 4;
  overflow: hidden;
}

.sidebar-head {
  padding: 0.85rem 1rem 0.55rem;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
}

.sidebar-search {
  padding: 0.65rem 1rem 0.35rem;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  max-width: none;
  flex: 0 0 auto;
}

/* RU: Ф6.9. Ряды кнопок обязаны переноситься, а не выдавливать страницу. Причина в минимуме
       цели нажатия: кнопка с `min-width: 44px` НЕ сжимается ниже 44 px, сколько бы ей ни
       ставили `flex: 1`, и три-четыре кнопки в ряду на экране 320 px дают горизонтальную
       прокрутку body — а по горизонтали не должны прокручиваться ни body, ни html ни при
       какой ширине от 320 px. Перенос здесь дешевле прокрутки: это не навигация, ряд
       короткий, и вторая строка появляется только на узком экране.
   EN: Ф6.9. Rows of buttons must wrap rather than push the page out. The reason is the tap-target
       minimum: a button with `min-width: 44px` does NOT shrink below 44 px no matter how much
       `flex: 1` it is given, and three or four buttons in a row on a 320 px screen produce a
       horizontal body scroll — and neither body nor html may ever scroll horizontally at any
       width from 320 px up. Wrapping is cheaper than scrolling here: this is not navigation, the
       row is short, and the second line appears only on a narrow screen. */
.sidebar-search .search-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.sidebar-search .search-actions button {
  flex: 1;
}

/* Keep the submit/nearby actions reachable when suggestions are open. */
body.public.shell-v2 .sidebar-search.has-suggestions .suggest {
  position: static;
  width: 100%;
  box-sizing: border-box;
  margin-top: 0.25rem;
}

.sidebar-quick {
  display: flex;
  /* RU: Перенос — см. .search-actions. EN: Wrapping — see .search-actions. */
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0 1rem 0.55rem;
}

.sidebar-quick .ghost-btn {
  flex: 1;
  padding: 0.45rem 0.55rem !important;
  font-size: 0.82rem;
}

/* I5.1 — Breadcrumb navigation */
.breadcrumb-nav {
  margin: 0 1rem 0.55rem;
  padding: 0;
  font-size: 0.78rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.breadcrumb-nav:empty { display: none; }
.breadcrumb-list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.15rem;
  list-style: none;
  padding: 0;
  margin: 0;
}
.breadcrumb-item {
  color: var(--ink-soft);
}
.breadcrumb-item a {
  color: var(--ink-soft);
  text-decoration: none;
}
.breadcrumb-item a:hover {
  color: var(--accent);
  text-decoration: underline;
}
/* RU: --k-text-subtle, а не --line: цвет рамки для текста слишком бледный (1,46:1 ночью), а
       --k-border-strong днём даёт на подложке панели 2,88:1 — чуть ниже порога 3:1.
   EN: --k-text-subtle rather than --line: a border colour is too faint for text (1.46:1 at night), and
       --k-border-strong gives 2.88:1 on the panel ground by day — just under the 3:1 floor. */
.breadcrumb-sep {
  color: var(--k-text-subtle);
  margin: 0 0.15rem;
  user-select: none;
}
.breadcrumb-item.is-current {
  color: var(--ink);
  font-weight: 600;
}

.filter-drawer {
  margin: 0 1rem 0.55rem;
  padding: 0.65rem 0.75rem;
  border-radius: 12px;
  background: var(--k-surface);
  border: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.filter-drawer[hidden] {
  display: none !important;
}

.filter-drawer-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.filter-sort {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
}

.filter-sort select {
  font: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 0.4rem 0.5rem;
  background: var(--k-surface);
}

.sidebar-view {
  flex: 1;
  min-height: 0;
  overflow: auto;
  padding: 0.35rem 1rem 1.25rem;
}

.sidebar-view[hidden] {
  display: none !important;
}

.home-hero {
  margin: 0.35rem 0 0.85rem;
}

.home-title {
  margin: 0;
  font-family: var(--font-brand);
  font-size: 1.55rem;
  line-height: 1.15;
  letter-spacing: -0.02em;
}

.recent-searches {
  margin: 0 0 0.85rem;
}

.recent-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.4rem;
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.recent-clear {
  border: 0;
  background: transparent;
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.72rem;
  text-transform: none;
  letter-spacing: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.recent-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.recent-chip {
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--k-surface);
  padding: 0.35rem 0.7rem;
  font: inherit;
  font-size: 0.82rem;
  color: var(--ink);
  cursor: pointer;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.recent-chip:hover {
  border-color: var(--accent);
  background: var(--k-surface-3);
}

/* U2.4 — Recently viewed firms on home page */
.recent-firms {
  margin: 0 0 0.85rem;
}
.recent-firms:empty { display: none; }
.recent-firms-title {
  font-size: 0.82rem;
  font-weight: 650;
  color: var(--ink-soft);
  margin: 0 0 0.45rem;
}
.recent-firms-list {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.recent-firm-item {
  border: 1px solid var(--line);
  border-radius: 0.55rem;
  background: linear-gradient(180deg, var(--k-surface) 0%, var(--k-surface-3) 100%);
  padding: 0.38rem 0.7rem;
  font: inherit;
  font-size: 0.82rem;
  cursor: pointer;
  color: var(--ink);
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}
.recent-firm-item:hover {
  border-color: var(--accent);
  background: var(--k-surface-3);
}
.recent-firm-rubric {
  font-size: 0.72rem;
  color: var(--ink-soft);
}

.home-meta {
  margin: 0.35rem 0 0;
  color: var(--ink-soft);
  font-size: 0.88rem;
}

/* P81 U4 — inline CTA when firm has no phone */
.report-missing-phone-btn {
  margin-top: 0.35rem;
  font-size: 0.84rem;
}

.firm-action-report-phone {
  border-color: color-mix(in srgb, var(--accent) 40%, var(--line)) !important;
  color: var(--accent);
  font-weight: 650;
}

/* P81 U1 + F7 — home shortcuts (consumer + social infra) */
.public-quick-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0 0 0.85rem;
}

.public-quick-chips:empty {
  display: none;
}

/* RU: Фоны чипов — только из токенов темы. Раньше здесь стояли светлые #fbfdfc/#fffdf8, а цвет
       текста уже шёл из токена: ночью светлый текст ложился на светлый фон (контраст 1,15:1),
       и рубрики «Школы / Сады / Аптеки» не читались. Тёплый оттенок «бытовых» чипов — примесь
       #e9c77b к поверхности: днём даёт прежние #fffdf8→#f7f3ea, ночью — едва тёплый тёмный.
   EN: Chip backgrounds come from theme tokens only. They used to be hardcoded light #fbfdfc/#fffdf8
       while the text colour already came from a token: at night light text sat on a light
       background (1.15:1 contrast) and the "Schools / Kindergartens / Pharmacies" rubrics were
       unreadable. The warm tint of "consumer" chips is #e9c77b mixed into the surface: by day it
       reproduces the old #fffdf8→#f7f3ea, at night it is a barely warm dark. */
.public-quick-chip {
  border: 1px solid var(--line);
  border-radius: 0.55rem;
  background: linear-gradient(180deg, var(--k-surface) 0%, var(--k-surface-3) 100%);
  padding: 0.42rem 0.75rem;
  font: inherit;
  font-size: 0.84rem;
  font-weight: 560;
  color: var(--ink);
  cursor: pointer;
  line-height: 1.2;
}

.public-quick-chip.is-consumer {
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--k-surface) 96%, #e9c77b) 0%,
    color-mix(in srgb, var(--k-surface-3) 88%, #e9c77b) 100%
  );
}

.public-quick-chip:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--k-brand-soft) 45%, var(--k-surface));
}

.public-quick-chip.is-consumer:hover {
  background: color-mix(in srgb, var(--k-surface-3) 84%, #e9c77b);
  border-color: color-mix(in srgb, var(--ink-soft) 45%, var(--line));
}

.public-quick-chip.is-active {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--k-brand-soft) 70%, var(--k-surface));
  color: var(--accent-ink, var(--ink));
}

.public-quick-chip.is-consumer.is-active {
  border-color: color-mix(in srgb, var(--ink) 28%, var(--line));
  background: color-mix(in srgb, var(--k-surface-3) 72%, #e9c77b);
}

.rubric-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.5rem;
}

.rubric-tile {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.2rem;
  text-align: left;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--k-surface);
  padding: 0.7rem 0.65rem;
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: border-color 160ms ease, transform 160ms ease, background 160ms ease;
}

.rubric-tile:hover {
  border-color: var(--accent);
  background: var(--k-surface-3);
  transform: translateY(-1px);
}

.rubric-tile.is-active {
  border-color: var(--accent);
  background: var(--k-brand-soft);
}

.rubric-tile-name {
  font-weight: 700;
  font-size: 0.82rem;
  line-height: 1.25;
}

.rubric-tile-count {
  font-size: 0.72rem;
  color: var(--ink-soft);
  font-variant-numeric: tabular-nums;
}

.results-head,
.detail-head,
.routes-head {
  margin-bottom: 0.65rem;
}

.results-head h2,
.detail-head h2,
.routes-head h2 {
  margin: 0.25rem 0 0;
  font-family: var(--font-brand);
  font-size: 1.2rem;
}

.back-link {
  border: 0;
  background: transparent;
  color: var(--accent);
  font: inherit;
  font-weight: 600;
  font-size: 0.85rem;
  padding: 0;
  cursor: pointer;
}

.detail-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem 0.9rem;
  margin-bottom: 0.1rem;
}

.detail-to-building {
  color: var(--ink-soft);
}

.detail-to-building:not([hidden]):hover,
.detail-to-building:not([hidden]):focus-visible {
  color: var(--accent);
}

.results-body,
.detail-body {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.result-row {
  display: flex;
  align-items: stretch;
  gap: 0.4rem;
}

.result-row .result-card {
  flex: 1;
  min-width: 0;
}

/* I4.4 — photo thumbnail in search result cards */
.result-thumb {
  width: 48px;
  height: 48px;
  border-radius: 0.35rem;
  object-fit: cover;
  flex-shrink: 0;
  float: left;
  margin: 0 0.55rem 0.35rem 0;
  border: 1px solid var(--line);
  background: var(--k-surface-2);
}

.result-card:has(.result-thumb) {
  display: flow-root;
}

.result-route {
  flex: 0 0 2.5rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.15rem;
  padding: 0.35rem 0.2rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--k-surface);
  color: var(--accent);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  transition: border-color 160ms ease, background 160ms ease, color 160ms ease, flex-basis 160ms ease;
}

.result-route:has(.route-eta:not([hidden])) {
  flex-basis: 3.35rem;
}

.result-route-icon {
  display: block;
  line-height: 1;
}

.result-route:hover {
  border-color: var(--accent);
  background: var(--k-surface-3);
}

/* Quick action buttons alongside search result rows */
.result-actions {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex: 0 0 auto;
}

.result-call-btn {
  flex: 0 0 2.5rem;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0.35rem 0.2rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--k-surface);
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  transition: border-color 160ms ease, background 160ms ease;
}

.result-call-btn:hover {
  border-color: var(--accent);
  background: color-mix(in srgb, var(--k-brand-soft) 60%, var(--k-surface));
}

/* Compact ETA next to «Маршрут» (search rows + firm/building actions) */
.route-eta {
  display: inline;
  font-size: 0.68rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
  color: color-mix(in srgb, var(--accent) 72%, var(--ink-soft));
  white-space: nowrap;
}

.result-route .route-eta {
  display: block;
  max-width: 3.1rem;
  overflow: hidden;
  text-overflow: ellipsis;
  line-height: 1.1;
  text-align: center;
}

.firm-action-btn .route-eta,
.building-card-action .route-eta {
  font-size: 0.72rem;
  font-weight: 600;
}

.result-card {
  text-align: left;
  width: 100%;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0.75rem 0.85rem;
  background: var(--k-surface);
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition: border-color 160ms ease, background 160ms ease, box-shadow 160ms ease;
}

.result-card:hover,
.result-card.is-active {
  border-color: var(--accent);
  background: var(--k-surface-3);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
}

.result-card h3 {
  margin: 0 0 0.25rem;
  font-size: 0.98rem;
  line-height: 1.25;
}

.result-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.45rem;
}

.result-card-head h3 {
  margin: 0;
  flex: 1;
  min-width: 0;
}

.result-card .firm-row-lite-rubrics {
  margin-top: 0.35rem;
}

.result-card .result-rubric {
  font-size: 0.78rem;
  color: var(--accent);
  font-weight: 600;
}

.result-card .result-addr {
  margin: 0.25rem 0 0;
  font-size: 0.82rem;
  color: var(--ink-soft);
}
/* RU: Ф6.13: расстояние в строке выдачи — перед адресом, цифрами одной ширины, чтобы колонка расстояний читалась сверху вниз.
   EN: Ф6.13: the distance in a result row — before the address, in tabular figures so the column of distances reads top to bottom. */
.result-card .result-dist {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--k-text);
}

.result-card .result-phone {
  margin: 0.2rem 0 0;
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.result-card-feature {
  border-left: 3px solid var(--accent);
}

.entrance-marker {
  background: var(--k-brand);
  color: var(--k-text-on-accent);
  font: 700 0.7rem/1 DM Sans, system-ui, sans-serif;
  padding: 0.28rem 0.45rem;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  white-space: nowrap;
}

.entrance-marker-hover {
  background: #2d6a4f;
  animation: entrance-marker-pulse 1.1s ease-in-out infinite;
}

@keyframes entrance-marker-pulse {
  0%,
  100% {
    transform: scale(1);
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
  }

  50% {
    transform: scale(1.06);
    box-shadow: 0 0 0 4px rgba(45, 106, 79, 0.22);
  }
}

.firm-row-lite.is-hover,
.firm-row-lite:focus-visible {
  border-color: var(--accent);
  background: var(--k-surface-3);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 25%, transparent);
}

.child-rubric-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.55rem;
}

.child-rubric-tab {
  border: 1px solid var(--line);
  background: var(--k-surface);
  font: inherit;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
  padding: 0.3rem 0.55rem;
  border-radius: 999px;
  cursor: pointer;
}

.child-rubric-tab.is-active {
  background: var(--k-brand-soft);
  border-color: color-mix(in srgb, var(--k-brand) 30%, transparent);
  color: var(--accent);
}

.firm-tabs {
  display: flex;
  /* RU: Перенос по той же причине, что у .search-actions выше: четыре вкладки по 44 px плюс
         отступы панели упираются в 320 px. EN: Wrapping for the same reason as .search-actions
         above: four 44 px tabs plus the panel's padding hit the 320 px limit. */
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.55rem;
  padding: 0.2rem;
  background: var(--k-surface);
  border: 1px solid var(--line);
  border-radius: 10px;
}

.firm-tab {
  flex: 1;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: 0.78rem;
  font-weight: 700;
  color: var(--ink-soft);
  padding: 0.4rem 0.35rem;
  border-radius: 8px;
  cursor: pointer;
}

.firm-tab.is-active {
  background: var(--k-brand-soft);
  color: var(--accent);
}

.building-fias {
  margin: 0.35rem 0 0.65rem;
  padding: 0.5rem 0.65rem;
  border-radius: 10px;
  background: var(--k-surface);
  border: 1px dashed color-mix(in srgb, var(--accent) 35%, var(--line));
  font-size: 0.8rem;
  color: var(--ink-soft);
}

.building-fias strong {
  color: var(--ink);
}

/* ——— Building card (P80 Phase B) ——— */
.building-card {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.building-card-desc {
  margin: 0;
  font-size: 0.88rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

.building-card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
}

.building-card-action {
  flex: 1 1 calc(50% - 0.25rem);
  min-width: 7.5rem;
  text-align: center;
  text-decoration: none;
  font-size: 0.82rem;
  padding: 0.55rem 0.65rem !important;
}

.building-card-media {
  margin: 0;
}

/* P81 U5 — photo strip in building-card header */
.building-card-media.is-header {
  display: flex;
  gap: 0.35rem;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  -webkit-overflow-scrolling: touch;
  margin: 0 0 0.15rem;
  border-radius: 12px;
}

.building-card-media.is-header .building-card-media-item {
  flex: 0 0 auto;
  scroll-snap-align: start;
  border-radius: 10px;
  overflow: hidden;
  line-height: 0;
  border: 1px solid var(--line);
}

.building-card-media.is-header .building-card-media-item img {
  display: block;
  width: 5.5rem;
  height: 4.25rem;
  object-fit: cover;
}

.building-card-media.is-header .building-card-media-item.is-hero img {
  width: min(100%, 14rem);
  height: 7.5rem;
}

.building-card-section-title {
  margin: 0 0 0.45rem;
  font-family: var(--font-brand);
  font-size: 0.92rem;
  font-weight: 700;
  color: var(--ink);
}

/* P81 U3 — social-infra block slightly tinted vs commercial list */
.building-card-firms + .building-card-firms {
  margin-top: 0.85rem;
  padding-top: 0.75rem;
  border-top: 1px solid var(--line);
}

.building-card-firms.is-public-infra .building-card-section-title {
  color: color-mix(in srgb, var(--accent) 55%, var(--ink));
}

.building-card-firms.is-public-infra .firm-row-lite {
  background: color-mix(in srgb, var(--accent) 6%, var(--k-surface));
  border-color: color-mix(in srgb, var(--accent) 22%, var(--line));
}

.building-card-empty {
  padding: 0.35rem 0;
}

.building-card-empty-cta,
.building-card-footer-cta {
  margin-top: 0.35rem;
}

.building-card-tech {
  margin-top: 0.25rem;
  padding: 0.55rem 0.65rem;
  border-radius: 10px;
  background: var(--k-surface);
  border: 1px dashed color-mix(in srgb, var(--accent) 28%, var(--line));
  font-size: 0.78rem;
  color: var(--ink-soft);
}

/* Quick tile-info popup shown while API resolves */
.tile-quick-info {
  padding: 0.5rem 0.65rem;
  margin-bottom: 0.5rem;
  border-radius: 8px;
  background: color-mix(in srgb, var(--accent) 8%, transparent);
  border: 1px solid color-mix(in srgb, var(--accent) 15%, var(--line));
  font-size: 0.82rem;
  color: var(--ink);
  line-height: 1.4;
}

.building-card-tech > summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--ink-soft);
  list-style: none;
}

.building-card-tech > summary::-webkit-details-marker {
  display: none;
}

.building-card-tech > summary::before {
  content: "▸ ";
  color: var(--accent);
}

.building-card-tech[open] > summary::before {
  content: "▾ ";
}

.building-card-tech-list {
  margin: 0.55rem 0 0;
  padding-left: 1.1rem;
}

.building-card-tech-list li {
  margin: 0.2rem 0;
}

.firm-list-lite {
  gap: 0.45rem;
}

.firm-row-lite {
  display: block;
  width: 100%;
  text-align: left;
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 0.65rem 0.75rem;
  background: var(--k-surface);
  cursor: pointer;
  font: inherit;
  color: inherit;
  transition:
    border-color 0.15s ease,
    box-shadow 0.15s ease;
}

.firm-row-lite:hover,
.firm-row-lite:focus-visible {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  box-shadow: 0 2px 10px rgba(31, 107, 74, 0.08);
  outline: none;
}

.firm-row-lite-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
}

.firm-row-lite-name {
  margin: 0;
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--ink);
}

.firm-row-lite-rubrics {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.35rem;
}

.rubric-chip-lite {
  display: inline-block;
  padding: 0.15rem 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--k-brand-soft) 65%, var(--k-surface));
  color: var(--accent);
  font-size: 0.72rem;
  font-weight: 600;
}

.firm-row-lite-status {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.35rem;
}

.firm-row-lite-status .chip {
  font-size: 0.68rem;
  padding: 0.12rem 0.4rem;
  border-radius: 999px;
}

.firm-row-lite-phone {
  margin: 0.35rem 0 0;
  font-size: 0.84rem;
  font-weight: 600;
  color: var(--ink);
}

.firm-row-lite-phone.muted {
  font-weight: 500;
  color: var(--ink-soft);
}

.firm-row-lite-phone a {
  color: var(--accent);
  text-decoration: none;
}

.firm-row-lite-phone a:hover {
  text-decoration: underline;
}

.firm-row-report-hint {
  color: var(--accent);
  font-weight: 650;
}

/* F7 — soft missing NAP badges (hours / photo); quiet, not alarm */
.firm-nap-gaps {
  display: flex;
  flex-wrap: wrap;
  gap: 0.28rem;
  margin-top: 0.3rem;
}

.chip-gap {
  display: inline-block;
  font-size: 0.66rem;
  font-weight: 600;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  color: var(--ink-soft);
  background: color-mix(in srgb, var(--line) 55%, var(--k-surface));
  border: 1px solid color-mix(in srgb, var(--line) 80%, transparent);
}

.firm-detail-status .firm-nap-gaps {
  display: contents;
}

.building-card-empty-hint {
  margin: 0.25rem 0 0;
}

.shell-v2 .hint,
.shell-v2 .muted {
  color: var(--ink-soft);
  font-size: 0.84rem;
  font-weight: 500;
}

.shell-v2 p.hint {
  margin: 0.2rem 0 0;
}

.open-now-badge {
  flex-shrink: 0;
  display: inline-block;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.01em;
  background: var(--k-surface-2);
  color: var(--ink-soft);
}

.open-now-badge.is-open {
  background: var(--k-brand-soft);
  color: var(--k-brand);
}

.open-now-badge.is-closed {
  background: var(--k-danger-soft);
  color: var(--k-danger);
}

/* ——— Firm detail (P80 Phase E) ——— */
.firm-detail-meta {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.firm-detail-addr {
  margin: 0;
  font-size: 0.88rem;
  color: var(--ink-soft);
  line-height: 1.35;
}

.firm-detail-rubrics {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.firm-detail-status {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

.firm-detail-status .chip {
  font-size: 0.68rem;
  padding: 0.12rem 0.4rem;
  border-radius: 999px;
}

/* P81 U6 — official .gov.ru / .edu.ru site */
.chip-official-site {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 600;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  color: color-mix(in srgb, var(--accent) 70%, var(--ink));
  background: color-mix(in srgb, var(--accent) 12%, var(--k-surface));
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line));
  vertical-align: middle;
}

.firm-action-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  position: sticky;
  top: 0;
  z-index: 3;
  padding: 0.55rem 0 0.65rem;
  margin: 0 0 0.35rem;
  background: linear-gradient(180deg, var(--k-surface-3) 88%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--line) 75%, transparent);
}

.firm-action-btn {
  flex: 1 1 calc(33.333% - 0.35rem);
  min-width: 5.5rem;
  text-align: center;
  text-decoration: none;
  font-size: 0.82rem;
  padding: 0.55rem 0.5rem !important;
}

.firm-action-btn:disabled {
  opacity: 0.42;
  cursor: not-allowed;
}

.firm-action-bar .fav-btn {
  width: auto;
  margin: 0;
}

.firm-detail-tab-panel {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.firm-siblings {
  margin-top: 0.75rem;
  padding-top: 0.65rem;
  border-top: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
}

.firm-siblings-title {
  margin: 0 0 0.55rem;
  font-family: var(--font-brand);
  font-size: 0.95rem;
  font-weight: 700;
}

@media (max-width: 720px) {
  .building-card-action {
    flex: 1 1 100%;
  }

  .firm-action-bar {
    top: 0;
    margin-left: -0.15rem;
    margin-right: -0.15rem;
    padding-left: 0.15rem;
    padding-right: 0.15rem;
  }

  .firm-action-btn {
    flex: 1 1 calc(50% - 0.25rem);
    min-width: 6.5rem;
  }

  .firm-action-report-phone {
    flex: 1 1 100%;
  }

  .firm-row-lite {
    padding: 0.75rem 0.85rem;
  }

  .public-quick-chip {
    font-size: 0.8rem;
    padding: 0.4rem 0.65rem;
  }
}

/* ——— Routes ——— */
.routes-form {
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.routes-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-soft);
}

.routes-field input {
  font: inherit;
  font-size: 0.9rem;
  font-weight: 500;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 0.55rem 0.7rem;
  background: var(--k-surface);
}

.routes-modes,
.routes-pick {
  display: flex;
  gap: 0.55rem;
  flex-wrap: wrap;
}

.routes-go {
  border: 0;
  border-radius: 10px;
  padding: 0.7rem 1rem;
  background: var(--accent);
  color: var(--k-text-on-accent);
  font: inherit;
  font-weight: 700;
  cursor: pointer;
}

.routes-result {
  margin-top: 0.85rem;
  padding: 0.75rem;
  border-radius: 12px;
  background: var(--k-surface);
  border: 1px solid var(--line);
  font-size: 0.9rem;
}

/* P83 Friends rail lite */
.friends-head {
  padding: 0.85rem 1rem 0.35rem;
}

.friends-head h2 {
  margin: 0;
  font-size: 1.15rem;
}

.friends-body {
  padding: 0.35rem 1rem 1.25rem;
  overflow: auto;
  min-height: 0;
  flex: 1;
}

.friends-summary {
  margin: 0 0 0.65rem;
  font-size: 0.82rem;
  color: var(--muted, var(--k-text-muted));
}

.friends-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}

.friends-row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.65rem 0.75rem;
  border-radius: 12px;
  background: var(--k-surface);
  border: 1px solid var(--line);
}

.friends-row-main {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.35rem 0.55rem;
}

.friends-name {
  font-size: 0.95rem;
}

.friends-tail {
  font-size: 0.78rem;
  color: var(--muted, var(--k-text-muted));
  font-variant-numeric: tabular-nums;
}

.friends-status {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  padding: 0.12rem 0.45rem;
  border-radius: 999px;
  margin-left: auto;
}

.friends-status.is-on {
  background: var(--k-brand-soft);
  color: var(--k-brand-ink);
}

.friends-status.is-off {
  background: var(--k-surface-2);
  color: var(--k-text-muted);
}

.friends-invite {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}

.friends-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.45rem;
  margin: 0.75rem 0 0.35rem;
}

.friends-cta {
  background: var(--accent) !important;
  color: var(--k-text-on-accent) !important;
  border-color: transparent !important;
}

.friends-note {
  font-size: 0.75rem;
  margin-top: 0.65rem;
}

.map-picking .maplibregl-canvas-container {
  cursor: crosshair !important;
}

/* Region → city soft CTA (no auto-navigate; overlays stay on region URL).
   Bottom of map-stage: clears top-right .map-tools / layers; search stays in sidebar. */
.region-city-offer {
  position: absolute;
  left: 50%;
  bottom: 0.85rem;
  top: auto;
  transform: translateX(-50%);
  z-index: 5;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  box-sizing: border-box;
  width: max-content;
  max-width: min(22rem, calc(100% - 1.5rem));
  padding: 0.4rem 0.35rem 0.4rem 0.8rem;
  border-radius: 12px;
  background: color-mix(in srgb, var(--accent-soft, var(--k-brand-soft)) 70%, var(--k-surface));
  border: 1px solid color-mix(in srgb, var(--accent, var(--k-brand)) 22%, var(--line));
  box-shadow: 0 2px 12px rgba(18, 32, 26, 0.08);
  font-size: 0.86rem;
  line-height: 1.3;
  pointer-events: none;
}

.region-city-offer[hidden] {
  display: none !important;
}

.region-city-offer-link {
  flex: 1 1 auto;
  min-width: 0;
  color: var(--accent, var(--k-brand));
  font-weight: 600;
  text-decoration: none;
  pointer-events: auto;
  /* Long «Открыть карту: …» — wrap up to 2 lines, then clip */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  overflow: hidden;
  word-break: break-word;
  overflow-wrap: anywhere;
}

.region-city-offer-link:hover {
  text-decoration: underline;
  text-underline-offset: 0.12em;
}

.region-city-offer-link:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent, var(--k-brand)) 55%, transparent);
  outline-offset: 2px;
  border-radius: 4px;
}

.region-city-offer-dismiss {
  flex: 0 0 auto;
  align-self: flex-start;
  width: 2.25rem;
  height: 2.25rem;
  margin: -0.15rem 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--ink-soft, var(--k-text-muted));
  font-size: 1.2rem;
  line-height: 1;
  cursor: pointer;
  pointer-events: auto;
  -webkit-tap-highlight-color: transparent;
}

.region-city-offer-dismiss:hover {
  background: color-mix(in srgb, var(--accent-soft, var(--k-brand-soft)) 55%, var(--k-surface));
  color: var(--ink, var(--k-text));
}

.region-city-offer-dismiss:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent, var(--k-brand)) 55%, transparent);
  outline-offset: 1px;
}

/* ——— Map tools (right stack) ——— */
.map-tools {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.map-tool {
  width: 2.5rem;
  height: 2.5rem;
  border-radius: 12px;
  border: 1px solid var(--line);
  background: var(--k-surface);
  box-shadow: var(--shadow);
  cursor: pointer;
  font-size: 1rem;
  line-height: 1;
}

.map-tool:hover,
.map-tool[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--k-brand-soft);
}

/* Flat ↔ 3D: explicit mode (compass only resets pitch). */
.map-tool-3d {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  font-family: var(--font-brand, inherit);
}

/* Traffic jams toggle (P84) — hidden until meta.traffic_enabled. */
.map-tool-traffic {
  font-size: 1.05rem;
  font-weight: 600;
  line-height: 1;
}

.map-tool-traffic[aria-pressed="true"] {
  border-color: var(--k-danger);
  color: var(--k-danger);
  background: var(--k-danger-soft);
}

.map-tool-traffic:disabled {
  opacity: 0.45;
}

/* Minimal congestion strip — visible only while #tool-traffic is on. */
.traffic-legend {
  position: absolute;
  left: 12px;
  bottom: 48px;
  z-index: 4;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 6px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(15, 23, 42, 0.12);
  font-size: 0.72rem;
  color: var(--k-text-muted);
  pointer-events: none;
}

.traffic-legend[hidden] {
  display: none !important;
}

.traffic-legend-swatch {
  width: 14px;
  height: 4px;
  border-radius: 2px;
}

.traffic-legend-swatch[data-level="free"] {
  background: #22c55e;
}
.traffic-legend-swatch[data-level="slow"] {
  background: #eab308;
}
.traffic-legend-swatch[data-level="heavy"] {
  background: #f97316;
}
.traffic-legend-swatch[data-level="jam"] {
  background: #ef4444;
}

.traffic-legend-label {
  margin-left: 2px;
  font-weight: 600;
}

.traffic-legend-score {
  margin-left: 2px;
  min-width: 1.4em;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--k-danger);
}

.traffic-legend-score[hidden] {
  display: none !important;
}

.traffic-legend-coverage {
  margin-left: 2px;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--k-surface-2);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--k-text-subtle);
  white-space: nowrap;
}

.traffic-legend-coverage[hidden] {
  display: none !important;
}

.traffic-legend-coverage[data-level="none"] {
  color: var(--k-text-muted);
}

.traffic-legend-coverage[data-level="low"] {
  color: var(--k-warn);
}

.traffic-legend-coverage[data-level="ok"] {
  color: var(--k-ok);
}

.routes-traffic-note {
  margin-top: 6px;
  font-size: 0.78rem;
  color: var(--k-text-muted);
}

.routes-updated-note {
  margin-top: 4px;
  font-size: 0.72rem;
  color: var(--k-text-subtle);
}

.traffic-legend-attrib {
  margin-left: 4px;
  padding-left: 8px;
  border-left: 1px solid var(--k-border);
  font-weight: 500;
  color: var(--k-text-muted);
  white-space: nowrap;
}

.traffic-legend-attrib:empty {
  display: none;
}

/* Honest viewport analytics: counts rendered vector objects, never inferred
 * population or demand. */
.analytics-hud {
  position: absolute;
  left: 12px;
  bottom: 86px;
  z-index: 4;
  width: min(230px, calc(100% - 72px));
  padding: 9px 11px;
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.94);
  border: 1px solid rgba(15, 23, 42, 0.14);
  box-shadow: 0 4px 14px rgba(15, 23, 42, 0.12);
  color: var(--k-text-muted);
  font-size: 0.72rem;
  pointer-events: none;
}

.analytics-hud[hidden] {
  display: none !important;
}

.analytics-hud-title {
  color: var(--k-text);
  font-size: 0.78rem;
  font-weight: 800;
}

.analytics-hud-subtitle {
  margin-top: 2px;
  color: #64748b;
}

.analytics-hud-grid {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 3px 12px;
  margin-top: 7px;
}

.analytics-hud-grid strong {
  color: var(--k-brand);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.analytics-hud-note {
  margin-top: 7px;
  color: #64748b;
  font-size: 0.64rem;
  line-height: 1.3;
}

.analytics-hud-top {
  margin-top: 7px;
  color: #0f766e;
  font-size: 0.66rem;
  line-height: 1.35;
}

.analytics-density-legend {
  display: flex;
  align-items: center;
  gap: 5px;
  margin-top: 6px;
  color: #64748b;
  font-size: 0.62rem;
}

.analytics-density-legend i {
  display: block;
  flex: 1;
  height: 5px;
  border-radius: 999px;
  background: linear-gradient(90deg, #d1fae5, #86efac, #22c55e, #166534);
}

.is-dark .analytics-hud {
  background: rgba(15, 23, 42, 0.92);
  border-color: rgba(148, 163, 184, 0.28);
  color: var(--k-text-muted);
}

.is-dark .analytics-hud-title {
  color: var(--k-text);
}

.is-dark .analytics-hud-subtitle,
.is-dark .analytics-hud-note {
  color: #94a3b8;
}

.is-dark .analytics-hud-top {
  color: var(--k-brand-ink);
}

.is-dark .analytics-density-legend {
  color: #94a3b8;
}

.analytics-density-popup .maplibregl-popup-content {
  padding: 8px 10px;
  border: 1px solid rgba(21, 128, 61, 0.24);
  border-radius: 10px;
  color: var(--k-brand-ink);
  box-shadow: 0 6px 18px rgba(15, 23, 42, 0.16);
}

.analytics-density-popup strong,
.analytics-density-popup small {
  display: block;
}

.analytics-density-popup small {
  margin-top: 2px;
  color: var(--k-text-muted);
  font-size: 0.68rem;
}

.analytics-density-rubric {
  margin: 0 2px 2px 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: #0f766e;
  font: inherit;
  font-size: 0.68rem;
  text-decoration: underline;
  cursor: pointer;
}

.analytics-density-rubric:hover,
.analytics-density-rubric:focus-visible {
  color: #166534;
}

.is-dark .analytics-density-popup .maplibregl-popup-content {
  background: var(--k-surface-2);
  border-color: rgba(94, 234, 212, 0.28);
  color: var(--k-brand-ink);
}

.is-dark .analytics-density-rubric {
  color: var(--k-brand-ink);
}

/* P85: opt-in probe consent (phase 0 UI; upload later). */
/* RU: Ф6.7, вторая часть (14.09.2026): литералы витрины без ночного переопределения переведены на токены внутри своих правил —
       плашка согласия на пробки, подписи легенды пробок и маршрутов, акценты аналитики, блок «заявите карточку» и кнопки
       владельца, звёзды, бегунок диапазона, резерв мощности, ручка шторки. Литералами остались цвета данных (уровни пробок,
       соцсети, геоточка) и слои, тёмные в любой теме (лайтбокс, панель мощностей, затемнение тура), и тени.
   EN: Ф6.7, part two (2026-09-14): storefront literals without a night override moved onto tokens inside their own rules — the traffic
       consent plate, traffic legend and route captions, analytics accents, the "claim the card" block and owner buttons, stars, the
       range thumb, power reserve, the sheet handle. Data colours (traffic levels, socials, location dot), layers dark in any theme
       (lightbox, capacity panel, tour dimmer) and shadows remain literals. */
.traffic-consent {
  position: absolute;
  left: 12px;
  bottom: 84px;
  z-index: 4;
  max-width: min(280px, calc(100% - 72px));
  padding: 8px 10px;
  border-radius: 8px;
  background: color-mix(in srgb, var(--k-surface) 94%, transparent);
  border: 1px solid var(--k-border);
  font-size: 0.72rem;
  color: var(--k-text-muted);
  line-height: 1.35;
  pointer-events: auto;
}

.traffic-consent[hidden] {
  display: none !important;
}

.traffic-consent-label {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  font-weight: 600;
  cursor: pointer;
}

.traffic-consent-label input {
  margin-top: 2px;
  flex-shrink: 0;
}

.traffic-consent-hint {
  margin: 6px 0 0;
  color: var(--k-text-muted);
  font-weight: 400;
}

.traffic-consent-hint a {
  color: #1d4ed8;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.map-tool.is-busy {
  opacity: 0.55;
  pointer-events: none;
}

#tool-geolocate.is-following {
  border-color: var(--k-info);
  background: var(--k-info-soft);
  color: var(--k-info);
}

/* #tool-geolocate marker (toolbar owns locate; no MapLibre GeolocateControl). */
.user-location-marker {
  position: relative;
  width: 22px;
  height: 22px;
  pointer-events: none;
}

.user-location-dot {
  position: absolute;
  inset: 5px;
  border-radius: 50%;
  background: #1a73e8;
  border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.35);
}

/* Coarse IP / network fix — amber so it is not mistaken for precise GPS. */
.user-location-marker.is-approx .user-location-dot {
  background: #e8a317;
}

.user-location-marker.is-approx .user-location-pulse {
  background: rgba(232, 163, 23, 0.3);
}

.user-location-pulse {
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: rgba(26, 115, 232, 0.28);
  animation: user-location-pulse 1.6s ease-out infinite;
}

@keyframes user-location-pulse {
  0% {
    transform: scale(0.55);
    opacity: 0.9;
  }
  100% {
    transform: scale(1.55);
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .user-location-pulse {
    animation: none;
    opacity: 0.35;
  }
}

.layers-menu {
  position: absolute;
  top: 1rem;
  right: 4rem;
  z-index: 6;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding: 0.7rem 0.85rem;
  border-radius: 12px;
  background: var(--k-surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  min-width: 11rem;
  /* RU: Меню выросло: базовые слои, инженерные сети, раскраска и легенда не
     помещаются в невысокое окно. Без прокрутки нижние строки недостижимы —
     пользователь их просто не видит и считает, что их нет.
     EN: The menu has grown: base layers, utilities, colouring and legend do not fit a short
     window. Without scrolling the bottom rows are unreachable — the user simply does not see
     them and assumes they do not exist. */
  max-height: calc(100vh - 5rem);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* RU: Полоса прокрутки тонкая и в цвет рамки: меню маленькое, и системная
   полоса в нём выглядит как отдельный элемент интерфейса.
   EN: A thin scrollbar in the border's colour: the menu is small, and a system-sized bar
   inside it reads as a separate interface element. */
.layers-menu::-webkit-scrollbar { width: 8px; }
.layers-menu::-webkit-scrollbar-thumb {
  background: var(--line);
  border-radius: 4px;
}

.layers-menu[hidden] {
  display: none !important;
}

/* --- P93: группа инженерных слоёв / utility layer group --- */
.layers-group {
  display: flex;
  flex-direction: column;
  gap: 0.28rem;
  margin-top: 0.45rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--line);
}

.layers-group[hidden] {
  display: none !important;
}

.layers-group-title {
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--muted, #5a6b62);
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

/* RU: Пометка «расчётная модель» стоит в заголовке группы, а не в подсказке:
       правовой статус данных нельзя прятать под наведение мыши.
   EN: The "computed model" note sits in the group title rather than a tooltip: the
       legal status of the data must not hide behind a hover. */
.layers-group-note {
  display: block;
  margin-top: 0.12rem;
  font-size: 0.64rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  color: var(--k-warn);
}

.layer-zoom-hint {
  margin-left: auto;
  padding-left: 0.5rem;
  font-size: 0.66rem;
  font-weight: 600;
  color: var(--k-text-subtle);
  font-variant-numeric: tabular-nums;
}

/* RU: Строки меню растягиваем на всю ширину — иначе подпись порога не может
       прижаться к правому краю и болтается сразу за названием.
   EN: Menu rows stretch to full width — otherwise the threshold caption cannot align
       right and dangles right after the label. */
.layers-menu .filter-check {
  display: flex;
  width: 100%;
  align-self: stretch;
}

.visual-presets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin-bottom: 0.25rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--line);
}

.visual-presets-label {
  flex: 0 0 100%;
  font-size: 0.7rem;
  font-weight: 700;
  color: var(--muted, var(--k-text-muted));
}

.layers-presets {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-bottom: 0.25rem;
  padding-bottom: 0.45rem;
  border-bottom: 1px solid var(--line);
}

/* RU: Фон — токены темы, а не #f6f8f6/#d5ebe0: текст кнопки берётся из --ink, и ночью светлый текст
       на зашитом светлом фоне давал 1,1:1 — «Как в системе / День / Ночь» было не прочитать.
   EN: Background uses theme tokens, not #f6f8f6/#d5ebe0: the button text comes from --ink, so at night
       light text on the hardcoded light background gave 1.1:1 — "System / Day / Night" was unreadable. */
.layers-preset {
  appearance: none;
  border: 1px solid var(--line);
  background: var(--k-surface-3);
  color: var(--ink, var(--k-text));
  border-radius: 8px;
  padding: 0.22rem 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  cursor: pointer;
}

.layers-preset:hover {
  border-color: var(--accent);
  background: var(--k-brand-soft);
}

.layers-preset.is-active,
.layers-preset[aria-pressed="true"] {
  border-color: var(--accent);
  background: var(--k-brand-soft);
  color: var(--ink, var(--k-text));
}

.veg-graphics {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.3rem;
  margin: 0.15rem 0 0.35rem;
}

.veg-graphics-label {
  flex: 0 0 100%;
  font-size: 0.7rem;
  font-weight: 600;
  color: var(--muted, var(--k-text-muted));
}

.measure-hud {
  position: absolute;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  z-index: 6;
  padding: 0.45rem 0.85rem;
  border-radius: 999px;
  background: rgba(18, 32, 26, 0.88);
  color: #f3faf6;
  font-size: 0.82rem;
  font-weight: 600;
  pointer-events: none;
}

.measure-hud[hidden] {
  display: none !important;
}

.shell-panel-legacy {
  display: none !important;
}

/* ——— Existing public polish ——— */
body.public .brand-mark {
  letter-spacing: -0.03em;
}

body.public .attrib a {
  color: inherit;
}

.priority-cta a {
  display: inline-block;
  margin: 0.35rem 0 0.15rem;
  padding: 0.45rem 0.85rem;
  border-radius: 8px;
  background: var(--k-brand);
  color: #d8f3dc !important;
  font-weight: 600;
  text-decoration: none;
}

.firm-promo {
  color: var(--k-brand);
  font-weight: 600;
}

.conversion-block {
  margin: 0.35rem 0 0.15rem;
  padding: 0.55rem 0.65rem;
  border-radius: 10px;
  background: linear-gradient(160deg, color-mix(in srgb, var(--k-brand) 8%, transparent), color-mix(in srgb, var(--k-surface) 65%, transparent));
  border: 1px solid color-mix(in srgb, var(--k-brand) 14%, transparent);
}

.priority-info {
  margin: 0 0 0.4rem;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--k-brand);
}

.priority-info a {
  font-weight: 600;
  color: var(--k-brand);
}

.claim-cta--owner .claim-cta-btn {
  display: inline-block;
  margin-top: 0.15rem;
  padding: 0.45rem 0.75rem;
  border-radius: 8px;
  background: var(--k-brand);
  color: var(--k-text-on-accent) !important;
  font-weight: 700;
  font-size: 0.86rem;
  text-decoration: none;
  transition: transform 0.18s ease, background 0.18s ease;
}

.claim-cta--owner .claim-cta-btn:hover {
  background: var(--k-brand-hover);
  transform: translateY(-1px);
  text-decoration: none;
}

.claim-cta--contacts .claim-cta-btn {
  background: var(--k-warn);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--k-warn) 25%, transparent);
}
.claim-cta--contacts .claim-cta-btn:hover {
  background: color-mix(in srgb, var(--k-warn) 85%, var(--k-text));
}

.claim-cta--manage a {
  font-size: 0.82rem;
}

/* Sheet handle — desktop hidden, mobile drag affordance */
.sheet-handle {
  display: none;
  width: 100%;
  border: 0;
  background: transparent;
  padding: 0.45rem 0 0.2rem;
  cursor: pointer;
}

.sheet-handle-bar {
  display: block;
  width: 2.5rem;
  height: 4px;
  margin: 0 auto;
  border-radius: 999px;
  background: color-mix(in srgb, var(--k-text) 22%, transparent);
}

.results-more {
  display: block;
  width: 100%;
  margin: 0.5rem 0 0.25rem;
  padding: 0.55rem 0.75rem;
  border: 1px dashed var(--line);
  border-radius: 10px;
  background: var(--k-surface-3);
  font: inherit;
  font-weight: 600;
  color: var(--k-brand);
  cursor: pointer;
}

.results-more:hover {
  background: color-mix(in srgb, var(--k-brand-soft) 45%, var(--k-surface));
}

.empty-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-top: 0.65rem;
}

/* Mobile: bottom sheet */
@media (max-width: 860px) {
  body.public.shell-v2 #app.shell {
    min-width: 0;
    grid-template-columns: 1fr;
    grid-template-rows: 48px 1fr minmax(38%, 52%) auto;
    grid-template-areas:
      "rail"
      "map"
      "sidebar"
      "attrib";
  }

  body.public.shell-v2 #app.shell.sheet-collapsed {
    grid-template-rows: 48px 1fr 72px auto;
  }

  body.public.shell-v2 #app.shell.sheet-expanded {
    grid-template-rows: 48px minmax(120px, 28%) minmax(55%, 70%) auto;
  }

  /* RU: Ф6.9. Рельс на телефоне — ряд из девяти целей по 44 px: логотип плюс восемь кнопок.
         44 × 9 плюс зазоры и отступы = 444 px, и на экране 390 px ряд физически не помещается
         НИКАКОЙ вёрсткой. Раньше он просто вылезал наружу, и вместе с ним по горизонтали
         прокручивался body: страница ездила влево-вправо целиком, смещая карту и панель, а часть
         рельса всё равно оставалась недоступной. Замер: ширина содержимого 412 px при окне
         390 px ещё до этой правки (потерянные скобки ниже держали минимум логотипа мёртвым,
         поэтому было 412, а не 444 — то есть переполнение было и до них).

         Прокрутка самого рельса, а не переносом в две строки: перенос забирает ещё 48 px высоты
         у карты, а на телефоне высотой 568 px это пятая часть экрана. Полоса прокрутки скрыта —
         её место в рельсе высотой 55 px заняло бы половину значка; о том, что ряд продолжается,
         говорит наполовину видимая последняя кнопка.

         overflow-y: hidden обязателен вместе с overflow-x: по спецификации `visible` рядом с
         `auto` сам превращается в `auto`, и в рельсе появлялась бы вторая, вертикальная полоса.
         Контур фокуса при этом не режется: отступ рельса 5,6 px больше, чем контур 2 px со
         смещением 2 px.
     EN: Ф6.9. On a phone the rail is a row of nine 44 px targets: the logo plus eight buttons.
         44 x 9 plus gaps and padding is 444 px, and on a 390 px screen the row physically does not
         fit with ANY layout. It used to simply stick out, and the body scrolled horizontally with
         it: the whole page slid left and right, shifting the map and the panel, while part of the
         rail stayed unreachable anyway. Measured: 412 px of content against a 390 px window even
         before this edit (the missing braces below kept the logo's minimum dead, hence 412 rather
         than 444 — so the overflow predates them).

         Scrolling the rail itself rather than wrapping into two rows: wrapping takes another 48 px
         of height from the map, and on a 568 px tall phone that is a fifth of the screen. The
         scrollbar is hidden — in a 55 px tall rail it would occupy half an icon; that the row
         continues is signalled by the half-visible last button.

         overflow-y: hidden is mandatory alongside overflow-x: per spec a `visible` next to an
         `auto` becomes `auto` itself, and a second, vertical scrollbar would appear in the rail.
         The focus ring is not clipped by this: the rail's 5.6 px padding exceeds a 2 px outline at
         a 2 px offset. */
  .nav-rail {
    min-width: 0;
    flex-direction: row;
    padding: 0.35rem 0.5rem;
    gap: 0.25rem;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }

  .nav-rail::-webkit-scrollbar {
    display: none;
  }

  .rail-spacer {
    display: none;
  }

  .rail-brand {
    width: 2rem;
    height: 2rem;
    margin: 0 0.25rem 0 0;
  }

  .rail-btn {
    flex-direction: row;
    gap: 0.25rem;
    width: auto;
    padding: 0.35rem 0.55rem;
    font-size: 0.72rem;
  }

  .rail-label {
    display: none;
  }

  .sheet-handle {
    display: block;
    position: sticky;
    top: 0;
    z-index: 2;
    background: linear-gradient(180deg, var(--k-surface) 70%, transparent);
  }

  .sidebar {
    min-width: 0;
    border-right: 0;
    border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 28px rgba(18, 32, 26, 0.12);
    overscroll-behavior: contain;
    transition: height 0.3s ease;
  }

  /* Sheet drag visual feedback */
  #app.sheet-dragging-up .sidebar {
    box-shadow: 0 -12px 32px rgba(18, 32, 26, 0.2);
    transition: box-shadow 0.15s ease;
  }
  #app.sheet-dragging-down .sidebar {
    box-shadow: 0 -2px 12px rgba(18, 32, 26, 0.06);
    transition: box-shadow 0.15s ease;
  }

  /* Smooth grid transition */
  #app.shell {
    transition: grid-template-rows 0.3s ease;
  }

  /* Touch-friendly: ensure tappable targets ≥ 44px */
  .rail-btn, .firm-action-btn, .ghost-btn, .filter-toggle,
  .search-btn, .back-btn, .map-tools button, .nav-rail button,
  .detail-back-btn, .results-back-btn, .place-switcher-btn,
  .firm-tab, .building-card-action, .firm-website-btn,
  .firm-phone-btn, .firm-route-btn, .firm-share-btn,
  .firm-fav-btn, .review-submit-btn, .rubric-chip,
  .region-city-offer button, .filter-bar select,
  .results-more, .firm-media-nav, .firm-media-dot {
    min-height: 44px;
    min-width: 44px;
  }

  /* Tighter spacing for mobile sidebar */
  .sidebar-head {
    padding: 0.4rem 0.6rem 0.2rem;
  }

  .panel-body .firm-row {
    padding: 0.5rem 0.5rem;
  }

  /* Increase font size for readability on mobile */
  .rail-label {
    display: inline;
    font-size: 0.7rem;
  }

  .place-switcher {
    font-size: 0.8rem;
  }

  body.public.shell-v2 #app.shell.sheet-collapsed .sidebar > :not(.sheet-handle):not(.sidebar-head) {
    display: none;
  }

  body.public.shell-v2 #app.shell.sheet-collapsed .sidebar-head .place-switcher {
    display: none;
  }

  .rubric-grid {
    grid-template-columns: repeat(2, 1fr);
  }

  .map-tools {
    top: 0.65rem;
    right: 0.65rem;
  }

  /* Chip above bottom sheet; keep clear of map-tools column */
  .region-city-offer {
    left: 0.65rem;
    right: 3.6rem;
    bottom: 0.55rem;
    transform: none;
    width: auto;
    max-width: none;
    font-size: 0.82rem;
    padding: 0.35rem 0.25rem 0.35rem 0.7rem;
  }

  body.public.shell-v2 .map-stage {
    min-height: 180px;
  }

  /* ── Mobile fullscreen search overlay ── */
  .mobile-search-overlay {
    position: fixed;
    inset: 0;
    z-index: 100;
    background: rgba(15, 31, 24, 0.6);
    backdrop-filter: blur(4px);
    display: flex;
    flex-direction: column;
    padding: env(safe-area-inset-top, 8px) 0.5rem 0.5rem;
    animation: mobile-search-in 0.2s ease-out;
  }
  .mobile-search-overlay[hidden] {
    display: none !important;
  }

  @keyframes mobile-search-in {
    from { opacity: 0; }
    to   { opacity: 1; }
  }

  .mobile-search-bar {
    display: flex;
    gap: 0.5rem;
    align-items: center;
    background: var(--k-surface);
    border-radius: 14px;
    padding: 0.3rem 0.5rem 0.3rem 0.9rem;
    box-shadow: 0 4px 24px rgba(0,0,0,0.15);
  }

  .mobile-search-bar input {
    flex: 1;
    border: 0;
    outline: 0;
    font: inherit;
    font-size: 1.05rem;
    padding: 0.5rem 0;
    background: transparent;
    color: var(--ink);
  }

  /* RU: Ф6.9. Поле выше сбрасывает контур (`outline: 0`) — так задумано: контур внутри
         скруглённой плашки поиска рисовался прямоугольником поперёк неё. Но сброс был
         БЕЗ замены, и специфичность `.mobile-search-bar input` (0,1,1) перебивает
         глобальное правило `:where(...):focus-visible` из kartivi.css (0,1,0), то есть
         клавиатурный фокус в мобильном поиске не был виден вообще. Замена — тень по
         форме самой плашки: она повторяет её радиус и не спорит с вёрсткой.
     EN: Ф6.9. The field above resets the outline (`outline: 0`) deliberately: an outline inside
         the rounded search plate was drawn as a rectangle across it. But the reset came WITHOUT a
         replacement, and the specificity of `.mobile-search-bar input` (0,1,1) beats the global
         `:where(...):focus-visible` rule from kartivi.css (0,1,0), so keyboard focus in the mobile
         search was not visible at all. The replacement is a shadow shaped like the plate itself:
         it follows its radius and does not fight the layout. */
  .mobile-search-bar:focus-within {
    box-shadow:
      0 4px 24px rgba(0, 0, 0, 0.15),
      0 0 0 2px var(--accent, var(--k-brand));
  }

  .mobile-search-bar input::placeholder {
    color: var(--ink-soft);
  }

  .mobile-search-close {
    width: 44px;
    height: 44px;
    border: 0;
    background: var(--k-surface-2);
    border-radius: 10px;
    font-size: 1.2rem;
    cursor: pointer;
    display: grid;
    place-items: center;
    flex-shrink: 0;
  }

  .mobile-suggest-list {
    margin: 0.35rem 0 0;
    background: var(--k-surface);
    border-radius: 14px;
    overflow: hidden;
    box-shadow: 0 4px 24px rgba(0,0,0,0.12);
    max-height: calc(100vh - 120px);
    overflow-y: auto;
  }

  /* RU: Пункт подсказки — это <button> (доступность с клавиатуры), а у кнопки браузер ставит свой фон,
         рамку, шрифт и ширину по содержимому. Без сброса ниже подсказки на телефоне выглядели серыми
         плашками разной длины (снимок 13.09.2026 при 375×812, тёмная тема). Поэтому ширина во всю строку,
         прозрачный фон, рамка только снизу, шрифт и цвет наследуются, текст прижат влево.
     EN: A suggestion item is a <button> (keyboard accessibility), and the browser gives a button its own
         background, border, font and content-sized width. Without the reset below, phone suggestions looked
         like grey plates of different lengths (screenshot 2026-09-13 at 375×812, dark theme). Hence full row
         width, transparent background, a bottom border only, inherited font and colour, left-aligned text. */
  .mobile-suggest-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    width: 100%;
    padding: 0.65rem 0.9rem;
    border: 0;
    border-bottom: 1px solid var(--line);
    background: transparent;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
    min-height: 48px;
  }

  .mobile-suggest-item:last-child {
    border-bottom: 0;
  }

  /* RU: Отклик нажатия от цвета поверхности, а не светлым #f2f7f4: в тёмной теме светлое пятно слепило.
     EN: Press feedback derived from the surface colour rather than a light #f2f7f4: in the dark theme a light patch glared. */
  .mobile-suggest-item:active {
    background: color-mix(in srgb, var(--k-brand-soft) 35%, var(--k-surface));
  }

  .mobile-suggest-icon {
    width: 1.8rem;
    height: 1.8rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--accent) 15%, var(--k-surface));
    display: grid;
    place-items: center;
    font-size: 0.85rem;
    flex-shrink: 0;
  }

  .mobile-suggest-label {
    flex: 1;
    font-weight: 600;
    font-size: 0.9rem;
    color: var(--ink);
    overflow: hidden;
    white-space: nowrap;
    text-overflow: ellipsis;
  }

  .mobile-suggest-sublabel {
    font-size: 0.75rem;
    color: var(--ink-soft);
  }

  /* ── Mobile firm card adaptations ── */
  .firm-card-v2 {
    border-radius: 0;
    box-shadow: none;
    margin: -0.35rem -1rem 0;
  }

  .firm-card-v2 .firm-photo-strip {
    grid-template-columns: 1fr;
    max-height: 220px;
  }

  .firm-card-body {
    padding: 0.75rem 0.75rem 0.5rem;
  }

  .firm-card-name {
    font-size: 1.15rem;
  }

  .firm-phone-link {
    font-size: 1.1rem;
    padding: 0.4rem 0;
  }

  .firm-action-bar {
    gap: 0.35rem;
    margin-top: 0.6rem;
  }

  .firm-action-btn {
    flex: 1 1 calc(50% - 0.25rem);
    font-size: 0.85rem;
    padding: 0.55rem 0.5rem;
    min-height: 48px;
    text-align: center;
  }

  .firm-card-v2 .firm-photo-strip .photo-item.is-hero {
    max-height: 180px;
  }
}

/* ——— Offline indicator ——— */
.offline-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 200;
  padding: 0.45rem 1rem;
  background: #ff9f1c;
  color: #1a1a1a;
  font-size: 0.8rem;
  font-weight: 600;
  text-align: center;
  animation: offline-bar-in 0.3s ease-out;
}
.offline-bar[hidden] { display: none !important; }
.offline-bar-icon { margin-right: 0.35rem; }

@keyframes offline-bar-in {
  from { transform: translateY(-100%); }
  to   { transform: translateY(0); }
}

/* ——— P81 U8 first-visit tour ——— */
/* Overlay dims the stage but must NOT swallow #map-tools (◎ / 3D / layers). */
.first-visit-tour {
  position: absolute;
  inset: 0;
  z-index: 40;
  display: grid;
  place-items: end center;
  padding: 1rem 1rem 1.25rem;
  background: color-mix(in srgb, #0f1f18 42%, transparent);
  pointer-events: none;
}

.first-visit-tour .tour-card {
  pointer-events: auto;
}

.first-visit-tour[hidden] {
  display: none !important;
}

.tour-card {
  width: min(22rem, 100%);
  padding: 1rem 1.1rem 1.05rem;
  border-radius: 16px;
  background: var(--k-surface-3);
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--line));
  box-shadow: 0 12px 40px rgba(15, 31, 24, 0.28);
  animation: tour-rise 0.35s ease-out;
}

.tour-card.is-static {
  animation: none;
}

@keyframes tour-rise {
  from {
    opacity: 0;
    transform: translateY(0.6rem);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .tour-card {
    animation: none;
  }
}

.tour-kicker {
  margin: 0 0 0.35rem;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: color-mix(in srgb, var(--accent) 65%, var(--ink-soft));
}

.tour-title {
  margin: 0 0 0.4rem;
  font-family: var(--font-brand);
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ink);
}

.tour-body {
  margin: 0 0 0.75rem;
  font-size: 0.9rem;
  line-height: 1.45;
  color: var(--ink-soft);
}

.tour-dots {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
}

.tour-dot {
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 999px;
  background: color-mix(in srgb, var(--line) 80%, var(--ink-soft));
}

.tour-dot.is-on {
  background: var(--accent);
  width: 1rem;
}

.tour-actions {
  display: flex;
  gap: 0.45rem;
  justify-content: flex-end;
}

.tour-actions .primary-btn {
  border: 0;
  border-radius: 10px;
  padding: 0.5rem 0.9rem;
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  color: var(--k-text-on-accent);
  background: var(--accent);
}

.tour-actions .primary-btn:hover {
  filter: brightness(1.05);
}

/* ——— Firm Card v2 ——— */
.firm-card-v2 {
  background: var(--k-surface);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}

.firm-card-v2 .firm-photo-strip {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 2px;
  max-height: 240px;
  overflow: hidden;
  background: var(--k-surface-2);
}

.firm-photo-strip .photo-item {
  display: block;
  overflow: hidden;
  background: var(--k-border);
}

.firm-photo-strip .photo-item.is-hero {
  grid-column: 1 / -1;
  max-height: 160px;
}

.firm-photo-strip .photo-item img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.firm-card-body {
  padding: 1rem 1rem 0.75rem;
}

/* RU: Ф6.16: рейтинг — строка с числом, а не ряд звёзд: разрядка и золотой цвет были для символов ★☆.
   EN: Ф6.16: the rating is a line with a number rather than a row of stars: the letter spacing and gold were for ★☆. */
.firm-rating {
  margin: 0 0 0.4rem;
  font-size: 0.9rem;
  color: var(--k-text-muted);
}
.firm-rating strong {
  color: var(--k-text);
}

.firm-phones {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin: 0.5rem 0;
}

.firm-phone-link {
  font-size: 1.05rem;
  font-weight: 600;
  color: var(--k-brand);
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.3rem;
}

.firm-phone-link:hover { color: var(--k-brand-hover); }

.phone-count {
  font-size: 0.8rem;
  color: var(--k-text-muted);
  background: var(--k-surface-2);
  padding: 1px 6px;
  border-radius: 8px;
}

.firm-website, .firm-email {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--k-brand-hover);
  text-decoration: none;
  font-size: 0.9rem;
  margin: 0.3rem 0;
}

.firm-website:hover, .firm-email:hover { text-decoration: underline; }

.firm-social-links {
  display: flex;
  gap: 0.5rem;
  margin: 0.5rem 0;
}

.social-link {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 6px;
  background: var(--k-surface-2);
  color: var(--k-text);
  text-decoration: none;
  font-size: 0.75rem;
  font-weight: 700;
  transition: background 0.15s;
}

.social-vk:hover { background: #0077ff; color: var(--k-text-on-accent); }
.social-telegram:hover { background: #0088cc; color: var(--k-text-on-accent); }
.social-instagram:hover { background: #e1306c; color: var(--k-text-on-accent); }
.social-youtube:hover { background: #ff0000; color: var(--k-text-on-accent); }
.social-ok:hover { background: #ee8208; color: var(--k-text-on-accent); }

.firm-hours {
  margin: 0.5rem 0;
  padding: 0.5rem;
  background: var(--k-surface-3);
  border-radius: 8px;
}

.firm-hours-head {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.hours-raw {
  font-size: 0.82rem;
  color: var(--k-text-muted);
  margin-top: 0.3rem;
  line-height: 1.4;
}

.hours-label {
  font-size: 0.85rem;
  color: var(--k-text-muted);
}

.firm-description { margin: 0.4rem 0; }

/* RU: Н1.2 — «Особенности» (public/js/firm-features.js): группа слева мельче и приглушённее, значения справа
       обычным текстом. Только токены темы: ночная тема (Ф6.7) переопределяет их сама.
   EN: N1.2 — "Features" (public/js/firm-features.js): the group label smaller and muted, the values in plain text.
       Theme tokens only: the night theme (Ф6.7) overrides them by itself. */
.firm-features { margin: 0.6rem 0; }
.firm-features-title { font-size: 0.9rem; margin: 0 0 0.3rem; }
.firm-features-list { margin: 0; }
.firm-feature { margin: 0 0 0.35rem; }
.firm-feature dt { font-size: 0.78rem; color: var(--k-text-muted); }
.firm-feature dd { margin: 0; font-size: 0.86rem; line-height: 1.45; }

.desc-text {
  font-size: 0.88rem;
  color: var(--k-text-muted);
  line-height: 1.5;
  margin: 0;
}

.desc-expand {
  font-size: 0.82rem;
  color: var(--k-brand-hover);
  cursor: pointer;
  border: none;
  background: none;
  padding: 2px 0;
}

.firm-action-bar {
  display: flex;
  gap: 0.4rem;
  margin-top: 0.75rem;
  padding-top: 0.6rem;
  border-top: 1px solid var(--k-border-soft);
  flex-wrap: wrap;
}

.firm-action-btn {
  font-size: 0.82rem;
  padding: 6px 12px;
  border: 1px solid var(--k-border);
  border-radius: 6px;
  background: var(--k-surface);
  color: var(--k-text);
  cursor: pointer;
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s;
}

.firm-action-btn:hover { background: var(--k-surface-2); border-color: var(--k-border-strong); }
.firm-action-btn:active { background: var(--k-surface-2); }

.firm-action-btn[data-action="report"] { color: var(--k-warn); border-color: color-mix(in srgb, var(--k-warn) 30%, transparent); }
.firm-action-btn[data-action="report"]:hover { background: var(--k-warn-soft); border-color: var(--k-warn); }

/* ——— Weekly hours table ——— */
/* RU: Ф6.16: таблица часов открыта сразу, без <details> «🕐 График работы на неделю» — расписание и есть то, за чем открывают
       блок часов. Заголовок блока общий для обеих раскладок карточки.
   EN: Ф6.16: the hours table is open right away, without the "🕐 График работы на неделю" <details> — the schedule is what the hours
       block is opened for. The block title is shared by both card layouts. */
.firm-block-title {
  margin: 0.75rem 0 0.2rem;
  font-size: 0.85rem;
  font-weight: 600;
  color: var(--k-text);
}
.firm-hours-raw {
  margin: 0.2rem 0 0;
  font-size: 0.88rem;
}
/* RU: Просьба сообщить номер — строкой с кнопкой, без жирного «Телефон» над пустотой. EN: The request for a number — a line with a
       button, without a bold "Телефон" over nothing. */
.firm-contact-missing {
  color: var(--k-text-muted);
}
.firm-card-footer {
  margin-top: 0.75rem;
}
/* RU: П1.1 (на основе Ф4.18): метка проверки у поля — мелкая строка с точкой цвета свежести. Цвета — токены темы, чтобы ночью не
       выпадали; «не проверено» серое, а не красное: это нормальное состояние базы, а не ошибка.
   EN: П1.1 (built on Ф4.18): a field's check mark — a small line with a freshness-coloured dot. Colours are theme tokens so they hold up
       at night; "не проверено" is grey rather than red: it is the database's normal state, not an error. */
.fact-mark {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  margin-top: 0.15rem;
  font-size: 0.75rem;
  color: var(--k-text-muted);
}
.fact-mark::before {
  content: "";
  width: 0.45rem;
  height: 0.45rem;
  border-radius: 50%;
  flex: none;
  background: var(--fact-dot, var(--k-border));
}
.firm-contact .fact-mark,
.firm-hours-block .fact-mark,
.firm-hours .fact-mark,
.firm-phones + .fact-mark {
  display: flex;
}
.fact-mark.is-fresh { --fact-dot: var(--k-ok); color: var(--k-ok); }
.fact-mark.is-aging { --fact-dot: var(--k-warn); }
.fact-mark.is-disputed { --fact-dot: var(--k-warn); color: var(--k-warn); }
.fact-mark.is-stale { --fact-dot: var(--k-border); }

/* RU: П1.1: строка доверия над вкладками — кольцо свежести (conic-gradient по доле --p) и две строки текста. Кольцо рисуется CSS, без
       картинок и SVG: одна переменная на карточку, и тема меняет цвет сама.
   EN: П1.1: the trust line above the tabs — a freshness ring (a conic-gradient over the --p share) and two lines of text. The ring is
       drawn in CSS without images or SVG: one variable per card, and the theme changes the colour by itself. */
.firm-trustline {
  display: flex;
  align-items: center;
  gap: 0.65rem;
  margin: 0.6rem 0;
  padding: 0.5rem 0.6rem;
  border-radius: 10px;
  background: var(--k-surface);
  border: 1px solid var(--k-border);
}
.trust-ring {
  --p: 0;
  --ring-c: var(--k-border);
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 2.6rem;
  height: 2.6rem;
  border-radius: 50%;
  background: conic-gradient(var(--ring-c) calc(var(--p) * 1turn), color-mix(in srgb, var(--k-border) 60%, transparent) 0);
}
.trust-ring::before {
  content: "";
  position: absolute;
  inset: 0.2rem;
  border-radius: 50%;
  background: var(--k-surface);
}
.trust-ring > b {
  position: relative;
  font-size: 0.8rem;
  font-variant-numeric: tabular-nums;
}
.trust-ring.is-fresh { --ring-c: var(--k-ok); }
.trust-ring.is-aging { --ring-c: var(--k-warn); }
.trust-ring.is-stale { --ring-c: var(--k-text-muted); }
.trust-text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  font-size: 0.85rem;
}
.trust-sub,
.trust-address {
  font-size: 0.78rem;
  color: var(--k-text-muted);
}

.hours-table {
  width: 100%;
  margin-top: 0.3rem;
  border-collapse: collapse;
  font-size: 0.82rem;
}
.hours-table th {
  text-align: left;
  padding: 3px 8px;
  color: var(--k-text-muted);
  font-weight: 500;
  width: 2.5em;
}
.hours-table td {
  padding: 3px 8px;
  color: var(--k-text);
}
.hours-table tr.is-today th,
.hours-table tr.is-today td {
  background: var(--k-ok-soft);
  font-weight: 600;
  color: var(--k-ok);
  border-radius: 4px;
}

/* ——— Public org section ——— */
.firm-public-section {
  margin: 0.5rem 0;
  padding: 0.5rem 0.75rem;
  background: var(--k-info-soft);
  border-radius: 8px;
  border-left: 3px solid var(--k-info);
}
.public-sector-badge {
  font-size: 0.82rem;
  font-weight: 600;
  color: var(--k-info);
}
.public-hint {
  font-size: 0.75rem;
  color: var(--k-text-muted);
  margin: 0.2rem 0 0;
}

/* ——— Reviews section ——— */
.firm-reviews-wrapper {
  margin-top: 1rem;
  border-top: 1px solid var(--k-border-soft);
  padding-top: 0.75rem;
}

.reviews-stats {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.75rem;
}

.reviews-avg {
  font-size: 1rem;
  color: var(--k-rating);
  letter-spacing: 1px;
  font-weight: 600;
}

.reviews-count {
  font-size: 0.82rem;
  /* RU: Было `#777` — 4.5:1 по белому впритык, а по светлой подложке карточки уже
         ниже нормы. Токен проверяется `npm run check:contrast` в обеих темах.
     EN: This was `#777` — 4.5:1 against white by a hair, and already below the threshold
         against the card's light surface. The token is checked by `npm run check:contrast`
         in both themes. */
  color: var(--k-text-muted);
}

.reviews-empty {
  font-size: 0.85rem;
  /* RU: Не `--k-text-subtle`: это единственный призыв в пустом блоке, а не подпись. / EN: Not `--k-text-subtle`: this is the only call to action in an empty block, not a label. */
  color: var(--k-text-muted);
  font-style: italic;
}

.review-form {
  background: var(--k-surface-3);
  border-radius: 10px;
  padding: 0.75rem;
  margin-bottom: 0.75rem;
}

.review-form-title {
  margin: 0 0 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--k-text);
}

.stars-picker {
  display: flex;
  gap: 2px;
  margin-bottom: 0.5rem;
}

.star-btn {
  background: none;
  border: 0;
  font-size: 1.4rem;
  cursor: pointer;
  color: var(--k-border-strong);
  padding: 2px 2px;
  transition: color 0.1s, transform 0.1s;
}

.star-btn.is-active {
  color: var(--k-rating);
}

.star-btn:hover {
  transform: scale(1.15);
}

.review-textarea {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid var(--k-border);
  border-radius: 8px;
  font: inherit;
  font-size: 0.85rem;
  resize: vertical;
  box-sizing: border-box;
}

.review-form-actions {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 0.4rem;
}

.review-char-count {
  font-size: 0.75rem;
  color: var(--k-text-subtle);
}

.review-submit-btn {
  padding: 0.45rem 1rem;
  border: 0;
  border-radius: 8px;
  background: var(--k-brand);
  color: var(--k-text-on-accent);
  font: inherit;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.15s;
}

.review-submit-btn:hover { background: var(--k-brand-hover); }
.review-submit-btn:disabled { opacity: 0.5; cursor: default; }

.review-msg {
  margin: 0.4rem 0 0;
  font-size: 0.82rem;
}

/* RU: Статусные цвета формы — из общего набора, а не свои. Свои означали бы, что
       «успех» в отзывах и «успех» в остальном интерфейсе — разного оттенка, и что
       проверка контраста их не видит.
   EN: The form's status colours come from the shared set rather than being bespoke.
       Bespoke would mean that "success" in reviews and "success" elsewhere in the
       interface are different shades, and that the contrast check does not see them. */
.review-msg.success { color: var(--k-ok); }
.review-msg.error   { color: var(--k-danger); }

.reviews-list {
  margin-top: 0.5rem;
}

.review-card {
  padding: 0.6rem 0;
  /* RU: Было `#f0f0f0` — на тёмном фоне такая линия светится ярче текста и
         разрезает список. Токен даёт мягкую границу в обеих темах.
     EN: This was `#f0f0f0` — on a dark ground such a line glows brighter than the text and
         slices the list apart. The token gives a soft border in both themes. */
  border-bottom: 1px solid var(--k-border-soft);
}

.review-card:last-child { border-bottom: 0; }

.review-card-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.25rem;
}

.review-card-stars {
  /* RU: Через токен, а не своим цветом. Проверка показала, что «одинаково читается
         на обоих фонах» было неправдой: по светлому выходило 2.15:1 — ниже порога и
         для текста, и для крупного знака. Токен разводит светлую и тёмную темы.
     EN: Through the token rather than its own colour. Measurement showed that "reads
         equally on both grounds" was untrue: against light it came out at 2.15:1 — below
         the threshold for both text and large glyphs. The token separates light from dark. */
  color: var(--k-rating);
  letter-spacing: 2px;
  font-size: 0.9rem;
}

.review-card-date {
  font-size: 0.75rem;
  /* RU: Был `--k-text-subtle`, но по его же контракту 3:1 разрешён только крупным
         подписям и неактивным состояниям, где смысл продублирован. Дата — мелкий
         текст, который больше нигде не повторяется: ей нужен полный порог 4.5:1.
     EN: This was `--k-text-subtle`, but by that token's own contract 3:1 is allowed only
         for large labels and inactive states where the meaning is repeated elsewhere. A
         date is small text repeated nowhere else: it needs the full 4.5:1. */
  color: var(--k-text-muted);
}

/* RU: Имя автора — вторичная строка над текстом: кто написал, важно, но не важнее
       того, что написано.
   EN: The author's name is a secondary line above the text: who wrote it matters, but not
       more than what was written. */
.review-card-author {
  display: block;
  font-size: 0.78rem;
  color: var(--k-text-muted);
  margin-bottom: 0.2rem;
}

.review-card-text {
  margin: 0;
  font-size: 0.85rem;
  /* RU: Было `#444`. В тёмной теме это давало серый текст на почти чёрном фоне —
         блок отзывов читался хуже всего остального на карточке.
     EN: This was `#444`. In the dark theme it produced grey text on near-black — the
         reviews block read worse than anything else on the card. */
  color: var(--k-text-muted);
  line-height: 1.45;
}

/* RU: Ответ организации — с отступом и полосой слева, как цитата в переписке:
       видно, что это другой голос, и видно, к какому отзыву он относится.
   EN: The organisation's reply is indented with a rule on the left, like a quote in a
       thread: it is visibly another voice, and visibly attached to that review. */
.review-card-reply {
  margin: 0.4rem 0 0 0.6rem;
  padding-left: 0.6rem;
  border-left: 2px solid var(--k-border);
}

.review-card-reply-label {
  display: block;
  font-size: 0.72rem;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  /* RU: «Ответ организации» — единственное, что отличает голос владельца от голоса
         посетителя. Ослабить эту подпись значит стереть различие.
     EN: "Ответ организации" is the only thing separating the owner's voice from a
         visitor's. Weakening this label erases the distinction. */
  color: var(--k-text-muted);
  margin-bottom: 0.15rem;
}

.review-card-reply-text {
  margin: 0;
  font-size: 0.82rem;
  color: var(--k-text-muted);
  line-height: 1.45;
}

.reviews-more {
  font-size: 0.8rem;
  /* RU: Было `#888` — 3.5:1 по белому, ниже нормы 4.5:1 для обычного текста. / EN: This was `#888` — 3.5:1 against white, below the 4.5:1 body-text threshold. */
  color: var(--k-text-muted);
  text-align: center;
  padding: 0.4rem;
}

.reviews-loading {
  font-size: 0.82rem;
  /* RU: Было `#aaa` — 2.3:1 по белому. Текст «Загрузка отзывов…» человек и правда
         почти не видел; из-за сломанных ручек он висел там постоянно.
     EN: This was `#aaa` — 2.3:1 against white. People genuinely could barely see the
         "Загрузка отзывов…" text; because the endpoints were broken it sat there
         permanently. */
  color: var(--k-text-muted);
}

/* ——— Акции организации (P99) ——— */
/* RU: Значок акции в строке выдачи. Цвет не из статусной шкалы: акция это не
       «хорошо» и не «внимание», а предложение — и путать её с «открыто сейчас»
       нельзя, они стоят рядом.
   EN: A promotion badge in the result row. The colour is not from the status scale: a promotion is
       neither "good" nor "warning" but an offer — and it must not be confused with "open now",
       which stands beside it. */
.promo-badge {
  display: inline-block;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--k-rating);
  border-radius: var(--k-r-1);
  color: var(--k-rating);
  font-size: 0.72rem;
  font-weight: 600;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.firm-promotions {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.firm-promo-item {
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--k-border-soft);
  border-radius: var(--k-r-2);
  background: var(--k-surface-3);
}

.firm-promo-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  font-size: 0.9rem;
}

.firm-promo-until {
  font-size: 0.76rem;
  color: var(--k-text-muted);
}

.firm-promo-desc {
  margin: 0.25rem 0 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--k-text-muted);
}

/* ——— Варианты маршрута (P98) ——— */
.routes-coverage {
  margin-top: 0.3rem;
  font-size: 0.76rem;
  color: var(--k-text-muted);
}

.routes-variants {
  margin-top: 0.55rem;
}

.routes-variants-title {
  font-size: 0.74rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--k-text-subtle);
  margin-bottom: 0.3rem;
}

.routes-variants-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.route-variant {
  display: grid;
  /* RU: Три колонки фиксированной доли, а не flex: числа обязаны стоять друг под
         другом, иначе сравнение превращается в чтение. Ради этого же `tabular-nums`.
     EN: Three proportional columns rather than flex: the numbers must line up one under another,
         otherwise comparison turns into reading. `tabular-nums` serves the same end. */
  grid-template-columns: 4.5rem 4.5rem 1fr;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  padding: 0.45rem 0.6rem;
  border: 1px solid var(--k-border);
  border-radius: var(--k-r-2);
  background: var(--k-surface);
  color: var(--k-text);
  text-align: left;
  cursor: pointer;
  min-height: var(--k-tap-min);
  font-variant-numeric: tabular-nums;
}

.route-variant:hover {
  border-color: var(--k-border-strong);
  background: var(--k-surface-2);
}

/* RU: Текущий вариант — акцентная граница, а не заливка: он один из равных, а не
       главная кнопка панели.
   EN: The current variant gets an accent border rather than a fill: it is one among equals, not
       the panel's primary button. */
.route-variant.is-current {
  border-color: var(--k-brand);
}

.route-variant-time {
  font-weight: 600;
  font-size: 0.9rem;
}

.route-variant-dist {
  font-size: 0.82rem;
  color: var(--k-text-muted);
}

.route-variant-badge {
  font-size: 0.74rem;
  color: var(--k-brand-ink);
}

.route-variant-delta {
  font-size: 0.8rem;
  color: var(--k-text-muted);
}

.routes-via {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.55rem;
}

.routes-via-text {
  flex: 1 1 12rem;
  font-size: 0.76rem;
  line-height: 1.35;
  color: var(--k-text-muted);
}

/* ——— «Спроси город» (P97) ——— */
/* RU: Предложение под строкой поиска. Появляется только на запрос, похожий на вопрос,
       поэтому места под него постоянно не резервируем.
   EN: The offer under the search field. It appears only for a question-like query, so no space
       is permanently reserved for it. */
.ask-offer {
  padding: 0 0 0.45rem;
}

.ask-offer-btn {
  width: 100%;
  padding: 0.5rem 0.75rem;
  border: 1px dashed var(--k-border-strong);
  border-radius: var(--k-r-2);
  background: var(--k-surface-3);
  color: var(--k-text);
  font-size: 0.85rem;
  cursor: pointer;
  min-height: var(--k-tap-min);
}

.ask-offer-btn:hover {
  background: var(--k-surface-2);
  border-style: solid;
}

/* RU: Ответ — отдельный блок над списком. Заливка и рамка нужны именно здесь:
       ответ это утверждение о городе, а карточки ниже — результат отбора, и читать их
       как одно целое неверно.
   EN: The answer is a separate block above the list. The fill and border belong here: the
       answer is a statement about the city while the cards below are a selection result, and
       reading them as one thing would be wrong. */
.ask-answer {
  padding: 0.7rem 0.8rem;
  margin-bottom: 0.6rem;
  border: 1px solid var(--k-border);
  border-radius: var(--k-r-2);
  background: var(--k-surface-3);
}

/* RU: Пометка ограниченного режима — над текстом ответа: она меняет то, как ответ
       следует читать, и внизу её прочли бы уже после того, как поверили написанному.
   EN: The limited-mode badge sits above the answer text: it changes how the answer should be
       read, and at the bottom it would be read only after the words had been believed. */
.ask-badge {
  margin: 0 0 0.4rem;
  font-size: 0.76rem;
  line-height: 1.35;
  color: var(--k-warn);
}

.ask-text {
  margin: 0;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--k-text);
}

/* RU: Названная организация внутри ответа. Выглядит как ссылка в тексте, а не как
       кнопка: это часть предложения, и кнопочная рамка разорвала бы фразу.
   EN: An organisation named inside the answer. It looks like a link in running text rather than
       a button: it is part of the sentence, and a button's border would break the phrase. */
.ask-cite {
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  color: var(--k-brand-ink);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}

.ask-cite:hover,
.ask-cite:focus-visible {
  color: var(--k-brand);
  text-decoration-thickness: 2px;
}

/* RU: Оговорки. Ниже ответа и тише его: они уточняют, а не отменяют.
   EN: The caveats. Below the answer and quieter than it: they qualify rather than revoke. */
.ask-caveats {
  margin: 0.5rem 0 0;
  padding-left: 1.1rem;
  font-size: 0.78rem;
  line-height: 1.4;
  color: var(--k-text-muted);
}

.ask-caveats li + li {
  margin-top: 0.2rem;
}

/* ——— Подборки города (P96) ——— */
.collections-strip {
  padding: 0 0 0.5rem;
}

.collections-title {
  margin: 0 0 0.4rem;
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--k-text-muted);
}

.collections-row {
  display: flex;
  gap: 0.4rem;
  /* RU: Прокрутка, а не перенос. Подборок бывает шесть, панель на телефоне узкая, и
         перенос дал бы три ряда чипов над рубриками — блок-украшение занял бы
         больше места, чем содержание главной.
     EN: Scrolling rather than wrapping. There can be six collections, the panel on a phone is
         narrow, and wrapping would give three rows of chips above the rubrics — a decorative
         block taking more space than the home screen's content. */
  overflow-x: auto;
  padding-bottom: 0.2rem;
  /* RU: Полоса прокрутки в панели мешает; жест остаётся. / EN: The scrollbar clutters the panel; the gesture remains. */
  scrollbar-width: none;
}

.collections-row::-webkit-scrollbar { display: none; }

.collection-chip {
  flex: 0 0 auto;
  padding: 0.45rem 0.75rem;
  border: 1px solid var(--k-border);
  border-radius: 999px;
  background: var(--k-surface);
  color: var(--k-text);
  font-size: 0.85rem;
  cursor: pointer;
  /* RU: Тач-цель из общего токена: чип в прокручиваемой полосе легко промахнуться. / EN: The tap target comes from the shared token: a chip in a scrolling strip is easy to miss. */
  min-height: var(--k-tap-min);
  white-space: nowrap;
}

.collection-chip:hover {
  border-color: var(--k-border-strong);
  background: var(--k-surface-2);
}

/* RU: Избранное — своё, а не редакционное. Акцентная граница отделяет его от
       тематических подборок, при этом чип остаётся чипом: заливка акцентом сделала бы
       его главной кнопкой экрана, чем он не является.
   EN: Favourites are one's own, not editorial. The accent border sets them apart from the themed
       collections while the chip stays a chip: an accent fill would make it the screen's primary
       button, which it is not. */
.collection-chip.is-favorites {
  border-color: var(--k-brand);
  color: var(--k-brand-ink);
}

/* RU: «Поделиться» — действие, а не переход, поэтому визуально тише соседей.
   EN: "Share" is an action rather than a navigation, hence visually quieter than its neighbours. */
.collection-chip.is-share {
  color: var(--k-text-muted);
  border-style: dashed;
}

/* ——— Строка о закрытых сейчас (P96) ——— */
/* RU: Появляется под выдачей, когда включён фильтр «открыто сейчас» и часть
       организаций из-за него не попала в список, но осталась на карте приглушённой.
       Без пояснения серые точки читаются как сбой отрисовки.
   EN: Appears under the results when the "open now" filter is on and some organisations fell
       out of the list because of it but stayed on the map, dimmed. Without the explanation the
       grey dots read as a rendering fault. */
.closed-now-note {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding: 0.55rem 0.75rem;
  margin-top: 0.35rem;
  border-top: 1px solid var(--k-border-soft);
  /* RU: Своя подложка, отличная от карточек выдачи: строка относится не к списку, а
         к карте, и не должна выглядеть ещё одним результатом.
     EN: Its own ground, distinct from the result cards: the line concerns the map rather than
         the list and must not look like one more result. */
  background: var(--k-surface-3);
}

.closed-now-text {
  flex: 1 1 12rem;
  font-size: 0.82rem;
  color: var(--k-text-muted);
  line-height: 1.4;
}

.closed-now-show {
  /* RU: Кнопка не растягивается: действие второстепенное, и полная ширина сделала бы
         его главным на экране.
     EN: The button does not stretch: the action is secondary, and full width would make it the
         main thing on screen. */
  flex: 0 0 auto;
  font-size: 0.8rem;
  /* RU: Минимальная цель нажатия — общий токен, а не своё число. / EN: The minimum tap target comes from the shared token, not a local number. */
  min-height: var(--k-tap-min);
}

/* ——— Filter bar ——— */
/* RU: Ф6.7 (14.09.2026): серые подложки, тексты, рамки и кольца фокуса этого раздела и карточки (часы, раздел госучреждения,
       звёзды, тур) переведены на токены --k-*: литералы #fafbfa, #444, #888, #aaa, #26734a оставались светлыми в ночной теме.
       Цвета данных (легенда пробок, соцсети, геоточка) и всегда тёмные панели (линейка, лайтбокс) остались литералами — они не
       зависят от темы; их число ведёт базовая линия scripts/check-theme-tokens.baseline.json.
   EN: Ф6.7 (2026-09-14): grey backings, texts, borders and focus rings of this section and the card (hours, public institution section,
       stars, tour) moved onto --k-* tokens: the #fafbfa, #444, #888, #aaa, #26734a literals stayed light in the night theme. Data
       colours (traffic legend, socials, location dot) and always-dark panels (ruler, lightbox) remain literals — they do not depend
       on the theme; their count is tracked by the scripts/check-theme-tokens.baseline.json baseline. */
.filter-bar {
  padding: 0.6rem 0.75rem;
  border-bottom: 1px solid var(--k-border-soft);
  background: var(--k-surface-3);
}

.filter-row {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  flex-wrap: wrap;
}

.filter-row-sub {
  margin-top: 0.4rem;
  font-size: 0.8rem;
}

.filter-select {
  flex: 1;
  min-width: 120px;
  padding: 6px 8px;
  border: 1px solid var(--k-border);
  border-radius: 6px;
  background: var(--k-surface);
  font-size: 0.85rem;
  color: var(--k-text);
  cursor: pointer;
}

.filter-select:focus {
  border-color: var(--k-brand);
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--k-brand) 15%, transparent);
}

/* I4.3 — distance range slider */
.filter-dist-wrap {
  flex: 1;
  min-width: 140px;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}
.filter-dist-label {
  font-size: 0.75rem;
  color: var(--ink-soft);
  white-space: nowrap;
}
.filter-range {
  width: 100%;
  height: 6px;
  -webkit-appearance: none;
  appearance: none;
  background: var(--k-border);
  border-radius: 3px;
  outline: none;
  cursor: pointer;
}
/* RU: Ф6.9. Ползунок сбрасывает контур ради своей формы (дорожка 6 px, контур вокруг неё
       выглядел рамкой у полоски), но замены не было, а специфичность у `.filter-range`
       (0,1,0) равна глобальному правилу из kartivi.css — и public.css подключается ПОЗЖЕ,
       поэтому сброс выигрывал по порядку. Радиус фильтра управляется только с клавиатуры
       (стрелками), и невидимый фокус означал, что найти его нельзя вовсе.
       Контур на бегунке, а не на дорожке: смещение 3 px выводит его за пределы 18-пиксельного
       кружка, иначе он сливается с самим бегунком.
   EN: Ф6.9. The slider resets the outline for the sake of its shape (a 6 px track, with an
       outline around it reading as a frame on a strip), but there was no replacement, and
       `.filter-range` specificity (0,1,0) equals the global rule from kartivi.css — and public.css
       is linked LATER, so the reset won on order. The distance filter is operated from the
       keyboard only (arrow keys), and invisible focus meant it could not be found at all.
       The outline goes on the thumb rather than the track: a 3 px offset takes it outside the
       18 px circle, otherwise it merges with the thumb itself. */
.filter-range:focus-visible {
  outline: 2px solid var(--accent, var(--k-brand));
  outline-offset: 3px;
  border-radius: 3px;
}

.filter-range::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent, var(--k-brand-hover));
  border: 2px solid var(--k-surface);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
.filter-range::-moz-range-thumb {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  background: var(--accent, var(--k-brand-hover));
  border: 2px solid var(--k-surface);
  box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}

/* Search results summary bar */
.results-summary {
  padding: 0.5rem 0.75rem;
  margin-bottom: 0.4rem;
  font-size: 0.82rem;
  color: var(--ink-soft);
  background: var(--k-surface-3);
  border-radius: 0.5rem;
  border: 1px solid var(--line);
}

.filter-toggle {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  font-size: 0.85rem;
  white-space: nowrap;
}

.toggle-label {
  color: var(--k-text-muted);
}

.filter-hint {
  color: var(--k-text-muted);
  font-size: 0.78rem;
  margin-left: auto;
}

/* ——— Searchable rubric dropdown ——— */
.filter-dropdown-wrap {
  position: relative;
  flex: 1;
  min-width: 140px;
}

.filter-dropdown-btn {
  width: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  padding: 6px 8px;
  border: 1px solid var(--k-border);
  border-radius: 6px;
  background: var(--k-surface);
  font-size: 0.85rem;
  color: var(--k-text);
  cursor: pointer;
  text-align: left;
}
.filter-dropdown-btn:hover { border-color: var(--k-border-strong); }
.filter-dropdown-btn:focus {
  border-color: var(--k-brand);
  outline: none;
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--k-brand) 15%, transparent);
}

.filter-dropdown-label {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.filter-dropdown-arrow {
  font-size: 0.7rem;
  color: var(--k-text-muted);
  flex-shrink: 0;
}

.filter-dropdown-panel {
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  margin-top: 4px;
  background: var(--k-surface);
  border: 1px solid var(--k-border);
  border-radius: 8px;
  box-shadow: 0 4px 16px rgba(0,0,0,0.12);
  z-index: 100;
  max-height: 300px;
  display: flex;
  flex-direction: column;
}

.filter-dropdown-search {
  padding: 8px 10px;
  border: none;
  border-bottom: 1px solid var(--k-border-soft);
  font-size: 0.85rem;
  outline: none;
  border-radius: 8px 8px 0 0;
  background: var(--k-surface-3);
}
/* RU: Ф6.9. Смена подложки на фокусе (правило ниже) — это НЕ признак фокуса: разница между
       #fafbfa и поверхностью почти неразличима, а на контрастной теме исчезает совсем. Контур
       здесь тоже сброшен без замены (тот же порядок подключения, что у .filter-range), поэтому
       даём внутреннюю тень: поле прижато к верхнему краю выпадающего списка, и внешний контур
       обрезался бы его границей.
   EN: Ф6.9. Swapping the ground on focus (the rule below) is NOT a focus indicator: the difference
       between #fafbfa and the surface is barely distinguishable and vanishes entirely on the
       contrast theme. The outline here is also reset with no replacement (same link order as
       .filter-range), so an inset shadow is used: the field is flush with the dropdown's top edge
       and an outer outline would be clipped by its border. */
.filter-dropdown-search:focus-visible {
  box-shadow: inset 0 0 0 2px var(--accent, var(--k-brand));
}

.filter-dropdown-search:focus { background: var(--k-surface); }

.filter-dropdown-list {
  overflow-y: auto;
  flex: 1;
}

.filter-dropdown-item {
  padding: 7px 10px;
  font-size: 0.85rem;
  cursor: pointer;
  color: var(--k-text);
  border-bottom: 1px solid var(--k-border-soft);
  transition: background 0.1s;
}
.filter-dropdown-item:hover { background: var(--k-surface-3); }
.filter-dropdown-item.is-selected {
  background: var(--k-brand-soft);
  color: var(--k-brand);
  font-weight: 600;
}
.filter-dropdown-child {
  padding-left: 24px;
  font-size: 0.8rem;
  color: var(--k-text-muted);
}

/* Dark mode overrides for filter dropdown */
.is-dark .filter-bar {
  background: var(--k-surface);
  border-bottom-color: var(--k-border);
}
.is-dark .filter-dropdown-btn,
.is-dark .filter-select {
  background: var(--k-surface);
  color: var(--k-text);
  border-color: var(--k-border);
}
.is-dark .filter-dropdown-panel {
  background: var(--k-surface);
  border-color: var(--k-border);
}
.is-dark .filter-dropdown-search {
  background: var(--k-surface-2);
  color: var(--k-text);
  border-bottom-color: var(--k-border);
}
.is-dark .filter-dropdown-item {
  color: var(--k-text);
  border-bottom-color: var(--k-border);
}
.is-dark .filter-dropdown-item:hover { background: var(--k-surface-2); }
.is-dark .filter-dropdown-item.is-selected {
  background: var(--k-brand-soft);
  color: var(--k-brand-ink);
}
.is-dark .toggle-label { color: var(--k-text-muted); }
.is-dark .filter-hint { color: var(--k-text-subtle); }

/* ══════════════════════════════════════════
   U1.1 — Onboarding tour (3-step overlay)
   ══════════════════════════════════════════ */
.tour-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 9998;
  backdrop-filter: blur(2px);
  animation: tour-fade-in 200ms ease;
}
@keyframes tour-fade-in { from { opacity: 0; } to { opacity: 1; } }

.tour-highlight {
  position: relative;
  z-index: 9999 !important;
  box-shadow: 0 0 0 4px var(--accent, #26734a), 0 0 24px color-mix(in srgb, var(--k-brand) 35%, transparent) !important;
  border-radius: 8px !important;
  transition: box-shadow 300ms ease;
}

.tour-tooltip {
  position: fixed;
  z-index: 10000;
  width: min(320px, calc(100vw - 32px));
  background: var(--k-surface);
  border-radius: 16px;
  padding: 1.5rem 1.4rem 1.2rem;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.2), 0 0 0 1px rgba(0, 0, 0, 0.06);
  animation: tour-slide-up 250ms ease;
  font-family: inherit;
}
@keyframes tour-slide-up {
  from { opacity: 0; transform: translateY(12px); }
  to { opacity: 1; transform: translateY(0); }
}
.tour-step-indicator {
  font-size: 0.72rem;
  color: var(--ink-soft);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.5rem;
}
.tour-step-icon {
  font-size: 2rem;
  margin-bottom: 0.35rem;
}
.tour-step-title {
  font-size: 1.15rem;
  font-weight: 700;
  color: var(--ink);
  margin: 0 0 0.45rem;
}
.tour-step-body {
  font-size: 0.9rem;
  color: var(--ink-soft);
  line-height: 1.5;
  margin: 0 0 0.6rem;
}
.tour-step-hint {
  font-size: 0.82rem;
  color: var(--accent);
  background: var(--k-surface-3);
  padding: 0.45rem 0.7rem;
  border-radius: 0.5rem;
  margin: 0 0 0.8rem;
}
.tour-actions {
  display: flex;
  justify-content: space-between;
  gap: 0.5rem;
}
.tour-btn {
  padding: 0.55rem 1.2rem;
  border-radius: 10px;
  font: inherit;
  font-weight: 600;
  font-size: 0.88rem;
  cursor: pointer;
  border: none;
  transition: background 160ms ease, transform 100ms ease;
}
.tour-btn:hover { transform: scale(1.03); }
.tour-btn-next,
.tour-btn-done {
  background: var(--accent, var(--k-brand-hover));
  color: var(--k-text-on-accent);
  margin-left: auto;
}
.tour-btn-prev {
  background: var(--k-surface-2);
  color: var(--ink);
}
.tour-close {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  border: none;
  background: none;
  font-size: 1.1rem;
  color: var(--ink-soft);
  cursor: pointer;
  padding: 0.25rem 0.4rem;
  border-radius: 0.35rem;
}
.tour-close:hover { background: var(--k-surface-2); }

/* Dark mode */
.is-dark .tour-tooltip {
  background: var(--k-surface);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(255, 255, 255, 0.06);
}
.is-dark .tour-step-title { color: #e8ecef; }
.is-dark .tour-step-body { color: #a0b0c0; }
.is-dark .tour-step-hint { background: var(--k-brand-soft); color: var(--k-brand-ink); }
.is-dark .tour-btn-prev { background: var(--k-surface-2); color: #d0d8e0; }
.is-dark .tour-close:hover { background: var(--k-surface-2); }


/* --- P93: энергоблок карточки здания / building card power block --- */
.building-power {
  margin-top: 0.9rem;
  padding: 0.7rem 0.8rem;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--k-surface-3);
}

.building-power .building-card-section-title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0 0 0.5rem;
}

.building-power-badge {
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  color: var(--k-warn);
  background: var(--k-warn-soft);
  border: 1px solid color-mix(in srgb, var(--k-warn) 35%, var(--k-warn-soft));
  border-radius: 5px;
  padding: 0.1rem 0.35rem;
}

.building-power-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.22rem;
}

.building-power-list li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.8rem;
}

.building-power-list span {
  color: var(--muted, #5a6b62);
}

.building-power-list strong {
  margin-left: auto;
  font-variant-numeric: tabular-nums;
  text-align: right;
}

/* RU: Цвета резерва повторяют точку ТП на карте: карточка и карта должны
       говорить об одном объекте одинаково.
   EN: The reserve colours repeat the substation dot on the map: card and map must
       speak about one object in the same way. */
.building-power-reserve.is-free { color: var(--k-ok); }
.building-power-reserve.is-tight { color: var(--k-warn); }
.building-power-reserve.is-full { color: var(--k-danger); }

.building-power-note {
  margin: 0.55rem 0 0;
  font-size: 0.68rem;
  line-height: 1.4;
  color: var(--muted, #5a6b62);
}


/* --- P93: инструмент «свободная мощность» / free-capacity tool --- */
.capacity-panel {
  position: absolute;
  right: 4rem;
  top: 1rem;
  z-index: 6;
  width: 14rem;
  padding: 0.7rem 0.85rem;
  border-radius: 12px;
  background: var(--k-surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
}

.capacity-panel[hidden] { display: none !important; }

.capacity-panel-title {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted, #5a6b62);
  margin-bottom: 0.45rem;
}

.capacity-field {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.75rem;
  margin-bottom: 0.35rem;
}

.capacity-field input[type="range"] {
  flex: 1;
  min-width: 0;
  accent-color: var(--accent);
}

.capacity-field output {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  min-width: 3.6rem;
  text-align: right;
}

.capacity-panel-hint {
  margin: 0.4rem 0 0;
  font-size: 0.66rem;
  color: var(--muted, #5a6b62);
}

/* RU: HUD результата — внизу по центру, как у линейки: взгляд не должен
       прыгать между панелью параметров и ответом.
   EN: The result HUD sits bottom-centre like the ruler's: the eye should not jump
       between the parameter panel and the answer. */
.capacity-hud {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: 3.2rem;
  z-index: 7;
  max-width: min(26rem, 90vw);
  padding: 0.55rem 0.9rem;
  border-radius: 12px;
  background: rgba(18, 38, 29, 0.92);
  color: #fff;
  font-size: 0.78rem;
  line-height: 1.45;
}

.capacity-hud[hidden] { display: none !important; }

.capacity-hud b { font-variant-numeric: tabular-nums; }

.capacity-hud-note {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.62rem;
  opacity: 0.75;
}


/* --- P93: раскраска зданий и схема сети / building colouring and network scheme --- */
.building-paint-row {
  border-bottom: 0;
  padding-bottom: 0;
  margin-bottom: 0.3rem;
}

.building-paint-note {
  color: var(--muted, #5a6b62);
  line-height: 1.35;
}

/* RU: В режиме схемы гасим и подписи базовой карты — они спорят с подписями
       сетей за то же место, а читать сейчас надо сеть.
   EN: In scheme mode the basemap labels are dimmed too — they compete with the network
       labels for the same space, and right now the network is what must be read. */
.map-stage.is-network-scheme .maplibregl-canvas-container {
  filter: saturate(0.45);
}


/* RU: Кольца в HUD — сетка «радиус → цифры»: колонки выравнивают числа, и
       кольца читаются сверху вниз как ряд, а не как абзац текста.
   EN: The rings in the HUD are a "radius → figures" grid: the columns align the numbers so
       the rings read top-down as a series rather than as a paragraph. */
.capacity-rings {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 0.1rem 0.6rem;
  margin-top: 0.35rem;
  padding-top: 0.35rem;
  border-top: 1px solid rgba(255, 255, 255, 0.18);
  font-size: 0.72rem;
}

.capacity-rings span { opacity: 0.75; }
.capacity-rings b { font-variant-numeric: tabular-nums; font-weight: 600; }


/* --- P93: легенда инженерных слоёв / utility layer legend --- */
.layers-legend {
  margin-top: 0.4rem;
  font-size: 0.75rem;
}

.layers-legend summary {
  cursor: pointer;
  font-size: 0.7rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted, #5a6b62);
  list-style: none;
}

.layers-legend summary::-webkit-details-marker { display: none; }

/* RU: Стрелка своя: стандартный маркер details в этом меню выглядит инородно
       рядом с чекбоксами и пресетами.
   EN: A custom arrow: the stock details marker looks alien in this menu next to the
       checkboxes and presets. */
.layers-legend summary::before {
  content: "▸";
  display: inline-block;
  margin-right: 0.3rem;
  transition: transform 0.12s ease;
}

.layers-legend[open] summary::before { transform: rotate(90deg); }

.layers-legend .legend-row {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.12rem 0;
  color: var(--ink, #1a2e24);
}

.legend-swatch {
  flex: none;
  width: 1.1rem;
  height: 3px;
  border-radius: 2px;
}

/* RU: Пунктир в легенде повторяет пунктир на карте: ВЛ и подземная труба
       рисуются прерывисто, и легенда обязана выглядеть так же.
   EN: The dash in the legend repeats the dash on the map: an overhead line and an
       underground pipe are drawn broken, and the legend must look the same. */
.legend-swatch.is-dashed {
  background-image: repeating-linear-gradient(
    90deg,
    currentColor 0 4px,
    transparent 4px 7px
  );
}

.legend-swatch.is-dot {
  width: 9px;
  height: 9px;
  border-radius: 50%;
}

/*
 * RU: Формы статуса ТП (P94 Ф1). Легенда обязана показывать ровно то, что рисует
 *     карта: кольцо у «впритык», крест у «занята». Если легенда показывает три
 *     кружка одного размера, второй канал для читателя не существует.
 * EN: Substation state shapes (P94 phase 1). The legend must show exactly what the map
 *     draws: a ring for "tight", a cross for "full". If the legend shows three dots of the
 *     same size, the second channel does not exist for the reader.
 */
.legend-swatch.is-ring {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  background: var(--k-surface);
  border: 2px solid currentColor;
}

.legend-swatch.is-dot.is-marked {
  position: relative;
}

.legend-swatch.is-dot.is-marked::after {
  content: "×";
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: #fff;
  font-size: 8px;
  line-height: 1;
}

/*
 * RU: Границы охранных коридоров. Рисунок штриха тот же, что в
 *     ZONE_EDGE_DASH боевого слоя: сплошной штрих — ЛЭП, штрих-точка — труба,
 *     точка — вода. Числа продублированы, потому что CSS не умеет читать JS;
 *     расхождение ловит e2e-проверка cvd.zone_dash_distinct.
 * EN: Protection-corridor boundaries. The dash pattern matches ZONE_EDGE_DASH in the
 *     production layer: a plain dash is power, dash-dot is a pipeline, dotted is water. The
 *     numbers are duplicated because CSS cannot read JS; a drift is caught by the
 *     cvd.zone_dash_distinct e2e check.
 */
.legend-swatch.is-edge-power {
  background-image: repeating-linear-gradient(90deg, currentColor 0 4px, transparent 4px 6px);
}

.legend-swatch.is-edge-pipe {
  background-image: repeating-linear-gradient(
    90deg,
    currentColor 0 6px,
    transparent 6px 8px,
    currentColor 8px 9px,
    transparent 9px 11px
  );
}

.legend-swatch.is-edge-water {
  background-image: repeating-linear-gradient(90deg, currentColor 0 1px, transparent 1px 3px);
}

/*
 * RU: Плашка отладочного режима симуляции дальтонизма (P94 Ф1, `?cvd=`).
 *     Тёмная и намеренно заметная: режим, оставшийся в ссылке, иначе выглядит
 *     как сломанная карта. Поверх всего, но ниже модалок — она не должна
 *     перехватывать клики по диалогам.
 * EN: The badge of the CVD simulation debug mode (P94 phase 1, `?cvd=`). Dark and
 *     deliberately conspicuous: a mode left behind in a link otherwise looks like a broken
 *     map. Above everything but below modals — it must not intercept clicks on dialogs.
 */
.cvd-badge {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 900;
  padding: 0.4rem 0.9rem;
  border: 0;
  border-radius: 999px;
  background: #2b2b2b;
  color: #fff;
  font: inherit;
  font-size: 0.78rem;
  cursor: pointer;
  box-shadow: 0 2px 10px rgb(0 0 0 / 30%);
}

/*
 * RU: Плашка достоверности на полотне карты (P94 Ф2). Левый нижний угол — там
 *     же, где атрибуция: место, куда смотрят, когда спрашивают «откуда данные».
 *     `pointer-events: none` обязателен: плашка перекрывает карту, и клик по
 *     зданию под ней должен доходить до карты, а не гаситься об текст.
 * EN: The provenance plate on the map canvas (P94 phase 2). Bottom-left — the same place as
 *     the attribution: where people look when asking "where is this from". `pointer-events:
 *     none` is mandatory: the plate overlaps the map, and a click on a building beneath it
 *     must reach the map rather than die on the text.
 */
.model-plate {
  position: absolute;
  left: 0.6rem;
  bottom: 2.6rem;
  z-index: 6;
  max-width: min(23rem, calc(100% - 1.2rem));
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--line, #d9d4cc);
  border-radius: 0.5rem;
  background: rgb(255 255 255 / 94%);
  color: var(--ink-soft, #5b6b7a);
  font-size: 0.72rem;
  line-height: 1.35;
  box-shadow: 0 2px 8px rgb(0 0 0 / 8%);
  pointer-events: none;
}

.model-plate[hidden] {
  display: none;
}

.model-plate b {
  display: block;
  color: var(--warn, #c47b00);
  font-size: 0.75rem;
}

.model-plate span {
  display: block;
}

.model-plate hr {
  margin: 0.4rem 0;
  border: 0;
  border-top: 1px solid var(--line, #d9d4cc);
}

/* RU: Строка легенды достоверности: квадрат цвета + текст в одну строку.
   EN: A trust-legend row: a colour chip plus text on one line. */
.model-plate-row {
  display: flex;
  align-items: center;
  gap: 0.35rem;
}

.model-plate-row i {
  flex: none;
  width: 0.6rem;
  height: 0.6rem;
  border: 1px solid rgb(0 0 0 / 20%);
  border-radius: 2px;
}

/*
 * RU: Узкий экран. На 390 px карта занимает около четверти высоты — остальное
 *     забирает шторка со списком. Плашка на пять строк прозы съедала половину
 *     оставшегося и налезала на строку атрибуции: замечено на первом же мобильном
 *     снимке, до него плашку на телефоне никто не видел.
 *
 *     Прячем объяснение, оставляем заголовок: «Сеть 0.4/10 кВ — расчётная модель»
 *     несёт всё существенное, а «построена по геометрии города…» — это подробность
 *     для того, кто уже заинтересовался, и на телефоне ей не место поверх карты.
 *
 *     Строки легенды достоверности (`.model-plate-row`) НЕ прячем: без них три
 *     цвета зданий не значат ничего, и режим превращается в раскраску ради
 *     раскраски.
 *
 * EN: A narrow screen. At 390 px the map takes about a quarter of the height — the rest goes
 *     to the results sheet. A five-line prose plate ate half of what was left and overlapped
 *     the attribution line: spotted on the very first mobile screenshot, before which nobody
 *     had seen the plate on a phone.
 *
 *     The explanation is hidden and the heading kept: "Сеть 0.4/10 кВ — расчётная модель"
 *     carries everything essential, while "built from the city's geometry…" is a detail for
 *     someone already interested, and on a phone it has no business covering the map.
 *
 *     The trust-legend rows (`.model-plate-row`) are NOT hidden: without them the three
 *     building colours mean nothing and the mode becomes colouring for its own sake.
 */
@media (max-width: 720px) {
  .model-plate {
    bottom: 5.4rem;
    font-size: 0.68rem;
    max-width: calc(100% - 4.2rem);
    padding: 0.35rem 0.55rem;
  }

  .model-plate span:not(.model-plate-row) {
    display: none;
  }

  .model-plate hr {
    margin: 0.25rem 0;
  }

  /* RU: Плашка покрытия — исключение: она объясняет пустую карту, и без текста
         пустая карта остаётся необъяснённой. Ужимаем, но не режем.
     EN: The coverage plate is the exception: it explains an empty map, and without its text an
         empty map stays unexplained. Compressed, not cut. */
  .coverage-plate span:not(.model-plate-row) {
    display: block;
  }

  .coverage-plate {
    top: 0.5rem;
    max-width: calc(100% - 4.2rem);
  }
}

/*
 * RU: HUD бюджета отрисовки (P94 Ф5, `?debug=layers`). Моноширинный и тёмный:
 *     это диагностика для того, кто правит пороги LOD, а не элемент интерфейса.
 *     `pointer-events: none` — HUD висит поверх карты и не должен ловить клики.
 * EN: The draw-budget HUD (P94 phase 5, `?debug=layers`). Monospace and dark: a diagnostic
 *     for whoever edits the LOD thresholds, not a piece of the interface. `pointer-events:
 *     none` — the HUD floats over the map and must not swallow clicks.
 */
.layer-budget-hud {
  position: fixed;
  right: 0.6rem;
  top: 0.6rem;
  z-index: 900;
  padding: 0.3rem 0.6rem;
  border-radius: 0.35rem;
  background: rgb(20 20 20 / 88%);
  color: #e8e8e8;
  font: 11px/1.4 ui-monospace, SFMono-Regular, Menlo, monospace;
  pointer-events: none;
  max-width: 60vw;
}

/* RU: Скрытая строка легенды — именно display:none, чтобы не оставлять отступ.
   EN: A hidden legend row uses display:none so it leaves no gap behind. */
.layers-legend .legend-row[hidden] {
  display: none;
}

/*
 * RU: Плашка покрытия данными (P94 Ф7). Наследует оформление от `.model-plate`,
 *     но стоит выше по экрану: обе одновременно не появляются (у города без
 *     данных нет и расчётной сети), однако при смене города они могут пересечься
 *     на кадр, и наложение выглядело бы как сбой.
 *
 *     `pointer-events: auto` в отличие от `.model-plate`: здесь есть ссылки на
 *     города с данными, и по ним надо иметь возможность кликнуть.
 *
 * EN: The data-coverage plate (P94 phase 7). It inherits its look from `.model-plate` but sits
 *     higher on screen: the two never appear together (a city with no data has no computed
 *     network either), yet on a city switch they can overlap for one frame, and the overlap
 *     would read as a glitch.
 *
 *     `pointer-events: auto`, unlike `.model-plate`: this one carries links to cities that do
 *     have data, and they must be clickable.
 */
.coverage-plate {
  bottom: auto;
  top: 0.6rem;
  max-width: min(26rem, calc(100% - 1.2rem));
  pointer-events: auto;
}

.coverage-plate a {
  color: var(--accent, #1b6ef3);
  text-decoration: underline;
}

/*
 * RU: Плашки достоверности и покрытия в тёмной теме (P94).
 *
 *     Без этого блока они остаются белыми прямоугольниками поверх ночной карты:
 *     читаемо, но выглядит как чужой элемент, приклеенный сверху. Замечено на
 *     первом же снимке ночного режима — до него плашки просто никто не видел
 *     ночью, потому что и ночную палитру, и сами плашки добавил P94.
 *
 *     Цвета взяты из ночной части токенов, а не подобраны на глаз: плашка должна
 *     сидеть в той же гамме, что боковая панель, иначе она читается как ошибка
 *     отрисовки.
 *
 * EN: The provenance and coverage plates in the dark theme (P94).
 *
 *     Without this block they stay white rectangles over the night map: readable, but looking
 *     like a foreign element glued on top. Spotted on the very first night-mode screenshot —
 *     before that nobody had seen the plates at night, because P94 added both the night
 *     palette and the plates themselves.
 *
 *     The colours come from the tokens' dark side rather than being eyeballed: a plate must
 *     sit in the same range as the sidebar, or it reads as a rendering fault.
 */
.is-dark .model-plate {
  background: rgb(24 28 33 / 94%);
  border-color: rgb(255 255 255 / 14%);
  color: rgb(226 232 238 / 82%);
  box-shadow: 0 2px 10px rgb(0 0 0 / 45%);
}

/* RU: Заголовок остаётся предупреждающим, но приглушённее — на тёмном тот же
       оранжевый жжёт глаз.
   EN: The heading stays a warning but is muted — on dark the same orange burns. */
.is-dark .model-plate b {
  color: #e0a020;
}

.is-dark .model-plate hr {
  border-top-color: rgb(255 255 255 / 14%);
}

.is-dark .model-plate-row i {
  border-color: rgb(255 255 255 / 25%);
}

.is-dark .coverage-plate a {
  color: #7fb8f5;
}

/* RU: HUD бюджета на тёмном уже тёмный — ему нужна только рамка, чтобы не
       сливаться с ночной картой.
   EN: The budget HUD is already dark; it only needs a border so as not to merge with the
       night map. */
.is-dark .layer-budget-hud {
  border: 1px solid rgb(255 255 255 / 14%);
}

/* RU: Закрывающая скобка выше была потеряна, и это ломало НЕ одно правило, а весь хвост
       файла: по правилам вложенности CSS всё, что идёт дальше, становилось потомком
       `.is-dark .layer-budget-hud` — то есть применялось только внутри HUD бюджета на
       тёмной теме, а фактически никогда. Вместе с этим блоком были потеряны ещё две
       скобки (у .building-assign-doc и у @media (pointer: coarse)), поэтому мёртвыми
       оказались 640 строк, включая объявленные минимумы целей нажатия и стили карточки
       борта. Снаружи это выглядело как «стили просто не написаны», поэтому и не
       замечалось. Проверка баланса скобок теперь стоит гейтом в scripts/check-a11y.mjs.
   EN: The closing brace above was missing, and that broke NOT one rule but the whole tail of
       the file: by CSS nesting rules everything that follows became a descendant of
       `.is-dark .layer-budget-hud` — applying only inside the budget HUD on the dark theme,
       which is to say never. Two more braces were missing along with this one (at
       .building-assign-doc and at @media (pointer: coarse)), so 640 lines were dead, the
       declared tap-target minimums and the hull card styles among them. From the outside it
       looked like "the styles were simply never written", which is why it went unnoticed. A
       brace-balance check is now a gate in scripts/check-a11y.mjs. */

/* ------------------------------------------------------------------ */
/* P105 — паспорт дома в карточке здания.                             */
/* RU: Блок дополняет карточку, а не спорит с ней: та же скруглённая   */
/*     плашка, что у остальных секций, без собственной рамки и тени —  */
/*     иначе паспорт читался бы как отдельная карточка внутри карточки.*/
/* EN: The block complements the card rather than competing with it:   */
/*     the same rounded panel as other sections, no border or shadow   */
/*     of its own — otherwise the passport would read as a card inside */
/*     a card.                                                          */
/* ------------------------------------------------------------------ */
.building-card-passport {
  margin: 0.5rem 0 0.35rem;
  padding: 0.6rem 0.7rem;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 5%, var(--k-surface));
}

.building-passport-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.building-passport-chips .chip {
  font-size: 0.78rem;
  padding: 0.15rem 0.5rem;
  border-radius: 999px;
  background: var(--k-surface);
  border: 1px solid var(--line);
  color: var(--ink-soft);
  white-space: nowrap;
}

.building-passport-flags {
  margin-top: 0.4rem;
  font-size: 0.8rem;
}

/* RU: аварийность — единственное в паспорте, что требует внимания, поэтому
       единственное, что окрашено. Остальное нейтрально.
   EN: the condemned flag is the only thing here that demands attention, so the
       only thing coloured. Everything else stays neutral. */
.building-passport-alarm {
  color: var(--k-danger);
  font-weight: 600;
}

/* RU: «сведений нет» — не тревога и не факт, поэтому приглушено, но видно.
   EN: "no data" is neither an alarm nor a fact — muted but present. */
.building-passport-unknown {
  color: var(--ink-soft);
  font-style: italic;
}

.building-passport-mgmt-title {
  margin-top: 0.55rem;
  font-size: 0.72rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.building-passport-mgmt {
  margin-top: 0.15rem;
  font-size: 0.85rem;
  line-height: 1.4;
}

.building-passport-mgmt-name {
  font-weight: 600;
  color: var(--ink);
}

/* RU: телефон УК — то, ради чего блок открывают чаще всего; делаем его
       очевидно нажимаемым и достаточно крупным для пальца.
   EN: the management phone is what this block is opened for most often; make it
       obviously tappable and large enough for a finger. */
.building-passport-phone {
  display: inline-block;
  margin-top: 0.2rem;
  padding: 0.15rem 0;
  font-size: 0.95rem;
  font-weight: 600;
  color: var(--accent);
  text-decoration: none;
}

.building-passport-phone:hover {
  text-decoration: underline;
}

.building-passport-mgmt-meta {
  font-size: 0.78rem;
  color: var(--ink-soft);
}

/* RU: подпись источника обязательна у каждого слоя данных — тот же принцип,
       что у легенды пробок. Мелко, но всегда на месте.
   EN: a source line is mandatory for every data layer — the same principle as
       the traffic legend. Small, but always present. */
.building-passport-src {
  margin-top: 0.5rem;
  padding-top: 0.4rem;
  border-top: 1px solid var(--line);
  font-size: 0.7rem;
  color: var(--ink-soft);
}

/* ------------------------------------------------------------------ */
/* P108 — блок «Приписка» в карточке здания.                          */
/* RU: Стоит выше паспорта: «к какой школе относится мой дом» — вопрос */
/*     более частый и более срочный, чем год постройки.                */
/* EN: Placed above the passport: "which school does my house belong   */
/*     to" is a more frequent and more urgent question than the build  */
/*     year.                                                            */
/* ------------------------------------------------------------------ */
.building-card-assign {
  margin: 0.5rem 0 0.35rem;
  padding: 0.6rem 0.7rem;
  border-radius: 10px;
  background: color-mix(in srgb, var(--accent) 8%, var(--k-surface));
}

.building-assign-list {
  display: grid;
  gap: 0.4rem;
}

/* RU: две колонки — вид слева, учреждение справа. Так взгляд находит нужную
       строку по виду, не читая все названия подряд.
   EN: two columns — kind on the left, institution on the right. The eye finds
       the right row by kind without reading every name. */
.building-assign-row {
  display: grid;
  grid-template-columns: 6.5rem minmax(0, 1fr);
  gap: 0.1rem 0.6rem;
  align-items: baseline;
}

.building-assign-kind {
  font-size: 0.72rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.building-assign-org {
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}

/* RU: основание мелким — оно нужно для проверки, а не для чтения. Но оно
       есть всегда: без ссылки на приказ это была бы догадка.
   EN: the legal basis in small type — it exists for verification rather than
       reading. But it is always there: without the decree reference this would
       be a guess. */
.building-assign-doc {
  font-size: 0.7rem;
  font-weight: 400;
  color: var(--ink-soft);
  margin-top: 0.1rem;
}
/* RU: Вторая потерянная скобка — см. пояснение у .is-dark .layer-budget-hud выше.
   EN: The second missing brace — see the note at .is-dark .layer-budget-hud above. */


/* ─────────────────────────────────────────────────────────────────────────────
   RU: Цели нажатия. Замер на живой странице показал восемь элементов управления
       мельче 44 px: логотип в рельсе, ручка мобильной шторки, селекторы города и
       страны, поле фильтра мест. Инструменты карты были ровно 40 px.
       Задаём минимумы через min-width/min-height, а не через padding: паддинг
       сдвинул бы содержимое внутри кнопок и разъехалась бы сетка иконок.
   EN: Tap targets. Measuring the live page found eight controls smaller than 44 px:
       the rail logo, the mobile sheet grip, the city and country selectors, the place
       filter field. The map tools were exactly 40 px. Minimums are set via
       min-width/min-height rather than padding: padding would shift the content inside
       the buttons and break the icon grid.
   ───────────────────────────────────────────────────────────────────────────── */
.map-tool {
  width: var(--k-tap-min);
  height: var(--k-tap-min);
}

.rail-brand {
  min-width: var(--k-tap-min);
  min-height: var(--k-tap-min);
}

/* RU: Ручка шторки визуально остаётся тонкой полоской (.sheet-handle-bar), но
       сама кнопка получает полноценную высоту: за неё тянут пальцем, и промах
       здесь стоит дороже всего — шторка просто не открывается.
   EN: The sheet grip stays a thin bar visually (.sheet-handle-bar), but the button
       itself gets a full height: it is dragged with a thumb, and a miss here costs the
       most — the sheet simply refuses to open. */
.sheet-handle {
  min-height: var(--k-tap-min);
}

/* RU: Селекторы страны и города и поле фильтра — основной способ сменить место,
       и на телефоне по ним попадают вслепую.
   EN: The country and city selectors and the filter field are the main way to change
       place, and on a phone they are hit without looking. */
.place-switcher select,
.place-switcher input,
.place-filter-input {
  min-height: var(--k-tap-min);
}

/* ─────────────────────────────────────────────────────────────────────────────
   RU: Загруженность по часам (public/js/popular-times.js). Серверная часть жила
       без интерфейса: эндпоинт был написан и не вызывался ни разу.
       Столбики рисуются flex'ом с выравниванием по низу — так гистограмма растёт
       вверх от базовой линии, а не висит в воздухе.
   EN: Popular times (public/js/popular-times.js). The server side lived without an
       interface: the endpoint was written and never called once.
       The bars are laid out with flex aligned to the bottom, so the histogram grows
       up from a baseline rather than floating.
   ───────────────────────────────────────────────────────────────────────────── */
.firm-popular-times {
  margin-top: var(--k-s-4);
  padding-top: var(--k-s-4);
  border-top: 1px solid var(--k-border-soft);
}

.firm-popular-times .pt-head {
  display: flex;
  align-items: baseline;
  gap: var(--k-s-2);
  margin-bottom: var(--k-s-2);
}

.firm-popular-times .pt-title {
  font-size: var(--k-fs-3);
}

.firm-popular-times .pt-day {
  font-size: var(--k-fs-1);
  color: var(--k-text-subtle);
  margin-left: auto;
}

.firm-popular-times .pt-chart {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  height: 56px;
}

/* RU: Столбик. Минимальная высота 4% нужна, чтобы час с нулевой загруженностью
       всё равно оставлял след: пропуск в ряду читается как «нет данных», а это
       другое сообщение.
   EN: A bar. The 4% minimum height exists so an hour with zero busyness still leaves a
       trace: a gap in the row reads as "no data", which is a different message. */
.firm-popular-times .pt-bar {
  flex: 1;
  min-height: 2px;
  background: var(--k-surface-2);
  border-radius: 2px 2px 0 0;
}

.firm-popular-times .pt-bar.is-empty {
  background: var(--k-border-soft);
}

/* RU: Текущий час — акцентом. Это единственная цветовая метка в блоке: если
       красить ещё и пик, глаз перестаёт понимать, что здесь главное.
   EN: The current hour takes the accent. It is the only colour marker in the block: if
       the peak were coloured too, the eye would stop knowing what matters here. */
.firm-popular-times .pt-bar.is-now {
  background: var(--k-brand);
}

.firm-popular-times .pt-ticks {
  display: flex;
  justify-content: space-between;
  font: 400 var(--k-fs-1) / 1 var(--k-font-mono);
  color: var(--k-text-subtle);
  margin-top: var(--k-s-1);
}

.firm-popular-times .pt-summary {
  margin: var(--k-s-2) 0 0;
  font-size: var(--k-fs-2);
  color: var(--k-text-muted);
}

.firm-popular-times .pt-peak {
  color: var(--k-text-subtle);
}

/* RU: Оговорка про происхождение данных. Мелкая, но обязательная: без неё
       владелец решит, что мы считаем посетителей, и построит на этом выводы.
   EN: The data-provenance caveat. Small but mandatory: without it an owner concludes we
       count visitors and draws conclusions on that basis. */
.firm-popular-times .pt-note {
  margin: var(--k-s-1) 0 0;
  font-size: var(--k-fs-1);
  line-height: var(--k-lh-normal);
  color: var(--k-text-subtle);
}

/* ─────────────────────────────────────────────────────────────────────────────
   RU: Подсказка о разборе запроса (public/js/nl-hint.js). Появляется, когда
       сервер понял запрос как набор фильтров: «где поесть недорого» → рубрика
       «Еда» + ценовой намёк. До неё выдача менялась молча.
   EN: The query-parse hint (public/js/nl-hint.js). It appears when the server read the
       query as a set of filters: "где поесть недорого" → the "Еда" rubric plus a price
       hint. Before it, the results changed silently.
   ───────────────────────────────────────────────────────────────────────────── */
.nl-hint {
  display: flex;
  align-items: center;
  gap: var(--k-s-2);
  flex-wrap: wrap;
  padding: var(--k-s-2) var(--k-s-3);
  margin-bottom: var(--k-s-3);
  border: 1px solid var(--k-info);
  border-radius: var(--k-r-2);
  background: var(--k-info-soft);
  font-size: var(--k-fs-2);
}

.nl-hint-label {
  font: 500 var(--k-fs-1) / 1 var(--k-font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--k-info);
}

.nl-hint-chips {
  display: flex;
  gap: var(--k-s-1);
  flex-wrap: wrap;
}

.nl-hint-chip {
  background: var(--k-surface);
  border-radius: var(--k-r-pill);
  padding: 2px var(--k-s-2);
  color: var(--k-text);
  font-size: var(--k-fs-1);
}

/* RU: Кнопка отмены прижата вправо и намеренно не выглядит основной: разбор чаще
       верен, чем нет, и предлагать отмену как главное действие было бы неправдой.
   EN: The undo button sits right and deliberately does not look primary: the parse is
       right more often than not, and offering undo as the main action would misrepresent that. */
.nl-hint-undo {
  margin-left: auto;
  border: 1px solid var(--k-border);
  background: var(--k-surface);
  color: var(--k-text-muted);
  border-radius: var(--k-r-pill);
  padding: var(--k-s-1) var(--k-s-3);
  font-size: var(--k-fs-1);
  min-height: 28px;
  cursor: pointer;
}

.nl-hint-undo:hover {
  border-color: var(--k-info);
  color: var(--k-text);
}

/* ══════════════════════════════════════════════════════════════════════
   RU: Цели нажатия на любом сенсорном устройстве, а не только на узком
       экране. Прежнее правило жило внутри `max-width: 860px`, поэтому
       планшет шириной 900 px получал мышиные размеры при пальцевом вводе.
       `pointer: coarse` спрашивает про способ ввода, а не про ширину.
   EN: Tap targets on any touch device, not only on a narrow screen. The old
       rule lived inside `max-width: 860px`, so a 900 px tablet got mouse-sized
       controls under finger input. `pointer: coarse` asks about the input
       method rather than the width.
   ══════════════════════════════════════════════════════════════════════ */
@media (pointer: coarse) {
  .rail-btn, .firm-action-btn, .ghost-btn, .filter-toggle,
  .search-btn, .back-btn, .map-tools button, .nav-rail button,
  .detail-back-btn, .results-back-btn, .place-switcher-btn,
  .firm-tab, .building-card-action, .firm-website-btn,
  .firm-phone-btn, .firm-route-btn, .firm-share-btn,
  .firm-fav-btn, .review-submit-btn, .rubric-chip,
  .results-more, .firm-media-nav, .back-link, .result-card {
    min-height: var(--k-tap-min);
  }

  /* RU: Ф6.9. Свой набор витрины: превью галереи и кнопки лайтбокса (Ф6.8), элементы
         подсказки поиска, чипы недавнего и быстрых запросов, подкатегории выдачи, кнопка
         «Маршрут сюда» в строке результата и кнопка закрытия мобильного поиска. Замер по
         разметке: у всех перечисленных заданы только паддинги, то есть высота зависела от
         размера шрифта и выходила 28–36 px. min-width задаётся там, где элемент узкий по
         содержимому (значок, стрелка): у широких чипов ширина и так больше 44 px, а
         min-width на них ничего не меняет.
     EN: Ф6.9. The storefront's own set: gallery thumbnails and lightbox buttons (Ф6.8), the
         search suggestion items, the recent and quick-query chips, the results' subcategories,
         the "route here" button in a result row and the mobile search close button. Measured
         from the markup: all of them declare padding only, so the height followed the font size
         and came out at 28–36 px. min-width is set where the element is narrow by content (a
         glyph, an arrow): wide chips are already past 44 px and min-width changes nothing there. */
  .suggest-item,
  .mobile-suggest-item,
  .recent-chip,
  .recent-firm-item,
  .public-quick-chip,
  .child-rubric-tab,
  .gallery-thumb,
  .gallery-btn {
    min-height: var(--k-tap-min);
    min-width: var(--k-tap-min);
  }

  .result-route,
  .mobile-search-close {
    min-height: var(--k-tap-min);
    min-width: var(--k-tap-min);
  }

  /* RU: Этим четырём min-height уже задан правилом выше, а min-width не задан. Для широких по
         вёрстке элементов (кнопка «Ещё N» и ручка шторки тянутся на всю ширину панели) это
         объявление ничего не меняет — оно стоит, чтобы минимум был выражен ОБОИМИ размерами и
         проверялся целиком: вкладка карточки 88×28 проходила «по ширине» и оставалась
         непопадаемой по высоте, и обратный случай так же реален для узкой кнопки.
     EN: These four already get min-height from the rule above but no min-width. For elements wide
         by layout (the "N more" button and the sheet grip stretch across the panel) this
         declaration changes nothing — it is here so the minimum is expressed in BOTH dimensions and
         checked as a whole: a card tab at 88x28 passed "on width" and stayed unhittable in height,
         and the reverse case is just as real for a narrow button. */
  .firm-tab,
  .results-more,
  .ghost-btn,
  .sheet-handle {
    min-height: var(--k-tap-min);
    min-width: var(--k-tap-min);
  }
}

/* ── P93 «Живые машины»: легенда и карточка борта ───────────────────────────
   RU: Легенда живёт рядом с легендой пробок и по тем же правилам: не перехватывает
       клики по карте (pointer-events: none) и прячется целиком, когда слой выключен.
       Карточка, наоборот, кликабельна — в ней есть кнопка закрытия.
   EN: The legend lives next to the traffic legend and follows the same rules: it never
       intercepts map clicks (pointer-events: none) and hides entirely when the layer is off.
       The card, by contrast, is interactive — it has a close button. */
.vehicles-legend {
  position: absolute;
  left: 12px;
  bottom: 88px;
  z-index: 4;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 10px;
  max-width: min(70%, 520px);
  padding: 6px 10px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.92);
  border: 1px solid rgba(15, 23, 42, 0.12);
  font-size: 0.72rem;
  color: #334155;
  pointer-events: none;
}

.vehicles-legend[hidden] {
  display: none !important;
}

.vehicles-legend-title {
  font-weight: 600;
}

.vehicles-legend-items {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 10px;
}

.vehicles-legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.vehicles-legend-item i {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  display: block;
}

.vehicles-legend-note {
  color: #64748b;
}

/* RU: Карточка отодвинута от правого края на ширину панели инструментов карты
       (44 px) плюс зазор. Замер показал, что при right:12 она закрывала сразу четыре
       кнопки — «Линейка», «Слои», «Пробки» и «3D», — то есть выбор машины отнимал у
       пользователя управление картой. Проверять такое приходится измерением: на глаз
       карточка выглядит аккуратно прижатой к краю.
   EN: The card is pushed off the right edge by the width of the map toolbar (44 px) plus a gap.
       Measurement showed that at right:12 it covered four buttons at once — ruler, layers, traffic
       and 3D — so picking a vehicle took the map controls away from the user. This has to be caught
       by measuring: to the eye the card simply looks neatly tucked against the edge. */
.vehicle-card {
  position: absolute;
  right: 62px;
  top: 68px;
  z-index: 6;
  width: 216px;
  padding: 10px 12px 8px;
  border-radius: 10px;
  background: var(--k-card-bg);
  border: 1px solid rgba(15, 23, 42, 0.14);
  box-shadow: 0 6px 22px var(--k-card-shadow);
  color: var(--k-text);
  font-size: 0.78rem;
}

.vehicle-card[hidden] {
  display: none !important;
}

.vehicle-card-close {
  position: absolute;
  right: 6px;
  top: 4px;
  border: 0;
  background: none;
  color: var(--k-text-subtle);
  font-size: 1.05rem;
  line-height: 1;
  padding: 3px 6px;
  cursor: pointer;
}

.vehicle-card-head {
  display: flex;
  align-items: center;
  gap: 8px;
  padding-right: 18px;
}

.vehicle-card-chip {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex: none;
}

.vehicle-card-head strong {
  display: block;
  font-size: 0.92rem;
}

.vehicle-card-plate {
  display: block;
  font-size: 0.7rem;
  color: var(--k-text-subtle);
  font-variant-numeric: tabular-nums;
}

.vehicle-card-rows {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: 2px 10px;
  margin: 8px 0 6px;
}

.vehicle-card-rows dt {
  color: var(--k-text-muted);
}

.vehicle-card-rows dd {
  margin: 0;
  text-align: right;
  font-variant-numeric: tabular-nums;
}

/* RU: Подпись источника — обязательный элемент, а не украшение: по ней пользователь
       отличает модель движения от настоящего борта. Приглушать её нельзя до
       нечитаемости, поэтому цвет muted, а не subtle.
   EN: The source line is mandatory, not decoration: it is how a user tells a motion model from a
       real hull. It must not be dimmed into illegibility, hence muted rather than subtle. */
.vehicle-card-source {
  margin: 0;
  padding-top: 6px;
  border-top: 1px solid rgba(15, 23, 42, 0.1);
  font-size: 0.7rem;
  color: var(--k-text-muted);
}

/* RU: Узкий экран. Замер на 390×844: карта в мобильной оболочке занимает всего 244 px
       по высоте, а легенда с цветными чипами разворачивается в три строки на 90 px и
       вместе с карточкой (174 px) закрывает её почти целиком — да ещё и накладывается
       на неё. Поэтому на телефоне легенда сжимается до одной строки без чипов, а
       карточка становится компактнее.

       Чипы убраны сознательно: на 390 px они нечитаемы и стоят трёх строк карты.
       Подпись источника («модель движения») остаётся — она обязательная, а не
       декоративная: именно по ней видно, что поток смоделирован.
   EN: Narrow screen. Measured at 390×844: inside the mobile shell the map is only 244 px tall,
       while the legend with colour chips wraps into three lines of 90 px and together with the card
       (174 px) covers almost all of it — and overlaps it too. So on a phone the legend collapses to
       a single line without chips and the card gets more compact.

       The chips are dropped deliberately: at 390 px they are illegible and cost three lines of map.
       The source line ("модель движения") stays — it is mandatory rather than decorative: it is what
       shows the flow is modelled. */
@media (max-width: 720px) {
  .vehicles-legend {
    left: 8px;
    bottom: 8px;
    max-width: calc(100% - 16px);
    gap: 4px 8px;
    padding: 4px 8px;
    font-size: 0.68rem;
  }

  .vehicles-legend-items {
    display: none;
  }

  .vehicle-card {
    right: 62px;
    top: 8px;
    width: 160px;
    padding: 8px 10px 6px;
    font-size: 0.74rem;
  }

  .vehicle-card-head strong {
    font-size: 0.84rem;
  }

  .vehicle-card-rows {
    gap: 1px 8px;
    margin: 6px 0 5px;
  }

  .vehicle-card-source {
    padding-top: 5px;
    font-size: 0.66rem;
  }
}

/* RU: P93 в тёмной теме. Ночной режим — это настоящий тёмный стиль карты, а не
       инверсия канвы (см. `.is-dark .map-stage canvas { filter: none }`), поэтому
       плашка легенды и карточка обязаны перекрашиваться сами: белый прямоугольник на
       ночной карте виден за километр и выглядит как ошибка. Условие приёмки P93.6
       про контраст в тёмной теме — про это.
   EN: P93 in the dark theme. Night mode is a real dark map style rather than a canvas inversion
       (see `.is-dark .map-stage canvas { filter: none }`), so the legend plate and the card must
       recolour themselves: a white rectangle on a night map is visible a mile away and reads as a
       bug. The P93.6 acceptance about dark-theme contrast is about exactly this. */
.is-dark .vehicles-legend {
  background: rgba(22, 33, 62, 0.92);
  border-color: var(--k-border);
  color: var(--k-text);
}

.is-dark .vehicles-legend-note {
  color: var(--k-text-muted);
}

.is-dark .vehicle-card {
  border-color: var(--k-border);
}

.is-dark .vehicle-card-source {
  border-top-color: var(--k-border);
}

/* RU: Контрастный режим — не тёмный, но подложка светлее и рамка в 12% чёрного
       растворяется. Даём ей ту же границу, что и остальным элементам режима.
   EN: Contrast mode is not dark, but its ground is lighter and a 12%-black border dissolves. It
       gets the same border the rest of the mode uses. */
.is-contrast .vehicles-legend,
.is-contrast .vehicle-card {
  border-color: var(--k-border);
}

/* RU: Фокус с клавиатуры на кнопке закрытия карточки. Штатный контур браузера на
       полупрозрачной подложке карты почти не виден, а карточка — единственный элемент
       слоя, который вообще принимает фокус.
   EN: Keyboard focus on the card's close button. The browser's default outline is barely visible
       against the map's translucent ground, and the card is the only element of the layer that
       takes focus at all. */
.vehicle-card-close:focus-visible {
  outline: 2px solid var(--k-brand);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ═══════════════════════════════════════════════════════════════════════════
   RU: Ф6.8 — галерея фотографий карточки и лайтбокс (public/js/gallery.js).

       Что было: `.firm-media` — ряд ссылок на голый файл. Отсюда и правила ниже:
       сетка вместо ряда, кнопка вместо ссылки, просмотр вместо перехода на JPEG.

       Почему grid с auto-fill, а не фиксированные три колонки: панель карточки на
       телефоне 320–420 px, на десктопе 380–520 px, и при фиксированном числе колонок
       на узкой панели превью выходило 88 px — на такой картинке не видно, что на ней.

       Почему aspect-ratio у превью: размеры снимка известны далеко не всегда (width/height
       в базе заполнены только у своих файлов, прошедших localize). Без заданной пропорции
       сетка прыгает по мере загрузки, и палец нажимает не на то фото, на которое нацелился.
   EN: Ф6.8 — the card's photo gallery and lightbox (public/js/gallery.js).

       What it used to be: `.firm-media`, a row of links to a bare file. Hence the rules below:
       a grid instead of a row, a button instead of a link, a viewer instead of navigating to a
       JPEG.

       Why a grid with auto-fill rather than fixed three columns: the card panel is 320–420 px on
       a phone and 380–520 px on desktop, and with a fixed column count a narrow panel produced
       88 px thumbnails — too small to tell what is on them.

       Why aspect-ratio on the thumbnails: the shot's dimensions are known far from always
       (width/height in the database are filled only for our own files that went through
       localize). Without a fixed ratio the grid jumps as images arrive, and a thumb taps a photo
       other than the one it aimed at.
   ══════════════════════════════════════════════════════════════════════════ */
.gallery-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 0.4rem;
  margin: 0.2rem 0 0.5rem;
  padding: 0;
  list-style: none;
}

/* RU: Ячейка сбрасывает отступы списка: <ul> здесь выбран ради семантики (список фотографий с
       подписью), а не ради маркеров, и браузерный отступ сдвинул бы сетку вправо на 40 px.
   EN: The cell resets the list's indentation: the <ul> is chosen for semantics here (a labelled
       list of photographs) rather than for bullets, and the browser's padding would shift the grid
       40 px to the right. */
.gallery-cell {
  margin: 0;
}

.gallery-thumb {
  display: block;
  width: 100%;
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--k-surface-3, #eef2f0);
  cursor: pointer;
  overflow: hidden;
}

/* RU: Наведение меняет только рамку: затемнение или смещение превью соврало бы о том, что
       откроется — открывается ТОТ ЖЕ снимок, просто крупнее.
   EN: Hover changes the border only: dimming or shifting the thumbnail would lie about what opens —
       what opens is the SAME shot, merely larger. */
.gallery-thumb:hover {
  border-color: var(--accent, var(--k-brand));
}

.gallery-thumb-img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

/* RU: Подпись под сеткой — не украшение: она отвечает на вопрос «откуда эти фото», который
       иначе задаёт каждый второй. Автор и лицензия у каждого снимка показываются в просмотре,
       потому что в сетке из восьми превью они не поместились бы читаемо.
   EN: The note under the grid is not decoration: it answers the "where are these photos from"
       question every other person asks. Each shot's author and licence appear in the viewer, because
       in a grid of eight thumbnails they would not fit legibly. */
.gallery-note {
  margin: 0 0 0.4rem;
}

/* RU: Пустое состояние по отступам совпадает с сеткой: переключение вкладки не должно дёргать
       высоту панели в зависимости от того, есть у организации фото или нет.
   EN: The empty state matches the grid's spacing: switching tabs must not jolt the panel's height
       depending on whether the organisation has photos. */
.gallery-empty {
  margin: 0.2rem 0 0.4rem;
}

/* RU: Лайтбокс. position: fixed и подложка во всю область просмотра — просмотр перекрывает
       страницу целиком, это и делает его модальным. z-index берётся из шкалы kartivi.css с
       запасом над картой и её панелями: всплывший поверх фотографии элемент управления картой
       выглядел бы как часть снимка.
   EN: The lightbox. position: fixed with a backdrop over the whole viewport — the viewer covers
       the page entirely, which is what makes it modal. The z-index comes from the kartivi.css
       scale with headroom over the map and its panels: a map control floating above the photo
       would read as part of the shot. */
/* RU: Атрибут hidden обязан побеждать `display` ниже. `display: none` для [hidden] живёт в
       стилях САМОГО БРАУЗЕРА, и любое авторское правило с display его перебивает — тот же приём
       уже применён в этом файле к .map-tool и .offline-indicator, и причина та же. Без этих трёх
       правил лайтбокс висел бы поверх страницы постоянно, а скрытые стрелки и пустая подпись
       занимали бы место в панели просмотра. Проверка держит инвариант в scripts/check-a11y.mjs:
       класс, который витрина скрывает через hidden, обязан иметь пару [hidden].
   EN: The hidden attribute must beat the `display` below. `display: none` for [hidden] lives in the
       BROWSER'S OWN stylesheet, and any author rule with display overrides it — the same trick is
       already used in this file for .map-tool and .offline-indicator, for the same reason. Without
       these three rules the lightbox would hang over the page permanently, and hidden arrows plus an
       empty caption would take up room in the viewer's bar. The invariant is held by a check in
       scripts/check-a11y.mjs: a class the storefront hides through hidden must have a [hidden]
       counterpart. */
.gallery-lightbox[hidden],
.gallery-lightbox-caption[hidden],
.gallery-btn[hidden] {
  display: none;
}

.gallery-lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--k-z-modal, 900);
  display: flex;
  align-items: center;
  justify-content: center;
}

/* RU: Подложка отдельным элементом, а не фоном контейнера: по ней закрывают просмотр кликом, и
       клик обязан отличаться от клика по кадру — на фоне контейнера они неразличимы.
   EN: The backdrop is its own element rather than the container's background: a click on it closes
       the viewer, and that click must be distinguishable from a click on the frame — on a container
       background they are indistinguishable. */
.gallery-lightbox-bg {
  position: absolute;
  inset: 0;
  background: rgba(9, 18, 14, 0.88);
}

/* RU: Три колонки — стрелка, снимок, стрелка — и две строки: панель управления и сам кадр.
       Сетка, а не flex: снимок обязан получить ВСЮ остальную площадь (1fr) и не сжимать стрелки,
       а во flex-контейнере картинка с max-height растягивала бы соседей.
       Размеры в vw/vh с потолком: на телефоне просмотр занимает почти весь экран, на большом
       мониторе кадр не имеет смысла растягивать дальше — своих файлов шире 1100 px у нас нет.
   EN: Three columns — arrow, shot, arrow — and two rows: the control bar and the frame itself.
       A grid rather than flex: the shot must take ALL the remaining area (1fr) without squeezing the
       arrows, while inside a flex container an image with max-height would stretch its siblings.
       Sizes in vw/vh with a ceiling: on a phone the viewer takes nearly the whole screen, and on a
       large monitor there is no point stretching the frame further — we hold no own files wider
       than 1100 px. */
.gallery-lightbox-inner {
  position: relative;
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-rows: auto 1fr;
  align-items: center;
  gap: 0.5rem;
  width: min(96vw, 1100px);
  height: min(94vh, 900px);
  padding: 0.5rem;
}

.gallery-lightbox-bar {
  grid-column: 1 / -1;
  display: flex;
  /* RU: Перенос — см. .search-actions: пять кнопок по 44 px плюс счётчик не помещаются в 320 px.
     EN: Wrapping — see .search-actions: five 44 px buttons plus the counter do not fit 320 px. */
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}

/* RU: Распорка отодвигает кнопки к правому краю, оставляя счётчик слева. Пустой элемент, а не
       `margin-left: auto` у первой кнопки: кнопки в панели скрываются (стрелки — на единственном
       снимке), и «первая» каждый раз другая.
   EN: The spacer pushes the buttons to the right edge, leaving the counter on the left. An empty
       element rather than `margin-left: auto` on the first button: buttons in the bar get hidden
       (the arrows, on a single shot), so "the first" is a different one each time. */
.gallery-bar-spacer {
  flex: 1;
}

/* RU: Счётчик белым по затемнению, а не токеном темы: подложка лайтбокса всегда тёмная в любой
       теме — фотографию нельзя показывать на светлом фоне, он засвечивает её края.
   EN: The counter is white on the dim layer rather than a theme token: the lightbox ground is always
       dark in every theme — a photograph must not be shown on a light background, which washes out
       its edges. */
.gallery-counter {
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
}

/* RU: Кнопки просмотра. Свои, а не .ghost-btn: те рассчитаны на светлую подложку панели, а
       здесь фон — затемнение, и светлая кнопка на нём засвечивает фотографию рядом.
       44 px здесь безусловные, а не только под `pointer: coarse`: стрелки в просмотре нажимают
       на ходу, одной рукой, и это единственный способ листать без клавиатуры.
   EN: The viewer's buttons. Their own, not .ghost-btn: those are made for the panel's light
       ground, while here the background is a dim layer and a light button on it washes out the
       photo beside it.
       The 44 px here are unconditional rather than only under `pointer: coarse`: the arrows in the
       viewer are pressed on the move, one-handed, and they are the only way to flip without a
       keyboard. */
.gallery-btn {
  min-width: 44px;
  min-height: 44px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid rgba(255, 255, 255, 0.3);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.12);
  color: #fff;
  font: inherit;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
}

/* RU: Наведение и отключённое состояние — только прозрачностью подложки: цветом на затемнении
       разницу не показать, а кнопка зума гаснет на краях диапазона и обязана читаться как
       недоступная, а не как сломанная.
   EN: Hover and the disabled state are expressed through the ground's opacity alone: colour cannot
       show the difference on a dim layer, while a zoom button goes dim at the range's edges and must
       read as unavailable rather than broken. */
.gallery-btn:hover {
  background: rgba(255, 255, 255, 0.22);
}

.gallery-btn:focus-visible {
  outline: 2px solid #fff;
  outline-offset: 2px;
}

.gallery-btn[disabled] {
  opacity: 0.4;
  cursor: default;
}

/* RU: Стрелки по центру высоты кадра — там, где их ищет большой палец, и там, где они не
       перекрывают подпись с автором и лицензией.
   EN: The arrows sit at the frame's vertical centre — where a thumb looks for them, and where they do
       not cover the caption with the author and the licence. */
.gallery-nav {
  align-self: center;
}

/* RU: figure + figcaption, а не div + p: подпись с автором и лицензией обязана быть СВЯЗАНА со
       снимком, а не просто лежать под ним — иначе скринридер читает её как отдельный абзац
       страницы, и связь «эта лицензия относится к этому фото» теряется.
   EN: figure + figcaption rather than div + p: the caption with the author and the licence must be
       TIED to the shot rather than merely sit under it — otherwise a screen reader reads it as a
       separate paragraph of the page and the "this licence belongs to this photo" link is lost. */
.gallery-lightbox-figure {
  position: relative;
  grid-column: 2;
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  min-width: 0;
  min-height: 0;
  height: 100%;
  overflow: hidden;
}

.gallery-lightbox-img {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  /* RU: Начало координат в центре — увеличение растёт от середины кадра, а не от левого
         верхнего угла, иначе при зуме снимок уезжает из виду.
     EN: The origin is the centre — magnification grows from the middle of the frame rather than
         the top-left corner, otherwise the shot slides out of view on zoom. */
  transform-origin: center center;
  transition: transform 120ms ease;
}

/* RU: Скелетон под фотографией: видно, что файл идёт, а не что просмотр пуст. Снимается по
       load И по error (см. gallery.js) — иначе на недоехавшем файле он висит навсегда и
       читается как «грузится».
   EN: A skeleton under the photo: it shows the file is on its way rather than that the viewer is
       empty. Removed on load AND on error (see gallery.js) — otherwise it hangs forever on a file
       that never arrived and reads as "loading". */
.gallery-lightbox-skeleton {
  position: absolute;
  width: min(70%, 420px);
  height: 40%;
  display: none;
}

.gallery-lightbox-figure.is-loading .gallery-lightbox-skeleton {
  display: block;
}

.gallery-lightbox-figure.is-zoomed {
  /* RU: На увеличенном снимке разрешаем прокрутку области, иначе часть кадра недостижима.
     EN: On a magnified shot the area is scrollable, otherwise part of the frame is unreachable. */
  overflow: auto;
}

/* RU: Подпись переносится по словам и растёт вниз: «Автор: … · Лицензия: …» на узком экране не
       влезает в строку, а обрезать по ширине здесь нельзя — обрезанное имя автора это нарушение
       условий, а не косметический дефект.
   EN: The caption wraps by parts and grows downwards: "Author: … · Licence: …" does not fit one line
       on a narrow screen, and clipping by width is not an option here — a clipped author's name is a
       breach of the terms, not a cosmetic defect. */
.gallery-lightbox-caption {
  display: flex;
  flex-wrap: wrap;
  gap: 0.15rem 0.6rem;
  max-width: 100%;
  color: rgba(255, 255, 255, 0.92);
  font-size: 0.82rem;
  text-align: center;
}

/* RU: Ссылка на лицензию подчёркнута всегда: на затемнении цвет различить нельзя, а ссылка тут
       обязательная по условиям лицензии — по ней должны попадать, а не догадываться о ней.
   EN: The licence link is always underlined: colour cannot be distinguished on the dim layer, while
       the link here is mandatory under the licence terms — it must be hittable, not guessable. */
.gallery-lightbox-caption a {
  color: #fff;
  text-decoration: underline;
  text-underline-offset: 0.15em;
}

/* RU: Пока просмотр открыт, страница под ним не прокручивается: иначе жест над затемнением
       уносит выдачу, и после закрытия человек оказывается в другом месте списка.
   EN: While the viewer is open the page beneath does not scroll: otherwise a gesture over the dim
       layer carries the results away, and after closing the person lands elsewhere in the list. */
.has-gallery-open,
.has-gallery-open body {
  overflow: hidden;
}

/* RU: На узкой панели стрелки уводят фотографию в щель между ними. Переносим их в строку
       управления под счётчиком: на телефоне листают свайпом, а кнопки остаются как
       клавиатурная и «резервная» опора.
   EN: On a narrow panel the arrows squeeze the photo into the gap between them. They move into the
       control row under the counter: on a phone people flip by swiping, and the buttons remain as
       the keyboard and fallback affordance. */
@media (max-width: 560px) {
  .gallery-lightbox-inner {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
  }

  .gallery-lightbox-figure {
    grid-column: 1;
  }

  .gallery-nav {
    grid-column: 1;
    justify-self: center;
  }

  .gallery-nav-prev {
    grid-row: 3;
    justify-self: start;
  }

  .gallery-nav-next {
    grid-row: 3;
    justify-self: end;
  }
}

/* ——— Заявка с карточки организации (Ф6.5, public/js/lead-form.js) ———
   RU: Цвета только из токенов `--k-*`: у витрины три темы (светлая, ночная, контрастная), и зашитый светлый
       фон, как у части старых блоков, ночью дал бы белую плашку со светлым текстом. Пары «текст на фоне» взяты
       из списка, который сторожит scripts/check-contrast.mjs. Все цели нажатия — не меньше `--k-tap-min`
       безусловно, а не только под `pointer: coarse`: форму заполняют и с ноутбука с сенсорным экраном, а поле
       ввода ниже 44 px на телефоне ещё и провоцирует увеличение страницы.
   EN: Colours come only from `--k-*` tokens: the storefront has three themes (light, night, contrast), and a
       hard-coded light background, as in some older blocks, would give a white plate with light text at night.
       The "text on background" pairs are taken from the list guarded by scripts/check-contrast.mjs. Every tap
       target is at least `--k-tap-min` unconditionally, not only under `pointer: coarse`: the form is also
       filled from touch-screen laptops, and an input below 44 px on a phone also provokes page zoom. */
.lead-block {
  position: relative;
  margin: 0.75rem 0;
  padding: 0.75rem;
  border: 1px solid var(--k-border);
  border-radius: var(--k-r-3);
  background: var(--k-surface);
  color: var(--k-text);
}

/* RU: Отступы между частями блока — полями соседей, а не `gap` сетки: пустая строка состояния иначе занимала бы
       свою полосу промежутка под кнопками, хотя сообщения ещё нет.
   EN: Spacing between the block's parts comes from sibling margins rather than a grid `gap`: otherwise the empty
       status line would take its own gap strip under the buttons while there is no message yet. */
.lead-block > * + * {
  margin-top: 0.55rem;
}

.lead-block-title,
.lead-form-title {
  margin: 0;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--k-text);
}

.lead-cta-row,
.lead-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.lead-open-btn,
.lead-submit-btn,
.lead-cancel-btn {
  min-height: var(--k-tap-min);
  min-width: var(--k-tap-min);
  padding: 0.5rem 0.9rem;
  border-radius: var(--k-r-2);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}

.lead-open-btn,
.lead-submit-btn {
  border: 1px solid var(--k-brand);
  background: var(--k-brand);
  color: var(--k-text-on-accent);
}

.lead-open-btn:hover,
.lead-submit-btn:hover {
  border-color: var(--k-brand-hover);
  background: var(--k-brand-hover);
}

/* RU: Вторая кнопка и «Отмена» — акцентный текст на панели: два одинаково залитых действия рядом спорили бы
       за внимание, а главное здесь одно — позвать организацию.
   EN: The second button and "Cancel" use accent text on the panel: two equally filled actions side by side
       would compete for attention, and there is one main thing here — reaching the organisation. */
.lead-open-btn.is-secondary,
.lead-cancel-btn {
  border: 1px solid var(--k-border-strong);
  background: var(--k-surface);
  color: var(--k-brand-ink);
}

.lead-open-btn.is-secondary:hover,
.lead-cancel-btn:hover {
  background: var(--k-brand-soft);
}

/* RU: Во время отправки кнопка помечена aria-disabled, а не disabled — выключенная кнопка теряет фокус (см. `sending`
       в public/js/lead-form.js). Прозрачность не снижаем — она роняла бы контраст подписи, а состояние и так
       объявлено строкой «Отправляем заявку…».
   EN: While sending the button is marked aria-disabled rather than disabled — a disabled button loses focus (see
       `sending` in public/js/lead-form.js). Opacity is not lowered — it would drop the label's contrast, and the state
       is announced anyway by the "Отправляем заявку…" line. */
.lead-submit-btn[aria-disabled="true"] {
  cursor: progress;
}

.lead-form {
  display: grid;
  gap: 0.55rem;
}

/* RU: Пара к `display: grid`: без неё авторское правило перебило бы браузерное `[hidden]`, и «скрытая» форма
       висела бы на карточке всегда (см. HIDDEN_TOGGLED в scripts/check-a11y.mjs).
   EN: The counterpart to `display: grid`: without it the author rule would beat the browser's `[hidden]`, and the
       "hidden" form would always hang on the card (see HIDDEN_TOGGLED in scripts/check-a11y.mjs). */
.lead-form[hidden] {
  display: none;
}

.lead-form-note,
.lead-hint {
  margin: 0;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--k-text-muted);
}

.lead-field {
  display: grid;
  gap: 0.25rem;
  font-size: 0.85rem;
  color: var(--k-text);
}

.lead-optional,
.lead-required {
  font-weight: 400;
  color: var(--k-text-muted);
}

.lead-field input,
.lead-field select,
.lead-field textarea {
  box-sizing: border-box;
  width: 100%;
  min-height: var(--k-tap-min);
  min-width: var(--k-tap-min);
  padding: 0.5rem 0.6rem;
  border: 1px solid var(--k-border-strong);
  border-radius: var(--k-r-2);
  background: var(--k-surface);
  color: var(--k-text);
  font: inherit;
}

.lead-field textarea {
  resize: vertical;
}

/* RU: Ошибочное поле — рамкой цвета ошибки, а текст причины стоит в строке состояния: один цвет без слов не
       различим для тех, кто его не видит.
   EN: An invalid field gets a danger-coloured border, while the reason text sits in the status line: colour
       alone, without words, is indistinguishable for those who cannot see it. */
.lead-field [aria-invalid="true"] {
  border-color: var(--k-danger);
  box-shadow: 0 0 0 1px var(--k-danger);
}

.lead-consent {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  font-size: 0.8rem;
  line-height: 1.4;
  color: var(--k-text);
}

/* RU: Сам флажок маленький, поэтому целью нажатия служит подпись: она не ниже `--k-tap-min` и по клику
       переключает флажок (связь `for`). Флажок выровнен по первой строке подписи: текст согласия длинный и на
       карточке всегда занимает несколько строк. Подпись — блок, а не flex: у flex-контейнера каждый кусок текста и
       ссылка становятся отдельными колонками, и ссылка на политику вставала узким столбцом рядом с текстом (так
       было на первом замере в браузере). Обёртка `.lead-consent-text` держит текст и ссылку одним строчным потоком.
   EN: The checkbox itself is small, so the label is the tap target: it is at least `--k-tap-min` tall and toggles the
       checkbox on click (the `for` link). The box is aligned with the label's first line: the consent text is long and
       always takes several lines on the card. The label is a block rather than flex: in a flex container every text
       run and the link become separate columns, and the policy link stood as a narrow column beside the text (as the
       first browser measurement showed). The `.lead-consent-text` wrapper keeps text and link in one inline flow. */
.lead-consent input {
  flex: 0 0 auto;
  width: 1.25rem;
  height: 1.25rem;
  margin: 0.05rem 0 0;
  accent-color: var(--k-brand);
}

.lead-consent label {
  display: block;
  min-height: var(--k-tap-min);
  min-width: var(--k-tap-min);
  cursor: pointer;
}

.lead-consent a {
  color: var(--k-brand-ink);
  text-decoration: underline;
}

.lead-consent [aria-invalid="true"] {
  outline: 2px solid var(--k-danger);
  outline-offset: 2px;
}

/* RU: Ловушка для ботов убрана с экрана приёмом «только для чтения с экрана», но ещё и `aria-hidden` в разметке —
       то есть она не видна никому из людей. `display: none` не используем: часть ботов пропускает именно такие поля.
   EN: The bot honeypot is taken off screen with the visually-hidden technique, plus `aria-hidden` in the markup —
       so no person perceives it. `display: none` is avoided: some bots skip exactly such fields. */
.lead-trap {
  position: absolute;
  top: 0;
  left: 0;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.lead-status {
  padding: 0.5rem 0.65rem;
  border-radius: var(--k-r-2);
  font-size: 0.85rem;
  line-height: 1.4;
}

/* RU: Пустая строка состояния не исчезает из дерева (живая область обязана существовать до появления текста), а
       лишь не занимает места.
   EN: The empty status line does not leave the tree (a live region must exist before text appears); it merely
       takes no room. */
.lead-block > .lead-status:empty {
  margin: 0;
  padding: 0;
}

.lead-status.is-ok {
  background: var(--k-ok-soft);
  color: var(--k-ok);
}

.lead-status.is-info {
  background: var(--k-info-soft);
  color: var(--k-info);
}

.lead-status.is-warn {
  background: var(--k-warn-soft);
  color: var(--k-warn);
}

.lead-status.is-error {
  background: var(--k-danger-soft);
  color: var(--k-danger);
}
