/* =============================================================
   МАММ — стили сайта
   Один файл, без сборки. Разделы идут в порядке каскада.
   ============================================================= */

@layer reset, base, layout, components, utilities;

/* -------------------------------------------------------------
   ТОКЕНЫ
   Единственное место, где живут значения цветов и размеров.
   ------------------------------------------------------------- */
:root {
  /* Только чёрный и красный. Серого в гамме нет: второстепенный текст,
     линии и рамки — того же чёрного, что и основной текст. Токены
     оставлены раздельными, чтобы позже можно было ввести второй уровень
     контраста, не переписывая компоненты. */
  --white: #fff;
  /* Второй уровень контраста. Заводится ровно под одно: сказать
     «нет в наличии» там, где у остальных стоит цена. Чёрным это
     читалось бы как цена, красным — как акция. 4,5:1 к белому,
     нижняя граница читаемости по WCAG. */
  --muted: #767676;
  --black: #111;
  --red: #ee342f;
  --red-dark: #c62520;

  --bg: var(--white);
  --text: var(--black);
  --text-muted: var(--black);
  --accent: var(--red);
  --accent-hover: var(--red-dark);
  --line: var(--black);
  --tick: var(--black);

  /* Светлая линейка. Единственное место, где она есть, — полоса под
     вкладками раздела: там чёрная линия спорила бы с красной чертой
     текущей вкладки. Значение из макета. */
  --line-soft: #eaeaea;

  /* Подложка под изображением до загрузки: без серого её просто нет */
  --placeholder: transparent;

  /* Единственный серый в гамме — подложка под лентой снимков.
     Кадр на ней отделяется от белой страницы, не заводя контура. */
  --surface: #f3f3f3;

  /* Отступы: шаг 8, крупные значения — для воздуха */
  --s-8: 8px;
  --s-16: 16px;
  --s-24: 24px;
  --s-32: 32px;
  --s-48: 48px;
  --s-64: 64px;
  --s-96: 96px;
  --s-160: 160px;

  /* Сетка */
  /* Ширина макета (Figma, MacBook Pro 14″): 1512 при полях 52,
     содержимое 1408. Раньше стояло 1440 при полях 64 — страница
     была уже макета, и все расстояния внутри неё считались
     от другого числа. */
  --page-max: 1512px;
  --prose-max: 62ch;
  --gutter: 20px;
  --col-gap: 8px;

  /* Высота кнопок в шапке. Круглая кнопка поиска берёт её и как ширину,
     поэтому размер не зависит от растяжения флекса: aspect-ratio на
     растянутом элементе в разных браузерах считается по-разному. */
  /* Кликабельная область иконок. На узком экране в шапке помещаются
     логотип, три знака и «Билет» — с 42px они не влезали в 362px. */
  --control: 38px;

  /* Предельная высота любой картинки в полосу: и ленты снимков, и
     одиночной обложки. Одна на всех — иначе обложка события
     разворачивалась по своим правилам и вырастала вдвое против
     ленты. Ширина у кадра своя, по пропорциям: фотографии не
     кадрируем, это работы, а не иллюстрации. */
  --media-h: 58vh;

  /* Общий рост логотипа и кнопки «Билет» — они стоят рядом
     и должны читаться как одна строка. */
  --brand-h: 34px;

  /* Расстояние от края окна до края контента. Нужно ленте, которая
     выходит за контейнер: пока окно уже --page-max, это просто поле
     страницы, дальше добавляются боковые отступы центрированного
     контейнера. Без процентов намеренно — в scroll-padding проценты
     считаются от самого контейнера прокрутки, а не от родителя. */
  --bleed: max(var(--gutter), calc((100vw - var(--page-max)) / 2 + var(--gutter)));

  --font: "PT Root UI", "Helvetica Neue", helvetica, arial, sans-serif;

  /* Шкала кеглей — шесть ступеней на весь сайт. Нового размера
     не заводим: берём ближайшую ступень.
     У --fs-xl и --fs-display общий минимум и общий наклон, поэтому
     на мобильном заголовок страницы и пункты меню одного размера,
     а на широком экране заголовок уходит вперёд. */
  --fs-xs: 12px;
  --fs-s: 14px;
  --fs-m: 17px;
  --fs-l: clamp(20px, 2.4vw, 28px);

  /* Кегль для чтения. Отдельный от --fs-m: тот работает в подписях
     и датах, где 17px в самый раз, а длинный текст выставки с него
     читается мелко. 19.3px — это 14.5 пункта, размер из макета. */
  --fs-read: 19.3px;
  --fs-xl: clamp(30px, 6vw, 44px);
  --fs-display: clamp(30px, 6vw, 64px);

  /* Начертание заголовков. У PT Root UI ступени 300 / 400 / 500 / 700 / 900,
     поэтому шаг вниз от Bold — это Medium, а не мифические 600. */
  /* Начертаний два: Medium под текст, Bold под заголовки
     и выделения. Regular в наборе нет. */
  --fw-text: 500;
  --fw-heading: 700;

  /* Оптическая компенсация левого края.
     Круглая прописная «С» визуально западает внутрь, хотя
     геометрически стоит ровно на границе контента; поправка
     заголовка в em, чтобы росла вместе с кеглем.

     В PT Root UI чернила заголовка при -0.04em встают на два
     пикселя левее крошек — ровно как в макете (заголовок 52,
     крошки 54). Число привязано к шрифту: полукегельная у него
     втрое уже, чем у широких экранных гарнитур, и при смене
     шрифта поправку надо мерить заново.

     Логотип, наоборот, отодвинут внутрь: в макете он стоит
     на 59 при кромке содержимого 52. Его первый штрих —
     диагональ, и вровень с текстом он вылезал. */
  --optical-title: -0.04em;
  --optical-logo: 7px;

  --ease: 0.4s cubic-bezier(0.2, 0, 0.2, 1);
}

@media (min-width: 768px) {
  :root {
    --gutter: 40px;
    --col-gap: 16px;
    --control: 42px;
  }
}

@media (min-width: 1024px) {
  :root {
    --control: 50px;
    /* Рост логотипа и билетной кнопки. В макете логотип 196×46,
       кнопка 48 — берём 48 на обоих: разницы в два пикселя
       не увидеть, а два токена вместо одного развели бы их. */
    --brand-h: 48px;
    --media-h: 546px;
  }
}

@media (min-width: 1280px) {
  :root {
    --gutter: 52px;
    --col-gap: 24px;
  }
}

/* Режим повышенной доступности: подмена токенов, без сторонних виджетов */
:root[data-a11y] {
  --text-muted: var(--black);
  --line: var(--black);
  --tick: var(--black);
  --accent: #b3231f;
}

/* -------------------------------------------------------------
   ШРИФТ

   PT Root UI, свои файлы в fonts/ — ни Google Fonts, ни другой
   чужой сервер не участвуют. Если файлы вдруг не отдаются,
   подхватывается запасной стек и вёрстка не ломается.

   Начертания два: Medium для текста и Bold для заголовков. Regular
   не подключён — разницы между 400 и 500 на экране почти нет,
   а лишнее начертание это лишние 45 КБ на каждой странице.
   ------------------------------------------------------------- */
@layer base {
  @font-face {
    font-family: "PT Root UI";
    src: url("../fonts/pt-root-ui-500.woff2") format("woff2");
    font-weight: 500;
    font-style: normal;
    font-display: swap;
  }

  @font-face {
    font-family: "PT Root UI";
    src: url("../fonts/pt-root-ui-700.woff2") format("woff2");
    font-weight: 700;
    font-style: normal;
    font-display: swap;
  }

  /* Атрибут hidden должен прятать что угодно. Браузерное правило
     «[hidden] { display: none }» слабее любого нашего display,
     и элемент со своим display: flex оставался на месте: значок
     корзины висел в шапке пустой пятидесятипиксельной ссылкой,
     а с появлением красного кружка стал ещё и виден. Важность
     в нижнем слое перебивает обычные объявления во всех верхних —
     это ровно тот случай, ради которого она и нужна. */
  [hidden] {
    display: none !important;
  }

  /* Границы и поля внутрь размера. Без этого контейнер с полями
     по 52 пикселя становится шире окна, и на всех страницах
     появляется горизонтальная прокрутка. */
  *,
  *::before,
  *::after {
    box-sizing: border-box;
  }

  * {
    margin: 0;
  }

  img,
  picture,
  svg {
    display: block;
    max-width: 100%;
  }

  /* У знаков прямые концы штрихов, скругляются только углы.
     CSS перебивает stroke-linecap из разметки. */
  svg :is(path, circle, line, polyline, rect) {
    stroke-linecap: butt;
    stroke-linejoin: round;
  }

  /* Поля формы шрифт не наследуют: браузер даёт им свой, Arial
     обычного начертания. На сайте начертаний два — Medium и Bold,
     и ничего легче Medium быть не должно, поэтому наследование
     задаём всем полям разом, включая переключатели и флажки:
     они спрятаны и рисуются своими значками, но начертание
     в них всё равно приезжало чужое. */
  button,
  input,
  select,
  textarea,
  optgroup {
    font: inherit;
    color: inherit;
  }

  ul {
    padding: 0;
    list-style: none;
  }

  @media (prefers-reduced-motion: reduce) {
    *,
    *::before,
    *::after {
      transition-duration: 0.01ms !important;
      animation-duration: 0.01ms !important;
    }
  }
}

/* -------------------------------------------------------------
   БАЗА
   ------------------------------------------------------------- */
@layer base {
  body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--font);
    font-size: var(--fs-m);
    font-weight: var(--fw-text);
    line-height: 1.45;
    -webkit-font-smoothing: antialiased;
  }

  h1 {
    font-size: var(--fs-display);
    font-weight: var(--fw-heading);

    /* 65 на кегле 64 — из макета. При 1,1 две строки заголовка
       занимали на девять пикселей больше, и всё, что ниже,
       уезжало от макета. */
    line-height: 1.016;
    letter-spacing: -0.005em;
    text-transform: uppercase;
    text-wrap: balance;

    /* см. --optical-title */
    margin-inline-start: var(--optical-title);
  }

  h2 {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1.15;
    text-wrap: balance;
  }

  h3 {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1.3;
    text-wrap: balance;
  }

  p {
    text-wrap: pretty;
  }

  a {
    color: var(--accent);
    text-underline-offset: 0.2em;
  }

  a:hover {
    color: var(--accent-hover);
  }

  :focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

  time {
    font-variant-numeric: tabular-nums;
  }
}

/* -------------------------------------------------------------
   РАСКЛАДКА
   ------------------------------------------------------------- */
@layer layout {
  .container {
    width: 100%;
    max-width: var(--page-max);
    margin-inline: auto;
    padding-inline: var(--gutter);
  }

  /* Вертикальный ритм: расстояние задаёт родитель, а не потомки */
  .stack {
    --stack-gap: var(--s-16);

    display: flex;
    flex-direction: column;
    gap: var(--stack-gap);
  }

  .stack--tight {
    gap: var(--s-8);
  }

  /* Средняя ступень ритма: 96 под табами — пустое поле в треть
     экрана, 32 — теснота. */
  .stack--mid {
    --stack-gap: var(--s-48);
  }

  .stack--wide {
    --stack-gap: var(--s-64);
  }

  .stack--page {
    gap: var(--s-96);
  }

  /* Контент начинается ближе к шапке: прежние 96px сверху
     отодвигали заголовок почти на треть первого экрана. */
  .section {
    padding-block: var(--s-48) var(--s-64);
  }

  /* Страница выставки: между шапкой и заголовком много воздуха,
     а заголовок с датами прижат к ленте — это одна группа. */
  .exhibition {
    display: flex;
    flex-direction: column;
    gap: var(--s-64);
  }

  .exhibition__intro {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
  }

  @media (min-width: 1024px) {
    .section {
      /* 112 сверху — расстояние от шапки до крошек в макете
         (Figma, 1512: низ шапки 75, верхушка крошек 193).
         В шкалу отбивок оно не ложится: шкала кончается на 96,
         а тут нужен именно этот воздух. */
      padding-block: 112px var(--s-64);
    }

    .exhibition {
      gap: var(--s-96);
    }

    .exhibition__intro {
      gap: var(--s-48);
    }

    .stack--wide {
      --stack-gap: var(--s-96);
    }
  }

  /* Сетка карточек — отдельно от колоночной сетки */
  .cards {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(280px, 100%), 1fr));
    gap: var(--s-32);
  }
}

@layer layout {
  /* Двенадцатиколоночная сетка — основа всех страниц.
     Мобильный: элемент по умолчанию занимает все 12 колонок.

     Ширина задаётся через grid-column-end, а не через сокращение
     grid-column: span N. Сокращение сбрасывает начало диапазона,
     и тогда .col--start-N ломает ширину: элемент схлопывается
     в одну колонку. */
  .grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    row-gap: var(--row-gap, 0);
  }

  .col {
    grid-column-end: span 12;
  }

  .col--1 { grid-column-end: span 1; }
  .col--2 { grid-column-end: span 2; }
  .col--3 { grid-column-end: span 3; }
  .col--4 { grid-column-end: span 4; }
  .col--5 { grid-column-end: span 5; }
  .col--6 { grid-column-end: span 6; }
  .col--7 { grid-column-end: span 7; }
  .col--8 { grid-column-end: span 8; }
  .col--9 { grid-column-end: span 9; }
  .col--10 { grid-column-end: span 10; }
  .col--11 { grid-column-end: span 11; }
  .col--12 { grid-column-end: span 12; }
  .col--start-2 { grid-column-start: 2; }
  .col--start-3 { grid-column-start: 3; }
  .col--start-4 { grid-column-start: 4; }
  .col--start-5 { grid-column-start: 5; }
  .col--start-6 { grid-column-start: 6; }
  .col--start-7 { grid-column-start: 7; }
  .col--start-8 { grid-column-start: 8; }
  .col--start-9 { grid-column-start: 9; }
  .col--start-10 { grid-column-start: 10; }
  .col--start-11 { grid-column-start: 11; }
  .col--start-12 { grid-column-start: 12; }

  @media (min-width: 768px) {
    .col--md-1 { grid-column-end: span 1; }
    .col--md-2 { grid-column-end: span 2; }
    .col--md-3 { grid-column-end: span 3; }
    .col--md-4 { grid-column-end: span 4; }
    .col--md-5 { grid-column-end: span 5; }
    .col--md-6 { grid-column-end: span 6; }
    .col--md-7 { grid-column-end: span 7; }
    .col--md-8 { grid-column-end: span 8; }
    .col--md-9 { grid-column-end: span 9; }
    .col--md-10 { grid-column-end: span 10; }
    .col--md-11 { grid-column-end: span 11; }
    .col--md-12 { grid-column-end: span 12; }
    .col--md-start-2 { grid-column-start: 2; }
    .col--md-start-3 { grid-column-start: 3; }
    .col--md-start-4 { grid-column-start: 4; }
    .col--md-start-5 { grid-column-start: 5; }
    .col--md-start-6 { grid-column-start: 6; }
    .col--md-start-7 { grid-column-start: 7; }
    .col--md-start-8 { grid-column-start: 8; }
    .col--md-start-9 { grid-column-start: 9; }
    .col--md-start-10 { grid-column-start: 10; }
    .col--md-start-11 { grid-column-start: 11; }
    .col--md-start-12 { grid-column-start: 12; }
  }

  @media (min-width: 1024px) {
    .col--lg-1 { grid-column-end: span 1; }
    .col--lg-2 { grid-column-end: span 2; }
    .col--lg-3 { grid-column-end: span 3; }
    .col--lg-4 { grid-column-end: span 4; }
    .col--lg-5 { grid-column-end: span 5; }
    .col--lg-6 { grid-column-end: span 6; }
    .col--lg-7 { grid-column-end: span 7; }
    .col--lg-8 { grid-column-end: span 8; }
    .col--lg-9 { grid-column-end: span 9; }
    .col--lg-10 { grid-column-end: span 10; }
    .col--lg-11 { grid-column-end: span 11; }
    .col--lg-12 { grid-column-end: span 12; }
    .col--lg-start-2 { grid-column-start: 2; }
    .col--lg-start-3 { grid-column-start: 3; }
    .col--lg-start-4 { grid-column-start: 4; }
    .col--lg-start-5 { grid-column-start: 5; }
    .col--lg-start-6 { grid-column-start: 6; }
    .col--lg-start-7 { grid-column-start: 7; }
    .col--lg-start-8 { grid-column-start: 8; }
    .col--lg-start-9 { grid-column-start: 9; }
    .col--lg-start-10 { grid-column-start: 10; }
    .col--lg-start-11 { grid-column-start: 11; }
    .col--lg-start-12 { grid-column-start: 12; }
  }
}

/* -------------------------------------------------------------
   КОМПОНЕНТЫ
   Имя блока = сущность музея. block__element--modifier.
   ------------------------------------------------------------- */
@layer components {
  /* --- Шапка --- */
  /* На 402 логотип нужного роста, две иконки и «Билет» не помещаются
     в 12 колонок — им нужно 13. Поэтому шапка здесь обычный флекс,
     а колонки включаются с 768, где место есть. */
  .header {
    position: relative;
    z-index: 95;
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-8);
    padding-block: var(--s-24) 0;
    background: var(--bg);
  }

  @media (min-width: 768px) {
    .header {
      display: grid;
      align-items: center;
      row-gap: var(--s-16);
    }
  }

  @media (min-width: 1024px) {
    .header {
      /* 27 — посадка логотипа в макете. */
      padding-block: 27px 0;
    }

    /* Логотип в макете 196×46, билетная кнопка 48. Рост логотипа
       задаём отдельно: с общим --brand-h он выходил на два пикселя
       выше и на шестнадцать шире макетного. Селектор с родителем —
       правило .header__logo стоит ниже по файлу и иначе перебивало
       бы это своим ростом. */
    .header .header__logo {
      height: 46px;
    }
  }

  .header__logo {
    height: var(--brand-h);
    width: auto;
    color: var(--text);

    /* см. --optical-logo */
    margin-inline-start: var(--optical-logo);
  }

  .header__actions {
    display: flex;
    align-items: center;
    justify-content: flex-end;
    gap: 6px;
  }

  @media (min-width: 768px) {
    .header__actions {
      gap: var(--s-8);
    }
  }

  @media (min-width: 1024px) {
    .header__actions {
      gap: var(--s-24);
    }
  }

  /* --- Иконки в шапке ---
     Лупа и бургер — простые знаки без обводки на всех экранах.
     Рамка у самой иконки нулевая, но кликабельная область равна
     --control: тот же рост, что у «Билета», и удобное попадание пальцем. */
  /* Переключатель языка. Одно слово — то, куда ведёт: на русской
     версии «En», на английской «Ру». Флага нет намеренно: язык
     не страна, и флаг на переключателе всегда кому-нибудь врёт. */
  .lang {
    align-self: center;
    /* Кегль как у вкладок и текста страницы: «En», «Билет» и вкладки
       стоят в одном ряду внимания, и разнобой в кегле между ними
       читался как разная важность. */
    font-size: var(--fs-read);
    font-weight: var(--fw-heading);
    color: var(--text);
    text-decoration: none;
    white-space: nowrap;
  }

  .lang:hover {
    color: var(--accent);
  }

  /* Ширина по знаку, а не по --control. Коробка в 50 пикселей вокруг
     разных по ширине знаков давала разный воздух: у лупы 24 пикселя
     чернил и по 13 в запасе с боков, у бургера 36 и по 7 — и зазоры
     в строке шапки выходили 29, 36 и 23 при одном и том же gap.
     Теперь коробка равна знаку, и расстояние между соседями всюду
     одно, потому что его задаёт только gap. Высота осталась прежней:
     пальцем попадать по-прежнему удобно, а по ширине и лупа, и бургер
     крупнее минимальных 24 пикселей. */
  .header__icon {
    display: flex;
    flex: none;
    align-items: center;
    justify-content: center;
    width: auto;
    height: var(--control);
    padding: 0;
    border: 0;
    background: none;
    color: var(--text);
    cursor: pointer;
    transition: color var(--ease);
  }

  .header__icon:hover {
    color: var(--accent);
  }

  /* Корзина: значок и число на нём. */
  .header__cart {
    position: relative;
  }

  /* Тележка нарисована мельче прочих знаков: её чернила занимали
     17×15 там, где у лупы 19×19, а у бургера и очков около
     30 в ширину. В ряду она проваливалась. Рисуем крупнее —
     чернила выходят 21×19, вровень с лупой. */
  .header__icon.header__cart svg {
    width: 30px;
    height: 30px;
  }

  /* Число в красном кружке. Голой красной цифрой у края оно
     не читалось: двенадцатый кегль поверх линий тележки — пятно,
     а не число. Кружок отделяет его от рисунка и делает знаком,
     который видно, не вглядываясь.

     Место считаем от середины кнопки, а не от её края: рост
     кнопки меняется от экрана к экрану (38, 42, 50), а знак
     внутри всегда один и тот же. */
  .header__count {
    position: absolute;
    top: calc(50% - 21px);
    left: calc(50% + 4px);
    display: grid;
    place-items: center;
    min-width: 18px;
    height: 18px;
    padding-inline: 5px;
    border-radius: 999px;
    background: var(--accent);
    color: var(--white);
    font-size: var(--fs-xs);
    font-weight: var(--fw-heading);
    font-variant-numeric: lining-nums tabular-nums;
    line-height: 1;
  }

  /* На наведении краснеет только сама тележка. Кружок остаётся
     красным с белой цифрой: раньше он белел, и на белой шапке от него
     оставалась висеть одна красная цифра без опоры. */

  .header__icon svg {
    width: 24px;
    height: 24px;
  }

  /* Лупа — один знак на шапку и на страницу находок: разметка лежит
     в чанке mammIconSearch, размер и толщина штриха здесь. Прежде
     знак был двумя копиями разметки, и они разъехались по толщине.

     В самом чанке комментария нет намеренно: чанк из одной разметки
     без единого вызова pdoTools через Fenom не пропускает, и
     комментарий {* … *} печатается на странице как текст. */
  .icon-search {
    width: 24px;
    height: 24px;
  }

  .icon-search :is(path, circle) {
    stroke-width: 2;
  }

  /* На мобильном знаки чуть плотнее: в мелком размере тонкая линия
     теряется. CSS перебивает stroke-width из разметки. */
  .header__icon :is(path, circle) {
    stroke-width: 2;
  }

  /* Бургер в полтора раза длиннее лупы: своя вытянутая система координат */
  .header__icon--menu svg {
    width: 36px;
  }

  /* Одна кнопка на весь сайт: в шапке и на странице события.
     Высота от --brand-h держит её вровень с логотипом в шапке. */
  .ticket {
    display: flex;
    align-items: center;
    justify-self: end;
    height: var(--brand-h);
    padding-block: 0;
    padding-inline: 13px;
    border: 0;
    border-radius: 999px;
    background: var(--accent);
    color: var(--white);
    /* см. .lang: один кегль на шапку, вкладки и текст. */
    font-size: var(--fs-read);
    font-weight: var(--fw-text);
    line-height: 1.2;
    text-decoration: none;
    white-space: nowrap;
    cursor: pointer;
    transition: background var(--ease);
  }

  .ticket:hover {
    background: var(--accent-hover);
    color: var(--white);
  }

  /* Контурная кнопка. «Купить билет» стоит в колонке сбоку, и залитой
     ей нельзя: на одном экране с залитым «Билетом» в шапке выходили
     два одинаковых красных пятна, из которых главное не назовёшь.
     Контур говорит то же слово тише.

     Углы прямые: скруглённая пилюля — форма шапки, и повторять её
     в колонке значит спорить с ней ещё и формой. */
  /* Контурная кнопка: рамка в три пикселя, прямые углы, красный
     Bold — всё из макета (Figma, 1512: 162×55, рамка 3, кегль 23,
     значок 30×18 и 15 до слова). Залитой ей нельзя: на одном экране
     с залитым «Билетом» в шапке выходили два одинаковых красных
     пятна, из которых главное не назовёшь. */
  .ticket--out {
    gap: 0.65em;
    height: auto;
    padding-block: 11px;
    padding-inline: 16px;
    border: 3px solid var(--accent);
    border-radius: 0;
    background: transparent;
    color: var(--accent);
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    transition: background var(--ease), color var(--ease);
  }

  /* Значок задан в em, а не в пикселях: кегль подписи меняется
     от экрана к экрану, и в пикселях значок то тонул рядом с ней,
     то её перерастал. В макете при 23 он 30,4×18,4 — это и есть
     1,32 на 0,8 кегля. */
  .ticket__pic {
    flex: none;
    width: 1.32em;
    height: 0.8em;
  }

  .ticket--out:hover {
    background: var(--accent);
    color: var(--white);
  }

  /* «Событие завершилось» — та же плашка, что и кнопка билета,
     но серая и неживая: ни ссылки, ни наведения, ни значка билета.
     Пустое место вместо кнопки читалось бы как недогрузившаяся
     страница, а красная плашка обещала бы действие. */
  .ticket--past {
    gap: 0;
    height: auto;
    padding-block: 11px;
    padding-inline: 16px;
    border: 3px solid var(--muted);
    border-radius: 0;
    background: transparent;
    color: var(--muted);
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    cursor: default;
  }

  /* Наведение гасим: класс .ticket несёт заливку по наведению,
     а плашка ничего не обещает — краснеть ей не с чего. */
  .ticket--past:hover {
    border-color: var(--muted);
    background: transparent;
    color: var(--muted);
  }

  /* В шапке та же кнопка ростом пониже и с меньшими полями —
     в макете 140×48 при значке 26×16 против 162×55 и 30×18
     на странице события. Кегль тоже свой: на странице события кнопка
     набрана в 23 пункта, а в шапке она стоит в одном ряду с «En»
     и вкладками, и держит их кегль. */
  .header .ticket--out {
    height: var(--brand-h);
    padding-block: 0;
    font-size: var(--fs-read);
  }

  /* В шапке значок чуть мельче: в макете 26×16 при том же кегле. */
  .header .ticket__pic {
    width: 1.13em;
    height: 0.7em;
  }

  @media (min-width: 1024px) {
    .ticket {
      padding-inline: 17px;
    }

    /* Размеры из макета — с ширины, на которую он нарисован.
       Ниже 1024 кнопка живёт по кеглю основного текста: в 375
       пикселях подпись в 23 пункта занимала пол-шапки и вытесняла
       логотип. Правило стоит здесь, а не выше, потому что поля
       шапки объявлены ниже по файлу и иначе перебивали бы его. */
    .ticket--out {
      gap: 15px;
      height: 55px;
      padding-block: 0;
      padding-inline: 22px;
      font-size: 23px;
    }

    .header .ticket--out {
      height: var(--brand-h);
      padding-inline: 16px;
      font-size: var(--fs-read);
    }

    .ticket--past {
      height: 55px;
      padding-block: 0;
      padding-inline: 22px;
      font-size: 23px;
    }
  }

  .header__skip {
    position: absolute;
    top: var(--s-8);
    left: var(--s-8);
    padding: var(--s-8) var(--s-16);
    background: var(--bg);
    border: 1px solid var(--black);
    transform: translateY(-200%);
  }

  .header__skip:focus {
    transform: translateY(0);
  }

  /* --- Хлебные крошки ---
     Тот же приём, что у меток в подвале: мелко, прописными,
     разделитель рисуется псевдоэлементом, а не лишним тегом. */
  /* Своего поля у крошек нет: расстояние до заголовка задаёт
     сетка шапки страницы. С ним выходило тридцать два вместо
     семнадцати макетных — поле складывалось с зазором сетки. */
  /* Отступ от крошек до заголовка один на весь сайт: 8 на узком
     экране, 16 на широком — так набрана шапка выставки, и она здесь
     эталон. Раньше это расстояние держал row-gap шапки, поэтому на
     страницах с обычной сеткой крошки лежали на заголовке вплотную.

     Там, где шапка задаёт свой row-gap, поле у крошек снимаем:
     иначе два отступа складываются и расстояние удваивается. */
  .breadcrumbs {
    margin-bottom: var(--s-8);
  }

  .exhibition-head .breadcrumbs {
    margin-bottom: 0;
  }

  @media (min-width: 1024px) {
    .breadcrumbs {
      margin-bottom: var(--s-16);
    }
  }

  .breadcrumbs__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-8);
    padding: 0;
    list-style: none;

    /* В макете крошки набраны шестнадцатым. Ближайшая ступень шкалы —
       семнадцатый; заводить ради одного пикселя новый кегль незачем. */
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
  }

  .breadcrumbs__item {
    display: flex;
    align-items: center;
    gap: var(--s-8);
  }

  /* Одна красная точка на весь сайт: разделитель в крошках, маркер
     статуса музея в шапке и день с событиями в календаре. В подписях
     футера её больше нет: точка там ничего не разделяла и не отмечала,
     а только доводила подпись до вида ярлыка. */
  .breadcrumbs__item + .breadcrumbs__item::before,
  .header__status::before,
  a.datepick__day::after {
    content: "";
    flex: none;
    width: 4px;
    height: 4px;
    border-radius: 50%;
    background: var(--accent);
  }

  .breadcrumbs__link {
    color: var(--text);
    text-decoration: none;
  }

  .breadcrumbs__link:hover {
    color: var(--accent);
  }

  /* Двоеточие после последнего родителя вместо повтора заголовка:
     он и так стоит строкой ниже.

     Оно вне ссылки — иначе краснело бы по наведению и попадало
     в область нажатия. А чтобы не отъехать от слова, гасим
     восьмипиксельный зазор флексбокса отрицательным отступом. */
  .breadcrumbs__item:last-child::after {
    content: ":";
    margin-left: calc(var(--s-8) * -1);
  }

  /* --- Заголовок выставки ---
     Зазор между строками шапки держим переменной: крупная дата
     гасит его отрицательным полем, чтобы встать вплотную
     к названию. Двумя разными числами они бы разъехались. */
  .exhibition-head {
    --head-gap: var(--s-8);

    row-gap: var(--head-gap);
  }

  /* Дата над названием и тем же кеглем. У события день и час —
     половина решения: их читают раньше заголовка, а мелкой строкой
     под ним они этому мешали.

     Красная только пунктуация — точка, дробь, двоеточие, тире.
     Цифры чёрные: цвет тут делит число на части, а не выделяет его.

     Цифры набираем пропорционально, а не табличными: табличные
     нужны, когда числа стоят столбцом и их сравнивают. В заголовке
     сравнивать не с чем, зато «1» с табличными полями зияет. */
  .head-date {
    margin-bottom: calc(var(--head-gap) * -1);
    font-size: var(--fs-display);
    font-weight: var(--fw-heading);
    line-height: 1.1;
    letter-spacing: -0.005em;
    font-variant-numeric: lining-nums;

    /* см. --optical-title: та же поправка, что у заголовка,
       иначе дата и название встанут по разным вертикалям */
    margin-inline-start: var(--optical-title);
  }

  .head-date__sep,
  .title__sep {
    color: var(--accent);
  }

  /* Город перед датами. У передвижных выставок он объясняет,
     почему даты не совпадают с годом в архиве. В карточке города
     цвет не задаём: там подпись лежит на снимке и наследует белый. */
  .exhibition-head__city {
    color: var(--text);
  }

  /* Ссылка на фестиваль в той же строке, что город и даты: она
     подчёркнута, потому что цветом её тут не выделить — красный
     в этой строке значил бы цену. */
  /* Имя автора ссылкой в самом заголовке. Подчёркивания нет:
     в наборе кеглем 64 черта под словом читается как ошибка, да и
     остальные ссылки на сайте её не носят. Что это ссылка, говорит
     наведение — тем же красным, что и косая рядом. */
  .title__author {
    color: inherit;
    text-decoration: none;
  }

  .title__author:hover {
    color: var(--accent);
  }

  .exhibition-head__fest {
    color: var(--text);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  .exhibition-head__fest:hover {
    color: var(--accent);
  }

  .exhibition-head__dates,
  .exhibition-aside__value {
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
  }

  /* Имя куратора не разрывается: колонка рядом с рассказом узкая,
     и «Ольга Свиблова» ломалась пополам, а фамилия оказывалась под
     чужим именем. Каждое имя — своей строкой. */
  .curator {
    display: block;
    white-space: nowrap;
  }

  @media (min-width: 768px) {
    /* Город и даты стоят строкой под заголовком: у выставок с
       длинными названиями правая колонка оставалась почти пустой,
       а заголовок при этом ломался на десяток строк. */
    .exhibition-head {
      --head-gap: var(--s-16);

      align-items: start;
    }
  }

  /* =============================================================
     ФОТОПЛЁНКА — слайдер выставки

     Лента выходит за поля страницы: соседние кадры выглядывают
     с обеих сторон, как кадры на плёнке. Прокрутка нативная,
     без JavaScript она тоже работает.

     Под лентой — шкала с рисками, как на оправе объектива.
     Красная точка идёт по шкале вслед за прокруткой.
     ============================================================= */
  /* Лента лежит на серой полосе во всю ширину окна. Отступы по краям
     полосы задаём тем же --bleed, которым лента из контейнера выходит:
     подпись и стрелки остаются на линии контента, а кадры доходят
     до края экрана. */
  .filmstrip {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    margin-inline: calc(var(--bleed) * -1);
    padding: var(--s-32) var(--bleed);
    background: var(--surface);
  }

  /* Один снимок — листать нечего: ни прокрутки, ни снапа. Ставить
     его посередине больше не нужно: колонку ему задаёт сетка слайда,
     та же, что и у череды кадров. */
  .filmstrip--single .filmstrip__track {
    scroll-snap-type: none;
    overflow-x: hidden;
  }

  .filmstrip__track {
    display: flex;
    gap: 6px;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;

    /* Выход за край окна, а не за край контейнера: на широких экранах
       контейнер упирается в --page-max и по бокам остаются поля, а лента
       должна доходить до самого края. Первый кадр при этом стоит на линии
       контента — за это отвечает scroll-padding. */
    margin-inline: calc(var(--bleed) * -1);
    padding-inline: var(--bleed);
    scroll-padding-inline: var(--bleed);

    /* Браузер иначе «удерживает» видимое при изменении вёрстки и
       сдвигает ленту, пока догружаются кадры разной ширины. */
    overflow-anchor: none;
  }

  .filmstrip__track::-webkit-scrollbar {
    display: none;
  }

  /* Кадр за раз, во всю ширину полосы. Бесконечная лента заставляла
     подгонять снимки под общую высоту: вертикальные и панорамы жили
     в чужом росте, а маленькие исходники растягивались и мылились.
     Теперь каждый снимок стоит сам по себе, в своих пропорциях —
     и одиночный, и в череде.

     Слайд — это сетка страницы, а не просто коробка: снимок встаёт
     в те же колонки, что и рассказ ниже, и начинается по той же
     вертикали. Посередине полосы его левый край гулял от страницы
     к странице вслед за пропорциями снимка. Так же устроен кадр
     события — .event-cover__image. */
  .filmstrip__slide {
    flex: 0 0 100%;
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    column-gap: var(--col-gap);
    align-items: center;
    height: auto;
    max-height: var(--media-h);
    scroll-snap-align: start;
  }

  /* Ширину не навязываем: снимок не растягивается сверх исходника.
     Прижат к началу колонки: когда он уже своей полосы, серое
     добирает справа, а левый край остаётся на линии текста. */
  .filmstrip__image {
    grid-column: 1 / -1;
    justify-self: start;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: var(--media-h);
  }

  /* Колонки — те же, что у .exhibition-text: с четвёртой до края,
     на широком экране восемь из двенадцати. */
  @media (min-width: 768px) {
    .filmstrip__image {
      grid-column: 4 / -1;
    }
  }

  @media (min-width: 1024px) {
    .filmstrip__image {
      grid-column: 4 / 12;
    }
  }

  /* --- Шкала с рисками --- */
  .filmstrip__scale {
    position: relative;
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    height: 7px;

    /* Шкала идёт по ширине основного контента — вровень с активным
       кадром, а не от края до края экрана. Крошечные поля нужны,
       чтобы точка в крайних положениях не вылезала за контент. */
    padding-inline: 3px;
  }

  .filmstrip__tick {
    width: 1px;
    height: 5px;
    background: var(--tick);
    will-change: opacity;
  }

  /* Крупная риска приходится ровно на кадр — как метка на объективе */
  .filmstrip__tick--major {
    height: 7px;
    background: var(--text);
  }

  /* --- Второй вариант индикатора: простая полоса с точкой --- */
  .filmstrip__bar {
    position: relative;
    display: flex;
    align-items: center;
    height: 7px;
  }

  .filmstrip__bar::before {
    content: "";
    width: 100%;
    height: 1px;
    background: var(--text);
  }

  .filmstrip__bar .filmstrip__dot {
    --dot: 7px;

    bottom: auto;
    width: var(--dot);
    height: var(--dot);

    /* Ореол шириной с саму точку: разрыв в полосе получается
       ровно втрое шире точки — она и два поля по краям.
       Цвет — подложка ленты: на белом фоне страницы ореол
       выглядел бы вырезанным. */
    box-shadow: 0 0 0 var(--dot) var(--surface);
  }

  .filmstrip__dot {
    position: absolute;
    bottom: 1px;
    left: 0;
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--accent);

    /* Кольцо цвета фона вырезает риски под точкой */
    box-shadow: 0 0 0 4px var(--surface);
    will-change: transform;
  }


  /* --- Листание на десктопе ---
     На тач-экранах лента листается пальцем, стрелки там не нужны. */
  .filmstrip__nav {
    display: none;
  }

  @media (min-width: 768px) {
    .filmstrip__nav {
      display: flex;
      justify-content: flex-end;
      gap: var(--s-8);
    }
  }

  /* Квадрат с рамкой в две линии — тот же знак, что у возрастной
     маркировки: на странице выставки они соседствуют, и разной
     формы им быть незачем. */
  .filmstrip__arrow {
    display: grid;
    place-items: center;
    width: var(--control);
    height: var(--control);
    padding: 0;
    border: 2px solid var(--text);
    background: none;
    color: var(--text);
    cursor: pointer;
    transition:
      border-color var(--ease),
      color var(--ease);
  }

  .filmstrip__arrow:hover {
    border-color: var(--accent);
    color: var(--accent);
  }

  .filmstrip__arrow[disabled] {
    opacity: 0.25;
    cursor: default;
  }

  .filmstrip__arrow[disabled]:hover {
    border-color: var(--text);
    color: var(--text);
  }

  .filmstrip__arrow svg {
    width: 14px;
    height: 14px;
  }

  /* Полоса работает как перемотка: клик переносит на нужный кадр */
  .filmstrip__bar {
    cursor: pointer;
  }

  /* Подпись набрана заодно со строкой «Москва 9.04—10.05.1996»
     под заголовком: это два сообщения одного рода — где и когда,
     что на снимке, — и разниться им незачем. */
  /* Курсив приходит из исходника у выходных данных работы; здесь
     он ничего не значит и только рябит. */
  .filmstrip__caption em {
    font-style: normal;
  }

  .filmstrip__caption {
    color: var(--text);
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
    line-height: 1.35;

    /* Место под две строки: подписи разной длины не должны
       дёргать текст, который идёт ниже */
    min-height: 2.7em;

    /* Возвращение — длинное и с торможением в конце */
    transition:
      opacity 420ms cubic-bezier(0, 0, 0.2, 1),
      transform 420ms cubic-bezier(0, 0, 0.2, 1);
  }

  /* На десктопе кадры стоят просторнее: на большом экране шов
     в шесть пикселей читается как склейка, а не как зазор.
     Кегль подписи при этом не растёт — он один на всех ширинах
     и равен строке «Москва 9.04—10.05.1996» под заголовком. */
  @media (min-width: 1024px) {
    .filmstrip__track {
      gap: 12px;
    }
  }

  /* Если подписей нет вовсе — блок не занимает место */
  .filmstrip__caption:empty {
    min-height: 0;
  }

  /* Смена кадра: подпись мягко уходит и так же мягко возвращается.
     Уход короче возвращения — иначе пауза читается как провал. */
  .filmstrip__caption--out {
    opacity: 0;
    transform: translateY(4px);
    transition-duration: 220ms;
    transition-timing-function: cubic-bezier(0.4, 0, 1, 1);
  }

  /* --- Текст выставки ---
     Ширина держится колонками: на широком экране восемь из двенадцати.
     Во всю ширину контента строка выходила за сотню знаков, и глаз
     терял начало следующей; шесть колонок оказались уже нужного. */
  .exhibition-text {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    font-size: var(--fs-read);
    line-height: 1.5;
  }

  @media (min-width: 1024px) {
    .exhibition-text {
      gap: var(--s-24);
    }
  }

  /* Разметка внутри рассказа. Тексты музея — это не сплошные абзацы:
     там программы курсов списками, подзаголовки занятий и ссылки на
     запись. Кегль один и тот же, разделяют начертание и воздух. */
  .exhibition-text h3,
  .exhibition-text h4 {
    margin-top: var(--s-16);
    font-size: inherit;
    font-weight: var(--fw-heading);
    line-height: 1.3;
  }

  .exhibition-text h3:first-child,
  .exhibition-text h4:first-child {
    margin-top: 0;
  }

  /* Списки в рассказе.

     Отступа нет: список стоит на той же кромке, что и абзацы,
     и переносы в нём начинаются оттуда же. В текстах музея это
     чаще всего программа занятий — продолжение рассказа, а не врезка.

     Между пунктами воздуха нет: тот же интерлиньяж, что и в абзаце.
     Восьмипиксельный зазор превращал программу из тридцати занятий
     в лестницу через всю страницу.

     Красная только точка. У ::marker цвет один на весь маркер, а два
     цвета нужны — поэтому маркера нет вовсе: число рисует ::after,
     точку ::before. Число стоит абсолютно у кромки, точка идёт
     в строке и отодвинута от кромки ровно на ширину числа: при
     табличных цифрах знак равен ровно 1ch, и разрядность видно
     по номеру пункта. Так точка всегда вплотную за числом,
     сколько бы в нём ни было цифр.

     Через ::marker с content это было бы короче, но такой маркер
     рисуют не все браузеры: при list-style-type: none часть из них
     не создаёт коробку маркера вовсе, и номера пропадали целиком. */
  .exhibition-text ul,
  .exhibition-text ol {
    padding-left: 0;
    list-style-position: inside;
  }

  .exhibition-text ol {
    counter-reset: item;
    list-style-type: none;
  }

  .exhibition-text ol > li {
    position: relative;
    counter-increment: item;
  }

  .exhibition-text ol > li::after {
    content: counter(item);
    position: absolute;
    top: 0;
    left: 0;
    font-variant-numeric: lining-nums tabular-nums;
  }

  .exhibition-text ol > li::before {
    content: ".";
    margin-left: 1ch;
    margin-right: 0.45em;
    color: var(--accent);
    font-variant-numeric: lining-nums tabular-nums;
  }

  .exhibition-text ol > li:nth-child(n+10)::before {
    margin-left: 2ch;
  }

  .exhibition-text ol > li:nth-child(n+100)::before {
    margin-left: 3ch;
  }

  /* У маркированного списка маркер и есть точка — красим целиком. */
  .exhibition-text ul > li::marker {
    color: var(--accent);
  }

  /* Видео, вставленное в текст музеем: в разметке у него width=640
     height=360 жёстко, и в узкой колонке оно вылезало за край.
     Ширину задаём колонкой, высоту — соотношением сторон. */
  .exhibition-text iframe {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: 16 / 9;
    border: 0;
  }

  /* Ссылка в тексте: подчёркнута, чтобы её было видно и без цвета. */
  .exhibition-text a {
    text-decoration: underline;
    text-decoration-thickness: 1px;
  }

  /* Колонка слева от рассказа. На узком экране встаёт над ним —
     тогда между ними нужна отбивка, которой в сетке по умолчанию нет. */
  .exhibition-body {
    row-gap: var(--s-32);
  }

  .exhibition-aside {
    display: flex;
    flex-direction: column;
    align-items: start;
    gap: var(--s-8);
  }

  /* Подпись к снимку — сноской внизу полосы сведений. */
  .exhibition-aside__caption {
    margin-top: var(--s-8);
    font-size: var(--fs-s);
    line-height: 1.4;
  }

  /* Возрастная маркировка по ФЗ-436. Красный залитый круг, внутри
     только знак: подпись «Возраст» не нужна, «18+» читается само.
     Стоит первым в колонке сведений — правовая отметка идёт впереди
     цены, а не следом за кнопкой покупки.

     Круг, а не квадрат в рамке: рамок на странице и так хватает
     (кнопка, плашка), и чёрный квадрат среди них терялся. Красный
     круг — единственное пятно такой формы, его находишь не читая. */
  .age {
    display: grid;
    place-items: center;
    width: 2.3em;
    height: 2.3em;
    border-radius: 50%;
    background: var(--accent);
    color: var(--white);

    /* 24 в круге 55 — размеры из макета (Figma, 1512). В шкалу
       кеглей они не ложатся: это знак, а не текст, и мерить его
       текстовой шкалой не за что. */
    font-size: 24px;
    font-weight: var(--fw-heading);
    line-height: 1;
    font-variant-numeric: lining-nums tabular-nums;
  }

  .exhibition-aside .age {
    margin-bottom: var(--s-16);
  }

  /* В строке с кнопкой отбивка снизу не нужна: она задирала знак
     к верху строки, и кнопка вставала ниже него на десяток
     пикселей вместо того, чтобы стоять с ним вровень. */
  .event-buy .age {
    margin-bottom: 0;
  }

  /* Зато сама строка отделена от «Входа» сильнее, чем строки
     сведений друг от друга: восьми пикселей между кружком возраста
     и двадцативосьмипиксельным «ВХОД» не хватало — они слипались
     в одно пятно. Ниже по колонке разница уже не нужна: там подпись
     и значение, они и должны стоять вплотную. */
  .event-buy:not(:last-child) {
    margin-bottom: var(--s-24);
  }

  /* Полоса работает как перемотка: клик переносит на нужный кадр */
  .filmstrip__bar {
    cursor: pointer;
  }

  /* --- Список лет ---
     Тот же язык, что у меню: строка, линия, строка. */
  .year-list {
    display: flex;
    flex-direction: column;
  }

  .year-list__item {
    padding-block: var(--s-16);
    border-top: 1px solid var(--line);
    color: var(--text);
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    font-variant-numeric: tabular-nums;
    text-decoration: none;
  }

  .year-list__item:last-child {
    border-bottom: 1px solid var(--line);
  }

  .year-list__item:hover {
    color: var(--accent);
  }

  /* =============================================================
     ГЛАВНАЯ

     Первый экран отдан одному кадру во всю высоту окна: так устроены
     12 из 16 музейных сайтов, которые я смотрел. Карусель держат
     девять — и у всех она прячет от шести до восьми афиш.
     ============================================================= */
  /* На главной кадр начинается сразу под шапкой: воздух сверху,
     который нужен внутренним страницам, здесь съедал первый экран. */
  .section--home {
    padding-top: var(--s-32);
  }

  .hero {
    position: relative;
    margin-inline: calc(var(--bleed) * -1);
    height: min(88vh, 900px);
    background: var(--black);
    isolation: isolate;
  }

  .hero__link {
    display: block;
    height: 100%;
    color: var(--white);
    text-decoration: none;
  }

  .hero__image {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Подпись лежит внизу кадра на растушёванном затемнении —
     тем же приёмом, что и карточка выставки. */
  .hero__body {
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    padding-block: var(--s-64) var(--s-48);
    background: linear-gradient(to top,
      rgb(0 0 0 / 0.74) 0%, rgb(0 0 0 / 0.70) 14%, rgb(0 0 0 / 0.58) 32%,
      rgb(0 0 0 / 0.40) 52%, rgb(0 0 0 / 0.20) 74%, rgb(0 0 0 / 0) 100%);
  }

  .hero__text {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  .hero__author {
    font-size: var(--fs-m);
  }

  .hero__title {
    font-size: var(--fs-display);
    font-weight: var(--fw-heading);
    line-height: 1.03;
    letter-spacing: -0.02em;
    text-transform: uppercase;
  }

  .hero__dates {
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
  }

  .hero__link:hover .hero__title {
    color: var(--accent);
  }

  /* --- Сегодня в музее --- */
  .today__head {
    font-size: var(--fs-xl);
    font-weight: var(--fw-heading);
    line-height: 1.05;
    letter-spacing: -0.02em;
    text-transform: uppercase;
  }

  /* --- Прочие блоки --- */
  .home-block {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
  }

  /* --- Магазин ---
     Серой плашки под обложкой больше нет. Она рисовала второй
     прямоугольник вокруг того, который у книги и так есть, а мелкие
     обложки тонули в пустом сером — и все клетки выходили на одно
     лицо. Теперь клетка держит только пропорцию, чтобы подписи
     в ряду начинались на одной высоте, а видно в ней одну книгу. */
  .shop-grid {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-48) var(--s-24);
  }

  @media (min-width: 768px) {
    .shop-grid {
      grid-template-columns: repeat(4, minmax(0, 1fr));
    }
  }

  .shop-card {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    color: var(--text);
    text-decoration: none;
  }

  /* Клетка под книгу — квадрат, и книга встаёт в неё ростом.
     Не шириной: обложки в собрании от узких (0,70) до альбомных
     (1,4), и если каждой отдать всю ширину колонки, узкая выходит
     вдвое выше альбомной. Ряд рассыпается — одна книга огромная,
     соседняя вчетверо мельче.

     Ростом же они равны все, кроме четырёх самых широких: тем
     упирается ширина колонки, и они выходят ниже — как и в жизни,
     альбом лежит ниже тома.

     Пропорция каждой обложки приходит из галереи полем shop_ratio.
     Без неё книга не знает своей формы, и обрез с фальцем легли бы
     мимо края: тень и стопка страниц рисуются по краям обёртки. */
  .shop-card__pic {
    display: flex;
    align-items: flex-end;
    aspect-ratio: 1 / 1;
  }

  /* --- Книга ---
     Плоская обложка сама по себе — просто картинка. Собираем из неё
     книгу: справа от лицевой сторонки встаёт обрез — стопка страниц,
     слева, у корешка, — фальц. Тени под книгой нет: на белой
     странице она читалась как наклейка поверх листа, а предмет
     делают обрез и фальц, а не подложенное серое пятно.

     Толщина обреза не выдумана: она считается по настоящему объёму
     издания из поля shop_pages, которое приезжает в --pages. Объём
     с карточки убран из подписи, но в разметке остался — он тут
     работает. Без объёма берётся медиана собрания, 192 страницы.

     А вот корешок у всех книг одинаковой доли. Он не торец блока,
     а сгиб сторонки: его ширину задаёт не объём издания, а то,
     насколько круто обложка уходит за край, — и у брошюры, и
     у альбома этот заворот один и тот же.

     Светотень здесь не цвет, а форма самого предмета: гамма сайта
     по-прежнему чёрное и красное.

     Компонент общий — и для карточки в сетке, и для страницы
     издания. */
  .book {
    /* Толщина — долей от ширины книги, а не в пикселях: колонка
       магазина на широком экране и на планшете разной ширины, и
       обрез должен держать ту же долю. Пределы в пикселях, чтобы
       на узкой колонке он не пропал вовсе, а на широкой не разросся
       в полку. */
    --thick: clamp(3px, calc(var(--pages, 192) * 0.018%), 22px);

    position: relative;
    display: block;

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

       Место справа отдано обрезу: иначе стопка страниц вылезала бы
       в промежуток сетки и упиралась в соседнюю книгу. */
    aspect-ratio: var(--ratio, 0.69);
    width: calc(100% - var(--thick));
    transition: transform 320ms cubic-bezier(0.2, 0, 0, 1);
  }

  /* В сетке магазина ограничений два: ширина колонки и рост клетки.
     Задать и height, и max-width нельзя — браузер обрежет ширину,
     а высоту оставит, и коробка перестанет повторять форму обложки:
     обрез с фальцем полезут выше её края. Поэтому считаем одним
     выражением. Клетка квадратная, значит её рост — те же 100%,
     что и ширина; «100% × пропорция» — ширина книги, вставшей
     в клетку ростом. Что меньше, то и берём.

     На странице издания этого правила нет: там кадр и так по форме
     обложки, и книга просто занимает его целиком. */
  .shop-card__pic .book {
    width: min(
      calc(100% - var(--thick)),
      calc(100% * var(--ratio, 0.69))
    );
  }

  .book__cover {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: contain;
    object-position: left bottom;

    /* Углы переплёта скруглены слегка, и справа тоже: обрез рядом
       закруглён на те же три пиксела, и рядом с ним прямой угол
       сторонки читался ступенькой. Силуэт должен идти одной
       непрерывной кромкой — сторонка, обрез, сторонка. */
    border-radius: 3px;
  }

  /* Обрез: тонкие вертикальные штрихи — это и есть торцы страниц.
     Сверху и снизу он короче обложки на кант: переплёт всегда
     чуть больше блока. */
  .book::after {
    content: "";
    position: absolute;
    top: 3px;
    bottom: 3px;
    left: 100%;
    width: var(--thick);
    border-radius: 0 3px 3px 0;
    background: repeating-linear-gradient(
      90deg,
      rgba(255, 255, 255, 0.95) 0 1px,
      rgba(17, 17, 17, 0.22) 1px 2px
    );
    box-shadow: inset -1px 0 0 rgba(17, 17, 17, 0.4);
  }

  /* --- Фальц ---
     У твёрдого переплёта сторонка не продолжает корешок, а отделена
     от него канавкой: по ней книга и открывается. Без канавки
     обложка читается как журнал — просто лист с тенью по краю.

     Свет идёт уступами, а не плавно: у самого края тонкая тёмная
     кромка — корешок уходит от нас, — за ней светлый горб самого
     сгиба, потом резкая тёмная канавка, за ней кант поднявшейся
     сторонки, и дальше ровное поле обложки. Плавная растяжка на
     этом месте читается не корешком, а засветкой от вспышки.

     Слоя два, и они разного знака: светлый по горбу и тёмный
     по канавке. На чёрной сторонке видно светлое, на белой —
     тёмное; будь слой один, на половине собрания корешок пропал бы.

     Доли считаем от ширины книги, а не от объёма издания: сгиб —
     не торец блока, а заворот сторонки, и у брошюры он такой же,
     как у альбома. Поэтому здесь проценты, а --thick не при чём. */
  .book::before {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 1;
    border-radius: 3px;
    background:
      linear-gradient(
        90deg,
        rgba(17, 17, 17, 0.34) 0,
        rgba(17, 17, 17, 0.06) 0.5%,
        rgba(17, 17, 17, 0) 1.8%,
        rgba(17, 17, 17, 0.10) 2.65%,
        rgba(17, 17, 17, 0.42) 3.15%,
        rgba(17, 17, 17, 0.30) 3.4%,
        rgba(17, 17, 17, 0) 3.6%
      ),
      linear-gradient(
        90deg,
        rgba(255, 255, 255, 0.06) 0,
        rgba(255, 255, 255, 0.22) 0.9%,
        rgba(255, 255, 255, 0.20) 1.9%,
        rgba(255, 255, 255, 0.04) 2.9%,
        rgba(255, 255, 255, 0) 3.4%,
        rgba(255, 255, 255, 0.26) 3.5%,
        rgba(255, 255, 255, 0.08) 4.3%,
        rgba(255, 255, 255, 0) 5.4%
      );
    pointer-events: none;
  }

  /* Книга приподнимается — как будто её берут с полки. Своя тень
     у неё не меняется: тень, растущая по наведению, читается как
     подсветка кнопки, а книга не кнопка. */
  .shop-card:hover .book {
    transform: translateY(-6px);
  }

  /* --- Галерея издания ---
     Крупный снимок и ряд превью под ним. Лента с привязкой к кадру:
     пальцем на мобильном она листается сама, без скрипта. Превью —
     ссылки на якоря слайдов, и без скрипта браузер прокручивает
     ленту к нужному кадру сам. Скрипт лишь убирает прыжок страницы
     и подсвечивает текущее превью. */
  .pgal {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  .pgal__track {
    display: flex;
    gap: var(--s-24);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;

    /* Браузер иначе «удерживает» видимое при изменении вёрстки
       и сдвигает ленту, пока догружаются кадры разной ширины. */
    overflow-anchor: none;
  }

  .pgal__track::-webkit-scrollbar {
    display: none;
  }

  /* Кадр за раз, во всю ширину колонки. Форму кадра задаёт сама
     обложка — через --cover от разметки: тогда она занимает его
     целиком, без пустых полей, а при листании высота не скачет.
     Развороты, они шире, встают в тот же кадр посередине. */
  /* Кадр прижат к верху, а не по середине: рядом с ним стоит кнопка
     с ценой, и книга должна начинаться с той же горизонтали. При
     выравнивании по центру обложка съезжала вниз на разницу между
     пропорцией кадра и пропорцией самой книги — на «России» это
     четырнадцать пикселей, и кнопка висела выше корешка. */
  .pgal__slide {
    display: flex;
    flex: 0 0 100%;
    align-items: flex-start;
    justify-content: flex-start;
    aspect-ratio: var(--ratio, 0.69);
    scroll-snap-align: start;
  }

  .pgal__image {
    display: block;
    width: 100%;
    height: auto;
    max-height: 100%;
    object-fit: contain;
    object-position: left bottom;
  }

  /* Превью в ряд. Их бывает до полутора десятков, поэтому ряд
     прокручивается, а не переносится: перенос ломает высоту
     колонки и уводит подпись вниз. */
  .pgal__thumbs {
    display: flex;
    gap: var(--s-8);
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scrollbar-width: none;
  }

  .pgal__thumbs::-webkit-scrollbar {
    display: none;
  }

  .pgal__cell {
    flex: none;
  }

  /* Квадратная клетка, снимок вписан целиком: развороты бывают
     и вытянутые, и почти квадратные, и кадрировать их нельзя —
     это страницы книги, а не фон. */
  .pgal__thumb {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 56px;
    height: 56px;
    padding: 3px;
    border: 1px solid transparent;
    transition: border-color var(--ease);
  }

  .pgal__thumb img {
    max-width: 100%;
    max-height: 100%;
  }

  .pgal__thumb:hover {
    border-color: var(--line);
  }

  .pgal__thumb.is-on {
    border-color: var(--accent);
  }

  @media (prefers-reduced-motion: reduce) {
    .pgal__thumb {
      transition: none;
    }
  }

  /* Обложка на странице издания — та же книга, только одна. */
  .book--lone {
    margin-bottom: var(--s-16);
  }

  /* Цена держится за название, а не висит между ним и следующей
     книгой: восемь пикселей зазора плюс собственный интерлиньяж
     строки давали на глаз тринадцать, и цена отходила от своей
     книги к чужой обложке снизу. */
  .shop-card__body {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  /* Названия набраны ступенью ниже читательского кегля: в сетке
     из полусотни книг они идут сплошной стеной, и с 19 пикселей
     подписи спорили с самими обложками. */
  .shop-card__title {
    font-size: var(--fs-m);
    line-height: 1.25;
  }

  .shop-card:hover .shop-card__title {
    color: var(--accent);
  }

  /* Под названием только цена. Год и объём с карточки убраны: в сетке
     из полусотни книг это был ряд одинаковых серых строк, который
     ничего не решал. Кто выбирает — смотрит на обложку и на цену,
     остальное ждёт на странице издания.

     Красный тут единственный на карточке, поэтому цену видно
     раньше названия — так и надо в витрине. */
  .shop-card__price {
    color: var(--accent);
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1.25;
    font-variant-numeric: tabular-nums;
  }

  /* Издания, которых нет в наличии, стоят в конце списка и вместо
     цены говорят об этом — серым и обычным начертанием: это
     не цена, и притворяться ею не должно. */
  .shop-card__price--none {
    color: var(--muted);
    font-weight: var(--fw-text);
  }

  /* --- Страница издания ---
     Книга слева и крупно, покупка справа. Заголовок стоит рядом
     с обложкой, а не над разворотом: в магазине сперва смотрят
     на книгу, а название читают уже при ней. */
  .product {
    row-gap: var(--s-32);
  }

  .product__info {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-24);
  }

  /* Кнопка с ценой. Черты над ней нет: покупку и так видно —
     она единственная красная на всей странице, и подпирать её
     ещё и линией значит говорить дважды. */
  .product__buy {
    display: flex;
    align-items: center;
    gap: var(--s-24);
    width: 100%;
  }

  /* Кегль ступенью ниже общего заголовка страницы: в шесть колонок
     из двенадцати «дисплейный» размер не помещается и рассыпается
     на пять строк. */
  .product__title {
    font-size: var(--fs-xl);
  }

  /* Год и объём идут сразу под названием, одной группой с ним. */
  .product__meta {
    margin-top: calc(var(--s-16) * -1);
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
  }

  .product__price {
    color: var(--accent);
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    font-variant-numeric: tabular-nums;
  }

  /* Нет цены — нет и кнопки. Треть собрания музей не продаёт,
     и притворяться, что продаёт, нельзя. */
  .product__price--none {
    color: var(--muted);
    font-size: var(--fs-m);
    font-weight: var(--fw-text);
  }

  /* .ticket по умолчанию тянется во всю строку — в шапке он растянут
     по сетке. Здесь ему хватает своей ширины. */
  .product__buy .ticket {
    width: max-content;
  }

  /* Кнопка с ценой: корзинка слева, цифра справа. Ростом повыше
     билетной — на ней теперь не слово, а решение. */
  /* Знак сумки в кнопке магазина. Рост в em, как у билетного знака:
     кегль кнопки на странице события и в шапке разный, а знак должен
     держаться подписи. */
  .ticket__bag {
    flex: none;
    width: 0.96em;
    height: 1.05em;
  }


  .product__text {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    font-size: var(--fs-read);
    line-height: 1.5;
  }

  /* Выходные данные: копирайты, ISBN, «на обложке». Кегль мелкий
     и свой — это паспорт издания, а не продолжение рассказа.
     Прижаты к рассказу отрицательным полем: это его продолжение
     мелким кеглем, а не отдельный раздел.

     Своей меры тут нет. Была в 46 знаков — и мелкий текст ломался
     втрое раньше рассказа над ним: две строки копирайта вставали
     лесенкой посреди колонки, будто набор поехал. Ширину задаёт
     колонка, та же, что и у рассказа, и правая кромка у них
     общая. Длиннее всего копирайт в 63 знака — в шесть колонок
     он ложится в строку целиком. */
  .product__credits {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    margin-top: calc(var(--s-16) * -1);
    font-size: var(--fs-s);
    line-height: 1.45;
  }

  /* --- Доставка ---
     Аккордеон: условия одни и те же на каждой книге, и держать их
     всегда раскрытыми — заставлять пролистывать одно и то же.
     Внутри три столбца: что, как и почём. На узком экране они
     складываются в строки — в 375 пикселях три колонки нечитаемы. */
  .delivery {
    width: 100%;
    border-top: 2px solid var(--line);
  }

  .delivery__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-16);
    padding: var(--s-16) 0;
    cursor: pointer;

    /* Свой знак вместо системного треугольника: тот в разных
       браузерах разного размера и цвета. */
    list-style: none;
  }

  .delivery__head::-webkit-details-marker {
    display: none;
  }

  /* Плюс, который по раскрытии становится минусом: вертикальная
     черта просто исчезает. */
  .delivery__sign {
    position: relative;
    flex: none;
    width: 12px;
    height: 2px;
    background: var(--text);
  }

  .delivery__sign::after {
    content: "";
    position: absolute;
    inset: -5px 5px;
    background: var(--text);
    transition: opacity var(--ease);
  }

  .delivery[open] .delivery__sign::after {
    opacity: 0;
  }

  .delivery__list {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    padding-bottom: var(--s-16);
  }

  .delivery__row {
    display: grid;
    gap: 2px var(--s-16);
    font-size: var(--fs-read);
  }

  @media (min-width: 768px) {
    .delivery__row {
      grid-template-columns: 8em 1fr auto;
      align-items: baseline;
    }
  }

  .delivery__what {
    font-weight: var(--fw-heading);
  }

  .delivery__cost {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .delivery__note {
    padding-bottom: var(--s-24);
    font-size: var(--fs-s);
    line-height: 1.45;
  }

  .delivery__note a {
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  /* --- Корзина ---
     Строка на книгу: сама книга, название, счётчик, сумма, крестик.
     Черт между строками нет — их разводит воздух. Полосок на этой
     странице набралось столько, что подчёркнутые поля формы стали
     читаться как разделители; убрали и те, и другие. */
  .cart__empty {
    font-size: var(--fs-read);
  }

  .cart__empty a,
  .sum__note a {
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  .cart {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
  }

  .cart__row {
    display: grid;
    grid-template-columns: 72px 1fr;
    grid-template-areas:
      "pic what"
      "pic count"
      "pic sum";
    align-items: center;
    gap: var(--s-8) var(--s-16);
  }

  @media (min-width: 768px) {
    .cart__row {
      grid-template-columns: 72px 1fr auto auto;
      grid-template-areas: "pic what count sum";
      gap: var(--s-24);
    }
  }

  /* Книга в корзине — тот же предмет, что в сетке магазина:
     с обрезом и фальцем. Клетка квадратная, книга встаёт в неё
     ростом — правило то же, что в сетке. */
  .cart__pic {
    display: flex;
    grid-area: pic;
    align-items: flex-end;
    aspect-ratio: 1 / 1;
  }

  .cart__pic .book {
    width: min(
      calc(100% - var(--thick)),
      calc(100% * var(--ratio, 0.69))
    );
  }

  .cart__what {
    display: flex;
    grid-area: what;
    flex-direction: column;
    gap: 2px;
  }

  /* Кегль в корзине тот же, что в тексте сайта: это не служебный
     экран, а страница, где читают названия и суммы. Мелкими остаются
     только ярлыки полей и сноски. */
  .cart__name {
    color: var(--text);
    font-size: var(--fs-read);
    text-decoration: none;
  }

  .cart__name:hover {
    color: var(--accent);
  }

  .cart__one {
    font-size: var(--fs-s);
    font-variant-numeric: tabular-nums;
  }

  /* Счётчик: два знака и число между ними на серой плашке — той же,
     что у полей формы. Так он читается как орган управления, а не
     как строка текста. */
  .cart__count {
    display: flex;
    grid-area: count;
    align-items: center;
    gap: var(--s-8);
    justify-self: start;
    padding: 4px var(--s-8);
    background: var(--surface);
  }

  .cart__step {
    width: 22px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text);
    font: inherit;
    font-size: var(--fs-read);
    line-height: 1.5;
    cursor: pointer;
    transition: color var(--ease);
  }

  .cart__step:hover:not(:disabled) {
    color: var(--accent);
  }

  .cart__step:disabled {
    opacity: 0.3;
    cursor: default;
  }

  .cart__num {
    min-width: 1.4em;
    font-size: var(--fs-read);
    font-variant-numeric: tabular-nums;
    text-align: center;
  }

  /* Итог по строке — красным, как и всякая цена на сайте. Слева
     под названием стоит цена за экземпляр, чёрная: она справочная,
     а решает эта. */
  .cart__sum {
    grid-area: sum;
    justify-self: end;
    color: var(--accent);
    font-size: var(--fs-read);
    font-weight: var(--fw-heading);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  /* --- Сводка ---
     Липкая колонка справа на серой плашке: сумма и кнопка остаются
     перед глазами, пока человек заполняет поля слева. Плашкой, а не
     рамкой: рамка — ещё одна линия, а их тут и так было слишком.
     Кнопка лежит здесь, а форма слева — связывает их атрибут form,
     обычный HTML без скрипта. */
  .cart-aside {
    display: flex;
    flex-direction: column;
    gap: var(--s-24);
    align-self: start;
  }

  @media (min-width: 768px) {
    .cart-aside {
      position: sticky;
      top: var(--s-24);
    }
  }

  .sum {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    padding: var(--s-24);
    background: var(--surface);
  }

  .sum__row {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-16);
    font-size: var(--fs-read);
    font-variant-numeric: tabular-nums;
  }

  /* Слово и число одного начертания: это одна строка, а не подпись
     с выделенным значением. Число красное — оно тут главное. */
  .sum__row--total {
    margin-top: var(--s-8);
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
  }

  .sum__row--total b {
    color: var(--accent);
    font-weight: inherit;
  }

  /* Согласие. Настоящий флажок скрыт, а квадрат рисует подпись —
     тем же приёмом, что переключатели доставки: браузеры рисуют
     флажки по-своему, а собственная отрисовка внутри input работает
     не везде. Квадрат в две линии — знак с сайта: он же у возрастной
     маркировки и стрелок ленты. */
  .sum__ok {
    display: block;
    margin-top: var(--s-16);
  }

  .sum__box {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* Флексом, а не сеткой: в сетке ссылка внутри подписи становилась
     самостоятельной ячейкой и уезжала на свою строку. */
  .sum__deal {
    display: flex;
    align-items: flex-start;
    gap: var(--s-8);
    font-size: var(--fs-xs);
    line-height: 1.4;
    cursor: pointer;
  }

  .sum__tick {
    position: relative;
    flex: none;
    width: 18px;
    height: 18px;
    border: 2px solid var(--line);
    transition: border-color var(--ease);
  }

  /* Галочка — угол прямоугольника, повёрнутый на сорок пять
     градусов: две черты, как и всё остальное на сайте. */
  .sum__tick::after {
    content: "";
    position: absolute;
    top: 1px;
    left: 5px;
    width: 4px;
    height: 8px;
    border-right: 2px solid var(--accent);
    border-bottom: 2px solid var(--accent);
    opacity: 0;
    transform: rotate(45deg);
    transition: opacity var(--ease);
  }

  .sum__box:checked + .sum__deal .sum__tick {
    border-color: var(--accent);
  }

  .sum__box:checked + .sum__deal .sum__tick::after {
    opacity: 1;
  }

  .sum__box:focus-visible + .sum__deal .sum__tick {
    outline: 2px solid var(--accent);
    outline-offset: 2px;
  }

  .sum__deal a {
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  .sum__send {
    width: 100%;
    height: auto;
    justify-content: center;
    margin-top: var(--s-8);
    padding: 13px 20px;
    font-size: var(--fs-read);
  }

  .sum__note {
    font-size: var(--fs-xs);
    line-height: 1.45;
  }

  /* --- Заказ ---
     Поля залиты серым, а не подчёркнуты: подчёркивание путалось
     с разделительными чертами страницы. */
  .order {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: var(--s-16);
    margin-top: var(--s-48);
  }

  .order__fields {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    width: 100%;
  }

  /* Подпись живёт внутри поля и уходит наверх, когда в поле
     что-то есть. Не placeholder: тот пропадает, стоит начать
     печатать, и заполненная форма превращается в столбик значений
     без имён. Пустой placeholder оставлен нарочно — по нему
     :placeholder-shown и понимает, пусто поле или нет. */
  .order__field {
    position: relative;
    display: block;
    width: 100%;
  }

  /* Подпись стоит там, где окажется в итоге, а в пустом поле лишь
     опущена и увеличена. Двигается поэтому одно свойство — transform,
     и двигается ровно.

     Раньше менялись сразу три: top, transform и font-size. Кегль
     тем и плох, что при каждом его изменении браузер пересчитывает
     вёрстку заново — буквы дрожат и расползаются, а подпись будто
     улетает. Масштаб 1.42 даёт из 12 пикселей те же 17, но не
     трогает вёрстку вовсе.

     Четверть секунды вместо 0,4: подпись — мелочь, и разглядывать
     её полёт незачем. */
  .order__label {
    position: absolute;
    top: 9px;
    left: var(--s-16);
    font-size: var(--fs-xs);
    line-height: 1;
    transform: translateY(11px) scale(1.42);
    transform-origin: left top;
    pointer-events: none;
    transition: transform 180ms cubic-bezier(0.2, 0, 0.2, 1);
  }

  .order__input:focus + .order__label,
  .order__input:not(:placeholder-shown) + .order__label {
    transform: none;
  }

  /* У длинного поля подпись сверху всегда: середины у него нет. */
  .order__field--long .order__label {
    transform: none;
  }

  .order__input {
    width: 100%;

    /* Сверху больше, чем снизу: там живёт подпись. */
    padding: 24px var(--s-16) 9px;
    border: 0;
    background: var(--surface);
    color: var(--text);
    font: inherit;
    font-size: var(--fs-read);
  }

  .order__input--long {
    resize: vertical;
  }

  .order__input:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
  }

  .order__field.is-bad .order__input {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
  }

  /* Способы доставки: кружок, название, срок и цена. Без рамок
     и черт — выбранный помечен красным кружком и красным текстом. */
  /* Заголовок раздела отбит сверху: без отступа «Доставка» липла
     к последнему полю и читалась как подпись к нему. */
  .order__part {
    margin-top: var(--s-24);
  }

  .order__ways {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    width: 100%;
  }

  .order__radio {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }

  /* Название сверху, срок под ним, цена справа. Раньше срок стоял
     в своей колонке рядом — строка растягивалась через всю ширину,
     и глаз не связывал её с названием. */
  .order__pick {
    display: grid;
    grid-template-columns: 18px 1fr auto;
    grid-template-areas:
      "mark what cost"
      ".    how  how";
    gap: 2px var(--s-16);
    padding: var(--s-8) 0;
    cursor: pointer;
  }

  .order__mark { grid-area: mark; }
  .order__what { grid-area: what; }
  .order__how  { grid-area: how; }
  .order__cost { grid-area: cost; }

  .order__mark {
    width: 16px;
    height: 16px;
    align-self: start;
    margin-top: 3px;
    border: 2px solid var(--line);
    border-radius: 50%;
    transition: border-color var(--ease), box-shadow var(--ease);
  }

  .order__radio:checked + .order__pick .order__mark {
    border-color: var(--accent);
    box-shadow: inset 0 0 0 3px var(--accent);
  }

  .order__what {
    font-size: var(--fs-read);
    font-weight: var(--fw-heading);
  }

  .order__how {
    font-size: var(--fs-s);
    line-height: 1.4;
  }

  .order__cost {
    font-size: var(--fs-read);
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
  }

  .order__radio:checked + .order__pick .order__what,
  .order__radio:checked + .order__pick .order__cost {
    color: var(--accent);
  }

  .order__radio:focus-visible + .order__pick .order__mark {
    outline: 2px solid var(--accent);
    outline-offset: 3px;
  }

  /* Адрес нужен не всякой доставке. Какой нужен — сказано в самом
     справочнике miniShop2, форма лишь перекладывает это в атрибут.
     Прячем и показываем чистым CSS: :has() смотрит, отмечен ли
     способ, которому адрес нужен. */
  .order__address {
    display: none;
  }

  .order:has(.order__radio[data-address]:checked) .order__address {
    display: block;
  }

  /* Меры тут нет по той же причине, что и у выходных данных издания:
     строка про оплату стоит в одной колонке с описаниями доставки,
     а те идут во всю ширину. С мерой в 46 знаков она ломалась
     надвое там, где соседняя строка длиннее и укладывается
     целиком, — и читалась как сбой набора, а не как примечание. */
  .order__single {
    font-size: var(--fs-s);
    line-height: 1.45;
  }

  .order__bad {
    color: var(--accent);
    font-size: var(--fs-read);
  }

  @media (prefers-reduced-motion: reduce) {
    .order__mark,
    .order__label,
    .sum__tick,
    .sum__tick::after,
    .cart__step {
      transition: none;
    }
  }

  /* --- Заказ принят --- */
  .thanks {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    padding-bottom: var(--s-32);
  }

  .thanks__num {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    font-variant-numeric: tabular-nums;
  }

  .thanks__sum {
    color: var(--accent);
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
  }

  .thanks__note {
    max-width: 46ch;
    font-size: var(--fs-s);
    line-height: 1.45;
  }

  .thanks__back a {
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  @media (prefers-reduced-motion: reduce) {
    .book {
      transition: none;
    }
  }

  /* --- Команда музея ---
     У музея на своём сайте это карточки с портретом, но портрет
     есть у одного человека из тринадцати, и ради него заводить
     сетку карточек незачем. Слева имя и под ним должность,
     справа почта — на широком экране в одну строку, на узком
     друг под другом. */
  .team {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
    list-style: none;
    margin: 0;
    padding: 0;
  }

  /* Линеек между людьми нет: их разводит воздух. Полосы поперёк
     тринадцати строк превращали список в таблицу. */
  .team__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-8) var(--s-32);
  }

  .team__who {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .team__post {
    font-size: var(--fs-s);
    line-height: 1.3;
    color: var(--muted);
  }

  .team__name {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1.2;
    letter-spacing: -0.01em;
  }

  .team__mail a {
    font-size: var(--fs-m);
    color: var(--accent);
    text-decoration: none;
  }

  .team__mail a:hover {
    color: var(--accent-hover);
  }

  /* --- Паспорт издания ---
     Пары «подпись — значение» с отточием: год, ISBN, объём. Отточие
     то же, что в расписании меню, и по той же причине — пары читаются
     сразу, а не как сплошная строка. */
  .spec {
    display: flex;
    flex-direction: column;
    gap: 2px;
    font-size: var(--fs-s);
  }

  .spec__row {
    display: flex;
    align-items: baseline;
    gap: 0.5em;
  }

  .spec__dots {
    flex: 1 1 auto;
    align-self: baseline;
    height: 0;
    border-bottom: 1px dotted currentColor;
    opacity: 0.45;
  }

  .spec__val {
    flex: none;
    font-variant-numeric: tabular-nums;
  }

  /* --- Приложенный документ ---
     Из макета: красная ссылка со значком листа, без рамок и линеек.
     Значок нарисован разметкой и красится currentColor, поэтому
     он всегда одного цвета с подписью — в том числе под курсором.

     Ни типа файла, ни веса рядом с названием не пишем. */
  .docs {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .doc {
    display: flex;
    align-items: flex-start;
    gap: 14px;
    color: var(--accent);
    text-decoration: none;
  }

  .doc:hover {
    color: var(--accent-hover);
  }

  .doc__pic {
    flex: none;
    width: 20px;
    height: 24px;
    /* Лист ростом со строку: верх значка вровень с верхом первой
       строки подписи, а не с серединой абзаца. */
    margin-top: 0.1em;
  }

  .doc__name {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1.35;
  }

  /* --- Заголовок-переход ---
     Вместо мелкой ссылки «Все видео» внизу блока переходом служит сам
     заголовок: красный и кликабельный целиком. Знака при нём нет —
     ни стрелки, ни зубца: у прописных и без указки хватает веса,
     а приставка сбоку читалась как приклеенная. */
  .more {
    color: var(--accent);
    text-decoration: none;
  }

  /* Состояние музея в шапке — рядом с «Билетом»: посетитель решает
     идти или нет по этим двум фактам разом. */
  .header__brand {
    display: flex;
    align-items: center;
  }

  .header__status {
    display: none;
    align-items: center;
    gap: var(--s-8);
    font-size: var(--fs-xs);
    white-space: nowrap;
  }

  @media (min-width: 1024px) {
    .header__status {
      display: flex;
    }
  }

  /* =============================================================
     РАСПИСАНИЕ СОБЫТИЙ

     Не сетка карточек, а список строк: кадр слева, справа сверху
     вниз — когда, что, какого рода. Карточками расписание читается
     хуже: глаз ищет ближайшее время, а не картинку.

     Заголовков по дням нет — дата стоит в каждой строке. Так строку
     можно вынуть и положить в находки, где рядом события за двадцать
     лет, и она останется понятной.
     ============================================================= */
  .sched {
    display: flex;
    flex-direction: column;
    gap: var(--s-48);
  }

  .sched__rows {
    display: flex;
    flex-direction: column;
  }

  .sched__row {
    display: grid;
    gap: var(--s-16);
    padding-block: var(--s-32);
  }

  .sched__row + .sched__row {
    border-top: 1px solid var(--surface);
  }

  .sched__body {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  /* Дата и час — тем же кеглем, что название, и тем же начертанием:
     у события это половина решения, их читают раньше названия.
     Красную пунктуацию рисует mammBigDate, правило для неё общее
     с крупной датой на странице — см. .head-date__sep. */
  .sched__when {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1;
    letter-spacing: -0.01em;
    font-variant-numeric: lining-nums;
  }

  .sched__title {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1.15;
    letter-spacing: -0.01em;
  }

  /* Род события — серым и мельче. Второй случай серого в гамме
     (первый — «нет в наличии» в магазине): это не часть названия,
     а пометка о том, к какой программе строка относится, и чёрным
     она спорила с названием за внимание. */
  .sched__kind {
    margin-top: calc(var(--s-16) * -0.5);
    font-size: var(--fs-s);
    color: var(--muted);
  }

  .sched__link {
    color: var(--text);
    text-decoration: none;
  }

  .sched__link:hover {
    color: var(--accent);
  }

  /* Полоса во всю ширину окна, а внутри — сетка страницы: снимок
     встаёт в те же колонки, что и рассказ, и начинается по той же
     вертикали. Отрицательное поле выносит полосу за контейнер,
     равное ему внутреннее возвращает сетку на место. */
  /* Серая плашка. Поля и отбивки взяты из макета (Figma, 1512):
     над плашкой 32, под ней 64, внутри 32 сверху и снизу. К заголовку
     она стоит ближе, чем к рассказу, — держится за него, а рассказ
     начинается уже новым дыханием. Зазор ленты гасим отрицательным
     полем: он общий для всей страницы, а здесь нужны разные. */
  .event-cover {
    margin-inline: calc(var(--bleed) * -1);
    margin-block: calc(var(--s-32) - var(--stack-gap)) calc(var(--s-64) - var(--stack-gap));
    padding: var(--s-32) var(--bleed);
    background: var(--surface);
  }

  /* Дата в плашке: цифры вровень с верхом снимка. Кегль ступенью
     ниже заголовка — в макете 44 против 64. */
  .event-cover .head-date {
    /* Верх цифр вровень с верхом снимка. Отрицательное поле снимает
       расстояние от верха строки до верхушки цифры — при
       line-height: 1 у PT Root UI это 0,16 кегля. */
    margin-top: -0.16em;
    margin-bottom: 0;
    font-size: var(--fs-xl);
    line-height: 1;
  }

  /* Знак возраста и кнопка — одной строкой. Зазор 24 из макета. */
  .event-buy {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-24);
  }

  /* Знак возраста прибит к верху плашки — на одной линии с началом
     снимка, а не по середине колонки. */
  .event-cover .grid {
    align-items: start;
  }

  /* Картинка занимает полосу, но не выше общего предела --media-h —
     того же, по которому живёт лента снимков. */
  /* object-position слева, а не по центру: когда высота упирается
     в предел, «contain» вписывает снимок в колонку с полями по бокам,
     и его край уезжает вправо от вертикали текста. Прижимаем влево —
     край всегда на линии рассказа, серое добирает справа. */
  .event-cover__image {
    display: block;
    width: 100%;
    max-height: var(--media-h);
    object-fit: contain;
    object-position: left center;
  }

  .sched__pic {
    display: block;
    aspect-ratio: 4 / 3;
    overflow: hidden;
    background: var(--surface);
  }

  .sched__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  @media (min-width: 768px) {
    /* Кадр слева, подпись справа. Кадр крупный: на двухстах пикселях
       он был маркой в поле и ничего не показывал. */
    .sched__row {
      grid-template-columns: minmax(0, 4fr) minmax(0, 8fr);
      gap: var(--s-32);
      align-items: start;
    }
  }

  /* =============================================================
     ВИДЕО
     ============================================================= */

  /* Переключатель разделов — вкладками: слово обычным кеглем,
     под текущей красная черта, под остальными общая светлая линия.
     Мелкими прописными их набирать нельзя — это навигация, а не ярлык. */
  /* --- Разделы: строкой на широком экране, списком на узком ---
     Перенос сбивал вкладки в кучу: межстрочного зазора у полосы нет
     намеренно, красная черта активной вкладки должна лежать на общей
     линии. Прокрутка вбок прятала разделы за краем. Поэтому на узком
     экране это раскрывающийся список: на язычке текущий раздел,
     внутри остальные. Родной <details>, скрипта не нужно. */
  /* Расстояние от заголовка до вкладок одно на весь сайт — 48.
     Вкладки стоят в общей стопке страницы, а у стопки шаг разный:
     у разделов 48, у обычной страницы на широком экране 96. Поэтому
     не полагаемся на шаг, а вычитаем разницу: где шаг уже 48, поправка
     равна нулю, где больше — подтягивает вкладки на место. Эталон —
     раздел «Видео». */
  .tabs-pick {
    position: relative;
    margin-top: calc(var(--s-48) - var(--stack-gap, var(--s-48)));
  }

  .tabs-pick__now {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-8);
    padding-bottom: var(--s-8);
    border-bottom: 3px solid var(--line-soft);
    font-size: var(--fs-m);
    line-height: 1.2;
    list-style: none;
    cursor: pointer;
  }

  .tabs-pick__now::-webkit-details-marker {
    display: none;
  }

  /* Своя стрелка: родная стоит слева от текста и не поворачивается. */
  .tabs-pick__now::after {
    content: "";
    flex: none;
    width: 8px;
    height: 8px;
    border-right: 1.5px solid currentColor;
    border-bottom: 1.5px solid currentColor;
    transform: translateY(-2px) rotate(45deg);
    transition: transform var(--ease);
  }

  .tabs-pick.is-open .tabs-pick__now::after {
    transform: translateY(2px) rotate(-135deg);
  }

  /* Список ложится поверх страницы, а не раздвигает её. */
  .tabs {
    position: absolute;
    z-index: 10;
    top: 100%;
    left: 0;
    right: 0;
    display: flex;
    flex-direction: column;
    padding: var(--s-8) var(--s-16);
    border: 1px solid var(--line);
    background: var(--bg);
    /* Кегль вкладок равен кеглю текста страницы: они стоят под
       заголовком в одной с ним колонке, и вторая ступень кегля
       между заголовком и рассказом читалась бы как третий уровень
       иерархии, которого нет. */
    font-size: var(--fs-read);
    line-height: 1.2;
  }

  .tab {
    position: relative;
    display: block;
    flex: none;
    white-space: nowrap;
    padding-block: 0 0.25em;
    color: var(--text);
    /* Начертание как у «En» и «Билета» в шапке: вкладки стоят с ними
       в одном ряду внимания, и Medium против Bold читался как разная
       важность. Кегль у них уже общий. */
    font-weight: var(--fw-heading);
    text-decoration: none;
  }

  /* Подчёркнута только текущая вкладка, и подчёркнута насовсем.
     Прежде черта выезжала под тем словом, на которое навели, —
     полоска бегала по строке и перетягивала внимание на себя,
     хотя её дело — сказать, где ты сейчас. Наведение меняет цвет,
     и только. */
  .tab.is-current {
    color: var(--accent);
  }

  .tab.is-current::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    /* Красная черта ложится ровно поверх светло-серой линейки:
       у вкладки нет отрицательного отступа, поэтому её низ совпадает
       с верхом рамки родителя, и -3px накрывают эти три пикселя. */
    bottom: -3px;
    height: 3px;
    background: currentColor;
  }

  .tab:hover {
    color: var(--accent);
  }

  @media (max-width: 767px) {
    /* Свёрнуто по умолчанию, разворачивает класс. На атрибут open
       опираться нельзя: он стоит в разметке с самого начала, и
       список мигал бы развёрнутым при каждой загрузке. */
    .tabs-pick .tabs {
      display: none;
    }

    .tabs-pick.is-open .tabs {
      display: flex;
    }

    /* В списке черты не рисуем: текущий раздел написан на язычке,
       а красная полоска поперёк строки читалась бы как подчёркивание.
       Вместо неё текущий пункт красным. */
    .tab::after {
      display: none;
    }

    .tab.is-current {
      color: var(--accent);
    }
  }

  /* На широком экране язычок не нужен: разделы помещаются строкой. */
  @media (min-width: 768px) {
    .tabs-pick__now {
      display: none;
    }

    .tabs {
      position: static;
      flex-direction: row;
      gap: 0 var(--s-32);
      padding: 0;
      border: 0;
      border-bottom: 3px solid var(--line-soft);
    }

    .tab {
      padding-block: 0 var(--s-8);
    }
  }

  @media (prefers-reduced-motion: reduce) {
    .tab::after {
      transition: none;
    }
  }

  /* По три в строке: кадр 16:9 при четырёх колонках мельчал
     до 304px, и стоп-кадр переставал читаться. */
  .video-grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--s-48) var(--s-32);
  }

  @media (min-width: 640px) {
    .video-grid {
      grid-template-columns: repeat(2, minmax(0, 1fr));
    }
  }

  @media (min-width: 1024px) {
    .video-grid {
      grid-template-columns: repeat(3, minmax(0, 1fr));
    }
  }

  .video-card {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  .video-card__link {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
    color: var(--text);
    text-decoration: none;
  }

  .video-card__media {
    position: relative;
    display: block;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--black);
  }

  /* Кадр из ролика показываем как есть: в отличие от снимков
     на выставках, это стоп-кадр, и обесцвечивать его незачем. */
  .video-card__image {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  .video-card__time {
    position: absolute;
    right: var(--s-8);
    bottom: var(--s-8);
    padding: 2px 6px;
    background: rgb(0 0 0 / 0.7);
    color: var(--white);
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
  }

  /* Название под кадром, а не на нём: у роликов оно доходит
     до 113 знаков и на затемнении не читалось бы. */
  .video-card__title {
    font-size: var(--fs-m);
    line-height: 1.25;
  }

  .video-card__link:hover .video-card__title {
    color: var(--accent);
  }

  /* --- Плеер ---
     Заставка с кнопкой; сам iframe появляется только по нажатию. */
  .player {
    position: relative;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    background: var(--black);
  }

  .player__start {
    display: block;
    width: 100%;
    height: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    color: var(--white);
  }

  .player__poster {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Треугольник только на странице ролика: на превью его роль
     выполняет длительность в углу. */
  .player__play {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    transition: color var(--ease);
  }

  .player__play svg {
    width: 72px;
    height: 72px;
  }

  @media (hover: hover) {
    .player__start:hover .player__play {
      color: var(--accent);
    }
  }

  .player__frame {
    display: block;
    width: 100%;
    height: 100%;
    border: 0;
  }

  .player--gone .player__poster {
    opacity: 0.4;
  }

  .player__note {
    position: absolute;
    inset: 0;
    display: grid;
    place-items: center;
    color: var(--white);
    font-size: var(--fs-s);
  }

  /* --- Партнёры выставки ---
     Группы идут в строку и переносятся: подпись мелко сверху, под ней
     свои знаки. Прежде подпись стояла отдельной колонкой слева, и блок
     разворачивался на весь экран ради десятка мелких значков.

     Размер уравнен по ПЛОЩАДИ, а не по высоте: широкий словесный знак
     и квадратная эмблема одной высоты выглядят разновеликими. Высота
     посчитана при импорте из настоящих пропорций файла и приходит
     в --h; здесь её остаётся только умножить на общий множитель.

     Цвет снят — как и у снимков в карточках: два десятка разноцветных
     логотипов иначе перекрикивают страницу. Возвращается по наведению. */
  .partners {
    --logo: 1;

    /* Группы идут в строку и переносятся, как слова: у выставки их
       бывает десять, и столбиком они занимали экран целиком, а рядом
       с каждой подписью оставалось пустое поле в треть ширины.
       Линейки сверху нет: блок и так отделён от рассказа воздухом. */
    display: flex;
    flex-wrap: wrap;
    align-items: flex-start;
    gap: var(--s-32) var(--s-48);
  }

  .partners__group {
    display: flex;
    flex-direction: column;
    /* Между подписью и знаками воздуха больше, чем между строками
       самой подписи: иначе двухстрочная подпись читается заодно
       с логотипом, будто это одна колонка текста. */
    gap: var(--s-16);
  }

  /* Подпись тише знаков: она их называет, а не спорит с ними.
     Мелких прописных вразрядку на сайте нет — ни здесь, ни где-либо
     ещё. */
  .partners__label {
    font-size: var(--fs-xs);
    font-weight: var(--fw-text);
    color: var(--muted);
    line-height: 1.3;
  }

  .partners__list {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s-16) var(--s-24);
  }

  .partners__item {
    display: inline-flex;
  }

  .partners__logo {
    display: block;
    width: auto;
    height: calc(var(--h, 32) * 1px * var(--logo));
    max-width: 100%;
    filter: grayscale(100%);
    opacity: 0.7;
    transition: filter var(--ease), opacity var(--ease);
  }

  @media (hover: hover) {
    .partners__item:hover .partners__logo {
      filter: grayscale(0);
      opacity: 1;
    }
  }

  .partners__item:focus-within .partners__logo {
    filter: grayscale(0);
    opacity: 1;
  }

  @media (max-width: 767px) {
    .partners {
      --logo: 0.85;
    }
  }

  @media (min-width: 768px) {
    /* Группа не растягивается шире своих знаков, но и не сжимается
       уже подписи: так соседние группы встают в строку без дыр. */
    .partners__group {
      max-width: 100%;
    }
  }

  /* --- Где показывали ---
     Строка на показ: площадка и город, под ними адрес из справочника
     музея, справа сроки. Гастрольная выставка бывала в десяти городах,
     и списком это читается, а строкой через запятую — нет. */
  .shows {
    display: flex;
    flex-direction: column;
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .shows__item {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: 2px var(--s-24);
    padding-block: var(--s-16);
    border-bottom: 1px solid var(--surface);
  }

  .shows__item:first-child {
    border-top: 1px solid var(--surface);
  }

  .shows__where {
    display: flex;
    flex-direction: column;
    gap: 2px;
  }

  .shows__place {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1.25;
  }

  .shows__city,
  .shows__address {
    font-size: var(--fs-s);
    color: var(--muted);
  }

  .shows__when {
    flex: none;
    font-size: var(--fs-s);
    font-variant-numeric: tabular-nums;
  }

  /* --- Перечень биеннале ---
     Строки с названием и сроками: у музея четыре семьи фестивалей
     и сорок выпусков, сеткой карточек это читалось бы как каталог,
     хотя нужен указатель. */
  .fests-all {
    display: flex;
    flex-direction: column;
    gap: var(--s-48);
  }

  .fests {
    display: flex;
    flex-direction: column;
    list-style: none;
    margin: 0;
    padding: 0;
  }

  .fests__link {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-8) var(--s-32);
    padding-block: var(--s-16);
    border-bottom: 1px solid var(--surface);
    color: var(--text);
    text-decoration: none;
  }

  .fests__item:first-child .fests__link {
    border-top: 1px solid var(--surface);
  }

  .fests__link:hover {
    color: var(--accent);
  }

  .fests__name {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1.2;
    letter-spacing: -0.01em;
  }

  .fests__when {
    flex: none;
    font-size: var(--fs-m);
    font-variant-numeric: tabular-nums;
  }

  /* --- Стена годов ---
     Не список, а картина: под каждым годом черта длиной в число
     выставок. Видно и куда нажать, и как музей рос — 14 выставок
     в 1999-м против 147 в 2004-м перечнем не различить. */
  .wall {
    display: flex;
    flex-direction: column;
    gap: var(--s-48);
  }

  .wall__decade {
    display: grid;
    gap: var(--s-16);
  }

  .wall__label {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
  }

  .wall__years {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(min(96px, 100%), 1fr));
    gap: var(--s-24) var(--s-16);
  }

  .year {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    color: var(--text);
    text-decoration: none;
  }

  .year__num {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    line-height: 1;
    letter-spacing: -0.02em;
    font-variant-numeric: tabular-nums;
  }

  /* Черта — доля от самого урожайного года. Растёт по наведению
     не в длину, а в толщину: длина здесь значит число, её трогать нельзя. */
  .year__bar {
    display: block;
    width: calc(var(--dolya, 0) * 100%);
    min-width: 2px;
    height: 2px;
    background: var(--black);
    transition: height 180ms var(--ease-out, ease), background-color 180ms ease;
  }

  .year__count {
    font-size: var(--fs-xs);
    font-variant-numeric: tabular-nums;
  }

  .year:hover .year__num,
  .year:hover .year__count {
    color: var(--accent);
  }

  .year:hover .year__bar {
    height: 6px;
    background: var(--accent);
  }

  @media (min-width: 768px) {
    .wall__decade {
      grid-template-columns: 6em 1fr;
      align-items: start;
    }

    .wall__label {
      padding-top: 0.4em;
    }
  }

  /* --- Разделы года ---
     Архив делится так же, как на прежнем сайте: Москва, регионы,
     заграница. Отбивку между разделами даёт stack--wide на странице,
     поэтому здесь только заголовок. */
  .subhead {
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    text-transform: uppercase;
    letter-spacing: -0.01em;
    line-height: 1.1;
  }

  .exhibitions-group .subhead {
    margin-bottom: var(--s-32);
  }

  /* --- Карточка выставки ---
     Подписи лежат на самом снимке: сверху место и даты, снизу художник
     и название. Затемнение привязано к подписи, а не к кадру, — нет
     текста, нет и заливки, снимок остаётся открытым. */
  .exhibition-card {
    position: relative;
  }

  /* Снимок уходит под текст отрицательным слоем, поэтому обе подписи
     остаются в обычном потоке. Это важно: растянутая ссылка меряется
     от ближайшего позиционированного предка, и будь подпись
     абсолютной, кликалась бы только она, а не весь кадр. */
  .exhibition-card__media {
    position: relative;
    isolation: isolate;
    display: grid;
    grid-template-rows: auto 1fr auto;
    aspect-ratio: 1 / 1;
    overflow: hidden;
    background: var(--black);
    color: var(--white);
  }

  .exhibition-card__image {
    position: absolute;
    inset: 0;
    z-index: -1;
    width: 100%;
    height: 100%;
    object-fit: cover;
    object-position: center 30%;
  }

  /* Вуаль намеренно светлая: плотная плашка читается как наклейка
     поверх снимка. Поэтому вершина невысокая, зато растушёвка длинная
     и в девять ступеней — так она не заканчивается видимой границей
     и не идёт полосами.

     После перехода на цветные квадратные кадры прежние 0,64 и 0,70
     читались грубо: на цветном снимке чёрная вуаль заметнее, чем
     на чёрно-белом. Снизили до 0,46 и 0,52 — буквы остаются белыми
     и читаемыми, а снимок виден. */
  .exhibition-card__meta {
    grid-row: 1;
    padding: var(--s-16) var(--s-16) var(--s-64);
    font-size: var(--fs-s);
    background: linear-gradient(to bottom,
      rgb(0 0 0 / 0.46) 0%,
      rgb(0 0 0 / 0.43) 16%,
      rgb(0 0 0 / 0.35) 32%,
      rgb(0 0 0 / 0.26) 48%,
      rgb(0 0 0 / 0.16) 64%,
      rgb(0 0 0 / 0.08) 78%,
      rgb(0 0 0 / 0.03) 90%,
      rgb(0 0 0 / 0) 100%);
    font-variant-numeric: tabular-nums;
  }

  .exhibition-card__caption {
    grid-row: 3;
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    padding: var(--s-64) var(--s-16) var(--s-16);
    background: linear-gradient(to top,
      rgb(0 0 0 / 0.52) 0%,
      rgb(0 0 0 / 0.50) 14%,
      rgb(0 0 0 / 0.42) 28%,
      rgb(0 0 0 / 0.33) 44%,
      rgb(0 0 0 / 0.22) 60%,
      rgb(0 0 0 / 0.12) 74%,
      rgb(0 0 0 / 0.05) 86%,
      rgb(0 0 0 / 0.01) 95%,
      rgb(0 0 0 / 0) 100%);
  }

  .exhibition-card__author {
    font-size: var(--fs-s);
  }

  .exhibition-card__title {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1.1;
  }

  .exhibition-card__link {
    color: inherit;
    text-decoration: none;
  }

  /* Ссылка накрывает карточку целиком — кликается весь кадр. */
  .exhibition-card__link::after {
    content: "";
    position: absolute;
    inset: 0;
  }

  .exhibition-card__link:hover {
    color: var(--accent);
  }

  .exhibition-card__age {
    font-size: var(--fs-xs);
  }

  /* =============================================================
     МЕНЮ — белая панель под шапкой

     Шапка остаётся видимой: она выше по слоям, поэтому логотип,
     иконки и «Билет» не перекрываются. Отступ сверху скрипт берёт
     от фактической высоты шапки, а не от зашитого числа.
     ============================================================= */
  .menu {
    position: fixed;
    inset: 0;
    z-index: 90;
    overflow-y: auto;
    background: var(--bg);
    opacity: 0;
    visibility: hidden;

    /* Закрытие идёт двумя тактами и в сумме занимает 0,6 с.
       Сначала волной уходят пункты, и только потом медленно тает
       белый фон: если гасить его быстро, страница под ним
       проявляется рывком — это и читается как резкость. */
    transition:
      opacity 360ms ease 240ms,
      visibility 0s linear 600ms;
  }

  .menu--open {
    opacity: 1;
    visibility: visible;
    transition:
      opacity 220ms ease,
      visibility 0s;
  }

  /* Панель принимает фокус при открытии, но кольца не показывает:
     это не орган управления, а точка входа. У ссылок внутри кольцо
     остаётся — по табу его видно как положено. */
  .menu__inner:focus {
    outline: none;
  }

  .menu__inner {
    /* Отступ = высота шапки плюс воздух. Воздуха тут больше, чем
       на странице: под шапкой пусто, и меню должно читаться как
       отдельный разворот, а не как продолжение вёрстки. */
    padding-top: calc(var(--menu-offset, var(--s-96)) + var(--s-64));
    padding-bottom: var(--s-64);
  }

  .menu__grid {
    --row-gap: var(--s-48);
  }

  /* Разделы. В разметке они первыми: на узком экране меню открывают
     ради них, а часы работы дочитывают ниже. На широком колонки
     расходятся, и перечень уходит вправо — к большому полю, где
     ему просторно. */
  .menu__list {
    display: flex;
    flex-direction: column;
    align-items: flex-start;
  }

  /* Открытие — волна сверху вниз, закрытие — снизу вверх.
     Задержка закрытия живёт в базовом правиле: оно действует ровно
     тогда, когда класс --open снят. */
  .menu__item {
    opacity: 0;
    transform: translateY(10px);
    transition:
      opacity 280ms ease,
      transform 320ms cubic-bezier(0.4, 0, 1, 1);
    transition-delay: calc((var(--n, 8) - 1 - var(--i)) * 30ms);
  }

  .menu--open .menu__item {
    opacity: 1;
    transform: none;
    transition:
      opacity 320ms ease,
      transform 380ms cubic-bezier(0.2, 0.8, 0.2, 1);
    transition-delay: calc(60ms + var(--i) * 35ms);
  }

  /* Разделительных линеек между пунктами нет. Прописные Bold сами
     держат строй, а семь линеек поперёк пустого листа превращали
     меню в бланк. По наведению меняется только цвет: ничего
     не разъезжается и не подрагивает. */
  .menu__link {
    display: inline-flex;
    align-items: center;
    gap: 0.28em;
    padding-block: 0.14em;
    color: var(--text);
    font-size: var(--fs-xl);
    font-weight: var(--fw-heading);
    line-height: 1.15;
    letter-spacing: -0.01em;
    text-transform: uppercase;
    text-decoration: none;
    transition: color 160ms ease;
  }

  .menu__link:hover,
  .menu__link:focus-visible,
  .menu__link[aria-current="page"] {
    color: var(--accent);
  }

  /* Левая колонка — то, ради чего меню открывают с телефоном в руке:
     часы, адрес, телефон. Цен на билеты здесь нет: их нет и в базе,
     а выдумывать числа, по которым человек поедет через город,
     нельзя. */
  .menu__facts {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
    font-size: var(--fs-m);
    line-height: 1.45;
  }

  .menu__fact {
    display: flex;
    flex-direction: column;
    gap: 0.35em;
  }

  .menu__label {
    font-weight: var(--fw-heading);
  }

  .menu__facts a {
    color: inherit;
    text-decoration: none;
  }

  .menu__facts a:hover {
    color: var(--accent);
  }

  /* Строка с отточием: подпись слева, значение справа, между ними
     точки. Так расписание читается сразу парами, а не сплошным
     предложением. */
  .menu__row {
    display: flex;
    align-items: baseline;
    gap: 0.5em;
  }

  .menu__dots {
    flex: 1 1 auto;

    /* Пустая ячейка равняется по базовой линии нижним краем, поэтому
       при нулевой высоте отточие ложится ровно на строку, а не
       посередине неё. */
    align-self: baseline;
    height: 0;
    border-bottom: 1px dotted currentColor;
    opacity: 0.45;
  }

  .menu__val {
    flex: none;
  }

  @media (min-width: 768px) {
    /* Перечень разделов — вправо, к правому краю листа: он крупный,
       и держать его слева значит спорить с логотипом. */
    .menu__list {
      align-items: flex-end;
      text-align: right;
    }

    /* Практическое ставим в первый ряд слева, хотя в разметке оно
       второе: на узком экране порядок обратный. */
    .menu__facts {
      grid-row: 1;
    }
  }

  /* =============================================================
     ПОИСК — оверлей поверх размытой страницы

     Оформлен как остальной сайт: поле встаёт по колонкам контента,
     под ним та же линия в один пиксель, что под лентой и в подписке,
     кнопка закрытия — такой же знак без обводки, как в шапке.
     От страницы отличается только размытием, ничего лишнего.
     ============================================================= */
  .spotlight {
    position: fixed;
    inset: 0;
    z-index: 100;
    display: grid;
    place-items: center;
    background: color-mix(in srgb, var(--bg) 78%, transparent);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    opacity: 0;
    visibility: hidden;
    transition:
      opacity 280ms ease,
      visibility 0s linear 280ms;
  }

  .spotlight--open {
    opacity: 1;
    visibility: visible;
    transition:
      opacity 280ms ease,
      visibility 0s;
  }

  /* Поле живёт по тем же полям, что и вся страница */
  .spotlight__form {
    width: 100%;
    max-width: var(--page-max);
    padding-inline: var(--gutter);
    margin-bottom: 6vh;
    opacity: 0;
    transform: translateY(12px);
    transition:
      opacity 360ms ease 60ms,
      transform 440ms cubic-bezier(0.2, 0.8, 0.2, 1) 60ms;
  }

  .spotlight--open .spotlight__form {
    opacity: 1;
    transform: none;
  }

  .spotlight__field {
    position: relative;
    padding-bottom: var(--s-16);
  }

  .spotlight__field::after {
    content: "";
    position: absolute;
    inset-inline: 0;
    bottom: 0;
    height: 1px;
    background: var(--text);
    transform: scaleX(0);
    transition: transform 500ms cubic-bezier(0.2, 0.8, 0.2, 1) 120ms;
  }

  .spotlight--open .spotlight__field::after {
    transform: scaleX(1);
  }

  .spotlight__field:focus-within::after {
    background: var(--accent);
  }

  /* Набор — как заголовок страницы: прописные, жирные */
  .spotlight__input {
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    color: var(--text);
    font-family: var(--font);
    font-size: var(--fs-xl);
    font-weight: var(--fw-heading);
    line-height: 1.15;
    letter-spacing: -0.01em;
  }

  .spotlight__input::placeholder {
    color: var(--text);
    opacity: 0.3;
  }

  .spotlight__input:focus {
    outline: none;
  }

  .spotlight__input::-webkit-search-cancel-button {
    display: none;
  }

  /* Такой же знак без обводки, как в шапке */
  .spotlight__close {
    position: absolute;
    top: var(--s-24);
    right: var(--gutter);
    display: flex;
    align-items: center;
    justify-content: center;
    width: var(--control);
    height: var(--control);
    padding: 0;
    border: 0;
    background: none;
    color: var(--text);
    cursor: pointer;
    transition: color var(--ease);
  }

  .spotlight__close:hover {
    color: var(--accent);
  }

  .spotlight__close svg {
    width: 24px;
    height: 24px;
  }

  /* --- Подвал ---
     Тот же язык, что и на странице: прописные, линии в один пиксель,
     кегли из общей шкалы. Состав блоков — из недоделанной версии сайта,
     плюс разделы, которых там не было: «Открытая информация»,
     «Независимая оценка» и «Обратная связь». По приказу Минкультуры
     № 277 до них должно быть не больше двух переходов от главной. */
  /* Подвал стоит на серой плашке — той же, что под лентой снимков.
     Из контейнера он выходит тем же --bleed: отрицательное поле
     тянет плашку до краёв окна, равное ему внутреннее возвращает
     содержимое на место сетки. Линия сверху не нужна: цвет
     отделяет надёжнее черты. */
  .footer {
    margin-inline: calc(var(--bleed) * -1);
    margin-top: var(--s-64);
    padding: var(--s-48) var(--bleed);
    background: var(--surface);
    font-size: var(--fs-m);
    line-height: 1.35;
  }

  /* --- Верхний ярус: кто мы и приглашение подписаться --- */
  .footer__intro {
    row-gap: var(--s-48);
  }

  .footer__name {
    display: block;
    width: fit-content;
    color: var(--text);
    text-decoration: none;
  }

  .footer__logo {
    display: block;
    width: 100%;
    max-width: 150px;
    height: auto;
    margin-bottom: var(--s-24);
  }

  .footer__address {
    font-style: normal;
    line-height: 1.5;
  }

  /* --- Нижний ярус: перечни ссылок --- */
  /* Линия в подвале одна — на его верхнем крае. Внутренних больше
     нет: три черты подряд читались как таблица, а разделяет ярусы
     воздух, его для этого достаточно. */
  .footer__nav {
    row-gap: var(--s-48);
    margin-top: var(--s-64);
  }

  .footer__block {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  /* Заголовок столбца в футере. Обычный набор, полужирным: раньше
     тут были мелкие прописные вразрядку с красной точкой — приём,
     которого на сайте больше нет нигде. Линии над каждой меткой
     убраны давно: их набиралась целая лесенка. */
  .footer__label {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
  }

  /* Подпись и адрес в строку, одним кеглем. Мелкие прописные здесь
     были лишними: раздел уже назван меткой сверху, а пять подписей
     подряд превращали перечень в таблицу. */
  .footer__contacts {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }

  .footer__contacts li {
    display: grid;
    grid-template-columns: 9em 1fr;
    gap: var(--s-8);
  }

  /* Ячейка сетки растянута по колонке, и ссылка вместе с ней: адрес
     подсвечивался, когда указатель стоял в пустоте справа от него.
     Прижимаем к началу — наводка идёт ровно по адресу. */
  .footer__contacts a {
    justify-self: start;
  }

  .footer__links {
    display: flex;
    flex-direction: column;
  }

  /* Ссылка занимает ширину своего текста, а не всей колонки: строчно-
     блочная, чтобы отбивка сверху и снизу считалась, но подчёркивание
     и наведение срабатывали ровно по словам. */
  .footer__links a {
    display: inline-block;
    padding-block: 6px;
  }

  .footer a {
    color: var(--text);
    text-decoration: none;
  }

  .footer a:hover {
    color: var(--accent);
  }

  /* Повышенная доступность. Кнопка, а не ссылка: она переключает
     показ этой же страницы, никуда не ведёт. Очки оставлены прежние,
     но теперь с подписью — значок без слов в подвале никто не ищет. */
  .footer__a11y {
    margin-top: var(--s-32);
  }

  .a11y-switch {
    display: inline-flex;
    align-items: center;
    justify-self: start;
    gap: 10px;
    padding: 0;
    border: 0;
    background: none;
    color: inherit;
    font: inherit;
    font-size: var(--fs-s);
    cursor: pointer;
  }

  .a11y-switch__pic {
    width: 28px;
    height: 17.5px;
    flex: none;
  }

  .a11y-switch__pic :is(path, circle) {
    stroke-width: 2;
  }

  .a11y-switch:hover,
  .a11y-switch[aria-pressed="true"] {
    color: var(--accent);
  }

  .footer__bottom {
    row-gap: var(--s-8);
    margin-top: var(--s-64);
    font-size: var(--fs-s);
  }

  /* --- Выбор даты в расписании ---
     Раскрывается родным <details>, каждый день — обычная ссылка.
     Выбор живёт в адресе: его можно переслать и вернуться назад
     кнопкой браузера. Скрипта здесь нет вовсе. */
  /* На узком экране календарь встаёт над расписанием — между ними
     нужна отбивка, которой в сетке по умолчанию нет. */
  .sched-layout {
    row-gap: var(--s-48);
  }

  .datepick {
    display: flex;
    flex-direction: column;
    gap: var(--s-16);
  }

  .datepick__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: var(--s-16);
  }

  .datepick__month {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
  }

  .datepick__nav {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 32px;
    height: 32px;
    color: var(--text);
    font-size: var(--fs-l);
    line-height: 1;
    text-decoration: none;
  }

  .datepick__nav:hover {
    color: var(--accent);
  }

  /* Дальше архива листать некуда — стрелку не убираем, чтобы шапка
     не прыгала, а гасим. */
  .datepick__nav--off {
    opacity: 0.2;
  }

  .datepick__grid {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 2px;
  }

  /* Дни недели без прописных: в разметке они и так с прописной
     буквы — «Пн», «Вт». Целиком прописными это опять тот же ярлык. */
  .datepick__dow {
    padding-bottom: var(--s-8);
    font-size: var(--fs-s);
    font-weight: var(--fw-heading);
    text-align: center;
  }

  /* Выходные красным — как в настенном календаре. */
  .datepick__dow--rest {
    color: var(--accent);
  }

  .datepick__day {
    position: relative;
    display: grid;
    place-items: center;
    aspect-ratio: 1;
    border-radius: 50%;
    color: var(--text);
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1;
    text-decoration: none;
  }

  /* День с событиями — красный, и под числом красная точка: тот же
     знак, что разделяет крошки и метит статус музея. Дни без событий
     остаются обычными числами: щёлкать по ним нечего. */
  a.datepick__day {
    color: var(--accent);
  }

  /* Вплотную под числом: при 7px точка налезала на цифру,
     при 3px висела оторванной. */
  a.datepick__day::after {
    position: absolute;
    bottom: 4px;
    left: 50%;
    margin-left: -2px;
  }

  a.datepick__day:hover {
    background: var(--surface);
  }

  .datepick__day--void {
    visibility: hidden;
  }

  /* Сегодняшнее число обведено тонко: метка места, а не выбора. */
  .datepick__day--today {
    box-shadow: inset 0 0 0 1px currentColor;
  }

  /* Выбранное — чёрным: красный уже занят под «здесь что-то есть»,
     двумя красными состояниями подряд выбор было бы не различить. */
  .datepick__day.is-on,
  a.datepick__day.is-on:hover {
    background: var(--text);
    color: var(--white);
    box-shadow: none;
  }

  a.datepick__day.is-on::after {
    background: var(--white);
  }

  /* --- Фишка выбранного значения ---
     Серая плашка с крестиком: так во всех интерфейсах помечают
     наложенный фильтр, и так сразу видно, что его можно снять.
     Крестик — обычная ссылка на страницу без фильтра: работает
     без скрипта, и по ней можно уйти средней кнопкой мыши. */
  .chip {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 5px 5px 14px;
    border-radius: 999px;
    background: var(--surface);
    font-size: var(--fs-s);
    line-height: 1.2;
  }

  .chip__off {
    display: grid;
    place-items: center;
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    color: var(--text);
    transition: background var(--ease), color var(--ease);
  }

  .chip__off svg {
    width: 8px;
    height: 8px;
  }

  .chip__off:hover {
    background: var(--text);
    color: var(--bg);
  }

  .sched__empty {
    font-size: var(--fs-read);
  }

  /* --- Согласие на файлы cookie ---
     Белая плашка у нижнего края поверх страницы: вёрстку не двигает,
     по нажатию уходит навсегда. Теней на сайте нет, поэтому от фона
     её отделяет обычная линия в пиксел — та же, что везде. */
  .cookie {
    position: fixed;
    z-index: 30;
    /* По горизонтали — по колонкам страницы, а вот снизу поле
       у --gutter на широком экране 64px: плашка висела бы в воздухе.
       Прижимаем к краю окна на постоянную величину. */
    right: var(--gutter);
    bottom: var(--s-24);
    left: var(--gutter);
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-16) var(--s-32);
    max-width: calc(var(--page-max) - var(--gutter) * 2);
    margin-inline: auto;
    padding: var(--s-16) var(--s-24);
    border: 1px solid var(--line);
    border-radius: 24px;
    background: var(--bg);
    font-size: var(--fs-s);
    line-height: 1.4;
  }

  /* Текст занимает всё, что остаётся от кнопки, но на узком экране
     переносится под неё целой строкой, а не в два слова. */
  .cookie__text {
    flex: 1 1 20em;
    margin: 0;
  }

  .cookie a {
    color: inherit;
    text-decoration: underline;
    text-underline-offset: 2px;
  }

  .cookie a:hover {
    color: var(--accent);
  }

  /* =============================================================
     УКАЗАТЕЛЬ АВТОРОВ

     Две с половиной тысячи имён. Подряд его не читают — в нём ищут
     одного человека, поэтому главное здесь азбука: она липнет
     к верху окна и всегда под рукой.

     Раскладка по фамилии, а не по имени: «Фабрис Ибер» стоит на «И».
     Буквы, на которые никого нет, показаны и погашены — полный ряд
     читается как указатель, неполный как обрыв вёрстки.
     ============================================================= */
  .azbuka {
    position: sticky;
    top: 0;
    z-index: 3;
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    padding-block: var(--s-16);
    background: var(--bg);
    border-bottom: 3px solid var(--surface);
  }

  /* На узком экране ряд переносится вчетверо, и восьмёрка между
     строками отнимала бы у страницы шестую часть экрана. */
  .azbuka__row {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 6px var(--s-16);
  }

  @media (min-width: 768px) {
    .azbuka__row {
      gap: var(--s-8) var(--s-16);
    }
  }

  /* Латиница вторым рядом и мельче: на неё приходится три десятка
     имён из двух с половиной тысяч. */
  .azbuka__row--lat {
    font-size: var(--fs-s);
  }

  .azbuka__letter {
    font-size: var(--fs-m);
    font-weight: var(--fw-heading);
    line-height: 1;
    color: var(--text);
    text-decoration: none;
    transition: color var(--ease);
  }

  /* Красным горит буква той группы, что сейчас вверху окна,
     и та, на которую навели. Ставит её скрипт; без него азбука
     просто перестаёт подсвечиваться, ссылки работают. */
  .azbuka__letter:hover,
  .azbuka__letter.is-now {
    color: var(--accent);
  }

  .azbuka__letter--off {
    color: var(--muted);
  }

  .az-all {
    display: flex;
    flex-direction: column;
    gap: var(--s-48);
  }

  /* Буква висит в левом поле, имена занимают остальные колонки —
     тем же приёмом набран рассказ на странице выставки.
     scroll-margin спасает заголовок группы от липкой азбуки:
     без него переход по букве прячет её под азбукой. */
  .az {
    row-gap: var(--s-16);
    /* Высоту азбуки записывает скрипт: на узком экране ряд букв
       переносится и вырастает вдвое. Запасное значение — на случай,
       если скрипт не выполнился. */
    scroll-margin-top: calc(var(--azbuka-h, 77px) + var(--s-24));
  }

  .az__letter {
    display: flex;
    align-items: baseline;
    gap: var(--s-8);
    font-size: var(--fs-xl);
    font-weight: var(--fw-heading);
    line-height: 1;
    color: var(--accent);
  }

  /* Сколько имён на букву. Чёрным: красное здесь уже сказано
     буквой, а два красных рядом спорят друг с другом. */
  .az__count {
    font-size: var(--fs-s);
    font-weight: var(--fw-text);
    color: var(--text);
  }

  /* Колонками, а не сеткой: имена разной длины, и колонка сама
     разложит их плотно. Пунктом здесь считается имя, поэтому
     разрывать его между колонками нельзя. */
  .az__list {
    columns: 2;
    column-gap: var(--s-32);
    padding: 0;
    list-style: none;
  }

  .az__item {
    margin-bottom: 6px;
    break-inside: avoid;
  }

  /* Строчно-блочная: наведение идёт по имени, а не по всей ширине
     колонки — как в подвале. */
  .az__name {
    display: inline-block;
    color: var(--text);
    text-decoration: none;
    line-height: 1.3;
    transition: color var(--ease);
  }

  .az__name:hover {
    color: var(--accent);
  }

  /* =============================================================
     ПОИСК

     Список находок, а не карточки: искомое опознают по названию
     и по строчке текста вокруг совпадения, снимок тут не помогает
     и только растягивает страницу на экран в высоту.
     ============================================================= */
  .searchbar__field {
    display: flex;
    align-items: center;
    gap: var(--s-16);
    padding-bottom: var(--s-8);
    border-bottom: 3px solid var(--line);
  }

  .searchbar__input {
    flex: 1 1 auto;
    min-width: 0;
    padding: 0;
    border: 0;
    background: none;
    font: inherit;
    font-size: var(--fs-l);
    font-weight: var(--fw-heading);
    color: var(--text);
  }

  /* Общий контур фокуса тут не годится: поле стоит на черте, и
     красный прямоугольник обводил всю строку — при autofocus он
     встречал посетителя сразу на загрузке. Фокус показывает сама
     черта, она же и есть граница поля. */
  .searchbar__input:focus-visible {
    outline: none;
  }

  .searchbar__field:focus-within {
    border-bottom-color: var(--accent);
  }

  .searchbar__input::placeholder {
    color: var(--muted);
    font-weight: var(--fw-text);
  }

  /* Крестик очистки у type="search" в вебките рисуется своим
     значком поверх набора — убираем, поле и так очищается. */
  .searchbar__input::-webkit-search-cancel-button {
    appearance: none;
  }

  /* Кнопка — лупа из шапки, красная. Рост знака взят от кегля поля,
     чтобы он не всплывал над строкой набора при мелком экране. */
  .searchbar__go {
    display: flex;
    flex: none;
    align-items: center;
    padding: 0;
    border: 0;
    background: none;
    color: var(--accent);
    cursor: pointer;
  }

  /* Размер и толщина лупы заданы у самого знака (.icon-search),
     здесь ничего не переопределяем — иначе две копии одного знака
     снова разъедутся. */

  .searchbar__go:hover {
    color: var(--accent-hover);
  }

  .search-count {
    font-size: var(--fs-m);
  }

  .finds {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
  }

  .find {
    display: flex;
    flex-direction: column;
    gap: var(--s-8);
    max-width: 62ch;
  }

  .find__link {
    display: flex;
    flex-direction: column;
    gap: 2px;
    width: fit-content;
    color: var(--text);
    text-decoration: none;
  }

  /* Раздел мельче названия и не жирный: он подсказка, а не заголовок. */
  .find__where {
    font-size: var(--fs-s);
    color: var(--muted);
  }

  .find__title {
    font-size: var(--fs-read);
    font-weight: var(--fw-heading);
    line-height: 1.25;
  }

  .find__link:hover .find__title {
    color: var(--accent);
  }

  /* Отрывок вокруг совпадения. Само совпадение указатель обводит
     тегом <b> — красим его красным, но не утолщаем: жирным здесь
     набрано название, и второе жирное слово спорило бы с ним. */
  .find__intro {
    font-size: var(--fs-m);
    line-height: 1.45;
    color: var(--text);
  }

  .find__intro b {
    font-weight: inherit;
    color: var(--accent);
  }

  /* Пометка «текст только по-русски». Не ошибка и не тревога,
     поэтому серым, а не красным: это сообщение о языке, а не о сбое. */
  .text-note {
    margin-bottom: var(--s-16);
    font-size: var(--fs-s);
    color: var(--muted);
  }

  /* --- Находки по разделам ---
     Каждая находка рисуется тем же чанком, что и в своём разделе:
     выставка кадром с датами, ролик стоп-кадром с длительностью,
     книга обложкой. Плоский список из них подряд читался кашей. */
  .finds-all {
    display: flex;
    flex-direction: column;
    gap: var(--s-96);
  }

  .finds-group {
    display: flex;
    flex-direction: column;
    gap: var(--s-32);
  }

  .finds-group__head {
    display: flex;
    align-items: baseline;
    gap: var(--s-8);
  }

  /* Сколько всего в разделе. Не красное: красным набрана буква
     в указателе авторов, и два разных смысла одним цветом спорят. */
  .finds-group__count {
    font-size: var(--fs-s);
    font-weight: var(--fw-text);
    text-transform: none;
    letter-spacing: 0;
  }

  /* Строка расписания в находках занимает всю ширину страницы, и
     кадр в ней вырастал до 570 пикселей: в своём разделе он такой же,
     но там рядом календарь и колонка вдвое уже. Держим её в ширине
     колонки для чтения — как и обычную находку. */
  .finds-group .sched__rows {
    max-width: var(--prose-max);
  }

  .finds__more {
    font-size: var(--fs-m);
  }

  .finds__all {
    color: var(--accent);
    text-decoration: none;
  }

  .finds__all:hover {
    color: var(--accent-hover);
    text-decoration: underline;
    text-underline-offset: 3px;
  }

  /* --- Постраничная навигация --- */
  .pager {
    margin-top: var(--s-48);
  }

  .pager__list {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: var(--s-8) var(--s-16);
    padding: 0;
    list-style: none;
    font-size: var(--fs-m);
  }

  .pager__link {
    color: var(--text);
    text-decoration: none;
  }

  .pager__link:hover {
    color: var(--accent);
  }

  .pager__link--now {
    color: var(--accent);
  }

  .pager__item--skip {
    color: var(--muted);
  }

  @media (min-width: 768px) {
    .az__list {
      columns: 3;
    }
  }

  @media (min-width: 1280px) {
    .az__list {
      columns: 4;
    }
  }

}

/* -------------------------------------------------------------
   УТИЛИТЫ — единственное место, где допустим !important
   ------------------------------------------------------------- */
@layer utilities {
  .visually-hidden:not(:focus, :active) {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    white-space: nowrap;
    clip-path: inset(50%);
  }

  .prose {
    max-width: var(--prose-max);
  }

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

  /* Отменяет апперкейс на куске текста: названия работ вроде
     SANCTUM[green] пишутся так, как их задумал автор */
  .keep-case {
    text-transform: none;
  }
}
