/* Оформление IM. Цвета, шрифты и логотипы приходят из контент-пака переменными --im-*.
   Здесь только система: стеклянные поверхности, свечение, движение, состояния, доступность. */

/* ——— токены ——— */
/* Брейкпоинты: ≤480 (mobile), 481–767 (wide mobile), 768–1023 (tablet),
   1024–1439 (desktop), ≥1440 (wide desktop); по высоте ≤500; также portrait/landscape.
   Инвариант размеров:
   viewport | 390px | 1440px
   исходный | без изменений | без изменений
   токены   | без изменений | без изменений */
:root {
  color-scheme: dark;

  --font-body: clamp(15px, calc(14.42px + .149vw), 16.5px);
  --font-sm: clamp(12px, calc(11.57px + .11vw), 13.5px);
  --font-lg: clamp(17px, calc(16.43px + .149vw), 18.5px);
  --space-1: clamp(4px, calc(3.43px + .149vw), 5.5px);
  --space-2: clamp(8px, calc(6.86px + .298vw), 11px);
  --space-3: clamp(12px, calc(10.29px + .447vw), 16.5px);
  --space-4: clamp(16px, calc(13.72px + .596vw), 22px);
  --space-5: clamp(24px, calc(20.58px + .894vw), 33px);
  --safe-top: env(safe-area-inset-top, 0px);
  --safe-right: env(safe-area-inset-right, 0px);
  --safe-bottom: env(safe-area-inset-bottom, 0px);
  --safe-left: env(safe-area-inset-left, 0px);
  --tap-min: 44px;
  /* Полоса под один плавающий орган управления: сама цель в 44 px, 12 px до кромки кадра
     под ней и 8 px просвета над ней. Слои, лежащие поверх слайда, вырезают её из слайда
     этой мерой — и стоят в ней по той же арифметике. */
  --band-step: calc(var(--tap-min) + 20px);
  /* Полоса модулей столбцом у бокового края (page.bar_position: left|right). Мера одна и
     для самой полосы, и для выреза в слайде: шире объявленного столбец не становится, а
     подпись кнопки обрезается по нему. Иначе длинное имя модуля увело бы полосу под слайд. */
  --band-rail: 232px;

  /* Лестница чернил. Уровни держат 4.5:1 не по расчёту от --void, а по замеру отрисованного
     кадра: вторичный текст лежит на стекле поверх панорамы и фона слайда, и фон под ним
     доходит до яркости 0.077. Прежние .72 и .56 держали 4.5 только до 0.086 и 0.040 — подписи
     расстояния, категории и роль агента на светлой панораме проваливались до 3.70. Уровни
     подняты со сдвигом всей лестницы: шаг в .14 сохраняет три различимых голоса. */
  /* Базовые токены темы (#86). Сборка кладёт их атрибутом style на <html>; здесь значения
     темы Brand, и страница без них выглядит как прежде. Всё остальное выводится из них.
     --ink — текст, --void — основа страницы, плотного стекла и текст на акцентной заливке,
     --tint — белый у тёмной схемы и чёрный у светлой: каймы, подсветка наведения, прозрачное
     стекло, --shade — тон затемнения поверх фото и панорам. */
  --ink: #f4f2ee;
  --ink-2: color-mix(in srgb, var(--ink) 86%, transparent);
  --ink-3: color-mix(in srgb, var(--ink) 72%, transparent);
  --void: #0e0d0c;
  --tint: #ffffff;
  --shade: #060606;

  /* Стекло: три высоты. Каждая — заливка, кайма, внутренний блик, тень. Прозрачное стекло
     (clear) — эти значения; матовое и сплошное переопределяют их по data-glass ниже.
     Блик сверху остаётся белым, тени — чёрными при любой схеме. */
  --glass-1: linear-gradient(155deg, color-mix(in srgb, var(--tint) 10%, transparent),
                             color-mix(in srgb, var(--tint) 2.8%, transparent) 62%);
  --glass-2: linear-gradient(155deg, color-mix(in srgb, var(--tint) 14%, transparent),
                             color-mix(in srgb, var(--tint) 4.5%, transparent) 62%);
  --glass-3: linear-gradient(155deg,
                             color-mix(in srgb, color-mix(in srgb, var(--void) 94.6%, var(--tint)) 92%, transparent),
                             color-mix(in srgb, color-mix(in srgb, var(--void) 99.17%, var(--tint)) 96%, transparent));
  --edge: color-mix(in srgb, var(--tint) 16%, transparent);
  --edge-strong: color-mix(in srgb, var(--tint) 28%, transparent);
  --sheen: inset 0 1px 0 rgba(255, 255, 255, .22), inset 0 -1px 0 rgba(0, 0, 0, .28);
  --lift-1: 0 8px 24px -14px rgba(0, 0, 0, .8);
  --lift-2: 0 18px 50px -18px rgba(0, 0, 0, .85);
  --lift-3: 0 40px 90px -28px rgba(0, 0, 0, .9);
  --blur: blur(20px) saturate(1.55);
  --glass-delay: 0ms;
  --glass-settle: var(--mid);

  --glow: 0 0 0 1px color-mix(in oklab, var(--im-accent) 46%, transparent),
          0 10px 34px -12px color-mix(in oklab, var(--im-accent) 62%, transparent);
  --glow-soft: 0 0 26px -8px color-mix(in oklab, var(--im-accent) 55%, transparent);

  /* Надзаголовок — тот же третий уровень, на волос светлее: он всегда набран капителью
     в 10.5 px, и на панораме это самый тяжёлый для чтения текст в колоде. */
  --eyebrow: color-mix(in srgb, var(--ink) 74%, transparent);
  /* Семантические цвета интерфейса: отказ формы, предупреждение, пропуск сборки. Статусы
     юнита ими больше не красятся — их палитра приходит из бренда документа. */
  --available: #47cf46;
  --under-offer: #e8ac17;
  --sold: #e2564e;
  --reserved: #5aa2f0;

  /* Утопленная плоскость: не стекло, а вырез в кадре. Отдельные токены, потому что
     усиленный контраст обязан её догонять — литералы бы его выключили. */
  --sunk-1: color-mix(in srgb, var(--void) 62%, transparent);
  --sunk-edge: color-mix(in srgb, var(--tint) 10%, transparent);

  --r-pill: 999px;
  --r-lg: 20px;
  --r-md: 14px;
  --r-sm: 10px;
  --bar: 64px;
  /* Высота подвала без выреза. Равна полосе шапки везде, кроме узкого портрета: там подпись
     следующего слайда встаёт своей строкой, и подвал выше. Всё, что стоит над подвалом —
     нижняя кромка слайда, призыв, панель панорамы, тост, — считается от этой меры. */
  --foot-h: var(--bar);
  --tap: 44px;
  /* Левый край колонки текста. Его же читают плитки и поле сцены плана — разъехаться им не с чего. */
  --col-left: 56px;

  --fast: 140ms;
  --mid: 260ms;
  --slow: 520ms;
  --ease: cubic-bezier(.16, 1, .3, 1);
  --ease-soft: cubic-bezier(.4, 0, .2, 1);

  /* ——— слой перекрытий ——— */
  /* Всё, что ложится поверх кадра, — попап, панель модуля, лист сортировки, панель точки,
     островок подсказки, шторка меню — берёт вид отсюда. Поводом стала ретроспектива:
     одно и то же замечание о типографике модалок пришло на трёх страницах из двенадцати,
     потому что каждая поверхность была набрана своим числом в своём селекторе.
     Правка вида слоёв идёт в эти токены, а не в шесть правил. */
  --ov-bg: var(--glass-3);          /* плотное стекло: слой кроет то, что под ним */
  --ov-bg-soft: var(--glass-1);     /* лёгкое стекло: под слоем обязан читаться кадр */
  --ov-edge: var(--edge);
  --ov-radius: var(--r-lg);
  --ov-radius-sm: var(--r-md);      /* карточка у метки: скругление на ступень мельче */
  --ov-lift: var(--sheen), var(--lift-3);       /* окно и островок посреди кадра */
  --ov-lift-soft: var(--sheen), var(--lift-2);  /* лёгкий слой: карточка, список, сводка */
  --ov-lift-edge: var(--lift-3);    /* панель у кромки: блика нет — верхней кромки в кадре нет */
  --ov-blur: var(--blur);

  /* Вуали. Четыре, потому что четыре разные задачи: обычный промах мимо панели, фон
     модального окна, фон окна, под которым слайд обязан остаться виден, и вуаль внутри
     коробки модуля, где под ней не фото, а свой же список. */
  --ov-veil: color-mix(in srgb, var(--shade) 55%, transparent);
  --ov-veil-blur: blur(2px);
  --ov-veil-modal: color-mix(in srgb, var(--shade) 68%, transparent);
  --ov-veil-modal-blur: blur(6px);
  --ov-veil-light: color-mix(in srgb, var(--shade) 42%, transparent);
  --ov-veil-inset: color-mix(in srgb, var(--void) 60%, transparent);

  /* Затемнение кадра. Раньше доли стояли числами прямо в правилах #scrim — шаблон
     накладывал на фотографию плотность, о которой проект не просил, и погасить её было
     нечем. Теперь в правилах только геометрия, а сила — значения ниже:
       --scrim-read   пик затемнения со стороны текстовой колонки;
       --scrim-span   доля ширины (или высоты), на которой оно сходит на нет;
       --scrim-frame  полосы во всю ширину под шапкой и подвалом; 0 — их нет.
     Документ, который их не просит, получает эти значения; попросить — переменной на
     корне страницы рядом с --shade. Пик снят с живого IM проекта: 0.7 у кромки, ноль к
     80 % ширины, фотография выше и ниже чистая. */
  --scrim-read: .7;
  --scrim-span: 80%;
  --scrim-frame: 0;
  /* Ниже 1024 колонка ложится сверху во всю ширину: под ней затемнение считается по высоте
     и держит строки, которым правее некуда деться. Своё значение, не пик широкого кадра. */
  --scrim-stack: .94;
  /* Подложка под строками шапки и подвала. Это хром, а не кадр: полоса стоит в высоте
     своей панели и не имеет отношения к тому, сколько плотности просит фотография. */
  --chrome-veil: .92;

  --ov-pad: 22px;               /* поля панели во всю высоту */
  --ov-pad-head: 18px 20px;     /* шапка окна */
  --ov-pad-body: 20px;          /* тело окна */
  --ov-pad-card: 16px 18px;     /* островок подсказки */
  --ov-head-gap: 12px;          /* просвет в шапке слоя; окно ставит свой */

  /* Шкала заголовков слоя. Три голоса и один размер текста — больше в слоях не набирается. */
  --ov-title: 22px;                   /* окно на широком кадре */
  --ov-title-compact: var(--font-lg); /* лист у нижней кромки и панель сортировки */
  --ov-title-card: 19px;              /* островок подсказки */
  --ov-text: 13.5px;                  /* текст слоя под заголовком */

  --ov-move: var(--slow) var(--ease);       /* выезд панели и листа */
  --ov-fade: var(--mid) var(--ease-soft);   /* проявление слоя и вуали */

  /* Этажи слоёв поверх кадра. Числа живут здесь и только здесь: соседние этажи держат не
     вид, а порядок нажатия, и путать их дорого. Разобранный дефект: вуаль панели встала
     над полосой кнопок модулей, и нажатие по соседнему модулю доставалось вуали — вместо
     переключения панель закрывалась (`bar-above-veil` в tests/site/run.mjs).
     Полоса кнопок и сама панель стоят на одном этаже намеренно: их разводит порядок узлов.
     Шкала холста (1—9: сцена, фон, загрузка, затемнение, зерно) и шкала внутри коробки
     модуля — свои, и в эту лестницу не входят. */
  --z-slide: 10;
  --z-chrome: 12;       /* шапка и подвал */
  --z-float: 13;        /* плавает над хромой: призыв, карточка точки при наведении */
  --z-progress: 14;
  --z-poi-panel: 16;    /* немодальная панель точки: выше хромы, ниже шторки */
  --z-module-veil: 17;
  --z-module: 18;       /* панель модуля */
  --z-module-bar: 19;   /* полоса остаётся нажимаемой поверх открытой панели */
  /* Шторка меню выше полосы модулей. Равные этажи здесь не работают: в root полоса стоит
     ПОСЛЕ вуали меню, и на равном этаже она рисовалась бы поверх вуали — меню открыто, а
     кнопки модулей видны и нажимаются сквозь него. Каждому слою свой этаж. */
  --z-menu-veil: 20;
  --z-menu: 21;
  --z-tour-veil: 24;
  --z-tour-ring: 25;
  --z-tour-card: 26;
  --z-toast: 30;
  /* Внутри коробки модуля своя пара: лист сортировки кроет список, а не кадр. */
  --z-inset-veil: 3;
  --z-inset-sheet: 4;
}
/* Светлая схема: тёмный текст на светлой основе. Браузерные поля, полосы прокрутки и
   выпадающие списки рисуются светлыми. */
:root[data-scheme="light"] { color-scheme: light; }

/* Матовое стекло: плотная заливка цвета основы, кадр под панелью только угадывается.
   Кайма и подсветка — от --tint, как у прозрачного. */
:root[data-glass="frosted"] {
  --glass-1: linear-gradient(155deg, color-mix(in srgb, var(--void) 80%, transparent),
                             color-mix(in srgb, var(--void) 68%, transparent) 62%);
  --glass-2: linear-gradient(155deg,
                             color-mix(in srgb, color-mix(in srgb, var(--void) 94%, var(--tint)) 86%, transparent),
                             color-mix(in srgb, color-mix(in srgb, var(--void) 94%, var(--tint)) 74%, transparent) 62%);
  --glass-3: linear-gradient(155deg,
                             color-mix(in srgb, color-mix(in srgb, var(--void) 95%, var(--tint)) 94%, transparent),
                             color-mix(in srgb, var(--void) 96%, transparent));
  --edge: color-mix(in srgb, var(--tint) 14%, transparent);
  --edge-strong: color-mix(in srgb, var(--tint) 26%, transparent);
  --sunk-1: color-mix(in srgb, var(--void) 74%, transparent);
}
/* Сплошное стекло: непрозрачная основа и твёрдая кайма. */
:root[data-glass="solid"] {
  --glass-1: color-mix(in srgb, var(--void) 94%, transparent);
  --glass-2: color-mix(in srgb, color-mix(in srgb, var(--void) 90%, var(--tint)) 96%, transparent);
  --glass-3: color-mix(in srgb, var(--void) 96%, transparent);
  --edge: color-mix(in srgb, var(--tint) 32%, transparent);
  --edge-strong: color-mix(in srgb, var(--tint) 52%, transparent);
  --sunk-1: color-mix(in srgb, var(--void) 94%, transparent);
  --sunk-edge: color-mix(in srgb, var(--tint) 32%, transparent);
}

/* Пожелание «уменьшить движение» касается движения, а не всех переходов. Сдвиги, масштаб,
   параллакс и автоповорот снимаются полностью; затухание по прозрачности и цвету остаётся —
   вестибулярный аппарат оно не трогает, зато именно оно не даёт картинке щёлкать. Полное
   обнуление делало интерфейс рваным: всё менялось мгновенно, ровно на это и была жалоба. */
@media (prefers-reduced-motion: reduce) {
  :root { --fast: 120ms; --mid: 200ms; --slow: 260ms; --pano-glide: 0ms; }
  *, *::before, *::after {
    transition-property: opacity, color, background-color, border-color, box-shadow,
                         fill, stroke, backdrop-filter, filter !important;
    animation-duration: 200ms !important;
    animation-timing-function: linear !important;
    scroll-behavior: auto !important;
  }
  /* Кадровые анимации со сдвигом заменяются чистым проявлением. */
  #slide > *, .tour-card, dialog[open], dialog::backdrop, .im-pano-marker,
  .missing, #toast { animation-name: soften !important; }
  #slide[data-leaving="1"] > * { animation-name: soften-out !important; }
  #backdrop[data-enter="1"], .pill::before { animation: none !important; }
  /* Бесконечный цикл под общее «200 мс» становится миганием пять раз в секунду: подмена
     имени на soften гоняла метку от нуля до единицы по прозрачности, а индикатор ожидания
     сжимался с 1.6 с до 0.2 с. Кольцо метки движения не даёт — под сокращением движения оно
     снимается целиком; индикатору возвращается его период. */
  .im-pano-marker--link { animation: none !important; }
  #loading[data-state="wait"]::before { animation-duration: 1600ms !important; }
}
@keyframes soften { from { opacity: 0; } }
@keyframes soften-out { to { opacity: 0; } }
/* Усиленный контраст выводится из токенов темы и стоит после блоков стекла: селектор
   с [data-glass] равен им по весу и берёт порядком. */
@media (prefers-contrast: more) {
  :root, :root[data-glass] {
    --ink-2: color-mix(in srgb, var(--ink) 96%, transparent);
    --ink-3: color-mix(in srgb, var(--ink) 86%, transparent);
    --eyebrow: color-mix(in srgb, var(--ink) 88%, transparent);
    --edge: color-mix(in srgb, var(--tint) 40%, transparent);
    --glass-1: color-mix(in srgb, var(--void) 94%, transparent);
    --glass-2: color-mix(in srgb, var(--void) 96%, transparent);
    --sunk-1: color-mix(in srgb, var(--void) 94%, transparent);
    --sunk-edge: color-mix(in srgb, var(--tint) 40%, transparent);
  }
  /* Усиленный контраст тишины не признаёт: тихая кнопка получает вид обычной. Блок стоит
     выше правила самой кнопки, отсюда :root в селекторе. */
  :root .pill--quiet { color: var(--ink); border-color: var(--edge); background: var(--glass-1); }
}

* { box-sizing: border-box; }
/* Авторское правило с display перебивает браузерное [hidden], и скрытый элемент остаётся
   на экране. Возвращаем скрытие всем, кроме слоёв, которые гасятся с затуханием ниже. */
[hidden]:not(#stage):not(.tour-veil):not(.tour-ring):not(.tour-card) { display: none !important; }
html, body { height: 100%; }
body {
  margin: 0; overflow: hidden;
  background: var(--void);
  color: var(--ink);
  font-family: var(--im-font-base), ui-sans-serif, system-ui, sans-serif;
  /* Вес тела — начертание из файлов пака (--im-body-weight, tools/im_style.py): у семейства из
     одного тонкого файла телу нужен его вес, а не 400. Без файлов переменной нет — вес прежний. */
  font-weight: var(--im-body-weight, 400);
  font-size: 15px; line-height: 1.6;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
/* Пока шторка открыта, кадр под ней не ездит: вуаль перехватывает касание, а
   overscroll-behavior гасит отскок страницы за её краем. */
body[data-menu-open="1"] { overscroll-behavior: none; }
/* Шрифт заголовков: роль accent (brand.headingFont) кладёт --im-font-heading и вес ближайшего
   жирного начертания акцентного семейства. Роли нет — акцентный шрифт и вес 400, как были. */
h1, h2, h3 {
  font-family: var(--im-font-heading, var(--im-font-accent)), ui-serif, Georgia, serif;
  font-weight: var(--im-heading-weight, 400); margin: 0; letter-spacing: -.015em; line-height: 1.12;
}
p { margin: 0; }
img { max-width: 100%; }

button, a, summary, input, textarea, select, [role="button"], [tabindex]:not([tabindex="-1"]) {
  transition: color var(--fast) var(--ease-soft), background-color var(--fast) var(--ease-soft),
              border-color var(--fast) var(--ease-soft), box-shadow var(--mid) var(--ease),
              opacity var(--fast) var(--ease-soft), transform var(--fast) var(--ease-soft),
              visibility 0ms linear var(--fast);
}
button:active, a:active, summary:active, [role="button"]:active {
  transform: scale(.98);
}

/* ——— фокус ——— */
:focus-visible {
  outline: 2px solid var(--im-accent);
  outline-offset: 3px;
  border-radius: var(--r-sm);
}
.skip {
  position: absolute; left: 12px; top: -60px; z-index: 100;
  padding: 10px 16px; border-radius: var(--r-pill);
  background: var(--im-accent); color: var(--void); text-decoration: none;
  transition: top var(--mid) var(--ease);
}
.skip:focus { top: 12px; }
.sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

/* ——— стеклянная кнопка ——— */
.pill {
  position: relative; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: var(--tap); padding: 10px 20px;
  font: inherit; font-size: 14px; color: var(--ink);
  border: 1px solid var(--edge); border-radius: var(--r-pill);
   background: var(--glass-1);
   box-shadow: var(--sheen), var(--lift-1);
   cursor: pointer; overflow: hidden; white-space: nowrap;
   /* Размытие у кнопки постоянное, поэтому она не ездит: нажатие показывается тенью и фоном.
      Стекло и позиционная анимация на одном элементе не уживаются — из-за этого его и снимали. */
   backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
   transition: color var(--fast) var(--ease-soft), border-color var(--fast) var(--ease-soft),
              box-shadow var(--mid) var(--ease),
              background var(--fast) var(--ease-soft);
}
/* Блик: узкая световая полоса проходит по кнопке при наведении. */
.pill::before {
  content: ""; position: absolute; inset: 0; z-index: -1; opacity: 0;
  background: linear-gradient(105deg, transparent 32%,
    color-mix(in oklab, var(--im-accent) 34%, transparent) 48%, transparent 64%);
  transform: translateX(-120%);
  transition: opacity var(--fast) linear;
}
.pill:hover::before { opacity: 1; animation: sweep var(--slow) var(--ease); }
@keyframes sweep { to { transform: translateX(120%); } }
.pill:hover {
  border-color: var(--edge-strong);
  background: var(--glass-2);
  box-shadow: var(--sheen), var(--lift-2), var(--glow-soft);
  transform: translateY(-1px);
}
.pill:active { box-shadow: inset 0 1px 3px rgba(0, 0, 0, .35); background: var(--glass-2); }
.pill[aria-pressed="true"], .pill[aria-current="true"], .pill[aria-selected="true"] {
  color: var(--void);
  background: var(--im-accent);
  border-color: transparent;
  box-shadow: var(--glow);
}
.pill[disabled] { opacity: .34; pointer-events: none; }
.pill--ghost { background: transparent; box-shadow: none; }
.pill--icon { width: var(--tap); padding: 0; border-radius: 50%; }
/* Тихая кнопка: справка рядом с навигацией. Без каймы, блика и тени, иконка приглушена.
   Подложка затемняет то, что под ней: на тёмном слайде её не видно, на светлой панораме
   она держит иконке контраст от 3:1. Наведение и фокус возвращают полный вид кнопки. */
.pill--quiet {
  color: var(--ink-3);
  border-color: transparent;
  background: color-mix(in srgb, var(--void) 12%, transparent);
  box-shadow: none;
  backdrop-filter: var(--blur) brightness(.7); -webkit-backdrop-filter: var(--blur) brightness(.7);
}
/* На светлой схеме иконка тёмная: подложка не темнит кадр под собой, а высветляет. */
:root[data-scheme="light"] .pill--quiet {
  backdrop-filter: var(--blur) brightness(1.3); -webkit-backdrop-filter: var(--blur) brightness(1.3);
}
.pill--quiet:is(:hover, :focus-visible) {
  color: var(--ink);
  border-color: var(--edge-strong);
  background: var(--glass-2);
  box-shadow: var(--sheen), var(--lift-2), var(--glow-soft);
}

.glass {
  background: var(--glass-1); border: 1px solid var(--edge); border-radius: var(--r-lg);
  box-shadow: var(--sheen), var(--lift-2);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
}
.glass-move {
  --glass-delay: var(--glass-settle);
  backdrop-filter: none !important; -webkit-backdrop-filter: none !important;
}
.glass-move--rest {
  transition: backdrop-filter var(--glass-delay) linear,
              -webkit-backdrop-filter var(--glass-delay) linear;
  backdrop-filter: var(--blur) !important; -webkit-backdrop-filter: var(--blur) !important;
}

/* ——— слой перекрытий: общие классы ——— */
/* Каждый класс отвечает за одну вещь: стекло слоя, кайма со скруглением, геометрия панели
   во всю высоту, вуаль под слоем (с вариантом для коробки модуля), шапка слоя и крестик в
   ней. Всё, чем слои законно отличаются, — ширина, сторона, этаж, направление выезда,
   тень, — остаётся в правиле самого слоя: панель во всю высоту не попап. */
.ov-surface {
  background: var(--ov-bg);
  backdrop-filter: var(--ov-blur); -webkit-backdrop-filter: var(--ov-blur);
}
/* Слой, который стоит посреди кадра, а не у его кромки: кайма кругом и скругление. */
.ov-framed { border: 1px solid var(--ov-edge); border-radius: var(--ov-radius); }
/* Панель во всю высоту видимой части окна. Сторону, ширину, этаж и кайму задаёт сама
   панель: шторка меню приезжает слева, панель модуля справа. */
.ov-panel {
  position: absolute; top: var(--vv-top, 0px); bottom: auto;
  height: var(--vv-h, 100%);
  display: flex; flex-direction: column;
  padding: var(--ov-pad);
  padding-top: calc(var(--ov-pad) + var(--safe-top));
  padding-bottom: calc(var(--ov-pad) + var(--safe-bottom));
  box-shadow: var(--ov-lift-edge);
  transition: transform var(--ov-move);
  overflow: hidden; overscroll-behavior: contain;
}
/* Вуаль — она же промах мимо слоя. Закрытая указателя не ловит: иначе она глушит то,
   что под ней, даже будучи невидимой. */
.ov-veil {
  position: absolute; inset: 0;
  background: var(--ov-veil);
  backdrop-filter: var(--ov-veil-blur); -webkit-backdrop-filter: var(--ov-veil-blur);
  opacity: 0; pointer-events: none;
  transition: opacity var(--ov-fade);
}
.ov-veil[data-open="1"] { opacity: 1; pointer-events: auto; }
/* Вуаль внутри коробки модуля: под ней не фото и не панорама, а свой же список, поэтому
   тон идёт от основы страницы и размытия нет — второй слой стекла там лишний. */
.ov-veil--inset {
  background: var(--ov-veil-inset);
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* Стекло вуалей в кадре билдера выключено (C27). Кадр превью сжат CSS до дробного масштаба
   (0.658 на кадре 1600×900), и Chromium в таком кадре снимает подложку backdrop-filter
   размером со сжатый растр, а всё ниже достраивает её зеркалом: под вуалью модуля ровно с
   592-й строки документа появлялась перевёрнутая копия заголовка, подзаголовка и кнопки
   носителя. Дубля в DOM нет и правила отражения тоже; измерено — призрак снимает только
   отсутствие backdrop-filter: ни position: fixed, ни clip-path, ни маска, ни свой слой,
   ни backdrop root на #app его не трогают. Тон вуали остаётся, вне кадра остаётся и стекло.
   Признак кадра ставит конструктор Site по тому же ?panel=1. */
:root[data-panel="1"] { --ov-veil-blur: none; --ov-veil-modal-blur: none; }
:root[data-panel="1"] .ov-veil,
:root[data-panel="1"] dialog::backdrop {
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* Шапка слоя: имя слева, крестик справа. Просвет — токеном, окно ставит свой. */
.ov-head { display: flex; align-items: center; gap: var(--ov-head-gap); }
.ov-close { margin-left: auto; flex: none; }

/* ——— каркас ——— */
#app { position: fixed; inset: 0; overflow: hidden; }
#app[data-menu-open="1"] #slide,
#app[data-menu-open="1"] #slide > *,
#app[data-menu-open="1"] #stage { pointer-events: none; touch-action: none; }
/* Пока поверх слайда лежит модальный попап или островок подсказки, кадр под ними не ездит:
   ::backdrop и вуаль перехватывают касание, touch-action снимает жест прокрутки, а
   overscroll-behavior гасит отскок страницы за краем слоя. Саму прокрутку слайда при этом
   не обнуляем: когда слой уйдёт, позиция нужна та же.
   Панель точки локации сюда не входит — она немодальная, и метки со списком под ней
   остаются рабочими: ради этого она такой и сделана. */
#app[data-dialog-open="modal"] #slide, #app[data-tour-open="1"] #slide { touch-action: none; }
body[data-dialog-open="modal"], body[data-tour-open="1"] { overscroll-behavior: none; }
#stage, #backdrop { position: absolute; inset: 0; }
/* Порядок слоёв задан явно. Холст держит свой контекст наложения, потому что глубина меток
   задаётся z-index: без изоляции метка всплывает поверх заголовка слайда. А раз у холста
   появился слой, у всего, что рисуется поверх него, слой тоже обязан быть назван. */
#stage { z-index: 1; isolation: isolate; }
#backdrop, #backdrop-next { z-index: 2; }
#loading { z-index: 3; }
#scrim { z-index: 4; }
#stage { transition: opacity var(--mid) var(--ease-soft), visibility 0ms linear var(--mid); }
#stage[hidden] {
  display: block;
  opacity: 0; visibility: hidden; pointer-events: none;
}
#stage canvas { display: block; transition: opacity var(--mid) var(--ease-soft); }
#backdrop, #backdrop-next {
  background-size: cover; background-position: center;
  pointer-events: none; /* панораму крутят мышью, слой над холстом не должен её ловить */
  transition: opacity var(--slow) var(--ease), transform var(--slow) var(--ease);
  animation: bg-breathe 22s ease-in-out infinite;
}
/* Слайд с планом не показывает фотографию: под план идёт спокойный фирменный градиент. */
#backdrop[data-mode="gradient"], #backdrop-next[data-mode="gradient"] {
  background-image:
    radial-gradient(85% 65% at 18% 12%,
      color-mix(in oklab, var(--im-secondary) 62%, transparent), transparent 66%),
    radial-gradient(70% 60% at 86% 88%,
      color-mix(in oklab, var(--im-accent) 40%, transparent), transparent 62%),
    radial-gradient(120% 90% at 50% 50%, transparent 40%,
      color-mix(in srgb, var(--shade) 72%, transparent) 100%),
    linear-gradient(150deg, color-mix(in srgb, var(--void) 93.5%, var(--tint)),
      color-mix(in srgb, var(--void) 62.5%, var(--shade)) 80%);
  background-color: var(--im-primary);
}
#backdrop-next { position: absolute; inset: 0; opacity: 0; }
#backdrop-next[data-on="1"] { opacity: 1; }
@keyframes bg-breathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.015); } }
@media (prefers-reduced-motion: reduce) {
  #backdrop, #backdrop-next { animation: none !important; }
}
/* Два режима затемнения лежат отдельными слоями и переключаются прозрачностью. Через одно
   свойство background это не работает: у обычного и панорамного набора разное число стопов,
   такие градиенты не интерполируются — переход шёл мгновенной подменой. */
#scrim {
  position: absolute; inset: 0; pointer-events: none;
  transition: opacity var(--mid) var(--ease-soft);
}
#scrim::before, #scrim::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  transition: opacity var(--mid) var(--ease-soft);
}
#scrim::before {
  opacity: 1;
  background:
    radial-gradient(120% 90% at 8% 50%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 100%), transparent) 0%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 60%), transparent) 40%,
      transparent 70%),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--shade) calc(var(--scrim-frame) * 92%), transparent) 0%,
      transparent 22%, transparent 74%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-frame) * 100%), transparent) 100%);
}
#scrim.scrim--pano::before { opacity: 0; }
/* Над панорамой затемнение живёт только слева и сходит на нет к --scrim-span. Слой линейный
   по горизонтали: у эллипса радиус считается от коробки, поэтому граница выгибалась дугой и
   ползла вместе с высотой кадра. Второй слой — полосы во всю ширину под шапкой и подвалом;
   при --scrim-frame: 0 их нет, и панорама выше и ниже колонки остаётся нетронутой. */
#scrim::after {
  opacity: 0;
  background:
    linear-gradient(to right,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 100%), transparent) 0%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 74%), transparent) 34%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 40%), transparent) 55%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-read) * 14%), transparent) 70%,
      transparent var(--scrim-span)),
    linear-gradient(to bottom,
      color-mix(in srgb, var(--shade) calc(var(--scrim-frame) * 86%), transparent) 0%,
      transparent 16%, transparent 80%,
      color-mix(in srgb, var(--shade) calc(var(--scrim-frame) * 100%), transparent) 100%);
}
#scrim.scrim--pano::after { opacity: 1; }

/* ——— шапка ——— */
/* Вырез прибавляется к высоте полосы, а не съедает её: при height и box-sizing: border-box
   отступ под чёлку отбирал место у бургера. Боковые вырезы — то же самое в ландшафте. */
#head {
  position: absolute; inset: 0 0 auto 0; height: var(--bar); z-index: var(--z-chrome);
  min-height: calc(var(--bar) + var(--safe-top));
  display: flex; align-items: center; gap: 16px;
  padding: 0 calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  padding-top: var(--safe-top); pointer-events: none;
}
/* Подложка шапки и подвала. Строки хрома лежат на фотографии, и читаться им нечем — раньше
   их держали полосы затемнения кадра во всю его высоту: ради двух строк гасла вся картинка.
   Полоса принадлежит хрому и живёт в его высоте с небольшим выходом за кромку; кадр между
   шапкой и подвалом остаётся нетронутым. Сила — значением, а не числом в правиле. */
#head::before, #foot::before {
  content: ""; position: absolute; left: 0; right: 0; z-index: -1; pointer-events: none;
}
#head::before {
  top: 0; bottom: -20px;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--shade) calc(var(--chrome-veil) * 100%), transparent), transparent);
}
#foot::before {
  top: -20px; bottom: 0;
  background: linear-gradient(to top,
    color-mix(in srgb, var(--shade) calc(var(--chrome-veil) * 100%), transparent), transparent);
}

/* Полоса шапки во всю ширину кадра лежит над панорамой: пустое место пропускает указатель
   к меткам и холсту, органы управления забирают его обратно правилом без специфичности —
   состояния вроде .pill[disabled] по-прежнему его гасят. Так же устроен подвал. */
:where(#head > *) { pointer-events: auto; }
#burger span { display: block; width: 17px; height: 1.5px; background: currentColor;
  position: relative; transition: transform var(--mid) var(--ease); }
#burger span::before, #burger span::after {
  content: ""; position: absolute; left: 0; width: 17px; height: 1.5px; background: currentColor;
  transition: transform var(--mid) var(--ease), opacity var(--fast) linear;
}
#burger span::before { top: -5.5px; }
#burger span::after { top: 5.5px; }
#burger[aria-expanded="true"] span { transform: rotate(45deg); }
#burger[aria-expanded="true"] span::before { transform: translateY(5.5px) rotate(-90deg); }
#burger[aria-expanded="true"] span::after { opacity: 0; }

/* Рельс точек. Держит его оформление или нет — решает оформление, и говорит это вслух:
   `--rail` со значением on или off. Скрипт (_syncRail в tools/im_site.js) читает свойство по
   имени и сворачивает рельс в счётчик «3 / 10», когда оформление сказало off. Через display
   он это читал раньше, и порог, стоящий в @media ниже, менял поведение скрипта молча. */
#dots { display: flex; align-items: center; --rail: on; }
#dots .dot {
  position: relative; width: 26px; height: var(--tap); padding: 0;
  border: 0; background: none; cursor: pointer; display: grid; place-items: center;
}
#dots .dot::after {
  content: ""; width: 9px; height: 9px; border-radius: 50%;
  border: 1px solid var(--edge-strong); background: transparent;
  transition: background var(--mid) var(--ease), transform var(--mid) var(--ease),
              box-shadow var(--mid) var(--ease), border-color var(--mid) var(--ease);
}
#dots .dot::before {
  content: ""; position: absolute; left: 0; width: 10px; height: 1px; background: var(--edge);
  transform: translateX(-50%);
}
#dots .dot:first-child::before { display: none; }
#dots .dot[data-seen="1"]::after { background: var(--ink-3); border-color: transparent; }
#dots .dot[aria-current="true"]::after {
  background: var(--im-accent); border-color: transparent;
  transform: scale(1.5); box-shadow: var(--glow-soft);
}
#dots .dot:hover::after { transform: scale(1.35); border-color: var(--im-accent); }
/* Десять точек на телефоне дают шаг в 26 px — пальцем не попасть. Рельс сворачивается
   в счётчик «3 / 10», а он открывает то же меню: полный список слайдов там доступен
   и мышью, и с клавиатуры, и скринридером. */
#slide-count {
  margin-left: auto; flex: none;
  min-width: var(--tap); min-height: var(--tap); padding: 0 14px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--edge); border-radius: var(--r-pill);
  background: var(--glass-1); color: var(--ink-2);
  box-shadow: var(--sheen), var(--lift-1);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
  font: inherit; font-size: 13px; font-variant-numeric: tabular-nums;
  letter-spacing: .04em; white-space: nowrap; cursor: pointer;
}
#slide-count .current { color: var(--im-accent); }
/* Разделитель гасился прозрачностью в половину, и от --ink-2 оставалось .36 — 2.94 к 1 на
   стекле счётчика. Тише соседних цифр он остаётся уровнем чернил, а не прозрачностью. */
#slide-count .sep { color: var(--ink-3); padding: 0 3px; }
#slide-name {
  font-family: var(--im-font-base), sans-serif;
  font-size: 12px; font-weight: var(--im-body-weight, 400); letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-2); white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  max-width: min(30ch, calc(50vw - 250px));
}
#enquire-top { position: absolute; top: 10px; left: 50%; transform: translateX(-50%); z-index: var(--z-float); }

/* Полоса прогресса по всей ширине: сколько колоды пройдено. */
#progress {
  position: absolute; inset: 0 0 auto 0; height: 2px; z-index: var(--z-progress);
  background: color-mix(in srgb, var(--tint) 7%, transparent);
}
#progress i {
  display: block; height: 100%; width: 0;
  background: linear-gradient(to right,
    color-mix(in oklab, var(--im-accent) 55%, transparent), var(--im-accent));
  box-shadow: var(--glow-soft);
  transition: width var(--slow) var(--ease);
}

/* Зерно: тонкий шум поверх кадра, чтобы градиенты не полосили. */
#grain {
  position: absolute; inset: 0; z-index: 9; pointer-events: none; opacity: .5;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml;utf8,\
<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'>\
<filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/>\
<feColorMatrix type='saturate' values='0'/></filter>\
<rect width='120' height='120' filter='url(%23n)' opacity='.42'/></svg>");
}

/* ——— подвал ——— */
/* Высота — нижняя граница, не потолок: на узком экране подпись следующего слайда
   переносится на вторую строку, и при фиксированной высоте её срезал край кадра. */
#foot {
  position: absolute; inset: auto 0 0 0; z-index: var(--z-chrome);
  min-height: calc(var(--foot-h) + var(--safe-bottom));
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  padding-bottom: var(--safe-bottom); pointer-events: none;
}
#foot > * { pointer-events: auto; }
/* Промежутки ряда кнопок забирали нажатие по метке панорамы под ними. Кнопкам указатель
   возвращается правилом без специфичности: .pill[disabled] гасит его и дальше. */
#foot > .nav { display: flex; gap: 10px; flex: none; pointer-events: none; }
:where(#foot > .nav > *) { pointer-events: auto; }
#next-name { min-width: 0; font-size: 13px; color: color-mix(in srgb, var(--ink) 68%, transparent); letter-spacing: .04em; }
/* Светлое затемнение у нижней кромки гасит фото слабее тёмного: при .68 подпись на светлой
   схеме падала до 3.46 к 1 по кадру. Там она идёт вторым уровнем чернил. */
:root[data-scheme="light"] #next-name { color: var(--ink-2); }
/* Узкий портрет. Ряд из пяти кнопок оставлял подписи 98 px на 390: она шла в две строки
   вплотную к ряду, зазор 0 px, и на 360 её приходилось прятать. Подпись встаёт своей строкой
   под рядом — 10 px над рядом, 4 px между рядом и подписью, 12 px до выреза, — и видна на
   любой ширине телефона. Строка одна: длинное имя слайда из пака не растит подвал поверх
   слайда. Узкий ландшафт остаётся при прежнем подвале — высоты там нет.
   Границы в условии — литералы, @media значение var() не читает. */
@media (max-width: 599px) and (min-height: 501px) {
  :root { --foot-h: calc(10px + var(--tap-min) + 4px + 16px + 12px); }
  #foot {
    flex-direction: column; align-items: stretch; justify-content: flex-start; gap: 4px;
    padding-top: 10px; padding-bottom: calc(12px + var(--safe-bottom));
  }
  #foot .nav { align-self: flex-start; }
  #next-name {
    height: 16px; line-height: 16px;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}
/* Узкий и низкий кадр: подпись рядом с рядом из пяти кнопок ушла бы в три строки. */
@media (max-width: 380px) and (max-height: 500px) { #next-name { display: none; } }

/* ——— меню ——— */
/* Вуаль и шторка — .ov-veil и .ov-panel: тон, размытие, поля и выезд приходят оттуда. */
#menu-veil { z-index: var(--z-menu-veil); }
/* Шторка стоит по видимой части окна, а не по вёрстке: поднятая клавиатура на iOS
   layout viewport не трогает, и низ панели — контакты и правовые ссылки — уходил под
   неё. `--vv-h` и `--vv-top` пишет скрипт из visualViewport; без него оба значения
   дают ровно прежний столбец во всю высоту кадра. */
#menu {
  right: auto; left: 0;
  width: min(420px, 90vw); z-index: var(--z-menu);
  gap: 20px;
  padding-right: calc(var(--ov-pad) + var(--safe-right));
  padding-left: calc(var(--ov-pad) + var(--safe-left));
  border-right: 1px solid var(--ov-edge);
  transform: translateX(-102%);
  /* Горизонтальный жест остаётся у нас: браузеру он нужен только для отката страницы. */
  touch-action: pan-y;
}
/* Шапка панели не прокручивается: логотип и крестик обязаны быть видны всегда. */
.menu-top { flex: none; }
.menu-top img { max-width: 200px; max-height: 56px; display: block; }
.menu-top strong { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.menu-body {
  min-height: 0; flex: 1 1 auto; display: flex; flex-direction: column; gap: 20px;
  overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; touch-action: pan-y;
}
/* Блоки внутри держат свою высоту: сжатые до нуля, они роняли контакты и правовые
   ссылки за собственную коробку, и порядок на экране переставал совпадать с разметкой. */
.menu-body > * { flex: none; }
/* Закрытая панель выключается атрибутом inert: он же убирает её из порядка обхода.
   visibility здесь не годится — фокус нельзя поставить в тот же кадр, что и открытие. */
#menu[inert] { pointer-events: none; }
/* Свайп влево ведёт шторку за пальцем; порог и сам сдвиг считает скрипт. */
#menu[data-open="1"] { transform: translateX(var(--swipe-x, 0px)); }
#menu[data-drag="1"] {
  transition: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
@media (prefers-reduced-motion: reduce) {
  #menu[data-open="1"] { transform: none !important; }
}
/* Низкое окно и поднятая клавиатура — один и тот же случай, и оба видны только из
   visualViewport. Признак ставит скрипт, медиазапрос по высоте про клавиатуру не знает. */
#menu[data-compact="1"] { gap: 12px; }
#menu[data-compact="1"] .menu-body { gap: 12px; }
#menu[data-compact="1"] .menu-top img { max-height: 34px; }
#menu[data-compact="1"] ol { gap: 0; }
#menu[data-compact="1"] ol button { padding-top: 8px; padding-bottom: 8px; }
#menu[data-compact="1"] .cta-copy { display: none; }
#menu[data-compact="1"] .contacts { gap: 6px; padding-top: 10px; }
#menu ol { list-style: none; margin: 0; padding: 0; display: grid; gap: 2px; counter-reset: s; }
#menu ol button {
  width: 100%; min-height: var(--tap); display: flex; align-items: baseline; gap: 14px;
  padding: 11px 14px; border: 0; border-radius: var(--r-md); cursor: pointer;
  background: transparent; color: var(--ink-2); font: inherit; text-align: left;
  transition: background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft),
              padding-left var(--mid) var(--ease);
}
#menu ol button .num { font-size: 11px; letter-spacing: .1em; color: var(--ink-3); min-width: 20px; }
#menu ol button:hover { background: color-mix(in srgb, var(--tint) 7%, transparent); color: var(--ink); padding-left: 20px; }
#menu ol button[aria-current="true"] { color: var(--im-accent); }
#menu ol button[aria-current="true"] .num { color: var(--im-accent); }
#menu .contacts { margin-top: auto; display: grid; gap: 8px; padding-top: 16px;
  border-top: 1px solid var(--edge); }
#menu .contacts a { color: var(--ink-2); text-decoration: none; min-height: var(--tap);
  display: flex; align-items: center; gap: 10px; transition: color var(--fast) var(--ease-soft); }
#menu .contacts a svg { flex: none; }
#menu .contacts a:hover { color: var(--im-accent); }
#menu .contacts .pill { width: 100%; margin-top: 4px; }
/* Имя контакта стоит строкой над его телефоном и почтой; первое — без отступа сверху. */
#menu .contacts .contact-name { color: var(--ink); font-size: 13px; padding-top: 6px; }
#menu .contacts .contact-name:first-child { padding-top: 0; }
#menu .legal { display: flex; gap: 14px; flex-wrap: wrap; padding-top: 2px; font-size: 11px; }
#menu .legal a { color: var(--ink-3); text-decoration: none; min-height: var(--tap);
  display: inline-flex; align-items: center; }
#menu .legal a:hover { color: var(--im-accent); }
#menu .legal .unset { color: var(--ink-3); min-height: var(--tap);
  display: flex; align-items: center; cursor: help; }
/* Шестерёнка правки прижата к правому краю подвала: правовые ссылки читает зритель,
   а эта кнопка — владельца, и в общий ряд подписей она не встаёт. */
#menu .legal .legal-edit { margin-left: auto; opacity: .5; }
#menu .legal .legal-edit svg { width: 15px; height: 15px; }
#menu .legal .legal-edit:hover, #menu .legal .legal-edit:focus-visible { opacity: 1; }

/* Меню выключено паком (sideMenu: off): его телефон, почта и правовые ссылки — строкой подвала
   справа, тем же тихим тоном, каким они стояли внизу шторки. Правило — template/modules.json,
   page.side_menu.without_menu. */
#foot-legal {
  display: flex; align-items: center; justify-content: flex-end; flex-wrap: wrap;
  gap: 4px 14px; min-width: 0; font-size: 11px; letter-spacing: .04em;
}
#foot-legal a, #foot-legal .unset {
  color: var(--ink-3); text-decoration: none; min-height: var(--tap);
  display: inline-flex; align-items: center; gap: 6px;
  transition: color var(--fast) var(--ease-soft);
}
#foot-legal a:hover { color: var(--im-accent); }
#foot-legal .unset { cursor: help; }
#foot-legal .contact-name { color: var(--ink-2); }
#foot-legal svg { width: 14px; height: 14px; flex: none; }
#foot-legal .legal-edit { opacity: .5; }
#foot-legal .legal-edit:hover, #foot-legal .legal-edit:focus-visible { opacity: 1; }
/* Узкий портрет: подвал стоит столбцом, и строка встаёт третьей. Подвал растёт на её высоту —
   иначе строка легла бы на слайд. Телефон и почта остаются значками с именем в aria-label. */
@media (max-width: 599px) and (min-height: 501px) {
  #app[data-menu="off"] { --foot-h: calc(10px + var(--tap-min) + 4px + 16px + 12px + 28px); }
  #foot-legal { justify-content: flex-start; }
  #foot-legal a, #foot-legal .unset { min-height: 28px; }
  #foot-legal a > span { display: none; }
}

/* ——— модули панелью ——— */
/* Режим `navigation: panel`: носитель остаётся на экране, модуль открывается панелью поверх
   него. Признак ставит скрипт на #app и body по site.navigation; страница режима sequence
   этих правил не видит вовсе, и ни одного узла режима у неё нет.

   Цепочки в этом режиме нет, а точки, полоса прогресса и счётчик слайдов — её органы.
   Снимает их оформление, а не скрипт: видимостью точек и счётчика распоряжается _syncRail()
   по ширине кадра, и спорить с ним из скрипта значило бы возвращать их на каждом замере. */
#app[data-nav="panel"] #dots,
#app[data-nav="panel"] #progress,
#app[data-nav="panel"] #slide-count { display: none; }
#app[data-nav="panel"] #dots { --rail: off; }
/* Полоса кнопок висит над подвалом, и носитель вырезает её из своей нижней кромки — той же
   полосой, какой вырезает призыв и контролы панорамы. */
#app[data-nav="panel"] #slide { --band-modules: var(--band-step); }
/* Полоса у бокового края (page.bar_position: left|right): вырез уходит из нижней кромки в
   боковую. Признак ставит скрипт и только за словом пака — страница с полосой внизу этих
   правил не видит вовсе. */
#app[data-nav="panel"][data-bar="left"] #slide {
  --band-modules: 0px;
  --band-modules-left: calc(var(--band-rail) + var(--safe-left));
}
#app[data-nav="panel"][data-bar="right"] #slide {
  --band-modules: 0px;
  --band-modules-right: calc(var(--band-rail) + var(--safe-right));
}
/* Смешанный документ: полоса кнопок стоит и на странице-цепочке — панелью там открывается
   только названный модуль. Вырез называет сама полоса (data-modules), а не режим документа:
   у страницы без полосы признака нет вовсе, и ни одно из этих правил к ней не относится.
   Правила стоят до правил входа: на входе полосы нет, и вырез там снимается. */
#app[data-modules="bar"] #slide { --band-modules: var(--band-step); }
#app[data-modules="bar"][data-bar="left"] #slide {
  --band-modules: 0px;
  --band-modules-left: calc(var(--band-rail) + var(--safe-left));
}
#app[data-modules="bar"][data-bar="right"] #slide {
  --band-modules: 0px;
  --band-modules-right: calc(var(--band-rail) + var(--safe-right));
}
/* Вход режима панели (panelEntry) — во весь экран до носителя: полосы кнопок на нём нет, и
   вырезать под неё нечего. Назад на вход ведёт кнопка Home полосы — первой, со своей иконкой. */
#app[data-nav="panel"][data-at-entry="1"] #slide { --band-modules: 0px; }
#app[data-nav="panel"][data-at-entry="1"] #slide {
  --band-modules-left: 0px; --band-modules-right: 0px;
}
#module-bar[hidden] { display: none; }

/* Полоса лежит над вуалью панели, а не под ней: кнопка соседнего модуля обязана оставаться
   нажимаемой при открытой панели — иначе указатель попадает в вуаль и вместо переключения
   закрывает панель. Полосу и панель разводит порядок узлов: при равной полосе панель идёт
   ниже по документу и рисуется поверх. */
#module-bar {
  position: absolute; z-index: var(--z-module-bar);
  inset: auto 0 calc(var(--foot-h) + var(--safe-bottom) + 10px) 0;
  display: flex; justify-content: center;
  padding: 0 calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  pointer-events: none;
}
#module-bar ul {
  list-style: none; margin: 0; padding: 6px;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 4px; max-width: 100%;
  background: var(--glass-3); border: 1px solid var(--edge); border-radius: var(--r-pill);
  box-shadow: var(--sheen), var(--lift-2); backdrop-filter: var(--blur);
  pointer-events: auto;
}
.module-btn {
  min-width: var(--tap-min); min-height: var(--tap); display: flex; align-items: center; gap: 8px;
  padding: 8px 16px; border: 0; border-radius: var(--r-pill); cursor: pointer;
  background: transparent; color: var(--ink-2); font: inherit; white-space: nowrap;
  transition: background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft);
}
.module-btn svg { flex: none; }
.module-btn:hover { background: color-mix(in srgb, var(--tint) 8%, transparent); color: var(--ink); }
.module-btn[aria-expanded="true"] {
  color: var(--im-accent);
  background: color-mix(in srgb, var(--tint) 10%, transparent);
}
/* Вход полосы вида link — ссылка, а не кнопка: вид тот же, подчёркивания нет. */
a.module-btn { text-decoration: none; }

/* Полоса столбцом у бокового края. Ширина — та же мера, какой слайд вырезает полосу
   (--band-rail): полоса и вырез не расходятся ни на пиксель. Столбец выше кадра уходит в
   свою прокрутку, а не наружу — у полосы внизу ту же работу делает перенос строки. */
#app[data-bar="left"] #module-bar, #app[data-bar="right"] #module-bar {
  inset: var(--bar) auto calc(var(--foot-h) + var(--safe-bottom) + 10px) auto;
  width: var(--band-rail);
  align-items: center; justify-content: center;
}
#app[data-bar="left"] #module-bar {
  left: 0; padding: 0 0 0 calc(16px + var(--safe-left));
}
#app[data-bar="right"] #module-bar {
  right: 0; padding: 0 calc(16px + var(--safe-right)) 0 0;
}
#app[data-bar="left"] #module-bar ul, #app[data-bar="right"] #module-bar ul {
  flex-direction: column; flex-wrap: nowrap; align-items: stretch; justify-content: flex-start;
  width: 100%; max-height: 100%;
  overflow: auto; overscroll-behavior: contain; scrollbar-width: thin;
  border-radius: var(--r-md);
}
#app[data-bar="left"] .module-btn, #app[data-bar="right"] .module-btn { justify-content: flex-start; }
/* Имя кнопки в столбце обрезается, а не растягивает полосу. Имя целиком остаётся в
   aria-label и в title — кнопка названа и без подписи. */
#app[data-bar="left"] .module-btn__label, #app[data-bar="right"] .module-btn__label {
  overflow: hidden; text-overflow: ellipsis;
}

/* Вуаль и панель — соседи со своими полосами, ровно как у шторки меню. Вуаль и есть клик
   мимо панели: промах закрывает её на любой ширине и любом слое. Полоса кнопок из-под вуали
   выведена выше: промахом считается фон, а не соседняя кнопка модуля. */
#module-veil { z-index: var(--z-module-veil); }
#module-panel {
  left: auto; right: 0;
  width: min(560px, 100%); z-index: var(--z-module);
  gap: 16px;
  padding-right: calc(var(--ov-pad) + var(--safe-right));
  border-left: 1px solid var(--ov-edge);
  transform: translateX(102%);
}
#module-panel[data-open="1"] { transform: none; }
/* Панель с таблицей растёт вместе с кадром. Ширина 560 держала таблицу инвентаря в 513 px
   при её собственных 886: колонка цены и оснащение уходили за кромку кадра и на 1024, и на
   1440, где половина экрана пуста. Панель без таблицы остаётся прежней — молчание пака
   ничего не двигает. */
#module-panel:has(.table-wrap) { width: min(960px, 100%); }
/* Панель выезжает с того края, у которого стоит полоса: иначе она уходила бы от кнопки,
   которой её открыли, через весь кадр. Правая — та же, что у умолчания, и своего правила
   ей не нужно; левая меняет край, рамку и сторону выезда. Открытая панель объявляется
   заново: правило края специфичнее общего [data-open="1"] и без этого перебило бы его. */
#app[data-bar="left"] #module-panel {
  left: 0; right: auto;
  border-left: 0; border-right: 1px solid var(--ov-edge);
  padding-right: var(--ov-pad); padding-left: calc(var(--ov-pad) + var(--safe-left));
  transform: translateX(-102%);
}
#app[data-bar="left"] #module-panel[data-open="1"] { transform: none; }
/* Закрытая панель выключается inert — тем же способом и по той же причине, что шторка меню. */
#module-panel[inert] { pointer-events: none; }
.module-top { flex: none; }
#module-title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.module-body {
  position: relative; min-height: 0; flex: 1 1 auto;
  overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; touch-action: pan-y;
}
@media (prefers-reduced-motion: reduce) {
  #module-panel[data-open="1"] { transform: none !important; }
}
/* Модуль внутри панели идёт колонкой с прокруткой — тем же видом, каким слайд идёт ниже
   1024 px: колонки становятся потоком, а не слоями по кадру. Правила те же, что в блоке
   «планшет и телефон», но по ширине панели, а не кадра: панель узкая на любом экране. */
.module-body .left-col {
  position: static; width: auto; max-width: none; transform: none; justify-items: stretch;
}
.module-body .tiles, .module-body .detail-tiles {
  position: static; transform: none; width: auto; padding: 0;
  display: grid; grid-template-columns: minmax(0, 1fr); gap: 10px;
}
.module-body .centre-col, .module-body .filters, .module-body .strip,
.module-body .strip--raised, .module-body .strip--single, .module-body .disclaimer,
.module-body .table-wrap, .module-body .poi-list {
  position: static; inset: auto; width: auto; max-width: none; margin: 0; padding: 0;
}
.module-body .switch-track { min-width: 0; width: 100%; }
/* Таблица юнитов в панели (C28). Три закреплённых справа блока — цена, статус, действие —
   в панели шириной 513 px занимают 471, и на закреплённую слева первую колонку остаётся
   43: у пака с широкими колонками она уходит под них целиком, заголовок группы сделки
   ложится поверх заголовка юнита, а цена обрезается до хвоста. Отпускаем цену и статус в
   прокрутку — ровно как «планшет и телефон» ниже по тем же причинам; закреплёнными
   остаются номер слева и действие справа. min-width таблицы в 920 px — мера слайда во весь
   кадр: в панели она растягивает колонки любого пака и заводит прокрутку на ровном месте.
   Ширина панели от ширины кадра не зависит, поэтому правило здесь, а не в @media. */
.module-body table { min-width: 0; }
.module-body tbody td.table-col--deal,
.module-body thead tr:not(.table-groups) th.table-col--deal { position: static; }
.module-body thead .table-groups th:last-child { position: static; }
.module-body tbody td.table-col--deal:nth-last-child(3) { box-shadow: none; }
/* Панель входа полосы без модуля (barEntries): картинка во всю ширину панели, нажатие открывает
   её крупно; ссылка на документ — кнопкой под ней. */
.bar-panel { display: grid; gap: 16px; justify-items: start; }
.bar-figure {
  display: block; width: 100%; padding: 0; border: 1px solid var(--edge); border-radius: var(--r-md);
  background: none; cursor: zoom-in; overflow: hidden;
}
.bar-figure img { display: block; width: 100%; height: auto; }
.bar-link { text-decoration: none; }
/* Информационная панель — та же колонка, что у входа полосы: блоки идут сверху вниз в порядке
   пака, картинка занимает ширину колонки, кнопка документа стоит последней. Отличие одно —
   заголовки и абзацы внутри, поэтому межблочный отступ у текста свой. */
.info-panel { display: grid; gap: 16px; justify-items: start; }
.info-panel > :is(h4, p) { margin: 0; max-width: 62ch; }
.info-panel > h4 { font-family: var(--font-accent); font-size: 1.05rem; }
#slide[data-layout="info"] { overflow: auto; }
#slide[data-layout="info"] > .left-col { width: min(720px, 100%); }
@media (max-width: 1023px) {
  .info-panel { width: 100%; }
  .info-panel > .bar-link { width: 100%; justify-content: center; }
}
/* Ниже 1024 px от кнопки остаётся иконка: имя у неё в aria-label, и кнопка остаётся
   названной. Голые иконки без имени — дефект живого примера типа C, и повторять его
   нельзя: подпись снимается, имя — никогда. */
@media (max-width: 1023px) {
  .module-btn { padding: 8px 12px; }
  .module-btn__label { display: none; }
  #module-panel { width: 100%; border-left: 0; }
  /* Ниже 1024 край не действует: столбец на узком кадре съел бы половину ширины слайда, а
     панель во всю ширину выезжает с любой стороны одинаково. Полоса возвращается строкой
     вниз, вырез — в нижнюю кромку, и раскладка становится той же, что у умолчания: нижний
     лист ниже 768 видит ровно то, что видел всегда. Вход исключён признаком: на нём полосы
     нет, и вырезать под неё нечего. */
  #app[data-bar="left"] #module-bar, #app[data-bar="right"] #module-bar {
    inset: auto 0 calc(var(--foot-h) + var(--safe-bottom) + 10px) 0;
    width: auto; align-items: stretch; justify-content: center;
    padding: 0 calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  }
  #app[data-bar="left"] #module-bar ul, #app[data-bar="right"] #module-bar ul {
    flex-direction: row; flex-wrap: wrap; justify-content: center;
    width: auto; max-width: 100%; max-height: none; overflow: visible;
    border-radius: var(--r-pill);
  }
  #app[data-nav="panel"][data-bar="left"]:not([data-at-entry="1"]) #slide,
  #app[data-nav="panel"][data-bar="right"]:not([data-at-entry="1"]) #slide {
    --band-modules: var(--band-step);
    --band-modules-left: 0px; --band-modules-right: 0px;
  }
  #app[data-modules="bar"][data-bar="left"]:not([data-at-entry="1"]) #slide,
  #app[data-modules="bar"][data-bar="right"]:not([data-at-entry="1"]) #slide {
    --band-modules: var(--band-step);
    --band-modules-left: 0px; --band-modules-right: 0px;
  }
  #app[data-bar="left"] #module-panel { border-right: 0; }
}

/* ——— слайд ——— */
/* Слои, которые висят поверх слайда, получают собственные полосы: слайд вырезает их из
   своей нижней кромки, а не делит с ними угол. Вырез, а не отступ, — отступ держит
   только конец прокрутки, середина списка всё равно уезжает плавающей кнопке под низ.
   Каждая полоса — своя переменная, и в кадре, где слой лежит в потоке слайда или ушёл
   в шапку, её обнуляет тот же блок, который слой переставил. */
#slide {
  position: absolute; z-index: var(--z-slide); pointer-events: none;
  --band-tools: 0px;   /* плавающая панель контролов панорамы */
  --band-cta: 0px;     /* плавающий призыв #enquire-top */
  --band-modules: 0px; /* полоса кнопок модулей #module-bar, режим panel, край bottom */
  /* Та же полоса, ушедшая к боковому краю (page.bar_position). Вырез всегда один: либо
     нижний, либо боковой — оба сразу не ставит ни одно правило. */
  --band-modules-left: 0px;
  --band-modules-right: 0px;
  inset: var(--bar) var(--band-modules-right)
         calc(var(--foot-h) + var(--safe-bottom) + var(--band-tools) + var(--band-cta)
              + var(--band-modules)) var(--band-modules-left);
}
#slide > * { pointer-events: auto; }
/* Метка рендера (impressionNodes модуля): мелкой строкой внизу слева над подвалом, как у живых
   IM (.renderLabel: 12 px, 16 px от левого края). В режиме панели она поднимается над полосой
   модулей. Нажатий не берёт. */
#impression {
  position: absolute; z-index: var(--z-slide); pointer-events: none; margin: 0;
  left: calc(16px + var(--safe-left));
  bottom: calc(var(--foot-h) + var(--safe-bottom) + 6px);
  font-size: 12px; line-height: 16px; color: var(--ink);
  text-shadow: 0 1px 2px color-mix(in srgb, var(--void) 80%, transparent);
}
#impression[hidden] { display: none; }
#app[data-nav="panel"] #impression { bottom: calc(var(--foot-h) + var(--safe-bottom) + var(--band-step) + 16px); }
#app[data-nav="panel"][data-at-entry="1"] #impression { bottom: calc(var(--foot-h) + var(--safe-bottom) + 6px); }
/* Полоса у бокового края над меткой не стоит: метка возвращается к нижней кромке и
   отодвигается от того края, который полоса заняла. */
#app[data-nav="panel"][data-bar="left"] #impression,
#app[data-nav="panel"][data-bar="right"] #impression {
  bottom: calc(var(--foot-h) + var(--safe-bottom) + 6px);
}
#app[data-nav="panel"][data-bar="left"]:not([data-at-entry="1"]) #impression {
  left: calc(16px + var(--safe-left) + var(--band-rail));
}
@media (max-width: 1023px) {
  #app[data-nav="panel"][data-bar="left"]:not([data-at-entry="1"]) #impression,
  #app[data-nav="panel"][data-bar="right"]:not([data-at-entry="1"]) #impression {
    bottom: calc(var(--foot-h) + var(--safe-bottom) + var(--band-step) + 16px);
    left: calc(16px + var(--safe-left));
  }
}
/* Растянутые контейнеры поверх панорамы пустым местом ловили перетаскивание холста и нажатия
   по меткам под собой: полоса переключателя во всю ширину кадра, ряд призыва, стопка плиток.
   На всех ширинах указатель отдаётся только самим контролам внутри них. */
#slide > .strip, #slide > .pano-tools, #slide > .filters,
#slide > .detail-tiles, #slide > .agent-row {
  pointer-events: none;
}
#slide > .strip > *, #slide > .pano-tools > *, #slide > .filters > *,
#slide > .detail-tiles > *, #slide > .agent-row > * {
  pointer-events: auto;
}
/* Колонка заголовка поверх панорамы (мастерплан, локация, 360-слайд с текстом) рамками h2, p
   и линии шире самого текста: пустое место отбирало нажатия по меткам. Класс ставит рендер
   слайда. Указатель возвращают органы управления и плитки. Специфичность обязана перебить
   #slide > * (1,0,0): голого .left-col мало. */
#slide > .left-col--over { pointer-events: none; }
#slide > .left-col--over :is(a, button, summary, input, select, textarea, .pill, .detail-tiles > *) {
  pointer-events: auto;
}
/* Ниже 1024 слайд листается сам, и свайп, начатый на тексте колонки, обязан его прокрутить,
   а не повернуть панораму. Указатель берут строки: рендер оборачивает текст в строчный span,
   попадание по нему — только сами строки. Пустое место абзаца и промежутки колонки нажатие
   к меткам по-прежнему пропускают. Метка под строкой текста закрыта интерфейсом, её достают
   поворотом сцены. От 1024 слайд не листается, и текст указатель не держит. */
@media (max-width: 1023px) {
  #slide > .left-col--over > :is(h2, p) > span { pointer-events: auto; }
}
@media (min-width: 1024px) {
  /* Призыв у левого края стоит серединой по вертикали — там же, где колонка. Колонка
     отходит вправо на его ширину; ширину меряет рендер слайда (--enquire-w). */
  #slide[data-enquire-at="left"] > .left-col--pano, #slide[data-enquire-at="left"] > .pano-info {
    --col-left: calc(16px + var(--safe-left) + var(--enquire-w, 160px) + var(--space-4));
  }
}
#slide > * { animation: fade-in var(--slow) var(--ease) backwards; }
#slide > *:nth-child(2) { animation-delay: 70ms; }
#slide > *:nth-child(3) { animation-delay: 140ms; }
#slide > *:nth-child(4) { animation-delay: 210ms; }
/* Кадр появления пишет только opacity. Вернётся сдвиг — ставить его свойством `translate`,
   а не `transform`: у .tiles, .left-col и .missing свой transform, и кадр его затирает —
   отсюда брался диагональный вылет блоков Executive Summary. */
@keyframes fade-in { from { opacity: 0; } }
#slide[data-leaving="1"] > * { animation: fade-out var(--fast) var(--ease-soft) forwards; }
@keyframes fade-out { to { opacity: 0; } }

.left-col {
  position: absolute; left: var(--col-left); top: 50%; transform: translateY(-50%);
  width: min(430px, 44vw); display: grid; gap: 18px; justify-items: start;
}
.left-col .eyebrow {
  display: flex; align-items: center; gap: 12px;
  font-size: 11px; letter-spacing: .2em; text-transform: uppercase; color: var(--eyebrow);
}
.left-col .eyebrow::after {
  content: ""; flex: 1; height: 1px; max-width: 56px;
  background: linear-gradient(to right, var(--edge-strong), transparent);
}
.left-col h2 { font-size: clamp(30px, 3.4vw, 46px); }
.left-col .rule {
  width: 100%; height: 1px; border: 0;
  background: linear-gradient(to right, var(--edge-strong), transparent);
}
.left-col p { color: var(--ink-2); max-width: 42ch; }
.left-col .lede { color: var(--ink); font-size: 17px; }
/* Логотип старта. Ширина стояла инлайновым стилем прямо в разметке, а на коротком кадре
   её надо ужимать — инлайн правилом не перебить. Значение то же, что было. */
.start-logo { max-width: 240px; }
/* Второй логотип обложки — девелопера рядом с проектом: под текстом, мельче первого. */
.start-logo--second { max-width: 160px; max-height: 72px; }
.start-logo-link { justify-self: start; display: inline-flex; }
/* Пак несёт логотип для тёмного фона. На светлой схеме знак темнеет, иначе белый на белом. */
:root[data-scheme="light"] :is(.start-logo, .menu-top img) { filter: brightness(0); }

/* ——— блок вкладок Executive Summary ——— */
/* Три панели лежат в одной ячейке грида: высота блока равна самой высокой панели и на
   переключении вкладки не меняется, поэтому слайд под блоком не двигается. Стекла на
   движущемся нет: активная вкладка отмечена фоном и цветом, бегунка нет вовсе. */
.tiles {
  position: absolute; left: 56%; top: 50%; transform: translate(-50%, -50%);
  display: flex; gap: 12px; align-items: stretch;
}
.tile {
  width: 210px; padding: 26px 18px; display: grid; justify-items: center; gap: 14px;
  text-align: center; border-radius: var(--r-lg); border: 1px solid transparent;
  background: transparent; transition: background var(--mid) var(--ease),
    border-color var(--mid) var(--ease), transform var(--mid) var(--ease),
    box-shadow var(--mid) var(--ease);
}
.tile:hover {
   background: var(--glass-1); border-color: var(--edge);
   box-shadow: var(--sheen), var(--lift-2); transform: translateY(-4px);
}
.tile img { width: 46px; height: 46px; object-fit: contain;
  transition: transform var(--mid) var(--ease); }
.tile:hover img { transform: scale(1.08) translateY(-2px); }
.tile h3 { font-size: 23px; }

.strip {
  position: absolute; inset: auto 0 calc(18px + var(--band-modules, 0px)) 0;
  z-index: 11; display: flex; gap: 8px;
  justify-content: center; flex-wrap: wrap; padding: 0 16px;
}
.strip[role="group"] { gap: 6px; }
.strip--single { padding: 0 16px; }
.switch-track {
  position: relative; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  min-width: min(360px, calc(100vw - 32px)); padding: 4px;
  border: 1px solid var(--edge); border-radius: var(--r-pill);
   background: color-mix(in srgb, var(--void) 72%, transparent); box-shadow: var(--sheen), var(--lift-1);
   backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur); overflow: hidden;

}
.switch-track .pill {
  z-index: 1; width: 100%; border-color: transparent; background: transparent;
  box-shadow: none;
  /* Половины лежат над бегунком, и унаследованный из .pill blur размывал акцентную
     таблетку под собой: торцы мутнели, при переезде тянулся шлейф. */
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
/* У половины гасим только фон и тень. Прежнее transform: none перебивало .pill:active,
   поэтому мышью нажатие не давало отклика, а на тач-вводе давало. */
.switch-track .pill:hover { background: transparent; box-shadow: none; }
.switch-track .pill:active { transform: scale(.97); }
.switch-track .pill[aria-pressed="true"] { color: var(--void); background: transparent; box-shadow: none; }
/* У трека overflow: hidden, наружная обводка фокуса срезается с трёх сторон — уводим внутрь. */
.switch-track .pill:focus-visible { outline-offset: -3px; }
.switch-thumb {
  position: absolute; z-index: 0; inset: 4px auto 4px 4px; width: calc(50% - 4px);
  border-radius: var(--r-pill); background: var(--im-accent); box-shadow: var(--glow);
  transform: translateX(0); transition: transform var(--mid) var(--ease);
}
.switch-track[data-active="1"] .switch-thumb { transform: translateX(100%); }
/* Бегунок слегка растягивается под нажатой половиной — отклик на нажатие. */
.switch-track:has(.pill:active) .switch-thumb { transform: scaleX(1.04); }
.switch-track[data-active="1"]:has(.pill:active) .switch-thumb { transform: translateX(100%) scaleX(1.04); }
@media (prefers-reduced-motion: reduce) {
  .switch-track .pill:active { transform: none; }
  .switch-track:has(.pill:active) .switch-thumb { transform: none; }
  .switch-track[data-active="1"]:has(.pill:active) .switch-thumb { transform: translateX(100%); }
}
.strip--raised { bottom: 96px; }
/* Переключатель колонки юнитов стоит в текстовой колонке и растягивался на всю её ширину:
   528 px поперёк плана, и метки юнитов под ним переставали нажиматься. Кнопка занимает
   своё содержимое и стоит у начала колонки. */
.unit-labels-toggle { align-self: start; width: fit-content; max-width: 100%; }

/* Преимущества стоят слева, под текстовой колонкой слайда: справа они спорили с панелью
   контролов панорамы и с точками, а левая сторона кадра у 360-слайдов и так под текст. */
.detail-tiles {
  position: absolute; left: var(--col-left); bottom: 96px;
  display: grid; gap: 10px; width: 290px;
}
.detail-tiles .row {
  display: flex; align-items: center; gap: 14px; padding: 13px 16px;
  border-radius: var(--r-md); border: 1px solid var(--edge);
  background: var(--glass-1); box-shadow: var(--sheen), var(--lift-1);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
  transition: box-shadow var(--mid) var(--ease), border-color var(--fast) var(--ease-soft),
              background var(--fast) var(--ease-soft);
}
.detail-tiles .row:hover {
  border-color: var(--edge-strong); background: var(--glass-2);
  box-shadow: var(--sheen), var(--lift-2), var(--glow-soft);
}
.detail-tiles img { width: 27px; height: 27px; flex: none; }
/* Плитки под текстом колонки (Aerial 360 с текстом): в потоке колонки, её шириной. */
.left-col > .detail-tiles { position: static; width: 100%; padding: 0; }

/* ——— точки на панораме ——— */
.im-pano-markers { position: absolute; inset: 0; pointer-events: none; }
/* На время замера ширин переходы снимаются: сразу после разворота offsetWidth отдаёт значение
   на полпути между кружком и подписью, и расклейка считает по нему. */
.im-pano-markers--measure .im-pano-marker { transition: none !important; }
.im-pano-marker {
  position: absolute;
  /* Позиция, центрирование и глубина считаются каждый кадр, поэтому разведены по отдельным
     свойствам: transform остаётся за наведением и кадровые значения переходом больше не
     тянутся. Порядок задан спекой — translate, затем scale, затем transform. Собрать это
     обратно как transform: scale(var(--d)) translate(-50%,-50%) нельзя: центрирующий сдвиг
     умножится на масштаб и все метки уедут. */
  translate: -50% -50%;
  scale: var(--d, 1);
  transform: none;
  opacity: var(--dim, 1);
  pointer-events: auto; cursor: pointer;
  min-width: 30px; min-height: 30px; padding: 3px 9px;
  min-width: var(--tap-min); min-height: var(--tap-min);
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: 12px; font-variant-numeric: tabular-nums;
  color: var(--ink); border: 1.5px solid var(--edge-strong); border-radius: var(--r-pill);
   background: color-mix(in srgb, var(--void) 68%, transparent);
   box-shadow: var(--lift-1);
  animation: pop var(--mid) var(--ease) backwards;
  /* Свёрнутая метка задаёт ширину и высоту в пикселях, развёрнутая — ключевым словом auto.
     Между ними переход не шёл: auto не интерполируется, объявленный ниже width простаивал,
     и коробка на свёртке щёлкала на всю разницу за один кадр — у подписи в 192 px это 100 px
     сдвига левого края разом. Разрешение снимает ровно это; браузер без него ведёт себя
     как прежде. Мягкая кривая и var(--mid) здесь обязательны: на var(--ease) первый же кадр
     съедает половину пути, и щелчок остаётся щелчком. */
  interpolate-size: allow-keywords;
  /* Пол цели нажатия — 24 px на экране. Глубина масштабирует метку от 0.9 до 1.45, и кружок
     в 20 px у верхней кромки кадра рисовался 18–23 px: R1 нашёл его ниже пола WCAG 2.5.8.
     Пол делится на глубину, поэтому на экране он всегда 24 px, а ближняя метка остаётся
     крупнее. Подложка ::after в 44 px пол не заменяет: соседние подложки перекрываются, и
     попадание в 12 px от центра забирает соседняя метка. Своя цель — только коробка. */
  --hit-min: calc(24px / var(--d, 1));
  transition: transform var(--fast) var(--ease),
              background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft),
              box-shadow var(--mid) var(--ease), width var(--mid) var(--ease-soft),
              min-width var(--mid) var(--ease-soft), height var(--mid) var(--ease-soft),
              min-height var(--mid) var(--ease-soft), padding var(--mid) var(--ease-soft),
              font-size var(--fast) var(--ease);
}
/* Центрирование ушло в translate: оставшись здесь, оно удвоилось бы и метка на входной
   волне прыгала бы на половину своей ширины. */
@keyframes pop { from { opacity: 0; transform: scale(.6); } }
/* Ховер множит глубину, а не заменяет её: иначе дальняя метка прыгала бы в полный размер. */
/* Наведение меняет только цвет. Масштаб считался от середины метки, а якорь выносной — её
   низ: подпись уезжала от собственной точки, и ножка рвалась. Порядок по глубине наведение
   тоже не трогает — иначе дальняя метка под курсором прыгала бы поверх ближних. */
/* Активная метка — та, чья карточка открыта в списке. Читается так же, как наведение:
   объект в кадре и объект в карточке обязаны быть очевидно одним и тем же. */
.im-pano-marker[data-active="1"] {
  opacity: 1;
  background: var(--im-accent); color: var(--void); border-color: transparent;
  box-shadow: var(--glow);
}
.im-pano-marker[data-active="1"] svg { color: var(--void); opacity: 1; }
.im-pano-lead[data-active="1"] {
  opacity: 1;
  background: linear-gradient(to bottom,
    color-mix(in oklab, var(--im-accent) 45%, transparent), var(--im-accent));
}
.im-pano-lead[data-active="1"]::after {
  background: var(--im-accent); box-shadow: 0 0 0 1.5px color-mix(in srgb, var(--void) 70%, transparent);
}

.im-pano-marker:hover, .im-pano-marker:focus-visible {
  opacity: 1;
  background: var(--im-accent); color: var(--void); border-color: transparent;
  box-shadow: var(--glow);
}
/* Цвет статуса приходит из бренда документа переменной --status: перечень статусов правится
   в панели, и новый статус получает свой цвет без правки этого файла. */
.im-pano-marker[data-status] { border-color: var(--status, var(--edge));
  box-shadow: 0 0 16px -6px var(--status, transparent); }
/* Юнит в закрытом статусе гаснет. Это правило страницы, а не цвет: приглушение палитра не
   задаёт. Какой статус закрыт, говорит пак (Closed Statuses), и страница ставит data-closed. */
.im-pano-marker[data-closed="1"] { box-shadow: var(--lift-1); opacity: calc(var(--dim, 1) * .5); }
/* Метка вне кадра. Правило идёт после статусов, чтобы перебивать их приглушение. */
/* Уход и возврат метки идут прозрачностью, а не мгновенным visibility: метка на краю кадра
   больше не мигает. visibility переключается с задержкой в длину самого перехода, иначе она
   срезала бы его на первом же кадре. */
.im-pano-marker[data-off="1"] {
  opacity: 0; pointer-events: none;
  visibility: hidden;
  transition: opacity var(--mid) var(--ease-soft), visibility 0s linear var(--mid);
}
/* Метка в тесноте. Расклейка ищет ей ярус, потом место под кружок; когда свободно не осталось
   ни там, ни там, метка снимается целиком. Иначе она стоит поверх соседа: hit-test в её центре
   отдаёт чужую метку, и нажатие молча открывает чужой объект. Снятая метка уходит и из обхода
   клавиатурой — visibility это делает само. Приближение вида разводит объекты, расклейка
   считается заново, и метка возвращается. Правило идёт тем же порядком, что [data-off="1"]. */
.im-pano-marker[data-crowded="1"] {
  opacity: 0; pointer-events: none;
  visibility: hidden;
  transition: opacity var(--mid) var(--ease-soft), visibility 0s linear var(--mid);
}
/* Точка интереса несёт иконку категории и подпись. */
/* Высота метки точки: на мыши по содержимому, на тач-вводе цель остаётся 44 px. Раньше
   --tap-min держал 44 px на всех указателях, и пилюля выглядела вдвое выше нужного. */
/* Якорь метки точки — её нижняя кромка: подпись стоит над точкой, как и у выносной. Так
   порядок по глубине совпадает с тем, что видит зритель: метка ниже — точка ближе. */
.im-pano-marker--poi {
  gap: 7px; padding: 3px 11px 3px 8px; min-height: 30px;
  translate: -50% -100%;
}
@media (pointer: coarse) {
  .im-pano-marker--poi { min-height: var(--tap-min); }
}
.im-pano-marker--poi svg { width: 15px; height: 15px; flex: none; opacity: .9;
  color: var(--poi, currentColor); }
/* Цвет категории приходит переменной --poi: категории правятся в панели, и новая получает
   свой цвет без правки этого файла. Цвет не задан — метка остаётся прежней. */
.im-pano-marker--poi[data-poi] { border-color: var(--poi, var(--edge));
  box-shadow: 0 0 16px -6px var(--poi, transparent); }

/* Свёрнутая точка: подпись прячется, иконка остаётся. Наведение и фокус возвращают подпись.
   Кружок уходит под подписи, чтобы не резать их собой. */
.im-pano-marker[data-collapsed="1"] {
  width: 26px; min-width: max(26px, var(--hit-min)); height: 26px; min-height: max(26px, var(--hit-min));
  padding: 0;
  overflow: hidden;
}
/* У свёрнутой метки отступы обнуляются, иначе иконку выдавливает за пределы кружка. */
.im-pano-marker--poi[data-collapsed="1"] { padding: 0; gap: 0; }
.im-pano-marker[data-collapsed="1"] .lbl {
  visibility: hidden; opacity: 0; transition: opacity var(--fast) var(--ease-soft), visibility 0ms linear var(--fast);
}
.im-pano-marker[data-collapsed="1"]:not(.im-pano-marker--poi):not([data-unit="1"]) { font-size: 0; }

/* Номер юнита — единственное содержимое метки, прятать в ней нечего. Свёртка его ужимает:
   кегль ниже, поля в ноль, подложка плотнее, диаметр падает с 26 до 20 px. Развёрнутая метка
   тоже ужата: её 44 px держала цель нажатия (min-width: var(--tap-min) выше), и ровно эта
   ширина сталкивала соседей у горизонта. Цель нажатия возвращает невидимая подложка ::after
   в 44 px. Подложка стоит на всех указателях: у ноутбука с сенсорным экраном ведущий указатель
   мышь, и блок @media (pointer: coarse) до него не дотягивается. Раскладку ::after не двигает.
   Место блока обязательно: :hover и :focus-visible ниже по файлу той же специфичности, и
   наведение обязано остаться сильнее. */
.im-pano-marker[data-unit="1"] {
  min-width: max(26px, var(--hit-min)); min-height: max(26px, var(--hit-min));
  padding: 2px 6px; overflow: visible;
}
.im-pano-marker[data-unit="1"]::after {
  content: ""; position: absolute; left: 50%; top: 50%;
  width: var(--tap-min, 44px); height: var(--tap-min, 44px);
  transform: translate(-50%, -50%); border-radius: 50%;
}
.im-pano-marker[data-unit="1"][data-collapsed="1"] {
  width: auto; min-width: max(20px, var(--hit-min)); height: 20px; min-height: max(20px, var(--hit-min));
  padding: 0 3px; font-size: 11px; line-height: 1;
  background: color-mix(in srgb, var(--void) 82%, transparent);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .9);
}
/* Тень под цифрой держит светлый текст; под тёмным текстом светлой схемы она его мажет. */
:root[data-scheme="light"] .im-pano-marker[data-unit="1"][data-collapsed="1"] { text-shadow: none; }
/* Закрытый статус гасит метку вдвое правилом выше, и вместе с меткой гаснет сама цифра: замерено —
   0.45 против 0.90 у соседей. У метки с номером приглушение снимается, статус остаётся каймой
   и снятым свечением. Метка за краем кадра из правила исключена: [data-off="1"] слабее его и
   показал бы её снова. */
.im-pano-marker[data-unit="1"][data-closed="1"]:not([data-off="1"]) { opacity: var(--dim, 1); }
/* Табличка юнита под его точкой — число юнита (площадь или колонка пака), unitLabelsToggle
   мастерплана. Видна, пока включён переключатель колонки: признак data-figures на сцене. */
.im-pano-marker .unit-figure { display: none; }
#stage[data-figures="1"] .im-pano-marker .unit-figure {
  display: block; position: absolute; left: 50%; top: calc(100% + 4px); translate: -50% 0;
  padding: 1px 6px; border-radius: var(--r-pill); white-space: nowrap; pointer-events: none;
  font-size: 11px; line-height: 16px; color: var(--ink); text-shadow: none;
  background: color-mix(in srgb, var(--void) 82%, transparent);
}

.im-pano-marker[data-collapsed="1"]:hover,
.im-pano-marker[data-collapsed="1"]:focus-visible {
  width: auto; min-width: 30px; height: auto; min-height: 30px;
  padding: 3px 11px 3px 8px; font-size: 12px; overflow: visible; z-index: 3;
}
.im-pano-marker[data-collapsed="1"]:hover .lbl,
.im-pano-marker[data-collapsed="1"]:focus-visible .lbl {
  visibility: visible; opacity: 1; transition-delay: 0ms;
}

/* ——— выноска: подпись поднята над горизонтом, точка осталась на своём месте ———
   У горизонта объекты сходятся в узкую полосу, и подписи садятся друг на друга. Раскладка
   разводит их по ярусам: ближняя остаётся на месте, дальняя уезжает вверх и рисует вниз
   палочку с точкой на своём настоящем месте. Подъём и сдвиг приходят из плеера в пикселях
   кадра переменными --pano-lift и --pano-shift; метка, не получившая яруса, несёт нули.

   Подъём едет отдельным свойством translate. transform занят наведением, а translate и scale
   разведены по отдельным свойствам ради покадровой синхронности метки с кадром — собрать их
   обратно в один transform нельзя. Запасное значение в var() обязательно везде: при
   необъявленной переменной calc становится невалидным на этапе вычисления, translate уходит
   в none, и метка теряет центрирование целиком.

   Под prefers-reduced-motion общий блок выше оставляет в transition-property только цвет и
   прозрачность: подъём там встаёт мгновенно, и это нужное поведение. Своего блока здесь нет. */
.im-pano-marker[data-lead] {
  /* Перенос строк снят: коробка столкновения считается по развёрнутой подписи в одну строку,
     а без nowrap подпись у правой кромки режется shrink-to-fit по остатку места и рисуется
     в две-три строки — модель и экран расходятся ровно там, где это важнее всего. */
  white-space: nowrap;
  /* Ярус и сдвиг едут переходом, и ножка едет тем же временем и той же кривой: у неё
     анимируется height. Покадровое движение за панорамой идёт мимо переходов — у подписи
     это left/top, у ножки top. */
  transition: translate var(--mid) var(--ease-soft),
              transform var(--fast) var(--ease),
              background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft),
              box-shadow var(--mid) var(--ease), width var(--mid) var(--ease-soft),
              min-width var(--mid) var(--ease-soft), height var(--mid) var(--ease-soft),
              min-height var(--mid) var(--ease-soft), padding var(--mid) var(--ease-soft),
              font-size var(--fast) var(--ease);
}
/* Скачок вида — смена узла, ресайз, программная установка ракурса — переставляет метки разом.
   Плеер вешает этот класс на время одного принудительного layout: подпись встаёт на новый
   ярус мгновенно, а не едет вслед за уже сменившимся кадром. */
.im-pano-markers--snap .im-pano-marker[data-lead] { transition: none; }
.im-pano-marker[data-lead="1"] {
  /* Якорь выносной метки — её нижняя кромка, а не центр: подпись целиком стоит над своей
     точкой, ножка растёт от кромки вниз. При центрировании подпись садилась на объект. */
  translate: calc(-50% + var(--pano-shift, 0px)) calc(-100% - var(--pano-lift, 0px));
}
/* Смещения заданы в пикселях кадра, а внутри метки действует её собственный масштаб глубины:
   поэтому точка и палочка делят их на var(--d). Без деления точка уезжала бы от объекта на
   треть подъёма — на дальних метках это десятки пикселей. */
/* Ножка и точка выноски живут в слое .im-pano-leads ПОД метками: линия не имеет права лечь
   поверх чужой подписи, а псевдоэлементом этого не добиться — у метки свой контекст
   наложения из-за translate, scale и opacity. Координаты в пикселях кадра пишет плеер. */
.im-pano-leads { position: absolute; inset: 0; pointer-events: none; z-index: 0; }
.im-pano-markers { z-index: 1; }
.im-pano-lead {
  position: absolute; width: 1.5px; margin-left: -.75px; pointer-events: none;
  /* Ножка растёт вверх от своей точки: top ставится покадрово и переходом не участвует,
     height меняется только при смене яруса — его и анимируем вместе с подписью. */
  transform: translateY(-100%);
  /* Цвет тот же, что у самой метки: акцентный бежевый пропадал на небе, а неба в кадре
     локации больше всего. Линия тише подписи не цветом, а прозрачностью и толщиной. */
  opacity: .62;
  background: linear-gradient(to bottom,
    color-mix(in srgb, var(--void) 34%, transparent), color-mix(in srgb, var(--void) 82%, transparent));
  box-shadow: 0 0 0 .5px color-mix(in srgb, var(--tint) 14%, transparent);
  transition: height var(--mid) var(--ease-soft), opacity var(--mid) var(--ease-soft);
}
/* Точка на своём настоящем месте: тот же цвет и та же двойная обводка, что у метки. */
/* Точка на своём месте. Мельче и спокойнее метки: она указывает, а не спорит с подписью. */
.im-pano-lead::after {
  content: ""; position: absolute; left: 50%; bottom: 0;
  width: 8px; height: 8px; margin: 0 0 -4px -4px; border-radius: 50%;
  /* Подложка метки и её обводка: точка читается на небе так же, как пилюля. */
  background: color-mix(in srgb, var(--void) 82%, transparent);
  box-shadow: 0 0 0 1.5px var(--edge-strong);
}
/* Ножка скрытой метки уходит тем же временем: display: none обрывал бы переход. */
.im-pano-lead[hidden] { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .im-pano-lead { transition-duration: 0s; }
}

.im-pano-marker--link {
  width: 44px; min-width: 44px; height: 44px; border-style: dashed;
  animation: pulse 2.6s var(--ease-soft) infinite;
  transition: transform var(--fast) var(--ease),
              background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft),
              box-shadow var(--mid) var(--ease), border-color var(--fast) var(--ease-soft),
              width var(--fast) var(--ease), min-width var(--fast) var(--ease),
              padding var(--fast) var(--ease), font-size var(--fast) var(--ease);
}
.im-pano-marker--link:active { transform: scale(.94); }
.im-pano-marker--link::before {
  content: ""; width: 10px; height: 10px; border: 1.5px solid currentColor;
  border-left: 0; border-bottom: 0; transform: rotate(45deg) translate(-2px, 2px);
}
/* Картинка перехода из манифеста. Пока не загрузилась или не загрузится вовсе — видна
   встроенная стрелка выше. */
.im-pano-marker-img { display: none; width: 26px; height: 26px; object-fit: contain; pointer-events: none; }
.im-pano-marker--image .im-pano-marker-img { display: block; }
.im-pano-marker--image::before { content: none; }
@keyframes pulse {
  0%, 100% { box-shadow: 0 0 0 0 color-mix(in oklab, var(--im-accent) 45%, transparent); }
  50% { box-shadow: 0 0 0 10px transparent; }
}

/* ——— карточка точки при наведении ——— */
.hover-card {
  position: absolute; z-index: var(--z-float); pointer-events: none;
  width: 240px; padding: 12px 14px; display: grid; gap: 8px;
  /* Скругление на ступень мельче и тень полегче: карточка висит у метки, а не посреди кадра. */
  border-radius: var(--ov-radius-sm); box-shadow: var(--ov-lift-soft);
  opacity: 0; transform: translate(-50%, -100%) scale(.96);
  /* left/top в transition: карточка едет за меткой при параллаксе
     и при переходе на соседнюю точку, а не телепортируется. */
   transition: opacity var(--fast) linear, transform var(--fast) var(--ease),
               left var(--fast) var(--ease-soft), top var(--fast) var(--ease-soft);
}
.hover-card.glass-move,
.tour-card.glass-move {
  --glass-settle: var(--fast);
}
.hover-card[data-show="1"] {
  opacity: 1; pointer-events: auto; transform: translate(-50%, -100%) scale(1);
}
/* Мост между меткой и карточкой. pointer-events наследуется от карточки,
   то есть полоса ловит курсор только при [data-show="1"] и не мешает остальному. */
.hover-card::after {
  content: ""; position: absolute; left: 0; right: 0; top: 100%; height: 34px;
  pointer-events: inherit;
}
/* У верхней кромки карточка разворачивается вниз, мост уходит наверх. */
.hover-card[data-flip="1"] { transform: translate(-50%, 0) scale(.96); }
.hover-card[data-flip="1"][data-show="1"] { transform: translate(-50%, 0) scale(1); }
.hover-card[data-flip="1"]::after { top: auto; bottom: 100%; }
.hover-card img { width: 100%; height: 96px; object-fit: contain;
  background: color-mix(in srgb, var(--tint) 4%, transparent); border-radius: var(--r-sm); }
.hover-card .top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.hover-card .code { font-size: 15px; }
.hover-card .st { font-size: 10.5px; letter-spacing: .1em; text-transform: uppercase; }
.hover-card .st[data-status] { color: var(--status-ink, inherit); }
.hover-card dl { display: grid; grid-template-columns: 1fr auto; gap: 3px 12px;
  margin: 0; font-size: 12.5px; }
.hover-card dt { color: var(--ink-3); }
.hover-card dd { margin: 0; font-variant-numeric: tabular-nums; }

/* ——— список объектов рядом ——— */
/* Один блок: заголовок категории, под ним её точки. Ряд тегов над списком владелец снял,
   вместе с ним ушло и приглушение чужих меток на панораме. */
.poi-list {
  position: absolute; left: 56px; bottom: 24px; width: min(430px, 44vw);
  max-height: 38vh; overflow: auto; overscroll-behavior: contain;
  padding: 6px;
  /* Лёгкое стекло и лёгкая тень: список лежит на панораме, и панорама под ним читается —
     он не кроет кадр, а стоит в нём. */
  background: var(--ov-bg-soft); box-shadow: var(--ov-lift-soft);
  scrollbar-width: thin;
}
.poi-list h4 {
  margin: 12px 12px 6px; font: inherit; font-size: 10.5px; font-weight: 400;
  letter-spacing: .18em; text-transform: uppercase; color: var(--eyebrow);
  display: flex; align-items: center; gap: 9px;
}
.poi-list h4 svg { width: 15px; height: 15px; color: var(--poi, var(--im-accent)); opacity: .85; }
.poi-list .ico { display: grid; place-items: center; width: 34px; height: 24px;
  border-radius: 5px; background: color-mix(in srgb, var(--tint) 6%, transparent); }
.poi-list .ico svg { width: 15px; height: 15px; color: var(--poi, currentColor); }
.poi-list .poi-scroll button {
  width: 100%; display: grid; grid-template-columns: 34px 1fr auto; gap: 12px;
  align-items: center; min-height: var(--tap); padding: 7px 12px;
  border: 0; border-radius: var(--r-md); background: none; color: var(--ink-2);
  font: inherit; font-size: var(--ov-text); text-align: left; cursor: pointer;
  transition: background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft);
}
.poi-list .poi-scroll button:hover { background: color-mix(in srgb, var(--tint) 8%, transparent); color: var(--ink); }
.poi-list .poi-scroll img { width: 34px; height: 24px; object-fit: cover; border-radius: 5px; }
/* Иконка категории файлом пака: картинка как есть, в размер значка. Цвет файл несёт свой,
   переменная --poi ему не нужна. Правило стоит после миниатюры списка и сильнее её. */
.poi-list .poi-scroll img.poi-glyph, .poi-detail img.poi-glyph, .im-pano-marker--poi img.poi-glyph {
  width: 20px; height: 20px; flex: none; object-fit: contain; border-radius: 0; }
.im-pano-marker--poi img.poi-glyph { width: 18px; height: 18px; }
.poi-list .far { color: var(--ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }

/* Список и карточка объекта — один блок с двумя видами. Переключение идёт прозрачностью,
   высота блока едет переходом: без него выбор объекта дёргал бы кадр на всю разницу. */
.poi-list { transition: height var(--mid) var(--ease-soft); }
.poi-list[data-view="list"] .poi-detail,
.poi-list[data-view="detail"] .poi-scroll { display: none; }
.poi-scroll, .poi-detail { animation: soften var(--mid) var(--ease-soft); }
.poi-detail { display: grid; gap: 10px; padding: 10px 12px 12px; }
.poi-detail-head { display: flex; align-items: center; gap: 10px; }
.poi-detail-head h3 { font-size: 16px; font-weight: 600; }
.poi-detail .ico { flex: none; width: 34px; height: 24px; border-radius: 5px;
  display: grid; place-items: center; background: color-mix(in srgb, var(--tint) 6%, transparent); }
.poi-detail .ico svg { width: 15px; height: 15px; color: var(--poi, currentColor); }
.poi-back {
  flex: none; display: grid; place-items: center;
  width: 32px; height: 32px; border-radius: var(--r-md);
  border: 1px solid var(--edge); background: color-mix(in srgb, var(--tint) 5%, transparent);
  color: var(--ink-2); cursor: pointer;
  transition: background var(--fast) var(--ease-soft), color var(--fast) var(--ease-soft);
}
.poi-back:hover { background: color-mix(in srgb, var(--tint) 10%, transparent); color: var(--ink); }
.poi-back svg { width: 17px; height: 17px; }
.poi-detail .shot { width: 100%; height: 148px; object-fit: cover; border-radius: var(--r-md); }
.poi-detail .lede { color: var(--ink-2); font-size: 13px; }
.poi-detail p { color: var(--ink-2); font-size: 13px; }
.poi-steps { display: flex; align-items: center; justify-content: center; gap: 12px; padding-top: 2px; }
.poi-steps .pill { min-height: 34px; padding: 0 14px; }
.poi-steps .pill svg { width: 16px; height: 16px; }
.poi-count { color: var(--ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) {
  .poi-list { transition-duration: 0s; }
}

/* ——— карта участка (slide.mapView) ——— */
/* Слой карты — первый узел слайда: во весь кадр под шапкой и подвалом, под колонкой и списком
   слайда (z-index -1 внутри слоя #slide), поверх панорамы. Скрытая карта — display: none:
   так она не просит тайлов. Слева затемнение, как у панорамы: светлая схема иначе глушит
   заголовок колонки. */
#slide > .loc-map { position: fixed; inset: 0; z-index: -1; background: var(--void); }
#slide > .loc-map[data-on="0"] { display: none; }
.loc-map::after {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background: linear-gradient(to right, color-mix(in srgb, var(--void) 62%, transparent),
                              transparent min(640px, 60%));
}
/* Карта открыта — управление панорамой прячется: крутить под картой нечего. */
#slide > .loc-map[data-on="1"] ~ .pano-tools { display: none; }
.loc-map-view { position: absolute; inset: 0; overflow: hidden; cursor: grab; touch-action: none;
  outline-offset: -3px; }
.loc-map-view[data-drag="1"] { cursor: grabbing; }
/* Под схемой, пока тайлы идут, — ровный светлый фон схемы, а не провал в чёрное. */
.loc-map-view[data-layer="streets"] { background: #e6e2da; }
/* Слои карты по порядку: тайлы, кольца, метки. У слоя тайлов свой контекст наложения: глубина
   подписей над спутником задана z-index тайла и наружу выходить не должна. */
.loc-map-tiles, .loc-map-pins { position: absolute; inset: 0; }
.loc-map-tiles { z-index: 0; }
.loc-map-pins { z-index: 2; pointer-events: none; }
.loc-tile { position: absolute; left: 0; top: 0; width: 256px; height: 256px;
  user-select: none; -webkit-user-drag: none; }
.loc-tile[data-failed="1"] { visibility: hidden; }
.loc-map-rings { position: absolute; inset: 0; z-index: 1; width: 100%; height: 100%; pointer-events: none; }
.loc-ring { fill: color-mix(in srgb, #fff 5%, transparent); stroke: #fff; stroke-width: 2;
  stroke-opacity: .85; }
.loc-map-pins > * { position: absolute; left: 0; top: 0; }
.loc-map-pins > [hidden] { display: none; }
.loc-ring-label {
  translate: -50% -50%; padding: 3px 10px; border-radius: var(--r-pill);
  font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap;
  color: var(--void); background: #fff; box-shadow: var(--lift-1);
}
.loc-site { translate: -50% -50%; display: grid; place-items: center; width: 44px; height: 44px;
  border-radius: 50%; overflow: hidden; background: var(--void); border: 2px solid #fff;
  box-shadow: var(--lift-1); }
.loc-site img { width: 100%; height: 100%; object-fit: cover; }
.loc-site svg { width: 20px; height: 20px; color: var(--im-accent); }
/* Метка объекта — кружок с иконкой категории. Подпись раскрывается наведением, фокусом и у
   выбранной метки: девятнадцать подписей разом легли бы друг на друга у самого участка. */
.loc-pin {
  translate: -50% -50%; pointer-events: auto; cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  min-width: 30px; min-height: 30px; padding: 0 7px;
  font: inherit; font-size: 12px; color: var(--ink);
  border: 1.5px solid var(--poi, var(--edge-strong)); border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--void) 72%, transparent); box-shadow: var(--lift-1);
}
.loc-pin svg { width: 15px; height: 15px; flex: none; color: var(--poi, currentColor); }
.loc-pin img.poi-glyph { width: 18px; height: 18px; }
.loc-pin .lbl { display: none; max-width: 180px; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; }
.loc-pin:is(:hover, :focus-visible, [data-active="1"]) { z-index: 2; padding-right: 11px; }
.loc-pin:is(:hover, :focus-visible, [data-active="1"]) .lbl { display: inline; }
.loc-pin[data-active="1"] { background: var(--void); box-shadow: 0 0 0 3px var(--poi, var(--im-accent)); }
@media (pointer: coarse) { .loc-pin { min-width: var(--tap-min); min-height: var(--tap-min); } }
/* Указатель: подпись на координате и больше ничего. Не кнопка и не цель нажатия — карточки за
   ним нет, поэтому он не ловит указатель и не растёт до размера цели касания. */
.loc-mark {
  translate: -50% -50%; pointer-events: none;
  max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font: inherit; font-size: 12px; letter-spacing: .04em; text-transform: uppercase;
  color: var(--ink); text-shadow: 0 1px 3px var(--void), 0 0 2px var(--void);
}
.loc-map-zoom { position: absolute; right: 16px; top: calc(var(--bar, 0px) + 16px); display: grid; gap: 6px;
  z-index: 3; }
.loc-map-zoom button {
  width: var(--tap-min); height: var(--tap-min); font: inherit; font-size: 20px; line-height: 1;
  color: var(--ink); border: 1px solid var(--edge); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--void) 72%, transparent); cursor: pointer;
}
.loc-map-credit { position: absolute; right: 16px; z-index: 3; margin: 0;
  bottom: calc(var(--foot-h, 0px) + var(--safe-bottom, 0px) + 4px);
  font-size: 10px; color: var(--ink-2); text-shadow: 0 1px 2px var(--void); pointer-events: none; }
/* Строка вида: кнопки 360 View и Map View и тумблер спутника — по центру у нижней кромки
   слайда. Центрирование полями, а не transform: transform у детей слайда держит анимация. */
#slide > .loc-bar {
  position: absolute; left: 0; right: 0; bottom: 16px; margin-inline: auto; width: fit-content;
  display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; z-index: 11;
}
.loc-views { display: inline-flex; gap: 6px; }
.loc-sat { gap: 10px; }
.loc-sat[hidden] { display: none; }
.loc-sat input { width: 16px; height: 16px; margin: 0; accent-color: var(--im-accent); }
/* Ниже 1024 слайд — колонка: строка вида садится над листом, в поток. Открыта карта —
   управление панорамой скрыто, и отступ, что уводил лист вниз, берёт строка. */
@media (max-width: 1023px) {
  #slide[data-layout="poi"] > .loc-bar {
    position: static; order: 1; flex: none; margin: var(--space-2) auto 0;
    padding: 0 calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  }
  #slide[data-layout="poi"] > .loc-map[data-on="1"] ~ .loc-bar { margin-top: auto; }
}

/* Появление содержимого попапа: группы и карта внутри проявляются со сдвигом по времени,
   а не возникают разом. Кадр снимается под prefers-reduced-motion общим блоком выше. */
.exec-popup[data-reveal="1"] > * { animation: exec-rise var(--slow) var(--ease) backwards; }
.exec-popup[data-reveal="1"] > :nth-child(2) { animation-delay: 90ms; }
.exec-popup[data-reveal="1"] > :nth-child(3) { animation-delay: 180ms; }
.exec-popup[data-reveal="1"] .im-map-frame,
.exec-popup[data-reveal="1"] .exec-figures > * {
  animation: exec-rise var(--slow) var(--ease) 140ms backwards;
}
@keyframes exec-rise { from { opacity: 0; transform: translateY(10px); } }
/* Счётчик разделов между стрелками попапа. Ряд растягивает детей по высоте кнопок, и
   строка «1 / 3» вставала к верху 44 px — на 13 px выше центра стрелок. */
.exec-count { color: var(--ink-3); font-size: 12px; font-variant-numeric: tabular-nums;
  min-width: 34px; text-align: center; align-self: center; }

/* ——— панель управления панорамой ——— */
/* Кнопки вьюера стоят над полосой модулей, а не в ней: полоса кроет слайд своим этажом, и
   нижняя кнопка колонки оказывалась под её списком — hit-test отдавал ul полосы. Мера выреза
   та же, какой слайд режет себе кромку (--band-modules): полоса и отступ не расходятся. */
.pano-tools { position: absolute; right: 16px; bottom: calc(16px + var(--band-modules, 0px));
  display: grid; gap: 8px; z-index: 11; }

/* Индикатор загрузки панорамы. Волна в 5 % прозрачности на тёмном кадре не читалась, поэтому
   долю показывает кольцо с процентом, а волна осталась фоном. Значения пишет приложение:
   `--p` — доля 0..1 для кольца, `--pct` — целые проценты для подписи, состояние — data-state. */
@property --p { syntax: '<number>'; inherits: true; initial-value: 0; }
@property --pct { syntax: '<integer>'; inherits: true; initial-value: 0; }

#loading {
  --ring: clamp(72px, 12vmin, 104px);
  position: absolute; inset: 0; z-index: 8; pointer-events: none; opacity: 0;
  /* Тайлы приходят партиями: без перехода по --p и --pct кольцо и цифры прыгали бы ступенями. */
  transition: opacity var(--mid) var(--ease-soft),
              --p var(--slow) linear, --pct var(--slow) linear;
  background: linear-gradient(100deg, transparent 38%,
    color-mix(in srgb, var(--tint) 5%, transparent) 50%, transparent 62%);
  background-size: 260% 100%;
  counter-reset: im-pct var(--pct);
}
#loading[data-on="1"] { opacity: 1; animation: shimmer 1.5s linear infinite; }
@keyframes shimmer { from { background-position: 130% 0; } to { background-position: -130% 0; } }

/* Кольцо и подпись появляются вместе с самим слоем: масштаб идёт от .88 к 1. */
#loading::before, #loading::after {
  content: ''; position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(.88);
  transition: transform var(--mid) var(--ease);
}
#loading[data-on="1"]::before,
#loading[data-on="1"]::after { transform: translate(-50%, -50%) scale(1); }
/* Маска вырезает середину кольца — под цифры. Тёмная заливка под конусом держит дорожку
   читаемой на светлом кадре: сама дорожка полупрозрачная. */
#loading::before {
  width: var(--ring); height: var(--ring); border-radius: 50%;
  background-color: color-mix(in srgb, var(--void) 50%, transparent);
  background-image: conic-gradient(var(--im-accent) calc(var(--p, 0) * 360deg),
                                   color-mix(in srgb, var(--tint) 12%, transparent) 0);
  -webkit-mask-image: radial-gradient(closest-side, transparent 70%, #000 71%);
          mask-image: radial-gradient(closest-side, transparent 70%, #000 71%);
}
#loading::after {
  content: counter(im-pct) '%';
  display: grid; place-items: center;
  width: calc(var(--ring) * .72); height: calc(var(--ring) * .72); border-radius: 50%;
  background: color-mix(in srgb, var(--void) 55%, transparent);
  backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .5);
  color: var(--ink); font-size: calc(var(--ring) * .18); line-height: 1;
  font-variant-numeric: tabular-nums; letter-spacing: .01em;
}
/* Ни одного тайла ещё не пришло: кольцо дышит, иначе ноль процентов выглядит зависшим. */
#loading[data-state="wait"]::before { animation: pano-wait 1.6s var(--ease-soft) infinite; }
@keyframes pano-wait { 0%, 100% { opacity: .5; } 50% { opacity: 1; } }

/* ——— таблица ——— */
.table-wrap {
  position: absolute; inset: 74px 32px 84px; overflow: auto; border-radius: var(--r-lg);
  border: 1px solid var(--edge); background: var(--glass-3);
  box-shadow: var(--sheen), var(--lift-3); backdrop-filter: var(--blur);
  scrollbar-width: thin;
}
/* Полоса прокрутки таблицы объявлена тонкой. Признака прокрутки этого мало: накладная
   полоса macOS до первого движения не видна. Прежняя попытка дать тени у кромок —
   background-attachment: local у четырёх слоёв — стояла ДО `background` и сбрасывалась его
   же сокращением: слоёв не было, и тени не было тоже. Строка снята как мёртвая. */
table { border-collapse: separate; border-spacing: 0; min-width: 920px; width: max-content; font-size: 13.5px; }
/* Правовая строка под таблицей: в той же прокрутке, прижата к левому краю при прокрутке вбок. */
.table-note {
  position: sticky; left: 0; margin: 0; padding: 12px 16px; max-width: 80ch;
  font-size: 11.5px; line-height: 1.4; white-space: pre-line; color: var(--ink-3);
}
th, td { padding: 12px 16px; text-align: left; white-space: nowrap; }
td { font-variant-numeric: tabular-nums; }
thead th {
  position: sticky; top: 0; z-index: 1;
  background: linear-gradient(color-mix(in srgb, var(--cell-head) 99%, transparent),
                              color-mix(in srgb, var(--cell-head) 96%, transparent));
  border-bottom: 1px solid var(--edge);
  font-weight: 400; font-size: 12px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3);
}
thead .table-groups th {
  top: 0; height: 30px; padding: 7px 16px 4px;
  color: var(--im-accent); font-size: 10px; letter-spacing: .14em;
  border-bottom: 0;
}
thead tr:not(.table-groups) th { top: 30px; }
/* Кнопка сортировки занимает всю ячейку: на телефоне по заголовку надо попадать пальцем. */
thead th { padding: 0; }
thead th button,
thead th.table-col--actions > span {
  all: unset; box-sizing: border-box; cursor: pointer;
  display: flex; align-items: center; gap: 6px;
  width: 100%; min-height: var(--tap); padding: 12px 16px; color: inherit;
}
/* Подпись Actions живёт без кнопки сортировки: поля те же, что у соседей, и прижата
   вправо — к пилюле Floorplan, которую она называет. */
thead th.table-col--actions > span { justify-content: flex-end; cursor: default; }
thead th button:hover { color: var(--ink); }
thead th[aria-sort] button { color: var(--im-accent); }
thead th .arrow { font-size: 9px; opacity: .8; }
/* Ведущая иконка группы в ячейке: одна посадка на все четыре. Дочерний комбинатор
   отсекает иконку пилюли Floorplan и шевроны раскрытия строки. */
tbody td > svg { width: 16px; height: 16px; margin-right: 6px; vertical-align: -3px; color: var(--im-accent); }
/* Контур droplet в своём viewBox смещён вверх: полпикселя вниз. transform раскладку не трогает. */
tbody td.table-col--equipment > svg { transform: translateY(.5px); }
table td.table-col--actions { text-align: right; }
.table-plan { margin-left: auto; }
tbody tr {
  cursor: pointer;
  transition: background var(--fast) var(--ease-soft), box-shadow var(--fast) var(--ease-soft);
}
tbody td { border-bottom: 1px solid color-mix(in srgb, var(--tint) 5.5%, transparent); }
tbody tr:hover td, tbody tr:focus-visible td { background: color-mix(in srgb, var(--tint) 6%, transparent); }
tbody tr:focus-visible { outline: 2px solid var(--im-accent); outline-offset: -2px; }
:root {
  /* Непрозрачные ячейки прижатых колонок: основа, чуть подсвеченная --tint. */
  --cell: color-mix(in srgb, var(--void) 96.7%, var(--tint));
  --cell-hover: color-mix(in srgb, var(--void) 93.4%, var(--tint));
  --cell-head: color-mix(in srgb, var(--void) 97.5%, var(--tint));
}
thead th.table-col--actions, tbody td.table-col--actions { position: sticky; right: 0; }
/* Группа сделки прижата целиком: цена, статус и действие не должны уезжать под прокрутку. */
tbody td.table-col--deal, thead tr:not(.table-groups) th.table-col--deal { position: sticky; }
/* --pin-act и --pin-st выставляет скрипт по фактической ширине колонок Actions и Status.
   Значения в var() — запас на первый кадр, до первого замера. */
tbody td.table-col--deal:nth-last-child(2), thead tr:not(.table-groups) th.table-col--deal:nth-last-child(2) {
  right: var(--pin-act, 76px);
}
tbody td.table-col--deal:nth-last-child(3), thead tr:not(.table-groups) th.table-col--deal:nth-last-child(3) {
  right: calc(var(--pin-act, 76px) + var(--pin-st, 96px));
}
tbody td.table-col--deal, tbody td.table-col--actions { background: var(--cell); z-index: 1; }
thead tr:not(.table-groups) th.table-col--deal, thead th.table-col--actions { background: var(--cell-head); z-index: 4; }
tbody tr:hover td.table-col--deal, tbody tr:hover td.table-col--actions { background: var(--cell-hover); }
tbody td.table-col--deal:nth-last-child(3) { box-shadow: -1px 0 0 color-mix(in srgb, var(--tint) 6%, transparent); }
tbody td.table-col--actions { background: var(--cell); z-index: 2; }
thead th.table-col--actions { z-index: 4; background: var(--cell-head); }
tbody tr:hover td.table-col--actions { background: var(--cell-hover); }
tbody td.table-col--actions { box-shadow: -1px 0 0 color-mix(in srgb, var(--tint) 6%, transparent); }
thead th.table-col--actions { box-shadow: -1px 0 0 color-mix(in srgb, var(--tint) 10%, transparent); }
thead .table-groups th:last-child {
  position: sticky; right: 0; z-index: 5; background: var(--cell-head);
}
thead .table-groups th { display: table-cell; }
thead .table-groups th svg { width: 13px; height: 13px; margin-right: 7px; vertical-align: -2px;
  color: var(--im-accent); opacity: .9; }
tbody tr td:first-child { position: sticky; left: 0; }
tbody td:first-child { background: var(--cell); z-index: 2; box-shadow: 1px 0 0 color-mix(in srgb, var(--tint) 6%, transparent); }
thead tr:not(.table-groups) th:first-child { position: sticky; left: 0; z-index: 4; box-shadow: 1px 0 0 color-mix(in srgb, var(--tint) 10%, transparent); }
tbody tr td:first-child::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--status, transparent);
  transform: scaleY(0); transition: transform var(--mid) var(--ease);
}
tbody tr:hover td:first-child::before { transform: scaleY(1); }
/* Цвет ячейки статуса — чернила статуса строки: цвет палитры бренда, пока он читается на
   теме, иначе он же с подвинутой светлотой (statusInk в tools/im_site.js). */
td.is-status { font-weight: 500; color: var(--status-ink, inherit); }
.empty { padding: 40px; text-align: center; color: var(--ink-3); }

/* Раскрытие строки. Живёт только на планшете: там из кадра уходят AREAS и EQUIPMENT,
   а их значения нужны. От 1024 кнопка и строка сняты целиком — разметка та же, вид и
   порядок обхода прежние: display:none выносит кнопку и из табуляции тоже. */
.row-expand { display: none; margin-left: 6px; }
tr.row-detail { display: none; cursor: default; }
tbody tr.row-detail td {
  position: static; z-index: auto; white-space: normal;
  background: color-mix(in srgb, var(--tint) 3%, transparent); box-shadow: none;
}
tbody tr.row-detail:hover td { background: color-mix(in srgb, var(--tint) 3%, transparent); }
tbody tr.row-detail td::before { content: none; }
.row-detail dl {
  margin: 0; display: grid; gap: 6px var(--space-3);
  grid-template-columns: max-content minmax(0, 1fr) max-content minmax(0, 1fr);
}
.row-detail dt { display: flex; align-items: center; gap: 7px; color: var(--ink-3); }
.row-detail dt svg { width: 15px; height: 15px; color: var(--im-accent); }
.row-detail dd { margin: 0; font-variant-numeric: tabular-nums; }

/* ——— список юнитов на телефоне ——— */
/* Ниже 768 таблицы нет вовсе: скрипт собирает карточки, и слайд становится колонкой —
   полоса кнопок сверху, список в собственной прокрутке, панель фильтров снизу. */
#slide[data-layout="cards"] { display: flex; flex-direction: column; overflow: hidden; }
/* Локация — тоже колонка, и на всех ширинах: от 1024 в ней стоят заголовок и лист одним
   блоком, ниже 1024 — заголовок, просвет панорамы и лист у нижней кромки. */
#slide[data-layout="poi"] { display: flex; flex-direction: column; overflow: hidden; }
.list-tools {
  flex: none; display: flex; align-items: center; gap: var(--space-2);
  padding: var(--space-2) var(--space-3) 0;
}
.list-tools .pill { min-height: var(--tap-min); padding: 10px var(--space-4); }
.list-count { margin-left: auto; font-size: var(--font-sm); color: var(--ink-3);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.unit-list {
  flex: 1 1 auto; min-height: 0; overflow: auto; overscroll-behavior: contain;
  scrollbar-width: thin; list-style: none; margin: 0;
  /* Плавающий призыв висит поверх слайда в правом нижнем углу и заходит на 56 px внутрь
     списка. Таблица оставляет ему тот же зазор нижней врезкой — карточкам он нужен снизу,
     иначе кнопки последней из них так и остаются под ним. */
  padding: var(--space-2) var(--space-3) calc(var(--space-3) + 56px);
  display: grid; gap: var(--space-2); align-content: start;
}
/* Правовая строка — последней строкой списка карточек. */
.list-note { font-size: 11.5px; line-height: 1.4; white-space: pre-line; color: var(--ink-3);
  padding: var(--space-2) 2px 0; }
.unit-card {
  display: grid; grid-template-columns: minmax(0, 1fr) auto;
  align-items: start; gap: var(--space-2) var(--space-3);
  padding: var(--space-3); border-radius: var(--r-md);
  border: 1px solid var(--edge); border-left: 2px solid var(--status, var(--edge));
  background: var(--glass-3); box-shadow: var(--sheen), var(--lift-1);
}
.uc-head { grid-column: 1; margin: 0; min-width: 0;
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; }
.uc-id { font-size: var(--font-lg); letter-spacing: -.01em; }
.uc-code { font-size: var(--font-sm); color: var(--ink-3); letter-spacing: .06em; }
.uc-badge {
  grid-column: 2; justify-self: end; padding: 3px 10px;
  border: 1px solid currentColor; border-radius: var(--r-pill);
  color: var(--status-ink, var(--ink-2));
  /* 14% — STATUS_WASH в tools/im_site.js: чернила бейджа держат контраст и на этой заливке. */
  background: color-mix(in oklab, var(--status, transparent) 14%, transparent);
  font-size: var(--font-sm); white-space: nowrap;
}
.uc-facts {
  grid-column: 1 / -1; margin: 0; font-size: var(--font-sm);
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: 2px var(--space-3);
}
.uc-facts dt { color: var(--ink-3); }
.uc-facts dd { margin: 0; font-variant-numeric: tabular-nums; }
.uc-actions { grid-column: 1 / -1; display: flex; gap: var(--space-2); }
.uc-actions .pill { flex: 1 1 0; min-width: 0; min-height: var(--tap-min);
  padding: 10px var(--space-3); font-size: var(--font-sm); }

/* Панель сортировки и фильтров. Немодальная: фокус уходит внутрь и возвращается кнопке,
   которая её открыла, а вуаль, крестик и Escape закрывают. */
/* Указатель отбирается через коробку модуля: правило `#slide > *` весом с идентификатором,
   и закрытая вуаль иначе продолжала бы ловить касания по списку. Коробок у модуля две —
   слайд цепочки и тело панели, — и названы обе. Под именем одного слайда правило в панели
   не срабатывало вовсе: закрытая вуаль оставалась при родном `pointer-events: auto`, лежала
   слоем во всю коробку и глушила модуль целиком — ни сортировки, ни фильтра, ни строки
   списка нажать было нельзя, и нажатие висело до потолка проверки. */
.list-veil { z-index: var(--z-inset-veil); }
#slide > .list-veil, .module-body > .list-veil { pointer-events: none; }
#slide > .list-veil[data-open="1"],
.module-body > .list-veil[data-open="1"] { pointer-events: auto; }
.list-sheet {
  position: absolute; inset: auto 0 0 0; z-index: var(--z-inset-sheet);
  display: flex; flex-direction: column; gap: var(--space-3);
  max-height: min(74%, 560px);
  /* Подвал приложения выше нижней кромки слайда ровно на вырез: без этого отступа
     последняя кнопка панели уходит ему под низ. */
  padding: var(--space-3) var(--space-3) calc(var(--space-3) + var(--safe-bottom));
  /* Лист стоит на нижней кромке кадра: кайма и скругление только сверху. Блик снят —
     он рисует пару кромок, а нижняя у листа за кадром. */
  border-top: 1px solid var(--ov-edge);
  border-radius: var(--ov-radius) var(--ov-radius) 0 0;
  box-shadow: var(--ov-lift-edge);
  transform: translateY(101%);
  transition: transform var(--ov-move);
}
.list-sheet[data-open="1"] { transform: none; }
#slide > .list-sheet[inert] { pointer-events: none; }
/* Кадровая анимация слайда панель не касается: rise и fall перебивают её собственный
   transform, и закрытая панель на полсекунды выезжала на экран. */
#slide > .list-sheet, #slide > .list-veil,
#slide[data-leaving="1"] > .list-sheet, #slide[data-leaving="1"] > .list-veil { animation: none; }
.sheet-head { flex: none; --ov-head-gap: var(--space-2); }
.sheet-head h2 { font-size: var(--ov-title-compact); }
.sheet-body {
  min-height: 0; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin;
  display: grid; gap: var(--space-4); align-content: start;
}
.sheet-group h3 {
  font-family: var(--im-font-base), sans-serif;
  font-size: var(--font-sm); letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-3);
}
.sheet-sort, .sheet-filters { display: grid; gap: 6px; margin-top: var(--space-2); }
.sheet-sort .pill, .sheet-filters .pill {
  width: 100%; justify-content: flex-start; border-radius: var(--r-md);
  min-height: var(--tap-min); padding: 10px var(--space-3); font-size: var(--font-sm);
}
.sheet-sort .unit, .sheet-sort .arrow { color: var(--ink-3); }
.sheet-sort .arrow { margin-left: auto; font-size: 10px; }
.sheet-sort .pill[aria-pressed="true"] .unit,
.sheet-sort .pill[aria-pressed="true"] .arrow { color: inherit; }

.filters {
  position: absolute; inset: 12px auto auto 32px; z-index: 11;
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center;
}
.level-filter { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }
/* Ряд галереи модуля (G-15): в углу сцены, пустое место в нём указателя не берёт. */
.gallery-strip {
  position: absolute; left: 32px; bottom: calc(48px + var(--band-modules, 0px)); z-index: 11;
  display: flex; gap: 8px; align-items: center; max-width: calc(100% - 120px);
  overflow-x: auto; pointer-events: none;
}
.gallery-strip > * { pointer-events: auto; flex: none; }
.gallery-strip-item { padding: 0; border: 1px solid var(--line, #fff4); border-radius: 6px;
  overflow: hidden; background: none; cursor: pointer; min-width: 44px; min-height: 44px; }
.gallery-strip-item img { display: block; width: 56px; height: 44px; object-fit: cover; }
.swatch { width: 9px; height: 9px; border-radius: 50%; box-shadow: 0 0 10px -2px currentColor; }

/* ——— план ——— */
/* Ряд фильтров и панель зума лежат поверх сцены, и под ними оказывались контуры юнитов —
   нажать на такой контур было нечем. Полосы под обе панели вычтены из слоя масштабирования:
   план и разметка юнитов вписываются в остаток, каждый по своему `contain`, и совпадают.
   Ниже 1024 раскладка другая — поля переставляет свой блок. */
#plan-stage {
  position: absolute; inset: 0; overflow: hidden; touch-action: none; cursor: grab;
  --plan-gut-top: calc(var(--tap) + var(--space-4));
  --plan-gut-right: calc(var(--tap) + var(--space-5));
  --plan-gut-bottom: 0px;
  --plan-gut-left: 0px;
}
/* Текст слайда плана. Колонка стоит слева от сцены, и сцена отдаёт ей полосу тем же
   способом, что панелям: полем слоя отсечения. Текста нет — колонки нет, поле нулевое.
   Ниже 1024 колонка идёт в потоке, её раскладку держат блоки плана ниже. */
@media (min-width: 1024px) {
  #slide[data-layout="plan"] > .left-col { width: min(360px, 30vw); }
  #slide[data-layout="plan"]:has(> .left-col) > #plan-stage {
    --plan-gut-left: calc(var(--col-left) + min(360px, 30vw) + var(--space-4));
  }
}
#plan-stage[data-drag="1"] { cursor: grabbing; }
/* Отсечение и масштабирование — разные слои. Приближённый план выходит за свою коробку,
   и обрезать его обязан слой, который сам не двигается: иначе поля под панели он проходит
   насквозь и контуры юнитов снова оказываются под кнопками. */
#plan-stage .plan-clip {
  position: absolute; overflow: hidden;
  inset: var(--plan-gut-top) var(--plan-gut-right) var(--plan-gut-bottom) var(--plan-gut-left);
}
#plan-stage .zoom {
  position: absolute; inset: 0; transform-origin: 0 0;
  transition: transform var(--mid) var(--ease-soft);
}
#plan-stage[data-drag="1"] .zoom { transition: none; }
/* display: block обязателен. Строчная картинка стоит на базовой линии, и строка под ней
   добавляет место под выносные элементы: коробка .zoom выходила за свой слой отсечения на
   6–7 px, и .plan-clip срезал нижнюю кромку плана, не объявляя этого. */
#plan-stage img.bg { display: block; width: 100%; height: 100%; object-fit: contain; user-select: none; }
#plan-stage svg { position: absolute; inset: 0; width: 100%; height: 100%; }
/* Правовая строка вьюера — у нижнего края сцены, слева; указатель проходит к плану. */
#plan-stage .plan-note {
  position: absolute; z-index: 1; margin: 0; pointer-events: none;
  left: calc(var(--plan-gut-left) + var(--space-4)); bottom: var(--space-4);
  max-width: min(72ch, 60%); font-size: 11.5px; line-height: 1.4; white-space: pre-line;
  color: var(--ink-3);
}
#slide > .plan-note { margin: var(--space-3) 0 0; font-size: 11.5px; color: var(--ink-3); white-space: pre-line; }
#plan-stage svg [id^="unit"] {
  cursor: pointer; fill-opacity: .42;
  transition: fill-opacity var(--fast) var(--ease-soft), filter var(--fast) var(--ease-soft);
}
#plan-stage svg [id^="unit"]:hover { fill-opacity: .82; filter: brightness(1.25); }
/* Рамка стадии на плане (page.stage_framing, ключ документа stageFraming.highlight). Набор
   юнитов стадии обведён поверх статусов: заливка остаётся цветом статуса, стадия добавляет
   кайму. Кайма не масштабируется вместе с планом — на любом приближении она одной толщины. */
#plan-stage svg [id^="unit"][data-stage] {
  stroke: var(--im-accent); stroke-width: 2.5; stroke-opacity: .95;
  vector-effect: non-scaling-stroke;
}
/* Заливка: тот же цвет статуса, только плотнее — контраст статуса растёт, а не съедается. */
#plan-stage svg [id^="unit"][data-stage="fill"] { fill-opacity: .82; }
#plan-stage svg [id^="unit"]:focus-visible { outline: 2px solid var(--im-accent); }
.plan-tip {
   position: absolute; z-index: 12; pointer-events: none; padding: 7px 12px;
   border-radius: var(--r-sm); border: 1px solid var(--edge); background: var(--glass-3);
   backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
   box-shadow: var(--lift-2); font-size: 12.5px; white-space: nowrap;
  opacity: 0; transform: translate(-50%, -140%) scale(.96);
  transition: opacity var(--fast) linear, transform var(--fast) var(--ease);
}
.plan-tip[data-show="1"] { opacity: 1; transform: translate(-50%, -140%) scale(1); }

/* Слайд плана ниже 1024. Фильтры со сводным PDF и панель зума там перестают лежать
   поверх сцены: статичный ряд фильтров вообще оказывался под ней, а до кнопок нельзя
   было дотянуться. Слайд становится колонкой — фильтры, сцена, зум, — и ничто ни на что
   не наезжает. Полосу под подвал и призыв слайд вырезает из себя сам (--band-cta):
   собственный отступ снизу тут дублировал бы её и отнимал у сцены вторую такую же.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 1023px) {
  #slide[data-layout="plan"] {
    display: flex; flex-direction: column; overflow: hidden;
  }
  /* Пять статусов и ссылка на сводный PDF занимали до трёх рядов — 165 px из 448, и
     сцене оставалось 111. Ряд идёт одной строкой и прокручивается вбок: полоса под
     фильтры фиксирована высотой одной цели, сколько бы статусов ни пришло паком. */
  #slide[data-layout="plan"] > .filters {
    order: -1; flex: none; position: static; z-index: auto;
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    scrollbar-width: thin; touch-action: pan-x;
    padding: var(--space-2) calc(var(--space-3) + var(--safe-right))
             var(--space-2) calc(var(--space-3) + var(--safe-left));
    /* Признак прокрутки. Накладная полоса прокрутки macOS до первого движения не видна, и
       ряд читался как обрезанный без причины: ссылка на сводный PDF уходила за кромку
       целиком. Правая кромка затухает на 24 px. Маска проявляется сама — пока ряд
       помещается в кадр, за ней пусто и на экране ничего не меняется. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
  #slide[data-layout="plan"] > .filters > * { flex: none; }
  /* Текст идёт первым и не выше трети кадра: гарантия сцены ниже важнее длины текста. */
  #slide[data-layout="plan"] > .left-col {
    order: -2; flex: 0 1 auto; min-height: 0; max-height: 30vh; overflow: auto;
  }
  #slide[data-layout="plan"] > #plan-stage {
    position: relative; inset: auto; flex: 1 1 auto;
    padding: 8px;
    /* Гарантия сцены: остаток колонки, но не меньше этой меры. Ниже неё контуры юнитов
       становятся ниже пальца, и план перестаёт быть планом. */
    min-height: min(220px, 40vh);
    /* Полосы под панели обнуляются: панели ушли из кадра сцены в поток колонки. Нижняя
       полоса нулевая и в базовом правиле #plan-stage — повторять её здесь нечего. */
    --plan-gut-top: 0px; --plan-gut-right: 0px;
  }
  #slide[data-layout="plan"] > .pano-tools {
    position: static; flex: none; grid-auto-flow: column; justify-content: end;
    padding: var(--space-2) calc(var(--space-3) + var(--safe-right))
             0 calc(var(--space-3) + var(--safe-left));
  }
}

/* План на низком кадре — это ландшафт телефона. По высоте там 262 px на всё: ряд
   фильтров и ряд зума забирали их целиком, и сцена выходила в 0. Высоты нет,
   зато есть ширина: колонка ложится набок. Фильтры и зум уходят в левую полосу,
   сцена занимает остаток кадра во всю его высоту.
   Границы в условии — литералы, @media значение var() не читает. */
@media (max-width: 1023px) and (max-height: 500px) {
  #slide[data-layout="plan"] {
    display: grid; grid-template-columns: min(38%, 300px) minmax(0, 1fr);
    grid-template-rows: auto auto minmax(0, 1fr);
  }
  #slide[data-layout="plan"] > .filters { grid-area: 1 / 1; }
  #slide[data-layout="plan"] > .pano-tools { grid-area: 2 / 1; justify-content: start; }
  #slide[data-layout="plan"] > .left-col {
    grid-area: 3 / 1; max-height: none; padding-top: var(--space-2);
  }
  #slide[data-layout="plan"] > #plan-stage {
    grid-area: 1 / 2 / -1 / 3; min-width: 0; min-height: 0;
  }
}

/* Таблица юнитов на низком кадре — ландшафт телефона. Из 262 px слайда таблице доставалось
   120: ряд фильтров сверху, шапка в две строки на 75 px, строка в 65 px — ни одной строки
   целиком. Высоты нет, ширина есть: фильтры уходят столбцом влево, таблица забирает всю
   высоту слайда. Шапка теряет строку групп, ячейка — поля по вертикали: высоту строки держат
   её кнопки в 44 px. Статусов больше, чем входит по высоте, — столбец фильтров прокручивается
   сам. Таблица шире остатка листается вбок с закреплёнными номером и действиями, как на
   десктопе. Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 1023px) and (max-height: 500px) {
  #slide:has(> .table-wrap) {
    display: grid; grid-template-columns: max-content minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr); gap: var(--space-2); overflow: hidden;
    padding: var(--space-2) calc(16px + var(--safe-right)) var(--space-2) calc(16px + var(--safe-left));
  }
  #slide:has(> .table-wrap) > .filters {
    position: static; padding: 0; min-height: 0;
    flex-direction: column; flex-wrap: nowrap; align-items: stretch; gap: 4px;
    overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; touch-action: pan-y;
  }
  #slide:has(> .table-wrap) > .filters > * { flex: none; justify-content: flex-start; padding-inline: 12px; }
  #slide > .table-wrap { position: static; inset: auto; min-width: 0; min-height: 0; }
  .table-wrap thead .table-groups { display: none; }
  .table-wrap thead tr:not(.table-groups) th { top: 0; }
  .table-wrap tbody tr:not(.row-detail) > td:not(.empty) { padding: 0 6px; }
  .table-wrap thead th button,
  .table-wrap thead th.table-col--actions > span { padding: 0 6px; }
}

/* ——— попапы ——— */
dialog.dialog--poi {
  /* Высота по содержимому: справка на два абзаца во весь кадр выглядела пустой колонкой.
     Панель прижата к правой кромке и стоит по центру по вертикали. */
  position: fixed; inset: auto 0 auto auto; top: 50%; translate: 0 -50%;
  width: min(33.333vw, 520px); height: fit-content;
  max-width: none; max-height: min(78vh, calc(100vh - 96px)); margin: 0;
  border-radius: var(--r-lg) 0 0 var(--r-lg);
  /* Немодальный <dialog> в верхний слой не попадает: без полосы он лежит под холстом
     панорамы и крестик перекрывает #head. Этаж и его соседи — в лестнице --z-* наверху
     файла: выше слайда, хромы, призыва и полосы прогресса; ниже шторки, тура и тоста. */
  z-index: var(--z-poi-panel);
}
dialog.dialog--poi .body { max-height: calc(78vh - 78px); }
/* Панель точки приезжает и уезжает вбок: это справка у правой кромки, а не окно по центру.
   Пара с [open] обязательна: без второй строки открытая панель навсегда останется сдвинутой
   на 28px и обрежется краем кадра. transform в общий список переходов диалога не входит —
   там translate, его занимает ведение пальцем, — поэтому список повторён здесь целиком с
   добавленным transform. Базовое правило dialog не трогается: оно держит все прочие попапы.
   Ниже по файлу стоит dialog[data-drag="1"] { transition: none } той же специфичности и
   перебивает эту строку: за пальцем лист по-прежнему идёт без сглаживания. */
dialog.dialog--poi {
  transform: translateX(28px);
  transition: opacity var(--mid) var(--ease-soft), transform var(--mid) var(--ease),
              translate var(--mid) var(--ease),
              overlay var(--mid) var(--ease) allow-discrete,
              display var(--mid) var(--ease) allow-discrete;
}
dialog.dialog--poi[open] { transform: none; }
@starting-style { dialog.dialog--poi[open] { opacity: 0; transform: translateX(28px); } }
/* Уход задаётся только анимацией. closeDialog() меряет animationDuration с самого диалога;
   transition он увидит нулём и закроет панель мгновенно — кадр ухода пропадёт. */
dialog.dialog--poi[data-closing="1"] { animation: poi-out var(--mid) var(--ease-soft) forwards; }
@keyframes poi-out { to { opacity: 0; transform: translateX(28px); } }
/* Закрытие анимируется только переходом: close() снимает display в том же кадре, поэтому
   display и overlay переводятся в дискретном режиме, а @starting-style даёт кадр входа. */
dialog {
  /* Высоту попапа задаёт видимая часть окна, а не вёрстка: поднятая клавиатура layout
     viewport не трогает, и без этого низ формы уходил ей под низ. Клавиатуры нет —
     значение упирается в 90vh, ровно как было. */
  --dlg-h: min(90vh, calc(var(--vv-h, 100vh) - 2 * var(--space-3)));
  /* Шапка слоя ростом в одну строку заголовка с полями; тело получает остаток. */
  --dlg-head: 78px;
  --dlg-body-pad: var(--ov-pad-body);
  --dlg-body-h: calc(var(--dlg-h) - var(--dlg-head));
  /* Заливка, размытие, кайма и скругление — из слоя перекрытий (.ov-surface, .ov-framed). */
  padding: 0; color: var(--ink);
  box-shadow: var(--ov-lift);
  max-width: min(1120px, 94vw); max-height: var(--dlg-h); overflow: hidden;
  /* Модалку браузер центрирует полями auto между inset: 0. Нижнюю кромку поднимаем на
     высоту клавиатуры — центр уезжает вместе с ней, и попап остаётся в видимой части.
     Клавиатуры нет — ноль, то есть ровно то же значение, что ставит браузер. */
   bottom: var(--vv-keyboard, 0px);
   opacity: 0;
   translate: 0 var(--drag-y, 0px);
   transition: opacity var(--mid) var(--ease-soft), translate var(--mid) var(--ease),
              overlay var(--mid) var(--ease) allow-discrete,
              display var(--mid) var(--ease) allow-discrete;
}
/* Пока палец ведёт слой, сдвиг идёт без сглаживания: иначе слой отстаёт от пальца. */
dialog[data-drag="1"] { transition: none; }
/* Лист тянут за шапку и за ручку: тело под ними остаётся прокруткой. */
dialog .head { touch-action: none; }
/* Немодальная панель дискретных переходов display и overlay не получает: close() снимает
   её тем же кадром. Кадры ухода задаёт эта анимация, а скрипт откладывает close() ровно
   на её длительность и меряет её же по computed-стилю. */
dialog[data-closing="1"] { animation: dialog-out var(--mid) var(--ease-soft) forwards; }
@keyframes dialog-out { to { opacity: 0; } }
dialog[open] { opacity: 1; transform: none; }
@starting-style { dialog[open] { opacity: 0; } }
/* Пока идёт затухание, диалог ещё на экране — клики он ловить не должен. */
dialog:not([open]) { pointer-events: none; }
/* Клик по затемнённому фону закрывает попап, и курсор об этом сообщает заранее. Затемнение
   рисует ::backdrop, но попадание указателя по нему разрешается в сам диалог — поэтому вид
   курсора над фоном задаётся диалогу, а строки карточки его снимают: полей у диалога нет,
   и карточку они закрывают целиком. Немодальная панель точки фона не имеет и сюда не
   входит: закрывают её крестик, Escape и свайп вниз. */
#app[data-dialog-open="modal"] dialog[open] { cursor: pointer; }
#app[data-dialog-open="modal"] dialog[open] > * { cursor: auto; }
/* Всё нажимаемое внутри попапа отзывается пальцем под указателем: орган управления от
   текста отличает не только вид. Видимую рамку фокуса им даёт общее правило :focus-visible. */
dialog button:not([disabled]), dialog a[href], dialog summary, dialog select,
dialog [role="button"], dialog label.consent { cursor: pointer; }
dialog::backdrop {
  background: var(--ov-veil-modal);
  backdrop-filter: var(--ov-veil-modal-blur); -webkit-backdrop-filter: var(--ov-veil-modal-blur);
  opacity: 0;
  transition: opacity var(--mid) var(--ease-soft),
              overlay var(--mid) var(--ease-soft) allow-discrete,
              display var(--mid) var(--ease-soft) allow-discrete;
}
dialog[open]::backdrop { opacity: 1; }
@starting-style { dialog[open]::backdrop { opacity: 0; } }
dialog .head {
  --ov-head-gap: 20px; justify-content: space-between;
  padding: var(--ov-pad-head); border-bottom: 1px solid var(--ov-edge);
}
dialog .head h3 { font-size: var(--ov-title); }
dialog .body { padding: var(--dlg-body-pad); display: grid; gap: 16px; overflow: auto;
  max-height: var(--dlg-body-h); }
/* Прокручиваемое тело — цель обхода с клавиатуры: без фокуса содержимое ниже кромки
   доставалось только мышью. Рамку фокуса ему даёт общее правило :focus-visible. */
dialog .body[tabindex="0"] { scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) {
  dialog .body[tabindex="0"] { scroll-behavior: auto; }
}
dialog img.slide { width: 100%; height: auto; border-radius: var(--r-md); display: block;
  background: color-mix(in srgb, var(--tint) 4%, transparent); }
dialog dl {
  display: grid; grid-template-columns: max-content 1fr; gap: 0; margin: 0;
  border: 1px solid var(--edge); border-radius: var(--r-md); overflow: hidden;
}
dialog dt, dialog dd { padding: 9px 14px; margin: 0;
  border-bottom: 1px solid color-mix(in srgb, var(--tint) 6%, transparent); }
dialog dt { color: var(--ink-3); background: color-mix(in srgb, var(--tint) 3%, transparent);
  display: flex; align-items: center; gap: 8px; }
dialog dt svg { width: 18px; height: 18px; flex: none; }
dialog dd { font-variant-numeric: tabular-nums; }
dialog .actions { display: flex; gap: 10px; flex-wrap: wrap; }
dialog .head .badge {
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase;
  padding: 5px 12px; border-radius: var(--r-pill); margin-left: auto;
  border: 1px solid currentColor;
}
dialog .head .badge[data-status] { color: var(--status-ink, inherit); }

/* Карточка юнита: план слева, характеристики справа. */
.unit-view {
  display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(300px, 1fr);
  gap: 22px; align-items: start;
}
.unit-view img.slide { max-height: 60vh; object-fit: contain; }
.unit-view .side { display: grid; gap: 16px; }
@media (max-width: 899px) { .unit-view { grid-template-columns: 1fr; } }

/* Подвал попапа. Лежит вне прокручиваемого тела: кнопки карточки юнита обязаны быть
   видны в любом положении списка характеристик. Создаёт его скрипт и только там, где
   он нужен, — на широком экране кнопки остаются в колонке справа. */
.dialog-foot {
  flex: none; display: flex; gap: var(--space-2);
  padding: var(--space-3) var(--space-4) calc(var(--space-3) + var(--safe-bottom));
  border-top: 1px solid var(--edge); background: color-mix(in srgb, var(--tint) 3%, transparent);
}
/* Две кнопки делят ширину телефона, и «Enquire about this unit» резалась многоточием на 360,
   390 и 430, «Floor plan PDF» — на 360. В подвале кнопка говорит коротко: хвост подписи
   снят, полное имя остаётся в aria-label. Подписи, которой всё равно тесно, открыта вторая
   строка в той же цели — многоточия нет ни на какой ширине. */
/* Ряд кнопок растянут на весь подвал: по содержимому он сжимался до переносимых подписей. */
.dialog-foot .actions { flex: 1 1 auto; min-width: 0; }
.dialog-foot .pill { flex: 1 1 0; min-width: 0; min-height: var(--tap-min);
  padding-inline: var(--space-3); }
.dialog-foot .pill span { white-space: normal; line-height: 1.25; text-align: center; }
.dialog-foot .pill-more { display: none; }

/* Попап сводки. Размер слоя от раздела не зависит: высота и ширина заданы, тело забирает
   остаток и прокручивается само, переключатель разделов стоит в подвале. По содержимому
   слой рос и садился на каждом шаге, центр модалки ехал, а с ним и стрелки.
   Стекло — то же, что у списка точек на слайде локации: первая высота с бликом и
   размытием. Затемнение под слоем слабее общего — слайд сводки остаётся виден. */
dialog.dialog--exec {
  display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  width: min(1120px, 94vw); height: min(var(--dlg-h), 840px);
  background: var(--ov-bg-soft); box-shadow: var(--ov-lift-soft);
}
/* Сетка включается сразу. Общий переход display с allow-discrete переводит block в grid
   на середине длительности: попап, открытый следом за закрытием другого, полкадра стоял
   блоком, тело сжималось по содержимому, и подвал подпрыгивал на 180 px. Уход слоя
   переход display сохраняет — у закрытого слоя это правило не действует. */
dialog.dialog--exec[open] {
  transition: opacity var(--mid) var(--ease-soft), translate var(--mid) var(--ease),
              overlay var(--mid) var(--ease) allow-discrete;
}
dialog.dialog--exec::backdrop {
  background: var(--ov-veil-light);
  backdrop-filter: var(--ov-veil-blur); -webkit-backdrop-filter: var(--ov-veil-blur);
}
dialog.dialog--exec .body { max-height: none; min-height: 0; align-content: start; }
dialog.dialog--exec .dialog-foot { justify-content: center; }
.exec-steps { display: flex; align-items: center; gap: 10px; }
/* Подвал растягивает свои кнопки на ширину листа и даёт им поля по бокам. Круглой стрелке
   не нужно ни то ни другое: поля в 44 px сжимали иконку до 11 px. */
.dialog-foot .exec-steps .pill { flex: none; padding: 0; }

/* Низкий кадр: на 844×390 слою достаётся 351 px. Шапка и подвал сжимаются по вертикали,
   чтобы телу осталось больше строки текста. Цели остаются в 44 px.
   Границы в условии — литералы, @media значение var() не читает. */
@media (max-height: 500px) {
  dialog.dialog--exec .head { padding-block: 8px; }
  dialog.dialog--exec .dialog-foot { padding-block: var(--space-1) calc(var(--space-1) + var(--safe-bottom)); }
}

/* Ниже 768 карточка юнита перестаёт быть окном по центру: план сверху, характеристики
   ниже, кнопки прижаты к нижней кромке кадра. Тело — единственная прокручиваемая строка
   сетки, поэтому шапка со статусом и подвал с кнопками с экрана не уходят.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 767px) {
  dialog.dialog--unit {
    position: fixed; inset: auto 0 0 0; margin: 0;
    width: 100%; max-width: none;
    max-height: 92vh; max-height: 92dvh;
    border-radius: var(--ov-radius) var(--ov-radius) 0 0;
    display: grid; grid-template-rows: auto minmax(0, 1fr) auto;
  }
  dialog.dialog--unit .head { padding: var(--space-3) var(--space-4); gap: var(--space-3); }
  dialog.dialog--unit .head h3 { font-size: var(--ov-title-compact); }
  dialog.dialog--unit .body {
    min-height: 0; max-height: none; gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    overscroll-behavior: contain;
  }
  dialog.dialog--unit .unit-view { gap: var(--space-3); }
  /* Плану оставлено меньше половины листа: под ним ещё характеристики, и лист обязан
     прокручиваться до конца, а не упираться в картинку во весь экран. */
  dialog.dialog--unit .unit-view img.slide { max-height: 38vh; }
  dialog.dialog--unit .side { gap: var(--space-3); }
}

/* Ручка листа. Оформление, а не орган управления: лист закрывают крестик, Escape и свайп
   вниз, а вторая кнопка с тем же смыслом только засорила бы обход с клавиатуры. Выше 768
   листа нет — нет и ручки. */
.sheet-grip { display: none; }

/* Попап картинки Executive Summary. Снимок вписывается в кадр по большей стороне и не
   режется: ширину ограничивает сам попап, высоту — видимая часть окна за вычетом шапки и
   полей тела. Прокручивать в нём нечего, поэтому тело прокрутку и не получает, а жест
   вниз целиком уходит на закрытие. */
dialog.dialog--image { display: flex; flex-direction: column; }
/* Заголовок держится в одну строку. Высота шапки входит в то, сколько остаётся снимку, а
   перенос заголовка зависел бы от ширины снимка — счёт пошёл бы по кругу. Полный текст
   остаётся в разметке, скринридер читает его целиком. */
dialog.dialog--image .head h3 {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
dialog.dialog--image .body {
  min-height: 0; max-height: none; overflow: hidden; touch-action: none;
  justify-items: center; align-content: center;
}
dialog.dialog--image img.slide {
  width: auto; height: auto; max-width: 100%;
  /* Процент от строки неопределённой высоты CSS не разрешает: остаток под снимок считает
     скрипт по настоящей высоте шапки и полей тела. */
  max-height: var(--img-max, 60vh);
  object-fit: contain;
}

/* Попап галереи. Свой класс, а не расширение попапа картинки: у того в теле только
   снимок, а здесь под кадром стоят счётчик с полосой автолистания и рельс миниатюр.
   Остаток высоты под кадр считает скрипт (_fitGallery) — процент от строки
   неопределённой высоты CSS не разрешает. */
dialog.dialog--gallery { display: flex; flex-direction: column;
  /* Переход между кадрами (_galSwap): кроссфейд со сдвигом по направлению листания. */
  --gal-swap: 320ms; --gal-shift: 24px; --gal-ease: var(--ease); }
/* Заголовок в одну строку: его высота входит в счёт остатка, а перенос зависел бы от
   ширины кадра — счёт пошёл бы по кругу. */
dialog.dialog--gallery .head h3 {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
dialog.dialog--gallery .body { min-height: 0; max-height: none; overflow: hidden; }
/* Жест по кадру — листание, а не прокрутка: браузеру касание не отдаётся. */
dialog.dialog--gallery .frame { touch-action: none; display: grid; justify-items: center; }
dialog.dialog--gallery img.slide {
  width: auto; height: auto; max-width: 100%;
  max-height: var(--gal-img-max, 50vh); object-fit: contain;
}
/* Уходящий кадр: копия поверх рамки ровно на месте оригинала, размер пишет скрипт. Под
   стрелками — они позже в разметке. */
dialog.dialog--gallery img.gal-ghost {
  position: absolute; margin: 0; max-width: none; max-height: none; pointer-events: none;
}
.gal-meta { display: grid; gap: 6px; width: 100%; justify-items: center; }
/* Подпись кадра галереи модуля (U-9). Галерея проекта подписей не несёт, строка пуста и скрыта,
   и разметка попапа остаётся прежней: скрытый элемент места в сетке не занимает. */
.gal-caption { margin: 0; max-width: 60ch; text-align: center; text-wrap: balance;
               color: var(--ink-2); font-size: 14px; line-height: 1.4; }
/* Полоса остатка до следующего кадра. Ширину пишет скрипт: под сокращением движения
   width не в белом списке переходов, а @keyframes сжимается до 200 мс. */
.gal-timer { width: min(240px, 100%); height: 2px; border-radius: 2px;
             background: color-mix(in srgb, var(--tint) 12%, transparent); }
.gal-timer i { display: block; height: 100%; width: 0; border-radius: 2px;
               background: var(--im-accent); }
.gal-rail {
  display: flex; gap: 8px; width: 100%; overflow-x: auto; padding-bottom: 4px;
  touch-action: pan-x; overscroll-behavior-x: contain; scrollbar-width: thin;
}
.gal-rail button {
  flex: none; padding: 0; border: 1px solid var(--edge); border-radius: var(--r-sm);
  background: none; cursor: pointer; overflow: hidden; line-height: 0;
}
.gal-rail button[aria-current="true"] { border-color: var(--im-accent); box-shadow: var(--glow-soft); }
.gal-rail img { display: block; width: 88px; height: 60px; object-fit: cover; }
/* Кадру нужен остаток высоты: на 844x390 --dlg-h всего 351 px. */
@media (max-height: 480px) { dialog.dialog--gallery .gal-rail { display: none; } }
/* Свой блок, а не общий выше: тот принадлежит всей странице и не правится. */
@media (prefers-reduced-motion: reduce) {
  dialog.dialog--gallery .gal-timer { display: none; }
  /* Смена кадра остаётся проявлением: без сдвига, короче и мягкой кривой. */
  dialog.dialog--gallery { --gal-swap: 200ms; --gal-shift: 0px; --gal-ease: var(--ease-soft); }
}

/* Ниже 768 панель точки локации и попап формы перестают быть окном и колонкой: лист у
   нижней кромки во всю ширину, ручка сверху, шапка и подвал вне прокрутки. Прокручивается
   только тело — крестик и кнопка отправки с экрана не уходят ни при какой клавиатуре.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 767px) {
  dialog.dialog--poi, dialog.dialog--form {
    /* Клавиатура забирает низ кадра, а layout viewport об этом не знает: без подъёма
       ровно на её высоту лист с полями уезжает ей под низ. Клавиатуры нет — ноль. */
    position: fixed; inset: auto 0 var(--vv-keyboard, 0px) 0; margin: 0;
    width: 100%; max-width: none; height: auto;
    border-radius: var(--ov-radius) var(--ov-radius) 0 0;
    display: grid; grid-template-rows: auto auto minmax(0, 1fr) auto;
  }
  /* Панели точки отведена треть кадра: остальное — панорама с метками, по условию они
     должны оставаться видны и переключаться прямо из-под панели. */
  dialog.dialog--poi { max-height: min(42vh, var(--dlg-h)); }
  dialog.dialog--poi > .sheet-grip, dialog.dialog--form > .sheet-grip {
    display: grid; justify-items: center; touch-action: none;
    padding: var(--space-2) 0 var(--space-1);
  }
  dialog.dialog--poi > .sheet-grip::before, dialog.dialog--form > .sheet-grip::before {
    content: ""; width: var(--tap-min); height: 4px; border-radius: var(--r-pill);
    background: var(--edge-strong);
  }
  dialog.dialog--poi .head, dialog.dialog--form .head {
    padding: var(--space-2) var(--space-4) var(--space-3); gap: var(--space-3);
  }
  dialog.dialog--poi .head h3, dialog.dialog--form .head h3 { font-size: var(--ov-title-compact); }
  dialog.dialog--poi .body, dialog.dialog--form .body {
    min-height: 0; max-height: none; gap: var(--space-3);
    padding: var(--space-3) var(--space-4);
    overscroll-behavior: contain;
  }
  /* Подвала у панели точки нет, и её тело само отступает от нижнего выреза. */
  dialog.dialog--poi .body { padding-bottom: calc(var(--space-3) + var(--safe-bottom)); }
  dialog.dialog--poi img.slide { max-height: 20vh; object-fit: cover; }
  /* Поля во всю ширину листа: 480 px минимальной ширины в телефон не входят. */
  dialog.dialog--form form.enquire { min-width: 0; }
  /* Лист приходит снизу. Селектор и имя кадров те же, что у десктопного набора: решает
     порядок в файле, и этот блок стоит ниже. */
  dialog.dialog--poi { transform: translateY(28px); }
  dialog.dialog--poi[open] { transform: none; }
  @starting-style { dialog.dialog--poi[open] { opacity: 0; transform: translateY(28px); } }
  dialog.dialog--poi[data-closing="1"] { animation: poi-out var(--mid) var(--ease-soft) forwards; }
  @keyframes poi-out { to { opacity: 0; transform: translateY(28px); } }
}

/* Ниже 768 галерея тоже перестаёт быть окном: лист у нижней кромки во всю ширину с
   ручкой сверху. Правила скопированы с листа точки локации и формы, сам их блок не
   правится. Граница в условии — литерал: @media значение var() не читает, а пара к
   нему живёт в JS константой LAYER_SHEET. */
@media (max-width: 767px) {
  dialog.dialog--gallery {
    position: fixed; inset: auto 0 var(--vv-keyboard, 0px) 0; margin: 0;
    width: 100%; max-width: none; height: auto; max-height: var(--dlg-h);
    border-radius: var(--ov-radius) var(--ov-radius) 0 0;
    display: grid; grid-template-rows: auto auto minmax(0, 1fr);
  }
  dialog.dialog--gallery > .sheet-grip {
    display: grid; justify-items: center; touch-action: none;
    padding: var(--space-2) 0 var(--space-1);
  }
  dialog.dialog--gallery > .sheet-grip::before {
    content: ""; width: var(--tap-min); height: 4px; border-radius: var(--r-pill);
    background: var(--edge-strong);
  }
  dialog.dialog--gallery .head {
    padding: var(--space-2) var(--space-4) var(--space-3); gap: var(--space-3);
  }
  dialog.dialog--gallery .head h3 { font-size: var(--ov-title-compact); }
  dialog.dialog--gallery .body {
    min-height: 0; max-height: none; gap: var(--space-3);
    padding: var(--space-3) var(--space-4) calc(var(--space-3) + var(--safe-bottom));
    overscroll-behavior: contain;
  }
}

.lightbox { display: grid; gap: 12px; justify-items: center; }
.lightbox .frame { position: relative; width: 100%; }
.lightbox .arrow { position: absolute; top: 50%; transform: translateY(-50%); }
.lightbox .arrow.prev { left: 12px; }
.lightbox .arrow.next { right: 12px; }
.lightbox .count { color: var(--ink-3); font-size: 12px; font-variant-numeric: tabular-nums; }

/* ——— контакты ——— */
/* Колонка по центру кадра: агенты — главный призыв слайда. */
.centre-col {
  position: absolute; inset: 0; display: grid; align-content: center; justify-items: center;
  gap: 18px; padding: 24px 32px 76px; text-align: center;
}
.centre-col h2 { font-size: clamp(30px, 3.4vw, 46px); }
.centre-col .rule {
  width: min(320px, 60vw); height: 1px; border: 0;
  background: linear-gradient(to right, transparent, var(--edge-strong), transparent);
}
.centre-col .eyebrow::after { display: none; }
/* Край призыва контактов задаёт пак (enquirePosition). Призыв остаётся строкой колонки,
   поэтому ряд агентов от сдвига не двигается и наезда на карточки нет. «Top» поднимает
   призыв над колонкой, «Bottom» и промах значения оставляют его внизу по центру.
   Ландшафт телефона исключён: 390 px высоты не хватает уже сегодняшней колонке, ряд агентов
   там подрезан подвалом, и «Top» лишним рядом сверху углубляет подрез. Условие повторяет
   медиазапрос шапки ниже — @media значения var() не читает, границы приходится дублировать. */
@media not all and (min-width: 600px) and (max-width: 1023px) and (max-height: 500px) {
  #slide[data-enquire-at="left"] .centre-col > .pill { justify-self: start; }
  #slide[data-enquire-at="right"] .centre-col > .pill { justify-self: end; }
  #slide[data-enquire-at="top"] .centre-col > .pill { order: -1; }
}
/* От 1024 колонка контактов шириной во весь кадр, и «Left» прижимал призыв к её кромке —
   в 32 px от края кадра, в стороне от карточек по центру: на 1920 между ними оставалось
   317 px пустоты. Колонка сжимается до своего самого широкого ряда и стоит по центру:
   край призыва совпадает с краем ряда агентов, «Right» — зеркально, отступ под карточками —
   общий шаг колонки. Ряд шире кадра за колонку не выходит — дорожка упирается в её поля.
   Граница в условии — литерал, @media значение var() не читает. */
@media (min-width: 1024px) {
  #slide[data-enquire-at="left"] .centre-col,
  #slide[data-enquire-at="right"] .centre-col {
    grid-template-columns: minmax(0, max-content); justify-content: center;
  }
}
.agent-row { display: flex; gap: 16px; flex-wrap: wrap; justify-content: center; }
.agent-row .agent { width: min(410px, 86vw); text-align: left; }
.agent {
  display: grid; grid-template-columns: 82px 1fr; gap: 16px; align-items: center;
  padding: 16px; border-radius: var(--r-lg); border: 1px solid var(--edge);
   background: var(--glass-1); box-shadow: var(--sheen), var(--lift-1);
   backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
   transition: box-shadow var(--mid) var(--ease), border-color var(--fast) var(--ease-soft),
              background var(--fast) var(--ease-soft);
}
.agent:hover { background: var(--glass-2); border-color: var(--edge-strong);
  box-shadow: var(--sheen), var(--lift-2), var(--glow-soft); }
/* Карточка без портрета не держит под него колонку. Иначе строки контактов садятся в те
   82 px, что отведены картинке: замерено — почта в коробке 30 px, рассыпанная по букве
   в шесть строк. Пак портрет не обязан давать, и молчание пака не должно ломать карточку. */
.agent:not(:has(.portrait)) { grid-template-columns: minmax(0, 1fr); }
.agent .portrait { width: 82px; height: 82px; border-radius: 50%; object-fit: cover;
  border: 1px solid var(--edge); }
.agent .lines { display: grid; gap: 3px; min-width: 0; }
.agent .name { font-size: 17px; }
.agent .role { color: var(--ink-3); font-size: 13px; margin-bottom: 5px; }
.agent .line { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.agent .line a { flex: 1 1 auto; min-width: 0; }
.agent a { display: inline-flex; align-items: center; min-height: var(--tap-min); font-size: 13px;
  color: var(--ink-2); text-decoration: none; font-size: 13.5px;
  overflow-wrap: anywhere; white-space: normal;
  transition: color var(--fast) var(--ease-soft); }
.agent a:hover { color: var(--im-accent); }
/* Почта, телефон и копирование — цели для пальца и на десктопе: 30 px держались только
   потому, что 44 px были прописаны в медиазапросах телефона и планшета. */
.agent .copy { flex: none; width: var(--tap-min); min-height: var(--tap-min); }
.agent .logo { max-height: 22px; margin-top: 9px; opacity: .78; }

.disclaimer {
  position: absolute; inset: auto auto 16px 56px; max-width: min(440px, 44vw);
  font-size: 11.5px; line-height: 1.55; color: var(--ink-3);
}
/* Переключатель — цель для пальца на любой ширине: планшет в альбомной ориентации шире 1024,
   а 30 px от 1024 R1 нашёл ниже обещанных 44. Дисклеймер прижат к низу, лишняя высота
   уходит вверх, в пустой кадр над ним. */
.disclaimer summary {
  cursor: pointer; list-style: none; min-height: var(--tap-min); display: inline-flex;
  align-items: center; gap: 8px; letter-spacing: .14em; text-transform: uppercase;
  font-size: 10.5px; color: var(--eyebrow);
  transition: color var(--fast) var(--ease-soft);
}
.disclaimer summary::-webkit-details-marker { display: none; }
.disclaimer summary::after { content: "+"; font-size: 13px; }
.disclaimer[open] summary::after { content: "–"; }
.disclaimer summary:hover { color: var(--ink-2); }
.disclaimer p { margin-top: 10px; color: color-mix(in srgb, var(--ink) 62%, transparent); }

/* ——— подсказки по слайду ——— */
/* Щуп вырезов. env() читает только оформление: getComputedStyle отдаёт значение custom
   property как есть, без подстановки, и мерить вырезы из скрипта больше нечем. Узел
   пустой, нулевого размера и скрытый — на раскладку он не влияет. */
.safe-probe {
  position: absolute; top: 0; left: 0; width: 0; height: 0;
  visibility: hidden; pointer-events: none;
  padding: var(--safe-top) var(--safe-right) var(--safe-bottom) var(--safe-left);
}
/* Вуаль ловит клик мимо подсказки. Затемняет не она, а тень кольца: два слоя дают перебор. */
.tour-veil { position: absolute; inset: 0; z-index: var(--z-tour-veil); opacity: 1; visibility: visible;
  transition: opacity var(--ov-fade), visibility 0ms linear 0ms; }
.tour-veil[hidden] { display: block; opacity: 0; visibility: hidden; pointer-events: none;
  transition-delay: 0ms, var(--mid); }
/* Кольцо вокруг цели вырезает её из затемнения одной большой тенью. */
.tour-ring {
  position: absolute; z-index: var(--z-tour-ring); pointer-events: none;
  border-radius: var(--ov-radius-sm);
  opacity: 1; visibility: visible;
  /* Третья тень и есть вуаль тура: тон у неё общий со всеми вуалями слоёв. */
  box-shadow: 0 0 0 1px color-mix(in oklab, var(--im-accent) 70%, transparent),
              0 0 30px -4px color-mix(in oklab, var(--im-accent) 60%, transparent),
              0 0 0 9999px var(--ov-veil);
  transition: left var(--mid) var(--ease), top var(--mid) var(--ease),
              width var(--mid) var(--ease), height var(--mid) var(--ease),
              opacity var(--mid) var(--ease-soft), visibility 0ms linear 0ms;
}
.tour-ring[hidden] {
  display: block; opacity: 0; visibility: hidden;
  transition-delay: 0ms, 0ms, 0ms, 0ms, 0ms, var(--mid);
}
.tour-card {
  position: absolute; z-index: var(--z-tour-card); width: min(340px, calc(100vw - 24px));
  display: grid; gap: 10px; padding: var(--ov-pad-card);
  box-shadow: var(--ov-lift);
  transition: left var(--mid) var(--ease), top var(--mid) var(--ease),
              opacity var(--ov-fade), transform var(--mid) var(--ease),
              visibility 0ms linear 0ms;
}
/* Островок уходит тем же движением, что вуаль и кольцо: прозрачность за --mid, видимость
   снимается после. Под общее [hidden] с display:none он больше не попадает. */
.tour-card[hidden] {
  display: grid; opacity: 0; visibility: hidden; pointer-events: none;
  transform: translateY(6px);
  transition-delay: 0ms, 0ms, 0ms, 0ms, var(--mid);
}
.tour-card .head { justify-content: space-between; }
.tour-card .count {
  font-size: 11px; letter-spacing: .18em; color: var(--eyebrow);
  font-variant-numeric: tabular-nums;
}
.tour-card .head .pill { min-height: 30px; padding: 4px 12px; font-size: 12px; }
.tour-card h3 { font-size: var(--ov-title-card); }
.tour-card p { font-size: var(--ov-text); color: var(--ink-2); }
.tour-card .timer { height: 2px; border-radius: 2px; background: color-mix(in srgb, var(--tint) 12%, transparent); }
.tour-card .timer i {
  display: block; height: 100%; width: 0; border-radius: 2px; background: var(--im-accent);
}
.tour-card[data-paused="1"] .timer i { background: var(--ink-3); }
.tour-card .nav { display: flex; gap: 8px; justify-content: flex-end; }
.tour-card .nav .pill { min-height: 36px; padding: 6px 16px; font-size: 13px; }
/* Стрелка островка смотрит на цель. */
.tour-arrow {
  position: absolute; width: 14px; height: 14px; transform: translateX(-50%) rotate(45deg);
  background: var(--ov-bg); border: 1px solid var(--ov-edge); border-radius: 0 0 0 3px;
}
.tour-card[data-side="bottom"] .tour-arrow { top: -7.5px; border-right: 0; border-bottom: 0; }
.tour-card[data-side="top"] .tour-arrow { bottom: -7.5px; border-left: 0; border-top: 0;
  border-radius: 0 3px 0 0; }
.tour-card[data-side="dock"] .tour-arrow { display: none; }

/* Ниже 768 островок — лист во всю ширину у нижней кромки: стрелкой в кадре шириной с
   ладонь указывать некуда, а по центру островок закрыл бы ровно то, о чём рассказывает.
   Ширину считаем за вычетом вырезов — в альбомной ориентации они съедают бок.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 767px) {
  .tour-card { width: calc(100vw - 24px - var(--safe-left) - var(--safe-right)); }
  .tour-card .nav .pill { flex: 1; }
}

/* ——— форма ——— */
form.enquire { display: grid; gap: 12px; min-width: min(480px, 86vw); }
form.enquire label { display: grid; gap: 6px; font-size: 12.5px; color: var(--ink-3); }
form.enquire input, form.enquire textarea {
  font: inherit; color: var(--ink); min-height: var(--tap); padding: 12px 14px;
  border: 1px solid var(--edge); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--tint) 5%, transparent);
  transition: border-color var(--fast) var(--ease-soft), box-shadow var(--mid) var(--ease),
              background var(--fast) var(--ease-soft);
}
form.enquire input:hover, form.enquire textarea:hover { background: color-mix(in srgb, var(--tint) 8%, transparent); }
form.enquire input:focus, form.enquire textarea:focus {
  outline: none; border-color: var(--im-accent); box-shadow: var(--glow-soft);
  background: color-mix(in srgb, var(--tint) 9%, transparent);
}
form.enquire input:user-invalid, form.enquire textarea:user-invalid { border-color: var(--sold); }
form.enquire .note { color: var(--under-offer); font-size: 12px; }
/* Строка согласия: флажок и текст в ряд. Цель нажатия — вся строка, не квадрат 20 на 20. */
form.enquire label.consent {
  grid-template-columns: var(--tap) 1fr; align-items: center; gap: 0;
  min-height: var(--tap); color: var(--ink-2); cursor: pointer;
}
form.enquire label.consent input[type="checkbox"] {
  width: 22px; height: 22px; min-height: 0; padding: 0; justify-self: center;
  border: 0; background: none; accent-color: var(--im-accent);
}
/* Группа выбора: вопрос подписью над вариантами, вариант — строка с флажком или кружком. Цель
   нажатия — вся строка, как у согласия. */
form.enquire fieldset.choices { display: grid; gap: 0; margin: 0; padding: 0; border: 0; min-width: 0; }
form.enquire fieldset.choices legend { padding: 0 0 6px; font-size: 12.5px; color: var(--ink-3); }
form.enquire fieldset.choices label {
  grid-template-columns: var(--tap) 1fr; align-items: center; gap: 0;
  min-height: var(--tap); color: var(--ink-2); cursor: pointer;
}
form.enquire fieldset.choices input {
  width: 22px; height: 22px; min-height: 0; padding: 0; justify-self: center;
  border: 0; background: none; accent-color: var(--im-accent);
}
form.enquire fieldset.choices input:focus-visible { outline: 2px solid var(--im-accent); outline-offset: 2px; }

/* ——— оси подачи K2: гейт доступа, язык, рамка стадии ———
   Всё ниже рисуется только за словом пака (page.gating, page.language, page.stage_framing). */
#gate {
  position: absolute; inset: 0; z-index: calc(var(--z-toast) - 1);
  display: grid; place-items: center; padding: 16px;
  /* Закрытое не читается сквозь гейт: почти глухая вуаль и размытие. */
  background: color-mix(in srgb, var(--shade) 90%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
  overflow: auto; overscroll-behavior: contain;
}
#gate .gate-card {
  display: grid; gap: 14px; width: min(520px, 100%); max-height: 100%; overflow: auto;
  padding: clamp(18px, 4vw, 32px);
}
#gate h2 { margin: 0; font-family: var(--im-font-heading, var(--im-font-accent)), serif; font-size: clamp(20px, 3vw, 26px); }
#gate form.enquire { min-width: 0; }
#gate form.enquire button[type="submit"] { justify-self: start; }
/* Ловушка для ботов: поле есть в форме, человек его не видит и не доходит до него Tab. */
form.enquire label.gate-trap {
  position: absolute; left: -10000px; width: 1px; height: 1px; overflow: hidden;
}
#lang { display: flex; gap: 6px; margin-left: auto; flex: none; }
#lang + #slide-count { margin-left: 0; }
#lang button { min-height: 32px; padding: 0 12px; font-size: 12px; }
#lang button[aria-pressed="true"] { background: var(--im-accent); color: var(--on-accent, #fff); border-color: transparent; }
#stage-strip {
  display: flex; gap: 10px; margin: 0; padding: 0; list-style: none; flex: none;
  font-size: 11px; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3);
}
#stage-strip[hidden] { display: none; }
#stage-strip li[aria-current="step"] { color: var(--im-accent); }

/* ——— тост ——— */
#toast {
  position: absolute; left: 50%; bottom: calc(var(--foot-h) + 18px); z-index: var(--z-toast);
  transform: translate(-50%, 12px);
  /* Подпись — таблетка: скругление своё, а блика нет — слой слишком мелкий для него. */
  padding: 11px 18px; border-radius: var(--r-pill); box-shadow: var(--lift-2);
  font-size: 13px; opacity: 0; pointer-events: none;
  /* Всплывающая подпись появляется прозрачностью: со сдвигом размытие рвётся. */
  transition: opacity var(--mid) var(--ease);
}
#toast[data-show="1"] { opacity: 1; transform: translate(-50%, 0); }

/* ——— планшет и телефон ——— */
@media (max-width: 1023px) {
  body { font-size: 14px; }
  #slide { overflow: auto; overscroll-behavior: contain; }
  /* Строка не шире 72 знаков: на планшете колонка иначе растягивается во всю ширину
     кадра и заголовок с линией уходят за предел читаемости. Кадр телефона уже этого
     предела, и там правило ничего не меняет. */
  .left-col { position: static; width: auto; max-width: 72ch; transform: none;
    padding: 20px calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
    justify-items: stretch; }
  .tiles { position: static; transform: none; display: grid;
    grid-template-columns: minmax(0, 1fr);
    padding: 16px calc(16px + var(--safe-right)) 16px calc(16px + var(--safe-left));
    gap: 10px; }
  .tile { width: auto; grid-template-columns: 46px 1fr auto; justify-items: start;
    text-align: left; align-items: center; background: var(--glass-1);
    border-color: var(--edge); padding: 14px 16px; gap: 14px; }
  .tile .pill { padding: 8px 14px; }
  .tile h3 { font-size: 19px; }
  .detail-tiles { position: static; transform: none; width: auto; padding: 16px; }
  .centre-col { position: static;
    padding: 20px calc(16px + var(--safe-right)) calc(24px + var(--safe-bottom))
             calc(16px + var(--safe-left)); }
  /* Ряд занимает всю колонку, карточки делят её поровну и переносятся: на телефоне одна
     в строке, на планшете две. Ряд центрирован сам по себе, поэтому неполная строка
     встаёт по центру при любом числе агентов. */
  .centre-col .agent-row { width: 100%; }
  .agent-row .agent { flex: 1 1 300px; width: auto; max-width: 480px; }
  /* Почта, телефон и кнопка копирования — цели для пальца, а не строчки текста. */
  .agent a { min-height: var(--tap-min); }
  .agent .copy { width: var(--tap-min); min-height: var(--tap-min); }
  /* Правый отступ 16 px, а не 8: подпись закреплённой колонки ACTIONS упиралась в кромку
     кадра на 9 px, и текст без поля читается как обрезанный. Порог поля — 16 px. */
  .table-wrap { inset: 66px 16px 76px; }
  th, td { padding: 10px 12px; }
  thead th { padding: 0; }
  thead th button,
  thead th.table-col--actions > span { padding: 10px 12px; }
  /* Три закреплённых блока справа не помещаются на 390 px: цену и статус отпускаем в прокрутку. */
  tbody td.table-col--deal, thead tr:not(.table-groups) th.table-col--deal { position: static; }
  thead .table-groups th:last-child { position: static; }
  tbody td.table-col--deal:nth-last-child(3) { box-shadow: none; }
  .filters { position: static; padding: 12px 16px 0; }
  .strip, .strip--raised { position: static; padding: 12px 16px 20px; }
  .strip--single { padding: 12px 16px 20px; }
  /* Переключатель растёт по содержимому, а не во всю ширину кадра. На всю ширину он лёг
     поперёк плана: на 768 половины Current и Completion по 363 px закрывали собой метки
     юнитов, и нажатие по видимой метке доставалось кнопке стадии. Пол в 360 px сохраняет
     прежний вид на телефоне — там ширина кадра его и так не даёт превысить. */
  .switch-track { min-width: min(360px, 100%); width: fit-content; max-width: 100%; }
  .disclaimer { position: static; max-width: none;
    padding: 12px calc(16px + var(--safe-right)) calc(20px + var(--safe-bottom))
             calc(16px + var(--safe-left)); }
  /* Сам текст остаётся в 72 знаках: на планшете 653 знака дисклеймера иначе идут строкой
     во всю ширину кадра. Кадр телефона уже этого предела. */
  .disclaimer p { max-width: 72ch; }
  #slide-name { max-width: 14ch; }
  #grain { display: none; }
  .poi-list { position: static; width: auto; max-height: none; margin: 16px; }
  /* На телефоне призыв уезжает вниз справа: сверху его перекрывает бургер. */
  #enquire-top {
    top: auto; left: auto; right: calc(12px + var(--safe-right));
    bottom: calc(var(--foot-h) + 12px + var(--safe-bottom)); transform: none;
    box-shadow: var(--sheen), var(--lift-2), var(--glow-soft);
  }
  /* И забирает под себя полосу слайда. Без неё призыв лежал поверх содержимого: на сводке
     он накрывал кнопку нижней плитки, в списке юнитов — кнопку «Enquire» той строки, что
     оказалась под ним. Признак ставит рендер слайда, а не сам призыв: панель фильтров и
     попапы прячут кнопку на время, и полоса от этого прыгать не должна. */
  #slide[data-cta="1"] { --band-cta: var(--band-step); }
  #head { gap: 10px; }
  /* Рельса здесь нет: шаг точки уходит под палец. Скрипту это сказано словом --rail, а не
     через display: порог живёт в условии этого @media и только в нём. */
  #dots { display: none; --rail: off; }
}

/* Контакты в узком портрете. Подпись следующего слайда подняла подвал на 22 px, и
   переключатель дисклеймера на 390×844 ушёл за нижнюю кромку слайда: 745–789 при кромке 758,
   в центре переключателя оказывался ряд навигации. Колонка отдаёт 50 px своих полей — сверху
   12 вместо 20, между блоками 14 вместо 18, под кнопкой формы 8 вместо 24, над переключателем
   0 вместо 12, под ним 12 вместо 20, — и переключатель встаёт на 693–737 без прокрутки.
   Условие то же, что у высокого подвала.
   Границы в условии — литералы, @media значение var() не читает. */
@media (max-width: 599px) and (min-height: 501px) {
  .centre-col { gap: 14px; padding-top: 12px; padding-bottom: 8px; }
  .disclaimer { padding-top: 0; padding-bottom: calc(12px + var(--safe-bottom)); }
}

/* Узкий экран: шторка раскрывается на весь кадр. Кнопка открытия остаётся под ней и
   вуали снаружи не видно, поэтому закрытие живёт внутри — крестик, Escape, свайп влево. */
@media (max-width: 480px) {
  #menu { width: 100%; border-right: 0; }
}

/* Планшет: одиннадцать колонок в кадр не входят даже здесь. На виду остаются группы
   UNIT и DEAL, площади и оснащение открываются строкой под юнитом. Таблица при этом
   остаётся таблицей и в горизонтальную прокрутку не уходит: ширина по кадру.
   Границы в условии — литералы, @media значение var() не читает. */
@media (min-width: 768px) and (max-width: 1023px) {
  table { min-width: 0; width: 100%; }
  thead .table-groups th.group-areas, thead .table-groups th.group-equipment,
  thead th.table-col--areas, thead th.table-col--equipment,
  tbody td.table-col--areas, tbody td.table-col--equipment { display: none; }
  .row-expand { display: inline-flex; }
  tr.row-detail:not([hidden]) { display: table-row; }
  /* Подпись кнопки плана уступает место колонкам: без неё шесть колонок входят в 768,
     а имя для скринридера у кнопки своё и от подписи не зависит. */
  .table-plan span { display: none; }
  .table-plan { width: var(--tap-min); padding: 0; border-radius: 50%; }
}

/* ——— контентные слайды: старт, сводка, локация, контакты ——— */

/* Сводка на десктопе уже 1440. Колонка текста в 430 px и блок вкладок рядом в кадр не
   входят: с 1024 до 1439 колонка сжимается до читаемого предела в 280 px, и от этой
   ширины считается остаток, который занимает блок.
   Границы в условии — литералы, @media значение var() не читает. */
@media (min-width: 1024px) and (max-width: 1439px) {
  #slide[data-layout="summary"] > .left-col { width: clamp(280px, 30vw, 430px); }
}

/* Блок вкладок занимает правую часть кадра. Ширина зеркалит правило, по которому раньше
   вставал ряд плиток: остаток кадра за левой колонкой минус её отступ слева и два поля.
   Потолок 1120 — иначе на 1920 блок растянулся бы на 1368 px. Центрирующего transform
   нет ни по одной оси: кадр входа слайда анимирует ту же запись, и блок въезжал бы по
   диагонали. Вертикаль держат две привязки inset и margin-block: auto.
   Граница в условии — литерал, @media значение var() не читает. */
@media (min-width: 1024px) {
  #slide[data-layout="summary"] > .tiles {
    /* Плитки стоят по центру того, что осталось от кадра после текстовой колонки: прежде
       они жались к правой кромке и на широком экране висели отдельным островом. */
    left: calc(56px + clamp(280px, 30vw, 430px)); right: var(--space-5);
    justify-content: center; transform: translateY(-50%);
    display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
    width: auto; max-width: 654px; margin-inline: auto;
  }
  /* Панель выше кадра — прокрутка её собственная. Ниже 1024 прокручивается сам #slide,
     и вложенная прокрутка внутри прокручиваемого слайда там не заводится. */
  #slide[data-layout="summary"] .exec-panel { overflow: auto; overscroll-behavior: contain; }
}

/* Локация от 1024. Заголовок и лист были двумя островами: колонка висела по центру кадра,
   лист прижимался к нижней кромке, между ними на 1440 оставалось 145 px пустоты. Оба узла
   выходят из абсолютного позиционирования и становятся одной колонкой — общая ширина,
   общий левый край, отступ в один шаг между ними.
   Граница в условии — литерал, @media значение var() не читает. */
@media (min-width: 1024px) {
  #slide[data-layout="poi"] { justify-content: center; align-items: flex-start; padding: 24px 56px; }
  /* transform: none обязателен: без него колонка остаётся сдвинутой на translateY(-50%)
     из базового правила .left-col, и зазор выходит 92 px вместо 16. */
  #slide[data-layout="poi"] > .left-col {
    position: static; transform: none; top: auto; width: min(430px, 44vw);
  }
  /* max-height: none обязателен: потолок 38vh держал бы карточку на 342 px при доступных 556. */
  #slide[data-layout="poi"] > .poi-list {
    position: static; width: min(430px, 44vw); margin-top: var(--space-3);
    max-height: none; min-height: 0; flex: 0 1 auto;
  }
}

/* Локация ниже 1024. Двадцать один объект в общей прокрутке слайда накрывает панораму
   целиком: навести на объект камеру по тапу можно, а увидеть результат нельзя. На
   планшете тот же список вырастает до 1170 px, уходит за нижнюю кромку и ложится на
   панель управления панорамой. Слайд становится колонкой — заголовок с адресом сверху,
   список листом у нижней кромки со своей прокруткой, между ними просвет с живой
   панорамой. Просвет принадлежит #slide, а он указателя не ловит, поэтому панорама в
   нём тянется пальцем как обычно.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 1023px) {
  #slide[data-layout="poi"] > .left-col { flex: none; }
  /* Панель управления панорамой больше не висит в углу — там теперь лист. Она садится
     строкой над ним, и её же auto-отступ уводит лист к нижней кромке. */
  #slide[data-layout="poi"] > .pano-tools {
    order: 1; flex: none; position: static; margin-top: auto;
    grid-auto-flow: column; justify-content: end;
    padding: var(--space-2) calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
  }
  /* Ужиматься разрешено только листу: в низком кадре сжимать нечего, кроме него, а
     заголовок с адресом и панель управления обязаны остаться целыми. */
  #slide[data-layout="poi"] > .poi-list {
    order: 2; flex: 0 1 auto; min-height: 0; max-height: min(56vh, 420px);
    /* Зазор под плавающий призыв тут больше не нужен: слайд вырезает под него полосу
       сам (--band-cta), и правый край строк лист держит целиком. */
    margin: var(--space-2) calc(16px + var(--safe-right))
            calc(16px + var(--safe-bottom)) calc(16px + var(--safe-left));
    /* Прокрутка живёт на .poi-scroll. Оставь её здесь — карточка прокрутится целиком и
       ряд тегов уедет за верхнюю кромку: правка отменит сама себя. */
    overflow: hidden;
    display: flex; flex-direction: column;
  }
  /* Сама прокрутка. Правило пропало вместе с рядом тегов, и лист перестал листаться вовсе:
     на 390×844 из 21 объекта пальцем доставались 8, девятый резался краем карточки
     посреди текста. Список и карточка объекта ужимаются по листу и листаются внутри. */
  #slide[data-layout="poi"] > .poi-list > .poi-scroll,
  #slide[data-layout="poi"] > .poi-list > .poi-detail {
    flex: 0 1 auto; min-height: 0;
    overflow: auto; overscroll-behavior: contain; scrollbar-width: thin;
  }
  /* Нижняя строка уходит под кромку растворением на 24 px. Поле под списком той же высоты:
     в конце прокрутки последняя строка стоит над ним целиком. */
  #slide[data-layout="poi"] > .poi-list > .poi-scroll {
    padding-bottom: 24px;
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent);
            mask-image: linear-gradient(to bottom, #000 calc(100% - 24px), transparent);
  }
  /* Узкий кадр: чип показывает иконку и число. Имя категории уходит из вида, полное имя
     остаётся в aria-label — девять чипов укладываются в две строки и стоят в кадре. */
  .poi-list .poi-tags .tag-name { display: none; }
}

/* Почта и телефон — цели для пальца там, где есть палец. Ширина тут ни при чём: планшет
   в альбомной ориентации шире 1024, а тычут в него так же. */
@media (pointer: coarse) {
  .agent a { min-height: var(--tap-min); }
  .agent .copy { width: var(--tap-min); min-height: var(--tap-min); }
  .poi-list .poi-tags button { min-height: var(--tap-min); }
}

/* Низкий кадр — это ландшафт телефона: под панелями остаётся 250–260 px, и заголовок в
   исходном размере съедает их больше чем наполовину.
   Границы в условии — литералы, @media значение var() не читает. */
@media (max-width: 1023px) and (max-height: 500px) {
  /* В таком кадре высоты нет, зато есть ширина. Заголовок с адресом и список уходят в
     левую колонку, панорама остаётся видна справа целиком, а не полоской над листом:
     лист у нижней кромки оставлял бы ей 40 px. Панель управления возвращается в свой
     угол — правая половина кадра под неё свободна. */
  #slide[data-layout="poi"] > .left-col,
  #slide[data-layout="poi"] > .poi-list { width: min(52%, 430px); }
  #slide[data-layout="poi"] > .left-col { gap: var(--space-2); padding-top: var(--space-2); }
  #slide[data-layout="poi"] > .left-col h2 { font-size: clamp(22px, 4vh, 30px); }
  /* Панель возвращается в свой угол: на низком кадре призыв ушёл в шапку, и нижняя
     правая четверть свободна целиком. */
  #slide[data-layout="poi"] > .pano-tools {
    order: 3; position: absolute; margin: 0; padding: 0;
    inset: auto calc(16px + var(--safe-right)) calc(16px + var(--safe-bottom)) auto;
  }
  /* Лист забирает весь остаток колонки: потолок в 46 vh здесь только урезал бы его.
     Призыв остался в правой половине кадра, над панорамой, и зазор под него не нужен. */
  #slide[data-layout="poi"] > .poi-list {
    flex: 1 1 auto; max-height: none; margin-top: 0;
    margin-bottom: calc(16px + var(--safe-bottom));
  }
  /* Единственный кадр, где ряд не переносится: высоты нет физически, две строки чипов
     съели бы весь лист. Ряд идёт одной строкой и листается вбок, маска справа говорит,
     что там есть ещё; на последнем чипе она снимается по data-end. */
  .poi-tags {
    flex-wrap: nowrap; overflow-x: auto; overscroll-behavior-x: contain;
    touch-action: pan-x; padding: 4px 6px 6px; gap: 4px;
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 24px), transparent);
  }
  .poi-tags[data-end="1"] { -webkit-mask-image: none; mask-image: none; }
  .poi-list .poi-tags button { min-height: 30px; padding: 0 8px; }

  /* Старт на ландшафте телефона. Логотип, заголовок, адрес и подпись в такой кадр не
     влезают, и в общей прокрутке кнопка входа уезжает за нижнюю кромку. Колонка
     забирает левую долю кадра — панорама остаётся видна справа, а не уходит под текст
     целиком — и получает собственную прокрутку. Кнопка липнет к нижней кромке этой
     прокрутки: в кадре она обязана быть при любом положении текста. */
  #slide[data-layout="start"] { display: flex; flex-direction: column; overflow: hidden; }
  #slide[data-layout="start"] > .left-col {
    flex: 0 1 auto; min-height: 0; width: min(58%, 480px);
    gap: var(--space-2); padding-top: var(--space-2);
    overflow: auto; overscroll-behavior: contain; scrollbar-width: thin;
  }
  #slide[data-layout="start"] .start-logo { max-height: 38px; }
  #slide[data-layout="start"] > .left-col h2 { font-size: clamp(22px, 4vh, 30px); }
  /* Стекло и тень у кнопки свои — текст, уезжающий под неё, она перекрывает сама. */
  #slide[data-layout="start"] > .left-col > .pill { position: sticky; bottom: 0; z-index: 1; }
}

/* ——— содержимое вкладки Executive Summary ——— */
/* Вкладка собрана из блоков разбора картинки: колонка текста и колонка иллюстрации. Уже
   620 px по ширине блока колонки складываются в столбец — правило в конце файла.
   Иллюстрации в разборе не оказалось — колонка одна. */
.exec-popup {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: 22px; align-items: start;
}
.exec-popup .exec-copy { display: grid; gap: 12px; align-content: start; }
/* Кусок колонки текста: заголовок и то, что под ним. Собран разметкой, потому что
   многоколоночная вёрстка рвёт колонку между любыми соседями и заголовок уезжал в одну
   колонку, а его список — в другую. В остальных пресетах кусок ничего не меняет: та же
   сетка с тем же шагом. */
.exec-popup .exec-part { display: grid; gap: 12px; align-content: start; }
.exec-popup .exec-list { margin: 0; padding-left: 20px; display: grid; gap: 6px; }
.exec-popup h4 { font-size: 13px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-3); }
.exec-popup[data-cols="1"] { grid-template-columns: minmax(0, 1fr); }
.exec-popup .exec-media { display: grid; gap: var(--space-3); align-content: start; min-width: 0; }
/* Тезис в оригинале набран крупнее остального текста — здесь это ступень шкалы вверх. */
.exec-popup .exec-copy .lede { font-size: var(--font-lg); color: var(--ink); }
.exec-popup .exec-copy .lede strong { font-weight: 600; }

/* Цифра с подписью: число крупно, подпись рядом с ним, уточнение строкой ниже и мельче. */
.exec-metrics { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.exec-metrics li {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  column-gap: var(--space-3); align-items: baseline;
}
.exec-metrics .v {
  font-family: var(--im-font-heading, var(--im-font-accent)), ui-serif, Georgia, serif;
  font-size: clamp(30px, 3.2vw, 40px); line-height: 1; font-variant-numeric: tabular-nums;
}
.exec-metrics .l { font-size: var(--font-lg); }
.exec-metrics .d { grid-column: 2; font-size: var(--font-sm); color: var(--ink-3); }

/* Знак пункта приходит разбором — в оригинале это стрелка. Он оформление: маркер списка
   снимается, глиф встаёт отдельной колонкой и от скринридера скрыт. */
.exec-list[data-marker="1"] { list-style: none; padding-left: 0; }
.exec-list[data-marker="1"] li {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); gap: var(--space-2);
}
.exec-list .mk { color: var(--im-accent); }
/* Пары «подпись — значение» берут вид таблицы попапа — тот же, что у характеристик юнита. */
.exec-pairs { align-self: start; }


/* ——— пресеты раскладки попапа плитки ——— */
/* Десять раскладок на одних и тех же данных. Пресет приходит полем tiles[].variant, и вид
   меняется им одним: разбор, порядок блоков и разметка при этом не трогаются. Правила ниже
   описаны через группы, которые пришли, а не через обязательный набор, — плитка без цифр,
   без списка или без иллюстрации остаётся рабочей в любом пресете.

   Первый пресет, text-media, — это правила выше: текст колонкой, иллюстрация рядом. Своих
   правил ему не нужно, и ни одно из них здесь не переопределяется.

   Число колонок задаёт пресет, а не длина разбора: оно приходит в data-cols, и его читает
   правило .exec-popup[data-cols="1"] выше. Порядок групп задаёт разметка — порядок показа
   и порядок чтения с клавиатуры в пресетах не расходятся. */

/* 2. media-top — иллюстрация сверху во всю ширину, текст под ней.
   9. compact — тот же столбец, но плотнее и с невысокой иллюстрацией.
   10. plain — только текст: иллюстрацию отсеивает разметка, правил вид не требует.
   Все трое идут одной колонкой, её задаёт data-cols="1" выше. Порядок — в разметке. */

/* Пресеты, берущие всю ширину слоя, раскладываются всё равно одной колонкой: делят кадр
   внутри себя — на текстовые колонки, на плитки, на половины, — а не сеткой самого слоя. */
.exec-popup[data-variant="two-col"],
.exec-popup[data-variant="map-cover"],
.exec-popup[data-variant="metric-grid"],
.exec-popup[data-variant="split"] { grid-template-columns: minmax(0, 1fr); }

/* 3. two-col — две колонки текста, полоса цифр под ними.
   Текст ложится в две колонки многоколоночной вёрсткой, а не сеткой: блоки в сетке встали
   бы попарно в строку, и заголовок оторвался бы от своего списка. Отступы между блоками
   возвращаются полями — display у колонки перестал быть сеткой вместе с её gap. */
.exec-popup[data-variant="two-col"] > .exec-copy {
  display: block; column-count: 2; column-gap: var(--space-5);
}
/* Рвать запрещено куску целиком: заголовок и его список — один кусок, и колонка ломается
   только между кусками. */
.exec-popup[data-variant="two-col"] > .exec-copy > .exec-part { break-inside: avoid; }
.exec-popup[data-variant="two-col"] > .exec-copy > * + * { margin-top: var(--space-3); }
/* Кусок один — делить между колонками нечего, и вторая осталась бы пустой. Тогда в две
   колонки уходит его список, а заголовок стоит над обеими: заголовок от своего списка
   опять не отрывается. */
.exec-popup[data-variant="two-col"] > .exec-copy:has(> .exec-part:only-child) {
  column-count: 1;
}
.exec-popup[data-variant="two-col"] > .exec-copy > .exec-part:only-child .exec-list {
  display: block; column-count: 2; column-gap: var(--space-5);
}
.exec-popup[data-variant="two-col"] > .exec-copy > .exec-part:only-child .exec-list li {
  break-inside: avoid; margin-bottom: 6px;
}

/* 4. map-cover — карта во весь кадр, карточка с текстом поверх неё. Обе группы лежат в
   одной ячейке сетки; карточка рисуется поверх потому, что в разметке идёт следом. Она
   прижата к левому верхнему углу: панель управления картой стоит в правом нижнем, и они
   не встречаются ни на какой ширине. Органов управления в карточке нет — карта под ней
   остаётся живой везде, кроме площади самой карточки. */
/* Кадр пресета — тело слоя целиком: карта тянется на всю его высоту, а подпись, легенда
   и атрибуция лежат под картой внутри того же кадра. Без этого карта считала высоту от
   своих пропорций, вырастала выше слоя, и метка участка вместе с подписью уезжали за
   нижнюю кромку карточки. Кадр карты режется по большей стороне — так его и рисует SVG.
   Высоту тела задаёт строка сетки слоя (dialog.dialog--exec), поэтому кадр берёт 100 %:
   счёт от высоты шапки промахивался на её рамку и на подвал переключателя. */
.exec-popup[data-variant="map-cover"] {
  column-gap: 0; row-gap: var(--space-3);
  height: 100%;
  grid-template-rows: minmax(0, 1fr) auto;
}
/* Проценты высоты нужна определённая строка: тело отдаёт пресету всю свою высоту. Ниже 768
   и на низком кадре пресет разворачивается столбцом с прокруткой (правило ниже), и строка
   там остаётся по содержимому. Условие — дополнение того правила. */
@media (min-width: 1024px), (min-width: 768px) and (min-height: 501px) {
  dialog.dialog--exec .body:has(> .exec-popup[data-variant="map-cover"]) {
    grid-template-rows: minmax(0, 1fr);
  }
}
/* Группа иллюстрации и сама карта своих коробок здесь не держат: кадр карты, её подпись и
   карточка с текстом раскладываются одной сеткой попапа. Иначе карточку ограничивала бы
   высота всего попапа, и она наезжала бы на подпись карты снизу. */
.exec-popup[data-variant="map-cover"] > .exec-media,
.exec-popup[data-variant="map-cover"] .im-map { display: contents; }
/* Высота задана — заданные разбором пропорции кадра к ней не применяются, и картинка
   внутри режется, а не растягивается. */
.exec-popup[data-variant="map-cover"] .im-map-frame {
  grid-area: 1 / 1; min-height: 0; height: 100%;
}
.exec-popup[data-variant="map-cover"] .im-map-caption { grid-area: 2 / 1; }
.exec-popup[data-variant="map-cover"] > .exec-copy { grid-area: 1 / 1; }
/* Иллюстрация вместо карты живёт в этом пресете по тем же правилам: она и есть кадр, а
   карточка лежит поверх. Без явной ячейки снимок встал бы под карточку строкой ниже и
   отобрал бы у неё всю высоту. */
.exec-popup[data-variant="map-cover"] img.slide {
  grid-area: 1 / 1; height: 100%; min-height: 0; object-fit: cover;
}
/* Список объектов карты в этом пресете уходит с глаз: имена уже напечатаны на самой карте,
   а кадру нужна его высота — на коротком кадре срез по большей стороне съедал метку
   участка. Скринридеру список остаётся: он и есть текстовый путь к содержимому карты. */
.exec-popup[data-variant="map-cover"] .im-map-aside {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.exec-popup[data-variant="map-cover"] > .exec-copy {
  align-self: start; justify-self: start; z-index: 1;
  width: min(400px, calc(100% - var(--space-5)));
  max-height: calc(100% - 2 * var(--space-4)); overflow: auto;
  margin: var(--space-4); padding: var(--space-4);
   border: 1px solid var(--edge); border-radius: var(--r-md);
   background: var(--glass-3); backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
  box-shadow: var(--sheen), var(--lift-2);
}

/* 5. metric-grid — сетка плиток с цифрами. Цифры приходят отдельной группой и идут первыми:
   в этом пресете они и есть содержание, а текст — пояснение под ними. */
.exec-popup[data-variant="metric-grid"] .exec-figures .exec-metrics {
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
}
.exec-popup[data-variant="metric-grid"] .exec-figures .exec-metrics li {
  padding: var(--space-4); border: 1px solid var(--edge); border-radius: var(--r-md);
  background: var(--glass-1); box-shadow: var(--sheen);
}

/* Цифра в полосе и в плитке набирается столбцом: число, подпись, уточнение. Колонка
   уточнения в общем правиле вторая, здесь колонка одна — без сброса уточнение ушло бы в
   колонку, которой в этой сетке нет. */
.exec-popup[data-variant="two-col"] .exec-figures .exec-metrics li,
.exec-popup[data-variant="metric-grid"] .exec-figures .exec-metrics li {
  grid-template-columns: minmax(0, 1fr); align-items: start; row-gap: var(--space-1);
}
.exec-popup[data-variant="two-col"] .exec-figures .exec-metrics .d,
.exec-popup[data-variant="metric-grid"] .exec-figures .exec-metrics .d { grid-column: 1; }
.exec-popup[data-variant="two-col"] .exec-figures .exec-metrics {
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  padding-top: var(--space-3); border-top: 1px solid var(--edge);
}

/* 6. icon-list — пункт списка строкой: кружок со знаком слева, подпись справа. Знак берётся
   из разбора, а нет разбора — стрелкой набора; и то и другое от скринридера скрыто. */
.exec-popup[data-variant="icon-list"] .exec-list {
  list-style: none; padding-left: 0; gap: var(--space-3);
}
.exec-popup[data-variant="icon-list"] .exec-list li {
  display: grid; grid-template-columns: 28px minmax(0, 1fr); gap: var(--space-3);
  align-items: center;
}
.exec-popup[data-variant="icon-list"] .exec-list .ic {
  width: 28px; height: 28px; border-radius: var(--r-pill);
  display: grid; place-items: center; flex: none;
  border: 1px solid var(--edge); background: var(--glass-1);
  color: var(--im-accent); font-size: var(--font-sm); line-height: 1;
}
.exec-popup[data-variant="icon-list"] .exec-list .ic svg { width: 14px; height: 14px; }

/* 7. split — разделённый экран: иллюстрации делят кадр поровну, текст лежит над ними.
   Половин может быть и три: колонок ровно две, лишняя иллюстрация переносится строкой ниже
   и половину рядом с собой не сужает. Иллюстрация одна — колонка одна: делить кадр не с
   чем, и вторая половина осталась бы пустой. */
.exec-popup[data-variant="split"] > .exec-media {
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-3); align-items: start;
}
.exec-popup[data-variant="split"] > .exec-media:has(> :only-child) {
  grid-template-columns: minmax(0, 1fr);
}

/* 8. timeline — этапы: точка на рельсе против каждого пункта. Точка и рельс нарисованы
   оформлением, поэтому скринридер читает обычный список, а не поддельную нумерацию. */
.exec-popup[data-variant="timeline"] .exec-list {
  list-style: none; padding-left: 0; gap: 0;
}
.exec-popup[data-variant="timeline"] .exec-list li {
  display: block; position: relative;
  padding: 0 0 var(--space-4) var(--space-5);
}
.exec-popup[data-variant="timeline"] .exec-list li:last-child { padding-bottom: 0; }
.exec-popup[data-variant="timeline"] .exec-list li::before {
  content: ""; position: absolute; left: 3px; top: .45em;
  width: 10px; height: 10px; border-radius: var(--r-pill);
  background: var(--im-accent); box-shadow: var(--glow-soft);
}
.exec-popup[data-variant="timeline"] .exec-list li::after {
  content: ""; position: absolute; left: 7px; top: calc(.45em + 16px); bottom: 0;
  width: 2px; background: var(--edge);
}
.exec-popup[data-variant="timeline"] .exec-list li:last-child::after { content: none; }

/* 9. compact — тот же столбец плотнее: шкала на ступень вниз, иллюстрация не выше трети
   кадра. Пресет выбирает владелец, а не ширина: телефон получает лист у нижней кромки в
   любом пресете, а этот вид нужен и там, где материала мало. */
.exec-popup[data-variant="compact"] { gap: var(--space-3); }
.exec-popup[data-variant="compact"] > .exec-copy { gap: var(--space-2); }
.exec-popup[data-variant="compact"] > .exec-media { gap: var(--space-2); }
.exec-popup[data-variant="compact"] .exec-copy .lede { font-size: var(--font-body); }
.exec-popup[data-variant="compact"] .exec-metrics { gap: var(--space-2); }
.exec-popup[data-variant="compact"] .exec-metrics .v { font-size: clamp(24px, 2.4vw, 30px); }
.exec-popup[data-variant="compact"] img.slide { max-height: 32vh; object-fit: cover; }

/* Ниже 900 вторая колонка текста перестаёт быть колонкой: строка в ней короче, чем слово
   переносится. Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 899px) {
  .exec-popup[data-variant="two-col"] > .exec-copy { column-count: 1; }
}

/* Ниже 768 попап — лист у нижней кромки, и обе раскладки поперёк кадра складываются в
   столбец: карточка поверх карты накрыла бы её целиком, а половины разделённого экрана
   стали бы двумя марками. Границы в условии — литералы, @media значение var() не читает.

   Низкий кадр ландшафтного телефона складывается так же. Кадру `map-cover` там достаётся
   высота тела слоя — на 844×390 это 125 px под карту почти квадратных пропорций, — и срез
   по большей стороне уносит метку участка «PRECINCT 8» на 52 px ниже нижней кромки кадра,
   под `overflow: hidden`. Ширины в таком кадре хватает, высоты нет: карта разворачивается
   столбцом и держит свои пропорции, а тело слоя прокручивается. */
@media (max-width: 767px), (max-width: 1023px) and (max-height: 500px) {
  .exec-popup[data-variant="map-cover"] {
    gap: var(--space-3); height: auto; grid-template-rows: none;
  }
  .exec-popup[data-variant="map-cover"] > .exec-media { display: grid; height: auto; }
  .exec-popup[data-variant="map-cover"] .im-map { display: grid; height: auto; }
  .exec-popup[data-variant="map-cover"] .im-map-frame,
  .exec-popup[data-variant="map-cover"] .im-map-caption,
  .exec-popup[data-variant="map-cover"] img.slide,
  .exec-popup[data-variant="map-cover"] > .exec-copy { grid-area: auto; }
  .exec-popup[data-variant="map-cover"] .im-map-frame { height: auto; }
  .exec-popup[data-variant="map-cover"] img.slide { height: auto; object-fit: fill; }
  .exec-popup[data-variant="map-cover"] .im-map-aside {
    position: static; width: auto; height: auto; margin: 0;
    overflow: visible; clip-path: none; white-space: normal;
  }
  .exec-popup[data-variant="map-cover"] > .exec-copy {
    width: auto; max-height: none; overflow: visible; margin: 0; justify-self: stretch;
  }
  .exec-popup[data-variant="split"] > .exec-media { grid-template-columns: minmax(0, 1fr); }
}

/* ——— карта попапа ——— */
/* Карта — SVG по данным пака. Кадр держит пропорции оригинала, слои и цвета приходят
   в map-style.json, поэтому здесь только раскладка, интерактив и доступность. */
.im-map { margin: 0; display: grid; gap: 12px; min-width: 0; }
.im-map-frame {
  position: relative; overflow: hidden; border-radius: var(--r-md);
  border: 1px solid var(--edge); width: 100%;
}
.im-map-svg { display: block; width: 100%; height: 100%; }
.im-map-flat { width: 100%; }
/* Там, где карта по макету иллюстрация, она не ловит ни курсор, ни фокус. */
.im-map[data-interactive="0"] .im-map-svg { pointer-events: none; }
.im-map[data-interactive="1"] .im-map-svg { cursor: grab; touch-action: none; }
.im-map[data-interactive="1"] .im-map-svg:active { cursor: grabbing; }
.im-map-svg:focus-visible { outline: 2px solid var(--im-accent); outline-offset: -2px; }

.im-map-tools { position: absolute; right: 10px; bottom: 10px; display: grid; gap: 6px; }
.im-map-btn {
  width: var(--tap); height: var(--tap); border-radius: var(--r-sm);
  border: 1px solid var(--edge); background: var(--glass-3); color: var(--ink);
  font-size: 17px; line-height: 1; cursor: pointer; backdrop-filter: var(--blur);
  box-shadow: var(--sheen), var(--lift-1);
  transition: border-color var(--fast) var(--ease-soft), background var(--fast) var(--ease-soft);
}
.im-map-btn:hover:not(:disabled) { border-color: var(--edge-strong); }
.im-map-btn:focus-visible { outline: 2px solid var(--im-accent); outline-offset: 2px; }
.im-map-btn:disabled { opacity: .4; cursor: default; }

.im-map-caption { display: grid; gap: 8px; font-size: 12.5px; color: var(--ink-3); }
.im-map-credit { font-size: 11.5px; color: var(--ink-3); }

/* Внутренние колонки складываются по ширине блока, а не кадра: на 1024 кадр шире 899, а
   блок всего 601 px, и колонка текста получала 255 px. container-type стоит на
   .exec-panel — собственным @container-правилом контейнер не матчится. */
@container (max-width: 620px) {
  .exec-popup { grid-template-columns: minmax(0, 1fr); }
}
/* В попапе контейнера нет: слой сжимается по содержимому, и container-type на его теле
   обнулил бы ему ширину. Правило выше попап не видит, и на 390 колонки текста и
   иллюстрации делили 325 px на 141 и 162 — по три-четыре слова в строке и пустота под
   картинкой. Ниже 768 попап — лист во всю ширину кадра, и столбец задаёт сам кадр.
   Граница в условии — литерал, @media значение var() не читает. */
@media (max-width: 767px) {
  .exec-popup { grid-template-columns: minmax(0, 1fr); }
}

/* ——— панорама на тач-устройствах ——— */
/* Жесты холста забраны плеером: он ставит холсту touch-action: none, и перетаскивание,
   щипок и выбег остаются панораме. Обратная половина правила — здесь: свайп по кнопкам и
   плиткам контейнеров прокручивает слайд. Сами контейнеры указатель пропускают (правило у
   #slide): их пустое место забирало нажатия по меткам панорамы, и свайп по нему крутит
   панораму. touch-action стоит на контейнере: касанию по ребёнку достаётся пересечение
   его значения и значений предков. */
@media (max-width: 1023px) {
  #slide > .strip, #slide > .filters, #slide > .detail-tiles, #slide > .agent-row {
    touch-action: pan-y;
  }
  .poi-scroll { touch-action: pan-y; }

  /* Панель контролов лежит в прокручиваемом #slide и уезжала вместе с содержимым.
     Крепим её к окну: над нижней панелью, с отступом от выреза. */
  .pano-tools {
    position: fixed;
    right: calc(12px + var(--safe-right, env(safe-area-inset-right, 0px)));
    bottom: calc(var(--foot-h) + 12px + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
  }

  /* Ниже 1024 текстовая колонка ложится сверху во всю ширину, и затемнение считается по
     высоте: горизонтальный слой оставлял правую половину строк на светлой панораме. */
  #scrim.scrim--pano::after {
    background:
      linear-gradient(to bottom,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 100%), transparent) 0%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 96%), transparent) 22%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 81%), transparent) 38%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 49%), transparent) 50%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 17%), transparent) 58%,
        transparent 64%),
      linear-gradient(to top,
        color-mix(in srgb, var(--shade) calc(var(--scrim-frame) * 100%), transparent) 0%,
        transparent 14%);
  }
}

/* Портрет: столбец из четырёх кнопок — это 200 px, он накрывал и точки, и галерею.
   Кнопки идут строкой, а полоса под ней вырезается из самого слайда. Вырез, а не отступ:
   отступ держит только конец прокрутки, середина списка всё равно уезжала кнопкам под низ.
   Панорама в полосе остаётся видна — холст лежит слоем ниже и на всё окно. */
@media (max-width: 1023px) and (orientation: portrait) {
  .pano-tools { grid-auto-flow: column; }
  /* Призыв стоял на тех же 12/76, что и панель, и накрывал собой три кнопки из четырёх.
     Он поднимается над полосой панели — ровно на ту же величину, которую слайд уже под
     эту полосу отдаёт. Слайды, где панель стоит строкой в потоке, полосы не вырезают:
     панель там ничего собой не кроет, а призыву дальше подниматься некуда. */
  #app:has(> #slide:not([data-layout="plan"]):not([data-layout="poi"]) > .pano-tools)
    > #enquire-top {
    bottom: calc(var(--foot-h) + var(--band-step)
                 + var(--safe-bottom, env(safe-area-inset-bottom, 0px)));
  }
  #slide:has(> .pano-tools):not([data-layout="plan"]):not([data-layout="poi"]) {
    --band-tools: var(--band-step);
  }
}

/* Ландшафт: по высоте места нет, панель уходит вбок — столбцом у правого края по центру
   высоты. Полоса под неё вырезается из ширины слайда тем же порядком. */
@media (max-width: 1023px) and (orientation: landscape) {
  .pano-tools {
    top: calc(var(--bar) + 8px);
    bottom: calc(var(--foot-h) + 8px);
    grid-auto-flow: row; align-content: center;
  }
  /* В ландшафте панель уходит столбцом к правому краю, и призыв ложился на её нижнюю
     кнопку. Он сдвигается влево на ту же полосу. Слайды, где панель стоит в потоке,
     полосу не вырезают: правый край им нужен целиком. */
  #app:has(> #slide:not([data-layout="plan"]):not([data-layout="poi"]) > .pano-tools)
    > #enquire-top {
    right: calc(var(--band-step)
                + var(--safe-right, env(safe-area-inset-right, 0px)));
  }
  #slide:has(> .pano-tools):not([data-layout="plan"]):not([data-layout="poi"]) {
    right: calc(var(--band-step)
                + var(--safe-right, env(safe-area-inset-right, 0px)));
  }
}

/* Низкий и достаточно широкий кадр — ландшафт телефона. Полоса под призыв стоит там
   дороже всего: из 262 px высоты она забирает 64, а плану их взять больше неоткуда.
   По ширине же кадр свободен, и призыв возвращается в шапку — на своё десктопное место,
   между именем слайда и счётчиком. Полоса из слайда при этом снимается: в шапке призыв
   лежит поверх #head, а не поверх содержимого.
   Порог ширины — литерал: 600 px хватает бургеру, имени слайда, призыву и счётчику в
   одном ряду. Уже него призыв остаётся внизу справа со своей полосой.
   Границы в условии — литералы, @media значение var() не читает. */
@media (min-width: 600px) and (max-width: 1023px) and (max-height: 500px) {
  #enquire-top,
  #app:has(> #slide:not([data-layout="plan"]):not([data-layout="poi"]) > .pano-tools)
    > #enquire-top {
    top: calc(10px + var(--safe-top)); right: auto; bottom: auto;
    left: 50%; transform: translateX(-50%);
  }
  #slide[data-cta="1"] { --band-cta: 0px; }
}

/* Низкий экран: контент занимает почти всю высоту — граница затемнения опускается следом. */
@media (max-width: 1023px) and (max-height: 500px) {
  #scrim.scrim--pano::after {
    background:
      linear-gradient(to bottom,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 100%), transparent) 0%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 94%), transparent) 34%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 74%), transparent) 58%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 43%), transparent) 76%,
        color-mix(in srgb, var(--shade) calc(var(--scrim-stack) * 15%), transparent) 88%,
        transparent 94%);
  }
}

/* Тач-ввод. Цель метки — не меньше 44 px: у свёрнутой это невидимая подложка, сам кружок
   остаётся в 26 px, иначе точки слипаются на телефоне. Мост между меткой и карточкой нужен
   курсору, который идёт к ней по экрану; пальцу он только перехватывает тапы под собой. */
@media (pointer: coarse) {
  .im-pano-marker { min-width: var(--tap-min, 44px); min-height: var(--tap-min, 44px); }
  .im-pano-marker[data-collapsed="1"] { overflow: visible; }
  .im-pano-marker[data-collapsed="1"]::after {
    content: ""; position: absolute; left: 50%; top: 50%;
    width: var(--tap-min, 44px); height: var(--tap-min, 44px);
    transform: translate(-50%, -50%); border-radius: 50%;
  }
}
@media (hover: none) {
  .hover-card::after { content: none; }
  .hover-card { width: min(240px, calc(100vw - 32px)); }
}

/* ——— край плавающего призыва ——— */
/* Пак двигает #enquire-top по краю кадра. Правил под «Top» здесь нет намеренно: это
   сегодняшняя раскладка, и любой промах значения обязан дать ровно её. Блок стоит
   последним — он перебивает контекстные правила призыва выше, иначе выбор пака терялся
   бы на телефоне и в ландшафте.
   Полоса слайда идёт следом за призывом: она вырезается только там, где призыв реально
   лёг на содержимое, и снимается, когда он ушёл к боковому краю.
   Ландшафт телефона выключен целиком — и края, и полоса. Из 262 px высоты кадра полосу
   в 64 px взять неоткуда, а призыв там уже поднят в шапку: сдвиг к боковому краю вернул бы
   его на кнопки панели. Условие повторяет медиазапрос шапки выше. */
@media not all and (min-width: 600px) and (max-width: 1023px) and (max-height: 500px) {
  #app:has(> #slide[data-enquire-at="bottom"]) > #enquire-top {
    top: auto; left: 50%; right: auto;
    bottom: calc(var(--foot-h) + 10px + var(--safe-bottom)); transform: translateX(-50%);
  }
  #app:has(> #slide[data-enquire-at="left"]) > #enquire-top,
  #app:has(> #slide[data-enquire-at="right"]) > #enquire-top {
    top: 50%; bottom: auto; transform: translateY(-50%);
  }
  #app:has(> #slide[data-enquire-at="left"]) > #enquire-top {
    left: calc(16px + var(--safe-left)); right: auto;
  }
  #app:has(> #slide[data-enquire-at="right"]) > #enquire-top {
    right: calc(16px + var(--safe-right)); left: auto;
  }
  #slide[data-enquire-at="bottom"][data-cta="1"] { --band-cta: var(--band-step); }
  #slide[data-enquire-at="left"][data-cta="1"],
  #slide[data-enquire-at="right"][data-cta="1"] { --band-cta: 0px; }

  /* На узком кадре призыв у бокового края лёг бы серединой поверх содержимого, а сверху его
     кроет бургер. Там он остаётся в нижней полосе — как и без пака, — и пак меняет ему только
     горизонтальный край. Отступ от своего края у обоих краёв один: 12 px против десктопных
     16 — узкому кадру дорог каждый пиксель. Полоса под призывом при этом нужна. */
  @media (max-width: 1023px) {
    #app:has(> #slide[data-enquire-at="left"]) > #enquire-top,
    #app:has(> #slide[data-enquire-at="right"]) > #enquire-top {
      top: auto; bottom: calc(var(--foot-h) + 12px + var(--safe-bottom)); transform: none;
    }
    #app:has(> #slide[data-enquire-at="left"]) > #enquire-top {
      left: calc(12px + var(--safe-left)); right: auto;
    }
    #app:has(> #slide[data-enquire-at="right"]) > #enquire-top {
      right: calc(12px + var(--safe-right)); left: auto;
    }
    #slide[data-enquire-at="left"][data-cta="1"],
    #slide[data-enquire-at="right"][data-cta="1"] { --band-cta: var(--band-step); }
  }
}

/* ——— параметры вида проекта: слова пака, умолчание оставляет прежний вид ——— */

/* Заголовочные подписи вне h1..h3: имя агента и номер юнита. Роль accent берёт и их. */
:root[data-heading-font="accent"] :is(.agent .name, .uc-id) {
  font-family: var(--im-font-heading), ui-serif, Georgia, serif;
  font-weight: var(--im-heading-weight, 400);
}

/* Кнопка входа стрелкой (slide.ctaStyle = arrow): крупный круг, слово остаётся доступным именем. */
.pill--arrow {
  width: 58px; height: 58px; min-height: 58px; padding: 0; border-radius: 50%;
  color: var(--void); background: var(--im-accent); border-color: transparent;
  box-shadow: var(--lift-1);
}
.pill--arrow:hover { background: color-mix(in oklab, var(--im-accent) 84%, var(--tint)); }
.pill--arrow svg { width: 26px; height: 26px; }
#slide[data-layout="start"] > .left-col > .pill--arrow { justify-self: start; }

/* Информационная панель 360 видом panel (slide.infoPanel): одна панель слева. Контейнер
   указатель не держит — вид тянется сквозь него; кнопки и ссылки берут его назад. */
.pano-info {
  position: absolute; left: var(--col-left); top: 50%; transform: translateY(-50%);
  width: min(340px, 44vw); display: grid; gap: var(--space-4); justify-items: start;
  pointer-events: none;
}
.pano-info :is(button, a) { pointer-events: auto; }
.pano-info-logo { max-width: 210px; max-height: 64px; object-fit: contain; }
:root[data-scheme="light"] .pano-info-logo { filter: brightness(0); }
.pano-info-card {
  width: 100%; display: grid; gap: 22px; padding: 22px 18px 24px;
  border-radius: 6px; background: color-mix(in srgb, var(--void) 46%, transparent);
  backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px);
}
.pano-info-dev { display: flex; align-items: center; gap: 16px; color: var(--ink-3); font-size: 18px; }
.pano-info-dev img { max-width: 150px; max-height: 34px; object-fit: contain; }
.pano-info-text { color: var(--ink-2); font-size: 15px; }
.pano-info-rows { list-style: none; margin: 0; padding: 0; display: grid; gap: 22px; }
.pano-info-rows li { display: flex; align-items: center; gap: 16px; font-size: 15.5px; line-height: 1.35; }
.pano-info-rows img { width: 27px; height: 27px; flex: none; }
.pano-info-actions { display: flex; flex-wrap: wrap; gap: 14px; }
.pano-info-actions .pill { background: color-mix(in srgb, var(--void) 38%, transparent); }
@media (max-width: 1023px) {
  .pano-info {
    position: static; transform: none; width: auto; max-width: 72ch; pointer-events: auto;
    padding: 20px calc(16px + var(--safe-right)) 0 calc(16px + var(--safe-left));
    justify-items: stretch;
  }
}
.module-body .pano-info {
  position: static; transform: none; width: auto; max-width: none; padding: 0;
  pointer-events: auto; justify-items: stretch;
}

/* Подсказка вида (slide.lookHint): над панорамой, пока вид не потащили. */
.look-hint {
  position: absolute; left: 50%; bottom: 22%; transform: translateX(-50%); z-index: 2;
  display: grid; justify-items: center; gap: 10px; pointer-events: none;
  color: #fff; font-size: 17px; text-align: center; white-space: nowrap;
  text-shadow: 0 1px 8px rgba(0, 0, 0, .7);
  transition: opacity var(--mid) var(--ease-soft);
}
.look-hint[data-gone="1"] { opacity: 0; }

/* Контакты видом portrait (slide.layout): слева логотип, заголовок и кнопка заявки, справа
   портретные карточки. Ряд карточек остаётся в середине кадра, как в оригинале. */
.cp-side { display: grid; gap: var(--space-3); justify-items: center; }
.cp-logo { max-width: 200px; max-height: 70px; object-fit: contain; }
:root[data-scheme="light"] .cp-logo { filter: brightness(0); }
.cp-side .rule {
  width: 200px; height: 1px; border: 0; background: var(--im-accent);
}
.agent--portrait {
  display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0;
  text-align: center; background: none; border: 0; box-shadow: none;
  backdrop-filter: none; -webkit-backdrop-filter: none;
}
.agent--portrait:hover { background: none; border: 0; box-shadow: none; }
.agent-row .agent--portrait { width: 260px; }
.agent--portrait .portrait {
  width: 160px; height: 160px; margin-bottom: 8px; border: 4px solid var(--edge);
}
.agent--portrait .name { font-size: 24px; line-height: 1.2; font-weight: 600; }
.agent--portrait .role { margin-bottom: 8px; font-size: 15px; }
.agent-box {
  width: 100%; display: grid; gap: 4px; justify-items: center; padding: 14px 14px 12px;
  border-radius: var(--r-lg); background: color-mix(in srgb, var(--void) 70%, transparent);
  box-shadow: var(--lift-1);
}
.agent--portrait .line { max-width: 100%; justify-content: center; }
.agent--portrait .line a {
  display: block; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  line-height: var(--tap-min); min-height: 0;
}
.agent--portrait .line:first-child a { font-size: 20px; }
.agent--portrait .logo { margin-top: 4px; }
@media (min-width: 1024px) {
  /* Ряд агентов стоит по центру остатка кадра правее колонки слева, как в оригинале. */
  #slide .centre-col--portrait {
    grid-template-columns: minmax(0, max-content); justify-content: center;
    padding-left: calc(var(--col-left) + 360px);
  }
  #slide .centre-col--portrait .cp-side {
    position: absolute; left: var(--col-left); top: 50%; transform: translateY(-50%);
    justify-items: start; text-align: left;
  }
}
@media (max-width: 1023px) {
  .centre-col--portrait .cp-side { padding-bottom: var(--space-3); }
  .centre-col--portrait .agent-row .agent--portrait { flex: 0 1 260px; width: auto; max-width: 280px; }
}

/* ——— CP-542, партия 2: список статусов, боковая панель юнитов, стилизованная карта, врезка
   плана, простая таблица. Каждое правило ниже стоит за признаком, который ставит рендер по слову
   пака; без слова ни одно из них не находит себе узла. ——— */

/* Фильтр статусов списком (slide.statusFilter = list): заголовок и строки с точкой цвета статуса.
   Нажатая строка — заполненная точка и плотный шрифт. */
.filters--list { flex-direction: column; flex-wrap: nowrap; align-items: flex-start; gap: 2px; }
.filters-head { margin: 0 0 8px; font-size: 17px; font-weight: 600; color: var(--ink); }
.status-row {
  display: inline-flex; align-items: center; gap: 12px; min-height: var(--tap-min);
  padding: 4px 10px 4px 2px; font: inherit; font-size: 15px; color: var(--ink);
  background: none; border: 0; cursor: pointer; text-align: left;
}
.status-row .dot {
  flex: none; width: 20px; height: 20px; border-radius: 50%; box-sizing: border-box;
  border: 2px solid var(--dot, var(--ink-3));
  transition: background var(--fast) var(--ease-soft);
}
.status-row[aria-pressed="true"] { font-weight: 600; }
.status-row[aria-pressed="true"] .dot { background: var(--dot, var(--ink-3)); }
.status-row:focus-visible { outline: 2px solid var(--im-accent); outline-offset: 2px; border-radius: var(--r-sm); }
.filters--list > .pill { margin-top: var(--space-3); }
.left-col .filters--list { position: static; margin-top: var(--space-4); pointer-events: auto; }
.left-col .filters--list > * { pointer-events: auto; }
@media (min-width: 1024px) {
  #slide[data-layout="plan"] > .filters--list { top: 84px; }
  #slide[data-filters="list"][data-layout="plan"] > #plan-stage { --plan-gut-left: 280px; }
}

/* Боковая панель юнитов вьюера (slide.unitList): слева селектор, фильтр и кнопка, правее номера
   юнитов тремя столбцами сверху вниз. Панель не ловит указатель пустым местом. */
.plan-side {
  position: absolute; left: 24px; top: 72px; bottom: 16px; z-index: 11;
  width: min(390px, 36vw); display: flex; gap: 14px; pointer-events: none;
}
.plan-side > * { pointer-events: auto; }
.plan-side-main { flex: 0 0 188px; display: flex; flex-direction: column; gap: var(--space-5); min-width: 0; }
.unit-picker { display: grid; justify-items: center; gap: 10px; }
.unit-now { margin: 0; font-size: clamp(26px, 3vw, 36px); font-weight: 600; line-height: 1.1; text-align: center; }
.unit-step { width: 64px; min-height: 36px; padding: 0; border-radius: var(--r-sm); }
.unit-step svg { width: 20px; height: 20px; }
.unit-step:disabled { opacity: .35; cursor: default; }
.plan-side .filters--list { position: static; padding: 0; }
.plan-side .filters--list > .pill { align-self: stretch; }
.unit-numbers {
  flex: 1 1 auto; min-width: 0; margin: 0; padding: 0; list-style: none; overflow: auto;
  display: grid; grid-auto-flow: column; grid-auto-columns: minmax(40px, 1fr);
  grid-template-rows: repeat(var(--rows, 19), 32px); align-content: start;
  scrollbar-width: thin;
}
.unit-numbers > li[hidden] { display: none; }
.unit-num {
  width: 100%; height: 100%; padding: 0; font: inherit; font-size: 14px; color: var(--ink);
  font-variant-numeric: tabular-nums; background: none; border: 0; cursor: pointer;
}
.unit-num:hover { color: var(--im-accent); }
.unit-num[aria-pressed="true"] { color: var(--void); background: var(--ink); border-radius: var(--r-sm); font-weight: 600; }
.unit-num:focus-visible { outline: 2px solid var(--im-accent); outline-offset: -2px; border-radius: var(--r-sm); }
/* Выбранный юнит на плане: остальные контуры гаснут, выбранный плотный и обведён. */
#plan-stage svg[data-pick="1"] [id^="unit"] { fill-opacity: .1; }
#plan-stage svg[data-pick="1"] [id^="unit"][data-picked] {
  fill-opacity: .9; stroke: var(--ink); stroke-width: 3; vector-effect: non-scaling-stroke;
}
@media (min-width: 1024px) {
  #slide[data-side="1"][data-layout="plan"] > #plan-stage {
    --plan-gut-left: calc(min(390px, 36vw) + 44px);
  }
}
@media (max-width: 1023px) {
  .plan-side {
    position: static; order: -1; flex: none; width: auto; max-height: 38vh; overflow: auto;
    padding: var(--space-2) calc(var(--space-3) + var(--safe-right)) 0 calc(var(--space-3) + var(--safe-left));
  }
  .plan-side-main { flex: 1 1 180px; }
  .unit-numbers { grid-auto-flow: row; grid-template-rows: none; grid-template-columns: repeat(auto-fill, 44px); flex: 1 1 140px; }
  .unit-num { min-height: 36px; }
}

/* Карта локации стилизованной подложкой (slide.mapLook = styled): рисунок во всю свободную
   часть кадра на цвете карты, кольца и метки поверх. Не тянется и не масштабируется. Легенда
   категорий стоит над строкой вида. */
.loc-map-view[data-styled="1"] { cursor: default; }
.loc-map-view[data-styled="1"] .im-map { display: block; margin: 0; }
.loc-map-view[data-styled="1"] .im-map-frame { border: 0; border-radius: 0; }
.loc-legend {
  order: -1; flex: 0 0 100%; width: min(920px, calc(100vw - 48px)); margin: 0 0 4px; padding: 0;
  list-style: none; display: flex; flex-wrap: wrap; justify-content: center; gap: 6px 8px;
}
/* Широкий кадр: легенда столбцом у правого края, где нет колонки списка. */
@media (min-width: 1024px) {
  #slide > .loc-bar > .loc-legend {
    position: fixed; right: 16px; top: calc(var(--bar, 0px) + 84px); width: auto; margin: 0;
    flex-direction: column; flex-wrap: nowrap; align-items: flex-end; z-index: 11;
  }
}
.loc-legend[hidden] { display: none; }
.loc-legend li {
  display: inline-flex; align-items: center; gap: 8px; padding: 3px 12px 3px 3px;
  font-size: 12.5px; color: var(--ink); border-radius: var(--r-pill);
  background: color-mix(in srgb, var(--void) 72%, transparent);
}
.loc-legend .ico {
  display: grid; place-items: center; width: 24px; height: 24px; border-radius: 50%;
  background: var(--poi, var(--edge-strong)); color: #fff;
}
.loc-legend .ico svg { width: 14px; height: 14px; color: #fff; }
.loc-legend .ico img.poi-glyph { width: 16px; height: 16px; }

/* Схема колец (slide.mapLook = rings): плоская тёмная схема, как у оригинала. Ни тайлов, ни геометрии
   OSM, ни названий мест: тонкие концентрические кольца времени в пути с подписями, метки категорий
   цветными кружками, метка участка в центре. Легенда стоит слева на месте списка; список объектов в
   виде списка скрыт, карточка выбранного объекта открывается на его месте как всегда. */
.loc-map-view[data-look="rings"] {
  background: radial-gradient(circle at 50% 50%, #1c1d22 0%, #131418 75%);
}
.loc-map-view[data-look="rings"] .loc-ring { fill: none; stroke: #fff; stroke-width: 1.5; stroke-opacity: .26; }
.loc-map-view[data-look="rings"] .loc-ring-label {
  display: inline-flex; align-items: center; gap: 6px; padding: 5px 10px; border-radius: 6px;
  font-size: 11px; font-weight: 600; color: #fff; background: #17181c; box-shadow: none;
}
.loc-map-view[data-look="rings"] .loc-ring-label::before {
  content: ''; width: 15px; height: 15px; background: currentColor;
  -webkit-mask: var(--car) center / contain no-repeat; mask: var(--car) center / contain no-repeat;
  --car: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5 11l1.5-4.5A2 2 0 0 1 8.4 5h7.2a2 2 0 0 1 1.9 1.5L19 11h1a1 1 0 0 1 1 1v5h-2v1.5a1 1 0 0 1-1 1h-1a1 1 0 0 1-1-1V17H8v1.5a1 1 0 0 1-1 1H6a1 1 0 0 1-1-1V17H3v-5a1 1 0 0 1 1-1h1zm2.2 0h9.6l-1-3H8.2l-1 3zM6.5 14a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4zm11 0a1.2 1.2 0 1 0 0 2.4 1.2 1.2 0 0 0 0-2.4z'/%3E%3C/svg%3E");
}
.loc-map-view[data-look="rings"] .loc-pin {
  justify-content: center; min-width: 34px; min-height: 34px; padding: 0;
  background: var(--poi, #5b5f69); border: 2px solid rgba(255, 255, 255, .5); box-shadow: 0 1px 4px rgba(0, 0, 0, .5);
}
.loc-map-view[data-look="rings"] .loc-pin svg { width: 17px; height: 17px; color: #fff; }
.loc-map-view[data-look="rings"] .loc-pin:is(:hover, :focus-visible, [data-active="1"]) { padding: 0 11px 0 0; }
.loc-map-view[data-look="rings"] .loc-pin:is(:hover, :focus-visible, [data-active="1"]) svg { margin-left: 8px; }
.loc-map-view[data-look="rings"] .loc-pin[data-active="1"] { background: var(--poi, #5b5f69); box-shadow: 0 0 0 3px #fff; }
.loc-map-view[data-look="rings"] .loc-site { width: 52px; height: 52px; border-color: rgba(255, 255, 255, .6); }
/* Список в виде списка скрыт, но место держит: колонка заголовка остаётся там же, а легенда
   встаёт на освободившееся место. Открытая карточка объекта стоит на этом месте и легенду гасит. */
#slide:has(.loc-map-view[data-look="rings"]) > .poi-list[data-view="list"] { visibility: hidden; }
#slide:has(.loc-map-view[data-look="rings"]):has(.poi-list[data-view="detail"]) > .loc-bar > .loc-legend { display: none; }
@media (min-width: 1024px) {
  #slide:has(.loc-map-view[data-look="rings"]) > .loc-bar > .loc-legend {
    position: fixed; left: 56px; right: auto; top: calc(var(--bar, 0px) + 270px); width: 220px;
    align-items: flex-start; gap: 12px;
  }
  #slide:has(.loc-map-view[data-look="rings"]) > .loc-bar > .loc-legend li { padding: 0; background: none; gap: 12px; font-size: 14px; }
  #slide:has(.loc-map-view[data-look="rings"]) > .loc-bar > .loc-legend .ico { width: 32px; height: 32px; border: 2px solid rgba(255, 255, 255, .4); }
  #slide:has(.loc-map-view[data-look="rings"]) > .loc-bar > .loc-legend .ico svg { width: 17px; height: 17px; }
}

/* Врезка плана (slide.minimap): угол у правого края, левее кнопок панорамы. */
.minimap {
  position: absolute; right: calc(16px + var(--tap) + 12px); bottom: calc(16px + var(--band-modules, 0px));
  z-index: 11; width: clamp(130px, 14vw, 210px); padding: 6px; cursor: pointer;
  background: #fff; border: 0; border-radius: var(--r-md); box-shadow: var(--lift-2);
}
.minimap img { display: block; width: 100%; height: auto; }
.minimap:focus-visible { outline: 2px solid var(--im-accent); outline-offset: 3px; }
@media (max-width: 1023px) { .minimap { width: 120px; } }

/* Простая таблица списка (slide.compact): на десктопе без строки групп и без колонки Actions.
   Ниже 1024 колонка Actions несёт раскрытие строки, и таблица остаётся прежней. */
@media (min-width: 1024px) {
  .table-wrap[data-compact="1"] thead .table-groups { display: none; }
  .table-wrap[data-compact="1"] thead tr:not(.table-groups) th { top: 0; }
  .table-wrap[data-compact="1"] :is(thead th.table-col--actions, tbody td.table-col--actions) { display: none; }
}

/* Хром minimal (brand.chrome): без полосы прогресса, счётчика, точек, названия слайда, полосы
   стадий и справки. Шапка: меню, логотип проекта, заявка. Подвал: стрелки назад и вперёд и
   кнопка галереи модуля. Скрипт ничего не прячет: _syncRail() по-прежнему решает о точках и
   счётчике по ширине кадра, а здесь они просто не рисуются. Клавиши и свайп работают как были. */
#app[data-chrome="minimal"] #progress,
#app[data-chrome="minimal"] #dots,
#app[data-chrome="minimal"] #slide-count,
#app[data-chrome="minimal"] #slide-name,
#app[data-chrome="minimal"] #stage-strip,
#app[data-chrome="minimal"] #next-name,
#app[data-chrome="minimal"] #foot .nav > :is(.pill--quiet) { display: none; }
#app[data-chrome="minimal"] #dots { --rail: off; }
#app[data-chrome="minimal"] #head-logo { display: block; height: 28px; width: auto; max-width: 160px; object-fit: contain; }
#app[data-chrome="minimal"] { --foot-h: var(--bar); }
#app[data-chrome="minimal"] #foot { flex-direction: row; align-items: center; padding-top: 0; }
#head-logo { display: none; }

/* ——— CP-542, партия 4: простая таблица по ширине, счёт статусов, вкладки уровней, врезка плана
   карточкой с конусом взгляда, плоские точки мастерплана. Каждое правило стоит за признаком, который
   ставит рендер по слову пака; без слова ни одно не находит себе узла. ——— */

/* Простая таблица (slide.compact) вмещается в кадр: ширина колонок идёт от содержимого, длинный
   текст переносится, поля ячеек уже. Закрепление правых колонок снято: таблице некуда ехать, и
   отступы --pin-* сдвигали бы цену и статус на соседей. Без вмещения (узкий десктоп) прокрутка
   остаётся прежней. */
@media (min-width: 1024px) {
  .table-wrap[data-compact="1"] table { width: 100%; min-width: 0; font-size: 13px; }
  .table-wrap[data-compact="1"] :is(th, td) { white-space: normal; }
  .table-wrap[data-compact="1"] tbody td { padding: 8px 8px; line-height: 1.3; vertical-align: middle; }
  .table-wrap[data-compact="1"] thead th button {
    min-height: 0; padding: 10px 8px; gap: 4px; line-height: 1.2; letter-spacing: .02em;
    text-align: left; white-space: normal;
  }
  .table-wrap[data-compact="1"] thead th .unit { white-space: nowrap; }
  .table-wrap[data-compact="1"] tbody td > svg { margin-right: 4px; }
  .table-wrap[data-compact="1"] :is(tbody td, thead tr:not(.table-groups) th).table-col--deal { right: auto !important; }
  .table-wrap[data-compact="1"] tbody td.table-col--deal { box-shadow: none; }
}

/* Счёт статусов над таблицей (slide.statusCounts при onlyAvailable): блок «Filter By Status» со
   всеми статусами и итогами. Только показ, нажатий нет. Таблица опускается под него. */
.filters--counts {
  inset: 72px auto auto 32px; flex-direction: row; flex-wrap: wrap; align-items: center;
  gap: 6px 22px; max-width: calc(100% - 64px); padding: 12px 20px 14px;
  border-radius: var(--r-lg); border: 1px solid var(--edge); background: var(--glass-3);
  backdrop-filter: var(--blur);
}
.filters--counts .filters-head { flex: 0 0 100%; margin: 0 0 2px; }
.status-count { display: inline-flex; align-items: center; gap: 10px; font-size: 15px; color: var(--ink);
  font-variant-numeric: tabular-nums; }
.status-count .dot {
  flex: none; width: 20px; height: 20px; border-radius: 50%; box-sizing: border-box;
  background: var(--dot, var(--ink-3)); border: 2px solid color-mix(in srgb, var(--dot, var(--ink-3)) 55%, #fff);
}
@media (min-width: 768px) {
  #slide:has(> .filters--counts) > .table-wrap { top: 170px; }
}

/* Вкладки уровней боковой панели вьюера (slide.levelTabs): повёрнутые подписи на кромке списка
   номеров. Нажатая вкладка светлее и плотнее. Панель шире на столбец вкладок. */
.level-tabs { flex: none; display: flex; flex-direction: column; gap: 8px; align-self: flex-start; padding-top: 44px; }
.level-tab {
  writing-mode: vertical-rl; transform: rotate(180deg); padding: 14px 7px; font: inherit; font-size: 13px;
  letter-spacing: .05em; color: var(--ink-3); background: none; border: 0; border-radius: var(--r-sm); cursor: pointer;
}
.level-tab:hover { color: var(--ink); }
.level-tab[aria-selected="true"] { color: var(--ink); font-weight: 600;
  background: color-mix(in srgb, var(--tint) 12%, transparent); }
.level-tab:focus-visible { outline: 2px solid var(--im-accent); outline-offset: 1px; }
@media (min-width: 1024px) {
  #slide[data-tabs="1"] .plan-side { width: min(430px, 38vw); }
  #slide[data-tabs="1"][data-side="1"][data-layout="plan"] > #plan-stage {
    --plan-gut-left: calc(min(430px, 38vw) + 44px);
  }
}
@media (max-width: 1023px) {
  .level-tabs { flex-direction: row; padding-top: 0; align-self: auto; }
  .level-tab { writing-mode: horizontal-tb; transform: none; padding: 8px 12px; }
}

/* Врезка плана (slide.minimap): место узла на плане и конус взгляда. Позиция в процентах картинки
   плана, поворот конуса ставит скрипт переменной --turn. Конус и точка не ловят указатель: нажатие
   по врезке открывает вьюер планов. */
.minimap-plan { position: relative; display: block; }
.minimap-here { position: absolute; width: 0; height: 0; pointer-events: none; }
.minimap-here[hidden] { display: none; }
.minimap-cone {
  position: absolute; left: -42px; top: -42px; width: 84px; height: 84px; overflow: visible;
  transform: rotate(var(--turn, 0deg)); transform-origin: 50% 50%;
}
.minimap-cone path { fill: color-mix(in srgb, #d9402b 34%, transparent); stroke: #d9402b; stroke-width: .8; }
.minimap-cone circle { fill: #d9402b; stroke: #fff; stroke-width: 1.6; }
.minimap-cone--dot path { display: none; }
/* Карточка (slide.minimapStyle = card): высокая белая карточка у правого края. План повёрнут на
   четверть оборота и стоит во всю её высоту; размеры карточки идут от сторон картинки (--ar, ширина
   к высоте, ставит скрипт). */
.minimap--card {
  --cw: clamp(104px, 10vw, 150px);
  width: calc(var(--cw) + 16px); height: calc(var(--cw) * var(--ar, 1.6) + 16px);
  padding: 0; overflow: hidden;
}
.minimap--card .minimap-plan {
  position: absolute; left: calc(var(--cw) + 8px); top: 8px; width: calc(var(--cw) * var(--ar, 1.6));
  transform-origin: 0 0; transform: rotate(90deg);
}
@media (max-width: 1023px) {
  .minimap--card { --cw: 88px; }
}
/* Своя картинка врезки (slide.minimapPlan): рисуется как есть, без поворота. Карточка берёт высоту
   от пропорции самой картинки (--ar, ширина к высоте): стоячий чертёж даёт высокую карточку. */
.minimap--card[data-own="1"] { height: calc(var(--cw) / var(--ar, .45) + 16px); padding: 8px; }
.minimap--card[data-own="1"] .minimap-plan {
  position: relative; left: auto; top: auto; width: 100%; transform: none;
}

/* Плоские точки мастерплана (slide.pinStyle = dot): точка цвета статуса на здании, без ярусов и
   ножек. Номер юнита виден при наведении, фокусе и на выбранной точке. Закрытый статус не гаснет:
   у оригинала красная и синяя точки такие же яркие, как зелёные. */
#stage[data-pins="dot"] .im-pano-marker[data-unit="1"] {
  width: 15px; height: 15px; min-width: 15px; min-height: 15px; padding: 0; border-radius: 50%;
  font-size: 0; line-height: 0; color: #fff; opacity: 1;
  background: var(--status, var(--ink-3)); border: 2px solid #fff;
  box-shadow: 0 1px 4px rgba(0, 0, 0, .55); text-shadow: none;
}
#stage[data-pins="dot"] .im-pano-marker[data-unit="1"]::after { width: 24px; height: 24px; }
#stage[data-pins="dot"] .im-pano-marker[data-unit="1"]:is(:hover, :focus-visible, [data-active="1"], [data-hot="1"]) {
  width: auto; min-width: 26px; height: 26px; min-height: 26px; padding: 0 8px;
  font-size: 12px; line-height: 1; color: #fff; background: var(--status, var(--ink-3));
  border-color: #fff; box-shadow: 0 2px 8px rgba(0, 0, 0, .6); z-index: 1000 !important;
}
#stage[data-pins="dot"] .im-pano-marker[data-unit="1"][data-off="1"] { opacity: 0; }

/* ——— CP-542, партия 7: WP-вид хрома. Правила стоят за признаками, которые ставит рендер по слову
   пака (brand.progress, brand.enquireStyle, brand.switcherStyle); без слова ни одно не находит себе
   узла, и страница прежняя. ——— */

/* progress: dots. Рельс точек WP: полая точка с тонкой обводкой, текущая закрашена акцентом, имя
   текущего модуля стоит в рельсе сразу за ней. Полосы прогресса нет. Рельс виден и при хроме
   minimal: там он единственная примета места в презентации. */
#app[data-progress-style="dots"] #progress,
#app[data-progress-style="dots"] #slide-name { display: none; }
#app[data-progress-style="dots"] #dots { --rail: on; align-items: center; }
#app[data-progress-style="dots"] #dots .dot { width: 22px; }
#app[data-progress-style="dots"] #dots .dot::after {
  width: 12px; height: 12px; border: 1px solid color-mix(in srgb, var(--ink) 70%, transparent);
  background: transparent;
}
#app[data-progress-style="dots"] #dots .dot::before { width: 10px; background: color-mix(in srgb, var(--ink) 55%, transparent); }
#app[data-progress-style="dots"] #dots .dot[data-seen="1"]::after { background: transparent; border-color: color-mix(in srgb, var(--ink) 70%, transparent); }
#app[data-progress-style="dots"] #dots .dot[aria-current="true"]::after {
  background: var(--im-accent); border-color: transparent; transform: none; box-shadow: none;
}
#app[data-progress-style="dots"] #dots .dot:hover::after { transform: scale(1.2); }
#app[data-progress-style="dots"] #dots .dot-name {
  font-family: var(--im-font-base), sans-serif; font-size: 12px; font-weight: 600; letter-spacing: .04em;
  text-transform: uppercase; color: var(--ink); white-space: nowrap; padding: 0 8px 0 4px;
}
#app[data-progress-style="dots"][data-chrome="minimal"] #dots { display: flex; }
#app[data-progress-style="dots"][data-chrome="minimal"] #slide-count { display: none; }
@media (max-width: 719px) {
  #app[data-progress-style="dots"] #dots .dot-name { max-width: 30vw; overflow: hidden; text-overflow: ellipsis; }
}

/* enquireStyle: filled. Кнопка заявки сверху: плотная таблетка цвета акцента без значка. */
#app[data-enquire-style="filled"] #enquire-top {
  background: var(--im-enquire, var(--im-accent)); color: var(--on-accent, #fff);
  border-color: color-mix(in srgb, var(--im-accent) 60%, white);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 10px 26px; font-size: 16px; font-weight: 500;
}
#app[data-enquire-style="filled"] #enquire-top svg { display: none; }

/* switcherStyle: pill. Выбор лежит одним тёмным закруглённым контейнером, выбранный сегмент
   светлее остальных. Для двух вариантов это уже бегунок, для остальных его роль у фона сегмента. */
#app[data-switcher-style="pill"] .switch-track {
  background: color-mix(in srgb, #000 62%, transparent); border-color: color-mix(in srgb, #fff 28%, transparent);
  padding: 3px; min-width: 0;
}
#app[data-switcher-style="pill"] .switch-track .pill {
  font-size: 13px; letter-spacing: .02em; text-transform: uppercase; color: var(--ink); padding: 8px 16px; min-height: 36px;
}
#app[data-switcher-style="pill"] .switch-thumb {
  inset: 3px auto 3px 3px; width: calc(50% - 3px); background: color-mix(in srgb, #fff 22%, transparent); box-shadow: none;
}
#app[data-switcher-style="pill"] .switch-track .pill[aria-pressed="true"] { color: var(--ink); }
#app[data-switcher-style="pill"] .switch-track--n { grid-template-columns: none; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
#app[data-switcher-style="pill"] .switch-track--n .pill[aria-pressed="true"] { background: color-mix(in srgb, #fff 22%, transparent); }

/* ——— CP-542, партия 8: завершающие параметры вида. Каждое правило стоит за признаком, который
   ставит рендер по слову пака (brand.* на #app, slide.* на узле модуля); без слова ни одно не
   находит себе узла, и страница прежняя. Блок стоит последним: он перебивает WP-вид хрома партии
   7 там, где слово пака назвало регистр или выбранный сегмент. ——— */

/* slideCounter: false. Строка «NN / NN» над заголовком и счётчик в шапке. Точки и клавиши остаются. */
#app[data-slide-counter="off"] :is(#slide-count, .eyebrow) { display: none !important; }

/* bottomNav: false. Кнопки внизу слева: назад, вперёд, галерея, подсказки, клавиши. Клавиатура и
   свайп живут в скрипте, их ряд не держит. */
#app[data-bottom-nav="off"] #foot .nav { display: none; }

/* enquirePlacement: top-right. Кнопка заявки у правого края сразу под шапкой, как у оригинала
   (шапка 60 px, кнопка с 64). Модуль со своим краем (enquirePosition Left, Right, Bottom) остаётся при нём;
   Top и пусто значат место проекта. Узкий кадр
   прежний. Органы, которые стоят в том же углу, уходят ниже. */
@media (min-width: 1024px) {
  #app[data-enquire-placement="top-right"]:not(:has(> #slide:is([data-enquire-at="left"], [data-enquire-at="right"], [data-enquire-at="bottom"]))) > #enquire-top {
    top: var(--bar); left: auto; right: calc(20px + var(--safe-right)); transform: none;
  }
  #app[data-enquire-placement="top-right"] .loc-map-zoom { top: calc(var(--bar, 0px) + 64px); }
  #app[data-enquire-placement="top-right"] #slide > .loc-bar > .loc-legend { top: calc(var(--bar, 0px) + 140px); }
}

/* switcherCase. Регистр подписей переключателей сцен и уровней: upper — прописные, title — каждое
   слово с заглавной. Без слова с кнопками подписи как в паке, с Pill прописные (партия 7). */
#app[data-switcher-case="upper"] :is(.switch-track, .strip) .pill { text-transform: uppercase; letter-spacing: .02em; }
#app[data-switcher-case="title"] :is(.switch-track, .strip) .pill { text-transform: capitalize; letter-spacing: 0; }

/* switcherActive: outline. Выбранный сегмент не залит, а обведён тонкой линией. */
#app[data-switcher-active="outline"] .switch-thumb {
  background: transparent; box-shadow: none;
  border: 1px solid color-mix(in srgb, var(--ink) 75%, transparent);
}
#app[data-switcher-active="outline"] .switch-track .pill[aria-pressed="true"] { color: var(--ink); }
#app[data-switcher-active="outline"] :is(.switch-track--n, .strip) .pill[aria-pressed="true"] {
  background: transparent; color: var(--ink); box-shadow: none;
  border-color: color-mix(in srgb, var(--ink) 75%, transparent);
}

/* cardTone: glass. Информационные карточки над сценой: светлое прозрачное стекло с размытием
   вместо плотной тёмной заливки. */
#app[data-card-tone="glass"] :is(.detail-tiles .row, .pano-info-card) {
  background: color-mix(in srgb, #fff 11%, transparent);
  border-color: color-mix(in srgb, #fff 14%, transparent); box-shadow: none;
  backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px);
}
#app[data-card-tone="glass"] .pano-info-card { border-style: solid; border-width: 1px; }
#app[data-card-tone="glass"] .pano-info-actions .pill {
  background: color-mix(in srgb, #fff 11%, transparent); box-shadow: none;
}

/* sceneShade: light или none. Затемнение 360 и мастерплана на панораме: полоса слева под колонкой
   текста, полосы под шапкой и подвалом. Фото и слайды без панорамы прежние. */
#app[data-scene-shade="light"]:has(#scrim.scrim--pano) { --scrim-read: .4; --scrim-stack: .6; --chrome-veil: .5; }
#app[data-scene-shade="none"]:has(#scrim.scrim--pano) { --scrim-read: 0; --scrim-stack: 0; --scrim-frame: 0; --chrome-veil: .25; }

/* minimapSize: large. Пропорции оригинала: карточка около 150 px шириной, врезка крупнее, низ на
   высоте подвала (64 px). Без кнопок вьюера карточка встаёт к правому краю на 32 px, как у оригинала. */
.minimap[data-size="large"] { width: clamp(170px, 18vw, 260px); }
.minimap--card[data-size="large"] { --cw: clamp(124px, 13vw, 190px); width: calc(var(--cw) + 16px); }
@media (min-width: 1024px) {
  .minimap[data-size="large"] { bottom: var(--band-modules, 0px); }
  #app[data-viewer-controls="off"] .minimap[data-size="large"] { right: calc(32px + var(--safe-right)); }
}
@media (max-width: 1023px) { .minimap--card[data-size="large"] { --cw: 104px; } }

/* coneTone: neutral. Серый прозрачный конус и тёмная точка стояния, без цветной заливки. */
.minimap[data-cone="neutral"] .minimap-cone path { fill: color-mix(in srgb, #6d6d6d 46%, transparent); stroke: none; }
.minimap[data-cone="neutral"] .minimap-cone circle { fill: #4a4a4a; stroke: #fff; }

/* contactValues: truncated. Значение режется многоточием за иконкой, нажатие открывает ссылку. */
.agent--trunc .line { align-items: center; justify-content: center; gap: 8px; }
.agent--trunc .line svg { width: 16px; height: 16px; flex: none; color: var(--ink-2); }
.agent--trunc .line a {
  flex: 0 1 auto; display: block; max-width: 11ch; min-height: 0; line-height: var(--tap-min);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; overflow-wrap: normal;
}

/* Телефон режется короче почты: цифры уже букв, и за 11 знаков помещался целиком (партия 11). */
.agent--trunc .line--tel a { max-width: 8ch; }

/* disclaimerStyle: link. Подчёркнутая ссылка на месте строки-переключателя; текст открывает окно. */
.disclaimer-link {
  position: absolute; inset: auto auto 16px 56px; min-height: var(--tap-min);
  font: inherit; font-size: 13px; color: var(--ink); background: none; border: 0; padding: 0;
  text-decoration: underline; text-underline-offset: 3px; cursor: pointer;
}
.disclaimer-link:hover { color: var(--im-accent); }
.disclaimer-text { white-space: pre-line; color: var(--ink-2); font-size: 14px; line-height: 1.55; }
@media (max-width: 1023px) { .disclaimer-link { position: static; display: block; margin: 8px 16px calc(12px + var(--safe-bottom)); } }

/* tableWidth: centered. Таблица с полями по бокам, как у оригинала (около 162 px при 1440). */
@media (min-width: 1024px) {
  .table-wrap[data-width="centered"] { left: 11.25%; right: 11.25%; }
}
/* Правовая строка простой таблицы стоит под ней, вне прокрутки; таблица поднимается на её высоту. */
.table-note--below {
  position: absolute; left: 32px; right: 32px; bottom: calc(var(--foot-h) + 6px); margin: 0; padding: 0 4px;
  max-height: 2.8em; overflow: hidden; max-width: none; white-space: normal;
}
#slide:has(> .table-note--below) > .table-wrap { bottom: calc(var(--foot-h) + 50px); }
@media (min-width: 1024px) {
  #slide[data-table-width="centered"] > .table-note--below { left: 11.25%; right: 11.25%; }
}
@media (max-width: 1023px) { .table-note--below { left: 16px; right: 16px; } }
/* headerCase: title. Заголовки колонок как записаны в паке. */
.table-wrap[data-head="title"] thead th { text-transform: none; letter-spacing: .02em; }

/* lookHint на карте локации: над видом карты подсказка про панораму не нужна. */
#slide:has(> .loc-map[data-on="1"]) .look-hint { display: none; }

/* markerSize: large. Метки категорий и участка на карте локации крупнее, как у оригинала. */
.loc-map[data-markers="large"] .loc-pin { min-width: 42px; min-height: 42px; padding: 0 11px; }
.loc-map[data-markers="large"] .loc-pin svg { width: 22px; height: 22px; }
.loc-map[data-markers="large"] .loc-pin img.poi-glyph { width: 26px; height: 26px; }
.loc-map[data-markers="large"] .loc-site { width: 68px; height: 68px; }
.loc-map[data-markers="large"] .loc-site svg { width: 30px; height: 30px; }
.loc-map[data-markers="large"] .loc-map-view[data-look="rings"] .loc-pin { min-width: 46px; min-height: 46px; }
.loc-map[data-markers="large"] .loc-map-view[data-look="rings"] .loc-site { width: 76px; height: 76px; }
.loc-legend-site .ico--site { background: var(--void); border: 2px solid #fff; overflow: hidden; }
.loc-legend-site .ico--site img { width: 100%; height: 100%; object-fit: cover; }
.loc-legend-site .ico--site svg { width: 14px; height: 14px; color: var(--im-accent); }

/* ——— CP-542, партия 9: переопределения модуля и последние параметры вида. Каждое правило стоит за
   признаком, который ставит рендер по слову пака (brand.* и слово модуля на #app, slide.* на узле
   модуля); без слова ни одно не находит себе узла, и страница прежняя. ——— */

/* enquireOffset: lowered. Кнопка заявки на высоте оригинала (под шапкой, с 64 px) при любом месте по
   горизонтали: top-center остаётся по центру, top-right у правого края. Модуль со своим краем
   остаётся при нём. Органы в том же углу уходят ниже, как при top-right. */
@media (min-width: 1024px) {
  #app[data-enquire-offset="lowered"]:not(:has(> #slide:is([data-enquire-at="left"], [data-enquire-at="right"], [data-enquire-at="bottom"]))) > #enquire-top {
    top: var(--bar);
  }
  #app[data-enquire-offset="lowered"] .loc-map-zoom { top: calc(var(--bar, 0px) + 64px); }
  #app[data-enquire-offset="lowered"] #slide > .loc-bar > .loc-legend { top: calc(var(--bar, 0px) + 140px); }
}

/* headingScale: compact. Заголовок модуля размером оригинала: clamp(1.5rem, 2.2vw, 28px), вес 600,
   одна строка (измерено на живом оригинале: 28 px при 1440). Заголовок обложки остаётся крупным. */
@media (min-width: 1024px) {
  #app[data-heading-scale="compact"] #slide:not([data-layout="start"]) :is(.left-col, .centre-col) h2 {
    font-size: clamp(24px, 2.2vw, 28px); font-weight: 600; line-height: 1.15;
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  }
}

/* switcherCase и switcherActive на переключателе вида локации (360 View и Map View). */
#app[data-switcher-case="upper"] .loc-views .pill { text-transform: uppercase; letter-spacing: .02em; }
#app[data-switcher-case="title"] .loc-views .pill { text-transform: capitalize; letter-spacing: 0; }
#app[data-switcher-active="outline"] .loc-views .pill[aria-pressed="true"] {
  background: transparent; color: var(--ink); box-shadow: none;
  border-color: color-mix(in srgb, var(--ink) 75%, transparent);
}

/* viewerControls: false убирает и кнопки приближения карты локации. Колесо, жесты и клавиши живут. */
#app[data-viewer-controls="off"] .loc-map-zoom { display: none; }

/* ringLabels: badge. Подпись времени в пути на схеме колец на плотной тёмной плашке, темнее карты. */
.loc-map[data-labels="badge"] .loc-map-view[data-look="rings"] .loc-ring-label {
  padding: 6px 12px; border-radius: 7px; background: #07080a; box-shadow: 0 2px 10px rgba(0, 0, 0, .55);
}

/* ringsSatellite: тайлы под кольцами, пока тумблер включён. Снимок притушен, кольца ярче. */
.loc-map-view[data-look="rings"][data-sat="1"] .loc-tile { opacity: .62; }
.loc-map-view[data-look="rings"][data-sat="1"] .loc-ring { stroke-opacity: .72; }

/* backdrop: dark. Ровный почти чёрный фон под планом вместо фирменного градиента. */
#backdrop[data-mode="dark"], #backdrop-next[data-mode="dark"] { background: #0a0a0b; }
/* backdrop: dark на фотографии (партия 11). Фотография ставится инлайн-стилем и била сплошной цвет, так
   что слой оставался светлым. Тёмная вуаль поверх неё; без фотографии те же 100% того же цвета. */
:is(#backdrop, #backdrop-next)[data-mode="dark"][style*="url("]::after {
  content: ""; position: absolute; inset: 0; background: rgb(10 10 11 / .9);
}

/* cellAlign: center. Ячейки и заголовки таблицы по центру. */
@media (min-width: 1024px) {
  .table-wrap[data-align="center"] :is(thead th, tbody td) { text-align: center; }
  .table-wrap[data-align="center"] thead th button { justify-content: center; text-align: center; width: 100%; }
}

/* Панель статусов стоит с таблицей: у таблицы с полями по бокам левая кромка панели та же. */
@media (min-width: 1024px) {
  #slide[data-table-width="centered"] > .filters { left: 11.25%; }
  #slide[data-table-width="centered"] > .filters--counts { max-width: 77.5%; }
}

/* header: hide на обложке. Шапки нет вовсе: ни меню, ни точек, ни заявки. Клавиши и свайп живут. */
#app[data-slide-header="off"] :is(#head, #enquire-top) { display: none; }

/* ctaTone: outline у кнопки входа стрелкой: бледное кольцо на тёмной подложке, белая стрелка. */
.pill--arrow[data-tone="outline"] {
  color: #fff; background: color-mix(in srgb, #000 30%, transparent); box-shadow: none;
  border: 1.5px solid color-mix(in srgb, var(--im-accent) 45%, #fff);
}
.pill--arrow[data-tone="outline"]:hover { background: color-mix(in srgb, #fff 14%, transparent); }

/* ——— CP-542, партия 10: тон кнопки входа и телефон. ——— */

/* ctaTone: accent. Кнопка входа живого оригинала (start-page.css и home.css темы, .startScreenButton):
   заливка --start-button-bg (акцент пака), белая стрелка, белая рамка 1 px; под курсором рамка
   уходит и встаёт мягкое кольцо 0 0 0 4px rgba(255,255,255,.2), переход 0.3s ease-in-out. */
.pill--arrow[data-tone="accent"] {
  color: #fff; background: var(--im-accent); box-shadow: 0 0 0 0 rgba(255, 255, 255, 0);
  border: 1px solid #fff;
  transition: border-color .3s ease-in-out, box-shadow .3s ease-in-out;
}
.pill--arrow[data-tone="accent"]:is(:hover, :focus-visible) {
  background: var(--im-accent); border-color: transparent; box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.2);
}

/* ——— CP-542, партия 11: оттенок интерфейса, затемнение фото, стрелки по краям, кольцо у кнопки
   заявки, панель статусов в рамке, легенда категорий, ряд логотипов. Каждое правило стоит за
   признаком, который ставит рендер по слову пака; без слова ни одно не находит себе узла. ——— */

/* chromeTint: primary или secondary. Карточки, кнопки, плашка переключателя и кольцо портретов берут
   оттенок цвета бренда вместо нейтрального серого. Стекло переписано на #app: всё, что рисуется
   внутри, читает эти переменные. */
#app[data-chrome-tint] {
  --tc: var(--im-primary);
  --glass-1: linear-gradient(155deg, color-mix(in srgb, var(--tc) 66%, transparent),
                                     color-mix(in srgb, var(--tc) 52%, transparent));
  --glass-2: linear-gradient(155deg, color-mix(in srgb, var(--tc) 76%, transparent),
                                     color-mix(in srgb, var(--tc) 62%, transparent));
  --glass-3: linear-gradient(155deg, color-mix(in srgb, var(--tc) 90%, transparent),
                                     color-mix(in srgb, var(--tc) 80%, transparent));
  --edge: color-mix(in srgb, var(--im-secondary) 52%, transparent);
  --edge-strong: color-mix(in srgb, var(--im-secondary) 82%, transparent);
}
#app[data-chrome-tint="secondary"] { --tc: color-mix(in srgb, var(--im-secondary) 58%, var(--void)); }
#app[data-chrome-tint][data-switcher-style="pill"] .switch-track {
  background: color-mix(in srgb, var(--tc) 84%, transparent); border-color: var(--edge-strong);
}
#app[data-chrome-tint] .agent--portrait .portrait { border-color: color-mix(in srgb, var(--im-secondary) 85%, transparent); }

/* imageDim и imageDimShape: вуаль поверх фотографии за модулем. Панорамы и градиент плана не трогает:
   у них нет url() в стиле слоя. Тёмный режим свою вуаль уже несёт. */
#app[data-image-dim] { --img-dim: .3; }
#app[data-image-dim="light"] { --img-dim: .2; }
#app[data-image-dim="strong"] { --img-dim: .6; }
#app[data-image-dim] :is(#backdrop, #backdrop-next):not([data-mode="dark"])[style*="url("]::after {
  content: ""; position: absolute; inset: 0; background: rgb(10 10 11 / var(--img-dim));
}
#app[data-image-dim][data-image-dim-shape="left"] :is(#backdrop, #backdrop-next):not([data-mode="dark"])[style*="url("]::after {
  background: linear-gradient(90deg, rgb(10 10 11 / calc(var(--img-dim) * 1.5)) 0%,
    rgb(10 10 11 / calc(var(--img-dim) * .6)) 55%, rgb(10 10 11 / calc(var(--img-dim) * .1)) 100%);
}
#app[data-image-dim][data-image-dim-shape="bottom"] :is(#backdrop, #backdrop-next):not([data-mode="dark"])[style*="url("]::after {
  background: linear-gradient(0deg, rgb(10 10 11 / calc(var(--img-dim) * 1.5)) 0%,
    rgb(10 10 11 / calc(var(--img-dim) * .6)) 55%, rgb(10 10 11 / calc(var(--img-dim) * .05)) 100%);
}

/* sideArrows: sides. Круглые стрелки по середине левого и правого края; пара подвала уходит.
   Ниже 1024 пара подвала остаётся: боковым стрелкам там нет места. */
.side-arrow { display: none; }
@media (min-width: 1024px) {
  #app[data-side-arrows="sides"] .side-arrow {
    display: inline-flex; position: absolute; top: 50%; z-index: var(--z-float); transform: translateY(-50%);
  }
  #app[data-side-arrows="sides"] .side-arrow--prev { left: calc(10px + var(--safe-left)); }
  #app[data-side-arrows="sides"] .side-arrow--next { right: calc(10px + var(--safe-right)); }
  #app[data-side-arrows="sides"] .side-arrow:disabled { visibility: hidden; }
  #app[data-side-arrows="sides"] #foot .nav > :is([aria-label="Previous slide"], [aria-label="Next slide"]) { display: none; }
}

/* enquireStyle: ring и enquireColor. Таблетка цвета кнопки заявки с белым кольцом, без значка. Цвет
   кнопки заявки отдельный от акцента; не задан, берётся акцент. */
#app[data-enquire-style="ring"] #enquire-top {
  background: var(--im-enquire, var(--im-accent)); color: var(--on-accent, #fff);
  border: 2px solid #fff; backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 9px 25px; font-size: 16px; font-weight: 500;
}
#app[data-enquire-style="ring"] #enquire-top svg { display: none; }
#app[data-enquire-color]:not([data-enquire-style="filled"], [data-enquire-style="ring"]) #enquire-top {
  border-color: var(--im-enquire); color: var(--im-enquire);
}

/* Кнопка заявки опущена (enquireOffset lowered) и стоит по центру: ряд статусов слева доходил до неё и
   лежал под ней. На слайде с рядом статусов кнопка уходит к правому краю, как при top-right. */
@media (min-width: 1024px) {
  #app[data-enquire-offset="lowered"]:not(:has(> #slide:is([data-enquire-at="left"], [data-enquire-at="right"], [data-enquire-at="bottom"]))):has(> #slide > .filters) > #enquire-top {
    left: auto; right: calc(20px + var(--safe-right)); transform: none;
  }
}

/* statusFilter box: панель в рамке, заголовок и строки «статус (счёт)». Строки отбирают. */
.filters--box {
  flex-direction: row; flex-wrap: wrap; align-items: center; gap: 2px 22px;
  max-width: min(760px, calc(100% - 64px)); padding: 12px 20px 12px;
  border-radius: var(--r-lg); border: 1px solid var(--edge); background: var(--glass-3);
  backdrop-filter: var(--blur); -webkit-backdrop-filter: var(--blur);
}
.filters--box .filters-head { flex: 0 0 100%; margin: 0 0 2px; text-transform: capitalize; }
.filters--box .status-row { font-variant-numeric: tabular-nums; }
@media (min-width: 768px) {
  #slide:has(> .filters--box) > .table-wrap { top: 132px; }
}

/* poiCase original: названия категорий списка как записаны. */
#slide[data-poi-case="original"] .poi-list h4 {
  text-transform: none; letter-spacing: .02em; font-size: 13px; color: var(--ink-2);
}

/* logoRow developer: ряд логотипа девелопера на карточках 360. */
.pano-logos { display: flex; align-items: center; gap: 20px; margin-top: var(--space-3); }
.pano-logos img { display: block; max-height: 40px; max-width: 180px; object-fit: contain; }
#slide > .pano-logos {
  position: absolute; left: var(--col-left, 56px); bottom: calc(var(--space-5) + var(--band-modules, 0px)); z-index: 11;
}

/* ——— CP-542, партия 12: оттенок accent и свой цвет, таблица без оттенка, оттенок на стрелках и кнопках
   360, кнопка заявки контактов в стиле заявки, портрет под затемнением фото. Каждое правило стоит за
   признаком, который ставит рендер по слову пака; без слова ни одно не находит себе узла. ——— */

/* chromeTint accent берёт акцент, custom берёт chromeTintColor. Оба смешаны с основой: белый текст
   держит контраст и на светлом акценте. */
#app[data-chrome-tint="accent"] { --tc: color-mix(in srgb, var(--im-accent) 52%, var(--void)); }
#app[data-chrome-tint="custom"] { --tc: color-mix(in srgb, var(--im-tint-custom) 58%, var(--void)); }

/* Таблица юнитов остаётся на своём фоне: оттенок тянется на карточки и кнопки, в читаемую таблицу не идёт. */
#app[data-chrome-tint] .table-wrap {
  background: linear-gradient(155deg,
    color-mix(in srgb, color-mix(in srgb, var(--void) 94.6%, var(--tint)) 92%, transparent),
    color-mix(in srgb, color-mix(in srgb, var(--void) 99.17%, var(--tint)) 96%, transparent));
  border-color: color-mix(in srgb, var(--tint) 16%, transparent);
}

/* Боковые стрелки и кнопки Gallery и Floorplans на 360 берут тот же оттенок плотным стеклом. */
#app[data-chrome-tint] :is(.side-arrow, #gallery-open, .pill--gallery, .pill--floorplans) {
  background: var(--glass-3); border-color: var(--edge-strong);
}
#app[data-chrome-tint] :is(.side-arrow, #gallery-open, .pill--gallery, .pill--floorplans):hover {
  background: var(--tc);
}

/* enquireStyle filled и ring на кнопке заявки контактов: до этого стиль видела только верхняя кнопка,
   и в контактах оставался контур. */
#app[data-enquire-style="filled"] .pill--enquire {
  background: var(--im-enquire, var(--im-accent)); color: var(--on-accent, #fff);
  border-color: color-mix(in srgb, var(--im-accent) 60%, white);
  backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 10px 26px; font-size: 16px; font-weight: 500;
}
#app[data-enquire-style="ring"] .pill--enquire {
  background: var(--im-enquire, var(--im-accent)); color: var(--on-accent, #fff);
  border: 2px solid #fff; backdrop-filter: none; -webkit-backdrop-filter: none;
  padding: 9px 25px; font-size: 16px; font-weight: 500;
}
#app[data-enquire-style="filled"] .pill--enquire svg,
#app[data-enquire-style="ring"] .pill--enquire svg { display: none; }

/* imageDim: портрет контактов рисуется своим слоем на своей основе. Пока снимок не дорисован, под ним не
   видна сцена. */
#app[data-image-dim] .agent .portrait {
  position: relative; z-index: 1; background: var(--void); transform: translateZ(0);
}

/* unitSelector: подпись «Unit» и выпадающий список юнитов в ряду над планом, без боковой панели. */
.unit-select { display: inline-flex; align-items: center; gap: 10px; font-size: 14px; color: var(--ink); }
.unit-select-input {
  min-height: var(--tap-min); padding: 6px 30px 6px 14px; font: inherit; color: var(--ink);
  border: 1px solid var(--edge); border-radius: var(--r-pill); background-color: var(--glass-3);
  cursor: pointer;
}

/* actionsColumn hide и tableGroups hide: колонка Actions (десктоп, как у простой таблицы) и строка
   групп снимаются. Данные таблицы те же. */
@media (min-width: 1024px) {
  .table-wrap[data-actions="off"] :is(thead th.table-col--actions, tbody td.table-col--actions) { display: none; }
}
@media (min-width: 1024px) {
  .table-wrap[data-groups="off"] thead .table-groups { display: none; }
  .table-wrap[data-groups="off"] thead tr:not(.table-groups) th { top: 0; }
}
/* columnOrder pack: колонки сделки стоят там, где их поставил порядок инвентаря, а не у правого края,
   поэтому закрепление справа с них снято. */
.table-wrap[data-order="pack"] :is(tbody td, thead tr:not(.table-groups) th).table-col--deal:not(:first-child) {
  position: static; right: auto; box-shadow: none;
}
.table-wrap[data-order="pack"] tbody td.table-col--deal:not(:first-child) { background: none; }
.table-wrap[data-order="pack"] tbody tr:hover td.table-col--deal:not(:first-child) {
  background: color-mix(in srgb, var(--tint) 6%, transparent);
}
.table-wrap[data-order="pack"] thead .table-groups th:last-child { position: static; }

/* ——— CP-542, партия 13: tableTint (переделка в партии 14). Тело таблицы тёмное и полупрозрачное: оттенок подмешан
   в тёмную основу, фон модуля просвечивает. Шапка почти сплошная, цвета оттенка. Текст тела и шапки подобран по контрасту
   (--tt-ink, --tt-head-ink, WCAG AA к худшему фону). Ячейки статуса держат цвет статуса (--status-ink, он посчитан на
   фоне таблицы). Прижатые колонки берут сплошные двойники (--tt-*-solid): сквозь них не должна просвечивать прокрутка.
   Правила стоят за признаком data-tint, который ставит рендер по слову пака; без слова ни одно не находит себе узла. ——— */
#app .table-wrap[data-tint], #app[data-chrome-tint] .table-wrap[data-tint] {
  --cell: var(--tt-body-solid); --cell-head: var(--tt-head); --cell-hover: var(--tt-hover-solid);
  background: var(--tt-body); color: var(--tt-ink);
  border-color: color-mix(in srgb, var(--tt-ink) 22%, transparent);
}
.table-wrap[data-tint] thead th {
  background: var(--tt-head); color: var(--tt-head-ink);
  border-bottom-color: color-mix(in srgb, var(--tt-head-ink) 22%, transparent);
}
.table-wrap[data-tint] thead .table-groups th,
.table-wrap[data-tint] thead th[aria-sort] button,
.table-wrap[data-tint] thead th button:hover { color: var(--tt-head-ink); }
.table-wrap[data-tint] tbody td { color: var(--tt-ink); border-bottom-color: color-mix(in srgb, var(--tt-ink) 16%, transparent); }
.table-wrap[data-tint] tbody td.is-status { color: var(--status-ink, var(--tt-ink)); }
.table-wrap[data-tint] tbody td > svg { color: var(--tt-ink); }
.table-wrap[data-tint] tbody tr:hover td, .table-wrap[data-tint] tbody tr:focus-visible td { background: var(--tt-hover); }
.table-wrap[data-tint] .table-note { color: var(--tt-ink); }
.table-wrap[data-tint][data-order="pack"] tbody tr:hover td.table-col--deal:not(:first-child) { background: var(--tt-hover-solid); }
