/* Каркас страниц — НАШ слой поверх дизайн-системы.
 *
 * В системе есть токены и компоненты, но нет классов страничной раскладки:
 * её наборы экранов собирают вёрстку инлайновыми стилями из тех же токенов.
 * Поэтому контейнер, шапку, подвал и сетки описываем здесь — на токенах
 * системы, без собственных цветов и размеров. Всё это заменит Webflow.
 */

/* --- `hidden` обязан скрывать. Это не мелочь ---------------------------- --
 *
 * Атрибут `hidden` даёт `display:none` из ВСТРОЕННОЙ таблицы браузера, а любое
 * авторское правило её перебивает. У системы `.v-stack{display:flex}` — и
 * `<div class="v-stack" hidden>` остаётся видимым.
 *
 * Ровно так 05.08.2026 окно входа показало обе свои половины разом: вход и
 * регистрацию друг под другом. Разметка была верной, скрипт ставил `hidden`
 * правильно, а CSS его отменял.
 *
 * Правило общее и с `!important` намеренно: скрытых элементов у нас уже
 * несколько (окно входа и его половины, просмотр фотографий), и чинить каждый
 * своим селектором значит однажды забыть следующий. Так же поступают все
 * общеизвестные сбросы стилей.
 */
[hidden]{display:none !important}

/* Зона касания на мобильном — 44px, требование самой системы
   (`--tap-min`). Разбор: docs/frontend/Замер_мобильных_ширин.md */
/* Прокручиваемая колонка не сжимает детей: docs/frontend/Сжатие_в_прокручиваемой_колонке.md */
.v-modal__body>*,.v-fdrop__body>*,.v-side>*{flex-shrink:0}

/* Таблица на узком экране: docs/frontend/Таблицы_на_узком_экране.md */
.v-panel:has(>.v-table),.v-panel__body:has(>.v-table){overflow-x:auto}

@media (max-width:1023px),(pointer:coarse){
  .v-btn,.v-iconbtn,.v-fdrop__pill{min-height:var(--tap-min)}
  .v-iconbtn{min-width:var(--tap-min)}
  /* Ссылке-кнопке нужен и способ рост принять: у неё height:auto. */
  .v-btn--link{display:inline-flex;align-items:center;min-height:var(--tap-min)}
  /* ⚠ Только ИНТЕРАКТИВНЫЙ чипс: span.v-chip--sm — подпись, а не кнопка. */
  a.v-chip,button.v-chip,label.v-chip,[role=button].v-chip{min-height:var(--tap-min)}
}

/* Поле поиска: docs/frontend/Таблицы_на_узком_экране.md разд. 7 */
@media (max-width:767px){
  .v-search__input{height:var(--control-h-lg)}
}
@media (max-width:767px){
  label.v-check[data-vg="offer-check"],label.v-check[data-vg="review-name-show-row"],label.v-check[data-vg="review-name-scope-row"]{min-height:var(--tap-min);padding-block:var(--s-12)}
}

/* Список карточками: docs/frontend/Таблицы_на_узком_экране.md разд. 4a, 7 */
@media (max-width:767px){
  .vg-cardlist,.vg-cardlist tbody,.vg-cardlist tr,.vg-cardlist td{display:block}
  .vg-cardlist thead{display:none}
  .vg-cardlist tr{
    border:1px solid var(--border-default);border-radius:var(--r-dense);
    padding:var(--s-12);margin-bottom:var(--s-12);
  }
  .vg-cardlist td{border:0;height:auto;padding:var(--s-4) 0}
  .vg-cardlist td[data-label]::before{
    content:attr(data-label);display:block;
    font-size:12.5px;text-transform:uppercase;letter-spacing:.04em;
    color:var(--text-faint);
  }
  .vg-cardlist td.strong{font-size:16px;padding-bottom:var(--s-8)}
  .vg-cardlist td:last-child{padding-top:var(--s-12)}
  .vg-cardlist td:last-child .v-btn{width:100%}
  .vg-cardlist .v-status{white-space:normal;max-width:100%}
}


/* --- Каркас --- */
.vg-container{max-width:var(--grid-max);margin:0 auto;padding:0 var(--grid-margin)}
/* ⚠ ТОЛЬКО `padding-block`. Короткая запись `padding:24px 0 80px` обнуляла
   боковые поля: `.vg-main` висит на том же элементе, что `.v-container`
   системы, а тот задаёт поля через `padding-inline`. Наш слой грузится
   позже — и затирал их нулями на ВСЕХ ширинах. На широком мониторе это
   пряталось за центрированием (контейнер уже 1680px), а на телефоне
   содержимое упиралось в края экрана.
   Урок общий: свойство, которое системный класс задаёт длинной записью,
   нашим классом можно трогать только длинной. Стережёт
   `own-css-shorthand.spec.ts`. */
.vg-main{padding-block:var(--s-24) var(--s-80)}

.vg-header{background:var(--surface-card);border-bottom:1px solid var(--border-subtle)}
.vg-header__in{display:flex;align-items:center;gap:var(--s-16);min-height:var(--header-h);flex-wrap:wrap}
.vg-logo{font:var(--t-h3-w) var(--t-h3-size)/var(--t-h3-lh) var(--font-text);letter-spacing:var(--t-h3-ls);color:var(--text-primary);text-decoration:none;white-space:nowrap}
.vg-logo:hover{text-decoration:none}
.vg-logo b{color:var(--text-accent)}
.vg-nav{display:flex;gap:var(--s-12);flex-wrap:wrap}
.vg-nav a{font:var(--t-body-sm-w) var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls);color:var(--text-secondary)}
.vg-header__right{margin-left:auto;display:flex;align-items:center;gap:var(--s-12)}
@media (max-width:767px){.vg-wide-only{display:none}}
@media (max-width:767px){.vg-cabinet-name{display:inline-block;max-width:6em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:bottom}}
@media (min-width:768px) and (max-width:1023px){.vg-cabinet-name{display:none}}

.vg-footer{background:var(--surface-sunken);border-top:1px solid var(--border-subtle);margin-top:var(--s-80)}
.vg-footer__in{display:flex;gap:var(--s-16);flex-wrap:wrap;padding:var(--s-24) 0;font:var(--t-body-sm-w) var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls);color:var(--text-muted)}

/* --- Сетки --- */
.vg-grid{display:grid;gap:var(--block-gap);grid-template-columns:repeat(3,1fr)}
.vg-tiles{display:grid;gap:var(--block-gap);grid-template-columns:repeat(auto-fill,minmax(220px,1fr));list-style:none;padding:0;margin:0}
.vg-two-col{display:grid;gap:var(--s-32);grid-template-columns:var(--sidebar-w) 1fr;align-items:start}

/* --- Мелочи страниц --- */
.vg-h1{font:var(--t-h1-w) var(--t-h1-size)/var(--t-h1-lh) var(--font-text);letter-spacing:var(--t-h1-ls);letter-spacing:-.015em;margin:var(--s-12) 0 var(--s-16)}

/* Блок заголовка: заголовок и подзаголовок-счётчик.

   Числа — замером шаблона (`afisha-section`, `catalog-section`): заголовок
   отбит сверху на 12, подзаголовок стоит в 8 под ним, а весь блок — в 16 от
   того, что идёт следом. Нижнее поле переезжает с заголовка на блок: между
   ними 8, а не 16. */
.vg-titlebox{margin-bottom:var(--s-16)}

/* ⚠ ПЕРЕД РЕКЛАМНОЙ ПОЛОСОЙ ПОЛЕ НЕ НУЖНО: она держит свои 24 сама
   (`.vg-adband`), и общее с полем блока давало 40 против 24 у шаблона —
   замер 14.08.2026. У списка раздела за заголовком идёт ряд рубрик, своего
   поля у него нет, и там 16 остаются. */
.vg-titlebox--tight{margin-bottom:0}
.vg-titlebox .vg-h1{margin-bottom:0}
.vg-subcount{margin:var(--s-8) 0 0}

/* ⚠ НА ТЕЛЕФОНЕ СЧЁТЧИК СКРЫТ (решение владельца 14.08.2026, вечер).

   32 точки первого экрана он занимал, а число повторяет строкой «Найдено: N»,
   которая всё равно стоит прямо над выдачей: на узком экране это два одинаковых
   ответа на один вопрос, и верхний из них дороже. На 768 и шире подзаголовок
   остаётся — там места хватает, и он стоит там же, где у шаблона. */
@media (max-width:767px){
  .vg-subcount{display:none}
}
.vg-h2{font:var(--t-h3-w) var(--t-h3-size)/var(--t-h3-lh) var(--font-text);letter-spacing:var(--t-h3-ls);margin:var(--s-32) 0 var(--s-12)}
.vg-muted,.muted{color:var(--text-muted);font:var(--t-body-sm-w) var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls)}
/* .muted осталась от голого техшаблона и стоит в 36 местах — сохраняем как
   синоним, чтобы правка оформления не превращалась в правку всех шаблонов. */
.vg-row{display:flex;gap:var(--s-8);align-items:center;flex-wrap:wrap}
.vg-stack{display:flex;flex-direction:column;gap:var(--s-12)}
.vg-gallery{display:grid;gap:var(--s-8);grid-template-columns:repeat(auto-fill,minmax(200px,1fr));list-style:none;padding:0;margin:0}
.vg-list-reset{list-style:none;padding:0;margin:0}

/* Лид материала. У системы есть токены `--t-lead-*`, но нет класса под них:
   её наборы задают лид инлайном. Класс стоял в трёх шаблонах и не был описан
   НИГДЕ — то есть лид рисовался обычным абзацем (найдено 05.08.2026 разбором
   собственных классов). */
.vg-lead{font:var(--t-lead-w) var(--t-lead-size)/var(--t-lead-lh) var(--font-text);
  letter-spacing:var(--t-lead-ls);color:var(--text-secondary);margin:0 0 var(--s-16)}

/* Текст только для скринридера: растянутая ссылка на плитке афиши не имеет
   видимого текста, и без доступного имени она читается как «ссылка». Класс
   тоже стоял в шаблонах неописанным — то есть название события показывалось
   ВСЕМ поверх постера. */
.vg-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;
  overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}

/* Рекламные места видимы в разметке, но пустые: контейнер не должен
   схлопываться и создавать иллюзию, что блока нет (Модуль_05). */
[data-vg-slot]{min-height:0}

/* Вся карточка в списке — одна цель для клика (замечание владельца
   30.07.2026: «нужно нажимать не на карточке, а на названии»).
   Приём — растянутая ссылка: сама ссылка остаётся на названии (у неё есть
   текст, значит и доступное имя), а ::after накрывает плитку целиком.
   Так карточка не превращается в <a> — а это важно, потому что по
   дизайн-системе внутрь придут кнопки «Показать телефон», «Записаться» и
   «в избранное», и интерактивные элементы внутри ссылки были бы невалидной
   вёрсткой. Всё, что кликается само, обязано лежать ВЫШЕ накладки. */
.vg-hit{position:relative}
/* Растягивается ссылка, помеченная `data-vg-link="stretch"`, либо ссылка
   карточки списка. Второй случай был первым и остаётся: разметку витрины
   ради обобщения не трогаем. */
.vg-hit [data-vg="card-link"]::after,
.vg-hit [data-vg-link="stretch"]::after{content:"";position:absolute;inset:0;z-index:1}
.vg-hit button,.vg-hit input,.vg-hit label,.vg-hit textarea,
.vg-hit a:not([data-vg="card-link"]):not([data-vg-link="stretch"]){position:relative;z-index:2}
/* Клик по всей плитке должен быть заметен курсором и наведением, иначе
   увеличенная область срабатывания остаётся невидимой для пользователя. */
.vg-hit{cursor:pointer;transition:box-shadow var(--dur-base) var(--ease-out),border-color var(--dur-base) var(--ease-out)}
/* ⚠ Только НЕ карточки витрины: у `.v-card` своё наведение в системе
   (`--shadow-3`, подъём −2), и наша тень его перебивала — специфичность та
   же, а наш файл грузится позже. Замер 14.08.2026: было 0 1px 2px вместо
   0 10px 28px. */
.vg-hit:not(.v-card):hover{border-color:var(--border-strong);box-shadow:var(--shadow-1)}

/* ⚠ Заголовок плитки под курсором НЕ синеет. Система красит его правилом
   `.v-card a.v-card__title:hover`, рассчитанным на то, что ссылка — сам
   заголовок. У нас кликабельна вся плитка (растянутая `::after` у ссылки
   названия), поэтому её `:hover` срабатывает при наведении в любую точку, и
   название синело там, где у шаблона оно остаётся чернильным. */
.vg-hit.v-card a.v-card__title:hover{color:var(--text-primary)}

/* Контейнер карты (только страница объекта). Высота здесь не оформление, а
   работоспособность: контейнер нулевой высоты Яндекс-карта заполнит нулём
   пикселей, и блок будет «пустым» без всякой ошибки. */
.vg-map{height:320px;border-radius:var(--r-media);overflow:hidden;background:var(--surface-sunken)}
/* Отказ загрузки карты не должен выглядеть как пустая рамка: ссылка наружу
   лучше молчания (JS ставит её сам, см. card.njk). */
.vg-map[data-vg-map-state="unavailable"]{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--s-6)}
.vg-map__note{font:var(--t-body-sm-w) var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls);color:var(--text-muted);margin:0}
/* Карта модератора выше: он по ней работает, а не смотрит. */
.vg-map--admin{height:420px;margin-bottom:var(--s-12)}

/* --- Форма карточки: атрибуты группами (замечание владельца 30.07.2026) --- */
/* Флаги «да/нет» — сеткой, а не столбиком по одному в строке: у «Ресторанов»
   таких атрибутов 15, и колонка из них не читается. */
.vg-attr{border:0;padding:0;margin:0 0 var(--s-12)}
.vg-attr legend{font:500 var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls);color:var(--text-secondary);padding:0 0 var(--s-6)}
.vg-req{color:var(--red-700)}
.vg-checks{display:grid;gap:var(--s-4) var(--s-12);
  grid-template-columns:repeat(auto-fill,minmax(240px,1fr));max-height:320px;overflow:auto}
/* Состояние автосохранения: спокойное по умолчанию, заметное при сбое. */
[data-vg="autosave-state"][data-vg-state="error"]{color:var(--red-700);font-weight:600}
.vg-map__fallback{font:var(--t-body-sm-w) var(--t-body-sm-size)/var(--t-body-sm-lh) var(--font-text);letter-spacing:var(--t-body-sm-ls);color:var(--text-accent)}
/* «Показать маршрут» — под картой, но живёт и без неё: ссылке наружу ключ
   JS API не нужен (срез 7a). */
.vg-map__route{margin:var(--s-6) 0 var(--s-12)}
/* Кнопка «В избранное» — форма, поэтому по умолчанию заняла бы всю строку
   блочным элементом и оторвала заголовок от подзаголовка (7b). */
.vg-fav{display:inline-block;margin:0 0 var(--s-8)}

/* --- Мобильный адаптив (система требует его с первого дня) --- */
@media (max-width:1024px){
  .vg-two-col{grid-template-columns:1fr}
  .vg-grid{grid-template-columns:repeat(2,1fr)}
}
@media (max-width:768px){
  .vg-container{padding:0 var(--grid-margin-m)}
  .vg-grid{grid-template-columns:1fr}
  .vg-header__in{min-height:var(--header-h-m)}
}

/* --- Очередь модерации: цель плиткой (макет владельца 05.08.2026) ---------- */
/* В системе есть строка таблицы (`v-queue__row`), но не плитка: у половины
   типов внутрь нужно уместить форму решения, текст отзыва и фотографии, а это
   уже не строка. Плитка — наш слой, на токенах системы. */
.vg-queuecard{display:flex;align-items:flex-start;gap:var(--s-16);
  background:var(--surface-page);border:1px solid var(--border-default);
  border-left:3px solid var(--border-default);border-radius:var(--r-dense);
  box-shadow:var(--shadow-1);padding:var(--s-14,14px) var(--s-16);
  transition:box-shadow var(--dur-base) var(--ease-out),border-color var(--dur-base) var(--ease-out)}
/* Цель, залежавшаяся дольше суток, помечена и полосой, а не только цифрой:
   в списке из десятка целей цвет читается раньше текста. */
.vg-queuecard.is-old{border-left-color:var(--amber-500)}
.vg-queuecard.vg-hit:hover{border-color:var(--blue-600);box-shadow:var(--shadow-2)}
.vg-queuecard__main{min-width:0;flex:1;display:flex;flex-direction:column;gap:var(--s-10)}
.vg-queuecard__title{font-family:var(--font-display);font-size:17px;font-weight:var(--t-display-sm-w);font-stretch:var(--t-display-sm-stretch);
  letter-spacing:-.015em;color:var(--text-primary)}
.vg-queuecard__body{margin:0;font-size:14px;color:var(--text-secondary)}
/* Форма решения отделена от сведений о цели: без черты кнопки читаются как
   часть текста отзыва. */
.vg-queuecard__decide{padding-top:var(--s-10);border-top:1px solid var(--border-subtle)}
.vg-queuecard__open{display:flex;align-items:center;gap:var(--s-6);white-space:nowrap;
  color:var(--text-brand);font-size:13px;font-weight:600;padding-top:var(--s-4)}

/* Девять вкладок в один ряд не помещаются: у системы `.v-tabs` без переноса. */
.vg-tabs--wrap{flex-wrap:wrap;gap:var(--s-16) var(--s-20)}

/* --- Экран проверки: читаем слева, решаем справа (макет 05.08.2026) -------- */
/* Раскладки в два столбца с липкой колонкой у системы нет — её наборы задают
   такую сетку инлайном. Ширина правой колонки 380px взята из макета: в неё
   помещается плашка состояния в две строки и кнопки решения в один ряд. */
.vg-checkgrid{display:grid;grid-template-columns:minmax(0,1fr) 380px;
  gap:var(--s-24);align-items:start}
/* Липкой колонка держится ниже верхней панели кабинета (56px) плюс отступ. */
.vg-checkgrid__side{position:sticky;top:calc(56px + var(--s-24))}
/* На узком экране колонка решения уходит вниз и перестаёт липнуть: липкий
   блок высотой в пол-экрана съел бы страницу. */
@media (max-width:1199px){
  .vg-checkgrid{grid-template-columns:minmax(0,1fr)}
  .vg-checkgrid__side{position:static}
}

/* Последняя строка списка полей — без черты: она бы отделяла панель от её же
   нижнего края. */
.vg-formrow--last{border-bottom:0}

/* --- Фотографии на экране проверки: мозаика и полноэкранный просмотр ------- */
/* Правка макета владельца 05.08.2026. Главный снимок крупно, остальные
   ячейками: модератор решает по фотографиям, и «то ли это заведение» по
   миниатюре 140px не видно. */
.vg-photogrid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));
  grid-auto-rows:148px;gap:var(--s-8)}
.vg-photogrid__item{all:unset;position:relative;cursor:zoom-in;overflow:hidden;
  border-radius:var(--r-media);background:var(--ink-100);
  transition:transform var(--dur-base) var(--ease-out)}
.vg-photogrid__item:hover{transform:scale(1.02)}
.vg-photogrid__item:focus-visible{box-shadow:var(--focus-ring)}
.vg-photogrid__item img{width:100%;height:100%;object-fit:cover;display:block}
/* Главный снимок — четверть сетки: он же обложка на витрине. */
.vg-photogrid__item--cover{grid-column:span 2;grid-row:span 2}
.vg-photogrid__item--cover:hover{transform:scale(1.01)}
.vg-photogrid__badge{position:absolute;left:10px;bottom:10px;padding:2px 8px;
  border-radius:var(--r-pill);background:rgba(19,26,33,.62);color:#fff;
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase}
@media (max-width:1023px){
  .vg-photogrid{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (max-width:640px){
  .vg-photogrid{grid-template-columns:repeat(2,minmax(0,1fr))}
  .vg-photogrid__item--cover{grid-column:span 2;grid-row:span 1}
}

/* Полноэкранный просмотр. Разметку отдаёт сервер, скрипт только показывает;
   скрывает атрибут `hidden` — общее правило в начале файла. */
.vg-lightbox{position:fixed;inset:0;z-index:60;background:rgba(19,26,33,.86);
  backdrop-filter:blur(10px);display:flex;flex-direction:column;align-items:center;
  justify-content:center;gap:var(--s-16);padding:var(--s-32)}
.vg-lightbox__close{all:unset;position:absolute;top:20px;right:24px;display:flex;
  align-items:center;gap:var(--s-8);height:44px;padding:0 var(--s-16);
  border-radius:var(--r-pill);background:rgba(255,255,255,.14);color:#fff;
  font-size:14px;font-weight:600;cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out)}
.vg-lightbox__close:hover{background:rgba(255,255,255,.26)}
.vg-lightbox__stage{display:flex;align-items:center;gap:var(--s-20);width:100%;
  justify-content:center}
.vg-lightbox__image{width:min(1100px,78vw);height:74vh;object-fit:contain;
  border-radius:var(--r-media);box-shadow:var(--shadow-4)}
.vg-lightbox__nav{all:unset;flex:none;width:48px;height:48px;display:grid;
  place-items:center;border-radius:var(--r-circle);background:rgba(255,255,255,.14);
  color:#fff;font-size:28px;line-height:1;cursor:pointer;
  transition:background var(--dur-fast) var(--ease-out)}
.vg-lightbox__nav:hover{background:rgba(255,255,255,.26)}
.vg-lightbox__foot{display:flex;align-items:center;gap:var(--s-16);margin:0;
  color:rgba(255,255,255,.82);font-size:13px}
/* Фон не прокручивается, пока открыт просмотр. */
.vg-noscroll{overflow:hidden}
/* Окно выбора города: docs/decisions/2026-10-07_Выбор_города_окном.md */
.vg-citydialog:not([hidden])~.v-consent{z-index:101}
@media (max-width:767px){.vg-citydialog{padding:0;justify-items:stretch;align-items:stretch}.vg-citydialog .v-modal{margin:0;max-width:none;max-height:none;height:100%;border-radius:0}}
@media (min-width:768px){.vg-citydialog .v-modal{height:calc(100dvh - var(--s-48,48px))}}

/* Плашка состояния не переносится по словам (правка макета 05.08.2026):
   «на модерации» в две строки ломает ряд осей. */
.v-status{white-space:nowrap}
.v-cardstate:not(.v-cardstate--row){gap:var(--s-16)}
.v-cardstate__axis{display:flex;flex-direction:column;gap:var(--s-6);align-items:flex-start}
.v-cardstate__axis>span:last-child{display:flex;flex-direction:column;gap:var(--s-6);align-items:flex-start;max-width:100%}
.v-cardstate__axis .v-status{white-space:normal;max-width:100%}

/* --- Отметка о проверке документа (M37) ----------------------------------- */
/* Живёт внутри пункта чек-листа, поэтому мельче его текста: это подпись под
   выводом, а не второй вывод. */
.vg-mark{display:block;margin-top:var(--s-4);font-size:12.5px;color:var(--text-muted)}
/* Проверка, относящаяся к ПРЕЖНЕМУ номеру: видна, но не читается как
   подтверждение. */
.vg-mark--stale{color:var(--text-faint);font-style:italic}
.vg-markform{display:flex;flex-direction:column;gap:var(--s-6);margin-top:var(--s-8)}

/* --- Страницы входа: то же окно, только не поверх страницы --------------- */
/* `v-auth` у системы — карточка внутри модального окна; на самой странице
   вокруг неё нужен вертикальный отступ, иначе она прилипает к шапке. */
.vg-authpage{padding-block:var(--s-40);margin-inline:auto}
.vg-authpage .v-modal{margin-inline:auto}

/* --- Реклама: текстовый блок --------------------------------------------- */
/* Дизайн-система рисует место картинкой (`v-ad__media`), а текстовый блок
   (форма `text_block` ОРД — первое, чем портал может прорекламировать себя)
   класса у неё не имеет: её наборы экранов такого формата не показывают.
   Без своего правила текст лёг бы в угол места с заданной пропорцией, поэтому
   правило заведено здесь, в НАШЕМ слое и на токенах системы. */
.vg-ad__text{display:flex;align-items:center;justify-content:center;height:100%;padding:var(--s-16);text-align:center;color:var(--text-primary);font:var(--t-body-w) var(--t-body-size)/var(--t-body-lh) var(--font-text);letter-spacing:var(--t-body-ls)}


/* ─────────────────────────────────────────────────────────────────────────
   ДОБАВКИ К ДИЗАЙН-СИСТЕМЕ, КОТОРЫХ В НЕЙ НЕТ

   Здесь и только здесь. До 08.08.2026 полоса загрузки была дописана прямо в
   `ds2/css/feedback.css` — то есть в файл системы. Поставка новой версии
   системы 08.08.2026 этот файл заменила целиком, и полоса исчезла вместе с
   правкой; поймал статический тест классов, а не глаз.

   Правило: файлы в `public/ds2/` — чужие, они переписываются поставкой
   целиком. Всё своё живёт в `public/css/`.
   ───────────────────────────────────────────────────────────────────────── */

/* Полоса выполнения. Два состояния: с известной долей (загрузка, ширина по
   проценту) и без неё — конвертация на сервере, сколько осталось, знает
   только он. Нужна загрузчику фотографий карточки и отзыва. */
.vg-progress{height:6px;border-radius:var(--r-pill);background:var(--surface-sunken);overflow:hidden}
.vg-progress__bar{height:100%;width:0;border-radius:var(--r-pill);background:var(--surface-brand);transition:width var(--dur-base) var(--ease-out)}
.vg-progress--indeterminate .vg-progress__bar{width:40%;animation:v-slide 1.1s var(--ease-out) infinite}
@keyframes v-slide{0%{transform:translateX(-100%)}100%{transform:translateX(250%)}}

/* Акцентная иконка. `CitySelect.jsx` красит булавку выбора города в
   `--blue-600` инлайновым стилем; в макрос иконки инлайн не передать, поэтому
   тот же цвет — классом. */
.vg-icon-brand{color:var(--blue-600)}

/* Меню объявления работает БЕЗ скрипта: три точки — это `<summary>`, окно —
   `<details>`. Раскрывает браузер, поэтому `erid` доступен при отключённом JS,
   а это требование закона, а не удобство.

   Свой класс нужен ровно для одного: убрать стрелку-треугольник, которую
   браузер рисует у `<summary>` по умолчанию. Позиционирование остаётся за
   системой (`.v-ad__dots`, `.v-admenu` абсолютны относительно `.v-adwrap`),
   поэтому `position` тут НЕ задаётся — иначе окно отсчитывалось бы от
   `<details>` и уехало бы под баннер. */
.vg-admenu>summary{list-style:none}
.vg-admenu>summary::-webkit-details-marker{display:none}
.vg-admenu>summary::marker{content:""}
.v-ad__label{top:var(--s-8);left:var(--s-8);right:auto;bottom:auto}
.v-ad__dots{border-radius:50%}
.vg-admenu>summary:focus-visible{outline:2px solid var(--border-focus);outline-offset:2px}
.v-topbar__title{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.v-topbar .v-role,.v-topbar>form{flex:none}

/* Плитка меню, внутри которой ЦЕЛИКОМ картинка владельца, а не текст.

   Своим классом и в нашем слое, а не правкой `.v-menutile` в `ds2/`: файлы
   системы переписываются поставкой целиком, и правка в них исчезает молча —
   08.08.2026 так уже пропали три класса.

   Что меняется против системной плитки:
   — `padding` и `gap` в ноль: у картинки свои поля нарисованы внутри, и
     системный отступ читался как двойная рамка;
   — `overflow:hidden` — чтобы углы изображения шли по скруглению рамки;
   — `line-height:0` — иначе строчный бокс добавляет снизу пару пикселей,
     и отступ выглядит неубранным.

   Ширина — 275px, это 60% от исходных 458px (решение владельца: половина
   оказалась мелковата). Считается именно от НАТУРАЛЬНОГО размера картинки, а
   не от ширины колонки: изображение растровое, и показывать его крупнее
   собственных 458px значит показывать мыло. `max-width` оставляет ему
   сжиматься на узком экране. */
.vg-menutile--image{padding:0;gap:0;display:inline-block;line-height:0;overflow:hidden}
.vg-menutile--image img{display:block;width:275px;max-width:100%;height:auto}

/* ─── ДОСТУПНОСТЬ: две ступени приглушённого текста темнее ───────────────
   Решение владельца 11.08.2026, после замера браузером
   (`scripts/contrast.js`, разбор — docs/decisions/
   2026-08-11_Контраст_текста_и_доступность.md).

   Палитра системы не дотягивала до WCAG 2.1 AA (4.5:1 обычному тексту):

   | Токен | Было | На белом | На сером #F4F6F8 |
   |---|---|---|---|
   | `--text-faint` | `--ink-400` #94A1B0 | 2.63 | 2.43 |
   | `--text-muted` | `--ink-500` #6B7A8B | 4.39 | 4.05 |

   Стало: 10.08 и 6.85 на белом, 9.30 и 6.32 на сером. Два уровня
   приглушённости СОХРАНЕНЫ — они нужны системе; обе ступени просто сдвинуты
   вниз по той же шкале, новых цветов не заведено.

   Слова владельца, ради которых это сделано: портал курортный, его смотрят на
   улице и на пляже при засвеченном экране, и там разница между 2.63:1 и 10:1
   — это «читается / не читается», а не оттенок. Макет рисовался на хорошем
   мониторе в помещении.

   ⚠ ПОЧЕМУ ЗДЕСЬ, А НЕ В `ds2/tokens/colors.css`. Поставка дизайн-системы
   переписывает свой каталог целиком: 08.08.2026 так молча пропали
   `v-progress`, `v-objhero__blur` и `v-objgrid__zone`. Всё своё живёт в
   `public/css/`, и этот файл подключается ПОСЛЕ `ds2/styles.css` — иначе
   переопределение не сработало бы вовсе.

   Заслон — `test/contrast-tokens.spec.ts`. */
:root{
  --text-muted:var(--ink-700);
  --text-faint:var(--ink-600);
}

/* ─── Страница объекта: правки поверх системы (10.08.2026) ─────────────── */

@media (max-width:767px){
  .vg-objgrid--film{row-gap:var(--s-40)}
  .vg-objgrid--film > .v-stack:first-child{display:contents}
  .vg-objgrid--film > .v-stack:first-child .vg-film-lead ~ *{order:2}
  .vg-objgrid--film > .v-objgrid__rail ~ *{order:2}
}

/* Обложка объекта — вариант А (решение владельца 17.10.2026, п. 1): до правого края колонки баннеров, колонка фото
   по пропорции снимка, горизонтальный снимок не обрезается. Разбор: docs/frontend/Карточка_фильма.md */
@media (min-width:901px){
  .vg-objhero--fixed{height:380px;grid-template-rows:minmax(0,1fr);
    grid-template-columns:minmax(340px,1fr) fit-content(calc(100% - 340px))}
  .vg-objhero--fixed > *{min-height:0}
  .vg-objhero--fixed .v-objhero__photo{overflow:hidden}
  .vg-objhero--fixed .v-objhero__photo img{width:auto;max-width:100%;height:100%;object-fit:cover}
}
@media (min-width:901px) and (max-width:1280px){
  .vg-objhero--fixed{height:320px}
}
/* Обложка → содержимое: +16, как у шаблона PlaceRestaurant (слово владельца 20.10.2026). */
.vg-objhero--fixed{margin-bottom:var(--s-16)}
@media (max-width:900px){
  .vg-objhero--fixed .v-objhero__photo{aspect-ratio:auto}
  .vg-objhero--fixed .v-objhero__photo img{height:auto;max-height:75vh;object-fit:contain}
}

/* Карточка фильма: docs/frontend/Карточка_фильма.md */
.vg-objhero__go{align-self:flex-start;margin-top:var(--s-8)}
.vg-film-sec,[data-vg=film-media],.vg-film-lead,.vg-schedule,[data-vg=film-about]{display:flex;flex-direction:column;gap:var(--s-14)}
.vg-gallery__trailer a{position:relative;display:block;width:100%;height:100%;background:var(--ink-900);color:#fff}
.vg-gallery__trailer img{opacity:.5}
.vg-gallery__play{position:absolute;inset:0;display:grid;place-items:center;font-weight:600;font-size:15px}
.vg-film-about{display:grid;grid-template-columns:200px minmax(0,1fr);gap:var(--s-24);align-items:start}
.vg-film-about__poster{position:relative;aspect-ratio:2/3;border-radius:var(--r-md);overflow:hidden;background:var(--ink-100)}
.vg-film-about__poster img{display:block;width:100%;height:100%;object-fit:cover}
.vg-film-about__ph{position:absolute;inset:0;display:grid;place-items:center;color:var(--text-faint)}
.vg-schedule__day{display:flex;flex-direction:column}
.vg-schedule__dayname{margin:var(--s-8) 0 0;font-size:13.5px;font-weight:600;color:var(--text-muted)}
.vg-schedule.is-tabbed .vg-schedule__dayname{display:none}
.vg-schedule__cinema{display:grid;grid-template-columns:minmax(0,240px) minmax(0,1fr);gap:var(--s-12) var(--s-24);
  padding-block:var(--s-16);border-bottom:1px solid var(--border-subtle)}
.vg-schedule__place{display:flex;flex-direction:column;gap:var(--s-4);min-width:0}
.vg-showtimes{display:flex;flex-wrap:wrap;gap:var(--s-8);align-content:flex-start}
.vg-showtime{display:inline-flex;flex-direction:column;justify-content:center;min-width:80px;min-height:var(--tap-min,44px);
  padding:var(--s-6) var(--s-12);border:1px solid var(--border-default);border-radius:var(--r-sm);background:var(--surface-page);
  color:var(--text-primary);line-height:18px;text-decoration:none;
  transition:border-color var(--dur-fast) var(--ease-out),background var(--dur-fast) var(--ease-out)}
a.vg-showtime:hover{border-color:var(--border-strong);background:var(--surface-hover);color:var(--text-primary);text-decoration:none}
.vg-showtime__time{font-size:16px;font-weight:600;font-variant-numeric:tabular-nums}
.vg-showtime__meta{font-size:12.5px;color:var(--text-muted)}
.vg-showtime--off{background:var(--surface-sunken);color:var(--text-faint)}
.vg-facts{display:grid;grid-template-columns:minmax(0,200px) minmax(0,1fr);margin:0;border-top:1px solid var(--border-subtle)}
.vg-facts__row{display:contents}
.vg-facts dt,.vg-facts dd{margin:0;padding-block:var(--s-10);border-bottom:1px solid var(--border-subtle)}
.vg-facts dt{padding-right:var(--s-16)}
.vg-facts .v-spec__k{margin-bottom:0;font-size:14px}
.vg-cast__item{position:relative;display:inline-block}
.vg-cast__name{padding:0;border:0;background:none;font:inherit;color:var(--text-link);cursor:pointer;
  text-decoration:underline dotted;text-underline-offset:3px}
.vg-cast__name:hover,.vg-cast__name[aria-expanded=true]{color:var(--text-link-hover)}
.vg-cast__card{position:absolute;left:0;top:calc(100% + 6px);z-index:30;display:flex;flex-direction:column;gap:var(--s-6);
  width:138px;padding:var(--s-8);border:1px solid var(--border-subtle);border-radius:var(--r-md);background:var(--surface-page);
  box-shadow:var(--shadow-2)}
.vg-cast__photo{display:block;width:120px;height:180px;object-fit:cover;border-radius:var(--r-sm);background:var(--ink-100)}
.vg-cast__caption{font-size:12.5px;line-height:16px;color:var(--text-primary)}
@media (min-width:768px){.vg-schedule{--grid-margin:0px}}
@media (max-width:767px){.vg-schedule__cinema{grid-template-columns:minmax(0,1fr)}}
@media (max-width:600px){
  .vg-film-about{grid-template-columns:112px minmax(0,1fr);gap:var(--s-16)}
  .vg-facts{grid-template-columns:minmax(0,38%) minmax(0,1fr)}
}

/* Панель обложки материала — размытое продолжение фотографии.
   Разбор: docs/frontend/Панель_обложки_материала.md */
.vg-objhero--photo .v-objhero__panel{position:relative;overflow:hidden;
  background-color:var(--ink-900)}
.vg-objhero--photo .v-objhero__panel .vg-objhero__blur{position:absolute;inset:0;
  width:100%;height:100%;object-fit:cover;filter:blur(40px) saturate(1.2);transform:scale(1.3)}
.vg-objhero--photo .v-objhero__panel .vg-objhero__scrim{position:absolute;inset:0;
  background:var(--scrim-full)}
.vg-objhero--photo .v-objhero__panel > *:not(.vg-objhero__blur):not(.vg-objhero__scrim){position:relative}

/* На узком экране система переворачивает панель в светлую
   (`background:var(--surface-page)`, тёмный текст) и ставит её ПОД фотографию.
   Размытый снимок с затемнением дал бы там тёмное по тёмному. */
@media (max-width:900px){
  .vg-objhero--photo .v-objhero__panel .vg-objhero__blur,
  .vg-objhero--photo .v-objhero__panel .vg-objhero__scrim{display:none}
  .vg-objhero--photo .v-objhero__panel{background-color:var(--surface-page)}

  /* ⚠ ДЕФЕКТ САМОЙ СИСТЕМЫ, чинить нам. Переворачивая панель в светлую, её
     мобильное правило (`ds2/css/data.css`, блок `max-width:900px`)
     перекрашивает только `__panel`, `__name` и `__addr`. Двум подписям цвет
     задан жёстко под ТЁМНУЮ панель, и на белом фоне они исчезают совсем:

       .v-objhero__rubric — rgba(255,255,255,.62), контраст 1:1;
       .v-objhero__open   — var(--green-50) #E7F5EF, контраст 1.12:1.

     На живой странице это выглядело так: над названием висела одинокая точка
     (разделитель рубрики и среднего чека — единственное, что там красилось), а
     «Откроется в 10:00» проступало еле-еле. То есть рубрика и средний чек,
     который владелец отдельно просил вынести в обложку, на телефоне пропадали.

     Измерено браузером 11.08.2026, заслон — `hero-contrast.e2e-spec.ts`.
     Это ровно тот класс, что разобран в решении «Система ждёт компонент, а у
     нас один шаблон»: у компонента на узком экране РАЗМЕТКА другая, и до
     этих подписей мобильное правило системы просто не доходит. */
  .vg-objhero--photo .v-objhero__panel .v-objhero__rubric{color:var(--text-secondary)}
  .vg-objhero--photo .v-objhero__panel .v-objhero__open{color:var(--green-700)}
}

/* ТОЧКА-РАЗДЕЛИТЕЛЬ И ДАТА ОТЗЫВА — неразрывной парой.

   `.v-review__head` у системы — флекс с переносом, и точка лежит в нём
   отдельным элементом рядом с датой. Когда имя автора с датой не помещаются в
   строку (на телефоне — всегда), дата переносится, а точка остаётся висеть в
   конце предыдущей строки. Обёртка переносится целиком, поэтому подбирать
   контрольную ширину не нужно: работает при любом имени и любой ширине.
   Отступ повторяет `gap` шапки — пара выглядит так же, как выглядела бы без
   обёртки. */
.vg-review__when{display:inline-flex;align-items:center;gap:var(--s-8)}

/* ЗАГЛУШКА ВМЕСТО ОБЛОЖКИ материала.

   `ArticleCard.jsx` при отсутствии картинки рисует значок и центрирует его
   ВСТРОЕННЫМ стилем (`position:absolute; inset:0; display:grid;
   place-items:center; color:var(--ink-400)`). Встроенные стили в CSS системы
   не попадают вовсе, поэтому своего класса у этой заглушки там нет.

   ⚠ ЗДЕСЬ БЫЛ ЧУЖОЙ КЛАСС. До 11.08.2026 на этом месте стоял
   `.v-empty__art` — но это art ПУСТОГО СОСТОЯНИЯ: круг 64×64 с фирменной
   заливкой, рассчитанный на середину большого блока «здесь пока ничего нет».
   В маленьком месте строчной карточки он вёл себя как посторонний предмет:
   не тянулся на всю ширину и вставал не по центру. Похожее имя — не то же
   назначение.

   Цвет взят из компонента дословно (`--ink-400`), а не из `--text-faint`:
   это украшение, а не текст, и требование контраста к нему не относится —
   иначе оно потемнело бы вместе с приглушённым текстом 11.08.2026. */
.vg-card__noimg{
  position:absolute;
  inset:0;
  display:grid;
  place-items:center;
  color:var(--ink-400);
}

/* Встроенные рекламные места на узком экране — 3:1 до 1023.
   Разбор: docs/frontend/Рекламные_форматы_на_узком_экране.md */
/* ⚠ ПАРА `wide` И `billboard` НА ТЕЛЕФОНЕ СОВПАДАЕТ НАМЕРЕННО — не разводить.
   Решение владельца 11.09.2026, после попытки развести их и отката.

   Соблазн понятен: на десктопе это разные товары с разной ценой (1216×190 у
   билборда против 1216×114 у полосы), а на телефоне оба дают 358×119, и
   рекламодателю не видно, за что он платит дороже. Пробовали 24:5 — это 75
   точек.

   Отказались по двум своим же решениям. Полоса в 119 точек — ПРЕДЕЛ
   ПРИГОДНОСТИ: 34 и 68 мы уже дважды признали непригодными («не влезает ни
   изображение, ни текст»), а 20.08.2026 замер показал, что и 119 на телефоне
   даёт 14,1 % экрана против 23,8–34,3 % на десктопе. Уйти ниже — вернуть
   полоску, которую сами же чинили. Для `_2` это к тому же стоило бы 44 точки
   первого экрана, за который боролись отдельно (14.08).

   Слова владельца: «различие между ними живёт на десктопе, там оно и видно
   рекламодателю при покупке; жертвовать пригодностью ради наглядности прайса
   неправильно».
   ───────────────────────────────────────────────────────────────────────── */
@media (max-width:1023px){
  .v-ad--billboard,
  .v-ad--wide,
  .v-ad--inline,
  .v-ad--side{aspect-ratio:3/1}
}

/* Места внутри ленты на телефоне: docs/frontend/Рекламные_места_в_ленте_на_телефоне.md */
@media (max-width:767px){
  .vg-page-afisha [data-vg-slot$="_3"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_4"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_5"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_6"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_8"] .v-ad{aspect-ratio:2.5/1}

  /* ⚠ СОДЕРЖАТЕЛЬНЫЕ ВРЕЗКИ ПОДНЯТЫ ДО 251 (решение владельца 11.09.2026,
     `2026-09-11_Мобильные_форматы_по_роли.md`).

     Основание НОВОЕ и его в августе не было: замеры пяти площадок. Рынок
     держит содержательную врезку около 250 точек — Афиша 320×250, KudaGo и
     Onliner 300×250. Наш `card` в ориентир попадал и так (269), а соседняя
     врезка стояла на 143: два содержательных формата вели себя по-разному
     без причины.

     ⚠ ПРАВИЛО КАСАЕТСЯ ТОЛЬКО `_3`/`_4`/`_5`, и это не сокращение списка, а
     его СМЫСЛ: там стоят `inline` и `side` — врезки. На `_6`/`_8` стоит
     `wide`, то есть ПОЛОСА, и ей 251 не нужен; она остаётся на 2,5:1.
     Проверено запросом к `ad_slot`, а не предположено.

     10:7 даёт 251 при контейнере 358 — ровно рыночный ориентир. */
  .vg-page-afisha [data-vg-slot$="_2"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_3"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_4"] .v-ad,
  .vg-page-afisha [data-vg-slot$="_5"] .v-ad{aspect-ratio:10/7}

  /* ⚠ ТО ЖЕ ПРАВИЛО ДЛЯ КАТАЛОЖНОГО СПИСКА (22.08.2026) — своим селектором,
     а не расширением афишного, и это прямо следует из оговорки выше: «у
     каталожных списков своя раскладка и свои замеры». Замер проведён отдельно,
     и числа записаны в решении дня — не перенесены с афиши на веру.

     Список мест ТОТ ЖЕ, и это не совпадение: у пяти разделов и у афиши
     форматы восьми мест сходятся один в один (проверено запросом к
     `ad_slot`), и роли у них те же — `_1` и `_2` стоят НАД первой плиткой
     выдачи, остальные встречаются при прокрутке. ⚠ № 2 приведено к врезке
     17.09.2026: единообразие важнее первого экрана.

     ⚠ `_7` В СПИСКЕ НЕТ, и это не пропуск: его формат — `card`, то есть
     карточка со своей пропорцией, а не полоса. На афише он исключён по той же
     причине, и заслон пропорций проверяет это отдельной строкой («`_7` —
     карта»). Растянуть карточку до 2,5:1 значило бы обрезать креатив. */
  .vg-page-section [data-vg-slot$="_3"] .v-ad,
  .vg-page-section [data-vg-slot$="_4"] .v-ad,
  .vg-page-section [data-vg-slot$="_5"] .v-ad,
  .vg-page-section [data-vg-slot$="_6"] .v-ad,
  .vg-page-section [data-vg-slot$="_8"] .v-ad{aspect-ratio:2.5/1}

  /* Тот же подъём врезок в каталожном списке — своим селектором, по тому же
     уговору, что и у правила 2,5:1 выше: у списка своя раскладка. */
  .vg-page-section [data-vg-slot$="_2"] .v-ad,
  .vg-page-section [data-vg-slot$="_3"] .v-ad,
  .vg-page-section [data-vg-slot$="_4"] .v-ad,
  .vg-page-section [data-vg-slot$="_5"] .v-ad{aspect-ratio:10/7}

  /* ─────────────────────────────────────────────────────────────────────
     ⚠ СХЛОПНУВШАЯСЯ РЕЛЬСА — ПОРТРЕТ, А НЕ ЛЕНТА (решение владельца
     11.09.2026).

     До этого `side` на телефоне становился плоской лентой 358×119 — полной
     инверсией своего десктопного вида 300×600. Замер пяти площадок: так не
     делает ни одна. Афиша убирает боковое на мобильном вовсе, Onliner держит
     320×500.

     4:5 даёт 358×448 при ориентире владельца 320×400.

     ⚠ ПРАВИЛО ТОЛЬКО ДЛЯ РЕЛЬСЫ, и это главное в нём. У формата `side`
     двадцать два места, и лишь восемь — настоящие рельсы
     (`DETAIL_*_SIDE`, `OBJECT_SIDE`, `OBJECT_SIDE_2`). Остальные стоят В
     ЛЕНТЕ (`HOME_3`, `HOME_5`, `CATEGORY_*_2`, `CATEGORY_*_4`), и портрет в
     полэкрана посреди потока — это стена между блоками содержания, а не
     реклама. Различение берётся из РАЗМЕТКИ (`.v-objgrid__rail`,
     `.v-article__rail`), которой дизайн-система пользуется сама, а не из
     кода места и не из формата: ни то, ни другое роли не различает.

     ⚠ И ТОЛЬКО ДО 767: на планшете контейнер уже 720+, и портрет там дал бы
     под 900 точек — половину ноутбучного экрана. Ровно та же граница и по
     той же причине, что у правила 2,5:1. */
  .v-objgrid__rail .v-ad--side,
  .v-objgrid__rail .v-ad--side-sm,
  .v-article__rail .v-ad--side,
  .v-article__rail .v-ad--side-sm{aspect-ratio:4/5}
}

/* ⚠ ПОЛОСА НЕ СЖИМАЕТСЯ НИЖЕ СВОЕГО БАННЕРА.

   Найдено 11.08.2026 по вопросу владельца «почему справа только один баннер».
   Замер показал не «один», а ДВА, наложенных друг на друга: на странице
   мероприятия первая часть колонки чтения вышла 206px, полоса растянулась по
   строке грида, то есть тоже 206px, а баннер в ней — 600px. Он вылез за низ
   своей полосы и накрыл собой второй: перекрытие 370 точек.

   Причина в `.v-objgrid__slot{min-height:0}` дизайн-системы. Там это верно: у
   неё ОДНА полоса на оба места, она тянется на всю высоту сетки и делит её
   между слотами в отношении 3:2 — слотам нужно уметь сжиматься. У нас полос
   две, по одному месту в каждой, и сжиматься им нельзя: строка грида обязана
   быть не ниже баннера, иначе он выходит за неё и налезает на соседнюю.

   `min-height:auto` возвращает элементу флекса обычный запрет сжиматься ниже
   содержимого. Цена названа: на КОРОТКОЙ странице строка вырастет до высоты
   баннера, и под текстом останется пустое место. Это лучше наложения — пустое
   место видно и объяснимо, а два баннера друг на друге читаются как один. */
@media (min-width:1024px){
  .v-objgrid__rail > .v-objgrid__slot{min-height:auto}
}

/* СЕТКА СТРАНИЦЫ ОБЪЕКТА БЕЗ БОКОВОЙ ПОЛОСЫ.

   `.v-objgrid` системы жёстко объявлен `minmax(0,1fr) 300px`: он рассчитан на
   то, что боковые места есть всегда. У страницы мероприятия их нет вовсе —
   `slotCodesFor('event')` их не перечисляет, — и вторая колонка оставалась
   пустой, отбирая у чтения 300 точек. Здесь колонка одна.

   Правило понадобится и другим страницам объекта, если инвентарь у них
   разойдётся, поэтому имя общее, а не «афишное». */
.vg-objgrid--norail{grid-template-columns:minmax(0,1fr)}

/* СЕАНСЫ МЕРОПРИЯТИЯ — строками, а не таблицей с рамками.

   До 11.08.2026 расписание печаталось `<table border="1" cellpadding="6">` —
   атрибутами оформления из девяностых, мимо всей дизайн-системы. Компонента
   под расписание сеансов в системе нет (`Hours` описывает НЕДЕЛЮ заведения, а
   не список дат), поэтому строка собрана из её же примитивов: сетка,
   разделитель `--border-subtle`, приглушённый текст, бейдж для зала.

   Отменённый сеанс не исчезает, а гаснет: он уже был анонсирован, и
   «отменён» посетителю полезнее пустоты (Модуль_03 разд. 8). Гасится ТЕКСТ, а
   не весь блок прозрачностью: `opacity` на строке приглушила бы и бейдж зала
   до нечитаемого, а требование контраста действует и здесь. */
.vg-session{
  display:grid;
  grid-template-columns:1fr auto auto auto;
  align-items:center;
  gap:var(--s-12);
  padding-block:var(--s-10);
  border-bottom:1px solid var(--border-subtle);
}
.vg-session__at{font-weight:600}
.vg-session__price{color:var(--text-secondary);font-variant-numeric:tabular-nums}
.vg-session__state{color:var(--text-muted);font-size:13px}
.vg-session--off .vg-session__at{text-decoration:line-through}
.vg-session--off .vg-session__state{color:var(--red-500)}
/* На телефоне четыре колонки не помещаются: время и цена остаются в строке,
   зал и состояние переносятся под них. */
@media (max-width:600px){
  .vg-session{grid-template-columns:1fr auto}
  .vg-session__state{grid-column:1 / -1}
}

/* ─── Афиша: превью мероприятия по `EventCard` (11.08.2026) ─────────────── */

/* БЕЙДЖ В КОЛОНКЕ НЕ ТЯНЕТСЯ НА ВСЮ ШИРИНУ.

   `.v-card__body` — флекс-колонка, а у элемента колонки `align-self` по
   умолчанию `stretch`. `.v-badge` объявлен `display:inline-flex`, но inline-
   часть тут не работает: элемент флекса становится блочным. На живой ленте
   это выглядело как серая полоса во всю карточку со словом «предстоит» с
   краю — на плашку не похоже вовсе. То же было у возрастного «6+».

   В самой системе бейджи стоят внутри строк (`.v-card__meta`), поэтому её CSS
   этого случая не знает: у нас они выведены отдельными элементами колонки. */
.vg-badge--inline{align-self:flex-start}

/* ВОЗРАСТ — ПЛАШКОЙ НА ПОСТЕРЕ, как в компоненте.

   `EventCard.jsx` ставит её встроенным стилем (`position:absolute; right:10;
   top:10`), а встроенные стили в CSS системы не попадают — своего класса у
   этой плашки там нет. `.v-badge--onmedia` задаёт только вид: тёмная подложка
   с размытием под светлый текст поверх снимка. */
.vg-card__age{position:absolute;top:10px;right:10px;z-index:2}

/* ПЛИТКИ АФИШИ — ЧИСЛОМ КОЛОНОК, А НЕ МИНИМАЛЬНОЙ ШИРИНОЙ.

   Постер афиши 3:4 против 4:3 у каталога — так задумано системой и написано
   в промпте `EventCard`. Вертикальный постер на треть выше горизонтального,
   поэтому раскладка каталога афише не годится ни в одну сторону:

   — `auto-fill` от 220px давал на телефоне ОДНУ колонку, и постер вырастал до
     358×477 — одно мероприятие на весь экран;
   — `auto-fill` от 180px на десктопе давал ШЕСТЬ колонок по 180px, и постер
     съёживался до размера значка, а дата под ним переносилась в две строки.

   Числа колонок заданы явно, как у `.vg-grid` каталога: четыре на десктопе,
   три на планшете, две на телефоне. Две на телефоне — это обычный вид афиши
   (так же устроены киноприложения), и на экран попадает четыре события
   вместо одного. Контрольные ширины те же, что у каталога, чтобы страницы
   перестраивались вместе, а не по очереди. */
.vg-tiles--afisha{grid-template-columns:repeat(4,minmax(0,1fr))}
@media (max-width:1024px){
  .vg-tiles--afisha{grid-template-columns:repeat(3,minmax(0,1fr))}
}
@media (max-width:768px){
  .vg-tiles--afisha{grid-template-columns:repeat(2,minmax(0,1fr))}
}

/* ДАТА В ПЛИТКЕ РВЁТСЯ ПЕРЕД ТИРЕ, А НЕ ПОСЛЕ.

   `.v-card__meta` — флекс с переносом, а диапазон «19:00 — 22:00» состоит из
   трёх узлов: `<time>`, тире и второй `<time>`. Без обёртки второй конец
   уезжал на следующую строку, и тире оставалось висеть в её начале — ровно
   то же, что было с точкой перед датой отзыва.

   Запрет переноса на ВСЮ строку это не чинит, а меняет одну беду на другую:
   в колонке 167px (телефон, две колонки) неразрывный диапазон вылезал за край
   плитки. Поэтому неразрывен только ХВОСТ — «— 22:00»; перенос разрешён перед
   тире, и тогда тире уходит вниз вместе со своим временем. */
.vg-when__to{white-space:nowrap}

/* «ПОДЕЛИТЬСЯ»: обёртка и подтверждение.

   `display:contents` — чтобы кнопка встала в ряд действий обложки как обычный
   его элемент, а не отдельной коробкой со своими отступами. Обёртка при этом
   остаётся в DOM, и скрипт находит по ней подтверждение рядом с нажатой
   кнопкой (`closest`), не привязываясь к порядку элементов.

   Подтверждение стоит ПОВЕРХ ФОТОГРАФИИ, поэтому оно не просто серый текст, а
   плашка с непрозрачной подложкой: на светлом снимке обычная подпись
   потерялась бы. `white-space:nowrap` — фраза короткая и переносить её между
   значками незачем. */
.vg-share{display:contents}
.vg-share__done{
  align-self:center;
  padding-block:4px;
  padding-inline:10px;
  border-radius:var(--r-pill);
  background:var(--surface-inverse);
  color:var(--text-inverse);
  font-size:13px;
  white-space:nowrap;
}

/* ГАЛЕРЕЯ на 20% мельче (просьба владельца). Система жёстко ставит пять
   колонок; при широком контейнере кадр выходит около 236px. Здесь колонки
   считаются от минимальной ширины кадра, поэтому их число подстраивается под
   экран само — и это заодно закрывает промежуточные ширины, о которых
   фиксированная пятёрка не знает.
   До 767px система превращает ленту в прокрутку пальцем — там `display:flex`,
   и это правило уже не действует. */
.vg-gallery--compact{grid-template-columns:repeat(auto-fill,minmax(185px,1fr))}

/* Поле поиска в шапке — в рамке по ширине.

   `.v-search` дизайн-системы объявлено `width:100%`: оно рассчитано на то,
   что ширину задаёт ОБЁРТКА. В компоненте `SiteHeader.jsx` так и сделано —
   поле лежит в блоке `flex:1; max-width:360px`. Я вставил форму в полосу
   голой, и она забрала всю свободную ширину: полоса переполнилась, логотип и
   выбор города сжались до соприкосновения, и это выглядело как пропавший
   отступ между ними (замечание владельца 10.08.2026).

   `min-width:0` обязателен: у элемента флекса минимальный размер по умолчанию
   равен содержимому, и без обнуления поле отказалось бы сжиматься на узком
   экране, снова распирая полосу. */
.vg-header__search{flex:1 1 auto;min-width:0;max-width:360px}

/* ─────────────────────────────────────────────────────────────────────────
   МОБИЛЬНАЯ РАСКЛАДКА ВИТРИНЫ

   У шапки и у ряда разделов в дизайн-системе НЕТ мобильных правил. Не по
   недосмотру: компонент `SiteHeader.jsx` при `mobile` рисует другую
   разметку — гамбургер, знак без слова, город значком, поиск и кабинет
   иконками. У нас разметка одна на все ширины, поэтому мобильную раскладку
   обязаны дать мы.

   Пока её не было, полоса шапки (знак, город, поиск, три ссылки кабинета) и
   ряд из восьми разделов не помещались в телефон. Страница получала
   горизонтальную прокрутку, и «половину не было видно»; поля контейнера при
   этом были на месте — их просто уносило вбок вместе со всем остальным.
   ───────────────────────────────────────────────────────────────────────── */
/* Каркас шапки перестраивается на 1023px — на той же границе, где система
   схлопывает сетку страницы (`.v-objgrid`). Прежде правила стояли на 767px, и
   между 768 и 1023 шапка оставалась десктопной: знак, город, поиск и три
   ссылки кабинета в одну строку. Полоса переполнялась, страница получала
   горизонтальную прокрутку — и всё внутри рисовалось шире экрана, включая
   ленту фотографий и карту. Планшет и узкое окно ноутбука попадали именно
   сюда. */
@media (max-width:1023px){
  /* Полоса складывается: знак, город и ссылки кабинета сверху, поиск —
     отдельной строкой во всю ширину. Высота перестаёт быть фиксированной,
     иначе вторая строка вылезет за пределы шапки. */
  .v-header__bar{height:auto;flex-wrap:wrap;gap:var(--s-8);padding-block:var(--s-8)}

  /* Остаётся знак без слова — ровно как `withWordmark={false}` в мобильной
     ветке компонента. Слово «ВГороде82» съедало половину строки. */
  .v-header__logo span{display:none}

  /* Поиск: docs/decisions/2026-09-26_Поиск_по_порталу.md, разд. 22 */
  .vg-header__search{min-width:280px;flex-basis:280px}

  /* Восемь разделов в строку не влезают, а переносить их кучей — потерять
     порядок. Лента с прокруткой пальцем: тот же приём, которым система
     решает галерею на телефоне. Отрицательные поля — чтобы лента шла от
     края до края, а не обрывалась внутри полей контейнера. */
  .v-nav{flex-wrap:nowrap;overflow-x:auto;scrollbar-width:none;
    margin-inline:calc(-1 * var(--grid-margin));
    padding-inline:var(--grid-margin)}
  .v-nav::-webkit-scrollbar{display:none}
  .v-nav__link{white-space:nowrap}

  /* Меню объявления шире телефона: 264px плюс поля вылезали за правый край. */
  .v-admenu{width:min(264px,calc(100vw - 2 * var(--grid-margin)))}
}

@media (max-width:767px){

  /* Адрес сайта в характеристиках — единственная длинная неразрывная строка
     на странице; без переноса она растягивала сетку и всю страницу за ней. */
  .v-spec__v{overflow-wrap:anywhere}

  /* ⚠ ЛЕНТА ФОТОГРАФИЙ ПОКАЗЫВАЛА ОДИН КАДР.
     Наш список несёт оба класса — `v-gallery` и `v-gallery--strip`. В
     мобильном блоке системы есть правило `.v-gallery__item:not(:first-child)
     {display:none}`: её обычная галерея на телефоне схлопывается в одну
     обложку со счётчиком (так и написано в промпте компонента). Вес у него
     тот же, что у правил ленты, а стоит оно ниже по файлу — и побеждало,
     пряча все кадры, кроме первого.

     Лента на телефоне остаётся лентой: система сама делает её прокруткой
     пальцем, все кадры достижимы, и это лучше одной обложки — счётчика
     «Ещё N фото» у нас нет, потому что нет лайтбокса поверх обложки. */
  .v-gallery--strip .v-gallery__item:not(:first-child){display:block}
}

/* ─────────────────────────────────────────────────────────────────────────
   ПРЕДОХРАНИТЕЛЬ ОТ ПЕРЕПОЛНЕНИЯ

   Не подгонка под конкретный телефон: правила ниже действуют на любой ширине
   и снимают САМУ ВОЗМОЖНОСТЬ вылезти за экран. Разбирать переполнение по
   одному элементу бессмысленно — за каждым найденным приходит следующий, а
   проверить раскладку на этой машине нечем: браузера здесь нет.

   Причин ровно три, и все три общие.

   1. `1fr` в гриде — это `minmax(auto,1fr)`, а `auto` равен МИНИМАЛЬНОЙ
      ширине содержимого. Одной широкой вещи внутри (карта, лента кадров, ряд
      объявлений) хватает, чтобы колонка перестала сжиматься и растянула
      страницу. `minmax(0,1fr)` снимает этот минимум — колонка обязана уступать
      экрану, а содержимое разбирается со своей шириной само.

   2. Элемент флекса и грида по умолчанию не сжимается уже своего содержимого
      (`min-width:auto`). Из-за этого прокручиваемая лента внутри такого
      элемента не прокручивается, а распирает родителя — ровно то, что
      наблюдалось с галереей.

   3. Картинка шире контейнера. `max-width:100%` — обычная страховка; `svg`
      сюда НЕ включён намеренно: у иконок своя заданная ширина, и ограничивать
      её нечем.
   ───────────────────────────────────────────────────────────────────────── */
img,video,canvas,iframe{max-width:100%}

/* Колонки, которые обязаны уступать экрану, и их содержимое. */
.v-objgrid,
.v-stack,
.v-grid{min-width:0}
.v-objgrid > *,
.v-stack > *,
.v-grid > *{min-width:0}

@media (max-width:1023px){
  /* Система на этой ширине уже схлопывает сетку объекта в одну колонку, но
     оставляет `1fr` — с минимумом по содержимому. Отсюда и растягивание. */
  .v-objgrid{grid-template-columns:minmax(0,1fr)}
}

/* ─────────────────────────────────────────────────────────────────────────
   ФИЛЬТРЫ АФИШИ: ПОЛОСА СНАРУЖИ, ШТОРКА НА ТЕЛЕФОНЕ (14.08.2026)

   Пятый случай того же класса, что разобран 11.08.2026: система уводит
   `FilterBar` в шторку КОМПОНЕНТОМ — на узком экране он рисует другую
   разметку. У нас разметка одна, значит мобильную раскладку пишем мы.

   Что снаружи остаётся видимым, решил владелец: переключатель архива, кнопка
   «Фильтры · N» и лента пресетов. В афише сначала выбирают время, потом жанр,
   и «что сегодня вечером» обязано решаться одним касанием.

   ⚠ Граница 1023px, а не 767px, на которой у системы стоят мобильные правила
   `FilterBar` в `chips.css`. Рама и содержимое перестраиваются одновременно —
   иначе получаем зазор 768–1023, где шапка уже мобильная, а фильтры ещё
   десктопные. Это у нас уже было и стоило пяти заходов вслепую.
   ───────────────────────────────────────────────────────────────────────── */
.vg-filterbar{display:flex;flex-direction:column;gap:var(--s-8)}

/* Обвязка шторки в потоке не показывается: закрывать нечего, а сброс на
   широком экране делается теми же чипсами, которые видны все разом. */
.vg-fdrawer__head,[data-vg="filters-reset"]{display:none}

.vg-filters-open{align-self:flex-start}

@media (max-width:1023px){
  /* Пресеты — лента с прокруткой пальцем. Объявления те же, что у
     `.v-chiprow--scroll` системы; поставить сам класс нельзя — разметка одна
     на все ширины, а на широком экране лента не нужна. Отрицательные поля,
     чтобы лента шла от края до края, а не обрывалась внутри полей
     контейнера (тот же приём, что у ряда разделов в шапке). */
  /* ⚠ ВАРИАНТ В3 (решение владельца 22.08.2026): полоса — ОДНА строка, а не
     три. Кнопка «Фильтры», пресеты дат и «Вход свободный» стояли тремя
     рядами и занимали 120 точек первого экрана из 631 на 320.

     Приём тот же, что уже работает от 1024: ряды объявляются `display:contents`
     и их дети входят в одну строку родителя. Новое здесь только то, что
     родитель становится ЛЕНТОЙ С ПРОКРУТКОЙ, — пресеты и так листались вбок
     (392 точки при ширине 288), поэтому цена решения не в потере, а в
     уплотнении: «Фильтры» и «Вход свободный» встают в тот же ряд.

     ⚠ Отрицательные поля переезжают с рядов на саму полосу: лента обязана
     идти от края до края экрана, а не обрываться внутри полей контейнера
     (тот же приём, что у ряда разделов в шапке). Оставить их на детях при
     `display:contents` было бы нечем — таких боксов больше нет. */
  .vg-filterbar{flex-direction:row;flex-wrap:nowrap;align-items:center;
    overflow-x:auto;scrollbar-width:none;
    margin-inline:calc(-1 * var(--grid-margin));
    padding-inline:var(--grid-margin)}
  .vg-filterbar::-webkit-scrollbar{display:none}
  .vg-filterbar > .vg-presets,
  .vg-filterbar > .vg-quick{display:contents}
  .vg-filterbar .v-chip{white-space:nowrap}
  /* Кнопка не сжимается под давлением ленты: «Фильтры» — вход во все
     остальные фильтры, и обрезать её нельзя. */
  .vg-filters-open{align-self:center;flex-shrink:0}

  /* ⚠ Форма прячется ТОЛЬКО когда есть кому её открыть: класс `vg-jsdrawer`
     ставит скрипт. Не загрузился — форма остаётся в потоке, как была. Без
     этого условия фильтры на телефоне стали бы недостижимы вовсе. */
  .vg-jsdrawer .vg-fdrawer{display:none}

  .vg-jsdrawer .vg-fdrawer.is-open{display:flex;position:fixed;inset:0;
    z-index:100;margin:0;border:0;border-radius:0;gap:0;
    background:var(--surface-page);
    animation:v-fade var(--dur-enter) var(--ease-out)}

  .vg-fdrawer.is-open .vg-fdrawer__head{display:flex;align-items:center;
    justify-content:space-between;gap:var(--s-16);flex:none;
    padding:var(--s-12) var(--grid-margin);
    border-bottom:1px solid var(--border-subtle)}

  /* Прокручивается только тело: шапка и подвал липкие по построению —
     они просто не входят в область прокрутки. */
  .vg-fdrawer.is-open .vg-fdrawer__body{flex:1 1 auto;min-height:0;
    overflow-y:auto;overscroll-behavior:contain;
    padding:var(--s-16) var(--grid-margin)}

  .vg-fdrawer.is-open .vg-fdrawer__foot{display:flex;gap:var(--s-10);flex:none;
    padding:var(--s-12) var(--grid-margin);
    padding-bottom:calc(var(--s-12) + env(safe-area-inset-bottom));
    border-top:1px solid var(--border-subtle);background:var(--surface-page)}
  .vg-fdrawer.is-open [data-vg="filters-reset"]{display:inline-flex}
  .vg-fdrawer.is-open [data-vg="afisha-filters-apply"]{flex:1}

  /* Минимальная зона касания 44px — требование мобильного кита системы. */
  .vg-fdrawer.is-open .v-btn{min-height:44px}
}

/* ─────────────────────────────────────────────────────────────────────────
   ЛЕНТА АФИШИ: КАРКАС ПО ШАБЛОНУ СИСТЕМЫ (14.08.2026, шаг 1)

   Каркас `.v-catalog` и сетку `.v-grid--4` система схлопывает сама — и на той
   же границе 1023px, что мы выбрали для шторки фильтров. Здесь только то,
   чего у неё нет: мелочи нашей разметки и одно осознанное отступление.
   ───────────────────────────────────────────────────────────────────────── */
.vg-crumbs{color:var(--text-muted);flex-wrap:wrap}
.vg-crumbs a{color:var(--text-muted)}
.vg-crumbs .icon{color:var(--ink-300)}

/* Режим ленты и пояснение про архив — в строку, с переносом на узком. */
.vg-moderow{display:flex;align-items:center;gap:var(--s-16);flex-wrap:wrap}

/* ─────────────────────────────────────────────────────────────────────────
   ПЕРЕКЛЮЧАТЕЛЬ РЕЖИМА НА УЗКОМ ЭКРАНЕ — ВО ВСЮ ШИРИНУ (14.08.2026)

   `mobile.md`: «`Segmented` растягивается на всю ширину контейнера, две равные
   половины. Пояснение про архив уходит под переключатель отдельной строкой».
   У самого компонента мобильной ветки нет вовсе — `.v-segmented` объявлен
   `inline-flex` и на 390 занимал 241 точку из 358, оставляя справа пустоту.

   Это НАМЕРЕНИЕ поставки, не противоречащее её CSS (правило владельца
   14.08.2026: источник — CSS и компоненты, `mobile.md` — намерение). Здесь оно
   заполняет пробел, а не спорит: мобильного правила у переключателя нет.

   Граница 1023 — та же, на которой у нас перестраивается всё остальное:
   фильтры уходят в шторку, сетка схлопывается, боковая колонка встаёт в поток.

   Половины равны через `flex:1` у элементов, а не через `width:50%`: у полосы
   есть внутренние поля и зазор, и проценты дали бы переполнение на точку. */
@media (max-width:1023px){
  .vg-moderow .v-segmented{display:flex;width:100%}
  .vg-moderow .v-segmented__item{flex:1;justify-content:center}
  /* Пояснение про архив — отдельной строкой под переключателем. */
  .vg-moderow{flex-direction:column;align-items:stretch;gap:var(--s-8)}
}

/* Внутри колонки поля контейнера уже отработали: ленты с отрицательными
   полями (пресеты) не должны вылезать второй раз. Так же в шаблоне системы.

   ⚠ ДВА ИМЕНИ, ОДНО ПРАВИЛО (22.08.2026): у афиши и у каталожного списка
   колонки называются по-своему, потому что мобильный порядок внутри них
   РАЗНЫЙ — у афиши первым идёт счётчик «Найдено», у каталога полоса счётчика
   и сортировки. Само же «колонка не бокс и полей не удваивает» у них общее, и
   писать его дважды значило бы завести две правды об одном. */
.vg-afisha__main,.vg-catalog__main{--grid-margin:0px;min-width:0}

@media (max-width:1023px){
  /* Место _2 после первой порции: docs/frontend/Порядок_ленты_афиши_на_телефоне.md */
  /* ⚠ ТОЛЬКО ПЕРВАЯ ОБЛАСТЬ — ТА, ЧТО НЕСЁТ ВЫДАЧУ. Правило снимает бокс у
     главной колонки, чтобы её дети могли встать в собственном порядке рядом с
     боковым местом: счётчик, ПЕРВАЯ ПОРЦИЯ КАРТОЧЕК, и только потом баннер.

     ⚠ ПОРЯДОК ЗДЕСЬ — НЕ УКРАШЕНИЕ, А ПЕРВЫЙ ЭКРАН. Поставить баннер перед
     выдачей значит увести первую плитку за край экрана: замер 20.08.2026
     (вечер) дал 882 при экране 844, тогда как правильный порядок держит её на
     738. Ровно это и случилось, когда правило переписывали под три области, и
     покраснел `afisha-filters.e2e-spec.ts` — «выдача начинается в пределах
     первого экрана».

     ⚠ АДРЕСАЦИЯ ПО НОМЕРУ ОБЛАСТИ, А НЕ ПО КЛАССУ. Прежде правило цеплялось за
     `.vg-afisha`, а этот класс есть только у ЛИПКОЙ области: стоило порогу
     сделать её плоской (`vg-tail--flat`), и порядок переставал применяться
     молча. Номер области от липкости не зависит. */
  [data-vg-region="1"] .vg-afisha__main{display:contents}
  [data-vg-region="1"] .vg-afisha__main > *{order:3}
  [data-vg-region="1"] .vg-afisha__main > [data-vg="afisha-total"]{order:0}
  [data-vg-region="1"] .vg-afisha__main > ul:first-of-type{order:1}
  [data-vg-region="1"] .v-catalog__aside{order:2}

  /* ⚠ ТО ЖЕ ПРАВИЛО ДЛЯ КАТАЛОЖНОГО СПИСКА (22.08.2026), и оно НЕ СЛИТО с
     афишным нарочно: первым элементом у афиши стоит строка «Найдено», а у
     каталога — полоса «Найдено + сортировка» (`vg-catalog__bar`). Селектор
     `[data-vg="afisha-total"]` здесь не нашёл бы ничего, и полоса молча
     уехала бы за баннер — то есть первый экран снова начинался бы с рекламы.

     Порядок тот же и по той же причине: счётчик, ПЕРВАЯ ПОРЦИЯ КАРТОЧЕК, и
     только потом баннер. Поставить баннер перед выдачей значит увести первую
     плитку за край экрана — на афише это уже стоило замера и покрасневшего
     заслона (20.08, вечер).

     ⚠ В ВИТРИНЕ `ul` первого уровня нет вовсе (блоки рубрик — это `section` со
     своим списком внутри), и правило `ul:first-of-type` не находит ничего.
     Это не дефект: в витрине нет ни счётчика, ни порций, и баннер честно
     встаёт первым — ровно как на витрине афиши. */
  [data-vg-region="1"] .vg-catalog__main{display:contents}
  [data-vg-region="1"] .vg-catalog__main > *{order:3}
  [data-vg-region="1"] .vg-catalog__main > .vg-catalog__bar{order:0}
  [data-vg-region="1"] .vg-catalog__main > ul:first-of-type{order:1}
}

/* ─────────────────────────────────────────────────────────────────────────
   ФИЛЬТРЫ АФИШИ ПО `FilterBar` (14.08.2026, шаг 2)

   Оформление таблеток и панелей целиком системное (`ds2/css/chips.css`);
   здесь только то, чего у неё нет и быть не может: она раскрывает панель
   состоянием React-компонента, а у нас одна разметка на все ширины.
   ───────────────────────────────────────────────────────────────────────── */

/* Панель закрыта, пока её не открыли. У системы её в этот момент просто нет
   в разметке — у нас она есть всегда, и это единственный способ отдать её
   сервером. */
.v-fdrop__panel{display:none}
.v-fdrop.is-open > .v-fdrop__panel{display:block}

/* Ряд выбранного. Крестик внутри чипса — не отдельная кнопка: чипс целиком
   ссылка «снять это значение», и на телефоне в него легче попасть. */
.vg-chosen__item{gap:var(--s-6)}
.vg-chosen__item .icon{opacity:.8}

/* Возраст: переключатели в оформлении `FilterOptions`. Сам ввод не виден —
   его роль играет подпись, как в `.v-check`. */
.vg-ages{grid-template-columns:repeat(3,minmax(0,1fr))}
.vg-ages .v-fdrop__opt{display:grid;place-items:center}
.vg-ages input{position:absolute;opacity:0;width:0;height:0}
.vg-ages input:focus-visible + *,
.vg-ages .v-fdrop__opt:focus-within{outline:none;box-shadow:var(--focus-ring)}

@media (max-width:1023px){
  /* В шторке таблеток нет: три группы стоят раскрытыми одна под другой —
     так это описывает `mobile.md` поставки. Панель перестаёт быть всплывающей
     и становится обычным блоком. */
  .vg-fdrawer .v-fdrop__pill{display:none}
  .vg-fdrawer .v-filterbar{display:block}
  .vg-fdrawer .v-fdrop{margin-bottom:var(--s-20)}
  .vg-fdrawer .v-fdrop__panel,
  .vg-fdrawer .v-fdrop.is-open > .v-fdrop__panel{
    display:block;position:static;width:auto;max-width:none;
    border:0;border-radius:0;box-shadow:none;background:none;animation:none}
  .vg-fdrawer .v-fdrop__panel{padding:0}
  /* Заголовок группы остаётся, крестик у неё — нет: закрывает шторку её
     собственный крестик, а не крестик группы. */
  .vg-fdrawer .v-fdrop__close{display:none}
  .vg-fdrawer .v-fdrop__head{padding:0 0 var(--s-8)}
  .vg-fdrawer .v-fdrop__title{font-size:17px}
  .vg-fdrawer .v-fdrop__body{max-height:none;padding:0;overflow:visible}
  /* Подвал у каждой группы не нужен: у шторки один общий. */
  .vg-fdrawer .v-fdrop__foot{display:none}
}

/* На широком экране форма фильтров — не карточка, а ряд таблеток: рамку и фон
   ей даёт `.v-filters`, и вокруг `FilterBar` они лишние. В шторке фон
   возвращает правило `.is-open`. */
.vg-fdrawer{background:none;border-width:0;padding:0}

/* Общий подвал со «Сбросить/Показать» — принадлежность шторки: на широком
   экране у каждой панели свой, и второй внизу страницы висел бы ни при чём. */
.vg-fdrawer__foot{display:none}

/* Ряд быстрых фильтров рядом с пресетами.

   ⚠ Своей ленты у него больше НЕТ (вариант В3, 22.08.2026): ниже 1024 он
   входит в общую строку полосы через `display:contents`, а прокрутку держит
   сама полоса. Вторая лента внутри первой означала бы две области прокрутки
   на одной строке — палец не знал бы, какую из них тянет. */
.vg-quick{flex-wrap:wrap}

/* ─────────────────────────────────────────────────────────────────────────
   ОТСТУПЫ ЛЕНТЫ АФИШИ — ПО ЗАМЕРУ ШАБЛОНА (14.08.2026, правило 2c)

   Ни одно из этих чисел не выведено из разметки: отступы — вычисленная
   геометрия, и взяты они замером `templates/afisha-section` тем же кодом,
   которым мерилась наша страница.

   ⚠ В первой таблице замеров стояли 238 и 243 — это была ошибка САМОГО
   замерщика: он считал расстояние насквозь через контейнер баннера. У
   баннера свой контейнер с полями 24 сверху и снизу (24+190+24 = 238), а от
   нижнего места до подвала — 40.
   ───────────────────────────────────────────────────────────────────────── */

/* Каждое рекламное место лежит в собственной полосе с полями 24 — так во
   всех шаблонах поставки. У нас места стояли вплотную к соседям. */
.vg-adband{padding-block:var(--s-24)}

/* ⚠ ПЕРВОЕ МЕСТО СТРАНИЦЫ НЕ ДЕРЖИТ СВОИХ ПОЛЕЙ (вариант Б, решение владельца
   22.08.2026): 24 сверху и 24 снизу — это 48 точек первого экрана, а на 320
   их всего 631 под содержимое.

   У шаблона поставки этих полей нет вовсе: билборд лежит в `v-adwrap--bare`,
   и обёртка равна самому месту (замер 22.08 — 45 против 45). Отступ там
   держат соседи, а не реклама.

   ⚠ Убрать оба поля целиком нельзя — место прилипнет к заголовку. Верхнее
   поле переезжает на заголовок (`.vg-titlebox--tight` перестаёт быть
   «tight» ровно перед первым местом), нижнее снимается: у ряда режима под
   ним свой отступ уже есть. Ничего не теряется, кроме воздуха, которого у
   шаблона нет. */
.vg-adband--first{padding-block:0}
.vg-titlebox--tight:has(+ .vg-adband--first){margin-bottom:var(--s-16)}
/* ⚠ И НИЖНИЙ отступ тоже переезжает на соседа, а не снимается. Снимок на 390
   показал то, чего числа не сказали: без него рекламное место слипается с
   переключателем «Предстоящие / Прошедшие» вплотную и читается как ошибка
   вёрстки. Числа при этом были «лучше» — 728 вместо 744. */
.vg-adband--first + .vg-moderow{margin-top:var(--s-16)}
.vg-adband--first:not([hidden]):has(> *) + :is(.vg-moderow, div:has(> .v-filterbar)){margin-top:var(--s-24)}
.vg-adband--first:not([hidden]):has(> *) + .vg-moderow:has(~ .v-section--tight:not(.v-catalog ~ *)),
.vg-adband--first:not([hidden]):has(> *) + div:has(> .v-filterbar):has(> .v-section--tight:not(.v-catalog ~ *)){margin-top:var(--s-32)}

/* Режим ленты и фильтры: 16 до таблеток, 12 до ряда выбранного. */
.vg-moderow{margin-bottom:var(--s-16)}
.vg-chosen{margin-top:var(--s-12)}

/* ─────────────────────────────────────────────────────────────────────────
   ФИЛЬТРЫ В ОДИН РЯД (14.08.2026, по замеру против шаблона)

   У шаблона полоса управления — два ряда: режим, затем `FilterBar` с
   таблетками (и ряд выбранного, когда есть что показывать). У нас было
   четыре: режим, пресеты, быстрый чипс, таблетки — 172 точки против 78.

   ⚠ Одной флекс-строкой это не собиралось: пресеты и быстрый чипс лежат ВНЕ
   формы (чтобы на телефоне остаться снаружи шторки — решение владельца), а
   таблетки ВНУТРИ неё, потому что панель обязана быть соседом своей
   таблетки. `display:contents` снимает противоречие: обёртки перестают быть
   боксами, и таблетки становятся детьми той же строки, оставаясь в форме по
   разметке.

   Только на широком экране: на узком форма — шторка, и её обёртки обязаны
   снова стать боксами.
   ───────────────────────────────────────────────────────────────────────── */
@media (min-width:1024px){
  /* Полоса — одна строка: вычисляемые чипсы, разделитель, таблетки. */
  .vg-filterbar{flex-direction:row;flex-wrap:wrap;align-items:center;
    gap:var(--s-8) var(--s-12)}
  .vg-filterbar .vg-presets,
  .vg-filterbar .vg-quick{display:contents}

  /* Форма перестаёт быть боксом — её таблетки входят в ту же строку. */
  .vg-fdrawer,
  .vg-fdrawer > .vg-fdrawer__body,
  .vg-fdrawer .v-filterbar{display:contents}

  /* Разделитель перед атрибутными таблетками — как в «Разделе каталога». */
  .vg-fdrawer .v-filterbar::before{content:"";width:1px;height:24px;
    background:var(--border-default);margin-inline:var(--s-4)}

  /* Ряд выбранного — отдельный блок под полосой, как `v-chiprow` у
     шаблона. Внутри полосы он забирал строку целиком и вытеснял таблетки
     на вторую: замер показал 332 точки таблеток из 1216, то есть дело было
     не в тесноте. */
}

/* ─────────────────────────────────────────────────────────────────────────
   ССЫЛКА НЕ ШЕВЕЛИТСЯ ПОД КУРСОРОМ (решение владельца, 04.08.2026 и снова
   14.08.2026)

   Правило: ссылка подчёркнута ВСЕГДА либо не подчёркнута НИКОГДА; при
   наведении меняется цвет, а не появляется линия. Появляющаяся линия сдвигает
   базовую линию текста и «шевелит» страницу под курсором.

   ⚠ Чинится второй раз, и вот почему первый не помог: подчёркивание ставит
   САМА СИСТЕМА — `ds2/css/base.css`, правило `a:hover{text-decoration:
   underline}`. 04.08 его сняли точечно на экранах кабинета, а корень остался
   и красил остальной портал. Поэтому снимаем здесь, в своём слое, разом для
   всех страниц — файлы системы не правим, поставка их переписывает.

   Постоянно подчёркнутые ссылки (текст статьи, ссылка на объект в тексте)
   подчёркивание сохраняют: у них линия не появляется, а есть всегда.
   ───────────────────────────────────────────────────────────────────────── */
a:hover{text-decoration:none}

/* ⚠ КНОПКА, СДЕЛАННАЯ ССЫЛКОЙ, ОСТАЁТСЯ КНОПКОЙ (найдено замером кабинета
   23.08.2026, дефект доступности).

   Поставка красит любую ссылку под курсором: `base.css` →
   `a:hover{color:var(--text-link-hover);text-decoration:underline}`. У этого
   правила специфичность 0,1,1, а у `.v-btn--primary{color:…}` — 0,1,0, и
   правило ссылки ПЕРЕБИВАЕТ цвет кнопки. У `primary` цвет ссылки под курсором
   (`--text-link-hover`) совпадает с её же фоном (`--surface-brand-hover`) —
   надпись исчезает целиком. У шаблона поставки этого нет: там кнопка —
   `<button>`, и правило ссылок к ней не относится.

   Кнопок-ссылок на портале 54 в 33 шаблонах: это не экранный дефект, а общий.

   ⚠ ЦВЕТА ПОВТОРЕНЫ ТОКЕНАМИ, И ЭТО НАЗВАНО. Отменить чужое правило, не
   называя цвета, в CSS нечем: `inherit` возьмёт цвет родителя, `revert` —
   браузерный. Поэтому варианты перечислены по ГРУППАМ ЦВЕТА, а не по одному,
   и повторяется имя токена, а не значение. Что список не разошёлся с
   поставкой, стережёт `test/button-link-color.spec.ts`.

   `.v-btn--link` намеренно НЕ включена: она и выглядит ссылкой, цвет ссылки
   под курсором ей к лицу. */
a.v-btn--primary:hover,a.v-btn--primary:focus-visible,a.v-btn--primary:active,
a.v-btn--accent:hover,a.v-btn--accent:focus-visible,a.v-btn--accent:active,
a.v-btn--danger:hover,a.v-btn--danger:focus-visible,a.v-btn--danger:active,
a.v-btn--success:hover,a.v-btn--success:focus-visible,a.v-btn--success:active{color:var(--text-inverse)}
a.v-btn--secondary:hover,a.v-btn--secondary:focus-visible,a.v-btn--secondary:active{color:var(--text-primary)}
a.v-btn--soft:hover,a.v-btn--soft:focus-visible,a.v-btn--soft:active{color:var(--text-brand)}
a.v-btn--ghost:hover,a.v-btn--ghost:focus-visible,a.v-btn--ghost:active{color:var(--text-secondary)}
a.v-btn--danger-quiet:hover,a.v-btn--danger-quiet:focus-visible,a.v-btn--danger-quiet:active{color:var(--text-inverse)}
a.v-btn--onmedia:hover,a.v-btn--onmedia:focus-visible,a.v-btn--onmedia:active{color:#fff}
.v-btn--danger-quiet:hover:not(:disabled){background:var(--red-500);color:var(--text-inverse)}
.v-btn--link:hover:not(:disabled){text-decoration:none}
.v-prose a:hover,.v-placelink:hover{text-decoration:underline}

/* ─────────────────────────────────────────────────────────────────────────
   БЛОК «МАТЕРИАЛЫ О СОБЫТИЯХ» И ЛИПКАЯ КОЛОНКА С МЕСТАМИ `_4` И `_7`
   (шаг 4, 14.08.2026)

   Раскладка — `v-split` шаблона: колонка материалов и колонка 300 под
   рекламу. Своя пара классов, а не `v-split` напрямую, потому что вид
   переключается наличием материалов, и переключать надо ОДНУ разметку:
   двух копий кодов мест в шаблоне быть не должно (заслон `ad-slots-exist`
   сверяет последовательность кодов со списком `slotCodesFor`).
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ ПОЛОСА — СПИСОК, НО `vg-list-reset` ЕЙ НЕ ГОДИТСЯ. Он обнуляет поля
   короткой записью (`padding:0;margin:0`), а `.v-rail` держит на них ленту:
   `margin-inline` отрицательное и `padding-inline` симметричное — тем самым
   полоса на узком экране уезжает под края контейнера и прокручивается от
   кромки до кромки. Короткая запись стёрла бы оба.

   Поймано не глазами: заслон `own-css-shorthand.spec.ts`, заведённый ранее
   ровно на этот класс ошибок, покраснел на первом же прогоне. Поэтому здесь
   обнуляется ТОЛЬКО поперечная ось, которой система не касается. */
.vg-rail-list{list-style:none;margin-block:0;padding-block:0}

/* ⚠ ВРЕМЕННО: номер рекламного места на заглушке (признак `AD_SLOT_NUMBERS`,
   снимается перед открытием — п. 25 чек-листа). Кружок рядом с фразой, не
   поверх неё: заглушка бывает высотой в 56 точек, и накладка на текст сделала
   бы нечитаемым и то и другое. */
.vg-ad__no{display:inline-grid;place-items:center;min-width:20px;height:20px;
  margin-left:var(--s-8);padding:0 5px;border-radius:var(--r-circle);
  background:var(--ink-800);color:#fff;font-size:12px;font-weight:600;
  font-variant-numeric:tabular-nums}

/* ─────────────────────────────────────────────────────────────────────────
   КНОПКИ ПРОКРУТКИ ПОЛОСЫ — В СТРОКЕ ЗАГОЛОВКА (15.08.2026)

   Вид берётся у системы целиком: `.v-rail__nav` — круг 44×44, белый, с рамкой
   и тенью, внутри угловатый шеврон. Здесь только МЕСТО, и оно назначено
   владельцем: справа в строке заголовка, туда, где стояла убранная подпись.

   ⚠ Поэтому снимается абсолютное позиционирование системы (`position:absolute`,
   `left/right:4px`, `top:38%`) и её же правило показа: у неё кнопки видны
   только при настоящем указателе, а в строке заголовка они не накрывают
   карточки и нужны на планшете тоже.

   ⚠ На краю кнопка ГАСНЕТ, а не исчезает: у системы `hidden`, но в строке
   заголовка исчезновение дёргало бы всю строку при каждом касании края.
   Гашение — тем же приглушённым цветом, что у крайней стрелки пагинации.
   ───────────────────────────────────────────────────────────────────────── */
.vg-bandhead__side{display:flex;align-items:center;gap:var(--s-8)}
.vg-railnav{display:flex;align-items:center;gap:var(--s-8)}
.vg-railnav[hidden]{display:none}

.vg-railnav__btn{position:static;display:grid;width:44px;height:44px}
.vg-railnav__btn.is-disabled{color:var(--text-faint);border-color:var(--border-subtle);
  box-shadow:none;pointer-events:none}

/* ⚠ РАЗМЕР — 44×44, РОВНО КАК У `.v-rail__nav` СИСТЕМЫ. Здесь стояло 36, а
   рядом лежал комментарий, будто это уменьшение «до зоны касания на телефоне»:
   правило при этом не было ни в каком медиазапросе и действовало на всех трёх
   ширинах, а `rail-nav.njk` тем временем обещал «круг 44×44, вид берётся у
   системы целиком». Три описания одного места, и все три расходились с ним.
   Поймано замером 15.08.2026 (`scripts/measure-rails.js`), из разметки не
   читалось вовсе — ровно то, о чём правило 2c.

   На телефоне 44 нужнее, чем где-либо: у системы кнопок там нет вовсе
   (`@media (hover:hover) and (pointer:fine)`), а у нас они видны и их
   нажимают пальцем.

   На телефоне подпись и стрелки в одной строке с заголовком не помещаются —
   строка переносится сама (`.v-bandhead{flex-wrap:wrap}` у системы). */
@media (max-width:767px){
  .vg-bandhead__side{width:100%;justify-content:space-between}
}

/* ─────────────────────────────────────────────────────────────────────────
   ПАГИНАЦИЯ: КРАЙНЯЯ СТРЕЛКА И ВИД НА ТЕЛЕФОНЕ

   У компонента системы крайняя стрелка `disabled`; у нас это `span`, и
   гасится он здесь. Цвет — тот же приглушённый, что у разрыва `…`.

   «N из M» и номера меняются местами по ширине: на телефоне номера прячутся,
   остаются стрелки и величина словами (`mobile.md`); от 768 — наоборот.
   Прячется `display`, а не видимость: спрятанное `visibility` продолжает
   занимать место, и ряд остался бы прежней ширины.
   ───────────────────────────────────────────────────────────────────────── */
.v-pager__item.is-disabled{color:var(--text-faint);pointer-events:none}
.vg-pager__of{display:none;color:var(--text-secondary);font-size:15px;
  font-variant-numeric:tabular-nums;padding:0 var(--s-8)}

@media (max-width:767px){
  .vg-pager__num{display:none}
  .vg-pager__of{display:inline-flex;align-items:center}
}

/* ─────────────────────────────────────────────────────────────────────────
   «ПОКАЗАТЬ ЕЩЁ N» — вместо пагинации в списке афиши (правка 6 владельца
   20.08.2026).

   ⚠ ВИД ВЗЯТ У ПОРТАЛА, А НЕ ПРИДУМАН: точно так же подписана и одета кнопка
   «Показать ещё N отзывов» на странице объекта (`v-btn v-btn--ghost` в
   абзаце). Своего компонента у системы для этого нет вовсе — в её шаблоне
   списка стоит `Pagination`, — и заводить второй вид одного действия на одном
   портале не за чем.

   Отступление одно и названо: кнопка ПО ЦЕНТРУ колонки, тогда как у отзывов
   она прижата влево. Здесь она стоит под сеткой во всю ширину колонки, и
   прижатая влево читалась бы как ссылка последней плитки.

   На телефоне — во всю ширину: зона касания и без того самая частая на
   странице, а ряд там один. Это то же правило, по которому `Segmented`
   растягивается на 390 (`mobile.md`).
   ───────────────────────────────────────────────────────────────────────── */
.vg-more{display:flex;justify-content:center;margin:0}
@media (max-width:767px){
  .vg-more .v-btn{width:100%}
}

.vg-afmat{display:grid;grid-template-columns:minmax(0,1fr) 300px;
  gap:var(--s-40);align-items:start;padding-block:var(--s-32)}

/* Крупная статья и три строкой — пропорции шаблона. */
.vg-afmat__grid{display:grid;grid-template-columns:3fr 2fr;gap:var(--s-24)}

/* Заголовок крупной статьи — размер из компонента (`size="lg"`). */
.vg-material__title{font-size:26px;line-height:32px}

/* В липкой колонке места стоят стопкой, и поля полосы там лишние: расстояние
   между ними задаёт `v-stack`. */
.vg-afmat .vg-adband{padding-block:0}

/* ⚠ МАТЕРИАЛОВ НЕТ — МЕСТА ВСЁ РАВНО СТОЯТ ПРАВОЙ КОЛОНКОЙ (решение
   владельца 15.08.2026, после замера).

   Прежде они растягивались полосами во всю ширину и прижимались влево:
   `_4` выходил 1216×648, `_7` — 1216×273, хотя сам креатив внутри оставался
   300 точек. На полигоне это списывали на пустоту данных, но владелец назвал
   главное: материалов о мероприятиях НЕ БУДЕТ и на живом портале, пока
   кто-нибудь не напишет статью с упоминанием события. То есть «голый» блок —
   не редкое состояние, а обычное на ближайшие месяцы, и смотреть на него
   людям.

   Решение: сетка остаётся ДВУХКОЛОНОЧНОЙ и в пустом состоянии, а лента мест
   явно ставится во вторую колонку. Без `grid-column` она попала бы в первую
   (единственный ребёнок садится в первую дорожку) и осталась бы слева.

   Пустого поля на 1216 это не создаёт: левая колонка пуста, а высоту строки
   задаёт правая. Замер 15.08.2026: сами места были `1216×648` и `1216×273`,
   стали `300×600` и `300×225`; высота всей страницы 7282 → 7266, то есть
   короче на 16 точек. (Числа именно замеренные: выведенная «экономия в 72
   точки» не подтвердилась — липкая колонка перекрывается с соседями иначе.)

   «Инвентарь дороже полос» (решение 14.08.2026) остаётся в силе: места не
   исчезают, они меняют место стояния. */
.vg-afmat--bare{display:grid;grid-template-columns:minmax(0,1fr) 300px;
  gap:var(--s-40);align-items:start;padding-block:var(--s-32)}
.vg-afmat--bare .vg-afmat__rail{grid-column:2}
.vg-afmat--bare .vg-adband{padding-block:0}

@media (max-width:1023px){
  /* ⚠ ЗДЕСЬ ПРИМЕНЕНА ЦЕНА ВАРИАНТА B, названная в решении
     `2026-08-14_Первый_экран_телефона.md`, разд. 5: колонка перестаёт быть
     боксом, и КАЖДОМУ её ребёнку нужен свой порядок. Это ровно тот случай, о
     котором там предупреждено, — блок материалов приехал в колонку следующим
     же шагом.

     Порядок — из `mobile.md` поставки: место `_4` встаёт ПЕРЕД блоком
     материалов, `_7` — после него. Липкость на узком экране отключается сама:
     у `display:contents` бокса нет, а с ним нет и `position:sticky`. */
  .vg-afmat,.vg-afmat--bare{grid-template-columns:1fr;gap:var(--s-24)}
  .vg-afmat__grid{grid-template-columns:1fr}
  /* На узком экране колонки нет вовсе — правило второй дорожки снимается,
     иначе `grid-column:2` указывал бы на несуществующую. */
  .vg-afmat--bare .vg-afmat__rail{grid-column:auto}
  .vg-afmat__rail{display:contents}
  .vg-afmat__rail > [data-vg-slot="CATEGORY_AFISHA_4"]{order:0}
  .vg-afmat__main{order:1}
  .vg-afmat__rail > [data-vg-slot="CATEGORY_AFISHA_7"]{order:2}
  /* В одну колонку места снова полосы — и поля им возвращаются. */
  .vg-afmat .vg-adband{padding-block:var(--s-24)}
}

/* ⚠ Форматы `side` и `card` нарисованы под колонку 300: во всю ширину полосы
   пропорция даёт 1216×2432 и 1216×912 — замер 14.08.2026 показал это сразу,
   сперва на заглушках, потом на занятых местах. Ограничение остаётся и после
   переезда в колонку: без материалов места снова стоят полосами. На узком
   экране колонки нет и у шаблона, ограничение не нужно. */
@media (min-width:1024px){
  /* ⚠ ДЕТЬМИ ПОЛОСЫ БЫВАЮТ ДВА РАЗНЫХ УЗЛА, и первая редакция знала только
     один. Свободное место — это `.v-ad` напрямую, а занятое — обёртка
     `.v-adwrap` с баннером внутри. Селектор `> .v-ad` попадал только в
     заглушку: пока рекламы не было, полоса держалась в 300, а с первой же
     кампанией `_4` разъезжался в 1216×2432, `_7` — в 1216×912.

     Числа замера 14.08.2026 — журнал шаг 3.98.

     Поэтому ограничение стоит на ЛЮБОМ ребёнке полосы: обёртке или самому
     месту. Обёртка ограничивается вместе с баннером намеренно — три точки и
     меню объявления позиционируются относительно `.v-adwrap`, и при её
     ширине 1216 они уехали бы к правому краю страницы, за полтысячи точек от
     собственного баннера.

     ⚠ HOME_5/HOME_7 — другая причина: 2026-09-18_Липкость… разд. 4. */
  .vg-adband--col > *{max-width:300px}
}

/* Неразрывный кусок строки: применяется там, где перенос уводит знак
   препинания в начало строки. */
.vg-nowrap{white-space:nowrap}


/* ─────────────────────────────────────────────────────────────────────────
   ЖАНРОВЫЙ БЛОК ВИТРИНЫ АФИШИ (15.08.2026)

   Разметка одна — `.v-rail` дизайн-системы, — а вид разный, и это решение
   владельца: на телефоне восемьдесят карточек витрины в одну колонку дали бы
   страницу, которую он сам назвал неподъёмной, а показывать по четыре вместо
   восьми значит отдать половину содержимого. Поэтому на телефоне блок
   остаётся лентой с прокруткой, а от 768 разворачивается в сетку.

   ⚠ ГРАНИЦЫ И ЧИСЛО КОЛОНОК ВЗЯТЫ У `.v-grid--4` СИСТЕМЫ, а не назначены
   заново: 4 колонки от 1024, 2 в промежутке 768–1023. Иначе на одной странице
   оказались бы две разные сетки — у выдачи своя, у витрины своя, — и
   расходились бы они с первой же правки системы.

   Отменяется ровно то, чем `.v-rail` является лентой: направление потока,
   ширина авто-колонок, прокрутка, доводчик и отрицательные поля, которыми
   лента вылезает под края контейнера. Всё остальное (зазор, `--boxed`,
   выравнивание плиток по высоте) остаётся общим с лентами.
   ───────────────────────────────────────────────────────────────────────── */
/* ⚠ ГРАНИЦА — 1024, А НЕ 768. Первая редакция разворачивала блок в сетку уже
   с 768, и по две колонки: восемь карточек вставали ЧЕТЫРЬМЯ рядами, а
   страница на планшете вырастала до 35 017 точек — вдвое выше, чем на 1280
   (замер 15.08.2026). Это ровно та беда, ради которой прокрутку и заводили.

   768 у нас и не «широкий экран»: до 1023 включительно на афише работает
   шторка фильтров, то есть рама ещё мобильная. Лента живёт до той же
   границы, а сетка 2×4 начинается там же, где раскладка становится
   настольной. */
@media (min-width:1024px){
  .vg-gblock__grid{grid-auto-flow:row;grid-auto-columns:auto;
    grid-template-columns:repeat(4,minmax(0,1fr));
    overflow-x:visible;scroll-snap-type:none;
    margin-inline:0;padding-inline:0;scroll-padding-inline:0}
}


/* ─────────────────────────────────────────────────────────────────────────
   ЛИПКИЙ ОТСТУП — ОТ ПОЛНОЙ ВЫСОТЫ ШАПКИ (20.08.2026)

   Замечание владельца: боковое место залипает слишком высоко и заезжает под
   верхнюю панель. Замер показал причину, и она не та, о которой думали: у
   афиши и у страницы объекта отступ ОДИНАКОВЫЙ — 88 точек, — так что «взять
   как в шаблоне карточки» ничего бы не починило, там ровно тот же дефект.

   Система считает отступ правильно: `top: calc(var(--header-h) + var(--s-16))`.
   Неверна величина: `--header-h` = 72 — это высота ОДНОЙ строки шапки
   (`.v-header__bar`), а наша шапка липкая ЦЕЛИКОМ и состоит из двух строк:
   логотип с городом и ряд разделов. Замер 20.08.2026: 117 точек на 1280, 149
   на 768, 197 на 390. Отступ 88 против шапки 117 — колонка уходит под неё на
   29 точек.

   Сам `--header-h` не трогаем: он задаёт ВЫСОТУ первой строки
   (`.v-header__bar{height:var(--header-h)}`), и подмена растянула бы шапку.
   Поэтому заведена своя величина полной высоты, и ею переопределены оба
   липких правила системы — у витрин и у страницы объекта.

   ⚠ Числа здесь ЗАМЕРЕНЫ и потому стареют. Стережёт их заслон «липкий отступ
   не меньше высоты шапки»: вырастет шапка — покраснеет тест, а не всплывёт у
   владельца. Долг «мобильная шапка вдвое выше шаблонной» остаётся отдельным:
   когда шапку приведут к системной, эти строки уйдут вместе с ним.
   ───────────────────────────────────────────────────────────────────────── */
:root{--vg-header-total:117px}
@media (max-width:1023px){:root{--vg-header-total:105px}}
@media (max-width:767px){:root{--vg-header-total:153px}}

.v-sticky,.v-objgrid__stick{top:calc(var(--vg-header-total) + var(--s-16))}

/* Нижняя область без липкости — когда рядом нечему прокручиваться (порог
   `TAIL_REGION_MIN_HEIGHT`). Места остаются на экране и той же величины,
   просто стоят колонкой без залипания: инвентарь дороже прокрутки. */
.vg-tail--flat{display:grid;grid-template-columns:minmax(0,1fr) 300px;
  gap:var(--s-40);align-items:start;padding-block:var(--s-32)}
.vg-tail--flat .v-catalog__aside{position:static;grid-column:2}
.vg-tail--flat .vg-adband{padding-block:0}
/* ⚠ НА УЗКОМ ЭКРАНЕ КОЛОНКА ПЕРЕСТАЁТ БЫТЬ БОКСОМ (`display:contents`), и
   баннер встаёт В ПОТОК — ПЕРЕД содержимым своей области. Правило было у
   прежней обёртки блока материалов, переехало к местам 20.08.2026 утром и
   переписано вечером на три области: у каждой свой единственный баннер, и
   порядок «баннер → содержимое» один на все три.

   Прежде здесь стояли `order:0` для `_4` и `order:2` для `_7`, потому что оба
   лежали в ОДНОЙ колонке и их надо было развести по разные стороны
   содержимого. Колонок стало три, разводить больше нечего.

   Липкость на узком экране отключается сама: у `display:contents` бокса нет, а
   с ним нет и `position:sticky`. */
@media (max-width:1023px){
  .vg-tail--flat,.v-catalog.vg-afisha,.v-catalog.vg-catalog{grid-template-columns:1fr;gap:var(--s-24)}
  /* ⚠ И ДОРОЖКА У КОЛОНКИ СНИМАЕТСЯ ВМЕСТЕ С СЕТКОЙ. `grid-column:2` в сетке
     из одной дорожки заводит ВТОРУЮ, неявную: содержимое сжимается, чтобы
     освободить место справа. На 390 это дало плитки шириной 278 вместо 358 и
     боковое место 278×93 вместо 358×119.

     Прежде правило не кусалось: плоской бывала только нижняя область, а у неё
     на узком экране колонка `display:contents` и применять `grid-column` не к
     чему. С 20.08.2026 (вечер) плоской бывает и ПЕРВАЯ область — на короткой
     выдаче, — а её колонка боксом остаётся: там свой порядок из `mobile.md`.
     Найдено полным замером по 2c, до показа владельцу. */
  .vg-tail--flat .v-catalog__aside{grid-column:auto}
  /* ⚠ ВТОРАЯ И ТРЕТЬЯ ОБЛАСТИ — баннер ПЕРЕД своим содержимым. У первой
     порядок свой и другой (выше): там баннер стоит ПОСЛЕ первой порции
     карточек, иначе выдача уезжает за первый экран. */
  [data-vg-region="2"] .v-catalog__aside,
  [data-vg-region="3"] .v-catalog__aside{display:contents}
  [data-vg-region="2"] .v-catalog__aside > [data-vg-slot],
  [data-vg-region="3"] .v-catalog__aside > [data-vg-slot]{order:0}
  [data-vg-region="2"] .vg-afisha__main,
  [data-vg-region="3"] .vg-afisha__main,
  [data-vg-region="2"] .vg-catalog__main,
  [data-vg-region="3"] .vg-catalog__main{order:1}
}

/* ─────────────────────────────────────────────────────────────────────────
   ПАНЕЛЬ ФИЛЬТРА: ПОИСК ПО ВАРИАНТАМ (22.08.2026)

   Само устройство панели — системное и правок не требует: `chips.css` уже
   даёт слот `.v-fdrop__search`, тело с `max-height:300px; overflow-y:auto` и
   подвал ЗА пределами тела. Замер 22.08 подтвердил числами: тело 300 при
   полной высоте 650, подвал целиком в экране на всех трёх ширинах. Пункт
   владельца «содержимое прокручивается, кнопки остаются на месте» уже
   выполнен поставкой — мы просто не заводили поиск.

   Здесь только то, чего у системы нет и быть не может: она прячет
   несовпавшие строки состоянием React-компонента, а у нас одна разметка на
   все ширины и без скрипта.
   ───────────────────────────────────────────────────────────────────────── */

/* ─────────────────────────────────────────────────────────────────────────
   БАННЕР СОГЛАСИЯ: МЕСТО, КОТОРОЕ ОН ЗАНИМАЕТ (22.08.2026)

   Замер до правки: 323 точки на 320-точечном экране — **40 % первого экрана**.
   Обход портала в состоянии «первый визит» нашёл 19 состояний, где баннер
   перекрывал нажимаемые элементы: управление лентой афиши, ряд выбранного и
   сортировку списка, «Показать все» у блока рубрики и — на всех трёх ширинах
   — ссылки подвала «Оферта», «Политика обработки данных», «Файлы cookie».
   Последнее особенно: баннер закрывал ссылку на политику, на которую сам же и
   ссылается.

   Здесь две правки: поля потеснее на узком экране и НИЗ, ЗА КОТОРЫЙ МОЖНО
   ДОЛИСТАТЬ. Само сокращение текста — в шаблоне баннера.
   ───────────────────────────────────────────────────────────────────────── */

/* ⚠ ПОДВАЛ ОБЯЗАН БЫТЬ ДОСТИЖИМ. Баннер закреплён у нижнего края и висит до
   решения; всё, что оказалось под ним в конце страницы, без этого отступа
   нельзя ни увидеть, ни нажать — долистать некуда. Класс ставит сервер: CSS
   не умеет спросить «есть ли ниже такой элемент».

   Величина — с запасом и по ширинам, из замера сокращённого баннера. Точную
   высоту подставляет скрипт (`--vg-consent-h`), но и без него подвал
   достижим: запасной путь важнее точности. */
.vg-consent-on{padding-bottom:var(--vg-consent-h, 150px)}
@media (max-width:767px){.vg-consent-on{padding-bottom:var(--vg-consent-h, 230px)}}

/* На узком экране поля баннера теснее: 20 по кругу при ширине 288 съедают
   пятую часть строки. */
@media (max-width:479px){
  .v-consent{padding:var(--s-14,14px);gap:var(--s-10)}
}

/* Панель, развёрнутая ВВЕРХ, — когда под таблеткой места меньше, чем над ней.
   Сторона выбирается скриптом; здесь только положение. */
.v-fdrop__panel.is-up{top:auto;bottom:calc(100% + 8px)}

/* ⚠ ШЕВРОН ТАБЛЕТКИ СМОТРИТ ВВЕРХ, КОГДА ПАНЕЛЬ РАСКРЫТА. У компонента это
   ДРУГОЙ значок (`chevronUp` вместо `chevronDown`), у нас разметка одна на оба
   состояния — поворачиваем тот же. Результат на экране тот же, второго значка
   в разметке не появляется. */
.v-fdrop__pill.is-open .icon{transform:rotate(180deg)}

/* ⚠ ДЛИННАЯ ПОДПИСЬ ВАРИАНТА ПЕРЕНОСИТСЯ, А НЕ ВЫЛЕЗАЕТ ЗА ПАНЕЛЬ.

   Замечание владельца 22.08.2026: «на телефоне текст фильтров выходит за
   границы». Замер нашёл это у «Удобств» на 320: подписи вроде «Путёвки ФСС /
   соцстраха» и «Кондиционер в номерах» вылезали за панель на 13 точек.

   Причина — не в длине текста, а в правиле сеток: система кладёт флаги в две
   колонки (`.v-filters__flags{grid-template-columns:1fr 1fr}`), а у элемента
   сетки `min-width` по умолчанию `auto`, то есть он НЕ СЖИМАЕТСЯ УЖЕ СВОЕГО
   СОДЕРЖИМОГО. Колонка в 1fr при этом честно просит половину — и подпись
   вылезает наружу. То же и у самой строки: `.v-check` у системы
   `inline-flex`, и её текстовой части тоже нужен нулевой минимум, иначе
   переносить нечего.

   ⚠ `overflow-wrap:break-word` — на случай слова, которое не переносится вовсе
   («Опорно-двигательный» в колонке 140 точек). Обычный перенос по пробелам
   тут не спасает: пробела внутри слова нет. */
.v-filters__flags > .v-check{min-width:0}
.vg-fdrop__label{min-width:0;overflow-wrap:break-word}

/* ⚠ НА САМЫХ УЗКИХ — ОДНА КОЛОНКА. Числа: лист на 320 шириной 288, поля тела
   40, зазор сетки 12 → на колонку 118, минус коробка 22 и зазор 10 = 86 точек
   под текст. «Кондиционер» занимает 88, и слово рвётся посреди себя. От 360
   под текст остаётся 106 — двух колонок хватает, и сетка системы не трогается.

   Снимок показал то, чего числа не сказали: вылета за панель уже не было, а
   читать было нельзя. Правило владельца «снимайте и смотрите сами». */
@media (max-width:359px){
  .v-filters__flags{grid-template-columns:1fr}
}

/* Строка, не совпавшая с поиском. Прячется `display`, а не `visibility`:
   спрятанное `visibility` держало бы высоту, и список «сузился» бы в пустоту
   ровно той же длины. */
.vg-fdrop__row--off{display:none}

/* Слово вместо пустой панели: пустота без объяснения читается как поломка. */
.vg-fdrop__none{margin:0;padding-block:var(--s-8)}

/* ⚠ ПОЛЕ ПОИСКА ВОЗВРАЩАЕТСЯ К СВОЕМУ РАЗМЕРУ. `.v-search` в системе описан
   дважды: в `forms.css` — сам компонент, а в `ds2/css/layout.css` — крупная
   строка поиска ПО ПОРТАЛУ (высота 60, поля 24, тень). Второе правило идёт
   позже и потому достаётся всем `.v-search`, включая поле внутри панели
   фильтра, где шестидесятиточечная строка занимает пятую часть панели.
   Отменяем ровно три величины и только внутри панели. */
.v-fdrop__search .vg-fdrop__search{height:auto;padding-inline:0;box-shadow:none}

/* ⚠ Звезда категории золотая (`--sand-500`) ВЕЗДЕ, кроме инвертированного
   фона: внутри выбранного чипса весь текст белый по правилу системы, и
   золотая звезда на синем читается хуже белой. Это не второе правило цвета, а
   то же самое — «звезда цвета текста своего окружения», просто окружение тут
   инвертировано. `.v-hotelstars` задаёт свой `color`, поэтому наследование
   надо вернуть явно. */
.v-chip--brand .v-hotelstars,.v-chip.is-active .v-hotelstars{color:inherit}

/* ⚠ ССЫЛКА РЕКЛАМОДАТЕЛЯ НА СТРАНИЦЕ ПРОВЕРКИ КРЕАТИВА.
   Единственная строка этого экрана, которую приносят СНАРУЖИ и в которой может
   не оказаться ни одного места переноса. Замер 11.09.2026 (шаг 3.147): адрес в
   200 знаков без дефисов растянул ячейку до 2039 точек при ширине экрана 390 —
   содержимое ушло за край панели и стало недостижимым. В разметке этого не
   видно вовсе, ровно как описывает правило 2c.
   `anywhere`, а не `break-word`: второй не рвёт слово, которое уже начало свою
   строку, и длинный токен всё равно уезжает. `min-width:0` — потому что ячейка
   `.v-formrow` сеточная, и без него она отказывается сжиматься. */
.vg-adurl{overflow-wrap:anywhere;min-width:0}

/* Плитки героя ужимаются ниже содержимого — иначе вылет на 320.
   Разбор: docs/frontend/Замер_мобильных_ширин.md */
@media (max-width:767px){
  .v-cityhero__tiles>.v-tile{min-width:0;padding:var(--s-12)}
  .v-cityhero__tiles .v-tile__name{font-size:16px;overflow-wrap:anywhere}
  .v-bandhead__title{flex-wrap:wrap;min-width:0}
  .v-bandhead__count{white-space:normal}
}

.vg-confirmrow{align-items:flex-end}
.vg-confirmrow--roomy>.v-input{flex:1 1 calc(var(--s-120) * 2);width:auto}

@media (max-width:767px){
  .vg-page-home [data-vg-slot$="_4"] .v-ad,
  .vg-page-home [data-vg-slot$="_8"] .v-ad{aspect-ratio:2.5/1}
  .vg-page-home [data-vg-slot$="_2"] .v-ad,
  .vg-page-home [data-vg-slot$="_3"] .v-ad,
  .vg-page-home [data-vg-slot$="_5"] .v-ad,
  .vg-page-home [data-vg-slot$="_6"] .v-ad{aspect-ratio:10/7}
}

.vg-header__search{position:relative}
.vg-search-drop{position:absolute;z-index:40;top:calc(100% + var(--s-8));left:0;width:640px;min-width:100%;max-width:calc(100vw - 2 * var(--grid-margin));
  max-height:calc(100vh - 96px);display:flex;flex-direction:column;background:var(--surface-page);border:1px solid var(--border-subtle);
  border-radius:var(--r-dense);box-shadow:var(--shadow-4);overflow:hidden}
.vg-search-drop[hidden]{display:none}
.vg-search-drop [role="listbox"]{overflow-y:auto;padding-block:var(--s-4)}
.vg-search-group + .vg-search-group{border-top:1px solid var(--border-subtle)}
.vg-search-group__head{display:flex;align-items:center;justify-content:space-between;gap:var(--s-12);padding:var(--s-10) var(--s-14) var(--s-4)}
.vg-search-group__head .v-btn--link{font-size:var(--t-caption-size)}
.vg-search-group__title{font-size:var(--t-micro-size);line-height:var(--t-micro-lh);letter-spacing:var(--t-micro-ls);text-transform:uppercase;color:var(--text-muted);font-weight:600}
.vg-search-row{display:grid;grid-template-columns:48px minmax(0,1fr);grid-template-areas:"media title" "media sub";column-gap:var(--s-12);align-items:center;
  min-height:var(--tap-min);padding:var(--s-6) var(--s-14);color:var(--text-primary);text-decoration:none}
.vg-search-row:hover,.vg-search-row[aria-selected="true"]{background:var(--surface-hover)}
.vg-search-row__media{grid-area:media;width:48px;height:48px;border-radius:var(--r-sm);object-fit:cover;background:var(--surface-sunken)}
.vg-search-row__media--poster{width:32px;height:48px;justify-self:center}
.vg-search-row__media--empty{display:grid;place-items:center;color:var(--text-faint)}
.vg-search-row__title{grid-area:title;align-self:end;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--t-body-size);line-height:var(--t-body-lh)}
.vg-search-row__title mark{background:none;color:inherit;font-weight:700}
.v-card__title mark{background:var(--surface-brand-soft);color:inherit;border-radius:var(--r-xs)}
.vg-search-row__sub{grid-area:sub;align-self:start;overflow:hidden;white-space:nowrap;text-overflow:ellipsis;font-size:var(--t-caption-size);line-height:var(--t-caption-lh);color:var(--text-muted)}
.vg-search-row__title.v-skel{width:60%;height:14px}
.vg-search-row__sub.v-skel{width:40%;margin-top:var(--s-4)}
.vg-search-all{display:block;padding:var(--s-12) var(--s-14);border-top:1px solid var(--border-subtle);color:var(--text-link);text-decoration:none;font-size:var(--t-body-sm-size)}
.vg-search-all:hover,.vg-search-all[aria-selected="true"]{background:var(--surface-hover)}
.vg-search-note{padding:var(--s-12) var(--s-14);color:var(--text-muted);font-size:var(--t-body-sm-size)}
.vg-search-keys{padding:var(--s-8) var(--s-14);border-top:1px solid var(--border-subtle);background:var(--surface-sunken);color:var(--text-muted);font-size:var(--t-caption-size)}
.vg-search-keys kbd{display:inline-block;min-width:20px;margin-right:2px;padding:0 var(--s-4);border:1px solid var(--border-subtle);border-radius:var(--r-xs);background:var(--surface-page);font:inherit;text-align:center}

.vg-search-toggle{display:none}
.v-search:has(.v-search__clear) .v-search__input::-webkit-search-cancel-button{-webkit-appearance:none;display:none}
@media (max-width:1023px){
  .v-header__bar{position:relative}
  .vg-header__search{position:static}
}
@media (max-width:1023px),(pointer:coarse){
  .vg-header__search .v-search__input{font-size:16px}
  .vg-search-keys{display:none}
}
@media (max-width:767px){
  .vg-header__search{display:none}
  .vg-search-toggle{display:inline-flex;color:var(--text-primary)}
}
.vg-search-sheet{position:fixed;top:0;left:0;right:0;height:100dvh;z-index:100;display:flex;flex-direction:column;background:var(--surface-page);
  padding:env(safe-area-inset-top) env(safe-area-inset-right) 0 env(safe-area-inset-left)}
.vg-search-sheet__bar{display:flex;align-items:center;gap:var(--s-4);padding:var(--s-8) var(--grid-margin) var(--s-8) var(--s-4);border-bottom:1px solid var(--border-subtle)}
.vg-search-sheet__bar .v-search{flex:1;min-width:0}
.vg-search-sheet__back{flex:none;color:var(--text-primary)}
.vg-search-sheet .v-search__input{font-size:16px;height:var(--control-h-lg)}
.vg-search-sheet .vg-search-drop{position:static;flex:1;min-height:0;width:auto;min-width:0;max-width:none;max-height:none;
  border:0;border-radius:0;box-shadow:none;padding-bottom:env(safe-area-inset-bottom)}
.vg-search-sheet .vg-search-keys{display:none}
.vg-search-sheet .vg-search-row{grid-template-columns:56px minmax(0,1fr);min-height:56px;padding-inline:var(--grid-margin)}
.vg-search-sheet .vg-search-row__media{width:56px;height:56px}
.vg-search-sheet .vg-search-row__media--poster{width:40px;height:60px}
.vg-search-sheet .vg-search-group__head,.vg-search-sheet .vg-search-all,.vg-search-sheet .vg-search-note{padding-inline:var(--grid-margin)}
.vg-search-row:has(.vg-search-row__remove){grid-template-columns:48px minmax(0,1fr) auto;grid-template-areas:"media title remove";cursor:pointer}
.vg-search-row:has(.vg-search-row__remove) .vg-search-row__title{align-self:center}
.vg-search-row__remove{grid-area:remove;min-width:var(--tap-min);min-height:var(--tap-min)}
.vg-search-sheet .vg-search-row:has(.vg-search-row__remove){grid-template-columns:56px minmax(0,1fr) auto}
.vg-search-group>.v-chiprow{padding:var(--s-4) var(--s-14) var(--s-12)}
.vg-search-sheet .vg-search-group>.v-chiprow{padding-inline:var(--grid-margin)}
button.vg-search-all{width:100%;text-align:left;background:none;border-inline:0;border-bottom:0;cursor:pointer;font-family:inherit}

.vg-news-row{align-items:flex-start;color:inherit}
.vg-news-row__text{flex:1;min-width:0;display:flex;flex-direction:column;gap:var(--s-4)}
.vg-news-row__title{margin:0;color:var(--text-primary);overflow-wrap:break-word;transition:color var(--dur-fast) var(--ease-out)}
.vg-news-row:hover .vg-news-row__title{color:var(--text-link-hover)}
.vg-news-row__cover{flex:none;width:var(--s-96)}
.vg-news-row__cover img{display:block}
@media (min-width:768px){.vg-news-row__cover{width:160px}}
h1,h2,h3,h4,.v-objhero__name,.v-card__title,.v-tile__name,.v-modal__title,.v-empty__title{hyphens:none}
.v-news-item a{min-width:0;overflow-wrap:break-word}
/* Дата в «Новостях города»: колонка поставки (56) — под время, дата шире; «Гид по городу» — 3fr/2fr с 768 (docs/frontend/Даты_в_списках.md) */
.v-news-item__time{width:auto;min-width:56px;white-space:nowrap}
@media (min-width:768px){.vg-home-guide{grid-template-columns:3fr 2fr}}
.vg-approve-missing{margin:0;color:var(--status-review-fg);font-weight:600}
