/* ============================================================================
   components.css — бібліотека компонентів
   ----------------------------------------------------------------------------
   Розмітка з макета, скін через токени, двигуни власні. Жодного числа поза
   `tokens.css`, крім тих, що є властивістю самої розмітки (кількість колонок,
   співвідношення сторін фото).

   Наведення — **тільки** під `@media (hover: hover)`. Без цього на тачі
   стан наведення «залипає» після тапу: пристрій вважає палець курсором, і
   картка лишається підсвіченою, доки не торкнешся іншої.
   ============================================================================ */

/* ==========================================================================
   ========================================================================== */
/* ⚠️ КНОПКИ Й ПОЛЯ — ЗВИЧАЙНОЇ ЖИРНОСТІ (рішення власниці, 12.09: «прибери
   всюди додаткову жирність з полів та кнопок»). У макеті вони медіум (500);
   власниця подивилась на живі сторінки й вирішила інакше. Заголовки, ціни й
   посилання це рішення НЕ зачіпає — вони лишаються медіум, бо там жирність
   несе ієрархію, а не «натиск».

   Правило стоїть тут, поруч із самими компонентами, а не окремим файлом
   поверх: другий файл із тими самими селекторами розійшовся б із цим на
   першій же правці. */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s-8);
  min-height: var(--btn-h);
  /* Ширина в легенді фіксована — 216. Тут `min-width`, а не `width`: на
     216 вміщається «Купити», але не «Виготовлення на замовлення», і кнопка
     мусить рости, а не обрізати підпис. */
  min-width: var(--btn-min-w);
  padding-inline: var(--s-48);
  border-radius: var(--radius-pill);
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  text-align: center;
  transition: background-color var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .btn:hover { background: var(--btn-bg-hover); }
}

/* Вимкнений стан — для «Не виготовляється». `aria-disabled`, а НЕ атрибут
   `disabled`: вимкнена кнопка з `disabled` випадає з обходу клавіатурою, і
   користувач скрінрідера не дізнається ні що вона тут є, ні чому вона
   недоступна. Пояснення несе видимий текст поруч, а не тільки колір.

   `black/600` (#717070). 2.62:1 контрасту тут не порушення: WCAG 2.2 виводить вимкнені
   контроли з-під вимоги 1.4.3, а причину каже видимий текст ціни поруч
   («Не виготовляється»), а не самий лише тон. */
.btn[aria-disabled="true"] {
  background: var(--black-200);
  color: var(--ink-muted);
  cursor: not-allowed;
}

@media (hover: hover) {
  .btn[aria-disabled="true"]:hover { background: var(--black-200); }
}

/* Кругла кнопка: ♡ і «поділитись». Базовий стан контурний, наведення —
   суцільна заливка з інверсією іконки. */
.btn-round {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* `flex: none` — не оформлення, а форма: у гнучкому ряду коло стискається
     першим і перестає бути колом (22×48 на 440). Ловить це
     `check-breakpoints` рядком «КОНТРОЛЬ ДЕФОРМОВАНО». */
  flex: none;
  width: var(--btn-round);
  height: var(--btn-round);
  border: var(--hairline) solid var(--line);
  border-radius: 50%;
  color: var(--ink);
  background: var(--surface);
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

/* Іконки в круглих кнопках дрібніші за стандартну: у легенді ♡ це 20, а
   «поділитись» 16 при однаковому колі 56. Ставити скрізь 24 було б
   «приблизно так само» — і саме так зникає різниця, закладена дизайнером. */
/* ⚠️ Товщина названа разом із розміром, а не успадкована від файла: у наборі
   намальовані в полі 24 з 1.4. Без цих двох рядків браузер стискав би
   обведення разом із геометрією — 1.17 і 1.25 замість 1.5, тобто тонше за
   будь-що на сторінці. Числа в одиницях viewBox: 1.8 × 20/24 = 1.5 і
   2.25 × 16/24 = 1.5.
   Ловить це `.qa/check-icon-scale.js`. */
.btn-round svg { width: 20px; height: 20px; stroke-width: 1.8; }
/*   viewBox мусять збігатися, інакше обведення їде разом із масштабом — це
   ловить `.qa/check-icon-scale.js`. Вигадувати потрібну товщину добутком не
   можна: знак іншого розміру малюється у власній сітці. */
.btn-round--share svg { width: 16px; height: 16px; stroke-width: 1.4; }

@media (hover: hover) {
  .btn-round:hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
    color: var(--btn-ink);
  }
}

/* Стан «в обраному» — не наведення, а збережений вибір. Тому він
   лишається після того, як курсор пішов, і має власну ознаку в розмітці. */
.btn-round[aria-pressed="true"] {
  background: var(--btn-bg);
  border-color: var(--btn-bg);
  color: var(--btn-ink);
}

/* ==========================================================================
   Текстове посилання — підкреслення з'являється при наведенні
   ========================================================================== */
.link {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  font-weight: var(--fw-medium);
  color: var(--ink);
}

/* Підкреслення робиться `background-image`, а не `text-decoration`:
   градієнт анімується по `background-size`, а `text-decoration` — ні. Лінія
   лягає під ТЕКСТ, не під іконку, — так у легенді.

   ЛІНІЯ РОСТЕ З СЕРЕДИНИ, а не від лівого краю: `background-position: 50%`
   при `background-size` від 0 до 100 % розводить її в обидва боки від центру.
   Носіїв цієї лінії у проєкті ОДИНАДЦЯТЬ, і всі перелічені тут поіменно:
   `.link__text`, `.tab`, група шапки (`.header__lang a`, `.header__shop`,
   `.header__menu a`, `.header__account-text`), меню підвалу, заклик першого
   екрана, слово половини пари, хлібні крихти, смуга дій фільтра, заголовок
   пункту акордеона (`.doc__fold .fold__text`, вимога замовника 23.08) і два
   місця сторінки «Контакти» — телефон у рядку контактів та посилання на
   політику в примітці форми (обидва через `.link__text`, тон і вага —
   від сусіднього тексту).
   Зведення в один клас свідомо НЕ робиться: у кожного свій кегль, свій колір
   і свій стан-вмикач — спільним лишається саме РУХ.
   ⚠️ Число в цьому рядку — знаменник, а не прикраса: перелік, який не
   збігається з дійсністю, є твердженням про повноту, і неправдивим.
   Додаєш носія — правиш тут. */
.link__text {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .link:hover .link__text { background-size: 100% var(--hairline); }
}

/* Клавіатурний фокус має показувати те саме, що й наведення: інакше людина,
   яка ходить табом, бачить менше, ніж людина з мишею. */
.link:focus-visible .link__text { background-size: 100% var(--hairline); }

/* Іконка посилання — це ОДИН знак канону «arrow up round top bottom»
   ужитий у трьох положеннях і більше ніде не змінюється:
     90° «Перейти до колекції» — стрілка вправо, дуги стають дужками
     180° «Більше» — стрілка вниз
     0° «Згорнути» — стрілка вгору
   Попередня редакція малювала на цьому місці просту стрілку без дуг — саме
   тих дуг і бракувало (пункт 5 рев'ю). */
.link__icon { flex: none; width: var(--icon); height: var(--icon); }

.link__icon--right { rotate: 90deg; }

/* ==========================================================================
   Таб / перемикач зони — легенда: неактивний приглушений, активний темний
   із підкресленням
   ========================================================================== */
.tab {
  font-weight: var(--fw-medium);
  color: var(--ink-subtle);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition:
    color var(--dur-fast) var(--ease),
    background-size var(--dur-fast) var(--ease);
}

.tab[aria-selected="true"],
.pop__trigger[aria-expanded="true"] {
  color: var(--ink);
  background-size: 100% var(--hairline);
}

/* Наведення показує РІВНО те саме, що активний стан: тон `black/900` і
   таби різняться саме цими двома ознаками, і друга — підкреслення — у першій
   редакції була загублена: наведення міняло лише колір (пункт 6 рев'ю). */
@media (hover: hover) {
  .tab:hover { color: var(--ink); background-size: 100% var(--hairline); }
}

.tab:focus-visible { color: var(--ink); background-size: 100% var(--hairline); }

/* ==========================================================================
   Бейдж стану товару. Кольори — з таблиці станів `docs/COMPONENT-MAP.md`;
   три тони зсунуті так, щоб пройти AA зі збереженням ієрархії.
   ========================================================================== */
/* Розміри йдуть змінними, бо в каноні плашка має ДВА скіни, і поділ між ними
   не по ширині екрана, а по контексту: у стрічці слайдера на мобільному вона
   лишається 55×32, а в сітці каталогу того самого якоря стає
   і один із двох випадків був би неправильним.

   `min-width` — те, чого в макеті немає як властивості, але що з нього
   планшеті. Без мінімуму ширина визначається сумою гліфів і виходить дробовою
   (51.66 у «Sale», 53.84 у «New») — три плашки в ряд стоять трьома різними
   ширинами, жодна з яких не є числом макета. Мінімум ставить усі три на 54,
   довший текст, як «Пошиття», розсуває плашку далі. */
.badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: var(--badge-min, 54px);
  height: var(--badge-h, var(--s-32));
  padding-inline: var(--badge-px, var(--s-12));
  border: var(--hairline) solid currentColor;   /* рамка й текст — один тон */
  border-radius: var(--radius-pill);
  /*     і жодної заливки: крізь плашку видно знімок. Дві попередні редакції
     ставили спершу біле тло, потім біле напівпрозоре — обидві були домислом. */
  background: transparent;
  font-size: var(--badge-fs, var(--fs-ui));
  line-height: var(--badge-lh, var(--lh-ui));
  font-weight: var(--fw-regular);
  white-space: nowrap;
}

.badge--new  { color: var(--badge-new); }
.badge--sale { color: var(--badge-sale); }
.badge--sold { color: var(--badge-sold); }

/* ==========================================================================
   Ціна. Стара ціна існує ТІЛЬКИ у стані Sale і є окремим елементом.

   `<ins>` для нової і `<del>` для старої — це не стилістичний вибір, а
   **контракт передачі в CMS**, який перевіряє `.qa/lint-page.py`. Заразом він
   вирішує доступність: закреслення живе в семантиці, тож скрінрідер не
   прочитає стару ціну як чинну. Перше написання цього блоку використало
   `<s>` — контракт був порушений, поки його не прочитали.
   ========================================================================== */
.price { display: inline-flex; align-items: baseline; gap: var(--s-8); color: var(--price-ink); }
.price ins { text-decoration: none; }   /* підкреслення від <ins> тут зайве */
.price__old { color: var(--price-ink); }

/* У стані Sale тони міняються місцями: чинна ціна стає основною, приглушеною
   лишається закреслена (знято з картки канону — нова `black/900`
   стара `black/400`). Селектор через `:has`, а не через клас-модифікатор на
   `.price`: наявність `<del>` І Є ознакою знижки, і другий носій того самого
   факту рано чи пізно розійшовся б із першим. */
.price:has(.price__old) .price__new { color: var(--ink); }

/* ==========================================================================
   Картка товару
   ========================================================================== */
/* Пропорція фото — НЕ одна на весь проєкт. Знята властивостями вузлів на
   кожному якорі окремо:
   Одна пропорція на всі контексти була б майже правдою на десктопі й планшеті
   (0.7059 проти 0.7052), але на мобільному дала б 235 замість 241 у сітці й
   457 замість 466 у стрічці. Дельта сиділа б під, який писався про
   дворядкову назву й накрив собою чужий дефект — уже вдруге.

   Тримається двома змінними, а не готовим дробом, бо ту саму пропорцію
   вживає слайдер, щоб поставити стрілку по центру ФОТО. Два записи одного
   числа розійшлись би при першій же правці. */
.card, .slider { --card-w: 323; --card-h: 466; }

@media (min-width: 768px) {
  .card, .slider { --card-w: 378; --card-h: 536; }
}

@media (min-width: 1024px) {
  .card, .slider { --card-w: 432; --card-h: 612; }
}

/*   колонка тягне за собою власну пропорцію фото, менший ♡, компактну плашку й
   відступ 12 замість 20. Це ВАРІАНТ компонента, а не наслідок ширини екрана:
   у стрічці слайдера того самого якоря картка лишається великою. Тому скін
   вішається на контекст `.grid`, а від планшета скасовується цілком. */
.grid .card {
  --card-w: 166;
  --card-h: 241;
  --card-pad: var(--s-12);
  --card-like: 20px;
  --badge-min: 40px;
  --badge-h: var(--s-24);
  --badge-px: var(--s-8);
  /* Кегль 11/14. У самому наборі канону сусідні плашки набрані 11 і 10 — «New»
     більшим за «Sale» на піксель. Два кеглі в одному ряду читаються як збій
     набору, тому взято більший на всі три. */
  --badge-fs: 11px;
  --badge-lh: 14px;
  --flag-icon: 14px;
  --card-gap-media: 10px;
  --card-body-inline: 3px;
}

@media (min-width: 768px) {
  .grid .card {
    --card-w: 378;
    --card-h: 536;
    --card-pad: var(--s-24);
    --card-like: var(--icon);
    --badge-min: 54px;
    --badge-h: var(--s-32);
    --badge-px: var(--s-12);
    --badge-fs: var(--fs-ui);
    --badge-lh: var(--lh-ui);
    --flag-icon: 18px;
    --card-gap-media: var(--s-16);
    --card-body-inline: 34px;
  }
}

@media (min-width: 1024px) {
  .grid .card { --card-w: 432; --card-h: 612; }
}

/* Відступ плашок і ♡ від краю фото. У каноні він теж не один: 20 у стрічці на
   мобільному, 24 на планшеті й десктопі, 12 у мобільній сітці. */
.card {
  position: relative;
  display: flex;
  flex-direction: column;
  --card-pad: var(--s-20);
  --card-like: var(--icon);
  --card-body-inline: var(--s-6);
}

@media (min-width: 768px) {
  .card { --card-pad: var(--s-24); --card-body-inline: 34px; }
}

@media (min-width: 1024px) {
  .card { --card-body-inline: var(--s-32); }
}

.card__media {
  position: relative;
  aspect-ratio: var(--card-w) / var(--card-h);
  overflow: hidden;
  background: var(--black-100);
}

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

/* --------------------------------------------------------------------------
   у базовому кадрі прихована). Це і є перемикання знімка курсором: смуга
   фото поділена на три зони, зона під курсором показує свій кадр.

   Кадри складені стосом і перемикаються прозорістю, а не заміною `src`:
   заміна дала б білий проблиск на кожному русі миші. Усі три лежать у DOM
   від початку, тож і підвантажуються один раз.

   Без курсора (дотик, клавіатура) картка лишається з першим кадром — це не
   втрата: решта знімків є на сторінці товару.
   -------------------------------------------------------------------------- */
/* ПЕРЕХІД МІЖ КАДРАМИ — ОДНЕ ПРАВИЛО НА ДВОХ НОСІЇВ: картка стрічки і
   велика галерея картки товару. Носії різні, механіка одна — кадри лежать
   стосом і міняються прозорістю, а не підміною `src`.

   Підміна `src` того самого `<img>` дала б не перехід, а моргання — нове
   зображення ще й підвантажується. Носій галереї дописаний у ті самі три
   селектори, а не отримав власне згасання: одна механіка — одне правило. */
.card__frames, .pgal__frames { position: absolute; inset: 0; }

.card__frame, .pgal__frame {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
}

.card__frame.is-current, .pgal__frame.is-current { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .card__frame, .pgal__frame {
    transition: opacity var(--dur-base) var(--ease);
  }
}

/* Індикатор кадру — 160×2 по центру, 24 від низу фото (канон). Три сегменти
   з проміжком 2 дають рівно 160: (160 − 2×2) / 3 = 52. Ширина не записана
   числом на сегмент — її дає `flex: 1`, тому четвертий кадр не потребує CSS.

   У макеті смуга схована в базовому кадрі й показана в кадрі наведення —
   тобто це підказка «тут є ще знімки», а не постійний елемент. */
.card__dots {
  position: absolute;
  inset-block-end: var(--s-24);
  inset-inline-start: 50%;
  translate: -50% 0;
  display: flex;
  gap: var(--s-2);
  width: 160px;
  opacity: 0;
  pointer-events: none;
}

.card__dots li { flex: 1; height: var(--s-2); background: rgba(255, 255, 255, .6); }
/* Поточний сегмент шукається КЛАСОМ, а не парою «тег + клас»: картку
   каталогу в темі рендерить луп Woo, і `li` тут може стати будь-чим
   (CMS-READY §2.2). Правило вище лишається на тегу свідомо — воно описує
   вміст ВЛАСНОГО списку, а не стан, який пише двигун. */
.card__dots > .is-current { background: var(--ink); }

@media (prefers-reduced-motion: no-preference) {
  .card__dots { transition: opacity var(--dur-fast) var(--ease); }
}

@media (hover: hover) {
  .card:hover .card__dots { opacity: 1; }
}

/* Плашки й ♡ — по кутах фото з однаковим відступом 24 (канон). Плашки їдуть
   ОДНИМ рядком, бо в каноні поруч із бейджем стоїть іконка «Transformer»
   зміні тексту бейджа. */
.card__flags {
  position: absolute;
  inset-block-start: var(--card-pad);
  inset-inline-start: var(--card-pad);
  z-index: 1;
  display: flex;
  align-items: center;
  gap: var(--s-8);
}

/* `display: flex`, а не типовий для кнопки `inline-block`: інакше під іконкою
   лишається 4 px рядкового просвіту, коробка ♡ виходить 24×28 замість 24×24
   канону, і сам знак стоїть на 2 px вище, ніж має. Спіймано заміром коробки,
   не оком. */
.card__like  { position: absolute; inset-block-start: var(--card-pad); inset-inline-end: var(--card-pad); z-index: 1; display: flex; }
/* ЗНАК КАРТКИ МІНЯЄ РОЗМІР ПО ЯКОРЯХ, А ЛІНІЯ — НІ. `--card-like` дає 24 на
   десктопі й 20 на мобільному, і разом із коробкою браузер стискав обведення:
   1.17 замість 1.4, тобто тонше за все на сторінці. `non-scaling-stroke` —
   явна заява «лінія малюється в пікселях екрана»: геометрія масштабується,
   товщина ні. Канон дає для знака 20 власні 1.5, але то ІНШИЙ
   вузол з іншим шляхом; тут файл один, і базова товщина набору — 1.4. */
.card__like svg {
  width: var(--card-like);
  height: var(--card-like);
  vector-effect: non-scaling-stroke;
}

/* ⚠️ Розходження з каноном: ♡ стоїть по ЦЕНТРУ рядка плашок, а не по
   його верху. У каноні обидва елементи вирівняні по верхньому краю
   плашкою 32 виходить на 4 вище її середини. Відхилення дорівнює цим 4.

   Зсув застосовується ЛИШЕ там, де плашка справді є: `:has()` бере на себе
   те, чого не знає жоден `@media`. У мобільній сітці канону плашка прихована
   верху, а не з'їхати на 14 «про всяк випадок». */
.card__media:has(.card__flags) .card__like {
  inset-block-start: calc(
    var(--card-pad) + (var(--badge-h, var(--s-32)) - var(--card-like)) / 2);
}

/*   у базовому кадрі прихований) — це і є стан наведення. Знак той самий, тому
   заливається наявний контур, а не підставляється друга іконка: два носії
   одного знака розійшлися б при першій правці шляху. */
@media (prefers-reduced-motion: no-preference) {
  .card__like svg { transition: fill var(--dur-fast) var(--ease); }
}

@media (hover: hover) {
  .card__like:hover svg { fill: currentColor; }
}

.card__like:focus-visible svg,
.card__like[aria-pressed="true"] svg { fill: currentColor; }

/* --------------------------------------------------------------------------
   «Transformer» — іконка 32×32 з підказкою під нею (канон: іконка
   bn-t14, м'яка тінь, зазор 4 від іконки).

   Підказка — НЕ `title`: браузерний тултип не показується з клавіатури й не
   керується стилем. Вона живе в DOM і озвучується як частина назви кнопки.

   ХАЗЯЇВ У ЦЬОГО ЗНАКА ДВА: картка каталогу (`.card__flag`) і плитка галереї
   колекції (`.tile__flag`, блок «ГАЛЕРЕЯ» нижче по файлу). Спільними є вигляд
   і поведінка, різними — місце й розмір, тому в комі стоять обидва, а
   позиціювання кожен несе своє. Другий носій заведений замість
   текстової плашки на плитці.
   -------------------------------------------------------------------------- */
.card__flag { position: relative; display: inline-flex; }

/* Кружок «Transformer» тримає зріст плашки, що стоїть поруч, а не власне
   число: у компактному скіні сітки плашка 24, і кружок 32 поруч із нею
   виглядав більшим за неї на третину. Канон цієї пари на мобільному не малює
   зовсім — «Transformer» є лише на десктопній картці. */
.card__flag-btn,
.tile__flag-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--badge-h, var(--s-32));
  height: var(--badge-h, var(--s-32));
  border-radius: 50%;
  /* Залита ТЕМНИМ зі світлою іконкою — так у базовому кадрі канону
. При наведенні тони міняються місцями й виїжджає підказка
. */
  background: var(--btn-bg);
  color: var(--btn-ink);
}

/* Те саме, що в ♡: знак 18 на десктопі й 14 на мобільному з одного файла. */
.card__flag-btn svg,
.tile__flag-btn svg {
  width: var(--flag-icon, 18px);
  height: var(--flag-icon, 18px);
  vector-effect: non-scaling-stroke;
}

.card__flag-tip,
.tile__flag-tip {
  position: absolute;
  inset-block-start: calc(100% + var(--s-4));
  inset-inline-start: 50%;
  translate: -50% 0;
  display: flex;
  align-items: center;
  height: var(--s-32);
  padding-inline: var(--s-12);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  white-space: nowrap;
  box-shadow: 0 11px 24px rgba(0, 0, 0, .02), 0 44px 44px rgba(0, 0, 0, .02);
  opacity: 0;
  pointer-events: none;
}

@media (prefers-reduced-motion: no-preference) {
  .card__flag-tip,
  .card__flag-btn,
  .tile__flag-tip,
  .tile__flag-btn { transition: opacity var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease); }
}

@media (hover: hover) {
  .card__flag:hover .card__flag-btn,
  .tile__flag:hover .tile__flag-btn { background: var(--surface); color: var(--ink); }
}

.card__flag:focus-within .card__flag-btn,
.tile__flag:focus-within .tile__flag-btn { background: var(--surface); color: var(--ink); }

/* Наведення й фокус розведені: `:hover` живе під можливістю вказівника,
   `:focus-within` — завжди, бо фокус є і без миші. Лінт `lint-hover-media`
   спіймав це правило як єдине наведення поза `(hover: hover)`. */
@media (hover: hover) {
  .card__flag:hover .card__flag-tip,
  .tile__flag:hover .tile__flag-tip { opacity: 1; }
}

.card__flag:focus-within .card__flag-tip,
.tile__flag:focus-within .tile__flag-tip { opacity: 1; }

/* Підпис картки ЦЕНТРОВАНИЙ, а не притиснутий ліворуч, і має власні поля 32
   з кожного боку: 432 − 64 = 368, як у каноні. Зазор «назва → ціна» — 6. */
.card__body {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-6);
  /* Зазор «фото → підпис» і бокові поля підпису — з координат підпису на
     кожному якорі, а не одні на всі:
     Плоскі 16/32 дали б мобільній сітці картку 311 замість 305. */
  padding: var(--card-gap-media, var(--s-16)) var(--card-body-inline, var(--s-32)) 0;
  text-align: center;
  /* Кегль підпису НЕ той самий на всіх якорях. Канон:
     Кегль 16/20 на всіх якорях дав би картку на планшеті 597.5
     замість 590. Дельта 7.5 сиділа під і виглядала як наслідок
     дворядкової назви — доки той waiver не перевірили дослідом: на коротких
     назвах десктопна картка сіла рівно в 674, а планшетна лишилась +7.5.
     Waiver, який прикриває чужий дефект, гірший за відсутній. */
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  /* Підпис забирає ВЕСЬ залишок висоти картки, а ціна притиснута до низу
     `margin-block-start: auto`. Наслідок: у ряду, де хоч одна назва пішла в
     два рядки, усі ціни стоять на одній лінії — по найвищій картці. Без цього
     ціна їде за своєю назвою, і ряд читається як нерівний.

     Працює саме тому, що картки в ряду однакової висоти: і сітка, і стрічка
     слайдера тягнуть їх до найвищої. */
  flex: 1;
}

.card__body > :last-child { margin-block-start: auto; }

@media (min-width: 1024px) {
  .card__body { font-size: var(--fs-body); line-height: var(--lh-body); }
}

/* Назва товару в дві стрічки максимум — стрес-стан із П4: інакше довга назва
   ламає ряд карток по висоті. Третя стрічка обрізається багатокрапкою, повний
   текст лишається в `title` і в доступному імені посилання. */
.card__title {
  font-weight: var(--fw-regular);
  /* Довге слово, яке не вміщається в колонку, інакше вилазить за картку:
     обрізка по двох рядках ховає ЗАЙВІ рядки, а не задовге слово. */
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  overflow: hidden;
}

/* Наведення на картку підкреслює НАЗВУ волосиною — той самий закон, що в
   посилань шапки.  Підкреслення коробки не міняє. */
@media (hover: hover) {
  .card:hover .card__title {
    text-decoration: underline;
    text-decoration-thickness: var(--hairline);
    text-underline-offset: var(--s-4);
  }
}

/* Посилання розтягнуте на всю картку: клікабельна вся площа, але в дереві
   доступності лишається ОДНЕ посилання з назвою товару, а не три однакові. */
.card__link::after { content: ""; position: absolute; inset: 0; }

/* ==========================================================================
   Сітка карток. Проміжок — `--grid-gap`, добраний так, щоб картка була цілою
   на кожному якорі.
   ========================================================================== */
/* `minmax(0, 1fr)`, а не `1fr`: скорочення `1fr` означає `minmax(auto, 1fr)`,
   тобто колонка не має права стати вужчою за min-content свого вмісту. На
   мобільному якорі найдовша назва товару разом із полями підпису дає 210 px
   на колонку 166 — сітка мовчки розсувається і сторінка їде вбік.

   Спіймано стрес-станом: у бібліотеці лежить картка з навмисно найдовшою
   назвою, і саме вона показала виліт 64 px, якого на звичайних назвах не
   видно. */
/* Дві осі — два числа: колонка 11 · 16 · 32, ряд 24 · 24 · 32 (`--grid-gap`
   і `--grid-row-gap`). Збігаються вони лише на десктопі, і саме тому один
   спільний проміжок протримався до планшетного якоря. */
.grid {
  display: grid;
  /* Кількість колонок приходить токеном `--grid-cols`, а не трьома шаблонами
     тут: по цьому ж числу вирівнюється панель каталогу, і два описи однієї
     сітки розійшлися б при першій же правці порога. Пороги значень (935 і
     1420) живуть у `tokens.css` поруч зі `--slide-visible`, бо це той самий
     закон: наступна колонка вмикається там, де картка інакше переросла б
     432 px. */
  grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
  gap: var(--grid-row-gap) var(--grid-gap);
}

/* ==========================================================================
   Розкриття тексту — «Більше» / «Згорнути». Двигун власний, розмітка на
   `<details>`: працює без JS, доступна з клавіатури й правильно озвучується.
   ========================================================================== */
.expand__summary { list-style: none; cursor: pointer; }
.expand__summary::-webkit-details-marker { display: none; }
.expand[open] .expand__label--more,
.expand:not([open]) .expand__label--less { display: none; }
/**/
.expand__icon { rotate: 180deg; transition: rotate var(--dur-fast) var(--ease); }
.expand[open] .expand__icon { rotate: 0deg; }

/* ==========================================================================
   Шапка ЛИПКА. Шість кадрів «сторінка проскролена» в макеті — це стан
   липкої шапки, а не зміна вигляду на місці.

   `sticky`, а не `fixed`: `fixed` виймає шапку з потоку, і перший екран
   довелося б відсовувати доважком, який дублює її висоту третім місцем.

   Три наслідки, названі в момент рішення, бо кожен інакше зʼявився б як
   «раптовий баг» через тиждень:
     — шапка назавжди забирає 56 px мобільного екрана; це закладено в hero,
       який рахує висоту від `100svh` мінус `--header-h`;
     — усі якірні переходи мають `scroll-margin-block-start`, інакше ціль
       стрибка опиняється ПІД шапкою (правило в base.css, не тут);
     — `z-index` мусить бути вищим за накат секцій: секції їдуть на
       `sticky` теж, і без явного порядку шапка ховається під ними.
   ========================================================================== */
.header {
  position: sticky;
  inset-block-start: 0;
  z-index: 20;
  background: var(--surface);
  border-block-end: var(--hairline) solid var(--line);
}

.header__inner {
  display: flex;
  align-items: center;
  /* НАЙМЕНШИЙ ЗАЗОР «ЗНАК → ДІЇ». Вище мобільного якоря він ні на що не
     впливає: вільного місця там 38.67, і його забирає `margin-inline-start:
     auto` групи дій. Нижче — саме він не дає знаку злипнутись із лупою.
     Десктопна сітка (≥1140) має власний `column-gap: 32` і цього рядка не
     бачить. */
  column-gap: var(--s-8);
  min-height: var(--header-h);
}

/* Вордмарк — картинка з канону, а не текст: у макеті це намальована лігатура
   («JULIYA» з монограмою всередині), і набірним шрифтом вона не відтворюється.
   Висота 30 на десктопі, 24 до нього — та сама пропорція, тому ширина auto. */
/* ЗНАК ЗМЕНШУЄТЬСЯ ПРОПОРЦІЙНО — ЄДИНИЙ У ШАПЦІ, ХТО МОЖЕ. Нижче мобільного
   якоря рядок шапки не вміщається: на 320 контейнер дає 288, а знак 104.33 і
   група дій 200 просять 304.33. Хто скільки віддає — не смак: розміри знаків
   дій це 24 з канону, і зменшити їх не можна (`check-icon-scale`), а зазори
   всередині групи вже стиснуті до 4. Лишається вордмарк, і він тягнеться
   найкраще: це картинка з власною пропорцією.

   ⚠️ Стиск можна дозволити лише тому, що обидві осі знака — СТЕЛІ (`max-*`),
   а не розміри: при жорсткій висоті стиск давав би не зменшення, а
   сплющування. Знак зменшується цілим — на 320 це 80×18.4 замість 104.33×24,
   пропорція та сама до третього знака.

   Вище якоря нічого не змінюється: вільного місця в рядку 38.67, стиск не
   вмикається взагалі, і на 375 знак лишається канонічним 104.33×24. */
.header__logo { display: inline-flex; flex: 0 1 auto; min-inline-size: 0; }
/* `max-width: none` СКАСОВУЄ загальний скид `img { max-width: 100% }` — і це
   не вільність, а лік від деформації. У смузі 1140…1279 логотип стоїть у
   трьох колонках сітки шапки, і на її нижньому краї три колонки вужчі за
   сам знак: висота лишається 28, а скид зрізав би ширину до 106.8 замість
   121.7, тобто сплющив би знак на 12 %. Ловить таке ознака «пропорція коробки
   проти власної пропорції файла» в `.qa/check-breakpoints.js`. Зіткнення
   немає — праворуч перемикач мов починається на
   169.7 при правому краї знака 152.7. */
.header__logo img {
  inline-size: auto;
  block-size: auto;
  max-inline-size: 100%;
  max-block-size: var(--logo-h);
}

/* Перемикач мов і «Онлайн магазин» ховаються тільки на мобільному якорі. На
   планшетному вони Є — дає «Eng/Укр» на x=221 і «Онлайн магазин»
   на x=336, обидва видимі. Попередня редакція ховала їх аж до 1920, бо
   планшетну шапку взагалі не перезнімали: її вважали зменшеною мобільною. */
.header__lang,
.header__shop { display: none; }

.header__lang a { color: var(--ink-subtle); }
.header__lang a[aria-current] { color: var(--ink); }

/* Права група — одна на всіх якорях; шви задані тут одним місцем.
   «Кабінетом». */
.header__tools {
  display: flex;
  align-items: center;
  /* ЗАЗОР СТИСКАЄТЬСЯ НИЖЧЕ МОБІЛЬНОГО ЯКОРЯ. На 375 це канонічні 12, але
     нижче — 320 у DevTools і старі телефони — логотип 104 і група знаків 216
     разом із полями дають 352 при екрані 320, тобто сторінка їде вбік на 16.
     Зменшувати нема чого іншого: логотип і знаки — фіксовані розміри канону,
     а зазор між ними числом канону не є. `max` не дає йому впасти нижче 4.
     Порогом це не робиться: 320 — не якір проєкту, і заводити під нього
     медіа-запит означало б четверту композицію. */
  gap: clamp(4px, calc(var(--s-12) - (375px - 100vw) / 4), var(--s-12));
  margin-inline-start: auto;
}

/* До десктопного якоря навігація живе за бургером: інакше рядок меню просто
   їде за екран — на мобільному якорі він виходив би за вʼюпорт на 167 px. */
.header__nav { display: none; }

/* ⚠️ Розгорнутої панелі меню в каноні немає, тож скін нижче
   інтерполяція: біле полотно під шапкою, пункти кеглем врізки, більше нічого.
   Поведінка при цьому справжня, а не заглушкова (лок фону, Esc, пастка
   фокуса), тому заміна скіна не зачепить жодного хука двигуна. */
.header__nav.is-open {
  position: fixed;
  inset: var(--header-h) 0 0;
  display: block;
  z-index: 10;
  padding: var(--s-32) var(--gutter);
  background: var(--surface);
  overflow-y: auto;
}

.header__nav.is-open .header__menu { flex-direction: column; gap: var(--s-24); }

/* Перемикач мов і «Онлайн магазин» у панелі. Кегль і тон — ВЛАСНІ, з їхніх
   вузлів канону: у рядку шапки вони стоять кеглем тіла, і панель цього не
   міняє — інакше довелось би вигадувати за дизайнера ще й розмір. */
.header__nav.is-open .header__extra {
  display: flex;
  flex-direction: column;
  gap: var(--s-24);
  margin-block-start: var(--s-32);
}

.header__nav.is-open .header__lang {
  display: flex;
  gap: var(--s-12);
  margin-inline-start: 0;
}

.header__nav.is-open .header__shop { display: block; margin-inline-start: 0; }
.header__nav.is-open .header__menu a { font-size: var(--fs-lead); line-height: var(--lh-lead); }

.header__menu { display: flex; gap: var(--s-24); }
.header__menu a { font-size: var(--fs-body); line-height: var(--lh-body); }

/* Активний пункт позначає генератор (`build-pages.py`) — і `aria-current`,
   і клас одночасно. Колір без `aria-current` був би станом, який бачить лише
   зряча людина. */
/* Уся шапка йде накресленням Medium — це стиль канону `btn-16` (16/20,
   Inter Tight Medium), а не `Txt-16` Regular: із типовим Regular шапка
   виглядала б легшою за макет. */
.header a,
.header button { font-weight: var(--fw-regular); }

/* ==========================================================================
   НАВЕДЕННЯ В ШАПЦІ Й ПІДВАЛІ

   Канон станів наведення не малює взагалі — це статичні кадри, тож джерела в
   макеті тут немає, і воно назване прямо. Прийомів рівно ДВА, і обидва вже
   живуть у бібліотеці; третього не заводиться:

     ТЕКСТ — підкреслення, що виїжджає (`.link`, `.tab`, `.crumbs a`,
              `.footer__menu a` — усі вже так). Той самий градієнт, та сама
              тривалість.
     ЗНАЧОК — приглушення до 0.6. Чорнило #201F1F темнішим не буває, а кола
              під значками шапки немає, щоб інвертувати його, як у
              `.btn-round`; лишається прозорість. Одне число на всі значки —
              інакше «майже однакових» станів стане дванадцять, як у донора.

   Фокус із клавіатури показує ТЕ САМЕ: людина, яка ходить табом, мусить
   бачити не менше за людину з мишею. */
.header__lang a,
.header__shop,
.header__menu a,
.header__account-text {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .header__lang a:hover,
  .header__shop:hover,
  .header__menu a:hover,
  .header__account:hover .header__account-text { background-size: 100% var(--hairline); }
}

.header__lang a:focus-visible,
.header__shop:focus-visible,
.header__menu a:focus-visible,
.header__account:focus-visible .header__account-text { background-size: 100% var(--hairline); }

/* Лінія лягає під СЛОВО, а не під комірку сітки. «Онлайн магазин» — блочне
   посилання в рядку шапки, і градієнт малювався на всю його коробку: риска
   тяглася далеко за текст. Ширина по вмісту прибирає це в один рядок і нічого
   не зсуває — координата задана відступом ліворуч. */
.header__shop { width: fit-content; }

.header__logo,
.header__action,
.header__burger { transition: opacity var(--dur-fast) var(--ease); }

@media (hover: hover) {
  .header__logo:hover,
  .header__action:hover,
  .header__burger:hover { opacity: .6; }
}

/* БІЛЕ КОЛО ПІД КУРСОРОМ СТАЄ ЧОРНИМ, знак — білим (замовник
   «кнопки, де в нас білий чи обведення, при наведенні щоб були чорними»,
). Знак підвала — файл SVG із заданими кольорами (біле коло, знак
   чорнилом), і `currentColor` йому не дістати, тому інверсія фільтром:
   коло → чорне, знак → світлий. Той самий закон, що в `.btn-round:hover`. */
.footer__social img { transition: filter var(--dur-fast) var(--ease); }

@media (hover: hover) {
  .footer__social a:hover img { filter: invert(1); }
}

/* Клямка згортки підвала — цілий рядок 64, і назва в ньому та сама коробка:
   `summary` несе ОБИДВА класи (`fold__summary footer__title`), тож селектор
   мусить брати саму клямку, а не шукати назву всередині неї. У спокої назва
   приглушена до `black/400`, під курсором стає чорнилом. */
@media (hover: hover) {
  .footer__fold .fold__summary:hover { color: var(--ink); }
}

.footer__title { transition: color var(--dur-fast) var(--ease); }

/* Пошук стоїть від пари «обране / кошик» ДАЛІ, ніж вони одне від одного, і
   різниця на кожному якорі своя — з координат канону:
   Тому `gap` несе внутрішній шов пари, а пошук має власний доважок. Один
   `gap` на все дав би або злиплу пару, або відірваний пошук. */
.header__actions { display: flex; align-items: center; gap: var(--s-6); }

/* Пошук стоїть у шапці на всіх ширинах: артборди накладки на 820 і 375
   «пошук зникає нижче 1140» закрита ними. */
/* ⚠️ `display: flex` НА ПУНКТІ — не оформлення, а лік від зсуву на 2 px.
   Усередині `<li>` посилання лишалось інлайновим, тобто сідало на БАЗОВУ
   ЛІНІЮ рядка: під ним лишалось місце під нижні виносні, пункт виходив 28
   при іконці 24, і сама іконка спиралась у верх пункту. Виходило, що знак
   кабінету (він прямий син флекса й центрується чесно) стоїть на 16, а серце
   й кошик — на 14. Канон ставить усі чотири на 16
   на y=13). Пункт-флекс прибирає рядок цілком — і лічильник заразом стає на
   18, як `Group 569` у каноні. */
.header__actions > li { display: flex; }
.header__actions > li:first-child { margin-inline-end: var(--s-6); }

/* Іконка й лічильник стоять поруч із зазором 2 — так у каноні на обох якорях.
   Це НЕ бейдж поверх іконки: попередня редакція вішала кружечок у кут, чого в
   макеті немає жодного разу. */
.header__action {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.header__action svg { flex: none; width: var(--icon); height: var(--icon); }

/*   розтягнути його до квадрата означало б змалювати інший знак.

   Слот лишається 24: зі слотом за розміром самого знака вся права група
   стала б на 6 px коротшою.

   ⚠️ ПОЛІВ ТУТ БУТИ НЕ МОЖЕ. Шлях намальований у своїх 18.9×16.5 і вже
   відцентрований усередині viewBox 24 зсувом `translate(2.3 3.147)` — поле в
   знак закладене САМИМ шляхом. Додані зверху падінги стиснули б viewBox 24 у
   коробку 18×16, і глиф упав би до 14×11. Жоден чекер такого не бачить:
   коробка чесні 24, обведення чесні 1.4 — стиснутий тільки ВМІСТ. */
.header__action--fav svg {
  width: var(--icon);
  height: var(--icon);
}

/* Лічильник видно ЗАВЖДИ — нуль у каноні намальований, а не схований.
   Ширина 18 і центрування — щоб «0» і «12» не совали іконки. Тон нуля в
   макеті `black/400` (2.62:1); тут крок `black/600` — 4.94:1, бо це
   інформативний текст, а не декор. */
.header__count {
  width: 18px;
  text-align: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.header__count.is-zero { color: var(--ink-subtle); }

/* «Кабінет»: на десктопі слово, до нього — іконка користувача.
   Текст лишається в дереві доступності на всіх якорях: він і є доступним
   іменем посилання. */
.header__account { display: inline-flex; align-items: center; }
.header__account-icon { width: var(--icon); height: var(--icon); }

.header__account-text {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* Бургер існує лише там, де в макеті є — до десктопного якоря.

   Коробка 38×24, знак усередині 28×7.4. Це не «поле про запас»: у каноні
   край стоїть на гутері. Із коробкою за самим знаком 28 уся шапка вирівнялась
   би на 5 px не туди, і виправляти це довелось би відступом-компенсацією —
   другим числом на ту саму роль. Заразом 38×24 — це ціль дотику, а 28×7.4
   висотою в сім пікселів нею не є. */
.header__burger {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: var(--icon);
}

.header__burger svg { width: 28px; height: 7.4px; }

/* --------------------------------------------------------------------------
   Планшетний якір. Заміряні позиції (контейнер 772, гутер 24)
     логотип 24 … 145.7 (висота 28)
     Eng / Укр 221 … 288 (шов усередині 12)
     Онлайн магазин 336 … 455
     пошук 514 … 538
     обране+кошик 586 … 686
     Кабінет 710 … 734
     бургер 758 … 796 (правий край рівно на гутері)

   Шапка тут НЕ на сітці, і це факт, а не спрощення: жодна регулярна сітка
   контейнера 772 не дає одночасно 221 і 336 — перевірено на 6, 8, 12 і 24
   колонках із кроками 16/24/32, найближча схибила на 13.5 px. Тому позиції
   ліворуч задані швами, як їх поставив дизайнер: 75 після логотипа (єдине
   нерегулярне число шапки) і 48 після перемикача мов.

   Права група вирівняна по гутеру й збирається зі своїх власних чисел, тому
   пошук стає на 514 сам, без жодної координати.
   -------------------------------------------------------------------------- */
@media (min-width: 768px) {
  .header__lang { display: flex; gap: var(--s-12); margin-inline-start: 75px; }
  /* ОДИН РЯДОК, а не два. Від знак пошуку стоїть і тут, і на 768…790
     рядку бракує ~30: «Онлайн магазин» ламався у два рядки (замовник
: «Зламалось»). Слабину бере логотип — він і так тече
     (`max-inline-size: 100%`), а шви 75 і 48 — числа канону 820. */
  .header__shop { display: block; margin-inline-start: var(--s-48); white-space: nowrap; }
  .header__tools { gap: var(--s-24); }
  .header__actions { gap: var(--s-12); }
  /* 12 + 36 = 48 між пошуком і парою. Записано як різниця двох токенів, а не
     числом 36: 36 не існує в ряду відступів, існують 48 і 12. */
  .header__actions > li:first-child { margin-inline-end: calc(var(--s-48) - var(--s-12)); }
}

/* --------------------------------------------------------------------------
   Десктопний якір. Шапка сидить на сітці з 24 колонок того самого контейнера
   з проміжком 32 — це не здогад, а найпростіше правило, яке відтворює ВСІ
   заміряні позиції канону:
     колонка 1 → 48 логотип (макет 48)
     колонка 4 → 280 Eng / Укр (макет 285, розбіжність 5)
     колонка 7 → 512 Онлайн магазин (макет 512)
     колонка 13 → 976 меню (макет 976)
     колонка 21 → 1595 права група (макет 1596)
   `minmax(0, 1fr)`, а не `1fr`: інакше вміст, ширший за колонку (логотип 130
   при колонці 45), розсунув би трек і зсунув усі наступні позиції.
   -------------------------------------------------------------------------- */
/* ПОРІГ ІДЕНТИЧНОСТІ — 1140, і він ВИМІРЯНИЙ, а не круглий. Десктопна шапка
   стоїть на сітці 24 колонок; звужуючи вікно, меню
   наїжджає на групу іконок: на 1130 коробки `header__nav` і `header__tools`
   перетинаються, на 1140 — ні. Це і є дно композиції.

   Поріг СПІЛЬНИЙ із підвалом і парою, хоч власне дно в них нижче (підвал
   ламає рядок посилання на 1110, пара не стикається взагалі): блоки-обличчя
   сторінки перемикають якірну ідентичність разом, інакше на сусідніх ширинах
   виходить планшетна шапка з десктопним підвалом. Береться НАЙСУВОРІШЕ з
   трьох дон. ⚠️ Ставити тут 1920 не можна: на екрані 1920 вікно віддає 1905,
   тобто десктопної шапки не побачив би ніхто. */
@media (min-width: 1024px) {
  .header__inner {
    display: grid;
    grid-template-columns: repeat(24, minmax(0, 1fr));
    column-gap: var(--s-32);
    align-items: center;
  }

  /* `max-inline-size: none` СКАСОВУЄ стелю ширини — і це не вільність, а лік
     від деформації, той самий. У смузі 1140…1279 знак стоїть у трьох
     колонках сітки, і на її нижньому краї три колонки вужчі за сам знак:
     зі стелею 100 % він зменшився б до 106.8 при висоті 28, тобто вийшов би
     дрібнішим за канон там, де місця вистачає. Зіткнення немає — перемикач
     мов починається на 169.7 при правому краї знака 152.7. */
  .header__logo { grid-column: 1 / span 3; }
  .header__logo img { max-inline-size: none; }
  /* Шви планшетного якоря скасовуються: тут позицію дає колонка сітки, і
     доважок зсунув би її праворуч на ті самі 75. */
  /* Шов планшетного якоря (75) скасовується — позицію дає колонка
     сітки. Замість нуля тут рампа `--hdr-lang-nudge`: на самій стіні
     режиму вордмарк підходить до «Eng» на 1.6 px, і доважок 8 відсуває
     мову рівно на найменший токен ряду. Від 1140 рампа дає 0, тобто
     прийнятий якір 1920 не зрушений. */
  .header__lang { grid-column: 4; display: flex; gap: var(--s-12);
                  margin-inline-start: var(--hdr-lang-nudge); }
  /* Зазор пунктів меню тече від стіни до канону: 16 на 1024, 24 від
     1140. Без цього «Про нас» переноситься у два рядки на 1024…1069 —
     коробка меню 217.8 при потребі 229. */
  .header__menu { gap: var(--hdr-menu-gap); }
  .header__shop { grid-column: 7 / span 4; display: block; margin-inline-start: 0; }
  .header__nav  { grid-column: 13 / span 6; display: block; }

  /* ⚠️ ЩО САМЕ НАЇЖДЖАЄ. Меню зі своєї колонки НЕ вилазить: на 1024 його
     коробка 218 і вміст 218, тобто рівно в межах шести колонок сітки. Наїзд
     дає ПРАВА група: її вміст 276 при чотирьох колонках, і з
     `justify-self: end` зайве йде ліворуч — на 1024 на 26 px у колонку меню.
     Тому тече права група, а не меню; кегль пунктів лишається канонічним.

     Проміжок перед «Кабінетом» складається з двох однакових чисел (зазор
     групи плюс поле слова), тож рампа одна на обидва: 16 на стіні режиму →
     канонічні 32 від 1140. На 1024 це віддає 32 px і лишає 6 px просвіту.
     1140 тут — вісь рампи всередині смуги, а не поріг: жодного `@media` з
     цим числом більше немає. */
  .header__tools {
    grid-column: 21 / -1;
    justify-self: end;
    gap: var(--hdr-tools-gap);
    margin-inline-start: 0;
  }

  .header__actions { gap: var(--s-12); }
  .header__actions > li:first-child { margin-inline-end: var(--s-20); }   /* 12 + 20 = 32 */
  .header__account { margin-inline-start: var(--hdr-tools-gap); }
  .header__account-icon { display: none; }

  .header__account-text {
    position: static;
    width: auto; height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
  }

  .header__burger { display: none; }
}

/* ==========================================================================
   Футер
   ========================================================================== */
/*   тонований блок 560 з меню й вордмарком і білий рядок 68 із платіжними
   знаками, копірайтом і підписом розробника. */

/* Висота тонованого блоку задана на КОЖНОМУ якорі, бо вона не виводиться зі
   вмісту: вміст займає 296, решту тримає вордмарк, який лежить абсолютно.
   Числа зняті з вузлів канону кожного якоря окремо:
   Без цього блок стискався до висоти вмісту, а підвал на планшеті й
   мобільному виходив на 82 і 208 px вищим за макет. */
/* Нижній відступ РЕЗЕРВУЄ смугу, яку займає вордмарк. Він лежить абсолютно
   й притиснутий до низу блоку, тобто у потоці його немає — і поки блок тримала
   тільки `min-height`, розкрита згортка виростала просто крізь нього: на 375
   соцмережі 6817–6873 проти вордмарка 6791–6926, на 820 — 7970–8026 проти
   7897–8173. У закритому стані перекриття немає, тому `canon-geometry` цього
   не бачив: він міряє те, що намальоване в макеті, а розкритого стану макет
   не малює.

   Числа — видима висота вордмарка на кожному якорі, тобто його висота мінус
   те, що звисає нижче краю: M 136 − 30 = 108, T 276 − 78 = 200 (канон малює
   смугу 338…538), D 438 − 112 = 326. У ЗАКРИТОМУ стані вони нічого не
   міняють: природна висота лишається меншою за `min-height` на всіх трьох
   якорях (404 < 436, 496 < 538, 538 < 560). */
/* У РЕЗЕРВ ВХОДИТЬ І ПРОМІЖОК над вордмарком, не лише сам вордмарк. Якби
   резерв дорівнював самій смузі, проміжок існував би як ЗАЛИШОК від
   `min-height` і жив би рівно доти, доки вміст не доросте до неї: досить
   розкрити згортку, і соцмережі опиняться впритул до слова.

   Проміжок узятий із канону на кожному якорі — 32 (M), 42 (T), 22 (D), — і
   разом із висотою смуги дає рівно ту `min-height`, яку канон малює:
   296 + 140 = 436, 296 + 242 = 538, 212 + 348 = 560. Три збіги поспіль — це
   не підгонка, а те саме число, записане в макеті двічі. */
/* НИЖНЄ ПОЛЕ — ЧАСТКА ШИРИНИ, А НЕ ПІКСЕЛІ. Знак росте разом з екраном
   (ширина задана відсотком полотна), тож константа якоря під ним лишала б
   проміжок між знаком і соцмережами танути до верху кожної смуги — аж до
   мінуса, тобто до наїзду знака на іконки.

   Частка виводиться з тих самих трьох чисел, якими заданий знак, а не
   підбирається: ширина знака (`--wm-w`) × пропорція файла (0.1997) ×
   видима частка (1 − `--wm-drop`). Мобільна смуга: 1.816 × 0.1997 × 0.7826
   = 0.2838. Планшетна: 1.6854 × 0.1997 × 0.7179 = 0.2417. Доданок — той
   самий проміжок, який дає канон на якорі: 375 × 0.2838 + 33.6 = 140.0,
   820 × 0.2417 + 43.8 = 242.0.

   Відсоток тут рахується від ширини підвала, і це збігається з шириною
   коробки знака: її кап (`--container` + два гутери) вмикається лише за
   1920, тобто вище обох смуг. Десктопна смуга не чіпається — вона в
   заморожених ширинах. */
.footer__main {
  position: relative;
  overflow: hidden;             /* вордмарк свідомо вилазить за краї */
  padding-block: var(--s-24) calc(28.38% + 33.6px);
  min-height: 436px;
  background: var(--footer-surface);
}

@media (min-width: 768px) {
  .footer__main { padding-block: var(--s-24) calc(24.17% + 43.8px); min-height: 538px; }
}

/* Поле, у якому лежить вордмарк, — частина ДЕСКТОПНОЇ композиції, тож іде на
   тому самому порозі 1140, що й колонки підвала. На 1920 воно спізнилось би:
   у смузі 1140…1919 поле лишалось би планшетним при вордмарку, що росте з
   шириною, і знак наїхав би на пункти меню. */
@media (min-width: 1024px) {
  /* ПОЛЕ ПІД ЗНАКОМ ПРОПОРЦІЙНЕ, А НЕ ПЛОСКЕ. Вордмарк масштабується з
     шириною (114.15 % коробки, тобто 2192 на 1920 і 1169 на 1024, висота —
     та сама пропорція 438/2194), а поле під нього стояло плоскими 348 і
     плоскою стелею 560. Через це відступ між колонками й словом ріс, щойно
     екран вужчав: канонні 22 на 1920 перетворювались на 174 на 1024 і 155 на
     1180 — саме ту дірку показав замовник.

     десктопній смузі, бо це поле між двома об'єктами, а не частка екрана.

     Звідси частка: видима частина знака над низом підвала — це
     (1 − `--wm-drop`) × висота = 0.7443 × 0.19964 × 1.1415 × ширина, тобто
     16.9614 % ширини; плюс самі 22. `min(…, 348px)` — стеля вище 1920: там
     коробка знака вже впирається в `--container + 2×48`, знак рости
     перестає, а відсоток ріс би далі й повертав ту саму дірку (на 2560 —
     +108). Стеля тримає 1920 недоторканим: 0.169614 × 1920 + 22 = 347.7 при
     каноні 348, тобто відступ 21.7 при допуску 1.

     `min-height` тут знімається: висота підвала тепер виходить із власного
     вмісту (48 + ряд + поле) і на 1920 дає ті самі 560. Плоска стеля 560
     повернула б дірку на 1024 сама, у обхід нового поля. */
  .footer__main {
    padding-block: var(--s-48) min(calc(16.9614% + 22px), 348px);
    min-height: 0;
  }
}

/* На вузьких якорях колонки йдуть суцільним стосом БЕЗ проміжку: рядки
   згортки в каноні стикаються краями, і зазор між ними зробив би з трьох
   рядків 64 висоту 192 + два проміжки. */
.footer__inner { display: grid; gap: 0; }

/* Заголовок колонки — він же клямка згортки. Висота 64, тонка лінія знизу,
   шеврон 20×20 праворуч, текст 14/16 у тоні black/400 — усі чотири взяті
   знімка (інваріант 15).

   `list-style: none` + вебкітний маркер: без них браузер малює власний
   трикутник, якого в макеті немає. */
.footer__title { color: var(--ink-subtle); }   /* макет дає black/400 — 2.62:1 */

/* Остання колонка лінії знизу НЕ має: у каноні вузол — єдиний
   із трьох без `border-b`. Селектор по `nth-child`, а не `nth-of-type`:
   на цій же розкладці вже одного разу спрацювала пастка різнотипних
   колонок. */
.footer__col:first-child .fold__summary { border-block-start: 0; }

/* Розгорнутого стану канон не малює — усі три рядки в макеті закриті. Ритм
   всередині взято з десктопної колонки, де він є; це єдине місце підвала,
   яке не має вузла-джерела, і воно записане рядком. */
.footer__fold[open] .footer__menu,
.footer__fold[open] .footer__hours { padding-block: var(--s-16) var(--s-24); }

.footer__menu { display: flex; flex-direction: column; gap: var(--s-8); }
.footer__menu a,
.footer__hours { font-weight: var(--fw-medium); }

/* Кегль пунктів у ЗГОРТЦІ — 14/16, як у її ж заголовка. На десктопі колонка
   намальована, і там пункт більший за назву колонки; у згортці канону немає
   зовсім, і успадковані 16/20 давали пункт, БІЛЬШИЙ за клямку, яка
   його відкриває. Підпорядкований елемент, набраний крупніше за свій
   заголовок, читається як помилка верстки — нею він і був.

   Правило звужене до самої згортки: на десктопі `<details>` розкритий і
   виглядає колонкою, тому там кегль вертається до канонічного. */
.footer__fold .footer__menu a,
.footer__fold .footer__hours { font-size: var(--fs-ui); line-height: var(--lh-ui); }

@media (min-width: 1024px) {
  .footer__fold .footer__menu a,
  .footer__fold .footer__hours { font-size: var(--fs-body); line-height: var(--lh-body); }
}

/* ⚠️ `inline-block` — НЕ оформлення, а ціль дотику. Довгий пункт («Політика
   використання файлів cookie») у смузі, де колонка вужча за рядок, лягає у
   два рядки; в інлайнового посилання коробкою стають ДВА окремі рядкові
   фрагменти, і центр пункту падає в проміжок між ними — там уже не
   посилання, а `li`. Прямокутна коробка накриває центр, і `clickable`
   перестає бачити перекритий контрол. Перенос при цьому лишається
   переносом: він потік, а не поріг. */
.footer__menu a {
  display: inline-block;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .footer__menu a:hover { background-size: 100% var(--hairline); }
}

.footer__menu a:focus-visible { background-size: 100% var(--hairline); }

/*   всередині самих SVG, тому тут лише розкладка. */
/* На вузьких якорях ряд соцмереж стоїть ПО ЦЕНТРУ і на 24 нижче згортки
   (: 296 завширшки, ліворуч і праворуч по 39.5 при полотні 375
   правого краю — це правило нижче, у блоці 1920. */
.footer__social {
  display: flex;
  gap: 4px;
  justify-self: center;
  margin-block-start: var(--s-24);
}
/* ЗНАКИ НЕ СТИСКАЮТЬСЯ. Типове `flex-shrink: 1` дозволяє ряду віддавати
   ширину сусідам, і коло стає овалом — 45.9×56 на 1140 і 54.4×56 на 320 при
   56×56 усюди інде. `flex: none` знімає стиск, а не маскує його розміром. */
.footer__social li,
.footer__social a { flex: none; }
.footer__social img { width: var(--s-56); height: var(--s-56); }

/* Вордмарк. У каноні 2194×438 при полотні 1922 — тобто ширший за екран і
   зсунутий праворуч, через що видно «Juliya K». Обидва зсуви записані
   частками полотна, а не пікселями: інакше на іншій ширині обрізка поїхала б
   не так, як задумано. */
/* Коробка вордмарка ЗАКРІПЛЕНА на ширині контейнера, а не полотна: без неї на
   екрані ширшому за десктопний якір вордмарк ріс би разом із вьюпортом і
   наїжджав на пункти меню. Відсотки нижче рахуються від тих самих 1920, що й
   у макеті. Висота 0: коробка нічого не займає, вона лише система
   координат. */
.footer__mark {
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  width: 100%;
  /* КАНОНІЧНИЙ ГУТЕР, А НЕ ПОТОЧНИЙ. `--gutter` вище 1920 росте разом із
     порожнечею, і ця стеля перетворювалась на «вся ширина вікна»:
     вордмарк на 2560 виходив 2922 px і наїжджав на колонки підвала. Тут
     потрібне полотно макета — 1824 + два канонічні поля по 48. */
  max-width: calc(var(--container) + var(--s-48) * 2);
  height: 0;
  margin-inline: auto;
  pointer-events: none;
}

/* Три якорі — три різні обрізки, і кожна знята з ВЛАСНОГО вузла канону.
   Спокуса вивести їх одна з одної була б помилкою: дизайнер обрізає слово
   по-різному, і на мобільному видно «Juli», а не зменшену десктопну картину.


   Частки полотна, а не пікселі: інакше на проміжній ширині обрізка поїхала б
   не так, як задумано. */
.footer__wordmark {
  position: absolute;
  inset-block-end: 0;
  inset-inline-start: var(--wm-left);
  width: var(--wm-w);
  max-width: none;
  translate: 0 var(--wm-drop);
  pointer-events: none;
  --wm-left: -8.27%;
  --wm-w: 181.6%;
  --wm-drop: 21.74%;
}

@media (min-width: 768px) {
  .footer__wordmark { --wm-left: -8.41%; --wm-w: 168.54%; --wm-drop: 28.21%; }
}

/* Кадр вордмарка — теж десктопна композиція: на 1140 він мусить стати
   десктопним разом із полем, яке його тримає. Інакше в тій самій смузі
   планшетний кадр (168.54 % полотна) лягав би в десктопне поле. */
@media (min-width: 1024px) {
  .footer__wordmark { --wm-left: 18.89%; --wm-w: 114.15%; --wm-drop: 25.57%; }
}

/* Нижній рядок. На мобільному він у ДВА рядки, розділені тонкою лінією
, на планшетному й десктопному — в один.

   Висота задана `min-height` на кожному якорі, бо вивести її з відступів не
   можна: у макеті частини рядка МІЖ СОБОЮ не вирівняні (на десктопі смуга
   оплати займає 579…600, а копірайт 584…604 — різні верхи й різні низи).
   Відступ підібраний так, щоб природна висота була на 1 менша за канонічну, а
   решту добирав `min-height`: тоді довгий текст, який колись перенесеться,
   рядок розсуне, а не вилізе за нього.*/
.footer__bar {
  min-height: 97px;
  padding-block: var(--s-16);
}

@media (min-width: 768px) {
  .footer__bar { min-height: 49px; padding-block: 15px; }
}

@media (min-width: 1024px) {
  .footer__bar { min-height: 68px; padding-block: 23px; }
}

/* Кегль нижнього рядка на вузьких якорях — bn-t14 (14/16), на десктопі
   Txt-16 (16/20). Взято з висот текстових вузлів: 16 на T і M, 20 на D. */
.footer__bar-inner {
  display: grid;
  grid-template-columns: auto auto;
  align-items: center;
  column-gap: var(--s-16);
  row-gap: 15px;                /**/
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* Рядки й колонки розставлені ЯВНО. Без цього автопотік клав копірайт у
   другий рядок, а «Розробка» — у третій: копірайт іде в розмітці другим і
   займає обидві колонки, тож авторові вже не лишалось місця поруч зі смугою
   оплати. Спіймано заміром: `author` стояв на 536 замість 453. */
.footer__pay { grid-column: 1; grid-row: 1; width: 115px; height: 18px; }
.footer__author { grid-column: 2; grid-row: 1; justify-self: end; white-space: nowrap; }
.footer__author-label { color: var(--ink-subtle); }

.footer__copy {
  grid-column: 1 / -1;
  grid-row: 2;
  width: 100%;
  padding-block-start: 15px;    /* 15 + лінія 1 = 16 від лінії до тексту */
  border-block-start: var(--hairline) solid var(--line);
  text-align: center;
}

/* З планшетного якоря всі три частини стають в один рядок і лінія зникає:
   (`min-width`), а не проти нього (`max-width: 819px`): інакше в реєстрі
   порогів зʼявляється 819 — поріг-сирота, і гейт падає. */
@media (min-width: 768px) {
  .footer__bar-inner { grid-template-columns: 1fr auto 1fr; row-gap: 0; }
  .footer__copy {
    grid-column: 2;
    grid-row: 1;
    width: auto;
    padding-block-start: 0;
    border-block-start: 0;
  }
  .footer__author { grid-column: 3; }
}

@media (min-width: 1024px) {
  .footer__bar-inner { font-size: var(--fs-body); line-height: var(--lh-body); }
  .footer__pay { width: 136px; height: 21px; }
}

/* Той самий виміряний поріг ідентичності, що й у шапки (1140). Власне дно
   підвала нижче — три колонки тримаються до 1120, а на 1110 найдовше
   посилання («Політика використання файлів cookie») ламається на два рядки, —
   але блоки-обличчя перемикаються разом, тож бере найсуворіше з трьох.
   Разом із цим рядком іде JS-двійник `initFooterFold` (`matchMedia`,
   `js/main.js`): порогів двоє, число одне. */
@media (min-width: 1024px) {
  /* ЧОТИРИ ДОРІЖКИ, А НЕ ДВАДЦЯТЬ ЧОТИРИ. Блоків підвала рівно чотири, і на
     1920 вони стоять по 432 з проміжком 32 — тобто рівними чвертями
     контейнера. Сітка з 24 колонок давала те саме число ОКРУЖНИМ шляхом
     (6 колонок по 45.33 плюс п'ять проміжків), і саме тому нижче 1920
     ламалась: доріжка ряду соцмереж стискалась разом із контейнером — 432 на
     1920, 218 на 1024, — а вміст ряду стиснутись не вміє (п'ять кіл 56 із
     контейнер на +78 при 1024 і +41 при 1180, а `overflow: hidden` вордмарка
     це зрізав, тож підвал виглядав як блок БЕЗ правого поля при шапці з
     полем — розбіжність, яку побачив замовник.

     `minmax(max-content, 1fr)` каже те, що є насправді: остання доріжка
     ділить контейнер нарівні з рештою, але НЕ вужча за свій вміст. Числа
     296 тут немає навмисно — його дає сам ряд, тож зміна набору знаків не
     лишає в CSS протухлої константи.

     ⚠️ Порога це не додає ЖОДНОГО: обидві поведінки — одна формула, і на
     ширині, де чверть контейнера дорівнює 296 (замір: 1351), розкладка
     переходить сама, без стрибка. Заміри 1351…1920 збігаються зі старими до
     піксела, 1920 відтворює канон точно (колонки 48 / 512 / 976, ряд
     1576…1872). Ціна лише на 1024: ряд підвала 164 → 184, бо найдовше
     посилання переноситься на другий рядок — тобто те саме, що вже описано
     власним дном підвала (1110) абзацом вище. */
  .footer__inner {
    grid-template-columns: repeat(3, minmax(0, 1fr)) minmax(max-content, 1fr);
    column-gap: var(--s-32);
  }
  /* `nth-child`, а НЕ `nth-of-type`: другий рахує в межах свого тега, а
     колонки різнотипні — дві `nav` і один `div`. Третя колонка через це не
     потрапляла в правило взагалі; спіймано заміром, не оком. */
  /* На десктопі заголовок — просто заголовок: ні висоти рядка, ні лінії,
     ні шеврона. Згортка тут не існує, і двигун тримає `open`. */
  .footer__fold .fold__summary {
    display: block;
    height: auto;
    /* Лінія стоїть НАД заголовком, тому й знімається тут та сама сторона:
       `border-block-end` лишив би десктопному підвалу лінію, якої в каноні
       немає. */
    border-block-start: 0;
    margin-block-end: var(--s-16);
    cursor: auto;
  }
  /* НАВЕДЕННЯ НА ЗАГОЛОВОК ЗНЯТЕ: на десктопі це не клямка, а звичайний
     заголовок — згортки тут немає, курсор звичайний, і підсвічувати нічого.
     Правило стоїть нижче за те, що вмикає підсвітку, тож на рівній вазі
     перемагає воно. */
  .footer__fold .fold__icon { display: none; }
  .footer__fold[open] .footer__menu,
  .footer__fold[open] .footer__hours { padding-block: 0; }
  .footer__social { margin-block-start: 0; }
}

/* НАВЕДЕННЯ НА ЗАГОЛОВОК ЗНЯТЕ: на десктопі це не клямка, а звичайний
   заголовок — згортки тут немає, курсор звичайний, і підсвічувати нічого.
   ⚠️ Умова подвійна — ширина І можливість
   вказівника: сама ширина можливості не описує (`lint-hover-media`), а без
   ширини правило зняло б підсвітку й там, де згортка справді працює. */
@media (min-width: 1024px) and (hover: hover) {
  .footer__fold .fold__summary:hover { color: var(--ink-subtle); }
}

/* ⚠️ РОЗКЛАДКА КОЛОНОК СТОЇТЬ ОКРЕМО ВІД HOVER. Усередині `(hover: hover)`
   ці рядки не дістались би пристрою БЕЗ миші — планшета, ноутбука з тачем,
   емуляції в DevTools: ряд знаків утратив би притиск до правого краю доріжки
   і став би посеред неї, тобто на 1920 з'їхав би ліворуч на 136.

   Ширина й можливість вказівника — різні умови: від першої залежить
   розкладка, від другої тільки підсвітка. Тримає це `.qa/lint-hover-media`:
   властивість розкладки в hover-блоці = FAIL. */
@media (min-width: 1024px) {
  /* Явного `grid-column` тут більше НЕМАЄ: доріжок рівно стільки ж, скільки
     блоків, тож кожен лягає у свою автопотоком. Разом із номерами колонок
     пішла й пастка `nth-child` проти `nth-of-type` (колонки різнотипні —
     дві `nav` і один `div`), через яку третя колонка колись не потрапляла в
     правило взагалі.

     `justify-self: end` лишається: у каноні ряд знаків притиснутий до
     ПРАВОГО краю своєї доріжки, а не стоїть у ній по центру — на 1920 це
     видно як 136 порожніх ліворуч від нього (доріжка 1440…1872, знаки
     1576…1872). */
  .footer__social { justify-self: end; }
}

/* ==========================================================================
   Кнопка «Наверх». У макеті вона паркується над футером, а не висить над ним
 — це поведінка сторінки, тому тут лише сам контрол.
   ========================================================================== */
.to-top {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--btn-round);
  height: var(--btn-round);
  border-radius: 50%;
  background: var(--btn-bg);
  color: var(--btn-ink);
  transition: background-color var(--dur-fast) var(--ease);
}

.to-top svg { width: var(--icon); height: var(--icon); }

/*   стоїть ЦЕНТРОМ на верхній межі футера: y 7232 при висоті 56 дає центр 7260,
   а футер починається рівно на 7260 (7888 − 628). Парковка нижнім краєм на
   гутер вище межі лишала б порожній просвіт. */
.to-top--float {
  position: fixed;
  /* ГУТЕР ТУТ КАНОНІЧНИЙ, А НЕ ПОТОЧНИЙ. `--gutter` вище 1920 росте разом із
     порожнечею — і плаваюча кнопка їхала разом із ним до краю
     КОНТЕЙНЕРА: на 2560 її правий край опинявся на 2192 при вікні 2560, тобто
     посеред екрана. Кнопка не належить композиції сторінки, вона належить
     вікну, тому край рахується від вікна: `min` віддає той самий гутер до
     1920 і 48 далі. */
  inset-inline-end: min(var(--gutter), var(--s-48));
  inset-block-end: calc(var(--gutter) + var(--park, 0px));
  z-index: 5;
  visibility: hidden;
  opacity: 0;
}

.to-top--float.is-visible { visibility: visible; opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .to-top--float { transition: opacity var(--dur-base) var(--ease); }
}

@media (hover: hover) {
  .to-top:hover { background: var(--btn-bg-hover); }
}

/* ==========================================================================
   НАКАТ СЕКЦІЙ. Повноекранні кадри Головної наїжджають один на одного:
   попередній завмирає, наступний виїжджає поверх нього.

   Робить це `position: sticky` і ПОРЯДОК У DOM — жодного `z-index` і жодного
   покадрового JS. Пізніший сусід малюється поверх раннього за самим
   порядком; ставити йому z-index означало б заводити другу систему
   нашарування поруч із наявною.

   Липне кадр до нуля, а не до низу шапки: шапка сама липка й лежить вище
   (z-index 20), тому кадр заїжджає ПІД неї, а не зупиняється об неї.

   Ефект декоративний, тому цілком вимикається під `prefers-reduced-motion`:
   при знятому `sticky` секції просто йдуть одна за одною.

   ЩО ЛИПНЕ, А ЩО НІ — називає клас `stay`, а не місце в групі: у карті накату
   є пари, де наїзду бути НЕ повинно (стрічки товарів), тож «стоїть» мусить
   бути властивістю секції, а не наслідком її місця:

     стоїть (`stay`) наїжджає на нього
     «Новинки» пара «Апсайкл / Подіум»
     «Розпродаж» цитата засновниці

   Решта — перший екран, пара «Для неї / Для нього», пара «Трансформація»,
   SEO — просто йдуть у потоці: ні на кого не наїжджають і нікого під собою не
   тримають.

   ПЕРШІ ТРИ БЛОКИ В НАКАТ НЕ ВХОДЯТЬ на жодній ширині. Знімається це не
   правилом-винятком, а РОЗПУСКОМ ГРУПИ: секції не лежать у `.stack`, тож
   жоден селектор
   цього розділу до них не доходить — ні `main > .stack > .stay`, ні накат
   половинами на планшеті (`main > .stack > .pair > .pair__half`). Вимикач
   один, і його видно в розмітці, а не розсипаний по медіа-запитах.

   ГРУПА `.stack` лишається межею залипання, і в неї додалась друга робота:
   секції, що стоїть, потрібне ДЕ стояти, а діапазон дає контейнер, не вона
   сама. Тому в групу входить і той, хто наїжджає, і закінчується група
   СВІТЛИМ блоком:

     [«Новинки»* · пара «Апсайкл/Подіум»]
     [«Розпродаж»* · цитата · SEO]

   (зірка — `stay`.) ⚠️ Світлий хвіст обовʼязковий: темний кадр, який іде
   вгору останнім, показує свій нижній край, і той визирає з-під шапки
   темною смугою.
   ========================================================================== */
.stack { position: relative; }

/* Селектор навмисно з `main`: перший екран і цитата оголошують собі
   `position: relative` НИЖЧЕ по файлу, а при рівній вазі виграє нижнє
   правило — і рівно ці дві секції мовчки переставали липнути. Ваги (0,1,1)
   проти (0,1,0) досить; `!important` тут був би зброєю не по цілі. */
@media (prefers-reduced-motion: no-preference) {
  main > .stack > .stay {
    position: sticky;
    /* НУЛЬ ГОДИТЬСЯ ЛИШЕ ДЛЯ СЕКЦІЇ, НЕ ВИЩОЇ ЗА ЕКРАН. На 1920 кадр 980 у
       вікні 1000 — саме такий, і `0` там і лишається (мінімум із нуля й
       відʼємного числа). На 820 пара стає одноколонковою і виростає до двох
       половин: 1676 при екрані ~1000. Липке з `top: 0` приколюється, щойно
       його ВЕРХ дійшов до нуля, — тобто нижню половину не показало б уже
       ніколи.
       `calc(100dvh - 100%)` для високої секції відʼємний: вона їде далі, поки
       не зрівняється НИЖНІМ краєм із низом екрана, і приколюється тоді. Тобто
       правило одне на всі якорі, а не поріг у медіа-запиті. */
    inset-block-start: min(0px, calc(100dvh - 100%));
  }

  /* НЕПРОЗОРІСТЬ — ВИМОГА ДО ВСІХ У ГРУПІ, а не лише до липких. Секція, що
     стоїть, лишається приколотою до кінця групи; усе, що йде по ній далі,
     мусить її ЗАКРИТИ. Фото-секції непрозорі самі собою, а стрічка товарів і
     SEO-обгортка власного тла не мають, і крізь них видно приколоту пару.
     Тло лише для `.slider.stay` дісталось би рівно тим, кого й так нема кому
     просвічувати, тому правило написане на всіх дітей групи. */
  main > .stack > * { background: var(--surface); }

  /* ЗАЗОР УСЕРЕДИНІ ГРУПИ НАЛЕЖИТЬ ТОМУ, ХТО ЙДЕ, А НЕ ТОМУ, ХТО СТОЯВ.
     Біле поле між блоками зроблене зовнішнім відступом, а відступ прозорий за
     побудовою — жодне тло його не закриває. Усередині групи під ним лежить
     приколота секція, і крізь ці 100 px її видно. Тло дітей групи цього не
     лікує: воно закриває коробку, а зазор — за нею.
     Тому для дітей групи те саме число переїжджає у ВНУТРІШНЄ поле блока, що
     наїжджає. Геометрія не змінюється: 100 лишається 100, змінюється лише те,
     чиє воно — і, отже, чи є під ним фарба. Число одне й те саме
     (`--section-gap`), у двох місцях воно не розходиться. */
  /* Мінус піксель угору й плюс піксель у поле — це та сама «чорна лінія»
, але при ЦІЛОМУ масштабі екрана. Висота половини пари задана
     пропорцією 960/980, і при ширині 955 вона дає 974.89 — дробове число.
     Нижній рядок такої коробки намальований на 89 % світлою половиною
     «Трансформації» і на 11 % приколотою парою 2, що лежить під нею, і саме
     ці 11 % читаються як тонка сіра риска на білому.
     Блок, що йде наступним, підіймається на 2 px і накриває цей рядок собою.
     Вміст не зсувається: те, на що піднявся край, повернуто у внутрішнє поле.
     Чому 2, а не 1: при масштабі 1.25 один CSS-піксель — це 1.25 пікселя
     пристрою, і накриття на 1 лишало частку рядка непокритою; чекер міряв
     провал 44 при порозі 40. Два CSS-пікселі — 2.5 пікселя пристрою, тобто з
     запасом на будь-який дробовий масштаб. Поріг чекера при цьому не
     чіпався. */
  main > .stack > .slider {
    margin-block-start: -2px;
    padding-block-start: calc(var(--section-gap) + 2px);
  }
  main > .stack > .quote { margin-block-end: 0; }
  /* Плита лежить ПОЗА групою (вона нікого не накриває й ні на кого не
     наїжджає), але накрити нижній ряд цитати мусить так само: цитата —
     останній кадр своєї групи, і його дробовий край видно на білому. */
  main > .plate {
    margin-block-start: -2px;
    padding-block-start: calc(var(--section-gap) + 2px);
    background: var(--surface);
  }

  /* Останній у групі малюється на 1 px нижче за свою коробку — і цим накриває
     нижній край того, хто під ним стоїть.

     ⚠️ На самому НИЗУ групи лишається шов від дробового масштабу: край групи
     припадає на неціле число пікселів пристрою (3001 CSS × 1.25 = 3751.25),
     приколота секція малює цей рядок повністю, а світлий блок зверху —
     частково, і крізь ту частку прозирає її фото.

     `transform`, а не поле чи відступ: коробки, потоку й геометрії він не
     чіпає взагалі, тому жодне число сторінки від нього не зсувається. Ловить
     такий шов `.qa/check-seams.py`. */
  /* ⚠️ ПІКСЕЛЯ-НАКРИТТЯ ТУТ БУТИ НЕ МОЖЕ. Зсув останнього в групі на 1 px
     униз накрив би нижній край того, хто під ним стоїть, — але відкрив би
     рівно такий самий піксель НАД собою, де вже не малює ніхто: вийшла б
     біла смуга на всю ширину.

     Накривати низ групи є кому й без нього: наступний блок починається з
     −2 px (`main > .stack > .slider`, `main > .plate`), тобто край групи
     закритий тим, хто по ній іде. */
}

/* --------------------------------------------------------------------------
   НАКАТ НА ПЛАНШЕТІ: одиницею стає ПОЛОВИНА, бо саме вона там — екран.

   На 1920 пара — один кадр канону 1920×980 з двох половин поруч. На 820 пари
   як вузла НЕМАЄ ЗОВСІМ: макет малює дві сусідні композиції 820×838 підряд
   тобто впритул). Тобто на планшеті екранів удвічі більше, і кожен із них
   мусить стояти й бути накритим наступним.

   Липке на самій СЕКЦІЇ тут не працює: пара на 820 виростає до 1676 при
   екрані ~1180, тож формула високої секції приколює її НИЖНІМ краєм — поки
   стоїть нижня половина, верхня стоїть разом із нею, і наїзд зʼїдає їх знизу
   вгору.

   `display: contents` — щоб половини стали дітьми ГРУПИ, а не пари. Липке
   тримається межами свого блока-контейнера: половина всередині пари не може
   стояти довше за саму пару, тобто не доживає до наїзду наступної секції.
   Знявши коробку пари (на 820 їй у каноні й нема чого відповідати — вузла
   немає), ми даємо половинам контейнером усю групу — той самий діапазон, у
   якому на 1920 стоїть пара цілком.

   Хто стоїть — не змінилось: `stay` лишається на секції, а правило роздає
   його половинам. Карта накату замовника від цього не переписується.
   -------------------------------------------------------------------------- */
/* НИЖНЬОЇ МЕЖІ НЕМАЄ, і це не недогляд: `min-width: 820px` обірвав би накат
   половинами на 819, бо `display: contents` не вмикався б і половини лишались
   би вмістом однієї нелипкої коробки. 820 тут не виміряний поріг, а лише
   якір, з якого правило писалось. Верхню межу тримає блок нижче. */
@media (prefers-reduced-motion: no-preference) {
  main > .stack > .pair { display: contents; }

  /* БЕЗ `.stay`. Клас `stay` називає того, на кого наїжджають ІНШІ СЕКЦІЇ, і
     до накату ВСЕРЕДИНІ пари стосунку не має: там перша половина стоїть, а
     друга наїжджає на неї — і так у кожній парі, а не лише в тій, що комусь
     підставила плече. Із `.stay` у селекторі накат половинами мала б рівно
     одна пара з трьох. Одиницею накату в
     цій смузі є ПОЛОВИНА, і записано це без винятків. */
  main > .stack > .pair > .pair__half {
    position: sticky;
    inset-block-start: min(0px, calc(100dvh - 100%));
  }

  /* Пікселя-накриття, який на 1920 дістається останньому в групі, тут НЕМАЄ,
     і це перевірено, а не забуто. Зсунувши останню половину на 1 px униз, ми
     відкриваємо рівно такий самий 1 px НАД нею — а над нею не липка секція,
     а її ж сусідка по парі, тож у щілину видно біле тло групи. Замір: рядок
     5835 при 820 був суцільно білий (255) на всю ширину. Низ групи при цьому
     накриває наступний блок своїми −2 px (`.slider`, `.plate`), тому
     накривати його вдруге нема потреби.
     Тут це видно чекером `.qa/check-stack-gaps.js`: щілина між сусідніми
     кадрами групи = FAIL при будь-якій ширині. */
}

/* З 935 пара знову коробка — і половини знову лежать поруч, а не одна під
   одною. Обидва рядки скасовують саме те, що додав блок вище, і поріг у них
   ТОЙ САМИЙ, що в композиції: розійшовшись, вони дали б смугу, де половини
   стоять поруч, а накат рахує їх окремими екранами. */
@media (prefers-reduced-motion: no-preference) and (min-width: 935px) {
  main > .stack > .pair { display: grid; }
  /* RELATIVE, А НЕ STATIC. `static` теж знімає липкість — і разом із нею
     знімає КООРДИНАТНУ РАМКУ половини: `.pair__media` лежить абсолютно, і без
     позиціонованого предка його контейнером стає найближчий інший, тобто фото
     розтягнеться на всі 1920 і накриє сусідню половину. ⚠️ Заміри коробок
     цього не бачать: половини лишаються правильні, 960 і 960 — їде тільки
     фарба. */
  main > .stack > .pair > .pair__half { position: relative; }
}

/* ==========================================================================
   РИТМ СТОРІНКИ. Зазор між блоками не однаковий — він знятий із координат
   канону, а не призначений «через один»:

     перший екран → пара 1040 → 1040 0
     пара → пара 2020 → 2020 0
     пара → стрічка 3000 → 3100 100
     стрічка → пара 3866 → 3966 100
     пара → стрічка 4946 → 5046 100
     стрічка → цитата 5812 → 5912 100
     цитата → SEO 6892 → 6992 100
     SEO → підвал 7160 → 7260 100

   Тобто повноекранні кадри стоять упритул один до одного (їх розділяє межа
   фарби), а все, що має біле поле, відділяється зазором. Число одне —
   `--section-gap`, і воно вже є в токенах: 48 → 100.
   ========================================================================== */
/* Зазор названий і секції, і групі накату: секції лежать усередині `.stack`,
   і зовнішній відступ першої та останньої з них виходить назовні
   схлопуванням полів — те саме число, той самий результат. */
main .slider { margin-block: var(--section-gap); }
main .quote,
/* SEO-обгортка лежить УСЕРЕДИНІ групи накату, тому `main > .container` її не
   бачить, і без другого шляху біле поле перед підвалом зникло б. Селектор
   названий обома шляхами, а не помʼякшений до `main .container`: помʼякшений
   зачепив би контейнери всередині секцій — голову стрічки, наприклад. */
main > .container,
main > .plate { margin-block-end: var(--section-gap); }

/* ==========================================================================
   Hero. Назва вгорі ліворуч, підпис унизу ліворуч, між ними порожньо
.

   Висота — ПРОПОРЦІЯ вузла, а не `svh`. Канон малює 1920×980, 820×838 і
   375×540; замір гейта йде у вікні 1000 px заввишки, і будь-який варіант із
   `100svh − шапка` дав би 940 замість 980. Це той самий закон, за яким уже
   живуть половини пари й цитата: висота кадру приходить із вузла.
   ========================================================================== */
.hero {
  position: relative;
  isolation: isolate;
  aspect-ratio: var(--hero-ratio);
  display: grid;
  grid-template-rows: auto 1fr auto;
  justify-items: start;
  padding: var(--hero-pad-y) var(--hero-pad-x);
  overflow: clip;
  background: var(--black-100);
  color: var(--btn-ink);
}

.hero__media { position: absolute; inset: 0; z-index: -2; overflow: clip; }
.hero__media img { width: 100%; height: 100%; object-fit: cover; }

/* Стрічка кадрів. Зсув, а не проявлення: решта руху на сайті —
   `translate`, і crossfade вибивався б із системи. Кадр рівно один екран
   завширшки, тому крок зсуву — 100%, і числа ширини тут немає. */
.hero__reel {
  display: flex;
  width: 100%;
  height: 100%;
  will-change: transform;
}

.hero__reel > img { flex: 0 0 100%; }

/* Плівок ДВІ, і це не одна на 0.5: у каноні над фото стоїть `.2`
. Разом вони дають 0.52, і саме це число не збиралось би з
   однієї плівки без підгонки на око. */
.hero__media::after {
  content: '';
  position: absolute; inset: 0;
  background: var(--scrim-soft);
}

.hero__scrim {
  position: absolute; inset: 0; z-index: -1;
  background: var(--scrim);
}

/* Назва першого екрана. Роль Display — власна, не верхній край H1: вона
   оголошена окремим рядком у таблиці ролей. Ширина в каноні 1003 / 500 / 221 — це ширина ТЕКСТОВОГО
   вузла, тобто місце, у якому назва ламається на рядки; тому обмеження йде
   через `max-width`, а не через фіксовану ширину коробки. */
.hero__title {
  max-width: var(--hero-title-w);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: 500;
}

/* Знак усередині рядка, після «2026». Живе в рядку назви, а не
   окремим блоком: у макеті його низ стоїть на нижньому краї другого рядка.
   Розмір власний на кожному якорі — 45 / 64 / 110.

   ⚠️ Це САМ вузол, експортований у своїй кінцевій орієнтації, а не стрілка
   посилань, повернута на 90°. Родина знака та сама, але пропорція обводки —
   ні: у посилань 1.4 при полі 24 (0.058 від розміру), у канонного знака
   першого екрана 4 при полі 110 (0.036), і на кеглі 110 різниця в півтора
   раза помітна здалеку. */
/* НАЗВА ПЕРШОГО ЕКРАНА — ПОСИЛАННЯ. Знак-стрілка входить у клікабельну зону
   (у каноні він стоїть у тому самому рядку), а лінія лягає ТІЛЬКИ під текст —
   те саме правило, що в решти посилань.

   `box-decoration-break: clone` — бо назва йде у два рядки: без нього
   градієнт розтягується на обидва фрагменти як на один, і під першим рядком
   лінії немає взагалі. */
.hero__cta { color: inherit; text-decoration: none; }

.hero__cta-text {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  /* ЛІНІЯ ПІД ЛІТЕРАМИ, А НЕ ПІД КОРОБКОЮ РЯДКА. `100%` — це низ рядка, а не
     низ гліфа: між ними лежить нижній виносний елемент плюс півінтерліньяж.
     На кеглі 160 низ цифр стоїть на 37 px вище за низ коробки, тобто
     0.231em; просвіт під літерами 0.05em дає 0.18em. Число в em, тому воно
     тримається на кожній ширині: кегль назви іде рампою 60 → 160, і
     піксельний зсув поїхав би разом із ним. На дрібних носіях лінії цей зсув
     не потрібен — там 0.23em це 4 px. */
  background-position: 50% calc(100% - 0.18em);
  background-size: 0 var(--hairline);
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .hero__cta:hover .hero__cta-text { background-size: 100% var(--hairline); }
}

.hero__cta:focus-visible .hero__cta-text { background-size: 100% var(--hairline); }

.hero__mark {
  width: var(--hero-mark);
  height: var(--hero-mark);
  vertical-align: bottom;
  /* ЗАЗОР ДО ЗНАКА НА МОБІЛЬНОМУ ВДВІЧІ МЕНШИЙ — 8, а не 16. Канону тут
     ВЗАГАЛІ — це текстовий вузол без дітей, і на всьому артборді Home_375
     жодної стрілки 45 px не лежить. Число тут назване рішенням, а не зняте з
     макета. На 820 і 1920 зазор лишається канонічним 16. */
  margin-inline-start: var(--s-8);
}

@media (min-width: 768px) {
  .hero__mark { margin-inline-start: var(--s-16); }
}

/* Підпис під назвою — роль Hero-note (16/20 на M, 20/24 далі). Ширина з
   канону: 191 на M, 265 на T і D. */
.hero__note {
  align-self: end;
  max-width: var(--hero-note-w);
  font-size: var(--fs-hero-note);
  line-height: var(--lh-hero-note);
}

/* Прев'ю слайдів: три кола 56 із проміжком 8, на мобільному
   у каноні це 48 / 24 / 16, тобто рівно гутер. */
.hero__slides {
  position: absolute;
  inset-block-start: var(--hero-slides-top);
  inset-inline-end: var(--gutter);
  display: flex;
  flex-direction: column;
  gap: var(--s-8);
}

.hero__slide {
  position: relative;
  width: var(--hero-slide);
  height: var(--hero-slide);
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: none;
  cursor: pointer;
}

.hero__slide img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; }

/* ІНДИКАТОР СЛАЙДЕРА — ДВА ЗНАКИ, І ОБИДВА З КАНОНУ.

   ⚠️ Обидва знаки в макеті Є, просто вони живуть ВСЕРЕДИНІ інстанса
   мініатюри, і в дампі метаданих видно лише коробку 56×56. Узяті
   властивостями вузлів:

                                 радіус 25 у полі 56, від 12-ї години за
                                 годинниковою стрілкою; у макеті намальована
                                 чверть, тобто застигла на 25 % прогресу;
                                 полі 56 (вставка 7.143 % з усіх боків);
      (кадр попереду) — жодного знака, чисте фото.

   Нашою лишається тільки ТРИВАЛІСТЬ обертання дуги — макет статичний і числа
   часу не має.

   Неактивні мініатюри не приглушуються: у каноні кадр попереду світлий, а
   темний — той, що вже показаний. Стан «минув» несе диск, стан «іде» —
   дуга. */
.hero__seen,
.hero__ring {
  position: absolute;
  border-radius: 50%;
  pointer-events: none;
}

.hero__seen {
  inset: 7.143%;                 /* 4 із 56 — так само на кожному якорі */
  background: var(--hero-seen);
  opacity: 0;
}

.hero__slide[data-seen="true"] .hero__seen { opacity: 1; }

/* Дуга йде `stroke-dasharray` по `--p`, яку веде двигун: обведення — фарба,
   а не лейаут, тобто прогрес не змушує сторінку перераховувати коробки.
   `pathLength: 1` нормує довжину кола до одиниці, і `--p` лягає в неї прямо,
   без числа 2πr у стилях. Поворот на чверть проти стрілки ставить початок на
   12-ту годину — далі коло само йде за стрілкою. */
.hero__ring {
  inset: 0;
  rotate: -90deg;
  fill: none;
  stroke: var(--hero-ring);
  stroke-width: var(--hairline);
  stroke-linecap: round;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: var(--p, 0) 1;
}

/* Під знятим рухом автозміни немає, і дуга нікуди не їде — тому
   активний кадр позначається повним колом, а не порожнім. Інакше при знятому
   русі індикатор не показує НІЧОГО. */
@media (prefers-reduced-motion: reduce) {
  .hero__slide[aria-current="true"] .hero__ring { --p: 1; }
}

/* ==========================================================================
   Секція-пара. У макеті це рівно дві половини 960×980 без проміжку між ними
.

   На планшетному й мобільному якорях половини стають одна під одною.

   Висота половини на КОЖНОМУ якорі — пропорція вузла канону, а не висота
   вьюпорта. ⚠️ `50svh` на мобільному й `100svh` на планшеті дали б секцію
   2000 замість 1676 і 1000 замість 768 — мимо на 324 і 232 px, і жоден чекер
   цього не побачив би: вузли T і M не звʼязані між собою.

   Канон дає половину рівно так:
   ========================================================================== */
.pair { display: grid; }

.pair__half {
  position: relative;
  display: block;
  aspect-ratio: 375 / 384;
  overflow: clip;
  /*     домислу «текст поверх фото має бути світлим»; властивість вузла його не
     підтверджує.

     ⚠️ Розходження з каноном: «Подіум» у макеті БІЛИЙ, а
     тут чорний — як сусідній «Апсайкл». Тому окремого правила під нього
     немає.

     Світлим лишається тільки те, що лежить на темному навмисне: фраза в
     центрі правої половини і великий напис на мініатюрі наведення — обидва
     `white` за вузлом. */
  color: var(--ink);
  background: var(--black-100);
  /* ⚠️ ПІДСТИЛКА ПІД НИЖНІМ КРАЄМ. Дві сусідні половини стоять стик у стик
     (зазор 0.000 на всіх ширинах), але край цей БУВАЄ ДРОБОВИЙ:
     висота задана пропорцією 375/384, і на 375 вона ціла (384), а на 390 —
     ні (399.359). Коли обидві коробки різані `overflow: clip` і лежать на
     окремих шарах композитора (липке), рядок пристрою на дробовій межі
     кожна округляє САМА — і між ними лишається щілина, крізь яку видно біле
     тло групи. У безголовому Chromium щілина не відтворюється, тому правило
     тримається побудовою, а не спійманим дефектом: тінь малюється ПОЗА
     коробкою, власним `overflow` не ріжеться, наступна половина її накриває —
     видно її рівно там, де інакше було б видно біле тло, і ніде більше.

     Колір темний, бо на кожному такому шві зверху або знизу стоїть ФОТО:
     темна волосінь читається як край кадру, біла — як діра. Геометрії тінь не
     чіпає взагалі, тому жодне число сторінки не зсувається. */
  box-shadow: 0 2px 0 0 var(--black-100);
}

/* З 935 половини стоять ПОРУЧ, і шва між ними по вертикалі більше немає —
   підстилка нижнього краю тут зайва.

   ⚠️ ПРАВИЛО МУСИТЬ СТОЯТИ САМЕ ТУТ, ПІСЛЯ ОГОЛОШЕННЯ: вище за нього воно
   програє на рівній вазі, і тінь малюється на ВСІХ ширинах — під білою
   половиною «Трансформації» вона читається як сіра волосінь у пів-екрана
   завширшки. Чекер швів такого не бачить за побудовою: він рахує шов лише
   тоді, коли смуга покриває ≥ 90 % ширини. */
@media (min-width: 935px) {
  .pair__half { box-shadow: none; }
}

/* Світлий підпис — там, де кадр під ним темний. Зараз такий один («Подіум»,
   ), і клас названий за роллю, а не за секцією: наступний темний
   кадр візьме той самий клас, а не заведе другий. Число, яке тримає це
   правило: світність тла 6,6,7 → чорний дав би 1.28:1. */
.pair__label--light { color: var(--btn-ink); }

@media (min-width: 768px) {
  .pair__half { aspect-ratio: 820 / 838; }
}

/* ⚠️ ДВІ КОЛОНКИ ПОЧИНАЮТЬСЯ З 1140, А НЕ З 1920: поріг, що дорівнює якорю,
   недосяжний на екрані того ж якоря — на моніторі 1920 вікно віддає 1905
   (смуга прокрутки), тобто десктопної композиції не побачив би ніхто, крім
   ширших екранів.

   1140 — виміряне дно шапки і спільний поріг ідентичності всіх трьох
   блоків-облич. При 1905 половина виходить 952×972 проти канонних 960×980,
   тобто композиція та сама; при 1140 — 570×582. */
/* ДВІ КОЛОНКИ — З 935. Власного порога в пари немає: двоколонкова пара
   вміщається на всіх ширинах від 768 до 1140 — мініатюра 300×412 стоїть у
   половині, слово наведення в один рядок, виносу немає. 1140 діставалось би
   парі не від неї самої, а від шапки, де поріг виміряний.

   935 — не нове число, а вже зареєстрований поріг кількості колонок сітки й
   слайдера: ширина, на якій сторінка в цілому стає широкою. Власне число тут
   було б порогом-сиротою заради одного блоку.

   ⚠️ Ціна: у смузі 935…1139 пара десктопна, а шапка й підвал іще планшетні —
   відступ від правила спільної ідентичності, записаний рядком. */
@media (min-width: 935px) {
  .pair { grid-template-columns: 1fr 1fr; }
  .pair__half { aspect-ratio: 960 / 980; }
}

/* Декоративні шари НЕ ловлять вказівник. Через це таби «Для неї / Для нього»
   у половині «Трансформація» не працювали: `.pair__media` іде в розмітці
   ПІСЛЯ них і без `z-index` малюється поверх — а разом із фарбою забирає й
   клік. На екрані це виглядало як «двигун табів не працює», хоч двигун
   відпрацьовував бездоганно на програмному кліку. Симптом і причина тут
   різні настільки, що без проби кліком діагноз був би неправильний. */
.pair__media,
.pair__scrim,
.pair__thumb { pointer-events: none; }

.pair__media { position: absolute; inset: 0; }
.pair__media img { width: 100%; height: 100%; object-fit: cover; }

/* Затемнення половини під курсором. Знято заміром яскравості двох кадрів
   канону, а не оком: 183.7 → 122.7 при `0.6 × L + 0.4 × 31 = 122.6`.
   Лежить під мініатюрою — сама мініатюра не темнішає. */
.pair__scrim { position: absolute; inset: 0; background: var(--scrim); opacity: 0; }

/* Підпис у СПОКОЇ стоїть у лівому верхньому куті з відступом гутера (канон
: x 48, y 48 від краю половини), а не по центру.

   При наведенні підпис із кута зникає, а замість нього зʼявляється мініатюра
   з тим самим словом по центру. */
.pair__label {
  position: absolute;
  /* ВЕРТИКАЛЬ — ВЛАСНЕ ЧИСЛО, ГОРИЗОНТАЛЬ — ГУТЕР. У каноні підпис стоїть на
     лише на 1920: гутер на вузьких якорях підняв би підпис на 8 px вище за
     макет, і поруч із табами (26 / 35) це читалось би як невирівняний
     текст. */
  inset-block-start: var(--pair-label-top);
  inset-inline-start: var(--gutter);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
}

/* Мініатюра 300×412 по центру половини. Це НЕ плитка кольору: у кадрі
   наведення крізь неї видно знімок, тому всередині лежить фото, а зверху
   світла плівка. */
.pair__thumb {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  display: grid;
  place-items: center;
  /* ТІ САМІ ЧИСЛА, ЩО В МІНІАТЮРИ ПОЛОВИНИ (`--pair-card-*`): 146×198 на
     мобільному, 300×412 від планшета. Одні 300×412 на всіх якорях дали б на
     375 мініатюру в 80 % ширини половини проти 31 % на 1920 — замість «картки
     поверх кадру» вийшло б «фото на весь екран».
     Канон кадру наведення на вузьких якорях не малює зовсім, тож
     число не знімається з макета — воно БЕРЕТЬСЯ З СУСІДНЬОЇ РОЛІ, у якої
     та сама робота: знімок по центру половини. */
  /* І СТЕЛЯ ЧАСТКОЮ ПОЛОВИНИ. Константа на всю смугу від 820 не годиться:
     з 935 половина стає ПОЛОВИНОЮ екрана й падає з 934 до 467, тож та сама
     мініатюра займала б 32 % ширини половини на 934 і 60 % на 991 — на 1024
     вона накрила б і кутовий підпис, і таби.

     Частка взята з НАЙВУЖЧОЇ ПРИЙНЯТОЇ ширини, а не вигадана: на 1180
     половина 590×602.29, мініатюра 300×412, тобто 50.847 % і 68.405 %. Обидві
     тримається точно: на 1024 виходить 260.3×357.5, і 260.3/357.5 = 0.728.

     `min()` замість нового `@media`: на 1180 і вище частка дає рівно 300, і
     нового порога-сироти в `config.json → thresholds.registered` не
     зʼявляється. Нижче 935, де
     половина знову на весь екран, частка теж більша за 300 — якорі 820 і 375
     недоторкані. */
  width: min(var(--pair-card-w), var(--pair-card-max-w));
  height: min(var(--pair-card-h), var(--pair-card-max-h));
  overflow: hidden;
  opacity: 0;
}

/* ПЛОСКА ПАРА: СТЕЛЯ БЕРЕТЬСЯ ВІД ВИСОТИ, А НЕ ВІД ШИРИНИ. Спільне правило
   вище тримає ширину числом, а висоту — часткою половини (68.4 %), і це
   правда, поки половина майже квадратна (960×980). Тут вона вдвічі нижча
   (960×490 на десктопі, 410×320 на планшеті), тож частка висоти різала
   мініатюру, лишаючи ширину: замість вертикальної картки виходив
   горизонтальний прямокутник — видно на знімку, а не виведено з міркування.

   Тому висота тут ведуча, а ширина йде за пропорцією картки. 70 % — не
   канонне число: кадру наведення для цієї пари макет не малює зовсім
, і число взяте з умови «картка не торкається країв половини»
   тобто з тієї ж потреби, що й стеля в спільному правилі. */
.pair--flat .pair__thumb {
  block-size: min(var(--pair-card-h), var(--cl-pair-thumb-h));
  inline-size: auto;
  aspect-ratio: calc(var(--pair-card-w) / var(--pair-card-h));
}

.pair__thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pair__thumb::after { content: ""; position: absolute; inset: 0; background: var(--thumb-surface); }

/* Слово наведення — окремий шар, а не вміст мініатюри.

   342×52 у точці 309;2484 при половині 960×980, тобто центр 480;2510;
    — 160×52 у 399;1504, той самий центр. Поки воно жило всередині
   мініатюри (300 завширшки, `overflow: hidden`), слово ширше за неї
   обрізалось — «Трансформація» показувалась обрубком. Це не косметика:
   у каноні мініатюра й слово — два різні шари, і другий лежить над першим.

   Кегль ВЕЛИКИЙ — 48/52, а не 20/24, як у кутового підпису: це два різні
   стани одного слова, і кегль у них різний. Колір білий за вузлом
. */
.pair__word {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  z-index: 3;
  /* H2 замість жорстких 48/52. На 1920 це ті самі 48/52, що у вузлі
     наведення в макеті немає, слово йде тією ж рампою, що й решта
     заголовків (28 на 375, 36 на 820). Жорсткі 48 на мобільному дали б напис
     ушир половини. */
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  /* Medium, як у вузлі (стиль H2-48): успадкована 400 зробила б
     слово легшим за макет на цілу вагу. */
  font-weight: var(--fw-medium);
  text-align: center;
  white-space: nowrap;
  color: var(--btn-ink);
  opacity: 0;
  pointer-events: none;
  /* ПІДКРЕСЛЕННЯ — та сама механіка, що в посилань підвала: градієнт по
     `background-size`, а не `text-decoration`, бо друге не анімується. Крива
     й тривалість — проєктні токени, тобто рух той самий, що всюди.
     Навіщо: слово поверх мініатюри — це назва розділу, і клікабельність вона
     мусить показувати сама. */
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

/* Ціль наведення — САМА мініатюра, а не половина: слово підкреслюється, коли
   курсор дійшов до нього, а не щойно зайшов у кадр. Тому мініатюра тут
   перестає бути прозорою для вказівника — на клік це не впливає, бо вона
   лежить усередині посилання-половини й клік однаково доходить до нього. */
.pair__thumb { pointer-events: auto; }

@media (hover: hover) {
  .pair__thumb:hover ~ .pair__word,
  .pair__card:hover ~ .pair__word {
    background-size: 100% var(--hairline);
    /* ЗАТРИМКА ЛИШЕ ЛІНІЇ, не прозорості: перше значення — для `opacity`
       (нуль), друге — для `background-size`. Слово проявляється за
       `--dur-base`, і лінія, що стартує з ним одночасно, добігає до кінця
       раніше, ніж слово стає видимим, — на екрані це «навів і відразу
       підкреслення», без жодного руху. */
    transition-delay: 0s, var(--dur-fast);
  }
}

/* Клавіатура бачить те саме, що миша: фокус на картці підкреслює слово. */
.pair__card:focus-visible ~ .pair__word { background-size: 100% var(--hairline); }

/* Перехід — тільки `opacity`: у каноні задані два статичні стани, тип
   переходу не описаний, тому взято найдешевший із можливих.

   Наведення живе під `hover: hover`, а на тачі те саме показує тап —
   через `.is-open`, який ставить двигун. Дублювати ефект на `:active` не
   можна: він спрацьовує і при звичайному скролі пальцем. */
/* Кадр наведення пари іде `--dur-fast` (160), а не `--dur-base`: разом із
   лінією, що йде слідом, увесь показ укладається в 320. Число не власне —
   це сусідній токен тієї ж шкали. */
@media (prefers-reduced-motion: no-preference) {
  .pair__thumb,
  .pair__word,
  .pair__scrim,
  .pair__label,
  .pair__caption { transition: opacity var(--dur-fast) var(--ease); }

  /* У слова властивостей ДВІ, і скорочений запис вище лишав йому одну. Саме
     тому підкреслення зʼявлялось миттю: правило переходу для `background-size`
     стоїть ВИЩЕ по файлу, і цей рядок його перекривав. Обидві властивості
     називаються тут разом — інакше наступна правка знову залишить одну.
     Затримка окремим рядком нижче: рух лінії має початись ПІСЛЯ появи слова,
     інакше він добігає до кінця, поки слова ще не видно. */
  .pair__word {
    transition: opacity var(--dur-fast) var(--ease),
                background-size var(--dur-fast) var(--ease);
  }
}

/* --------------------------------------------------------------------------
   255,255,255 на всьому кадрі), тому це фон, а не зображення: класти сюди
   білий JPEG на 960×980 означало б вантажити мережу заради нічого.
   -------------------------------------------------------------------------- */
.pair__half--plain {
  background: var(--surface);
  color: var(--ink);
}

/* Таби половини стоять праворуч угорі. Праве поле — гутер якоря; верх —
   власне число канону (26 / 35 / 51), бо з гутером воно не збігається на
   жодному з трьох. */
/* Таби — НАЙВЕРХНІЙ шар половини: у каноні вони теж останні у своєму фреймі
   (після кадру наведення й після слова). Це перемикачі, і зникати
   під кадром наведення вони не мають права. */
.pair__tabs {
  position: absolute;
  z-index: 4;
  inset-block-start: var(--pair-tabs-top);
  inset-inline-end: var(--gutter);
  display: flex;
  gap: var(--s-16);
}


/* Мініатюра товару по центру половини: 146×198 на M, 300×412 далі
   зʼявляється при наведенні на фото-половину, — та світла й з написом. */
.pair__card {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  /* СТЕЛЯ — ЧАСТКА ПОЛОВИНИ, А НЕ ЛИШЕ ПІКСЕЛІ. Пояснення — біля
     `.pair__thumb`, число там одне на дві ролі. */
  width: min(var(--pair-card-w), var(--pair-card-max-w));
  height: min(var(--pair-card-h), var(--pair-card-max-h));
  overflow: hidden;
}

.pair__card img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Прихований знімок саме `hidden`, а не прозорий: для дерева доступності
   «немає» і «невидимий» — різні речі. Атрибут працює документом: `base.css`
   несе `[hidden] { display: none }`. */

/*   313;476 при половині 960×980, тобто центр в обох напрямках).

   `nowrap` — властивість ВУЗЛА, а не смак: фраза несе `whitespace-nowrap` на
   вузла це підтверджує — 268 при екрані 375. ⚠️ Без `nowrap` фраза ламається
   на два рядки не через кегль: абсолютний елемент із `left: 50%` і
   авто-шириною отримує рівно ПОЛОВИНУ контейнера (187.5 з 375), скільки б
   тексту в ньому не було.

   Кегль теж свій, а не спільний із підписом цитати: обидва брали `--fs-sign`,
   а на 820 їхні шляхи розходяться — підпису цитати там у каноні немає зовсім
   токен давав їй 20/24 планшета. */
.pair__phrase {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  margin: 0;
  white-space: nowrap;
  font-size: var(--fs-phrase);
  line-height: var(--lh-phrase);
  text-align: center;
  color: var(--btn-ink);          /**/
}

/* Плівка, яка стоїть ЗАВЖДИ, а не при наведенні: у парі 3 права половина
   несе окремий прямокутник. Без нього фраза лягла б на світле
   фото.

   Він НЕ той самий, що скрим наведення: властивість вузла дає заливку
   `black/900` при `opacity: 20%`, тобто вдвічі світліше за `--scrim`. Це
   `--scrim-soft`, який у токенах уже є. */
.pair__scrim--always { background: var(--scrim-soft); opacity: 1; }

/* НИЖНІЙ ЦЕНТРОВАНИЙ ПІДПИС ПОЛОВИНИ — єдине, що сторінка «Подіум» додає до
   центр по кадру, низ на 48 від низу половини.

   Обидва числа не власні: набір — роль `--fs-phrase` (20/24 → 24/28, на 1920
   дає рівно канонні 24/28), відступ — `--pair-label-top`, той самий, що
   тримає верхній підпис. Симетрія тут не оформлення, а факт канону: 48
   зверху й 48 знизу.

   `z-index` вищий за плівку, але нижчий за слово наведення: підпис лишається
   видимим під плівкою, а слово лягає поверх нього. */
.pair__caption {
  position: absolute;
  z-index: 2;
  inset-inline: 0;
  inset-block-end: var(--pair-label-top);
  text-align: center;
  font-size: var(--fs-phrase);
  line-height: var(--lh-phrase);
  letter-spacing: var(--ls-h1);
}
.pair__caption--light { color: var(--btn-ink); }

/* Кадр наведення — окремий шар, а не підміна `src`: підміна дала б порожній
   кадр на час завантаження. У КАНОНІ він лежить під основним (
   абсолютний елемент малюється ПОВЕРХ звичайного незалежно від порядку в
   потоці, тож «шар під низом» показував би в спокої саме кадр наведення.
   Замість боротьби з порядком малювання шар лишається зверху, а видимістю
   керує прозорість. */
.pair__photo--hover { position: absolute; inset: 0; opacity: 0; }

/* Кадр стрічки. Перехід МИТТЄВИЙ і це не недогляд: крок стрічки — 0.4 с, а
   найкоротша тривалість проєкту (`--dur-fast`) з'їла б половину кроку, тобто
   кадр не встигав би стати собою до наступної зміни. Плавність тут дала б
   не мʼякість, а суцільну кашу з двох напівпрозорих знімків.

   `inset-block-start`/`inset-inline-start` замість `inset: 0`: `inset` фіксує
   і низ теж, а паралакс мусить мати право зробити кадр вищим за коробку. */
.pair__frame {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  opacity: 0;
}

.pair__frame.is-current { opacity: 1; }

/*

   Ефект живе під `(hover: hover)` на ВСІХ ширинах, а не з 1920: це чиста
   прозорість, вона нічого не зсуває. Мініатюра з підписом лишається де була —
   з 1920, бо 300×412 у половину 375×384 не вміщається за побудовою. */
/* ⚠️ `:has(:focus-visible)`, А НЕ `:focus-within`. Стан наведення мусить бути
   доступний із клавіатури, але фокус ставить і КЛІК мишею: із `:focus-within`
   кадр наведення лишався б стояти після кліку по табу, коли курсор уже пішов.
   `:focus-visible` браузер ставить лише тоді, коли фокус прийшов не від
   мишки. Обгортка
   `:has()` потрібна тому, що фокус живе на ВНУТРІШНЬОМУ контролі (табі,
   посиланні), а стан показує половина. */
@media (hover: hover) {
  .pair__half:hover .pair__photo--hover,
  .pair__half:has(:focus-visible) .pair__photo--hover { opacity: 1; }
}

@media (prefers-reduced-motion: no-preference) {
  .pair__photo--hover { transition: opacity var(--dur-fast) var(--ease); }
}

/* Таби половини при наведенні стають білими: активний із
   підкресленням, неактивний — білий на 80%. Колір бере `currentColor`
   половини, тому окремого значення тут немає. */
/* Половина «Трансформація» під курсором поводиться так само, як фото-половини
   мініатюрі зʼявляється велике слово. Різниця одна — мініатюра тут стоїть і в
   спокої, тому вона не проявляється, а тільки набуває підпису.

   ⚠️ Чого тут НЕМАЄ: у варіанті канону біле поле під курсором заміняється
   чистий білий). Питання відкрите; кадр із голови не домальовується. */
/* ⚠️ ПОРОГА ШИРИНИ ТУТ НЕМАЄ ЗОВСІМ — лишається тільки `hover: hover`.
   НАВЕДЕННЯ НАЛЕЖИТЬ НЕ ШИРИНІ, А ВКАЗІВНИКУ: із порогом ширини пристрій із
   мишею на вузькому екрані лишався б без ефекту цілком.

   Тач нижче тим самим не зачеплений: там працює `.is-open`, який ставить
   двигун по тапу, і повнота того переліку доводиться
   `.qa/check-touch-parity.py`. */
@media (hover: hover) {
  /* Колір названий і для фокуса: без цього рядка кутовий підпис лишався
     темним на темному кадрі наведення, тобто «зникав». */
  .pair__half--plain:hover,
  .pair__half--plain:has(:focus-visible) { color: var(--btn-ink); }
  /* Обидва таби беруть колір половини. Без цього рядка вони лишались зі своїми
     власними тонами — активний `--ink`, неактивний `--ink-subtle`, — і на
     темному кадрі наведення активний зникав зовсім, а неактивний ледве
     активний із підкресленням, неактивний на 80 %. Підкреслення малює
     `currentColor`, тому окремого правила під нього немає. */
  .pair__half--plain:hover .tab,
  .pair__half--plain:has(:focus-visible) .tab { color: inherit; }
  .pair__half--plain:hover .tab[aria-selected="false"] { opacity: .8; }
  .pair__half--plain:hover .pair__scrim,
  .pair__half--plain:has(:focus-visible) .pair__scrim { opacity: 1; }
  .pair__half--plain:hover .pair__word,
  .pair__half--plain:has(:focus-visible) .pair__word { opacity: 1; }
}

/* Скрим лежить ПОВЕРХ мініатюри, а не під нею: у зворотному порядку
   мініатюра лишається єдиною незатемненою плямою в затемненому кадрі. Слово
   над
   скримом (z-index 3) лишається світлим, тобто читається на затемненій
   картці, а не сперечається з нею. Те саме рішення, що для мініатюри
   фото-половин: плівка тону `--scrim`, не власного. */
.pair__half--plain .pair__card { z-index: 1; }
.pair__half--plain .pair__scrim { z-index: 2; }
/* ⚠️ Кутовий підпис «Трансформація» лежить у розмітці ПЕРЕД фото й скримом,
   тому без свого шару малювався б ПІД ними: правило «не гасне при наведенні»
   виконувалось би чесно, а на екрані підпису не було б. Шар
   той самий, що в табів, — обидва живуть над кадром і не перетинаються з
   мініатюрою. */
.pair__half--plain .pair__label { z-index: 4; }

@media (hover: hover) {
  .pair__half:hover .pair__thumb,
  .pair__half:hover .pair__word,
  .pair__half:hover .pair__scrim,
  .pair__half:has(:focus-visible) .pair__thumb,
  .pair__half:has(:focus-visible) .pair__word,
  .pair__half:has(:focus-visible) .pair__scrim { opacity: 1; }
  /* Кутовий підпис гасне В УСІХ половинах, «Трансформацію» включно: два
     однакові написи — у кутку й на мініатюрі — читаються як дубль. */
  .pair__half:hover .pair__label,
  .pair__half:has(:focus-visible) .pair__label,
  /* Нижній підпис гасне разом із кутовим, і це не симетрія заради симетрії:
     `hidden="true"`, а на їхнє місце приходить слово. */
  .pair__half:hover .pair__caption,
  .pair__half:has(:focus-visible) .pair__caption { opacity: 0; }
}

/* Дотик показує ТЕ САМЕ, що курсор, — інакше на телефоні половина мовчить.
   Перелік станів тут дублює `:hover` не з ліні: під `(hover: hover)` він
   ЗАБОРОНЕНИЙ, і об'єднати обидва в один селектор не можна — правило з
   `:hover` усередині media-запиту не діє на тачі, а `.is-open` поза ним не
   має права вмикати ефект на миші (тап там не буває). ⚠️ Перелік мусить
   повторювати `:hover` ДОСЛІВНО: кожен стан, що з нього випав, на телефоні
   просто не існує. */
.pair__half.is-open .pair__thumb,
.pair__half.is-open .pair__scrim,
.pair__half.is-open .pair__photo--hover:not([hidden]),
.pair__half.is-open .pair__word { opacity: 1; }
.pair__half.is-open .pair__label,
.pair__half.is-open .pair__caption { opacity: 0; }
/* ⚠️ КОЛІР — теж частина стану. Без нього кутовий підпис і активний таб
   лишаються темними на темному кадрі, тобто на тачі читаються як зниклі,
   хоч непрозорість у них правильна.
   Дублювання перелічених станів прибрати не можна (`:hover` живе під
   `(hover: hover)`, `.is-open` — поза ним), тому повнота переліку
   доводиться машинно: `.qa/check-touch-parity.py` порівнює обчислені стилі
   двох станів вузол за вузлом. */
.pair__half--plain.is-open { color: var(--btn-ink); }
.pair__half--plain.is-open .tab { color: inherit; }
.pair__half--plain.is-open .tab[aria-selected="false"] { opacity: .8; }

/* ==========================================================================
   Слайдер товарів. Власний двигун — жодної бібліотеки.

   Стрічка НЕ сидить у контейнері: вона на всю ширину полотна, а картки
   вирівняні по контейнеру відступом. Так у канону картка, що
   пішла ліворуч, лишається видною в гутері, а наступна визирає праворуч.

   Перевірка десктопного якоря арифметикою, а не на око: 4×432 + 3×32 = 1824,
   тобто рівно контейнер. Ширина картки ніде не записана числом — вона
   виводиться з кількості видимих і проміжку й лишається цілою сама.

   Прокрутка лишається НАТИВНОЮ (`overflow-x` + `scroll-snap`): тач-інерція,
   клавіатура й скрінрідер там уже правильні. Крива й тривалість руху по
   стрілці — на JS (`js/slider.js`), бо `scroll-behavior: smooth` малює
   браузерною кривою, а не проєктною.
   ========================================================================== */

/* Голова слайдера повторює сітку карток: заголовок у першій колонці, таби —
   від лівого краю картки, яку називає канон, посилання притиснуте до правого
   краю контейнера. Числа тут не пишуться: та сама сітка їх і дає.

   Мобільний якір — єдиний, де сітки в голові НЕМА зовсім: заголовок ліворуч,
   таби праворуч, більше в рядку нічого. Тому база — `flex`, а сітка вмикається
   з планшета. Попередня редакція клала заголовок на весь рядок (`1 / -1`), і
   таби падали під нього другим поверхом на обох вужчих якорях.

   `baseline`, а не `end`: таби несуть під собою лінію активного стану й тому
   на 5 px вищі за свій текст. Вирівняні низами, вони сідали б на 5 px нижче
   заголовка — у каноні ж на одній лінії стоять самі ЛІТЕРИ (заголовок 4329,
   таби 4330.5). */
.slider__head {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s-16);
  margin-block-end: var(--s-24);
}

/* Проміжок між табами на мобільному вужчий: у каноні «Для нього» починається
   вся пара з'їжджала вліво: 208.4 замість 214. */
.slider__tabs { display: flex; gap: var(--s-16); }
@media (min-width: 768px) { .slider__tabs { gap: var(--s-24); } }

@media (min-width: 768px) {
  .slider__head {
    display: grid;
    grid-template-columns: repeat(var(--slide-visible), 1fr);
    gap: var(--grid-gap);
  }

  .slider__title { grid-column: 1; }
  .slider__tabs  { grid-column: 2; }

  /* У стрічки картки товару табів у голові НЕМАЄ ЖОДНИХ — тому заголовок
     бере рядок цілком, а не першу колонку. Інакше «Вам також сподобається»
     ламалось надвоє в колонці 378, тоді як канон дає його одним рядком
. Ділити рядок тут ні з ким. */
  .preel .slider__title { grid-column: 1 / -1; }
}

/* ТАБИ СТОЯТЬ НАД ТРЕТІМ ТОВАРОМ — з 1420, а не з 1920. Місце табів задає не
   ширина екрана, а КІЛЬКІСТЬ КОЛОНОК: голова стрічки — та сама сітка, що й
   картки. Четверта колонка вмикається на 1420 (`--slide-visible`), і рівно
   там колонка 3 перестає бути останньою й стає тією, над якою в каноні
   стоять таби. Поріг 1920 тримав би канонічне вирівнювання рівно на одному
   якорі, а нижче лишав би таби над ДРУГИМ товаром.
   Нижче 1420 колонок три, і таби лишаються в другій: у третій вони сіли б на
   посилання «Перейти до колекції». */
@media (min-width: 1420px) {
  .slider__title { grid-column: 1 / 3; }
  .slider__tabs  { grid-column: 3; }
}

@media (min-width: 1024px) {
  .slider__head { margin-block-end: var(--s-40); }   /* канон: 3152 → 3192 */
}

/* Посилання «Перейти до колекції» лежить у розмітці ПІСЛЯ стрічки, бо на
   мобільному воно там і стоїть — під товарами, по центру, підписане «Більше»
. На планшеті й десктопі воно піднімається в рядок заголовка
   тією ж сіткою секції: обидві смуги — контейнери однакової ширини, тож
   посилання само стає на правий край контейнера, без жодної координати.

   Порядок у DOM обрано за мобільним, а не за десктопним: там, де візуальний
   порядок мусить розійтися з порядком читання, розходження віддане ширшому
   екрану — на вужчому клавіатура й скрінрідер ідуть тим самим шляхом, що й око. */
/* `minmax(0, 1fr)`, а не типова колонка `auto`: стрічка всередині ширша за
   екран, і колонка, яка рахує себе від вмісту, бере ширину з неї. Голова
   тоді з'їжджається до 1214 при контейнері 1824, і таби стають у чужу
   колонку — на екрані це виглядає як «таби не там», а не як зламана сітка. */
.slider { display: grid; grid-template-columns: minmax(0, 1fr); }
.slider__more { display: flex; justify-content: center; margin-block-start: var(--s-24); }

@media (min-width: 768px) {
  /* Колонка названа в КОЖНОГО з трьох, а не лише там, де вона «потрібна».
     Grid спершу розставляє елементи з явними координатами, і рядок, у якого
     названо лише рядок, іде шукати вільне місце — тобто в другу колонку, якої
     в сітці немає, і вона з'являється. Голова їхала праворуч і звужувалась до
     794 при контейнері 1824. */
  .slider__head { grid-row: 1; grid-column: 1; }
  .slider__body { grid-row: 2; grid-column: 1; }

  .slider__more {
    grid-row: 1;
    /* Колонка названа явно. Без неї браузер не кладе смугу в ту саму комірку,
       що й голову, а заводить ДРУГУ колонку під неї — і сітка секції з однієї
       колонки стає двома, а голова втрачає 190 px ширини. */
    grid-column: 1;
    justify-content: flex-end;
    align-self: start;
    /* Посилання стоїть не на верхній межі заголовка, а на РЯДКУ табів: у
       каноні текст «Перейти до колекції» і текст «Для неї» починаються на
       на 2 вища за свій текст (у ній знак 24 проти рядка 20), тому зсув
       рахується від табів мінус ці 2: 27 − 2 і 17 − 2.

       `align-self: baseline` цього не давало й дати не могло: смуга лежить у
       ТІЙ САМІЙ комірці, що й голова, а не поруч із нею, — елементи одної
       комірки базові лінії між собою не рівняють. Симптом на екрані —
       посилання на 25 вище за таби. */
    margin-block-start: 15px;                  /* 17 (таби) − 2 (коробка) */
    /* Смуга посилання лежить у тій самій комірці, що й голова, і накрила б
       таби прозорим прямокутником на всю ширину контейнера. Клікає саме
       посилання, а не смуга. */
    pointer-events: none;
  }

  .slider__more .link { pointer-events: auto; }
}

/* Рядок табів на десктопі нижчий (заголовок там 52, а не 40), тому й зсув
   інший — 27 − 2. Правило стоїть ПІСЛЯ планшетного: у файлі з однаковою
   вагою селекторів виграє нижній, і 15 з планшета інакше накрив би 25. */
@media (min-width: 1024px) {
  .slider__more { margin-block-start: 25px; }
}

/* Підпис короткий на мобільному, повний далі — один контрол із двома скінами,
   а не два посилання. Доступне імʼя лишається повним на всіх якорях: його
   несе `aria-label`, інакше на мобільному скрінрідер оголошував би «Більше»
   без жодної вказівки, більше ЧОГО. */
.slider__link-full { display: none; }

@media (min-width: 768px) {
  .slider__link-full  { display: inline; }
  .slider__link-short { display: none; }
}

/* `container-type` тут не оформлення, а одиниця виміру: `100cqw` — це ширина
   стрічки в БУДЬ-ЯКІЙ властивості, тоді як `100%` у вертикальному відступі
   порахувався б від висоти. Без цього стрілка стала б на висоту, виведену з
   висоти ж, і поїхала б при першій зміні вмісту. */
.slider__body {
  position: relative;
  container-type: inline-size;
}

.slider__track {
  display: grid;
  grid-auto-flow: column;
  /* `100%` тут — КОНТЕНТНА коробка стрічки, тобто відступи по краях із неї вже
     відняті. ⚠️ Відняти їх ще раз означало б картку 404 замість 432 — на
     екрані така різниця читається як «десь трохи вужче». Нижче, у стрілки,
     той самий відступ відняти ТРЕБА:
     `100cqw` міряє всю ширину стрічки разом із ними. */
  grid-auto-columns: calc(
    (100% - var(--slide-peek)
     - (var(--slide-visible) - 1) * var(--slide-gap)) / var(--slide-visible)
  );
  gap: var(--slide-gap);
  /* Край стрічки — власний, а не гутер сторінки. На мобільному з гутера 16
     сусідні картки визирали по-різному: 5 ліворуч і 25 праворуч.
     На ширших якорях `--slide-edge` дорівнює гутеру, тобто нічого не міняє. */
  /* ПОЧАТОК СТРІЧКИ — ПО КРАЮ СТОРІНКИ, а сума полів лишається тією самою.
     На мобільному край стрічки власний (26) і не збігається з гутером (16):
     перша картка стояла на 10 px правіше за заголовок «Новинки», і замовник
     назвав це діркою. Просте `padding-inline: var(--gutter)` зробило б картку
     343 замість канонічних 323 (гейт це й спіймав), тому зсув іде ПЕРЕРОЗПОДІЛОМ:
     ліворуч гутер, праворуч решта суми 2 × 26. Ширина картки не змінюється —
     змінюється тільки те, з якого боку лежить окраєць.
     На 488 і вище `--slide-edge` дорівнює гутеру, і вираз віддає той самий
     гутер з обох боків, тобто нічого не міняє. */
  padding-inline: var(--gutter) calc(var(--slide-edge) * 2 - var(--gutter));
  /* ОКРАЙЦІ СУСІДНІХ КАРТОК РІВНІ — по 16 з обох боків на мобільному.
     Річ не в ширинах карток (усі 323), а в тому, ЧИМ виявляється окраєць з
     кожного боку. При прилипанні на гутері ліворуч після свайпу лишався б
     тільки ЗАЗОР (16 − 10 = 6), а праворуч — решта вікна
     (375 − 16 − 323 − 10 = 26): два законні числа з різних причин, а на око —
     картки різного розміру.

     Прилипання зсунуте на `--slide-edge` (26 на M): картка стає на 26, і по
     обидва боки лишається по 16. ПЕРША КАРТКА ЛИШАЄТЬСЯ ПО ГУТЕРУ — її
     позиція прилипання відʼємна (16 − 26), недосяжна, і браузер тримає
     `scrollLeft: 0`. Тобто вимога від 19.08 («перша картка по краю
     заголовка») не скасована: зсунуті лише картки з другої.

     Від 488 і вище `--slide-edge` дорівнює гутеру, тобто вираз віддає той
     самий гутер, і на планшеті з десктопом не міняється нічого. */
  scroll-padding-inline-start: var(--slide-edge);
  overflow-x: auto;
  overscroll-behavior-x: contain;   /* свайп у слайдері не гортає сторінку */
  scroll-snap-type: x mandatory;
  /* Смуга ховається лише там, де є інший спосіб зрозуміти, що список
     гортається: на дотику це сам жест, на десктопі — стрілка. */
  scrollbar-width: none;
}

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

/* Прилипання знімається на час руху по стрілці (`js/slider.js`). Поки
   воно лишалось `mandatory`, браузер приліплював кожне програмне присвоєння
   `scrollLeft`, і проєктна крива не малювалась узагалі — стрічка стрибала за
   один кадр. Клас живе рівно стільки, скільки триває анімація. */
.slider__track.is-gliding { scroll-snap-type: none; }

.slider__slide { scroll-snap-align: start; }

/* Стрілки лежать НАД картками, а не в голові: у каноні це коло
   56×56 із простою стрілкою всередині, вирівняне по центру ФОТО, не картки.
   Центр фото рахується з тієї самої пропорції 432/612, якою живе
   `.card__media`, — інакше при зміні пропорції стрілка поїхала б сама.

   Знак — експортований вузол: вертикальна риска й шеврон, обведення 1.4,
   торці квадратні. Дужок у нього НЕМАЄ: дуги — це інший знак канону, той, що
   стоїть у текстовому посиланні «Перейти до колекції». Дві попередні редакції
   плутали їх між собою.

   Стрілок дві. Вони з'являються тільки коли курсор над стрічкою, і кожна
   зникає, щойно вести їй нікуди: стрічка не закільцьована. Існують
   лише там, де є курсор: на дотику гортають пальцем. */
.slider__nav { display: none; }

/* hover-розкладка: стрілки стрічки — це САМ стан наведення, а не розкладка,
   що під нього заїхала. У базовому кадрі канону їх немає взагалі, вони є в
   кадрі наведення, а на дотику стрічку гортають пальцем — тому на
   пристрої без миші їх не повинно бути, і `display: none` за межами цього
   блоку саме це й задає. Виняток вузький: він стосується двох правил самої
   кнопки, а не сусідніх блоків. */
@media (hover: hover) and (min-width: 1024px) {
  .slider__nav {
    position: absolute;
    inset-block-start: calc(
      (100cqw - 2 * var(--slide-edge) - var(--slide-peek)
       - (var(--slide-visible) - 1) * var(--slide-gap))
      / var(--slide-visible) * var(--card-h) / var(--card-w) / 2);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--btn-round);
    height: var(--btn-round);
    translate: 0 -50%;
    border-radius: 50%;
    /* Біле коло з ледь помітним обідком — з експортованого вузла канону:
       заливка `#fff`, коло-обведення `black/100` на 15% прозорості. Воно
       читається тільки поверх фото, і саме там воно й стоїть. */
    background: var(--surface);
    box-shadow: inset 0 0 0 var(--hairline) rgba(231, 231, 231, .15);
    color: var(--ink);
    /* Без цього стрілка лежить ПІД картками: `.card__link:after` — теж
       позиційований елемент, іде в потоці пізніше й перекриває її цілком.
       Кнопку було не натиснути й майже не видно; спіймано `elementFromPoint`,
       який у центрі кнопки повертав посилання картки. */
    z-index: 2;
    /* З'являється по наведенню на СТРІЧКУ, а не висить постійно: у базовому
       кадрі канону її немає, вона є в кадрі наведення. */
    opacity: 0;
    transition:
      opacity var(--dur-fast) var(--ease),
      background-color var(--dur-fast) var(--ease);
  }

  .slider__nav--next { inset-inline-end: calc(var(--gutter) + var(--s-24)); }
  .slider__nav--prev { inset-inline-start: calc(var(--gutter) + var(--s-24)); }

  /* Стрілка «назад» — той самий знак, розвернутий у протилежний бік.
     hover-розкладка: розмір знака всередині кнопки, якої на пристрої без
     миші немає зовсім (причина — в шапці блоку). */
  .slider__nav svg { width: 12px; height: 14px; }
  .slider__nav--next svg { rotate: 90deg; }
  .slider__nav--prev svg { rotate: -90deg; }

  /* Показує не наведення на блок, а близькість курсора до краю: класи ставить
     двигун, зона — одна картка завширшки. */
  .slider.is-near-next .slider__nav--next,
  .slider.is-near-prev .slider__nav--prev { opacity: 1; }

  /* Клавіатурі стрілка потрібна завжди, а не тільки поки над стрічкою висить
     курсор — але саме КЛАВІАТУРІ. ⚠️ `:focus-within` на всій стрічці тримав би
     ОБИДВІ стрілки після звичайного кліку мишею: клік дає фокус, а фокус не
     знімається, доки не клацнеш деінде.

     `:focus-visible` цього не робить: браузер ставить його для клавіатури й
     не ставить для миші. Показується при цьому та стрілка, на якій фокус, а
     не пара — друга свого фокуса не має. */
  .slider__nav:focus-visible { opacity: 1; }

  .slider__nav:hover,
  .slider__nav:focus-visible { background: var(--ink); color: var(--btn-ink); }
}

/* `hidden` ставить двигун на кнопці, якій нема куди вести. Правило потрібне
   тому, що `display: inline-flex` вище перебиває типову поведінку атрибута. */
.slider__nav[hidden] { display: none; }

/* ==========================================================================
   Накат при прокруті: наступний блок не «виїжджає анімацією», а просто йде
   поверх попереднього, тоді як попередній залипає. ⚠️ Ні GSAP, ні обробника
   `scroll` тут немає: рух на подію `scroll` іде ступенями. Весь накат тримають
   два правила.

   Що це дає порівняно з анімацією `translateY`: блок, зсунутий трансформом,
   лишає по собі дірку в потоці, і її доводиться закривати третім числом.
   Липкий попередник дірки не лишає взагалі.

   `top` дорівнює висоті шапки, бо шапка липка і без цього залипаючий
   блок ховався б під нею на власну висоту.

   Паралакс — 20 % висоти блоку: знімок на 120 % кадру їде від −10 % до
   +10 % власної висоти, тобто рівно на 20 % кадру, і щілини не відкриває в
   жодній точці. Він на `animation-timeline: view()` — прокрутка керує
   ПОЗИЦІЄЮ кадру анімації, а не запускає її: браузер малює це на композиторі,
   тоді як `rAF` із лерпом малює в потоці JS.
   ========================================================================== */
.stack__under { position: sticky; inset-block-start: var(--header-h); }
.stack__over { position: relative; z-index: 1; background: var(--surface); }

/* Весь декоративний рух знімається разом. Липкість тут теж рух: блок,
   що залипає й від'їжджає, для вестибулярної чутливості нічим не кращий за
   анімацію. */
@media (prefers-reduced-motion: reduce) {
  .stack__under { position: static; }
}

/* ПАРАЛАКС — ТІЛЬКИ НА ДЕСКТОПНІЙ СМУЗІ, і ціна названа числом.

   Паралакс вимагає, щоб знімок був ВИЩИЙ за свій кадр: рухається та частина,
   якої в кадрі не видно. А на цій сторінці кадр і є фото: знімки 1440×1470
   (0.9796), кадри половин — 960/980 = 0.9796, 820/838 = 0.9785, 375/384 =
   0.9766. Тобто будь-який надлишок під рух відрізається від самої
   композиції: при 20 % кадр показував би верхні 83 % фото, при 10 % — 91 %,
   і в обох випадках у моделей зникали б голови.

   Тому на вузьких якорях руху НЕМАЄ ЗОВСІМ: там прийнято повний кадр
   і 820 заморожений саме таким. На десктопі надлишок 6 % — це 5.7 % знімка
   поза кадром; рішення показати цю ціну на живій сторінці, а не сперечатись
   про неї словами.

   Перший екран паралаксу не має з іншої причини.

   Фолбека для рушіїв без scroll-driven анімацій більше немає: він тримав
   ДРУГИЙ перелік тих самих селекторів і другу копію арифметики ходу, а
   платили за це кадром — там, де він розходився з CSS. Без нього такий
   браузер просто не бачить руху, і це рівно те, що бачать вузькі якорі. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) and (min-width: 1420px) {
    /* Цитата в перелік не входить: її знімок кадрований під якір, тобто
       кадр і є фото, і будь-який надлишок під рух ріже саму композицію — та
       сама причина, з якої паралакс уже знято з інших повноекранних кадрів. */
    .pair__media > img:not(.pair__photo--hover) {
      height: calc(100% + var(--parallax-over));
      animation: jk-parallax linear both;
      animation-timeline: view();
      animation-range: entry 0% cover 100%;
    }
  }
}

/* Хід іде ТІЛЬКИ ВНИЗ ВІД ВЕРХУ ФОТО: знімок вирівняний верхом до верху
   коробки, тому плюсовий бік відкриває кадр згори, а нуль — найвище
   положення фото, тобто голови в кадрі. */
@keyframes jk-parallax {
  from { translate: 0 calc(-1 * var(--parallax-rest)); }
  to   { translate: 0 calc(-1 * var(--parallax-rest) - var(--parallax-amp)); }
}

/* ==========================================================================

   Геометрія канону: текст 14/16 (`bn-t14`), проміжок 4 між ланкою й скісною.
   Останню ланку в макеті нічим не виділено, тому вона й тут така сама на
   вигляд — але це НЕ посилання, і в дереві доступності вона позначена
   `aria-current`.

   Скісна — власний вміст `<li>`, а не `:before`: генерований вміст читають
   не всі скрінрідери, і крихти перетворюються на суцільне слово.
   ========================================================================== */
.crumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* ПРИГЛУШЕНІ ТУТ ПОПЕРЕДНІ ЛАНКИ, А НЕ ПОТОЧНА. Канон дає `black/300`
   кожній ланці-посиланню й кожній скісній і `black/900` останній — на двох
   ШЛЯХ, а яскраве — те, де ти стоїш; зворотне прочитання («вести їй нікуди,
   отже вона тьмяна») канон не підтверджує. */
.crumbs__sep { color: var(--crumb-ink); }

.crumbs a {
  color: var(--crumb-ink);
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .crumbs a:hover { background-size: 100% var(--hairline); }
}

.crumbs a:focus-visible { background-size: 100% var(--hairline); }

/* ОСТАННЯ ланка — основним тоном, і саме ОСТАННЯ, а не «поточна». Різниця
   в тому, що на картці товару остання ланка це живе посилання на розділ
   («Для неї»), а не `aria-current`: сторінка тут — сам товар, і його назву
   каже h1 поруч. За `aria-current` остання ланка картки лишалась би тьмяною
   при чорній у каноні. */
.crumbs__list > li:last-child,
.crumbs__list > li:last-child a,
.crumbs [aria-current="page"] { color: var(--ink); }

/* ЧЕТВЕРТА ЛАНКА Є ТІЛЬКИ НА ДЕСКТОПІ. Канон дає чотири ланки на 1920
   обидві 159 = 44 + 8 + 43 + 9 + 39 із зазорами 4). Ховається саме поточна
   сторінка: її назву на вузькому екрані каже h1 рядком нижче, тож із розмітки
   не зникає нічого, чого не було б видно.

   Поріг спільний із рештою десктопної композиції каталогу — 1140, виміряне
   дно десктопної шапки й підвала. Власного порога крихти не отримують:
   ланка, що зʼявляється окремо від панелі, робить сторінку наполовину
   десктопною. Число сітки карток (1420) тут не годиться: у неї дно своє й
   виміряне, і ланка вмикалась би на 280 px пізніше за шапку. */
.crumbs__item--wide { display: none; }

@media (min-width: 1024px) {
  .crumbs__item--wide { display: block; }
}

/* КАРТКА ТОВАРУ МІНЯЄ ОСТАННЮ ЛАНКУ, а не ховає її. На T і D це посилання на
   кожному якорі видно рівно одну: підмінювати текст скриптом означало б
   тримати назву товару у двох місцях.

   Усічення тут не «щоб влізло»: назва довша за будь-яку розумну ланку, і
   канон сам показує її обрізаною. Ширину не задаємо числом — ланка бере те,
   що лишилось у рядку після стрілки «назад», і ріже хвіст. */
/* ⚠️ Розходження з каноном: канон малює крихти по-різному на трьох
   якорях — на 1920 і 820 без назви товару, на 375 із назвою ЗАМІСТЬ розділу
   й обрізаною. Тут обидві ланки видимі на всіх ширинах: крихти
   показують шлях, а шлях не буває різним залежно від екрана. Порога в них
   немає жодного, місце дає перенос рядка, а не обрізка. */
.crumbs__item--self { min-inline-size: 0; }

/* ==========================================================================

   Виділена в окремий компонент бібліотеки не тому, що так акуратніше, а тому
   що кількість ланок у ній перестане бути нашою: на WordPress меню підвала
   редагується в адмінці, і завтра там буде не пʼять пунктів, а один або
   дванадцять. Поки згортка жила тільки всередині підвала, перевірити її можна
   було рівно на одній конфігурації — тій, що в макеті. На стенді вона стоїть
   зі стрес-станами.

   Геометрія рядка вся з канону, властивостями вузлів: висота 64
, лінія знизу `#e7e7e7` там же, текст 14/16 у тоні `black/400`
, шеврон 20×20 праворуч. Останній рядок групи лінії не має
    єдиний із трьох без `border-b`.

   Розгорнутого стану канон НЕ малює: усі три рядки в макеті закриті. Ритм
   усередині взято з десктопної колонки підвала — єдине місце компонента без
   вузла-джерела, записане рядком.

   `list-style: none` + вебкітний маркер: без них браузер малює власний
   трикутник, якого в макеті немає.
   ========================================================================== */
.fold__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
  height: 64px;
  /* ЛІНІЯ НАД ЗАГОЛОВКОМ, а не під ним: так вона відбиває пункт від СПИСКУ
     попереднього, а не заголовок від власного списку — саме так читається
     розгорнута колонка. */
  border-block-start: var(--hairline) solid var(--line);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  font-weight: var(--fw-regular);
  cursor: pointer;
  list-style: none;
}

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

.fold__icon {
  flex: none;
  width: 20px;
  height: 20px;
  rotate: 0deg;
  /* Знак ЧОРНИЙ, а назва поруч — приглушена. Це не вільність: обведення в
     успадковував `currentColor` від клямки й вицвітав разом із назвою —
     єдиний контрол рядка ставав найблідішим, що в ньому є. */
  color: var(--ink);
}

@media (prefers-reduced-motion: no-preference) {
  .fold__icon { transition: rotate var(--dur-fast) var(--ease); }
}

.fold[open] .fold__icon { rotate: 180deg; }

/* ==========================================================================

   Повноекранний кадр із фото, скримом і чотирма написами. Висота — пропорція
   вузла на кожному якорі: 1920×980 · 820×824 · 375×500. Не `svh`: рівно на
   цьому місці половина пари прожила з домислом «на повний екран» і виходила
   на 324 px мимо.

   Підпис «(Юлія Перекрьостова )» стоїть у ГЕОМЕТРИЧНОМУ центрі кадру, і це не
   окомір: 476 + 28/2 = 490 = 980/2 на десктопі, 238 + 24/2 = 250 = 500/2 на
   мобільному. Обидва рівні до пікселя.

   На планшеті підпису НЕМАЄ. Твердження про відсутність спирається на повний
   заголовок, — і жодна з них не підпис.

   Вертикальні відступи 48 · 32 · 24 — це НЕ гутер (48 · 24 · 16): згори й
   знизу кадр дихає ширше, ніж з боків, на планшеті й мобільному. Горизонтальні
   — гутер.
   ========================================================================== */
.quote {
  position: relative;
  aspect-ratio: 375 / 500;
  overflow: hidden;
  color: var(--btn-ink);
  /* Тло СВІТЛЕ, і це не смак: знімок у каноні — вирізка з прозорим тлом
     заливка вузла). Поки фото лежало в JPEG, прозорість сплющувалась у
     чорне — звідси «фото не те»: кадр той самий, тло протилежне. Формат
     файла тут webp саме через альфу, решта знімків лишаються jpeg. */
  background: var(--surface);
}

@media (min-width: 768px)  { .quote { aspect-ratio: 820 / 824; } }
/* 1140, а не 1920: кадр цитати — такий самий повноекранний блок, як перший
   екран, і пропорцію він міняє на тому ж порозі ідентичності. У смузі
   1140…1919 планшетна пропорція давала кадр заввишки майже в дві третини
   ширини — на 1680 це 1689 px. */
@media (min-width: 1024px) { .quote { aspect-ratio: 1920 / 980; } }

/* Кадр фото заданий чотирма числами на кожному якорі, а не `object-fit`.
   Причина не в примхі: у макеті знімок НЕ вписаний у рамку жодним із режимів,
   які вміє `object-fit`. Дизайнер його наблизив і зсунув, і на кожному якорі

     1920×980 w 100% h 293.84% x 0 y −37.43%
      820×824 w 137.99% h 206.07% x −11.78% y −26.79%
      375×500 w 130.79% h 147.2% x −15.45% y −9.5%

   `cover` дає лише перший рядок — на 1920 канон справді збігається з ним. На
   планшеті ж знімок збільшений понад `cover` у 1.38 раза, і центрована рамка
   показувала не портрет, а руки. Саме це видно як «фото не того масштабу»:
   масштаб і був не той — на двох якорях із трьох.

   Відсотки, а не пікселі: між якорями рамка тягнеться, і кадр тягнеться
   разом із нею. Пропорція знімка на самих якорях ціла — 0.6664 у всіх трьох
   наборах, тобто рівно 1706/2560 вихідного файла. */
.quote__media { position: absolute; inset: 0; overflow: hidden; }

.quote__media img {
  position: absolute;
  inset-block-start: var(--photo-y);
  inset-inline-start: var(--photo-x);
  width: var(--photo-w);
  height: var(--photo-h);
  max-width: none;
  /* COVER ТУТ НЕ СКАСОВУЄ ЧОТИРИ ЧИСЛА — ВІН ЇХ РЯТУЄ МІЖ ЯКОРЯМИ.
     Чотири відсотки задають рамку знімка від рамки кадру по КОЖНІЙ осі
     окремо. На самому якорі це те, що треба: 137.99 % × 820 і 206.07 % × 824
     дають коробку 1131×1698, тобто рівно 0.6664 — пропорцію файла. Але висоту
     кадру обмежує стеля (`--quote-cap` і `100dvh − шапка`), і щойно вона
     спрацьовує, кадр перестає бути пропорцією якоря: на 1024 він 1014×824
     замість 1014×1019. Дві осі рахуються від різних чисел — і знімок
     розтягується: пропорція коробки знімка 0.6664 на 375 і 820, 0.8322 на
     1024, 1.3647 на 768. Це і є «сплющена засновниця»; вона була не
     обрізана, а стиснута по вертикалі в 1.25 і в 2.05 раза.

     `cover` вписує файл у ту саму коробку без спотворення. На якорях коробка
     вже має пропорцію файла, тому там правило не міняє НІЧОГО (0.6664
     проти 0.6664) — воно працює рівно в проміжних ширинах, і замість
     розтягування дає обрізку, якої в кадрі і так не видно. */
  object-fit: cover;
  /* ЦЕНТРОМ, А НЕ ВЕРХОМ. Знімків три, кожен кадрований під свій якір, тож
     надлишок зʼявляється тільки на ширинах ПОЗА якорями — і він
     ВЕРТИКАЛЬНИЙ:
     на 3440 файл 1920×980 розтягується до 3440×1755 у коробку 3440×1130,
     тобто зайвих 625 px по висоті.

     4 % — не смак, а межа, порахована з кадру. Обличчя в файлі займає
     приблизно 2…63 % висоти; у масштабі 3440 це рядки 36…1110 при вікні
     1130. Щоб воно лишалось цілим, зсув має бути НЕ БІЛЬШИЙ за 36 px, тобто
     не більший за 5.7 % надлишку. Центр (50 %) дає 312 і зрізає маківку —
     саме це видно на знімку; нуль лишає обличчя цілим, але притискає його до
     верхнього краю. 4 % — 25 px на 3440 і 7 px на 2560.

     ЩО ЦИМ НЕ ЛІКУЄТЬСЯ: на 3440 файл шириною 1920 розтягується в 1.79 раза,
     і портрет займає весь кадр. Це не обрізка, а брак вихідного розміру —
     закривається файлом під ультраширокі екрани, а не правилом у CSS. */
  object-position: 50% 4%;
}

/* КРОПУ ТУТ НЕМАЄ, бо його робить джерело: знімків три, по одному на якір,
   уже кадровані (1920×980, 820×824, 375×500), і вибір робить `<picture>`.
   Файл дорівнює кадру — отже рамка 100 % і нуль зсуву; `object-fit: cover`
   працює лише в проміжних ширинах, де кадр відходить від пропорції якоря. */
.quote {
  --photo-w: 100%;
  --photo-h: 100%;
  --photo-x: 0;
  --photo-y: 0;
}

/* Рамка знімка їде за пропорцією свого кадру: числа десктопного набору
   вмикаються там само, де десктопна пропорція, — інакше в смузі 1140…1919
   кадр був би десктопний, а кроп у ньому планшетний. */
/* ⚠️ Розходження з каноном: у макеті підпис білий, тут
   чорний. Під написом кадр світлістю 210, тобто білий дає 1.51:1 проти 4.5 за
   WCAG 2.2 AA, а чорний — 10.50:1. Колір названий явно, а не успадкований від
   секції. */
.quote__brand {
  color: var(--ink);
  position: absolute;
  inset-block-start: var(--s-24);
  inset-inline-start: var(--gutter);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
}

@media (min-width: 768px)  { .quote__brand { inset-block-start: var(--s-32); } }
@media (min-width: 1024px) { .quote__brand { inset-block-start: var(--s-48); } }

.quote__sign {
  position: absolute;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  translate: -50% -50%;
  font-size: var(--fs-sign);
  line-height: var(--lh-sign);
  white-space: nowrap;
}

/* Підпису засновниці немає рівно там, де його немає в каноні: на планшетному
. На мобільному й десктопному якорях він є. */
@media (min-width: 768px)  { .quote__sign { display: none; } }
@media (min-width: 1024px) { .quote__sign { display: block; } }

/* Цитата з кнопкою — один блок, прибитий до низу кадру: у каноні низ кнопки
   стоїть на 48 · 32 · 24 від низу секції на трьох якорях, а сама цитата
   висить над нею з проміжком 26 · 26 · 14. */
.quote__body {
  position: absolute;
  inset-block-end: var(--s-24);
  inset-inline-start: var(--gutter);
  width: 279px;
}

@media (min-width: 768px) {
  .quote__body {
    inset-block-end: var(--s-32);
    width: 383px;
    /* ВІДСТУП ЗЛІВА: ГУТЕР ДО 1024, ДАЛІ РАМПА ДО 374.

       Обидва краї канонічні й між собою НЕ вирівняні: цитата на десктопі
       стоїть на 374, а SEO-колонка (897 по центру полотна) починається на
       510 — між ними 136 px у самому макеті. У смузі, де цитата вже поїхала
       вправо, а SEO ще стоїть по гутеру, вони розходились на 14…90 px, і око
       читає це не як задум, а як недбалість.

       Тому до 1024 цитата стоїть рівно по гутеру — тобто по краю SEO-колонки
       й усього контейнера, — а вище йде до канонічних 374.

       Порогу-медіазапиту тут немає навмисно: нижче 1024 доданок відʼємний, і
       `clamp` сам віддає гутер. Один рядок замість двох правил і ще одного
       зареєстрованого порога. */
    inset-inline-start: clamp(
      var(--gutter),
      calc(var(--gutter) + 326 * (100vw - 1024px) / 896),
      374px);
  }
}

@media (min-width: 1024px) {
  .quote__body { inset-block-end: var(--s-48); }
}


.quote__text {
  margin-block-end: 14px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

@media (min-width: 768px) {
  .quote__text {
    margin-block-end: 26px;
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
  }
}

/* ==========================================================================

   Заголовок, згорнутий текст і «Більше». Ширина колонки: на десктопі 897 і по
   центру (вузол стоїть на x 510..1407 при центрі полотна 960), на планшеті й
   мобільному — увесь контейнер.

   Кількість видимих рядків у згорнутому стані взята з висоти вузла тексту:
   80/20 = 4 на десктопі, 100/20 = 5 на планшеті, 160/16 = 10 на мобільному.
   Це не «приблизно абзац» — це три різні числа, і кожне зчитане.
   ========================================================================== */
/* 897 — ДЕСКТОПНЕ ЧИСЛО, і вмикається воно разом із десктопною композицією
   (1140). Без порога стеля робила б колонку вужчою за контейнер уже на 991 і
   відводила б її від краю шапки на 19…35 px, хоча на планшеті канон дає SEO
   на ВЕСЬ контейнер. */
.seo { width: 100%; }

@media (min-width: 1024px) {
  .seo { max-width: 897px; margin-inline: auto; }
}

.seo__title {
  margin-block: 0 var(--s-16);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
}

@media (min-width: 768px) {
  .seo__title {
    margin-block-end: var(--s-24);
    font-size: var(--fs-h3);
    line-height: var(--lh-h3);
  }
}

.seo__text {
  margin-block-end: var(--s-16);
  font-size: var(--fs-xs);
  line-height: var(--lh-xs);
}

@media (min-width: 768px) {
  .seo__text { font-size: var(--fs-body); line-height: var(--lh-body); }
}

/* Обрізка вмикається ДВИГУНОМ, а не стилем. Без JS текст лишається повним, і
   це не поблажка, а єдиний чесний варіант: кнопка без двигуна не розкриє
   нічого, і людина без скриптів дивилась би на обрізаний текст без способу
   його дочитати. Пошуковик бачить повний текст в обох випадках — він у DOM. */
.clamp.is-clamped .seo__text,
.quote__body.is-clamped .quote__text {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: var(--clamp-lines);
  line-clamp: var(--clamp-lines);
  overflow: hidden;
}

/* Кнопку показує двигун: без нього вона не робить нічого, а неробоча кнопка
   гірша за відсутню. Він же її ховає, якщо текст і так уміщається — «Більше»
   під текстом, що не обрізаний, читається як зламане. */
.clamp__btn[hidden] { display: none; }

/* Коробка кнопки 24 на всіх трьох якорях — так у каноні (84×24
   там іконка 20, а не 24. Без цього мобільний блок виходив би на 4 нижчим. */
.clamp__btn { min-height: var(--icon); }

.clamp__btn[aria-expanded="false"] .clamp__label--less,
.clamp__btn[aria-expanded="true"]  .clamp__label--more { display: none; }

/* Згорнуто — знак ВНИЗ, розгорнуто — ВГОРУ. Джерело не назва вузла, а його
   ПОВОРОТ: у кнопці SEO знак «arrow up round top bottom» лежить
   із `rotate-180`, тобто дивиться вниз. Два попередні прочитання цього місця
   були зроблені з назви шару («arrow up» → вгору) і обидва були хибні —
   пастка №9 у своєму чистому вигляді.
   Той самий знак у посиланні-переході стоїть на `rotate-90` (вбік):
    «Перейти до колекції», «Більше» під цитатою. */
.clamp__icon { rotate: 180deg; }
.clamp__btn[aria-expanded="true"] .clamp__icon { rotate: 0deg; }

@media (prefers-reduced-motion: no-preference) {
  .clamp__icon { transition: rotate var(--dur-fast) var(--ease); }
}

.seo .clamp { --clamp-lines: 10; }
@media (min-width: 768px)  { .seo .clamp { --clamp-lines: 5; } }
@media (min-width: 1024px) { .seo .clamp { --clamp-lines: 4; } }

/* Цитата: 6 рядків по 24 на D і T (144), 7 по 20 на M (140).

   Обрізка тут ПОСТІЙНА, а не станом двигуна: кнопка під цитатою веде на
   сторінку «Про нас», а не розкриває абзац (рішення замовника). Тому
   й трьох крапок наприкінці не додає JS — вони частина канонічного тексту
   вузла, у якого назва так і закінчується. */
.quote__body { --clamp-lines: 7; }
@media (min-width: 768px) { .quote__body { --clamp-lines: 6; } }

/* Посилання успадковує білий колір кадру: у каноні текст кнопки — `white`
   як «кнопки немає». */
.quote__more { color: inherit; }

/* Іконка «Більше» на мобільному дрібніша — 20 проти 24 (проти
   ). Це різні вузли з різними розмірами, а не один масштабований. */
/* ЗНАК 20 МАЄ ВЛАСНУ ТОВЩИНУ — 1.5, а не 1.4. Це не інтерполяція: у каноні це
   два різні вузли одного знака, і геометрія в них пропорційна (шлях 20-ки —
   рівно 0.8333 від 24-ки), а обведення НІ: дає 1.5 при полі 20
   інтерфейсні знаки 24 — 1.4. Ми лишаємо один файл і одну геометрію, а
   товщину називаємо в одиницях viewBox: 1.8 × 20/24 = 1.5 на екрані.
   Без цього рядка знак на мобільному малювався обведенням 1.17 — тоншим за
   будь-що в наборі. Ловить це `.qa/check-icon-scale.js`. */
.seo .link__icon { width: 20px; height: 20px; stroke-width: 1.8; }
@media (min-width: 768px) {
  .seo .link__icon { width: var(--icon); height: var(--icon); stroke-width: 1.4; }
}

/* ==========================================================================
   ГАРДРЕЙЛИ: понад десктопним якорем росте тільки порожнеча.

   Вимога приймання Г4 дослівна: «на guardrail-ширинах ширина колонки й висота
   документа НЕ ростуть». Контейнер її вже виконував (капить на 1824 + гутери),
   а три речі — ні, і це замір, а не побоювання:

     ширина картка стрічки половина пари висота документа
       1920 432 960×980 7 889
       2048 464 1024×1045 8 306
       2560 592 1280×1307 9 976

   Повноекранні кадри задані ПРОПОРЦІЄЮ, тож на ширшому екрані ростуть і
   вгору: на 2560 кадр 1307 заввишки при канонних 980. Стрічка ж ділить свою
   ширину на видимі картки, і ширша стрічка дає ширшу картку.

   Тому вище якоря кадр капить ВИСОТУ (пропорція лишається, зайве по боках
   ріже `object-fit: cover` — композиція від цього не втрачається, бо ріжеться
   не верх), а стрічка капить ШИРИНУ тим самим числом, що й контейнер. Числа
   з якоря, тобто на самому 1920 не змінюється нічого: правило починає діяти
   рівно там, де починається зайве.
   ========================================================================== */
@media (min-width: 1920px) {
  /* ВИСОТА, А НЕ `max-height`. Кадр заданий пропорцією, і пропорція працює в
     обидва боки: обмеживши висоту, ми обмежили б і ШИРИНУ — кадр переставав
     бути повноекранним і ставав коробкою 1920 посеред 2560. Тому пропорція
     тут скасовується, а висота називається числом якоря; ширина лишається
     100 %, зайве по боках ріже `object-fit: cover`. */
  .hero,
  .quote,
  .pair__half {
    aspect-ratio: auto;
    /* НЕ КОНСТАНТА 980. Вище 1920 кадр лишався 980 при ширині 2560 — знімок
       ішов у різку обрізку, а композиція читалась смугою. Замовник: «більше
       1920 фотки краще зробити довшими 100–150 п». Стеля росте рампою до
       +150 і далі стоїть; на самому 1920 це ті самі 980, тобто якір не
       зачеплений. Число живе токеном, а не тут: `--hero-cap`. */
    block-size: var(--hero-cap);
  }

  .slider {
    max-inline-size: calc(var(--container) + var(--gutter) * 2);
    margin-inline: auto;
  }
}

/* ==========================================================================
   КАДР НЕ ВИЩИЙ ЗА ЕКРАН.

   Повноекранні кадри задані пропорцією канону, і в межах смуги вона тягнеться
   разом із шириною. На верхньому кінці смуги це дає кадр, вищий за вікно:
   замір до правки — перший екран 1717 px при вікні 1174 (1680), 1396 при 768
   (1366), 1308 при 800 (1280); підпис під заголовком лежав ЗА краєм екрана, і
   з накатом його не бачив ніхто. Замовник назвав це прямо: «зараз на 1680 він
   дуже високий і текст взагалі не видно».

   Стеля — рівно екран мінус шапка: кадр задуманий як ЕКРАН, і бути вищим за
   нього не має підстав. Пропорція лишається, стеля її лише підрізає, коли
   вікно нижче.

   `inline-size: 100%` тут ОБОВʼЯЗКОВЕ. Пропорція працює в обидва боки, і без
   явної ширини стеля висоти обрізала б заразом ширину — кадр перестав би бути
   повноекранним (та сама пастка, що в гардрейлах).

   Числа якорів від цього не зсуваються: 540 при 375, 838 при 820, 980 при
   1920 — усі менші за екран тих самих якорів. Щоб це лишалось правдою і в
   ранері, замір знімається у вікні 1200, а не 1000: у тисячі 980 + шапка вже
   не вміщались, і стеля різала б канон у самому вимірі.
   ========================================================================== */
.hero {
  inline-size: 100%;
  max-block-size: min(var(--hero-cap), calc(100dvh - var(--header-h)));
}

.quote {
  inline-size: 100%;
  max-block-size: min(var(--quote-cap), calc(100dvh - var(--header-h)));
}

/* ==========================================================================
   КАТАЛОГ. Сторінка (D). Вертикальний ритм цілком живе токенами
   `--cat-*` у tokens.css — тут лишається розкладка й скін.
   ========================================================================== */

/* Шапка сторінки: крихти + заголовок розділу. Відступ від шапки сайту стоїть
   на обгортці, а не на самих крихтах: той самий `.crumbs` живе ще й на стенді
   бібліотеки, де ніякого каталожного ритму немає. */
.page-head { margin-block-start: var(--crumbs-top); }

/* Заголовок розділу —. У каноні він ЗАВЖДИ у два рядки: 108 на
   375, 208 на 820, 288 на 1920 — рівно два інтерліньяжі кожен. Тримає це
   ширина коробки (`--cat-title-w`), а не жорсткий `<br>`: назва розділу
   приходить із CMS, і зашитий перенос розʼїхався б на першій же іншій назві.

   `min()` з контейнером — захист, а не інтерполяція: на 375 коробка канону
   370 при контейнері 343, тобто в макеті заголовок сам виходить за гутер. Це
   питання вузьких якорів, і поки вони не здані, ширина не пускається далі
   контейнера. */
.page-head__title {
  margin-block-start: var(--cat-title-top);
  max-inline-size: min(var(--cat-title-w), 100%);
  color: var(--black-800);
  letter-spacing: var(--ls-h1);
}

/* --------------------------------------------------------------------------
   Панель керування каталогом. У неї ТРИ склади, і це не одна смуга, стиснута
   втричі, — дизайнер малює її окремим вузлом на кожному якорі:

     375 343×24 — «Фільтр» ліворуч, два знаки формату праворуч
                        по краю контейнера (група 64 при x 279 = 343 − 64).
                        Сортування немає ЗОВСІМ — не «не влізло», а немає.
                        формату стоять ПРАВИМ краєм на кінці першої колонки
                        сітки (377 при колонці 378), а не від краю смуги.
     1920 1824×24 — знаки отримують підписи, і група формату йде
                        вже ЛІВИМ краєм від кроку колонки (464 = 432 + 32).

   Через це смуга описана двома переходами, а не одним, і жодне число тут не
   піксель: і 378, і 464 виведені з тієї самої сітки карток, тому панель
   зсунеться рівно тоді, коли зсунеться сітка.

   Поведінка (розкриття фільтра) — окрема задача; тут статика канону.
   -------------------------------------------------------------------------- */
.catbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16) var(--s-24);
  margin-block-start: var(--cat-bar-top);
}

.catbar__group { display: flex; align-items: center; gap: var(--s-24); }
.catbar__group--sort { justify-content: flex-end; }

/* Зазор між двома знаками формату — 16, а не 24: у каноні група 64 = 24 + 16
   + 24 на обох вузьких якорях. Десктопні 24 їй повертає блок 1420 нижче. */
.catbar__group--format { gap: var(--s-16); }

/* СОРТУВАННЯ НА МОБІЛЬНОМУ НЕМАЄ В КАНОНІ, а не «не помістилось»: вузол
   панелі 343×24 містить рівно «Фільтр» і два знаки, і третьої групи в ньому
   немає жодним шаром. */
.catbar__group--sort { display: none; }

/* ПІДПИС У ЗНАКА ФОРМАТУ — теж десктопна композиція: на 820 і 375 канон дає
   групу рівно 64×24, тобто два знаки без тексту. Назва кнопки при цьому
   нікуди не зникає — вона стоїть в `aria-label` розмітки постійно, а який
   формат увімкнений, каже заливка квадратиків (вона ж і на десктопі). */
.catbar__group--format .link__text { display: none; }

/* ПОРЯДОК ТУТ ЗНАЧУЩИЙ, і це не стиль: обидва блоки нижче перекривають
   `display` і `gap`, оголошені щойно, тією самою вагою селектора. Варіант,
   де базові рядки стояли ПІСЛЯ медіа, протримався один прогін: сортування
   зникло разом із десктопом, а зазор знаків формату впав з 24 на 16 на всіх
   ширинах. Спіймав це замір, а не погляд, — на 1920 різниця в 8 px між
   двома підписами не читається. */

/* Планшет: перша колонка сітки тримає обидві ліві групи — «Фільтр» від її
   початку, знаки формату від її кінця. Обидві лежать в одній комірці, і це
   не накладання: одна притиснута до початку, друга до кінця, а між ними
   порожньо.

   ПОЧИНАЄТЬСЯ ЦЯ КОМПОЗИЦІЯ НА 516, А НЕ НА 820, І ЧИСЛО ТУТ ВИМІРЯНЕ.
   Планшетний якір — не межа, а точка звірки («якір ≠ межа», ADAPTATION §2.2);
   дно ж у цієї композиції СВОЄ, і воно є: друга доріжка (`1fr`) не може стати
   вужчою за групу сортування — 223.38 при кеглі 16, і коли доріжка вужча,
   група ЛАМАЄТЬСЯ на два рядки. Це й міряється: прогін по одному пікселю від
   500 до 535 дає висоту панелі 40 включно по 519 і рівно 24 з 520.

   Зазор «формат → сортування» тут доказом НЕ Є: нижче дна він показує рівно
   24, тобто задане `gap`, і виглядає як «ще влізло». Саме на цьому й
   спіткнувся, оголосивши, що дна в панелі немає.

   Що було до цього: на 768 сторінка вже показувала МОБІЛЬНУ панель — без
   сортування взагалі, — хоч місця там лишалось 146 px порожнечі між знаками
   формату й правим краєм. Помітив замовник оком.

   Тим самим прогоном спростовано й твердження, що дна в панелі немає
   там міряли той самий притиснутий до 24 зазор і читали його як «задане
   `gap`». Для десктопної композиції висновок не міняється — її межа 1140
   стоїть високо над будь-яким стиканням. */
@media (min-width: 768px) {
  .catbar {
    display: grid;
    /* ПЕРША КОЛОНКА ПАНЕЛІ = ПЕРША КАРТКА СІТКИ, скільки б колонок у сітці не
       було. Канон 820 дає праву межу знаків формату на 377 при картці 378,
       тобто вирівнювання йде по КАРТЦІ, а не по половині смуги — на 820 це
       одне й те саме число тільки тому, що колонок там дві.  Обидва якорі формула віддає без зміни. */
    grid-template-columns:
      calc((100% - (var(--grid-cols) - 1) * var(--grid-gap)) / var(--grid-cols)) 1fr;
    column-gap: var(--s-24);
  }

  .catbar__group--filter { grid-area: 1 / 1; }
  .catbar__group--format { grid-area: 1 / 1; justify-self: end; }
  .catbar__group--sort   { grid-area: 1 / 2; justify-self: end; display: flex; }
}

/* ДЕСКТОПНА КОМПОЗИЦІЯ ПАНЕЛІ ПОЧИНАЄТЬСЯ НА 1140, А НЕ НА 1420. Фізичного
   дна в неї немає — прогін із примусово ввімкненою десктопною композицією
   від 1500 до 710 з кроком 10 не дав жодного стикання груп (найменший зазор
   «формат → сортування» — 24 на 710, і це задане `gap`, а не залишок). Тобто
   межу вибирає не вміст, а ідентичність: 1140 — виміряне дно десктопної
   композиції шапки й підвала, і панель із крихтами належать тому
   самому «обличчю» сторінки (ADAPTATION §2.2).

   Що було до цього: у смузі 1140…1419 сторінка діставалась із десктопною
   шапкою й десктопним підвалом, але з планшетною панеллю без підписів і з
   трьома ланками крихт замість чотирьох. Порогом стояло 1420 — число,
   позичене в кількості колонок сітки, у якої дно СВОЄ й виміряне. Той самий
   клас, що дав плитці «Переглядали» стелю 306 замість 200.

   Крок колонки в цій композиції теж іде від `--grid-cols`: канон 1920 ставить
   ліву межу групи формату на 464 = 432 + 32, тобто рівно на крок колонки. */
@media (min-width: 1024px) {
  .catbar {
    grid-template-columns:
      calc((100% + var(--grid-gap)) / var(--grid-cols)) 1fr auto;
    /* НУЛЬ, А НЕ 24. Крок колонки вже містить проміжок сітки, тому доріжка
       закінчується рівно там, де починається друга картка; зайві 24 зверху
       Frame 657 при x 464 у групі, що починається на 48). Помилка жила на
       самому десктопному якорі й не ловилась нічим: із каноном у цього
       компонента звіряється ШИРИНА панелі, а не позиції груп усередині. */
    column-gap: 0;
  }

  .catbar__group--format { grid-area: 1 / 2; justify-self: start; gap: var(--s-24); }
  .catbar__group--sort   { grid-area: 1 / 3; }
  .catbar__group--format .link__text { display: inline; }
}

/* Кнопка панелі. Активна — `black/900` з підкресленням, неактивна —
   `black/400`; обидва тони зняті властивістю з вузлів панелі, а не з легенди
   табів: там неактивний на сходинку темніший (`black/600`). Рух підкреслення
   спільний із рештою проєкту — з середини. */
.catbar__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  font-weight: var(--fw-regular);
  color: var(--ctl-off);
  transition: color var(--dur-fast) var(--ease);
}

.catbar__btn[aria-pressed="true"],
.catbar__btn[aria-current="true"] { color: var(--ink); }

.catbar__btn[aria-pressed="true"] .link__text,
.catbar__btn[aria-current="true"] .link__text {
  background-size: 100% var(--hairline);
}

/* «Фільтр» стоїть у каноні `black/900` і БЕЗ підкреслення — це не таб, а
   кнопка розкриття панелі. Тому тон названий окремо, а не отриманий станом. */
.catbar__btn--plain { color: var(--ink); }

.catbar__icon { flex: none; width: var(--icon); height: var(--icon); }

/* ЗАЛИВКА ЗНАКА ФОРМАТУ ЙДЕ ЗІ СТАНОМ, А НЕ З РОЗМІТКИ. У каноні це видно на
   двох артбордах того самого компонента:

       квадрати — БЕЗ заливки, обведення #a0a09f
       смуга — заливка #232221, обведення #232221

   Тобто вибраний формат фарбує свій знак, невибраний лишає його контурним.
    Тон обидва беруть від `currentColor`,
   тому вони міняються разом із написом, а не окремим правилом. */
.catbar__icon--format rect { fill: var(--surface); stroke: currentColor; }

.catbar__btn[aria-current="true"] .catbar__icon--format rect { fill: currentColor; }

/* Стрілка напрямку сортування — 180°, тобто «вниз»: у каноні вона стоїть
   ПЕРЕД написом. */
.catbar__icon--down { rotate: 180deg; }

/* Лінійка під панеллю —, 1824×1 по контейнеру. */
.catbar__rule {
  block-size: var(--hairline);
  margin-block-start: var(--cat-bar-rule);
  border: 0;
  background: var(--line);
}

.catalog__grid { margin-block-start: var(--cat-rule-grid); }

/* --------------------------------------------------------------------------
   (M). Композицій тут дві, а не три: планшет і десктоп дають той самий вузол
   550×88, мобільний — 186×56.

   ПЕРЕМИКАЄ ЇХ 590, А НЕ ПЛАНШЕТНИЙ ЯКІР. Число виміряне, і міряти тут є що
   рівно тому, що таблетка ФІКСОВАНА: 550 не тече, тож вона або лізе в екран
   разом із гутерами, або ні. Прогін по пікселю дає перше «лізе» на 590 —
   вільного місця з боку 20 при гутері 19.86; на 589 його 19.5 при 19.84.
    Помітив замовник оком, і це вже третій блок каталогу з тим
   самим класом дефекту (панель — 520, тепер таблетка — 590): межа стояла на
   якорі, хоч власне дно в блока є і воно нижче.

   Числа з вузлів, і обидві суми сходяться рівно — тому ширина задана
   каноном, а не вмістом (так її й звіряє canon-geometry):
       M 186 = 8 + [82 чіп] + 16 + [64 знаки] + 16 висота 56
       D 550 = 16 + [176 чіп] + 24 + [310 формати] + 24 висота 88

   ДВІ РОЗБІЖНОСТІ КАНОНУ САМОГО З СОБОЮ, обидві розвʼязані на користь
   десктопного вузла: заливка мобільної таблетки `#121110` проти
   `#232221` на T і D — на сайті один основний чорний; кегль підпису «Фільтр»
   15 на M проти 16 на T і D — 16 названий у стилях канону (`btn-16`), а 15 у
   наборі не оголошений ніде.

   ВІДСТАНЬ ВІД НИЗУ ЕКРАНА — 24, з обох ПОВНИХ артбордів (1080: таблетка
   968…1056). Мобільний артборд обрізаний по 654 і дає 14, але 654 не є
   висотою жодного екрана: це край КАДРУ, а не край вікна, і числом канону
   він не є.
   -------------------------------------------------------------------------- */
.filterbar {
  position: fixed;
  inset-inline: 0;
  inset-block-end: var(--s-24);
  z-index: 15;
  display: flex;
  justify-content: center;
  /* Панель лежить на всю ширину вікна тільки щоб центрувати таблетку —
     кліки ловить сама таблетка, а не порожнеча по боках. */
  pointer-events: none;
  visibility: hidden;
  opacity: 0;
}

.filterbar.is-visible { visibility: visible; opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .filterbar {
    translate: 0 var(--s-8);
    transition:
      opacity var(--dur-base) var(--ease),
      translate var(--dur-base) var(--ease);
  }

  .filterbar.is-visible { translate: 0 0; }
}

/* Скруглення в таблетки НЕМАЄ: вузол дає прямий прямокутник.
   Скруглений тут тільки білий чіп усередині (radius 100). */
.filterbar__pill {
  pointer-events: auto;
  display: flex;
  align-items: center;
  gap: var(--s-16);

  /* ШИРИНА СМУГИ — ВМІСТ, А НЕ ЧИСЛО. Канон дає їй 186 без лічильника
     приріст плашки всередині. Записане число тримало б лише один із двох
     станів, і двозначний лічильник тіснився б у плашці, розрахованій на
     підпис без нього. */
  block-size: 56px;
  padding-inline: var(--s-8) var(--s-16);
  background: var(--ink);
}

/* Плашка теж хапає ширину за підписом, але не вужчає за канонічні 82: саме
   підпис «Фільтр» стоїть у ній по центру, а не притиснутий. Внутрішній
   окраєць 16 із того ж вузла: 82 = 50 підпис + 2 × 16. */
.filterbar__filter {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  min-inline-size: 82px;
  padding-inline: var(--s-16);
  block-size: 40px;
  border-radius: 100px;
  background: var(--surface);
  color: var(--ink);
  font-weight: var(--fw-regular);
}

.filterbar__formats {
  display: flex;
  align-items: center;
  gap: var(--s-16);
  margin-inline-start: auto;
}

.filterbar__format {
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  color: var(--ink-muted);
}

.filterbar__format[aria-current="true"] { color: var(--btn-ink); }

/* СТАН «ФІЛЬТР ВІДКРИТИЙ» — ДОПИСАНИЙ, А НЕ ЗНЯТИЙ З КАНОНУ: макет малює
   таблетку тільки при закритій панелі, тож стану «натиснуто» в ньому немає
   жодним вузлом. Тон і підпис назвав замовник; форма плашки лишається та
   сама, міняється тільки заливка. */
.filterbar__filter[aria-expanded="true"] {
  background: var(--filter-open-bg);
  color: var(--btn-ink);
}

/* Знак формату на ЧОРНОМУ: незалитий — обведення приглушеним, залитий —
   білим. У світлій панелі порожній знак заливається кольором полотна
   (`--surface`), і тут це дало б білий квадрат замість порожнього. */
.filterbar .catbar__icon--format rect { fill: none; stroke: currentColor; }

.filterbar__format[aria-current="true"] .catbar__icon--format rect {
  fill: currentColor;
}

/* Підпис знака — тільки в композиції 820 і вище: мобільний вузол дає рівно
   два знаки 24 із зазором 16 і жодного тексту. */
.filterbar .link__text { display: none; }

@media (min-width: 768px) {
  .filterbar__pill {
    gap: var(--s-24);
    inline-size: 550px;
    block-size: 88px;
    padding-inline: var(--s-16) var(--s-24);
  }

  .filterbar__filter { inline-size: 176px; block-size: 56px; }

  .filterbar__formats { gap: var(--s-24); }

  .filterbar .link__text { display: inline; }

  /* Підкреслення під активним підписом — `Vector 31`: 98 при підписі 99,
     тобто рівно ширина тексту, а не всієї кнопки. */
  .filterbar__format[aria-current="true"] .link__text {
    border-block-end: var(--hairline) solid currentColor;
  }
}


/* --------------------------------------------------------------------------
   ФІЛЬТР КАТАЛОГУ — D · T · M.
   Числа й рампи — у токенах `--cf-*`; тут лише те, ЯК вони складаються.

   Оболонка міняє рід, а не тільки розміри: на мобільному це лист на весь
   екран, від планшета — панель зліва під шапкою. Модальною панель на
   десктопі НЕ є: каталог поруч лишається видимим і клікабельним, тому ні
   замка прокрутки, ні пастки фокуса тут немає.

   ⚠️ Підпис «Фільтрувати за параметрами» і «Очистити все» набрані
   `--black-400` — 2.62:1 на полотні при вимозі 4.5 (WCAG 2.2 AA). Це
   свідомий виняток за рішенням замовника, і контраст-гейт його не спіймає:
   пари `--black-400 × --surface` у `a11y.contrast_pairs` немає.
   -------------------------------------------------------------------------- */
.cfilter {
  position: fixed;
  inset-block: 0;
  inset-inline-start: 0;
  z-index: 20;
  inline-size: var(--cf-w);
  max-inline-size: 100%;
}

.cfilter[hidden] { display: none; }

.cfilter__panel {
  position: relative;
  display: flex;
  flex-direction: column;
  block-size: 100%;
  background: var(--surface);
}

/* НА ЛИСТІ ЦЕ СМУГА ЗАВВИШКИ З ШАПКУ, яку лист накриває, і обидва канонічні
   положення падають із неї самі: підпис і хрестик стоять по центру смуги.
   У панелі смуги немає — там підпис просто відступає від верху. */
.cfilter__top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
  flex: none;
  block-size: var(--cf-top-h);
  padding-inline: var(--cf-pad);
}

.cfilter__caption {
  color: var(--black-400);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

.cfilter__close {
  flex: none;
  inline-size: var(--s-24);
  block-size: var(--s-24);
  color: var(--ink);
}

.cfilter__cross {
  display: block;
  inline-size: var(--s-24);
  block-size: var(--s-24);
}

/* ВІДСТУП ДО ПЕРШОЇ СЕКЦІЇ ЛІЧИТЬСЯ ВІД ВЕРХУ ПАНЕЛІ, а не від підпису: у
   каноні названі саме дві відстані від верху — підпис і перша секція, — і
   між ними лежить рядок підпису. Записати тут третє число означало б
   тримати ту саму відстань двома джерелами. */
.cfilter__body {
  flex: 1 1 auto;
  min-block-size: 0;
  margin-block-start: calc(var(--cf-body-top) - var(--cf-top-h));

  /* Дійшовши до свого краю, панель НЕ передає прокрутку сторінці: інакше
     останній оберт колеса в панелі зсуває каталог під нею. */
  overscroll-behavior: contain;
  padding-inline: var(--cf-pad);

  /* Хвіст прокрутки: останній заголовок не має впиратись у смугу дій. Число
     те саме, що тримає ритм секцій, — власного канон не називає, бо в макеті
     панель узагалі не прокручена до кінця. */
  padding-block-end: var(--cf-sec-gap);

  overflow-y: auto;

  /* ⚠️ СИСТЕМНА СМУГА ВИМКНЕНА ЦІЛКОМ, як у списку розміру картки: у Chrome
     `scrollbar-width: thin` вимикає власне оформлення через
     `:-webkit-scrollbar`, тож підфарбувати системну й лишити її розмір
     одночасно не можна. Смужку малює `.cfilter__thumb`. */
  scrollbar-width: none;
}

.cfilter__body::-webkit-scrollbar { width: 0; height: 0; }

.cfilter__thumb {
  position: absolute;
  inset-inline-end: var(--cf-thumb-x);
  inset-block-start: calc(var(--cf-body-top) + var(--cf-thumb-y, 0px));
  inline-size: var(--thumb-w);
  block-size: var(--thumb-h);
  border-radius: var(--thumb-r);
  background: var(--ink);
}

.cfilter__thumb[hidden] { display: none; }

/* РИТМ СЕКЦІЙ. На 46 від верху заголовка стоїть або лінія (згорнуто), або
   список (розкрито) — заголовок 22 плюс `--cf-sec-gap`. Підбивка сусіда на
   волосину менша за відступ, і саме тому крок згорнутої секції виходить
   рівно 70, а не 71: лінія займає свій піксель. */
.cfilter__sec + .cfilter__sec {
  margin-block-start: var(--cf-sec-gap);
  padding-block-start: calc(var(--cf-sec-gap) - var(--hairline));
  border-block-start: var(--hairline) solid var(--line);
}

.cfilter__sum {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-16);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
  cursor: pointer;
  list-style: none;
}

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

/* Знак у каноні дивиться ВГОРУ в розкритій секції — тобто малює дію
   «згорнути». Перевернутий він у згорнутій, а не навпаки. */
.cfilter__icon {
  flex: none;
  inline-size: 22px;
  block-size: 22px;
}

.cfilter__sec:not([open]) .cfilter__icon { rotate: 180deg; }

@media (prefers-reduced-motion: no-preference) {
  .cfilter__icon { transition: rotate var(--dur-fast) var(--ease); }
}

.cfilter__wrap { padding-block-start: calc(var(--cf-list-top) - var(--lh-body) - 2px); }

.cfilter__list {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--cf-item-gap);
}

/* ПУНКТ ХАПАЄ ШИРИНУ ЗА ВМІСТОМ, І ВОЛОСИНА ЙДЕ ЗА НИМ. У каноні коробки
   сусідніх пунктів різні — 203, 257, 196, 254, — тобто підкреслення в
   кожного своє. Рівна лінія по колонці була б іншим малюнком. */
/* ⚠️ ЗАЗОРИ ТУТ НЕ СПІЛЬНІ, І ЦЕ НЕ СТИЛЬ. Один `gap` на весь ряд лягає між
   УСІМА трьома дітьми, тобто додається ще й перед лічильником: канонічні 80
   ставали 100, і кожен пункт виходив на 19–20 ширшим за макет. Волосина йде
   за шириною пункту, тож помилка була видима, але не спіймана — ширину
   пункту чекер тоді не звіряв. Кожен зазор названий власним відступом. */
.cfilter__opt {
  display: inline-flex;
  align-items: flex-start;
  padding-block-end: var(--s-16);
  border-block-end: var(--hairline) solid var(--line);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  cursor: pointer;
}

/* Останній пункт списку без волосини — так у каноні. */
.cfilter__item:last-child .cfilter__opt { border-block-end-color: transparent; }

.cfilter__name { margin-inline-start: var(--cf-box-gap); }

.cfilter__mark {
  flex: none;
  inline-size: var(--cf-box);
  block-size: var(--cf-box);
  margin-block-start: 2px;
  border: var(--hairline) solid var(--black-200);
}

/* ОБРАНИЙ ПУНКТ — ЗАЛИТЕ КОЛО, А НЕ ГАЛОЧКА У КВАДРАТІ.
   Форма міняється разом зі станом, тому це не «оформлення прапорця», а два
   різні знаки. */
/* Радіо має КОЛО і в порожньому стані: квадрат сказав би «можна кілька»,
   а вибір тут один. Залите коло обраного лишається спільним зі списками. */
.cfilter__mark--one { border-radius: 100px; }

.cfilter__box:checked + .cfilter__mark {
  border-color: transparent;
  border-radius: 100px;
  background: var(--ink);
}

.cfilter__count {
  margin-inline-start: var(--cf-count-gap);
  opacity: 0.4;
}

.cfilter__box:checked ~ .cfilter__name { font-weight: var(--fw-medium); }

/* НАВЕДЕННЯ ПІДНІМАЄ І ПІДПИС, І ЛІЧИЛЬНИК. У каноні це видно на одному
   пункті — «Адаптивний одяг»: прапорець порожній, підпис Medium, лічильник
   без прозорості. Інше прочитання (це «поточний розділ») канон не
   підтверджує: крихти цього артборда закінчуються на «Для неї». */
@media (hover: hover) {
  .cfilter__opt:hover .cfilter__name { font-weight: var(--fw-medium); }
  .cfilter__opt:hover .cfilter__count { opacity: 1; }
}

.cfilter__box:focus-visible ~ .cfilter__name { text-decoration: underline; }

/* ---------------------------------------------------------------- ціна --- */

.cfilter__price { position: relative; }

.cfilter__fields {
  display: grid;
  grid-template-columns: 1fr 1fr;
}

.cfilter__field {
  display: flex;
  align-items: center;

  /* Клітинка сітки не звужується нижче свого вмісту, поки їй цього не
     дозволити, а текстове поле всередині має власну внутрішню ширину
     близько 180. Без цього рядка пара полів вилазила за панель — і жодне
     число цього не показало, бо всі виміряні коробки лишались правильними;
     видно було тільки на знімку. */
  min-inline-size: 0;

  block-size: var(--cf-field-h);
  padding-inline: var(--cf-field-pad);
  border: var(--hairline) solid var(--cf-field-line);
}

/* Межа між полями СПІЛЬНА — у каноні праве поле не має власної лівої
   рамки. Дві рамки поруч дали б лінію 2px. */
.cfilter__field + .cfilter__field { border-inline-start: 0; }

/* ШИРИНА ПОЛЯ — В ЗНАКАХ, а не в пікселях: текстове поле не вміє хапати
   ширину за вмістом жодним засобом CSS, а фіксовані 45 з канону обрізали б
   пʼятизначну ціну. Пʼять знаків — рівно найдовше значення канону («3 945»). */
.cfilter__num {
  flex: none;
  inline-size: 5ch;
  min-inline-size: 0;
  border: 0;
  background: none;
  color: var(--ink);
  font-size: var(--fs-18);
  line-height: var(--lh-18);
}

/* ФОКУС ПОЛЯ ЦІНИ — БЕЗ РАМКИ. Рішення власника 11.09.2026 (D551):
   глобальний контур 2 px лягав на вузьке поле всередині клітинки й
   читався окремою жирною рамкою поверх власної рамки поля. У полі фокус
   видно кареткою набору, а межу ціни з клавіатури ведуть повзунки — у них
   індикатор лишається (W-149). */
.cfilter__num:focus-visible { outline: none; }

/* Проміжок «значення → одиниця» — 34. Число справжнє, а не округлення на
   око: обидва поля канону дають те саме (34 і 35), тоді як самі відступи
   значень у них розходяться (16 і 8) — тобто ЗАЗОР дизайнер тримав, а
   ліву межу ставив рукою. */
.cfilter__unit {
  flex: none;
  margin-inline-start: var(--cf-unit-gap);
  opacity: 0.4;
  font-size: var(--fs-body);
  line-height: var(--lh-18);
}

/* СМУГА ДІАПАЗОНУ ЛЕЖИТЬ НА НИЖНІЙ МЕЖІ ПАРИ ПОЛІВ, а не під нею: доріжка
   канону стоїть на 55 від верху поля висотою 56, повзунки 16×16 сідлають
   цей край. Окремої сірої рейки в каноні немає жодним вузлом — намальовано
   тільки обраний проміжок, і саме він тут і є. */
.cfilter__range {
  position: absolute;
  inset-block-end: 0;
  inset-inline: var(--cf-field-pad);
  block-size: 0;
}

.cfilter__fill {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: var(--cf-from, 0%);
  inline-size: calc(var(--cf-to, 76%) - var(--cf-from, 0%));
  block-size: var(--hairline);
  background: var(--ink);
}

/* ⚠️ ТРИ РЕЧІ, ЯКИХ НЕ ВИДНО НА МИШІ.
   `touch-action: none` — без нього палець на повзунку ПРОКРУЧУЄ панель, а не
   тягне ручку: браузер віддає жест прокрутці, доки правило не скаже інакше,
   і на миші цього не побачити взагалі.
   Ціль дотику 44 при знаку 16 — вимога 2.5.8 AA (мінімум 24) і здоровий
   палець; сам знак лишається канонічним, росте лише невидима зона.
   `touch-action` не діє на `<span>` без власного шару подій, тому ручка ще
   й фокусована: інакше діапазон ціни не мав би клавіатури зовсім. */
.cfilter__knob {
  position: absolute;
  inset-block-start: 0;
  inline-size: var(--cf-knob);
  block-size: var(--cf-knob);
  border: var(--hairline) solid var(--ink);
  border-radius: 100px;
  background: var(--surface);
  translate: -50% -50%;
  touch-action: none;
  cursor: grab;
}

.cfilter__knob::before {
  content: "";
  position: absolute;
  inset: calc((var(--cf-knob) - var(--tap-min)) / 2);
}

.cfilter__knob:active { cursor: grabbing; }

.cfilter__knob--min { inset-inline-start: var(--cf-from, 0%); }

/* Правий повзунок ЗАЛИТИЙ, лівий порожній — два різні вузли канону
, а не один стан. */
.cfilter__knob--max {
  inset-inline-start: var(--cf-to, 76%);
  background: var(--ink);
}

/* ----------------------------------------------------------- смуга дій --- */

/* Зʼявляється ЛИШЕ коли щось обрано — це підпис дизайнера в макеті, а не
   здогад верстки. Порожній фільтр закривають тим самим, чим відкрили. */
.cfilter__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex: none;
  block-size: var(--cf-bar-h);
  padding-inline: var(--cf-pad);
  border-block-start: var(--hairline) solid var(--line);
  background: var(--surface);
}

.cfilter__act[hidden] { display: none; }

.cfilter__act {
  color: var(--ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

.cfilter__act--clear { color: var(--black-400); }

/* НАВЕДЕННЯ — ТА САМА ЛІНІЯ, ЩО В ШАПЦІ Й ПІДВАЛІ, а не третій словник
   станів. Прийом узятий із дослівно: градієнт у тон тексту росте від
   нуля до повної ширини, тобто рухається РОЗМІР ТЛА, а не товщина рамки —
   рамка, що зʼявляється, зсувала б сусідів у смузі.

   Наведення в каноні не намальоване взагалі (макет статичний), тому нового
   тут рівно нуль: беруться вже вжиті в бібліотеці прийом, тривалість і крива.

   `fit-content` — щоб лінія лягла під СЛОВО, а не під усю коробку кнопки:
   на це вже наступали в шапці, де риска тяглася далеко за текст.
   `:focus-visible` поруч із `:hover` — інакше з клавіатури стан не видно
   зовсім, а це те саме 2.4.7, що й у решті інтерактиву. */
.cfilter__act {
  inline-size: fit-content;
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .cfilter__act:hover { background-size: 100% var(--hairline); }
}

.cfilter__act:focus-visible { background-size: 100% var(--hairline); }

/* ⚠️ ПАНЕЛЬ — З ДЕСКТОПНОЇ СТІНИ, А НЕ З ПЛАНШЕТНОЇ. У всій планшетній смузі
   фільтр лишається ЛИСТОМ: панель поруч із сіткою фізично вимагає ширини —
   на 441 панель 337 лишила б 104 px, і картка каталогу (282) за нею не
   вміщається, тобто панель перестає бути накладкою на каталог.

   верстається — це свідоме відхилення за рішенням замовника, а не
   недогляд. Повертається воно двома умовами, названими там же.

   Хрестик є тільки на листі; у панелі закриття робить слово
   «Закрити» в нижній смузі. */
@media (min-width: 1024px) {
  .cfilter { inset-block-start: var(--header-h); }

  /* Хрестик і сортування — риси ЛИСТА, і разом із ним зникають: у панелі
     закриття робить слово «Закрити», а сортування стоїть над сіткою. */
  .cfilter__close { display: none; }
  .cfilter__sec--sort { display: none; }

  .cfilter__top {
    align-items: flex-start;
    block-size: auto;
    padding-block-start: var(--cf-cap-top);
  }

  .cfilter__body {
    margin-block-start:
      calc(var(--cf-body-top) - var(--cf-cap-top) - var(--lh-ui));
  }
}


/* --------------------------------------------------------------------------
   поданням, тому й у розмітці це КЛАС на тій самій сітці, а не другий
   список карток.

   КОЛОНОК ТУТ НЕ ДВІ, А СТІЛЬКИ, СКІЛЬКИ ВМІЩАЄТЬСЯ. Канон:


   Тобто задане число — ширина КАРТКИ (`--card-wide-w`), а блок є наслідком:
   1504 = 2×736 + 32. 
   Знайшов замовник, показавши артборди і.

   `repeat(auto-fit, …)` віддає рахунок колонок рушію, і власного порога цей
   перехід не заводить: друга колонка вмикається там, де дві картки з
   проміжком уперше вміщаються в контейнер (≈1565 при заміреному ходi
   гутера). Це той самий закон, що дав пороги звичайної сітки, тільки
   без числа в `@media`. `min(…, 100%)` — сторожа, а не інтерполяція: без неї
   доріжка, ширша за контейнер, виїхала б за екран.
   -------------------------------------------------------------------------- */
.grid.grid--wide {
  grid-template-columns: repeat(auto-fit, min(var(--card-wide-w), 100%));
  justify-content: center;
}

/* ДРУГА КОЛОНКА ВЕЛИКОГО ФОРМАТУ ВМИКАЄТЬСЯ НА 991 — і це число замовника,
   а не виведене. Виведене дало б 1565: рахунок
   `auto-fit` ставить другу картку туди, де дві намальовані в каноні (708 і
   736) вперше вміщаються поруч. На екрані це читалось так, як і сказав
   замовник: до 1564 у великому форматі стояла ОДНА картка на весь екран, і
   на 1400 вона була вдвічі більша за все, що дизайнер малював.

   Замовник вибрав інший бік розміну: картка зменшується пропорційно, аби
   колонок стало дві раніше. Тому тут не `auto-fit`, а дві рівні доріжки зі
   стелею: ширина картки = (контейнер − проміжок) / 2, поки не впреться в
   намальовану `--card-wide-w`. На 1920 стеля й дає канон — 2×736 + 32 = 1504
   центрований), на 991 — 458.5.

   Розрив на самому порозі є й він законний (§3.9): 712 → 458 — це та сама
   сходинка, що й у звичайної сітки на 935 (458 → 318). Кількість колонок не
   інтерполюється. */
@media (min-width: 1024px) {
  .grid.grid--wide {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    max-inline-size: calc(2 * var(--card-wide-w) + var(--grid-gap));
    margin-inline: auto;
  }
}

/* Скін великої картки — з вузла, не з десктопної картки сітки
   пропорція фото інша (736×1005 проти 432×612), а зазор «назва → ціна» — 8
   замість 6 (назва на 1021, ціна на 1049 при висоті рядка 20). Поля підпису
   тут не потрібні: підпис центрований і вужчий за картку вдвічі. */
/* Пропорція фото у великої картки СВОЯ на кожному якорі, і це не дрейф:
   Одним числом їх не звести, тому пропорція йде сходинками — так само, як у
   картки стрічки вище. Поля картки теж свої: 20 на 375 і 24 з планшета, а
   знак «серце» на 375 тут ПОВНОГО розміру (24), а не зменшений до 20, як у
   сітці, — велика картка на мобільному є картка стрічки, не плитка сітки. */
.grid--wide .card {
  --card-w: 323;
  --card-h: 466;
  --card-pad: var(--s-20);
  --card-like: var(--icon);
  --card-gap-media: var(--s-16);
  --card-body-inline: 0px;
}

@media (min-width: 768px) {
  .grid--wide .card { --card-w: 708; --card-h: 1004; --card-pad: var(--s-24); }
}

@media (min-width: 1024px) {
  .grid--wide .card { --card-w: 736; --card-h: 1005; }
}

.grid--wide .card__body { gap: var(--s-8); }

/* --------------------------------------------------------------------------
   Пагінація — (D і T однакові, 584×56), (M, 316×48).

   Три числа розходяться між якорями і тому записані тут же, а не плоскими px
   десктопної смуги
       коло 48 → 56 зазор стрілки 8 → 16 зазор у ряду 4 → 8
   Обидві сходинки стоять на 820: планшет уже дорівнює десктопу.
   -------------------------------------------------------------------------- */
/* КОЛО НИЖЧЕ МОБІЛЬНОГО ЯКОРЯ МЕНШАЄ РІВНО НАСТІЛЬКИ, ЩОБ РЯД ЛИШИВСЯ РЯДОМ.
   Ряд із чотирьох слотів — це шість кіл (дві стрілки + чотири) і 28 px між
   ними: 2×8 біля стрілок і 3×4 всередині; плюс 32 px гутерів і 4 px запасу
   на субпіксель — звідси 64. На 375 вираз дає 52.5, тобто стеля 48 (канон
: 316 = 48 + 8 + [4×48 + 3×4] + 8 + 48) лишається недоторканою
   і меншає коло тільки там, де малюнка немає: 42.67 на 320.

   Що було до цього: на 320 ряд не вміщався (316 при контейнері 288) і
   переносив стрілку «вперед» у другий рядок — рішення `flex-wrap`, тобто
   аварійне, а не задумане. Помітив замовник оком. `flex-wrap` лишається
   сторожем на випадок ще вужчого екрана, але спрацьовувати йому вже нема на
   чому. */
.pager {
  --pager-size: min(48px, calc((100vw - 64px) / 6));
  --pager-gap: var(--s-8);
  --pager-step: 4px;
  display: flex;
  align-items: center;
  gap: var(--pager-gap);
  /* Коробка обтягує вміст і стоїть по центру полотна — так у каноні: 584 при
     контейнері 1824 і 772, 316 при 343. Блочний `nav` розтягнувся б на весь
     контейнер, і звірка з вузлом показала б +1240 на десктопі — саме це вона
     й показала до цього рядка. */
  inline-size: fit-content;
  /* Нижче наймолодшого якоря коробка вже не вміщається: 316 при контейнері
     288 на 320 px — це та ширина, на якій дизайнер макета не малював. Замість
     виїзду за viewport ряд переноситься; на 375 і вище переносу немає, бо
     316 < 343, і замір із каноном від цього не зсувається. */
  flex-wrap: wrap;
  justify-content: center;
  max-inline-size: 100%;
  margin-block-start: var(--cat-pager-top);
  margin-inline: auto;
}

/* РЯД ПАГІНАЦІЇ ТЕЧЕ, А НЕ ЛАМАЄТЬСЯ. У планшетній смузі видно всі сім
   слотів, і розмір кола рахується з того, скільки місця є: девʼять кіл, два
   зазори групи і шість кроків складаються рівно в 73/7 діаметра, тож
   зворотна формула дає найбільший діаметр, при якому ряд ще стоїть в ОДИН
   рядок. Вісім пікселів запасу — на субпіксель контейнера.

   Канон від цього не зсувається: стеля 56 досягається на 640 і далі стоїть,
   тобто на планшетному якорі 820 ряд рівно 584, як у вузлі.

   ⚠️ ЦЕ Й Є «ОДНА МЕХАНІКА» ЗАМІСТЬ ТРЬОХ ПОРОГІВ. Слоти вмикались по
   одному на 406 · 459 · 513 — числах, порахованих для мобільного кола 48.
   Щойно планшетна смуга почалась із 441, кола стали 56, і ряд із семи слотів
   переставав уміщатись: заміряно два-чотири рядки на всій смузі 441…631.
   ⚠️ Стіна планшета — 768 (`config.json → modes.tablet.from`), тому кола
   лишаються мобільними 48 аж до 767: ряд має БІЛЬШЕ запасу, ніж коли ці
   пороги виводились. Пороги складу після кожного переїзду стіни
   переміряються заново — окремий рядок в `docs/adapt-log/404.md` §8, як
   велить ADAPTATION §3.2.
   Порогів на це не заводиться — тече розмір. 406 лишається: у мобільній
   смузі склад справді міняється, бо канон малює там чотири слоти. */
@media (min-width: 768px) {
  .pager {
    --pager-size: min(56px, calc((100vw - 2 * var(--gutter) - 8px) * 7 / 73));
    --pager-gap: calc(var(--pager-size) * 2 / 7);
    --pager-step: calc(var(--pager-size) / 7);
  }

}

.pager__list {
  display: flex;
  align-items: center;
  gap: var(--pager-step);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Ланка пагінації — коло того самого розміру, що й `.btn-round`, але власним
   правилом: у круглої кнопки інший вміст (знак, не число) і власний токен
   розміру. Обведення `#E7E7E7` — з вузла кола (`Ellipse 74`, r 27.5 при
   коробці 56), а не з ока: 27.5 і є «1 px усередину». */
.pager__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--pager-size);
  block-size: var(--pager-size);
  border: var(--hairline) solid var(--line);
  border-radius: 50%;
  font-weight: var(--fw-regular);
  color: var(--ink);
  background: var(--surface);
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

/* ЗНАК У КОЛІ — 24, І ЦЕ ЧИСЛО ВУЗЛА, А НЕ ОКА. Рамка знака в каноні 24×24
   тільки коло. Стояло 20 при обведенні 1.8: і розмір, і товщина були свої, а
   не проєктні, і масштаб знака дорівнював 0.83 замість одиниці — рівно те, на
   що заведений check-icon-scale.

   Стрілка «наступна» — той самий знак, розвернутий: другого шляху для нього
   в наборі немає, як і в каноні. */
.pager__link svg { width: var(--icon); height: var(--icon); }

/* ПОВОРОТУ В «НАЗАД» НЕМАЄ, І ЦЕ НАСЛІДОК ЗМІНИ ЗНАКА. 
   Окремого файла під кожен бік у наборі немає й бути не повинно. */
.pager__link--next svg { rotate: 180deg; }

/* Поточна сторінка — суцільне коло `#232221` без обведення (той самий
   `Ellipse 74` у другому стані: fill, r 28, тобто рівно коробка). */
.pager__link[aria-current="page"] {
  color: var(--btn-ink);
  background: var(--btn-bg);
  border-color: var(--btn-bg);
}

@media (hover: hover) {
  .pager__link:hover {
    color: var(--btn-ink);
    background: var(--btn-bg);
    border-color: var(--btn-bg);
  }
}

/* Розрив «…» — не посилання, а мітка пропущених сторінок: стану в неї немає,
   але коло Є. Усі сім слотів пагінації в каноні — той самий компонент із
   крапками не виняток. 
   Помітив замовник оком — жоден чекер такого не міряє. */
.pager__gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--pager-size);
  block-size: var(--pager-size);
  border: var(--hairline) solid var(--line);
  border-radius: 50%;
  color: var(--ink);
}

/* СЛОТИ ЗʼЯВЛЯЮТЬСЯ ПО ОДНОМУ, А НЕ ВСІ ТРОЄ РАЗОМ НА 820. Канон дає дві
   чотирислотним: на 768 у ньому було стільки ж кіл, скільки на 440, при
   вдвічі ширшому екрані. Помітив замовник оком.

   Пороги тут не вибрані, а пораховані з того самого ряду: n слотів займають
   2×48 + 2×8 + n×48 + (n−1)×4 = 108 + 52n, тобто 368 · 420 · 472 для пʼяти,
   шести й семи, плюс 4 px запасу на субпіксель. Контейнер у цій смузі
   дорівнює 0.964×екран − 18.5 (гутер тече 16 → 24), звідки 406 · 459 · 513;
   перевірено заміром на кожному порозі й на пікселі нижче.

   Стеля — СІМ, і вона теж не вибрана: сім слотів намальовано в каноні, і
   восьмого кола немає з чого взяти. Ширші екрани дістають не більше кіл, а
   більші (56 з 820) — так у каноні. */
.pager__slot--s5 { display: none; }
.pager__slot--s6 { display: none; }
.pager__slot--s7 { display: none; }

/* Пʼятий слот — єдиний поріг складу, що лишився: канон малює на мобільному
   число пораховане з того самого ряду (108 + 52n при колі 48). Шостий і
   сьомий власних порогів не мають: у планшетній смузі видно всі сім, а
   розмір кола тече. */
@media (min-width: 406px) { .pager__slot--s5 { display: block; } }

/* ⚠️ ПОРЯДОК ТУТ ЗНАЧУЩИЙ. Базове `display: none` слотів стоїть ВИЩЕ за
   блок розмірів, тому показ мусить іти після нього: вага селекторів однакова,
   і вирішує порядок.  */
@media (min-width: 768px) {
  .pager__slot--s6,
  .pager__slot--s7 { display: block; }
}

/* Роздільник на всю ширину полотна — (x 0, ширина 1920).
   Контейнер його не тримає: у каноні лінія йде від краю до краю, і в гутерах
   вона теж є. */
.rule-bleed {
  block-size: var(--hairline);
  margin-block-start: var(--section-gap);
  border: 0;
  background: var(--line);
}

/* --------------------------------------------------------------------------
   «Переглядали» —. Сітка плиток без підписів, і колонок у ній
   чотири на обох вузьких якорях, вісім на десктопі:


   378×319: у групи немає власної рамки, її коробка дорівнює намальованому
   вмісту. Намальовано дві плитки з чотирьох, і 378 — це їх сума (181 + 16 +
   181), а не ширина блока. Що ряд саме чотириколонковий, доводить арифметика
   контейнера: 4×181 + 3×16 = 772 рівно. 

   Пропорція плитки на T і D одна (181/255 = 0.7098 проти 200/282 = 0.7092,
   різниця в третьому знаку), на M своя — тому перемикається вона на 820,
   а кількість колонок на 1420.
   -------------------------------------------------------------------------- */
.viewed { margin-block-start: var(--section-gap); }

/* РОЗМІР ЗАДАЄ ПЛИТКА, А ПРОМІЖОК — ЗАЛИШОК. Це і є корінь дробових чисел:
   доти проміжок був заданий числом, а плитка виходила діленням контейнера —
   тобто збирала на себе КОЖЕН залишок. На 375 контейнер 343 не ділиться на
   4×80 + 3×8 = 344, і плитка ставала 79.75 замість канонічних 80. Дизайнер
   робить навпаки: кладе плитки 80 і розподіляє залишок 23 між трьома
   проміжками (у макеті це видно як 8 · 7 · 8 — ті самі 23/3, округлені до
   цілих пікселів).

   Тепер проміжок обчислюється із плитки, і канонічні числа виходять самі:
       375 (343 − 4×80) / 3 = 7.67 → плитка рівно 80
       820 (772 − 4×181) / 3 = 16 → плитка рівно 181, і це канонічний
                                          проміжок, а не збіг
   Тобто там, де канон ділиться націло, формула дає те саме число, що стояло
   доти; де не ділиться — ціле дістається плитці, а не залишку. */
/* НИЖЧЕ МОБІЛЬНОГО ЯКОРЯ КОЛОНОК ТРИ, А НЕ ЧОТИРИ, І ПЛИТОК ШІСТЬ.
   Чотириколонковий ряд має власне дно, і воно збігається з якорем: канон 375
   кладе плитки 80 у контейнер 343, лишаючи проміжкам рівно 23 px (8·7·8).
   Кожен піксель нижче 375 забирає з цих 23, і на 344 проміжок стає нулем —
   саме це й було видно на 320: чотири плитки 72×98.81 злиплись у суцільну
   стрічку без жодного зазору. Помітив замовник оком.

   Закон той самий, що ставить колонки вгору, тільки прочитаний вниз
   колонка ВИМИКАЄТЬСЯ там, де плитка інакше стала б меншою за найменшу
   намальовану. Три колонки з тим самим канонічним
   проміжком 8 дають на 320 плитку 90.67, на 360 — 104, тобто більшу за
   мобільний канон, а не меншу.

   Плиток при цьому шість, а не вісім: два ряди по три. Вісім у канон 375
   лягли двома рядами по чотири, і «два ряди» тут головніше за «вісім штук» —
   інакше нижче якоря зʼявився б обрубок третього ряду з двох плиток. */
.viewed__grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-8);
  margin: 0;
  margin-block-start: var(--section-title-gap);
  padding: 0;
  list-style: none;
}

@media (min-width: 375px) {
  .viewed__grid {
    --viewed-tile: clamp(80px, calc(80px + 101 * (100vw - 375px) / 445), 181px);
    grid-template-columns: repeat(4, minmax(0, 1fr));
    /* КРОК РЯДУ ТЕЧЕ, А НЕ СТОЇТЬ ПЛОСКО ДО 820. Канон дає 8 на 375 і 16 на
       820; плоска вісімка до 819 і стрибок на 16 — це «плоска пара» з §3.4
       ADAPTATION, і крок колонки поруч уже тік (7.67 → 16), тобто дві осі
       одного проміжку розходились посеред смуги: на 640 ряд 8 при колонці
       12.6. Обидва якорі рампа віддає точно. */
    row-gap: clamp(8px, calc(8px + 8 * (100vw - 375px) / 445), 16px);
    column-gap: calc((100% - 4 * var(--viewed-tile)) / 3);
  }
}

/* Від планшета до дна восьмиколонкової смуги плитка росте 181 → 200: 200 —
   стеля канону, і рівно на ній вмикається восьма колонка. */
@media (min-width: 768px) {
  .viewed__grid {
    /* ТОТОЖНІСТЬ, а не другий `clamp` — той самий лік, що для токенів `:root`
. Пара «база 80…181 + перекривач із підлогою 181» пінила
       плитку на 181 у всій смузі 768…820, а контейнер там 721: чотири плитки
       по 181 = 724 не вміщались, і `column-gap`, який рахується залишком,
       схлопувався в НУЛЬ. Око цього не бачило — знайшов розширений знаменник
       `check-grid-step` («@768 viewed__grid: крок колонки 0»), і лише після
       нього `check-torn-ramps` навчився дивитись поза `:root`. */
    --viewed-tile: calc(clamp(80px, calc(80px + 101 * (100vw - 375px) / 445), 181px)
                        + clamp(181px, calc(181px + 19 * (100vw - 820px) / 83), 200px)
                        - 181px);
    row-gap: var(--grid-gap);   /* на 820 це ті самі 16, тобто рампа нижче
                                   стикується з ним без сходинки */
  }
}

/* ВІСІМ КОЛОНОК ВМИКАЮТЬСЯ НА 904, А НЕ НА 1420 — і поріг тут виведений, а
   не вибраний. Закон той самий, що дав пороги сітки карток: наступна
   колонка вмикається рівно там, де плитка інакше переросла б найбільшу
   намальовану в каноні.

   Що було до цього: у смузі 820…1419 плитка росла без стелі — заміряно 220 на
   991, 264 на 1180, **306 на 1366**, — тобто вчетверо більша за мобільну і в
   півтора раза більша за десктопну. На 1420 вона падала до 159. Порогом
   стояло 1420, узяте від сітки карток, хоч плитка має власне дно.

   Звідки 904: контейнер і проміжок ідуть рампами від 820, і рівняння
   (100vw − 2×гутер − 3×проміжок) / 4 = 200 дає 903.3. Перевірено заміром на
   903 і 904. Поріг зареєстрований у config.json → thresholds.registered. */
/* У восьмиколонковій смузі проміжок повертається до спільного `--grid-gap`:
   1824 = 8×200 + 7×32 ділиться націло, тобто залишку, який треба комусь
   віддати, тут просто немає. */
@media (min-width: 904px) {
  .viewed__grid {
    grid-template-columns: repeat(8, minmax(0, 1fr));
    column-gap: var(--grid-gap);
  }
}

/* Пропорція плитки — з вузла: 80/116 на мобільному, 200/282 з планшета вгору.
   Зазор іде тим самим переходом (8 → 16 → 32), і від 820 це вже спільний
   `--grid-gap` сітки, а не власне число блока. */
/* Пропорція — з вузла СВОГО якоря, а не одна на два. Спільні 200/282 давали
   на планшеті 255.2 замість канонічних 255: різниця в третьому знаку
   пропорції (0.70922 проти 0.70980) на ширині 181 виростає у видиму дробову
   висоту. Три вузли — три пропорції, і на кожному якорі висота ціла. */
/* ПРОПОРЦІЯ M І T РІЗНА, ТОМУ МІЖ НИМИ ТЕЧЕ ВИСОТА, А НЕ СТРИБАЄ ПРОПОРЦІЯ.
   0.6897 на 375 і 0.7098 на 820 — сходинкою це давало на шві падіння висоти
   при ЗРОСТАННІ ширини: 180.77×262.11 на 819 проти 181×255 на 820, тобто
   мінус 7 px від ширшого екрана (немонотонність, §4 режим 5 ADAPTATION).
   Шов 820 тут не структурний поріг, а стик двох ділянок рампи, і розрив на
   ньому не легітимний (§3.9).

   Чому саме висота, а не пропорція: `aspect-ratio` не інтерполюється, а
   ширину плитки в цій смузі задає та сама рампа viewport-а, що й тут, —
   колонок у смузі рівно чотири від 320 до 903. Вище 820 висота знову
   віддається пропорції: там колонок стає вісім (904), плитка вдвічі вужчає,
   і рампа від viewport-а брехала б.

   Підлоги в рампи немає навмисно. З підлогою 116 плитка нижче мобільного
   якоря РОСЛА б угору при звуженні екрана (72×116 на 320 замість 72×104):
   ширина там уже не слухає `--viewed-tile`, бо залишок під проміжки йде в
   нуль. Без підлоги висота йде далі вниз тим самим нахилом — 98.8 на 320,
   на 5.6 px нижче за пропорційну; це монотонно, і на самому якорі 375 вираз
   дає рівно 116, бо доданок обертається на нуль. */
/* Нижче якоря висоту тримає пропорція канонічної плитки (80/116), а не рампа
   від viewport-а: ширину там задає не `--viewed-tile`, а третина контейнера,
   і рампа рахувала б висоту від числа, якого на екрані немає. */
.viewed__item {
  aspect-ratio: 80 / 116;
}

@media (min-width: 375px) {
  .viewed__item {
    aspect-ratio: auto;
    block-size: min(calc(116px + 139 * (100vw - 375px) / 445), 255px);
  }
}

@media (min-width: 768px) {
  .viewed__item { block-size: auto; aspect-ratio: 181 / 255; }
}

@media (min-width: 1024px) {
  .viewed__item { aspect-ratio: 200 / 282; }
}

/* Плиток різна кількість: 12 на десктопі (, 1824×596 — два ряди
   по 8 із недобраним другим) і 8 на вузьких (, 343×240 — рівно
   два ряди по 4; намальовано в ньому шість, дві комірки лишились порожні).
   Чотири останні ховаються там само, де сітка стає восьмиколонковою — тобто
   на тому самому виведеному порозі 904, а не на власному числі. */
.viewed__item--wide { display: none; }

@media (min-width: 904px) {
  .viewed__item--wide { display: block; }
}

/* Дві останні плитки чотириколонкового ряду: нижче якоря колонок три, і без
   них два ряди сходяться рівно (3 + 3), а з ними лишався б обрубок. */
.viewed__item--tail { display: none; }

@media (min-width: 375px) {
  .viewed__item--tail { display: block; }
}

/* `container-type` тут не про запити, а про одиницю: підпис наведення міряє
   ширину ПЛИТКИ, а не екрана (див. `.viewed__hint`). Обмеження — тільки по
   інлайновій осі, тому висоту, яку плитці задає пропорція, це не чіпає. */
.viewed__link {
  position: relative;
  display: block;
  block-size: 100%;
  overflow: hidden;
  container-type: inline-size;
}

.viewed__link img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Наведення — шар `hovert` вузла плитки: затемнення на все поле
   й підпис у центрі. У макеті шар прихований, бо це СТАН, а не вимкнений
   елемент: усередині нього лежать і заливка, і текст.

   Дужки круглі, хоч вузол канону названий фігурними: рішення замовника
 — на сайті один вид дужок, той, що в підписі засновниці. */
.viewed__hint {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  color: var(--btn-ink);
  background: var(--scrim);
  /* ПІДПИС МІРЯЄТЬСЯ ПЛИТКОЮ, А НЕ ЕКРАНОМ. «(Переглянути )» кеглем 16 —
     це 110.19 px в один рядок, тобто ширший за плитку скрізь, де колонок
     вісім і екран вужчий за 1140 (91.47 на 904), і за мобільну плитку теж.
     Ловилось це не оком: рядок мовчки переносився на три і вилазив за фото
     на 20.66 px на 320, на 12.66 на 375 і на 1.19 на 904 — заміряно рамкою
     тексту, а не рамкою span-а (span розтягнутий на всю плитку і про
     переповнення не каже нічого).

     Число 13cqw — не на око: ширина цього рядка дорівнює 6.887 × кегль,
     тобто щоб влізти в плитку W, кегль мусить бути ≤ 0.145×W; 0.13 лишає
     від 124 px і ширшій діє саме вона.

     ПІДЛОГА 12 — НЕ ДИЗАЙН, А ДОСТУПНІСТЬ: дрібніший кегль ловить чекер
     `browser/fonts` (він і зловив 10.4 на 375 у першій редакції цієї
     правки). На плитці 80 рядок при 12 просить 82.6 — на 2.6 більше, ніж
     є, — тому саме там він переноситься по своїх же пробілах і лишається в
     кадрі. Це свідомий розмін: у смузі 375…386, і тільки в ній, підпис іде
     двома-трьома рядками замість одного. Обрізати його `overflow: hidden`
     було б гірше — обрізане не читається взагалі. */
  font-size: max(12px, min(16px, 13cqw));
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .viewed__link:hover .viewed__hint { opacity: 1; }
}

.viewed__link:focus-visible .viewed__hint { opacity: 1; }

/* SEO-плита каталогу відбивається від плиток тим самим ритмом секції. */
/* Відступ SEO-блока — МОДИФІКАТОРОМ, а не посторінковим класом і не на
   самому `.seo`. Обидві крайності вже перевірені: посторінковий клас
   (`.catalog-seo`) роздвоїв би число на другій сторінці, а спільне правило на
   `.seo` додало 100 px Головній, де блок бере ритм від сусіда, — і знімок
   прийнятих ширин це спіймав (docH 5514 → 5579). Число записане один раз,
   носіїв у нього два. */
.seo--section { margin-block-start: var(--section-gap); }

/* ==========================================================================
   `docs/page-plan/product.md`.

   ПЕРШИЙ ЕКРАН ВУЖЧИЙ ЗА КОНТЕЙНЕР: 80 [мініатюри] + 32 + 750 [фото] + 64 +
   570 [інфо] = 1496 при контейнері 1824. Композиція притиснута до лівого
   краю, праворуч лишається повітря — і це задум, а не забутий блок: крихти
   стоять на 976, тобто над КОЛОНКОЮ, а не над сторінкою, і плеєр
   відео-лайтбокса закінчується рівно на 1546, правому краї цієї ж
   композиції.
   ========================================================================== */
.pcard {
  --pc-thumb-w: 80px;
  --pc-thumb-h: 106px;
  --pc-thumb-gap: var(--s-16);
  /* СТЕЛЯ ФОТО, А НЕ ЙОГО ШИРИНА: 750 — число десктопного якоря
     `.pgal`).

     ⚠️ Не `calc` із `100vw`: `100vw` включає смугу прокрутки, тобто на
     реальному вікні Windows воно на ~15 px більше за контейнер. З порога
     1025 композиція дорівнює контейнеру, і ці 15 px виїхали б за екран —
     причому в headless їх не видно. Тому все, що тече часткою, міряється
     ВІДСОТКОМ від справжньої коробки. */
  --pc-photo-max: 750px;
  display: grid;
  /* Крихти стоять над ТІЛОМ картки, а тіло ставить колонки одна під одною —
     два різні шви (16 і 32) вимагають двох потоків. Обидва числа однакові на
     шов композиції. Звести їх в один потік не можна: нерівний крок ряду
     всередині потоку — дефект, який ловить `.qa/check-grid-step.py`. */
  row-gap: var(--s-16);
}

/* ⚠️ НУЛЬОВИЙ МІНІМУМ ДІТЯМ СІТКИ — захист від типової пастки grid/flex:
   `min-width` дитини сітки за замовчуванням `auto`, тобто вона не може стати
   вужчою за свій min-content. Одного нерозривного рядка досить, щоб колонка
   роздулась під нього, а за нею вся сторінка. Обрізати чи переносити текст
   можна лише там, де мінімум нульовий. */
.pcard > *,
.pcard__body > * { min-inline-size: 0; }
.pcard__crumbs { margin-block-start: var(--crumbs-top); }
/* ШОВ НЕСЕ САМ БЛОК, А НЕ КРОК РЯДУ. Дітей у тіла картки троє — галерея,
   інфо-колонка, блок довідок, — і спільний `row-gap` дав би зайвий шов над
   довідками, де шов уже є: власна волосина блока.

   Канонічні 32 стоять ПАДІНГОМ, а не відступом: `.qa/check-grid-step.py`
   міряє крок як відстань між коробками, і відступ читався б як другий, інший
   крок у тому самому потоці. Падінг лежить усередині коробки, тож крок
   потоку нульовий на всіх швах, а ритм тримає кожен блок сам. На екрані це
   те саме — коробка інфо-колонки нічим не пофарбована. */
.pcard__body { display: grid; row-gap: 0; }
.pcard__body > .pinfo { padding-block-start: var(--s-32); }

/* ПОРІГ ДЕСКТОПНОЇ КОМПОЗИЦІЇ — 1025. Число обране, а не виміряне: композиція
   тут не ламається на якійсь ширині, а зменшується пропорційно, тож фізичного
   дна в неї немає. Обґрунтування вибору — `docs/adapt-log/product.md`.

   ЯК ТЕЧЕ. Нижче 1456.76 кожна частина першого екрана віддає свою частку
   контейнера: 570/1456.76 — інфо-колонка, 64/1456.76 — шов між колонками,
   80/822.76 — смуга мініатюр усередині галереї. Форма `min(канон, частка)`
   віддає канонічне число там, де контейнер ширший, і частку — де вужчий;
   нового числа в ній немає, лише канонні та ширина, від якої частка
   рахується. 1456.76 — контейнер на 1536, найвужчій прийнятій ширині: на ній
   композиція ще канонічна, тож нижче вона тече в тих пропорціях, у яких її
   прийняли.

   ⚠️ У смузі 1025…1139 шапка й підвал ще планшетні (їхнє дно 1140 виміряне й
   нижче не опускається), а перший екран уже десктопний. Пороги двох блоків
   не зобовʼязані збігатись, коли один виміряний, а другий обраний. */
@media (min-width: 1024px) {
  .pcard {
    /* Інфо-колонка: канонічні 570 або та сама частка вужчого контейнера. */
    --pc-info-w: min(570px, calc(100% * 570 / 1456.76));
    /* Ліва доріжка бере залишок, але не росте вище канону: 80 + 32 + 750 =
       дозволяє їй стиснутись, `justify-content: start` кладе надлишок
       праворуч. */
    grid-template-columns:
      minmax(0, calc(var(--pc-thumb-w) + var(--s-32) + var(--pc-photo-max)))
      var(--pc-info-w);
    /* ТРИ РЯДКИ: крихти, інфо-колонка, блок довідок. Крихти на D стоять над
       ПРАВОЮ колонкою (`976, 84`), а галерея починається на рівні шапки
       (`162, 60`), тож галерея накриває рядки цілими прольотами. Слабину
       бере ТРЕТІЙ рядок — довідки стоять відразу під інфо-колонкою, а не на
       дні картки. */
    grid-template-rows: auto auto 1fr;
    /* Шов «крихти → назва» задає відступ `.pinfo__head` (56); другий запис
       того самого шва розійшовся б із першим на першій же правці. */
    row-gap: 0;
    column-gap: min(var(--s-64), calc(100% * 64 / 1456.76));
    justify-content: start;
    align-items: start;
  }

  /* Обгортка тіла зникає з розкладки: галерея й інфо-колонка стають
     власними дітьми сітки картки. Розмітка від цього не міняється — на
     кожному якорі стоїть та сама. */
  .pcard__body   { display: contents; }
  /* Галерея накриває всі три рядки: інакше довідки лягли б окремим рядком
     ПІД нею, і картка виросла б на їхню висоту. */
  .pcard .pgal   { grid-area: 1 / 1 / 4 / 2; }
  .pcard__crumbs { grid-area: 1 / 2; }
  .pcard .pinfo  { grid-area: 2 / 2; padding-block-start: 0; }
  /* Блок довідок стоїть у ПРАВІЙ колонці — третій рядок, під інфо-колонкою
     і в її ширині, як у каноні. */
  .pcard .pguide { grid-area: 3 / 2 / 4 / 3; }

  /* Стрілки «назад» на D у каноні немає — і це не «забули намалювати»: на D
     видно всю сторінку каталогу в одному кліку від крихт, а на T і M крихти
     стоять над каруселлю, де промахнутись по 16-піксельній ланці легко. */
  .crumbs__back { display: none; }
}


/* Стрілка «назад» —, 24×24 при ПРАВОМУ краї контейнера. Рядок
   крихт заради неї стає розпіркою: ланки ліворуч, стрілка праворуч. */
.pcard__crumbs {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
}

/* Рядок крихт переноситься, як і всюди; нульовий мінімум потрібен, щоб
   довга назва товару не роздувала контейнер замість того, щоб перенестись. */
.pcard__crumbs .crumbs__list { min-inline-size: 0; }

/* `.crumbs a` фарбує ланки приглушеним `--crumb-ink`, і селектор із тегом
   переважує клас — тому стрілка виходила сірою разом із ланками. Тон у неї
   інший за суттю: ланки це «де я», а стрілка — ДІЯ, і в каноні вона
   `black/900` (, той самий тон, що остання ланка).

   Іконка 24 у рядку заввишки 16 не має РОЗСУВАТИ рядок: у каноні стрілка
   стоїть на 68…92 при тексті 72…88, тобто виступає по 4 з обох боків, а
   галерея починається на 104 — за 16 від ТЕКСТУ. Без відʼємного поля рядок
   ставав 24, і вся сторінка нижче їхала на 8. */
.crumbs .crumbs__back {
  flex: none;
  margin-block: -4px;
  color: var(--ink);
}

.crumbs__back svg { display: block; inline-size: var(--icon); block-size: var(--icon); }

.pgal { position: relative; display: grid; row-gap: var(--s-16); }

@media (min-width: 1024px) {
  .pgal {
    /* 822.76 — сама галерея на 1536, найвужчій прийнятій ширині. Смуга
       мініатюр і шов беруть від неї свої канонічні частки, фото — залишок:
       записане числом, воно розійшлося б із залишком на першій же правці
       сусіда. */
    grid-template-columns:
      min(var(--pc-thumb-w), calc(100% * 80 / 822.76)) minmax(0, 1fr);
    column-gap: min(var(--s-32), calc(100% * 32 / 822.76));
    align-items: start;
  }
}

/*   кадрів, і це не недогляд: 6 кадрів + 6 зазорів + ХВІСТ 41. Замовник
   Останній кадр обрізаний навмисно — це підказка, що кадрів більше, ніж
   видно. Підігнати висоту «щоб не різало» означало б стерти
   єдиний натяк, що кадрів більше, ніж видно.

   Гортається смуга ВСЕРЕДИНІ СЕБЕ, а не тягне сторінку: `overscroll-behavior`
   не віддає прокрутку далі, коли смуга дійшла до краю. */
.pgal__strip {
  display: flex;
  flex-direction: column;
  gap: var(--pc-thumb-gap);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: none;
}

.pgal__strip::-webkit-scrollbar { width: 0; height: 0; }

/* ПЛАВНІСТЬ ЖИВЕ ТУТ, А НЕ У ВИКЛИКУ. Двигун каже смузі лише «підтягни цей
   кадр у видиме» (`scrollIntoView` з типовою поведінкою), а ЯК саме вона це
   робить — властивість смуги. Тому `behavior: 'smooth'` у JS не пишеться:
   інакше плавність довелось би вимикати в двох місцях, і `prefers-reduced-
   motion` рано чи пізно розійшовся б із фактичною поведінкою.

   Кадр перемикається згасанням за 320 мс, і смуга під ним мусить доїжджати
   за той самий час — інакше два рухи одного жесту йдуть врізнобій. */
@media (prefers-reduced-motion: no-preference) {
  .pgal__strip { scroll-behavior: smooth; }
}

/* ⚠️ Розходження з каноном: у макеті смуга зсунута вгору — 80 зверху
   проти 193 знизу на 1920. Тут вона
   стоїть ПО ЦЕНТРУ фото.

   Центр не рахується числом: рядок галереї заввишки з фото, тож `align-self:
   center` ставить смугу посередині без жодної арифметики. Відступ при цьому
   мусить бути нульовий — центрування рахує поля. */
/* ВІДСТУП СМУГИ ДОРІВНЮЄ ЇЇ ВЛАСНІЙ ШИРИНІ — і це не збіг, а пропорція
   канону: на десктопному якорі смуга 80 завширшки стоїть на 80 від верху
   контейнера, тобто по ширині колонки смуги, тож обидва числа течуть разом і
   на якорі сходяться в канон.
   ⚠️ Центрування тут стояти не може: рядок галереї заввишки з фото, і на
   1920 центр дав би смузі 136 замість намальованих 80 — розходження з
   макетом на самому ЯКОРІ. Пороги «вище такої-то ширини — інший спосіб» цим
   і знімаються: спосіб один на всю смугу режиму. */
@media (min-width: 1024px) {
  .pgal__strip { align-self: start; margin-block-start: 100%; }
}

.pgal__thumb {
  position: relative;
  flex: none;
  inline-size: var(--pc-thumb-w);
  block-size: var(--pc-thumb-h);
  background: var(--thumb-surface);
}

.pgal__thumb img {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* ⚠️ ВІДКРИТИЙ КАДР ПОЗНАЧАЄ КРУГ — і це друга роль того самого круга
   стрілку; замовник розвів дві ролі: круг показує, яке фото
   зараз відкрите, а відео лишається зі знаком без круга.

   Геометрія круга задана в десктопному блоці — там, де смуга взагалі є:
   нижче 1025 фото-мініатюр немає жодної, а єдина видима мініатюра — відео.
   Обведення відкритого кадру як другого носія стану НЕМАЄ: замовник
 «Забери обводку» — круг лишається єдиною міткою. */

/*   радіус 1000). Геометрію кола задають блоки нижче — карусельний і
   десктопний, — бо вона різна в двох композиціях. */
.pgal__play {
  position: absolute;
  display: grid;
  place-items: center;
  color: var(--surface);
}

.pgal__play svg { inline-size: var(--icon); block-size: var(--icon); }

.pgal__stage { position: relative; }

/* Коробка кадру. Відношення стоїть ТУТ, а не на зображенні: кадрів у стосі
   багато, і кожен тягнеться на всю коробку. Обрізку вмикає десктопний блок —
   вона потрібна лупі, а в каруселі заважала б сусіднім кадрам. */
.pgal__zoom {
  position: relative;
  display: block;
  inline-size: 100%;
  aspect-ratio: var(--pc-frame-ar);
}

/* ЛУПА — КВАДРАТ ІЗ ФРАГМЕНТОМ, а не збільшене фото. Саме фото не рухається
   взагалі: воно лежить у потоці, і будь-який його масштаб штовхав би сусідів
   або вимагав би обрізки, яка ховає частину кадру.

   Три числа лупа бере від двигуна, і всі три — заміряні, а не вгадані: розмір
   тла (кадр × `--pc-zoom`), зсув тла (щоб точка під курсором опинилась у
   центрі квадрата) і позиція самого квадрата. Порахувати їх у CSS не можна:
   вони залежать від ФАКТИЧНОЇ коробки кадру, яку знає тільки браузер.

   Тільки при `hover: hover`. На тачі наведення триває рівно до відпускання
   пальця, і лупа блимала б на кожен тап; там для розгляду є сам лайтбокс. */
.pgal__lens {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  inline-size: var(--pc-lens);
  block-size: var(--pc-lens);
  translate: calc(var(--lens-x, 0px) - 50%) calc(var(--lens-y, 0px) - 50%);
  /* Курсор мусить лишатись над ФОТО, а не над лупою: інакше `pointermove`
     перестає приходити тієї ж миті, коли лупа зʼявляється під курсором. */
  pointer-events: none;
  /* ⚠️ НАД КАДРАМИ: стос кадрів теж позиційований і стоїть у розмітці ПІСЛЯ
     лупи, тож без цього рядка малювався б поверх неї — лупа була б увімкнена
     й невидима. Перевірка стану такого не ловить за побудовою:
     `elementFromPoint` не повертає елемент із `pointer-events: none`. */
  z-index: 1;
  opacity: 0;
  background-repeat: no-repeat;
  outline: var(--hairline) solid var(--surface);
  outline-offset: -1px;
}

@media (hover: hover) {
  .pgal__lens.is-on { opacity: 1; }
}

@media (hover: hover) and (prefers-reduced-motion: no-preference) {
  .pgal__lens { transition: opacity var(--dur-fast) var(--ease); }
}

/* Бейдж стоїть У КАДРІ, і відступ у нього той самий, що у відео-кадру:
   20 на M, 24 на T і D. Одне число на дві ролі — тому токен
   один. */
.pgal__flags {
  position: absolute;
  inset-block-start: var(--pc-frame-pad);
  inset-inline-start: var(--pc-frame-pad);
}

/* --------------------------------------------------------------------------
   ГАЛЕРЕЯ НА T І M — КАРУСЕЛЬ, а не десктопна пара в меншому масштабі.
   Канон: (T, кадри 772×1094, крок 788) і (M).

   ЦЕ ТА САМА КОРОБКА І ТІ САМІ ДЕСЯТЬ ЗНІМКІВ. На D вони лежать стосом і
   перемикаються згасанням, тут — стають доріжкою з прилипанням. Другий набір
   `<img>` означав би подвійне вантаження й дві правди про відкритий кадр.

   ХВОСТИ СУСІДНІХ КАДРІВ — З ОБОХ БОКІВ, і в каноні їх теж два: доріжка
   стоїть на −764 при кадрах 772 і зазорі 16, тобто попередній кадр
   закінчується на 8, а наступний починається на 812 — по 8 px з кожного
   краю вікна. Хвіст лише з одного боку читався б як край стрічки, а не як
   «попередній кадр там само, де наступний».

   Механіка — та сама, що у стрічки товарів: доріжка на гутер ширша з обох
   боків, а власний падінг ставить кадр рівно в контейнер. Тоді ліворуч у
   падінг заїжджає попередній кадр, праворуч — наступний, і жодне число не
   зʼявляється двічі. Це єдина підказка «гортається»: ні стрілок, ні крапок
   канон тут не малює.

   Порядок блоків нижче — базовий; десктопний стос відновлюється одним блоком
   на 1025. Проти порогу (`max-width`) не пишемо ніде: у реєстрі порогів
   живуть тільки `min-width`.
   -------------------------------------------------------------------------- */
.pgal__zoom { overflow: visible; }

/* Лупа — тільки в десктопній композиції. На тачі наведення триває до
   відпускання пальця, а на планшеті з мишею вона стала б квадратом, що їде
   поверх каруселі: коробка кадру тут навмисно не обрізає вміст. */
.pgal__lens { display: none; }

.pgal__frames {
  position: static;
  display: flex;
  gap: var(--pc-frame-gap);
  block-size: 100%;
  /* Доріжка ширша за контейнер на гутер З ОБОХ БОКІВ, а падінг повертає кадр
     у контейнер: `scroll-padding` тримає те саме число при прилипанні, тож
     обраний кадр завжди стає рівно там, де стоїть решта сторінки. */
  margin-inline: calc(-1 * var(--gutter));
  padding-inline: var(--gutter);
  scroll-padding-inline-start: var(--gutter);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
}

.pgal__frames::-webkit-scrollbar { width: 0; height: 0; }

.pgal__frame {
  position: static;
  flex: none;
  /* 100% — контентна коробка доріжки, з якої падінги вже відняті, тобто рівно
     ширина контейнера сторінки. ⚠️ Віднімати гутер ще раз тут не треба:
     подвійне віднімання дало б кадр, вужчий за контейнер на гутер. */
  inline-size: 100%;
  block-size: 100%;
  opacity: 1;
  scroll-snap-align: start;
}

/* СМУГА МІНІАТЮР НЕ ЗНИКАЄ — вона зводиться до свого ПЕРШОГО кадру. Відео і
   120×160 у лівому низу кадру). Кнопка одна на всі якорі: другий примірник
   означав би дві кнопки з одним `data-hook`. */
.pgal__strip {
  position: absolute;
  z-index: 1;
  inset-block-start: auto;
  inset-block-end: var(--pc-frame-pad);
  inset-inline-start: var(--pc-frame-pad);
  max-block-size: none;
  overflow: visible;
}

.pgal__thumb { display: none; }

/* На M відео-кадр РІВНО той самий, що мініатюра смуги на D — 80×106
   запис тих самих чисел. На T кадр більший, і там зʼявляється власний. */
.pgal__thumb--video {
  display: block;
  inline-size: var(--pc-thumb-w);
  block-size: var(--pc-thumb-h);
}

@media (min-width: 768px) {
  .pgal__thumb--video {
    inline-size: var(--pc-video-w);
    block-size: var(--pc-video-h);
  }
}

/* Знак відео в каруселі — плівка на ВЕСЬ кадр плюс стрілка по центру, без
   верстають. Тон плівки — властивість вузла (`rgba(32,31,31.2)`), тобто той
   самий `--scrim-soft`, а не підібране око-в-око. */
.pgal__play {
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  border-radius: 0;
  background: var(--scrim-soft);
}

/* --------------------------------------------------------------------------
   ДЕСКТОПНИЙ СТОС ПОВЕРТАЄТЬСЯ. Кожен рядок тут скасовує рівно один рядок
   каруселі — і жодного зайвого: якщо блок нижче колись розійдеться з блоком
   вище, розійдуться саме ці пари.
   -------------------------------------------------------------------------- */
@media (min-width: 1024px) {
  .pgal__zoom { overflow: clip; }
  .pgal__lens { display: block; }

  .pgal__frames {
    position: absolute;
    inset: 0;
    display: block;
    block-size: auto;
    margin-inline: 0;
    padding-inline: 0;
    overflow: visible;
    scroll-snap-type: none;
  }

  .pgal__frame {
    position: absolute;
    inset: 0;
    inline-size: 100%;
    block-size: 100%;
    opacity: 0;
  }

  .pgal__strip {
    position: static;
    inset: auto;
    /* ВИСОТА СМУГИ — ВІДНОШЕННЯ, А НЕ СУМА. 773 — це «6 кадрів + 6 зазорів +
       ширини смуги: сума фіксованих доданків за текучою смугою не встигала б,
       і хвіст або зникав би, або звисав. */
    max-block-size: none;
    aspect-ratio: 80 / 773;
    overflow-y: auto;
    /* ⚠️ Зазор кадрів іде полем самого кадру (нижче): відсотковий `gap` у
       колонковому флексі міряється по ВЕРТИКАЛІ, а поле — по горизонталі,
       тобто по ширині смуги, яка тут і є одиницею. */
    gap: 0;
  }

  .pgal__thumb { display: block; }

  /*     16 між кадрами — це 20% тієї самої ширини. Селектор зі смугою переважує
     `.pgal__thumb--video`, тож планшетні `--pc-video-*` скасовує саме він, і
     окремого рядка для відео тут не потрібно. */
  .pgal__strip .pgal__thumb {
    inline-size: 100%;
    block-size: auto;
    aspect-ratio: 80 / 106;
    /* ⚠️ АВТОМІНІМУМ ФЛЕКС-ДИТИНИ СИЛЬНІШИЙ ЗА ВІДНОШЕННЯ: у колонковому
       флексі `min-block-size` за замовчуванням `auto`, тож без нуля кадр не
       може стати нижчим за свій вміст і бере пропорцію файла замість
       канонічної. */
    min-block-size: 0;
    margin-block-end: 20%;
  }

  /* ⚠️ КРУГ ІДЕ ЗА ВІДКРИТИМ КАДРОМ, А НЕ ЗА ВІДЕО. Чотири числа
     канону лишились ті самі — коло 72 при кадрі 80×106, зсув 4 і 17
     тепер це мітка відкритого кадру, а знак відео лишається без круга.
     Числа записані частками ВЛАСНОГО кадру, бо кадр тече.
     ⚠️ Відсоток в `inset-block-start` міряється по ВИСОТІ, а в `inline-size`
     і `inset-inline-start` — по ширині; звідси різні знаменники. */
  .pgal__strip .pgal__thumb[aria-current="true"]::after {
    content: "";
    position: absolute;
    inset-block-start: calc(100% * 17 / 106);
    inset-inline-start: calc(100% * 4 / 80);
    inline-size: calc(100% * 72 / 80);
    block-size: auto;
    aspect-ratio: 1;
    border-radius: 50%;
    background: color-mix(in srgb, var(--ink) 80%, transparent);
  }

  /* Десктопного скіна в знака відео більше немає: він бере той самий вигляд,
     що в каруселі, — плівка на весь кадр плюс стрілка по центру. Один знак
     на всі якорі замість двох різних. */

  /* Стрілка не тече разом із колом: знак намальований у viewBox 24×24 із
     обведенням 1.4, і зменшена коробка дала б тоншу лінію — це ловить
     `.qa/check-icon-scale.js`. Масштабується коло, знак у ньому лишається
     канонічним. */
}

/* --------------------------------------------------------------------------
   Інфо-колонка —, 570×767.
   -------------------------------------------------------------------------- */
/* Власного відступу в ряда немає нижче 1025: шов «галерея → назва» задає
   падінг інфо-колонки (32). Другий запис того самого шва подвоїв би його, і
   жодна перевірка цього не спіймала б — відстань МІЖ блоками не міряє жодна
   з них. */
.pinfo__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  column-gap: var(--s-24);
}

@media (min-width: 1024px) {
  /* Крихти → назва: 56. Це шов саме цієї колонки, а не сторінки: фото ліворуч
     починається вище — на рівні шапки. */
  .pinfo__head { margin-block-start: var(--s-56); }
}

.pinfo__title {
  margin: 0;
  max-inline-size: var(--pc-title-w);
  font-size: var(--pc-title-fs);
  line-height: var(--pc-title-lh);
  font-weight: var(--fw-medium);
}

/*   а не `order`: у розмітці стара ціна йде першою (`<del>` перед `<ins>` —
   контракт передачі в CMS), і міняти розмітку заради розкладки не можна.
   На T і D та сама пара лишається рядком. */
.pinfo__price .price {
  flex-direction: column-reverse;
  align-items: flex-end;
}

@media (min-width: 768px) {
  .pinfo__price .price { flex-direction: row; align-items: center; gap: var(--s-16); }
}

/* Ціна тут — основний чорний, а не приглушений `--price-ink` картки в
   стрічці: на сторінці товару це головне число, і канон дає йому `black/900`
. Розбіжність із карткою каталогу — контекст, не дрейф. */
.pinfo__price {
  margin: 0;
  color: var(--ink);
  font-size: var(--pc-price-fs);
  line-height: var(--pc-price-lh);
  font-weight: var(--fw-medium);
  letter-spacing: 0.01em;
  text-align: right;
  white-space: nowrap;
}

.pinfo__rule {
  block-size: var(--hairline);
  margin-block: var(--pc-rule-y);
  border: 0;
  background: var(--line);
}

/* Волосина ПЕРЕД рядом дій: зверху шов коротший рівно на 16, бо останній
   рядок характеристик несе власний падінг 16 (волосини в нього немає, а
   падінг лишається). 12 + 16 = 28 — той самий шов, що й у решти. */
.pinfo__rule--tight {
  margin-block-start: var(--pc-rule-tight);
  margin-block-end: var(--pc-act-rule-y);
}

/* Тон — `--label-ink` (`black/400`), тон канону.  */
.pinfo__label {
  margin: 0 0 var(--pc-label-text);
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.pinfo__text {
  margin: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* --------------------------------------------------------------------------
   волосина 1 + зазор 16; в останнього рядка волосини немає, а падінг є, тому
   останній рядок 36, а вся коробка 5×37 + 4×16 − 1 = 248.

   ВОЛОСИНА ЙДЕ ПО ВМІСТУ РЯДКА, а не по колонці: у каноні рядки різної
   ширини (140 · 158 · 204 · 99 · 167), тобто підкреслена пара, а не таблиця.
   -------------------------------------------------------------------------- */
.pspecs {
  margin: var(--pc-text-specs) 0 0;
  display: grid;
  justify-items: start;
  row-gap: var(--s-16);
}

.pspecs__row {
  display: flex;
  align-items: center;
  gap: var(--s-16);
  padding-block-end: var(--s-16);
  border-block-end: var(--hairline) solid var(--line);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.pspecs__row:last-child { border-block-end: 0; }

/* ДВІ КОЛОНКИ — склад, якого в каноні немає: канон малює три артборди, а не
   смугу між ними, і між однією колонкою та трьома лежить рівно один цілий
   крок. Кількість колонок задають РЯДКИ: при `grid-auto-flow: column` пʼять
   пунктів у три рядки стають двома колонками, у два — трьома. Тому нижче
   міняється `grid-template-rows`, а не перелік колонок у двох місцях.

   456 — виміряне дно двох колонок: 2 × 210 = 420, і контейнер дорівнює 420
   рівно на 455.1. Нижче колонки не вміщаються, і характеристики стають
   стовпчиком. */
@media (min-width: 456px) {
  .pspecs {
    grid-auto-flow: column;
    grid-template-rows: repeat(3, auto);
    grid-template-columns: repeat(2, var(--pc-spec-col));
    justify-content: space-between;
  }

  /* Волосини немає в НИЖНЬОГО рядка кожної колонки. При трьох рядках нижній
     у першій колонці — третій пункт; у другій — останній, і його вже знімає
     базове правило. */
  .pspecs__row:nth-child(3n) { border-block-end: 0; }
}

/*   Колір] [Склад]. Порядок ПО КОЛОНКАХ, тому `grid-auto-flow: column` при двох
   рядках, а не три окремі списки. Колонка 210 — число канону; зазор 71 ніде
   не записаний, бо він залишок (3 × 210 + 2 × 71 = 772), і `space-between`
   рахує його сам.

   634 — виміряне дно трьох колонок: 3 × 210 = 630, і на 634 контейнер 586 ще
   тримає рядки, а на 632 найширший виходить за нього. */

@media (min-width: 634px) {
  .pspecs {
    grid-template-rows: repeat(2, auto);
    grid-template-columns: repeat(3, var(--pc-spec-col));
  }

  /**/
  .pspecs__row:nth-child(3n) { border-block-end: var(--hairline) solid var(--line); }
  .pspecs__row:nth-child(2n) { border-block-end: 0; }
}

@media (min-width: 1024px) {
  .pspecs {
    grid-auto-flow: row;
    grid-template-rows: none;
    grid-template-columns: none;
    justify-content: start;
  }

  .pspecs__row:nth-child(2n) { border-block-end: var(--hairline) solid var(--line); }
  .pspecs__row:last-child { border-block-end: 0; }
}

.pspecs dt { color: var(--label-ink); }
.pspecs dd { margin: 0; }

/* --------------------------------------------------------------------------
   сходяться в 570 рівно: 168 + 24 + [232] + 26 + 56 + 8 + 56. Двадцять шість
   — не описка: той самий зазор перед ♡ стоїть і на планшетному вузлі
, тобто його кажуть ДВА вузли.

   «Купити» тут — гнучкий елемент, а не задана ширина: 232 виходить залишком
   і на десктопі, і на планшеті. Задати його числом означало б записати те
   саме двічі.
   -------------------------------------------------------------------------- */
/* РЯД ДІЙ НА M РОЗПАДАЄТЬСЯ НА ДВА РЯДКИ, і це не наслідок тісноти, а
   канон: поле «Розмір» займає рядок цілком, а нижче
   стоять «Купити» 223 і два кола по 48 — разом рівно 343.

   Перенос робить не «не вмістилось», а ПРАВИЛО: нижче планшетного якоря поле
   бере рядок цілком (`flex-basis: 100%`), тому кнопка з колами йде вниз
   ЗАВЖДИ. Покладатись на те, що рядок не зійдеться сам, тут не можна: у
   верхній частині мобільної смуги поле вміщається поруч із кнопкою, і ряд
   ставав би планшетним посеред мобільної композиції, зі скинутими вниз
   колами третім поверхом. Складів у ряду два, і
   вибирає між ними поріг, а не залишок місця.

*/
.pinfo__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  row-gap: var(--s-20);
}

/*   232 на T і D. Стискатись їй дозволено лише нижче мобільного якоря — на 320
   рядок «кнопка + два кола» (223 + 16 + 48 + 8 + 48 = 343) інакше вийшов би
   за контейнер 288.

   СТЕЛЯ, А НЕ `flex-shrink`: перенос у флексі відбувається РАНІШЕ за
   стиснення — браузер спершу шукає, що скинути на новий рядок, і лише потім
   тисне те, що лишилось. Тому `flex: 0 1 auto` тут не працює: друге коло
   поїхало б третім поверхом, а кнопка лишилась би 223. `min()` знімає сам
   привід переносити: ширина кнопки — це або канонічне
   число, або те, що лишається в рядку після кіл із їхніми зазорами. На 375 і
   вище лишок більший за канон, тобто стеля не спрацьовує й кнопка стоїть
   рівно 223. */
.pinfo__buy {
  flex: 0 1 auto;
  inline-size: min(
    var(--pc-buy-w),
    100% - (2 * var(--pc-round) + var(--pc-round-gap) + var(--s-8))
  );
  min-inline-size: 0;
  min-height: var(--pc-btn-h);
  margin-inline-start: var(--pc-size-gap);
}

/* 566 — виміряне дно ряду дій одним рядком: на 566 контейнер 518 і ряд ще
   один (поле 124 + 16 + «Купити» 232 + 26 + коло 56 + 8 + коло 56), на 564
   поле впирається у власний мінімум. Нижче тримати ряд одним означало б
   робити кнопку й кола дрібнішими за МОБІЛЬНИЙ канон (223 і 48 —
   ширин і малює. */
@media (min-width: 768px) {
  .pinfo__buy { flex: none; }
}

/*   першим із них теж різний: 16 проти 26. Обидва числа канонічні на своїх
   якорях, тому обидва в токенах, а не в правилі. */
.pinfo__actions .btn-round {
  inline-size: var(--pc-round);
  block-size: var(--pc-round);
  width: var(--pc-round);
  height: var(--pc-round);
  margin-inline-start: var(--pc-round-gap);
}

.pinfo__actions .btn-round + .btn-round { margin-inline-start: var(--s-8); }

/* РЯД ДІЙ ТЕЧЕ РАЗОМ ІЗ КОЛОНКОЮ. Канон складає його в 570 рівно:
   слабину бере поле «Розмір». Самих лише сталих частин 402, тож у вужчій
   колонці полю не лишалось би нічого й ряд ламався б надвоє. Тому ті самі
   канонічні числа записані ще й часткою від ряду: `min(канон, частка)` віддає
   232 · 56 · 26 · 24 там, де ряд дорівнює 570, і зменшує їх разом із ним.
   Перевизначення стоїть на самому ряду, щоб відсоток у кожного споживача
   міряв ОДНУ коробку. */
@media (min-width: 1024px) {
  .pinfo__actions {
    --pc-buy-w:     min(232px, calc(100% * 232 / 570));
    --pc-round:     min(56px,  calc(100% * 56  / 570));
    --pc-round-gap: min(26px,  calc(100% * 26  / 570));
    --pc-size-gap:  min(24px,  calc(100% * 24  / 570));
  }

  /* ⚠️ Відсоток у `block-size` міряється по ВИСОТІ, а висота ряду тут
     невизначена — частка всередині неї дала б колу висоту вмісту замість
     діаметра. Тому вертикаль задає відношення, а частка лишається в ширині. */
  .pinfo__actions .btn-round {
    block-size: auto;
    height: auto;
    aspect-ratio: 1;
  }
}

/* Кнопка «Розмір» — не `.btn`: прямий кут, тонка рамка, приглушений підпис.
   Це поле вибору, а не дія, і саме тому воно не бере скін кнопки. */
.psize {
  display: inline-flex;
  align-items: center;
  justify-content: space-between;
  /* ⚠️ Ширину полю задає його ОБГОРТКА (`.psize-wrap`) — саме вона стоїть у
     ряду дій, а кнопка лише заповнює її. Гнучкість, повішена сюди, лишила б
     обгортку стиснутою до вмісту, а звірка коробок цього не показала б: ряд
     має свою ширину незалежно від дітей. */
  inline-size: 100%;
  block-size: var(--pc-btn-h);
  padding-inline: var(--pc-field-pad);
  border: var(--hairline) solid var(--line);
  color: var(--ctl-off);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  background: var(--surface);
  text-align: start;
}

/* Внутрішнє поле теж тече: інакше у вузькій колонці воно зайняло б майже
   половину поля й від підпису лишилось би «Розм…» при вільному місці поруч.
   168 — саме поле на десктопному якорі. У липкій панелі поле
   ширше (184), і там `min` віддає ті самі 24. */
@media (min-width: 1024px) {
  .psize { padding-inline: min(var(--pc-field-pad), calc(100% * 24 / 168)); }
}

/* Підпис ОБРІЗАЄТЬСЯ, а не розсуває кнопку: у каноні обраний розмір стоїть
   поля. Ширина кнопки — число композиції ряду дій (168 + 24 + … = 570), і
   пускати її рости від довжини слова означало б ламати ряд текстом. */
/* ОБРАНИЙ РОЗМІР — ЧОРНИЙ, порожнє поле — приглушене. У каноні підпис поля
 просив розрізняти «нічого не обрано» і «обрано M» тоном, а не
   тільки словом. Клас ставить двигун у момент вибору — один writer
   той самий, що пише текст підпису. */
.psize__label.is-chosen { color: var(--ink); }

.psize__label {
  min-inline-size: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

/* ПЛЮС — ЧОРНИЙ, а не в тоні підпису: у каноні це два різні тони в одній
.
   Спадком від кнопки знак вийшов би сірим. */
.psize__icon {
  flex: none;
  inline-size: var(--icon);
  block-size: var(--icon);
  color: var(--ink);
}

/* --------------------------------------------------------------------------
   ДОВІДКИ — ТРИ ПІДПИСИ, КОЖЕН ЗІ СВОЇМ ВІКНОМ. Ряд той самий, що був
   зазорами 16 у рядку і 8 між рядками, проти 24 на T і D. Перенос робить сам
   `flex-wrap` — третій підпис не вміщається після другого.
   -------------------------------------------------------------------------- */
.pguide__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-8) var(--s-16);
  inline-size: fit-content;
  max-inline-size: 100%;
}

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

/* Текст під рядом — той самий вузол, що був слотом вкладок, і той самий тон.
   Змінного стану в ньому більше немає: він один і статичний. */
.pguide__model {
  margin-block-start: var(--s-24);
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

@media (min-width: 1024px) {
  /* 423 — або та сама частка інфо-колонки, коли вона вужча за
     570: інакше рядок довідки стирчав би за колонку саме там, де місця
     найменше. */
  .pguide__model { max-inline-size: min(423px, calc(100% * 423 / 570)); }
}

/* ВІКНО ДОВІДКИ. Два з трьох вікон канон малює на 1920: «Догляд»
   стоять НАД рядом, низом на 16 вище підписів, лівим краєм на 24 лівіше
   свого підпису. «Виготовлення на замовлення» канону не має — його вікно й
   далі зібране з ряду відступів. Вузьких артбордів вікон немає.

   Вікно стоїть НАД підписом, а не під ним: ряд довідок лежить у самому низу
   інфо-колонки, і вікно, розкрите вниз, виходило б за екран на кожному
   якорі. Так і в каноні.

   ⚠️ Нижче стіни десктопа вікно КРІПИТЬСЯ ДО РЯДА, а не до підпису — лік від
   вилізання за екран: ряд лягає у ДВА рядки, і нижній підпис стоїть близько
   до лівого краю. Ряд один на всі три довідки й не ширший за колонку, тому
   вікно від нього не вилізе ніде. Від стіни десктопа вікно кріпиться до
   СВОГО підпису (так у каноні), а від вилізання за правий край його тримає
   стеля від краю підпису до краю вікна перегляду: край підпису двигун
   кладе у `--pop-x` при відкритті. Твердження «вікно не вилізло за вікно
   перегляду» тримає `.qa/check-guide-behaviour.py`. */
.pguide__row { position: relative; }

/* Межа вікна — `outline` усередину, а не `border`: коробка канону 279×256 і
   643×448 рахує волосину, і з рамкою в потоці поля довелось би зменшувати на
   волосину (як у рядку замовлення). Обвідка розміру не міняє. */
.pop__panel {
  position: absolute;
  z-index: 6;
  inset-block-end: calc(100% + var(--pop-lift));
  inset-inline-start: 0;
  box-sizing: border-box;
  /* Ширина від РЯДА: він сам не ширший за колонку, тому вікно теж. */
  inline-size: min(360px, 100%);
  padding: var(--pop-pad);
  outline: var(--hairline) solid var(--line);
  outline-offset: calc(0px - var(--hairline));
  background: var(--surface);
}

.pop__panel[hidden] { display: none; }

/* «Виготовлення» — на всю КОЛОНКУ на всіх ширинах: ряд — `fit-content`
   (коробка канону 429), тож `100%` дав би ширину ряду; ширину колонки кладе
   двигун у `--pop-w` при відкритті. Без JS — ширина ряду. */
.pop__panel--made { inline-size: var(--pop-w, 100%); }

/* «Розмірна сітка» нижче стіни десктопа — теж на всю колонку: таблиця 643
   у вікні 360 гортається, а на планшеті колонка 720…772 вміщує її цілком
   (замовник: «на 820 краще без скролу, і по розширеннях поруч»
). Гортання лишається лише там, де колонка вужча за таблицю. */
.pop__panel--sizes { inline-size: var(--pop-w, 100%); }

/* Таблиця розмірів — без полів вікна: комірки самі несуть волосини, підпис
   вузьких ширинах таблиця гортається всередині вікна, а не ламає його. */
/* Ліворуч — волосина обвідки: таблиця канону починається на 952 при вікні
   на 951, тобто відразу за нею. */
.pop__panel--sizes { padding: 0 0 0 var(--hairline); }

.pop__head {
  margin: 0;
  block-size: var(--pop-head-h);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
  text-align: center;
}

/* ГОРТАЄТЬСЯ ТАБЛИЦЯ ВСЕРЕДИНІ СЕБЕ, А НЕ ТЯГНЕ СТОРІНКУ. Без
   `overscroll-behavior-x` свайп, який дійшов до краю таблиці, ЧІПЛЯЄТЬСЯ за
   сторінку — і людина бачить це як «сторінка товару їде вбік», хоч документ
   не ширший за вікно на жодній ширині. Це той самий закон, що вже стоїть у
   стрічки слайдера, смуги кадрів галереї й оверлея колекції; тут його не
   було — машинний перебір усіх горизонтальних скролерів дав 5, без закону
   були 2. */
.pop__scroll {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

@media (min-width: 1024px) {
  .pop { position: relative; }
  /* «Виготовлення» канону не має і від стіни десктопа кріпиться до
     РЯДА, як усі три нижче неї: замовник обвів вікно на всю колонку.
     Ряд не ширший за колонку — стеля від краю екрана йому не потрібна. */
  .pop--made { position: static; }

  .pop__panel {
    inset-inline-start: calc(0px - var(--pop-pad));
    max-inline-size: calc(100vw - var(--gutter) - var(--pop-x, 0px) + var(--pop-pad));
  }

  .pop__panel--care  { inline-size: var(--pop-care-w); }
  .pop__panel--sizes { inline-size: var(--pop-sizes-w); max-inline-size: none; }
  .pop__panel--made  { inset-inline-start: 0; max-inline-size: none; }
}

.pop__title {
  margin-block: 0 var(--s-16);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
}

.pop__text + .pop__title { margin-block-start: var(--s-24); }

.pop__text {
  margin-block: 0 var(--s-12);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.pop__text:last-child { margin-block-end: 0; }

/* Власного скидання список не має: `ul, ol` у `base.css` уже зняли поля,
   відступи й маркер з УСІХ списків проєкту. Клас лишається як гачок для
   правил рядків нижче. */

/* Рядки довідки «Догляд» — 14/16 кроком 32, без волосин. */
.pop__list {
  display: grid;
  gap: var(--pop-line-gap);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/*   40, волосина праворуч і знизу кожної комірки (`border-r border-b`), у
   першого рядка ще зверху; крайні волосини збігаються з обвідкою вікна, тому
   їх немає. Числа по центру, підписи рядків — від краю на 16. */
.pop__grid {
  inline-size: 100%;
  min-inline-size: calc(var(--pop-sizes-w) - var(--hairline));
  table-layout: fixed;
  /* Не `collapse`: у зведеній моделі половини волосин додаються до висоти
     рядка, і 10 рядків по 40 давали 409.5. Тут волосини — власність
     комірки, рядок рівно 40. */
  border-collapse: separate;
  border-spacing: 0;
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

.pop__grid th,
.pop__grid td {
  box-sizing: border-box;
  block-size: var(--pop-cell-h);
  padding: 0;
  /* Колір — на всіх чотирьох сторонах, ширина — лише праворуч і знизу:
     так `check-canon-paint` бачить одне обведення `black/100`, а не чотири
     різні сторони. */
  border: 0 solid var(--line);
  border-inline-end-width: var(--hairline);
  border-block-end-width: var(--hairline);
  font-weight: var(--fw-regular);
  text-align: center;
}

/* Ширина першої колонки — на `<col>`: у `table-layout: fixed` саме колонка
   задає ширину, а логічна `inline-size` на комірці її не дає. Фізична
   `width`, бо `<col>` іншої не знає. */
.pop__grid-c1 { width: var(--pop-col-1); }

.pop__grid th[scope="row"] {
  padding-inline: var(--pop-cell-pad);
  text-align: start;
  white-space: nowrap;
}

.pop__grid tr:first-child > * { border-block-start-width: var(--hairline); }
.pop__grid tr > :last-child { border-inline-end-width: 0; }
.pop__grid tr:last-child > * { border-block-end-width: 0; }

/* Заливка — на самому рядку, не на комірках: комірки без тла, і рядок
   і фарба звіряється там, де волосин немає. */
.pop__grid-key {
  background: color-mix(in srgb, var(--line) calc(var(--pop-row-dim) * 100%), transparent);
}

/* ⚠️ ЗГАСАННЯ НА ПОЯВІ ТУТ НЕМАЄ НАВМИСНО. Анімована поява робить знімок
   стану напівпрозорим: ранер знімає кадр одразу після кліка, тобто посеред
   руху. Це та сама пастка, що зі стилями під транзішеном — доказ
   мусить показувати СТАН, а не кадр руху. Вікно зʼявляється миттєво: і на
   знімку, і в людини. */

/* --------------------------------------------------------------------------
   Нижня частина сторінки.
   -------------------------------------------------------------------------- */
.pcut {
  block-size: var(--hairline);
  margin: var(--section-gap) 0 0;
  border: 0;
  background: var(--line);
}

.preel { margin-block-start: var(--pc-reel-top); }


/* Заголовок стрічки на M — у два рядки, як у каноні.
   Стеля знімається з планшетного якоря: там канон дає один рядок 424×40, і
   ширину заголовку задає вже сітка голови стрічки. */
.preel .slider__title { max-inline-size: var(--pc-reel-title-w); }

@media (min-width: 768px) {
  .preel .slider__title { max-inline-size: none; }
}

/* --------------------------------------------------------------------------
   ВИПАДНИЙ СПИСОК РОЗМІРУ —.

   СПИСОК ОДИН НА СТОРІНКУ, А ПОЛІВ ДВА. Кнопка «Розмір» стоїть і в
   але сам `<ul>` існує в одному примірнику: двигун переносить його у
   ВІДКРИТЕ поле. Два списки означали б два `id`, дві копії пунктів і дві
   правди про те, який розмір обраний, — а розходяться такі копії на першій же
   правці. Тому в CSS геометрія прив'язана до `.psize-wrap`, а не до місця.

   ВИПАДАЄ ВГОРУ, і це не смак: у каноні панель закінчується рівно низом
   кнопки, тобто кнопка — її нижні 64. Звідси `inset-block-end: 100%` і
   відсутність нижньої межі: волосини панелі й кнопки сходяться в одну.
   -------------------------------------------------------------------------- */
/* ОБГОРТКА ПОЛЯ БЕРЕ ЗАЛИШОК РЯДА — 168 при колонці 570, 378 при рядку 772:
   обидва числа канону виходять самі, тому в CSS немає ані 168, ані 378.
   Стала в ряду — кнопка «Купити» (`--pc-buy-w`), бо канон дає їй ОДНЕ число
   на двох якорях, а полю два різні. */
/* `flex: 1 1 100%` — це і є «поле над кнопкою» на мобільній смузі: базис на
   всю ширину відправляє решту ряду на другий рядок незалежно від того,
   скільки місця лишилось. З 566 базис вертається до залишку — там ряд стоїть
   в один рядок. */
.psize-wrap { position: relative; flex: 1 1 100%; min-inline-size: 0; }

@media (min-width: 768px) {
  .psize-wrap { flex: 1 1 auto; }
}

/* Відкрите поле піднімається над сусідами: без цього список ішов би ПІД
   кнопку «Купити», яка стоїть у розмітці пізніше. */
.psize-wrap.is-open { z-index: 5; }

/* Підказка «Оберіть розмір» — під полем, кольором помилки полів (той самий
   `--field-error`, що в оформленні). Текст — з атрибута обгортки: вузла в
   полі немає, будова поля та сама, що в еталоні. Рамка поля — тим самим
   кольором (D562). */
.psize-wrap.is-invalid::after {
  content: attr(data-jk-size-hint);
  position: absolute;
  inset-block-start: calc(100% + var(--s-8));
  inset-inline-start: 0;
  margin: 0;
  color: var(--field-error);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  white-space: nowrap;
}

.psize-wrap.is-invalid .psize { border-color: var(--field-error); }

/* Панель — коробка з волосиною й падінгом; прокручується СПИСОК усередині
   неї, а не вона сама. Розділено заради смужки: намальований повзунок мусить
   стояти нерухомо відносно панелі, а всередині списку він їхав би разом із
   рядками. */
.psize__panel {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 100%;
  padding: var(--psize-pad) var(--psize-pad) 0;
  border: var(--hairline) solid var(--line);
  border-block-end: 0;
  background: var(--surface);
  overflow: hidden;
}

.psize__list {
  margin: 0;
  padding: 0;
  /* 222 = волосина 1 + падінг 24 + список. Волосина віднімається явно:
     без неї панель виходила 223, тобто на піксель вища за канонічну. */
  max-block-size: calc(var(--psize-list-h) - var(--psize-pad) - var(--hairline));
  list-style: none;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* ⚠️ СИСТЕМНА СМУГА ВИМКНЕНА ЦІЛКОМ: у Chrome `scrollbar-width: thin`
     вимикає власне оформлення через `:-webkit-scrollbar`, тож спроба
     підфарбувати системну смугу дає товсту чорну. Повзунок тут свій —
     канонічний, 2×48. */
  scrollbar-width: none;
}

.psize__list::-webkit-scrollbar { width: 0; height: 0; }

/* Повзунок. Положення кладе двигун однією змінною; без JS він просто стоїть
   угорі, і це коректний стан «список не прокручено». */
.psize__thumb {
  position: absolute;
  inset-inline-end: var(--s-8);
  inset-block-start: calc(var(--psize-pad) + var(--psize-thumb-y, 0px));
  inline-size: var(--psize-thumb-w);
  block-size: var(--psize-thumb-h);
  border-radius: 10px;
  background: var(--ink);
}

.psize__thumb[hidden] { display: none; }

.psize__opt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
  padding-block-end: var(--s-16);
  border-block-end: var(--hairline) solid var(--line);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  cursor: pointer;
}

.psize__opt + .psize__opt { margin-block-start: var(--s-16); }

/* Пункт, якого не можна вибрати. У демо-наборі такого немає жодного: «S /
   Пошиття» вибірний, а «XXL / Не виготовляється» вибирається й
   вимикає кнопку «Купити». Правило лишається контрактом для списку з CMS,
   де вимкнені розміри цілком можливі, і тримає канонічний вигляд: приглушений
   САМ РОЗМІР, а не рядок цілком — `opacity: 0.4` лежить на тексті розміру,
   примітка поруч іде повним тоном. */
.psize__opt[aria-disabled="true"] { cursor: not-allowed; }
.psize__opt[aria-disabled="true"] .psize__val { opacity: .4; }

/* РОЗМІР У ПОШИТТІ ПРИГЛУШЕНИЙ, ПОКИ НЕ ОБРАНИЙ. Тон канону
 каже «цього немає в наявності» — але це стан
   а не заборона: обраний пункт бере повний тон, як усі. Приглушений
   САМ РОЗМІР, а не рядок: примітка «Пошиття» поруч іде повним тоном і тоді,
   коли розмір сірий. */
.psize__opt[data-state="sewing"] .psize__val { opacity: .4; }
.psize__opt[data-state="sewing"][aria-selected="true"] .psize__val { opacity: 1; }

.psize__note {
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  color: var(--ink);
}

@media (hover: hover) {
  .psize__opt:not([aria-disabled="true"]):hover { color: var(--ink-muted); }
}

.psize__opt[aria-selected="true"] .psize__val { font-weight: var(--fw-regular); }

/* Знак кнопки: плюс закритого поля стає мінусом у відкритого — так у каноні
   розмітці немає: ховається вертикальна половина того самого плюса, тому
   «мінус» не може розійтись із «плюсом» ні товщиною, ні довжиною. */
.psize__icon-v { transition: opacity var(--dur-fast) var(--ease); }
.psize[aria-expanded="true"] .psize__icon-v { opacity: 0; }

/* --------------------------------------------------------------------------
   СТАНИ ЦІНИ Й ДІЙ. П'ять станів канону, і кожен — свій артборд:
     Індивідуально «Індивідуально» «Замовити» + примітка
     Пошиття пункт списку, не стан сторінки

   ТОН СЛОВА-СТАНУ ЗНЯТИЙ ПОВУЗЛОВО, і саме тому він не однаковий:
   пофарбувати всі три однаково («це ж усе стани») стерла б різницю між «ціну
   назвемо окремо» і «купити не можна».
   -------------------------------------------------------------------------- */
/* ЦІНА ВЖИВАЄ КОМПОНЕНТ `.price`, а не власну копію. Стан «зі знижкою» на
   картці товару той самий, що в картці стрічки: `<del>` стара, `<ins>` нова,
   тони міняються місцями (стара `black/400`, нова `black/900` —
   першим, тому тут лишається тільки РОЗМІР і базовий тон — усе інше приходить
   із `.price`.

   Базовий тон переозначений: у стрічці ціна приглушена (`--price-ink`), а на
   сторінці товару вона головне число і йде основним чорним. */
.pinfo__price .price {
  color: var(--ink);
  font-size: var(--pc-price-fs);
  line-height: var(--pc-price-lh);
  font-weight: var(--fw-medium);
  gap: var(--s-16);
}

.pinfo__price .price__old { font-weight: var(--fw-regular); }

.pinfo__price--off .price__new { color: var(--label-ink); }

.pinfo__note {
  margin: var(--s-24) 0 0;
  max-inline-size: 570px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* --------------------------------------------------------------------------
   ЛИПКА ПАНЕЛЬ ДІЙ — `Group 734`, 1000×88.

   ОБОЛОНКА СПІЛЬНА З ТАБЛЕТКОЮ ФІЛЬТРА: обидві — «плитка внизу вікна, що
   з'являється, коли її оригінал поїхав під шапку». Тому позиціювання, поява й
   `prefers-reduced-motion` беруться з `.filterbar`, а тут лишається тільки
   скін. Другий примірник тих самих п'ятнадцяти рядків розійшовся б із першим
   на першій же правці порогу появи.

   Кнопки в панелі НИЖЧІ за сторінкові — 56 проти 64 і 48 проти 56. Це не
   «приблизно те саме»: панель 88 заввишки, і кнопка 64 лишила б по 12 зверху
   й знизу замість канонічних 16.
   -------------------------------------------------------------------------- */
/* ПОРОГА В ПАНЕЛІ БІЛЬШЕ НЕМАЄ: усі три склади зверстані.
           і два кола по 48;
       D —, 1000×88: те саме плюс мініатюра й бейдж.
   Тобто це не одна плитка, що стискається, а три склади одного стану —
   кожен зі своїм переліком того, що в ряду взагалі є. Числа лежать у
   токенах (`--pbar-*`), а правила нижче кажуть лише, ЧОГО в складі немає. */

.pbar {
  /* Стеля — КОНТЕЙНЕР сторінки, а не вікно: `100% − 48` рахувалось від
     вікна, і на 991…1024 панель вилазила за поля контейнера на 4 px з
     кожного боку. Ловить `check-container-edges`. */
  inline-size: min(var(--pbar-w), calc(100% - 2 * var(--gutter)));
  block-size: var(--pbar-h);
  background: var(--black-900);
  color: var(--surface);
  pointer-events: auto;
}

.pbar__inner {
  display: flex;
  align-items: center;
  block-size: 100%;
  /*     24 ліворуч на T (там перша в ряду назва), 16 на D — там перше не текст,
     а мініатюра. */
  padding-inline: var(--pbar-pad);
}

@media (min-width: 768px) {
  .pbar__inner { padding-inline: var(--s-24) var(--s-16); }
}

/* ЧОГО В СКЛАДІ НЕМАЄ. На M у плитці двоє контролів і більше нічого
   додається назва й кола; мініатюра з бейджем зʼявляються тільки на D.
   Правила писані «знизу вгору» — у той бік, у який росте склад. */
.pbar__thumb, .pbar__badge, .pbar__title { display: none; }

.pbar .btn-round { display: none; }

@media (min-width: 768px) {
  .pbar__title { display: block; }
  .pbar .btn-round { display: inline-flex; }
}

.pbar__thumb {
  flex: none;
  inline-size: var(--pbar-thumb-w);
  block-size: var(--pbar-thumb-h);
  object-fit: cover;
}

/* Ширина назви — місце переносу, а не оформлення: у каноні вона в два рядки
   (на D, на T — обидва 210×40 при рядку 20).

   `margin-inline-end: auto` — розпірка планшетного ряду: на D її роль виконує
   бейдж, якого тут немає, і без цього рядка кнопки збиралися б до назви,
   лишаючи порожнє місце праворуч. */
.pbar__title {
  margin: 0 auto 0 0;
  inline-size: 210px;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
}

/* Бейдж БЕРЕ СВІЙ ТОН, а не білий на всі стани: у каноні «New» на темній
   купиш». */
.pbar__badge { margin-inline-start: auto; }
.pbar .badge--new { color: var(--surface); }

/* У ПЛИТЦІ ВСЕ НАВПАКИ: поле задане числом, а слабину бере НАЗВА. Причина
   не в смаку — у складі ряду: на сторінці поруч із полем лише кнопка й два
   кола, а в плитці перед ним стоїть назва товару, яка й мусить стискатись.
 — те саме поле у трьох складах, тому три числа, а не рампа
   між ними. */
.pbar .psize-wrap {
  /* Стискатись полю дозволено, але тільки коли рядок інакше не вміщається:
     на 320 плитка має 288, а її склад — 8 + 160 + 16 + 151 + 8 = 343, тобто
     нижче мобільного якоря щось мусить поступитись. Поступається поле, не
     кнопка: кнопка несе дію з підписом, а поле — вибір, який усе одно
     обрізається трикрапкою. На 375 і вище стиснення не спрацьовує — рядок
     сходиться рівно. */
  flex: 0 1 auto;
  inline-size: var(--pbar-size-w);
  min-inline-size: 0;
  margin-inline-start: 0;
}

.pbar .psize {
  block-size: var(--pbar-btn-h);
  border-color: var(--pbar-line);
  /* ⚠️ Розходження з каноном: канонічний `black/600` дає на темній
     плитці 3.2:1 — нижче за AA, і ця пара в контраст-гейт не входить, тобто
     автоматично не спіймалась би.

     ПОРОЖНЄ ПОЛЕ — `--ctl-off` (#a0a09f), а не білий: це той самий тон, яким
     поле приглушене в колонці сторінки, і на `black/900` він дає 6.2:1,
     тобто AA тримає. Білим лишається ОБРАНИЙ розмір (правило нижче) — інакше
     обидва стани поля були б однакового тону, і різниця «обрано / не
     обрано» жила б тільки в слові. */
  color: var(--ctl-off);
  background: transparent;
}

/* У плитці знак БІЛИЙ — так у каноні: віддає той самий плюс зі
   `stroke="white"`. Чорний знак на `black/900` дав би 1:1, тобто його там
   просто не було б. */
.pbar .psize__icon { color: var(--surface); }

/* ⚠️ І ОБРАНИЙ РОЗМІР — ТЕЖ БІЛИЙ, з тієї самої причини, що й знак. Правило
   `.psize__label.is-chosen` фарбує підпис у `--ink` (#232221), щоб «обрано M»
   відрізнялось тоном від «нічого не обрано». У колонці сторінки тло
   світле, і це працює; у ТЕМНІЙ плитці той самий тон дає 1.1:1 — обраний
   розмір зникав із поля зовсім. Замовник: «нема розміра в плашці
   розміра, потрібний білий колір».

   Порожнє поле приглушене `--ctl-off` (правило вище) — тобто різниця
   «обрано / не обрано» виражена тут тим самим, чим і в колонці: ТОНОМ, лише
   переведеним на темне тло. */
.pbar .psize__label.is-chosen { color: var(--surface); }

.pbar .psize__list {
  /* Список у панелі — на світлому тлі, як і в колонці: у каноні
      панель списку біла на темній плитці. */
  color: var(--ink);
}

/* `:not([aria-disabled])` — не прикраса селектора. Без нього скін панелі
   перебивав вимкнений стан специфічністю, і в стані «Не виготовляється»
   кнопка лишалась білою й на вигляд робочою (правка замовника, пункт 6).
   Канон дає їй ту саму пару, що на сторінці: `black/200` із підписом
   `black/600`. */
.pbar .btn:not([aria-disabled="true"]) {
  background: var(--surface);
  color: var(--ink);
}

/* КНОПКА В ПЛИТЦІ — ЗАДАНА ЧИСЛОМ, а не залишком: 151 на M
   5 400 грн» 145 завширшки, тобто падінг там 24, а не 48 сторінкової кнопки.

   Без цього ряд не сходився: з падінгом 48 кнопці треба 241, і вона
   стискалась до `min-width` 216 — підпис із ціною ламався на два рядки прямо
   в плитці. Ширина назви на T при цьому виходить залишком (196 при
   канонічних 210): у самому каноні коробка назви налазить на кнопку на 14. */
.pbar .btn {
  flex: none;
  min-height: var(--pbar-btn-h);
  margin-inline-start: var(--s-16);
  inline-size: var(--pbar-buy-w);
  /* `min-width` кнопки — 216 із легенди; тут воно переважувало задану ширину,
     і на M кнопка виходила 216 замість канонічних 151, зʼївши поле поруч. */
  min-inline-size: 0;
  padding-inline: var(--s-24);
}

/* Ціна в підписі кнопки є на T і D і
   немає на M — у 151 px вона й не влізла б.
   Ховається вона, а не переписується підпис: інакше про ширину екрана знали
   б двоє — CSS і двигун. */
.btn__price { display: none; }

@media (min-width: 768px) {
  .btn__price { display: inline; }
}

@media (hover: hover) {
  .pbar .btn:not([aria-disabled="true"]):hover { background: var(--black-200); }
}

.pbar .btn-round {
  inline-size: var(--pbar-round);
  block-size: var(--pbar-round);
  width: var(--pbar-round);
  height: var(--pbar-round);
  margin-inline-start: var(--s-16);
  border-color: var(--pbar-line);
  background: transparent;
  color: var(--surface);
}

.pbar .btn-round + .btn-round { margin-inline-start: var(--s-8); }

@media (hover: hover) {
  .pbar .btn-round:hover {
    background: var(--surface);
    border-color: var(--surface);
    color: var(--ink);
  }
}

/* Десктопний склад панелі: мініатюра, бейдж-розпірка, ширше поле. */
@media (min-width: 1024px) {
  .pbar .btn { flex: initial; inline-size: auto; padding-inline: var(--s-48); }
  .pbar__thumb { display: block; }
  .pbar__badge { display: inline-flex; }
  .pbar__inner { padding-inline: var(--s-16); }
  .pbar__title { margin-inline: var(--s-16) 0; }
  .pbar .psize-wrap { margin-inline-start: var(--s-24); }
}

/* --------------------------------------------------------------------------
   ВІДЕО-ЛАЙТБОКС —.

   СКРИМ ПОЧИНАЄТЬСЯ ПІД ШАПКОЮ, а не від верху вікна: у каноні затемнення
   `Rectangle 211` стоїть на y = 60 при шапці 60, тобто шапка лишається
   видимою і чинною. Тон — `black/900` при 0.2, тобто вже наявний
   `--scrim-soft`, а не новий колір.

   КАДР ЗАДАНИЙ ВІДНОШЕННЯМ, А НЕ ДВОМА ЧИСЛАМИ: 1216×684 — це рівно 16:9,
   і саме відношення переживе будь-яку ширину вікна, тоді як 684 не існує
   ніде, крім артборда 1920.
   -------------------------------------------------------------------------- */
.lightbox {
  position: fixed;
  inset: var(--header-h) 0 0;
  z-index: 60;
  display: grid;
  place-items: center;
}

/* `display: grid` сильніший за типове `display: none` атрибута `hidden` — і
   саме так лайтбокс стояв відкритим на першому ж знімку. Правило потрібне
   КОЖНОМУ вузлу, якому CSS призначає `display` і якого ховає атрибут. */
.lightbox[hidden] { display: none; }

.lightbox__scrim { position: absolute; inset: 0; background: var(--scrim-soft); }

/*   окреме число: власне число обабіч зробило б плеєр вужчим за саму сторінку
   на планшеті, де стеля й так не діє. По контейнеру він виходить 772 при
   820, і та сама стеля 1216 на десктопі.

   По висоті 100 лишаються: там це не поле сторінки, а запас, щоб на низькому
   вікні плеєр не впирався в край. */
.lightbox__box {
  position: relative;
  inline-size: min(var(--pvideo-w), calc(100% - 2 * var(--gutter)));
  max-block-size: calc(100% - 2 * var(--s-100));
  aspect-ratio: 16 / 9;
  background: var(--black-900);
}

.lightbox__frame { position: absolute; inset: 0; }

.lightbox__frame iframe {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  border: 0;
}

/* Хрестик стоїть УСЕРЕДИНІ кадру, 32 від верхнього й правого краю
, а не над ним і не поруч. */
.lightbox__close {
  position: absolute;
  inset-block-start: var(--pvideo-inset);
  inset-inline-end: var(--pvideo-inset);
  display: grid;
  place-items: center;
  inline-size: var(--pvideo-close);
  block-size: var(--pvideo-close);
  border: var(--hairline) solid currentColor;
  border-radius: 50%;
  /* ПРОЗОРИЙ У СПОКОЇ, білий при наведенні. Канон малює суцільне біле коло,
     але суцільна плашка ріже кадр, а обведення того самого діаметра лишає
     видимим те, що під ним. Тон білий, бо кнопка лежить на відео, а не на
     сторінці. */
  background: transparent;
  color: var(--surface);
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

/*   мусять збігатися, інакше обведення їде разом із масштабом. */
.lightbox__close svg { width: 40px; height: 40px; stroke-width: 1.4; }

@media (hover: hover) {
  .lightbox__close:hover { background: var(--surface); color: var(--ink); }
}

/* Фокус НЕ фарбує кнопку, а лише обводить: двигун переводить фокус у модалку
   на відкритті (без цього клавіатура лишається на сторінці під скримом), і
   заливка на фокусі означала б, що кнопка приїжджає вже «наведеною» — тобто
   правка замовника «прозора, фарбується тільки при наведенні» не діяла б
   жодного разу. Контур глобальний тон `--ink` тут не годиться: під кнопкою
   темний кадр. */
.lightbox__close:focus-visible { outline-color: var(--surface); }

/* --------------------------------------------------------------------------
   (подяка). Обидві намальовані ЛИШЕ на 1920; планшетного й мобільного
   артбордів дизайнер не малює (замовник: «820 та 375 не малює
   сюди дизайнер, але резиновою модалку ти можеш і сам зробити»).

   ТОМУ ТУТ НЕМАЄ ЖОДНОГО `@media`. Вікно тече: ширина — стеля канону або
   сторінка мінус два гутери; сітка полів складається з двох колонок в одну
   сама, `auto-fit` по виведеному мінімуму поля; висота ніде не задана — вона
   сума проміжків, тому росте разом зі стовпчиком. Придуманий поріг тут був
   би третім класом порога на тому, чого канон не називав.

   ВІКНО ПО ЦЕНТРУ ВІКНА, СКРИМ — ПІД ШАПКОЮ. Канон: вікно 238…843 при
   висоті 1080, тобто центр 540.5 проти центра екрана 540; затемнення
   `Rectangle 211` починається на y = 60 при шапці 60. Це не те саме, що
   відео-лайтбокс (він центрується в тому, що НИЖЧЕ шапки) — і саме тому
   центрування тут своє, а не спільне з ним.
   -------------------------------------------------------------------------- */
.cust {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  /* Ряд заданий ЯВНО (`minmax(0, 1fr)`), а не лишений автоматичним: у
     автоматичного ряду висота береться з вмісту, і стеля `max-block-size:
     100%` на вікні резолвилась у його ж власну висоту — тобто не діяла
     зовсім. На 375×667 вікно виходило 797 і йшло за екран. */
  grid-template-rows: minmax(0, 1fr);
  place-items: center;
  /* Поле обабіч — ГУТЕР СТОРІНКИ, як у лайтбокса: власне число зробило б
     вікно вужчим за саму сторінку. По вертикалі 24 — це не поле сторінки, а
     запас, щоб на низькому вікні вікно не впиралося в край. */
  padding: var(--s-24) var(--gutter);
}

/* `display: grid` сильніший за типове `display: none` атрибута `hidden` — те
   саме правило, що в лайтбокса, і з тієї самої причини. */
.cust[hidden] { display: none; }

.cust__scrim {
  position: absolute;
  inset: var(--header-h) 0 0;
  background: var(--scrim-soft);
}

.cust__box {
  position: relative;
  display: flex;
  flex-direction: column;
  inline-size: min(var(--cust-w), 100%);
  /* Стеля висоти — щоб на низькому вікні (планшет лежачи, 375×667) вікно
     не вилазило за екран: тіло тоді гортається, смуга кадру лишається. */
  max-block-size: 100%;
  background: var(--surface);
}

/*   МІНІМАЛЬНА, а не задана: нижче стіни колонка заголовка вужчає, рядків
   стає більше, і смуга мусить рости, а не різати текст. */
.cust__band {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  margin: var(--cust-inset) var(--cust-inset) 0;
  min-block-size: var(--cust-band-h);
  padding: var(--cust-close-inset);
  overflow: hidden;
}

.cust__img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Затемнення кадру — `Rectangle 211` поверх знімка: `black/900` при 0.2,
   тобто наявний `--scrim-soft`, а не новий колір. Без нього білий заголовок
   на світлій ділянці знімка не читається. */
.cust__band::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--scrim-soft);
}

/* НАДПИСУ «Індивідуальне замовлення» В КУТІ СМУГИ НЕМАЄ, хоч канон його
   малює: замовник — «цього тексту бути не повинно
   в лівому кутку». Імʼям вікна для скрінрідера він теж більше
   не служить — його несе `aria-label` самого діалога. */
.cust__head,
.cust__close { position: relative; z-index: 1; }

/* Заголовок стоїть ПО ЦЕНТРУ смуги в обох станах: у формі 300…384 при смузі
   246…438 (центр 342 проти 342), у подяці 494…522 при 412…604 (508 проти
   508). Тобто це одна фігура з двома підписами, а не два різні місця.

   Стеля ширини — 267 з канону, але не ширша за смугу без місця під ЗНАК
   хрестика: рядки заголовка центровані, тож кожен з них розходиться від
   центра на половину своєї довжини, і на вузькому вікні найдовший дійшов би
   до знака. Запас 32 = 48 (коло) − 24 (інсет кола) + 8: коло стоїть на 24
   від краю смуги, знак усередині займає 16…32 з 48, тобто чиста зона від
   краю смуги — 56, а падінг смуги вже дає 24 з них. На 1920 стеля не діє
   (смуга дає 448 при канонних 267). */
.cust__head {
  inline-size: min(var(--cust-head-w), 100% - 2 * var(--s-32));
  color: var(--surface);
  font-size: var(--cust-head-fs);
  line-height: var(--cust-head-lh);
  font-weight: var(--fw-medium);
  text-align: center;
}

/* Хрестик —, коло 48 на 24 від верхнього й правого краю СМУГИ.
   `stroke #E7E7E7`, `opacity .2`, заливки немає зовсім), знак білий 1.5 з
   круглими кінцями. Заливки в спокої немає й у каноні — на відміну від
   лайтбокса, де біле коло було намальоване й знято правкою замовника. */
.cust__close {
  position: absolute;
  inset-block-start: var(--cust-close-inset);
  inset-inline-end: var(--cust-close-inset);
  display: grid;
  place-items: center;
  inline-size: var(--cust-close);
  block-size: var(--cust-close);
  border: var(--hairline) solid var(--cust-num-face);
  border-radius: 50%;
  background: transparent;
  color: var(--surface);
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

.cust__close svg { width: var(--cust-close); height: var(--cust-close); }

@media (hover: hover) {
  .cust__close:hover { background: var(--surface); color: var(--ink); }
}

/* Контур фокуса білий: під кнопкою темний кадр, і глобальний `--ink` на
   ньому не видно. Та сама причина, що в хрестика лайтбокса. */
.cust__close:focus-visible { outline-color: var(--surface); }

/* ТІЛО. Проміжок між дітьми 16 (підпис → сітка), а між ГРУПАМИ 24 — тому
   друга група й кнопка додають до нього ще 8, а не заводять свій ряд. */
.cust__body {
  display: grid;
  gap: var(--s-16);
  /* Обабіч — `--cust-pad-x` (нижче стіни планшета це інсет смуги, тобто
     поля йдуть урівень із фото), знизу — канонні 32. */
  padding: var(--cust-lead) var(--cust-pad-x) var(--cust-pad);
  /* Без `min-block-size: 0` елемент флексу не стискається нижче свого вмісту,
     і стеля висоти вікна не діяла б зовсім: на 375×667 вікно вилазило б за
     екран замість того, щоб гортатись усередині. */
  min-block-size: 0;
  overflow-y: auto;
}

/* `display: grid` сильніший за типове `display: none` атрибута `hidden` —
   і саме так прихована подяка стояла видимою під формою, додаючи вікну 93 px
   порожнечі. Правило потрібне КОЖНОМУ вузлу, якому CSS призначає `display` і
   якого ховає атрибут. */
.cust__body[hidden] { display: none; }

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

.cust__group + .cust__group,
.cust__send { margin-block-start: var(--s-8); }

.cust__legend {
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* СІТКА ПОЛІВ — дві колонки по 252 з проміжком 8 (512 = 2×252 + 8). Колонок
   тут не «дві», а «скільки влізе»: `auto-fit` по виведеному мінімуму поля.
   На 1920 виходить рівно канон, нижче ~507 — один стовпчик, і жодного
   порога в коді для цього не треба. */
.cust__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(var(--cust-fld-min), 1fr));
  gap: var(--cust-gap);
}

.cust__fld {
  display: flex;
  align-items: center;
  gap: var(--cust-gap);
  min-inline-size: 0;
  block-size: var(--cust-fld-h);
  padding-inline: var(--cust-fld-x);
  border: var(--hairline) solid var(--field-line);
  transition: border-color var(--dur-fast) var(--ease);
}

/* Віконце числа стоїть на 8 від правого краю поля (188…244 при полі 252), а
   підпис — на 16 від лівого. Поле тому й несе РІЗНІ падінги обабіч. */
.cust__fld--num { padding-inline-end: var(--s-8); }

.cust__fld:focus-within { border-color: var(--field-focus); }

.cust__lab {
  flex: 1 1 auto;
  min-inline-size: 0;
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  transition: color var(--dur-fast) var(--ease);
}

/* Заповнене поле темнить ПІДПИС — так канон відрізняє його від порожнього
   (: «Обхват грудей*» основним тоном при числі 32, решта пʼяти
   полів того ж кадру — приглушеним). Заливка поля при цьому не змінюється,
   тому сайтовий закон `.field__input` (світлішання рамки й тла) сюди НЕ
   переноситься: у макеті модалки його немає жодного разу. */
.cust__fld--num:has(.cust__val:not(:placeholder-shown)) .cust__lab {
  color: var(--ink);
}

.cust__val {
  flex: none;
  inline-size: var(--cust-num-w);
  block-size: var(--cust-num-h);
  border: var(--hairline) solid var(--cust-num-face);
  background: var(--cust-num-face);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  text-align: center;
}

.cust__inp {
  flex: 1 1 auto;
  min-inline-size: 0;
  block-size: 100%;
  border: 0;
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.cust__val::placeholder,
.cust__inp::placeholder { color: var(--label-ink); }

/* ФОКУС ОБВОДИТЬ ПОЛЕ, А НЕ ВІКОНЦЕ ЧИСЛА. Глобальний контур лягав
   на сам `<input>`, тобто на коробку 56×40 усередині поля 252×56, — і разом
   із потемнілою рамкою поля виходили дві рамки, менша з них жирна
   (замовник: «коли стоїть курсор, не повинно бути таке жирне
   поле для вводу»).

   Індикатор фокуса при цьому НЕ ЗНИКАЄ — він переїжджає на поле цілком і
   лягає врівень із його рамкою (відʼємний відступ у товщину контуру), як у
   `.field__input` після. Розкладка не їде: контур геометрії не займає
   а `:has()` не вмикається від наведення. */
.cust__val:focus-visible,
.cust__inp:focus-visible { outline: none; }

.cust__fld:has(:focus-visible) {
  outline: var(--focus-ring) solid var(--field-focus);
  outline-offset: calc(-1 * var(--focus-ring));
}

/* Кнопка вужча за сторінкову: 168 проти 216 при підписі 83, тобто 42
   обабіч, і 56 заввишки замість десктопних 64. Тому вона перекриває три
   числа `.btn`, а не заводить власну кнопку. */
.cust__send {
  justify-self: center;
  min-inline-size: var(--cust-send-w);
  min-height: var(--cust-fld-h);
  padding-inline: var(--cust-send-x);
}

/*   смуга 192, ті самі 21 під нею, абзац 348 у два рядки, 32 знизу. Разом
   293, і це знову сума, а не задане число. */
.cust__done {
  justify-self: center;
  inline-size: min(var(--cust-done-w), 100%);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  text-align: center;
}

/* --------------------------------------------------------------------------
   ТОСТ «ДОДАНО В КОШИК» —, 267×152.

   Стоїть у правому куті: 16 під шапкою і `--toast-inset` від краю ВІКНА.
   Інсет — не гутер (на 1920 гутер 48, а тост стоїть на 16) і не константа:
   три вузли дають 16 · 24 · 16, розбір — у токені.

   КІЛЬЦЕ НАВКОЛО ХРЕСТИКА — не декор, а зворотний відлік: канон малює його
   недобігленим, тобто показує рівно те, що
   тост живе обмежений час. Тривалість одна на двох носіїв: CSS крутить нею
   кільце, JS по ній закриває.
   -------------------------------------------------------------------------- */
.toast {
  position: fixed;
  inset-block-start: calc(var(--header-h) + var(--s-16));
  inset-inline-end: var(--toast-inset);
  z-index: 40;
  inline-size: var(--toast-w);
  max-inline-size: calc(100% - 2 * var(--toast-inset));
  padding: var(--toast-pad);
  background: var(--black-900);
  color: var(--surface);
  /* Стеля висоти: товарів у плашці буває багато, і список, довший за вікно,
     сховав би власну кнопку «Оформити замовлення». Далі — гортання. */
  max-block-size: calc(100dvh - var(--header-h) - 2 * var(--s-16));
  overflow-y: auto;
}

.toast[hidden] { display: none; }

.toast__body { display: flex; align-items: flex-start; gap: var(--s-16); }

/* `display` того самого рівня ваги стоїть ПІСЛЯ `[hidden]` у таблиці браузера
   й перебиває його — схований ряд-зразок був би видимим. Та сама пастка, що з
   плашкою дій «Обраного». */
.toast__body[hidden] { display: none; }

/* Кілька товарів — стовпчик в одній плашці (власниця, 12.09): «Перемістити в
   кошик» кладе їх разом, і разом же вони мусять бути названі. Крок між
   рядами — той самий 16, що й між кадром і назвою всередині ряду. */
.toast__body + .toast__body { margin-block-start: var(--s-16); }

/* ⚠️ СХОВАНИЙ СУСІД ЗАЛИШАЄ СВІЙ ВІДСТУП. Ряд-зразок лежить у розмітці
   першим і схований, але правило «сусід після сусіда» не питає, чи видно
   попередника: перший ВИДИМИЙ ряд отримував поле згори, і в плашці було
   32 зверху проти 16 знизу (власниця, 12.09: «відступи однакові зверху та
   знизу»). Відступ належить проміжку МІЖ видимими рядами — отже, ряд після
   схованого його не має. */
.toast__body[hidden] + .toast__body { margin-block-start: 0; }

/*   кошика губиться на світлому кадрі. */
.toast__thumb {
  position: relative;
  flex: none;
  inline-size: var(--toast-thumb-w);
  block-size: var(--toast-thumb-h);
  display: grid;
  place-items: center;
  isolation: isolate;
}

.toast__thumb img {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  z-index: -2;
}

.toast__thumb::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--black-900);
  opacity: .4;
  z-index: -1;
}

.toast__thumb svg { width: var(--icon); height: var(--icon); }

/* ПЛАШКА ЗІ СЛОВОМ, А НЕ З ТОВАРОМ (згода на cookie, «дякуємо» форми).
   Ряд плашки кошика — це знімок + назва 110 px + хрестик, і речення в такій
   колонці ставало вузьким стовпчиком із порожнечею праворуч (власниця,
   12.09: «оформи гарно текст, без дирки праворуч»). Тут знімка немає, тому
   текст бере всю ширину плашки, а сама плашка ширша: речення про cookie
   довше за назву товару.

   Ширина названа окремим токеном, а не числом: плашка товару лишається
   такою, якою її намалював макет. */
.toast--word { inline-size: var(--toast-word-w); }

/* ⚠️ ЗГОДА НА COOKIE — У НИЖНЬОМУ КУТІ, А НЕ У ВЕРХНЬОМУ. Верхній кут
   зайнятий плашкою подій («Додано в кошик», «Дякуємо, ми отримали
   звернення»): обидві коробки стояли на тих самих координатах і накладались
   — видно було текст однієї й кнопку другої. Згода живе довго (поки не
   відповіли), подія — пʼять секунд, тож розводимо їх по різних кутах, а не
   черзі. */
.toast--consent {
  inset-block-start: auto;
  inset-block-end: var(--toast-inset);
}

.toast--word .toast__title {
  inline-size: auto;
  flex: 1 1 auto;
  margin-inline-end: 0;
}

/* Посилання всередині речення — того ж кольору, що й текст плашки: вона
   темна, а канонний колір посилання на ній не читається. */
.toast--word .toast__title a {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: .2em;
}

/* Ширина назви — місце переносу, як і в липкій панелі: канон дає 110 при
   `auto`, а не рахується числом. */
.toast__title {
  margin: 0;
  flex: none;
  inline-size: 110px;
  margin-inline-end: auto;
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

.toast__close {
  position: relative;
  flex: none;
  display: grid;
  place-items: center;
  inline-size: var(--toast-close);
  block-size: var(--toast-close);
  color: var(--surface);
}

/* ПОЧАТОК ДУГИ — НА ДВАНАДЦЯТІЙ ГОДИНІ. Нуль градусів у SVG це третя година;
   поворот на −90° переносить його вгору, а `<circle>` малюється за
   годинниковою стрілкою — тобто дуга набігає вправо через верх, як стрілка
   годинника. */
.toast__ring { position: absolute; inset: 0; rotate: -90deg; }

.toast__ring circle {
  fill: none;
  stroke: currentColor;
  stroke-width: 1;
}

/* Доріжка приглушена, біжуча дуга — повна: інакше на темній плитці видно одне
   коло, а не два стани одного кільця. */
.toast__ring-track { opacity: .3; }

.toast__ring-run {
  /* 2πr при r = 15.5 — довжина кола, з якої й береться відлік. Пишеться
     числом тут, а не в розмітці: це властивість кривої, а не даних. */
  stroke-dasharray: 97.39;
  /* ПОВНИЙ ЗСУВ У СПОКОЇ = дуги немає. Кільце НАБІГАЄ, а не збігає: старт із
     повного кола читався б як «щось зникає», а не як «стільки лишилось». */
  stroke-dashoffset: 97.39;
}

@media (prefers-reduced-motion: no-preference) {
  .toast.is-running .toast__ring-run {
    animation: toast-countdown var(--toast-life) linear forwards;
  }

  /* Кільце спиняється разом із відліком: обіцянка «стільки лишилось» мусить
     стояти, поки стоїть таймер (WCAG 2.2.2). */
  .toast.is-paused .toast__ring-run { animation-play-state: paused; }
}

@keyframes toast-countdown {
  from { stroke-dashoffset: 97.39; }
  to   { stroke-dashoffset: 0; }
}

.toast__x { width: 18px; height: 18px; stroke-width: 1.4; }

.toast__cta {
  display: flex;
  margin-block-start: var(--s-16);
  min-height: var(--toast-cta-h);
  min-width: 0;
  inline-size: 100%;
  padding-inline: var(--s-16);
  background: var(--surface);
  color: var(--ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* `display: flex` того самого рівня ваги стоїть ПІСЛЯ `[hidden]` у таблиці
   браузера й перебиває його — схована кнопка лишалась видимою в плашці зі
   словом («Оформити замовлення» під «Дякуємо, ми отримали звернення»). Та
   сама пастка, що з рядом-зразком вище. */
.toast__cta[hidden] { display: none; }

@media (hover: hover) {
  .toast__cta:hover { background: var(--black-200); }
}

/* ==========================================================================
   --------------------------------------------------------------------------
   Це ПЛАКАТ, а не потік: знак «404» лежить ПОВЕРХ ряду фото, наїжджаючи на
   нього на сталу частку висоти кадру (≈35 % на всіх трьох якорях). Тому тут
   сітка з явними рядами й шарами, а не стос блоків.

   Композицій дві, і стіна між ними одна — `modes.desktop.from`:

        M і T D
     ┌────┬────┬────┬────┐ ┌────┬──────┬────┬────┬────┐
     │фото│ │фото│фото│ │фото│ текст│фото│фото│фото│ ряд 1
     ├────┴────┴────┴────┤ ├────┴──────┴────┴────┴────┤
     │ «404» │ │ «404» │ ряд 2 (наліз)
     ├───────────────────┤
     │ текст │ (ряду 3 немає — комірка порожня)
     └───────────────────┘

   ЧОМУ ФОТО — ДОРІЖКИ СІТКИ, А НЕ ВКЛАДЕНИЙ ПОТІК. Ширина кадру канонічна
   ЧАСТКОЮ ряду (14.706 % на D, 23.4 % на вузьких), а частка всередині
   вкладеного блока рахувалась би від НЬОГО, не від ряду, — і кадри виходили
   втричі меншими. Доріжка бере частку від ряду напряму.

   ЧОМУ ТЕКСТ ПЕРЕЇЖДЖАЄ КОМІРКОЮ, А НЕ ДУБЛЮЄТЬСЯ. Другий екземпляр рядка
   означав би два місця правки того самого тексту в CMS. Комірка — це одна
   розмітка й один текст; переставляє її `grid-area`, тобто чистий CSS.

   ПОРЯДОК У РОЗМІТЦІ ЗМІСТОВИЙ, А НЕ ВІЗУАЛЬНИЙ: `h1` іде першим, фото —
   після тексту. Читання з клавіатури й зі скрінрідера не залежить від того,
   у якій комірці що стоїть.
   ========================================================================== */
.e404 {
  display: grid;
  /* Фото · вільне поле · пара фото. Вільна доріжка `1fr` — це ПОВІТРЯ
     композиції на вузьких якорях і місце тексту на десктопі; сталої ширини
     вона не має за побудовою. */
  grid-template-columns:
    var(--e404-shot-w) 1fr var(--e404-shot-w) var(--e404-shot-w);
  column-gap: var(--e404-trio-gap);
  align-items: start;
  margin-block: var(--e404-top) var(--e404-bottom);
}

/* НИЗ СТОРІНКИ ТРИМАЄ САМА СЕКЦІЯ, А НЕ РИТМ РОЗДІЛІВ. `main > .container`
   несе `margin-block-end: var(--section-gap)`, і на вузьких якорях це 48 —
   більше за канонні 40 (підвал T на 747 при низі композиції 707). Суміжні
   відступи схлопуються по БІЛЬШОМУ, тому власні 40 секції просто не було
   видно: замір давав підвал на 753.5 замість 748.

   Ритм розділів тут ні до чого — розділ на сторінці один. Тому відступ
   контейнера знімається, і низ лишається один: `--e404-bottom`. На десктопі
   обидва числа й так дорівнюють 100, тож прийнятий якір не рухається —
   переміряно. */
main > .container:has(> .e404) { margin-block-end: 0; }

.e404__shot {
  grid-row: 1;
  inline-size: 100%;
  aspect-ratio: var(--e404-shot-ratio);
  object-fit: cover;
}

.e404__shot--1 { grid-column: 1; }
.e404__shot--2 { grid-column: 3; }
.e404__shot--3 { grid-column: 4; }

/* ХТО ПОВЕРХ КОГО. Знак і фото в макеті СПЛЕТЕНІ, і плетіння різне на різних
   артбордах — це порядок шарів у каноні, не здогад із картинки:

        над ним «7» (x=976). Тобто знак проходить позаду другого кадру й
        поперед третього.
     T стоїть ПІСЛЯ всієї групи фото — знак поверх усіх трьох.
        решти — плетіння таке саме, як на D.

   ⚠️ Артборди між собою тут не збігаються, і це названо замовнику: якщо
   плетіння випадкове, знак ставиться поверх усіх кадрів на всіх якорях
   однією правкою (зняти `z-index` у `.e404__shot--2`).

   Порядок у розмітці цього не задає й задавати не може: `h1` стоїть першим
   зі змістових причин, тобто малювався б ПІД усіма фото. */
.e404__glyph { position: relative; z-index: 1; }
.e404__shot--2 { position: relative; z-index: 2; }

/* ЧЕТВЕРТЕ ФОТО Є ТІЛЬКИ НА ДЕСКТОПІ. На планшеті канон малює його теж, але
   на x=1009 при ширині артборда 820 — за краєм полотна, тобто на екрані його
   немає жодного разу (, рішення замовника 22.08: три фото, ті самі, що на
   мобільному). У розмітці воно лишається — на D воно видиме. */
.e404__shot--4 { grid-column: 5; display: none; }

/* ЗНАК «404» — асет, а не текст. У `@font-face` проєкту оголошено
   `font-weight: 400 500`, а знак важчий: живим текстом він виводився б
   синтетичним жирним, тобто не тим шрифтом. Прецедент той самий, що з
   підписом «Juliya Kros» у підвалі (`img/brand/wordmark.svg`).

   Alt у нього НЕ порожній: тут «404» — зміст, і саме він робить `h1`
   заголовком. */
.e404__glyph {
  grid-row: 2;
  grid-column: 1 / -1;
  inline-size: var(--e404-glyph-w);
  /* СТЕЛІ `max-inline-size: 100%` ТУТ БУТИ НЕ МОЖЕ. Вона стояла тут як
     звичка-запобіжник і мовчки скасовувала мобільний токен: на 375 канон дає
     знаку 344 при контейнері 343, тобто він виходить
     за контейнер на 1 px з КОЖНОГО боку по чорнилу цифр. Токен просив
     100.3 %, стеля віддавала рівно 100 %, і замір ловив −1.0 px ширини —
     рівно на межі допуску, тобто майже непомітно.

     Виняток на це вже стояв у `check-container-edges.js` із цитатою того
     самого вузла: перевірка чекала напливу, а CSS його не давав. Напливу
     нікуди рости — праворуч знак кінчається на 359 при вікні 375. */
  margin-block-start: calc(var(--e404-rise) * -1);
  margin-inline-start: var(--e404-glyph-x);
}

/* ПРОПОРЦІЯ ЗНАКА РІЗНА НА ТРЬОХ АРТБОРДАХ: 895/375 = 2.387, 765/323 =
   2.368, 344/145 = 2.372 — дизайнер масштабував контури нерівномірно.
   Асет один, тому коробку задає пропорція СМУГИ, а картинка заповнює її
   `fill`: без цього висота йшла з пропорції файла й давала −2.5 px на 820 і
   −1.3 px на 375 при допуску 1. */
.e404__glyph { aspect-ratio: var(--e404-glyph-ratio); }

/* ЗАЛИВКА — З ТОКЕНА, а не з файла.  Роль тут `--ink` — знак це `h1`,
   тобто основний текст; сама роль і є `var(--black-900)`, тож заміряний
   колір не змінився (rgb(35, 34, 33) до і після).

   Саме заради цього рядка знак стоїть інлайном: зовнішній файл у `<img>`
   змінних CSS не бачить. */
.e404__glyph svg {
  display: block;
  inline-size: 100%;
  block-size: 100%;
  fill: var(--ink);
}

/* Текст і кнопка. На M і T — рядок під знаком: повідомлення ліворуч, кнопка
   праворуч. `baseline` тут не примха: кнопка вища за рядок тексту на 4 px
   (значок 24 проти рядка 20), і вирівнювання КОРОБОК підняло б її текст на
   2 px над текстом повідомлення. Канон вирівнює саме рядки. */
.e404__lead {
  grid-row: 3;
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: baseline;
  /* ЗАЗОРУ МІЖ НИМИ НЕМАЄ — є ЗАЛИШОК. На 375 канон дає 226 + 114 при
     контейнері 343, тобто рівно 3 px між ними; будь-який заданий зазор
     стиснув би повідомлення (заміряно: 213.5 замість 226 при `gap: 16`).
     Ширину тримає `space-between`, а не число.

     Перенос потрібен нижче мобільного якоря: на 320 контейнер 288, і два
     блоки по 226 та 114 в один рядок не стають ніяк. */
  column-gap: 0;
  row-gap: var(--s-16);
  /* ВІДСТУП МІРЯЄТЬСЯ ДО ПОВІДОМЛЕННЯ, А НЕ ДО КНОПКИ. Кнопка стоїть на 2 px
     вище за рядок (див. `.e404__back`), тому в каноні її y завжди на 2
     менший: T 665 при повідомленні 667, D 158 при 160, M 388 при 390.
     Спершу тут стояло планшетне 32 — знята саме кнопкова координата, і вся
     пара сідала на 2 px вище. Число одне на M і на T: 34. */
  margin-block-start: var(--e404-lead-top);
}

.e404__msg {
  flex: none;
  inline-size: var(--e404-msg-w);
  color: var(--ink);
}

/* Кнопка не стискається і не переносить рядок: у зазорі десктопної
   композиції для неї рівно 114 px (82 + 8 + 24), і без обох правил вимір
   давав 89.5×40 — тобто «На / головну» у два рядки при значку 23.2 замість
   24. Значок розміру не мав узагалі: у розмітці в нього тільки `viewBox`,
   а спільного правила `.link svg` у бібліотеці немає. */
.e404__back {
  flex: none;
  /* КНОПКА НАВИСАЄ НАД РЯДКОМ, а не опускає його. Її коробка вища за рядок
     тексту рівно на різницю значка й інтерліньяжу: (24 − 20) / 2 = 2 px
     згори й стільки ж знизу. Без цього `baseline` вирівнював рядки правильно,
     але опускав УСЮ пару на 2 px — повідомлення сідало на 163 замість 160.
     Число не підібране: воно виведене з двох токенів і міняється разом
     із ними. */
  margin-block-start: calc((var(--icon) - var(--lh-body)) / -2);
}
.e404__back .link__text { white-space: nowrap; }

.e404__back svg {
  flex: none;
  inline-size: var(--icon);
  block-size: var(--icon);
}

/*   живе у стіні режиму 768, а не у власному вікні `max-width`: власне вікно
   завело б третій клас порогів, а їх у проєкті рівно два — стіна режиму й
   поріг складу. */
@media (min-width: 768px) {
  .e404__shot--2 { z-index: auto; }
}

@media (min-width: 1024px) {
  /* ДЕСКТОПНА КОМПОЗИЦІЯ. Ряд фото ВУЖЧИЙ за контейнер: 1360 при контейнері
     1824, поля по 232 з обох боків. Це не гутер — крихти вище лишаються на
     гутері 48; це власне поле композиції. */
  .e404 {
    grid-template-columns:
      var(--e404-shot-w) 1fr var(--e404-shot-w) var(--e404-shot-w)
      var(--e404-shot-w);
    inline-size: min(100%, var(--e404-row-w));
    margin-inline: auto;
  }

  /* Текст переїжджає у вільну доріжку першого ряду — той самий зазор, який
     на вузьких якорях лишається порожнім. */
  .e404__lead {
    grid-row: 1;
    grid-column: 2;
    margin-block-start: 0;
  }

  .e404__shot--4 { display: block; }

  /* Плетіння вертається: на десктопі другий кадр знову поверх знака. */
  .e404__shot--2 { z-index: 2; }
}

/* ==========================================================================
   ТЕКСТОВА СТОРІНКА (`doc`). Канон — «Політика конфіденційності»:
    (D) · (T) · (M).

   Уся сторінка — ОДНА сітка слотів, і це знято з координат, а не вгадано:

     D шість слотів по 200 із зазором 32 (крок 232, старт 280, край 1640).
        Фото стоять у слотах 1, 2, 4, 6; заголовок — від слота 2 на три
        слоти (664 = 3·200 + 2·32); текст — від слота 2 на чотири
        (896 = 4·200 + 3·32, канон 895).
     T чотири слоти по 181 із зазором 16 (4·181 + 3·16 = 772 = контейнер).
     M чотири слоти по 80 із зазором 8 (4·80 + 3·8 = 344 проти контейнера
        343 — слоп макета на 1 px).

   Тому доріжки задані як `repeat(N, 1fr)`, а не відсотками: на T і D слот
   виходить ЦІЛИМ числом сам (181 і 200), а на M сітка чесно віддає 79.75
   замість намальованих 80 — тобто помилка макета не переїжджає у верстку.
   ========================================================================== */

.doc {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: var(--doc-shot-gap);
}

.doc__title {
  grid-row: 1;
  grid-column: 1 / -1;
  margin-block-start: var(--doc-title-top);
  max-inline-size: min(var(--doc-title-w), 100%);
  /* ПЕРЕНОСИТЬСЯ РЯДОК, А НЕ СЛОВО. Тут стояло `hyphens: auto`, і на
     телефоні замовника заголовок ішов «Політика конфі-денційності»
. Причина не в переносі як такому: слово
     «конфіденційності» на 40 px рендериться 313, а канонна коробка
     заголовка — 314, тобто запас був ОДИН піксель, і будь-яка різниця
     рендера з'їдала його. Коробка розширена в токені (`--doc-title-w`),
     автоперенос знято.

     `break-word` лишається останнім рубежем і працює тільки там, де слово
     ширше за САМ контейнер: на 320 контейнер 288 при слові 313, і без
     цього рядка сторінка поїхала б убік на 25 px. Він ріже без дефіса —
     дефіс дає рівно `hyphens`. */
  hyphens: manual;
  overflow-wrap: break-word;
  font-size: var(--fs-doc-title);
  line-height: var(--lh-doc-title);
  font-weight: 500;
}

.doc__shot {
  grid-row: 2;
  margin-block-start: var(--doc-shots-top);
  inline-size: 100%;
  aspect-ratio: var(--doc-shot-ratio);
  object-fit: cover;
}

.doc__shot--1 { grid-column: 1; }
.doc__shot--2 { grid-column: 2; }
.doc__shot--3 { grid-column: 4; }

/* ЧЕТВЕРТЕ ФОТО Є ТІЛЬКИ НА ДЕСКТОПІ. На планшеті канон малює його на
   x = 1009 при ширині артборда 820 — за краєм полотна; вузол поза межами
   свого фрейму джерелом не є. На мобільному його немає й у макеті. */
.doc__shot--4 { display: none; }

.doc__text {
  grid-row: 3;
  grid-column: 1 / -1;
  margin-block-start: var(--doc-text-top);
  font-size: var(--fs-doc-body);
  line-height: var(--lh-doc-body);
}

/* НИЗ СТОРІНКИ ТРИМАЄ `--section-gap`, і власного токена тут немає навмисно:
   канон дає 48 · 48 · 100 від низу тексту до підвалу — рівно ті числа, які
   вже стоять у `main > .container`. Другий запис того самого числа був би
   дублем. */

/* Розділи розділені лінією `#E7E7E7` — це рівно `--line`, звірено з вузлом
   однакове: 40 на D, 32 на T і M.

   ⚠️ Знизу віднімається товщина лінії. У Figma роздільник — вектор нульової
   висоти, а обведення 1 px лягає НА нього по пів пікселя з боку (`inset
   -0.5px 0` в експорті вузла), тобто від тексту до тексту рівно 80. Бордюр
   же займає свій піксель ПОРУЧ, і без цієї поправки кожен розділ їхав би на
   1 px нижче — дев'ять роздільників дають 9 px до низу сторінки. */
.doc__block + .doc__block {
  margin-block-start: var(--doc-rule-gap);
  padding-block-start: calc(var(--doc-rule-gap) - var(--hairline));
  border-block-start: var(--hairline) solid var(--line);
}

.doc__h {
  font-size: var(--fs-doc-h);
  line-height: var(--lh-doc-h);
  font-weight: 500;
  letter-spacing: var(--ls-h1);
}

/* Ритм усередині розділу: після заголовка більше, між абзацами менше. */
.doc__block > * + * { margin-block-start: var(--doc-para-gap); }
.doc__h + * { margin-block-start: var(--doc-head-gap); }

.doc__list { list-style: none; }
.doc__list > li + li { margin-block-start: var(--doc-item-gap); }

.doc__list > li {
  position: relative;
  padding-inline-start: var(--doc-indent);
}

/* Маркер — круг 10 у коробці 16. Ліва грань круга на 3 = це
   поле коробки, а не окреме число; по вертикалі круг центрований у ПЕРШОМУ
   рядку пункту, як у каноні (коробка на 4 при рядку 24 і на 2 при 20). */
.doc__list > li::before {
  content: "";
  position: absolute;
  inset-inline-start: calc((var(--doc-dot-box) - var(--doc-dot)) / 2);
  inset-block-start: calc((var(--lh-doc-body) - var(--doc-dot)) / 2);
  inline-size: var(--doc-dot);
  block-size: var(--doc-dot);
  border-radius: 50%;
  background: var(--ink);
}


/* --------------------------------------------------------------------------
    (T) · (M).

   Це НЕ новий компонент: клямка, знак і поворот на 180° при відкритті вже
   живуть у `.fold` (підвал). Тут — інший розмірний склад того самого:
   заголовок рядка не 14/16, а роль `doc-h`, знак не 20, а 32 на десктопі.

   Рядок вищий за свій текст, і тримає його ЗНАК: на десктопі знак 32 при
   інтерліньяжі 28, тому висота рядка 32 — рівно те, що дає канон. На вузьких
   якорях навпаки: знак 22 при рядку 24. Тому власної висоти рядку не задано —
   її дає більший із двох, як у макеті.
   -------------------------------------------------------------------------- */
.doc__fold .fold__summary {
  height: auto;
  gap: var(--s-16);
  border-block-start: 0;
  font-size: var(--fs-doc-h);
  line-height: var(--lh-doc-h);
  font-weight: 500;
  letter-spacing: var(--ls-h1);
}

/* ЗНАК РОСТЕ, А ОБВЕДЕННЯ — НІ, і це не вільність: канон віддає два асети —
   22×22 і 32×32, — і в ОБОХ обведення 1.4. Тобто товщина в макеті задана в
   пікселях екрана, а не в частках знака. Рівно це й означає
   `non-scaling-stroke`; інакше на десктопі лінія була б у 1.45 раза товща за
   мобільну. Оголошення стоїть і на `svg`, і на контурах: на контурах воно
   працює (властивість не успадковується), на `svg` його читає
   `check-icon-scale`. */
.doc__fold .fold__icon,
.doc__fold .fold__icon path { vector-effect: non-scaling-stroke; }

.doc__fold .fold__icon {
  width: var(--doc-fold-icon);
  height: var(--doc-fold-icon);
  stroke-width: 1.4;
}

/* Тіло пункту. Відступ від рядка й зазор між абзацами — числа канону; ролі
   набору тут ті самі, що в решті сторінок серії: перший абзац `doc-body`
   (16/20 → 20/24), другий — плоский `--fs-body`. Нової ролі немає. */
.doc__fold .fold__body {
  margin-block-start: var(--doc-fold-body);
  /* ТІЛО НАБРАНЕ ДРІБНИМ, А ПЕРШИЙ АБЗАЦ — БІЛЬШИМ, і це знято з вузлів, а не
     на вузьких якорях обидва 16/20. Тобто перший абзац росте разом зі
     сторінкою (`doc-body`), а другий стоїть на плоскому `--fs-body`.

     Без цього рядка другий абзац успадковував би 20/24 від колонки й давав
     на десктопі три рядки замість двох — заміряно: 72 проти канонних 40. */
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.doc__fold .fold__body > * + * { margin-block-start: var(--doc-para-gap); }
.doc__fold .fold__body > :first-child {
  font-size: var(--fs-doc-body);
  line-height: var(--lh-doc-body);
}

/* ДЕВʼЯТИЙ НОСІЙ СТАНДАРТНОЇ ЛІНІЇ (вимога замовника 23.08). Лінія лягає
   під ТЕКСТ заголовка, не під знак — тому текст має власний носій у
   розмітці, як `.link__text` у посиланнях. Рух той самий: зростання з
   середини, `--dur-fast`, спільна крива; `prefers-reduced-motion` гасить
   його разом з усім рухом проєкту, бо тривалості там стають 1ms.

   Клавіатура бачить те саме, що миша: `:focus-visible` вмикає ту саму
   лінію. Наведення живе під `hover: hover`, щоб на тачі лінія не залипала
   після тапу. */
.doc__fold .fold__text {
  background-image: linear-gradient(currentColor, currentColor);
  background-repeat: no-repeat;
  background-position: 50% 100%;
  background-size: 0 var(--hairline);
  transition: background-size var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .doc__fold .fold__summary:hover .fold__text {
    background-size: 100% var(--hairline);
  }
}

.doc__fold .fold__summary:focus-visible .fold__text {
  background-size: 100% var(--hairline);
}

/* Роздільник між пунктами — той самий прийом, що в розділах «Політики»:
   верхня межа наступного пункту, з поправкою на товщину лінії (у Figma
   штрих лягає ПО пів пікселя з боку вектора, а бордюр займає свій піксель
   поруч). Після останнього пункту лінії немає — на мобільному артборді
    вона стоїть `hidden`. */
.doc__fold + .doc__fold {
  margin-block-start: var(--doc-fold-rule);
  padding-block-start: calc(var(--doc-fold-rule) - var(--hairline));
  border-block-start: var(--hairline) solid var(--line);
}

/*   (M). Роль набору своя: `--fs-h2` дає на 1920 сорок вісім, `--fs-phrase` —
   двадцять чотири, а канон тут 32/36. */
.doc--service .doc__title { max-inline-size: min(var(--svc-title-w), 100%); }

.doc__phrase {
  grid-column: 1 / -1;
  margin-block-start: var(--doc-phrase-top);
  max-inline-size: min(var(--doc-phrase-w), 100%);
  font-size: var(--fs-doc-phrase);
  line-height: var(--lh-doc-phrase);
  font-weight: 500;
}

@media (min-width: 1024px) {
  .doc__phrase { grid-column: 2 / 6; }
}

@media (min-width: 1024px) {
  /* ДЕСКТОПНА КОМПОЗИЦІЯ. Ряд ВУЖЧИЙ за контейнер: 1360 при 1824, поля по
     232 з обох боків. Крихти вище лишаються на гутері 48 — це поле
     композиції, не гутер. */
  .doc {
    grid-template-columns: repeat(6, 1fr);
    inline-size: min(100%, var(--doc-row-w));
    margin-inline: auto;
  }

  /* Заголовок і текст зсуваються на слот управо — на ту саму вертикаль 512,
     де стоїть другий кадр. Порожні слоти 3 і 5 у ряду фото — повітря
     композиції, а не діра. */
  .doc__title { grid-column: 2 / 5; max-inline-size: none; }
  .doc__text  { grid-column: 2 / 6; }

  .doc__shot--4 { display: block; grid-column: 6; }
}

/* ==========================================================================
   ПРОКРУТУ (див. накат нижче).

   ⚠️ У СТОРІНКИ ОДИН ЯКІР ІЗ ТРЬОХ. Планшетного й мобільного артбордів немає
   (замовник, 23.08). Тому все, що нижче 1920, — тимчасове й тримається
   наявними компонентами; рядок у `docs/WAIVERS.md` тримає це видимим.

   ВСЯ СТОРІНКА СТОЇТЬ НА ОДНІЙ СІТЦІ З ВОСЬМИ СЛОТІВ, і це не здогад, а
   арифметика координат: слот 200, зазор 32, старт 48 — крок 232, тобто
   48 · 280 · 512 · 744 · 976 · 1208 · 1440 · 1672, край 1872. Вісім слотів
   дають 8·200 + 7·32 = 1824 = рівно `--container`.

   На цю сітку сідає КОЖЕН блок сторінки без винятку: поля героїв (48 =
   слот 1), текстові колонки (512 = слот 3, 976 = слот 5), ряд із чотирьох
   кадрів (280 · 512 · 976 · 1440 = слоти 2 · 3 · 5 · 7) і пара кадрів
   праворуч (1440 · 1672 = слоти 7 · 8). Прийнятий `doc` — це та сама сітка,
   взята слотами 2…7: 1360 = шість слотів.

   А ширини текстових коробок сумами слотів НЕ Є ЖОДНОГО РАЗУ — вони
   протягнуті в макеті рукою. Тому вони живуть окремими токенами `--ab-*` і
   входять сюди як `max-inline-size`, а не як доріжки.

   ЗАЗОРУ РЯДКІВ У СІТКИ НЕМАЄ НАВМИСНО (`row-gap: 0`): у канону два різні
   вертикальні кроки в межах однієї сітки — 100 між блоками, 40 і 24
   всередині. Спільний `row-gap` довелось би на кожному другому блоці
   гасити відʼємним відступом; замість цього крок оголошує сам блок.
   ========================================================================== */

/* Крихти в героєві — КОРОБКА ПО ВМІСТУ. Блоковий контейнер додавав власний
   рядок (20 px) до рядка крихт (16), і коробка виходила 36 замість канонних
   пропорцію, розпірка всередині нього `1fr`, тому зайві 20 px з'їдались
   розпіркою і H1 сідав на 20 нижче за канон. Знайшов `canon-rhythm`.

   Лікується двома речами. Нульовий рядок контейнера прибирає паразитний
   рядок (а не `display: flex`: флекс дав ті самі 36 з іншої причини). І
   `grid-column: 1 / -1` — бо крихти стояли в ОДНІЙ доріжці 80 px і
   переносились у два рядки; ширини 44 · 8 · 43 · 9 · 39 канону в 80 не
   влазять за побудовою. */
.cover__crumbs { line-height: 0; }

/* Нутро кадру стоїть на ТІЙ САМІЙ сітці, що й сторінка, тому геометрія
   оголошена одним правилом на два селектори, а не другим класом у розмітці:
   `.cover` мусить працювати на будь-якій сторінці, а не тягти за собою клас
   сторінки «Про нас». */
.about,
.cover__inner {
  inline-size: 100%;
  max-inline-size: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: var(--doc-shot-gap);
  row-gap: 0;
  /* ТЯГНУТИСЬ ПО ВИСОТІ РЯДКА НЕМАЄ ЧОМУ: у рядку сітки стоять сусіди різної
     висоти (пара кадрів проти колонки з двох абзаців), і розтягнутий кадр
     ламав би свою пропорцію. Виняток один — продовження цитати, яке канон
     вирівнює НИЗОМ по низу портрета. */
  align-items: start;
  /* Набір сторінки — роль Txt-20 (`--fs-doc-body`, 16/20 -> 20/24), та сама,
     що в тексті `doc`. Нової ролі немає: десять вузлів сторінки саме на ній. */
  font-size: var(--fs-doc-body);
  line-height: var(--lh-doc-body);
}

/* Крок між смисловими блоками — `--section-gap`, і власного числа тут немає:
   на десктопі це рівно канонні 100, і саме цим кроком уже живуть решта
   сторінок. Заміряно по 17 переходах канону: 100 — девʼять разів. */
/* Верх першої секції — 38 на мобільному, і це не «приблизно 40»: підпис
   стоїть на 634. Різницю рахує `canon-rhythm`, не я. */
.about--lead { margin-block-start: var(--ab-lead-top); }
.about--mid  { margin-block-start: var(--ab-block-gap); }

/* ЗАЗОР ПЕРЕД ГЕРОЄМ 2 — ВНУТРІШНЄ ПОЛЕ ТОГО, ХТО СТОЇТЬ, а не відступ того,
   хто йде. Правило групи накату каже класти зазор у поле блока, що
   наїжджає (`main > .stack > .slider` вище), — але герой має ПРОПОРЦІЮ
   (`aspect-ratio: 1920/980`) при `box-sizing: border-box`, і будь-яке його
   поле відрізалось би від тих самих 980, зсуваючи вміст усередині кадру.
   Тому 100 віддані нижньому полю липкого блока: він теж непрозорий
   (`main > .stack > *` фарбує його `--surface`), тобто зазор так само
   лишається зафарбованим, а не прозорою смугою над кадром. Число те саме
   — `--section-gap`, у двох місцях воно не розходиться. */
.stack > .about--mid { padding-block-end: var(--section-gap); }
.about--tail { margin-block: var(--ab-block-gap) var(--section-gap); }

/* ТИМЧАСОВИЙ СКЛАД НИЖЧЕ 1024 — ОДНА КОЛОНКА. Канону в цієї смуги немає
, тому вона не вигадує композицію, а робить найпростіше чесне: блоки
   стають один під одним у порядку розмітки з тим самим кроком `--section-gap`.

   `min-inline-size: 0` — не оформлення, а умова: доріжка `1fr` за
   замовчуванням не вужча за МІНІМАЛЬНИЙ вміст, і без цього рядка довгий
   абзац розпирав сітку зсередини — документ на 320 виходив 556 при вікні
   320, тобто сторінка їздила вбік на 236 px. Знайшов це `check-hscroll`,
   а не око. */
.about--lead > *,
.about--mid  > *,
.about--tail > * { grid-column: 1 / -1; min-inline-size: 0; }

/* Крок між блоками — з таблиці переходів, а не з загального ритму сторінок.
   стиків того ж числа); вище токен указує на `--section-gap`, тобто нічого
   не міняє. Числа тут не переписані рукою — їх віднімає `canon-rhythm`. */
.about--lead > * + *,
.about--tail > * + * { margin-block-start: var(--ab-block-gap); }
/* Два стики, де канон дає НЕ крок блока, а крок абзаца: виноска й колонка
 — обидва 16. */
.about--lead > .about__col--a,
.about--tail > .about__col--c { margin-block-start: var(--s-16); }

/* Портрет — `<figure>`, і його власне `margin: 0` нижче по файлу перебивало
   крок потоку: рядок виходив [0, 48] замість рівного. Селектор із батьком
   важчий, тому крок лишається кроком. */
.about--lead > .about__portrait { margin-block-start: var(--ab-block-gap); }

/* РЯД КАДРІВ — ТРИ, А НЕ ЧОТИРИ, І ЦЕ СЛОВО ЗАМОВНИКА.
   Обидва вузькі артборди дають однакову фігуру: дві доріжки поспіль,
   280 при доріжці 79.75; планшет /92/93` — 181×255 на 24 · 221 ·
   615 при доріжці 181. Обидва набори лягають на ту саму четвірку доріжок,
   тому число тут одне на два якорі, а не два числа.
   артборда 820) — тобто ПОЗА артбордом: рештка копіювання з 1920. */
.about--mid > .about__shot { grid-row: 1; }
.about--mid > .about__shot--1 { grid-column: 1; }
.about--mid > .about__shot--2 { grid-column: 2; }
.about--mid > .about__shot--3 { grid-column: 4; }
.about--mid > .about__shot--4 { display: none; }
.about--mid > .about__col--head { margin-block-start: var(--ab-block-gap); }

/* Колонка абзаців. Два кроки, обидва канонні: 24 всередині колонки
   (пʼять разів) і 40 між заголовком та його текстом (двічі). */
/* КРОК АБЗАЦІВ У КОЛОНЦІ — 16 НА МОБІЛЬНОМУ, 24 ВИЩЕ. Число не виведене
   те саме дає крок сусідів у колонці А. На 820 той самий крок — 24 (низ
    1939 → верх 1975). */
.about__col > * + * { margin-block-start: var(--s-16); }
/*   `29506`), десктопний — 40. Число бере смуга, не це правило. */
/* ДВА КЛАСИ В СЕЛЕКТОРІ НАВМИСНО. При рівній вазі виграє нижнє правило, а
   смугове `.about__col > * + *` стоїть нижче по файлу — і крок шапки мовчки
   ставав 24 замість 40 на десктопі. Спіймав `canon-geometry` (висота блока
   276 проти 292), не око. */
.about__col.about__col--head > * + * { margin-block-start: var(--ab-head-gap); }
/* Стеля колонки — це стеля ЗАГОЛОВКА, і на мобільному вона не поширюється
. Тому нижче 768 стеля висить на самому заголовку. */

.about__aside { max-inline-size: min(var(--ab-aside-w), 100%); }
.about__col > p { max-inline-size: min(var(--ab-col-narrow), 100%); }
.about__col > p:first-child { max-inline-size: min(var(--ab-col-w), 100%); }
.about__col > .about__thanks { max-inline-size: min(var(--ab-thanks-w), 100%); }

/* ВИНОСНИЙ АБЗАЦ — 24/28 Medium. Роль не нова: це `--fs-doc-h`, заведений на
   «Політиці конфіденційності». */
.about__lead-line,
.about__tail-lead {
  font-size: var(--fs-doc-h);
  line-height: var(--lh-doc-h);
  font-weight: 500;
  letter-spacing: var(--ls-h1);
}
.about__lead-line { max-inline-size: min(var(--ab-lead-w), 100%); }
.about__tail-lead { max-inline-size: min(var(--ab-tail-lead-w), 100%); }

.about__quote,
.about__final {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  font-weight: 500;
}

/* H2 «Сьогодні…» стоїть окремо від двох інших H2 сторінки, бо на планшеті
   канон дає йому власний кегль (`--fs-ab-h2`); на десктопі токен указує на
   ту саму роль, тому третього числа не заводиться. */
/* ЖОРСТКИЙ ПЕРЕНОС СТАЄ ПРОБІЛОМ, А НЕ ЗНИКАЄ. Спершу я його прибрав
   (`display: none`) — і знімок показав «Ми продовжимостворювати речі»:
   перенос був ЄДИНИМ роздільником слів, бо в розмітці навколо нього немає
   пробілу. Підміна вмісту (`content: " "`) рядок не спиняє — розрив у `br`
   не з вмісту. Тому пробіл стоїть у РОЗМІТЦІ, перед переносом: на десктопі
   він гасне в кінці рядка, на мобільному лишається роздільником.

   ЖОРСТКИЙ ПЕРЕНОС — ДЕСКТОПНИЙ, І НА 324 ВІН ЗАЙВИЙ. У розмітці стоїть
   `<br>` після «Сьогодні», бо так ламає рядок канон 1920. Вузол
   при коробці 324 дає ТРИ рядки суцільним текстом, а з жорстким переносом
   виходило чотири — тобто зайві 32 px, які далі зсували всю сторінку. */
/* Цитата засновниці сюди НЕ входить: там жорсткий перенос після «вірила,»
   стоїть і в мобільному каноні. */
.about__col--head > h2 br,
.about__tail-lead br,
.about__final br { display: none; }
.about__col--head > h2 {
  font-size: var(--fs-ab-h2);
  line-height: var(--lh-ab-h2);
  font-weight: 500;
}
.about__quote { max-inline-size: min(var(--ab-quote-w), 100%); }
.about__col--head > h2 { max-inline-size: min(var(--ab-h2-w), 100%); }
.about__col--head > p  { max-inline-size: min(var(--ab-lead-w), 100%); }
.about__final { max-inline-size: min(var(--ab-final-w), 100%); }

/* ПОРТРЕТ. Градієнт унизу кадру — не оформлення, а умова читаності підпису:
   білий текст лежить на фото. Обидва числа з вузлів: — градієнт
   до `#201F1F` при прозорості 40 %, висота 139 з 631 кадру. */
.about__portrait {
  position: relative;
  margin: 0;
  inline-size: 100%;
  aspect-ratio: var(--ab-portrait-ratio);
  overflow: clip;
  color: var(--btn-ink);
}
.about__portrait > img { inline-size: 100%; block-size: 100%; object-fit: cover; }

.about__portrait-fade {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  block-size: var(--ab-portrait-fade);
  background: linear-gradient(to bottom, rgba(32, 31, 31, 0), rgba(32, 31, 31, .4));
}

/* Підпис центрований ПО КАДРУ, а не поставлений координатою: канон дає
   коробку 625…831 при кадрі 512…944 — центр 728 в обох. */
.about__sign {
  position: absolute;
  inset-inline-start: 50%;
  translate: -50% 0;
  inset-block-end: var(--s-24);
  /* ОДИН РЯДОК НА БУДЬ-ЯКІЙ ШИРИНІ (замовник: «на 320 не треба
     переносити в два рядки, там в 1 поміститься» — і справді поміщається:
     рядок ~175 px при кадрі 288).
     Ламало його НЕ місце й не кегль, а спосіб центрування: абсолютній
     коробці з `inset-inline-start: 50%` доступна ширина рахується від цієї
     позиції ДО ПРАВОГО КРАЮ кадру, тобто рівно половина. На 375 половина
     171 — текст ліг упритул; на 320 половина 144 — уже не вліз. `translate`
     цього не лікує: він рухає готову коробку, а перенос стався раніше.
     Тому ширина береться зі змісту, а не з залишку місця. На прийнятих
     ширинах підпис і без цього стояв одним рядком — жодне їхнє число не
     рухається. */
  white-space: nowrap;
}

/* Пара кадрів праворуч. Два слоти з тим самим зазором, що й у сітці. */
.about__pair {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  column-gap: var(--doc-shot-gap);
}
/* ПАРА КАДРІВ І РЯД КАДРІВ — РІЗНІ ПРОПОРЦІЇ, і це не недогляд. Ряд на
   мобільному канон дає 80×116 (0.690) — тією ж пропорцією, що й ряд фото
   текстових сторінок, тому він і бере `--doc-shot-ratio`. Пара ж тримає
   0.710 на ВСІХ трьох якорях: 167×235 · 181×255 · 400×564 — три вузли, одне
   число, тому воно й оголошене один раз без смуг. */
.about__pair > img { inline-size: 100%; aspect-ratio: var(--ab-pair-ratio); object-fit: cover; }
.about__shot {
  inline-size: 100%;
  aspect-ratio: var(--doc-shot-ratio);
  object-fit: cover;
}

/* ==========================================================================
   ПОВНОШИРИННИЙ КАДР (`cover`) — обидва герої сторінки.

   Висота — ПРОПОРЦІЯ вузла (`--hero-ratio`), той самий закон, за яким живуть
   перший екран Головної й цитата: канон малює 1920×980, і `100dvh − шапка`
   дало б у вікні гейта 940 замість 980.

   Плівок ТУТ ОДНА, на відміну від Головної: канон цієї сторінки дає рівно
   один прямокутник — 50 % у першому героєві, 40 % у другому
. Чорний узятий із токена проєкту; чому не з вузла.
   ========================================================================== */
.cover {
  position: relative;
  isolation: isolate;
  aspect-ratio: var(--hero-ratio);
  overflow: clip;
  background: var(--black-100);
  color: var(--btn-ink);
}

.cover__photo {
  position: absolute;
  inset: 0;
  z-index: -2;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.cover__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background: var(--ab-scrim);
}
.cover__scrim--soft { background: var(--scrim); }

/*   30 %, висота 455 з 980 кадру. Він потрібен білому абзацу, що лежить на
   світлій частині знімка. */
.cover__fade {
  position: absolute;
  inset-inline: 0;
  inset-block-end: 0;
  z-index: -1;
  block-size: var(--ab-cover-fade);
  background: linear-gradient(to bottom, rgba(32, 31, 31, 0), rgba(32, 31, 31, .3));
}

/* Вміст героя стоїть на ТІЙ САМІЙ сітці, що й решта сторінки, — тому тут
   `.about` як другий клас, а не власна розкладка. Порожній рядок `1fr`
   розсуває верх і низ: канон дає підпис унизу кадру, а не після заголовка. */
.cover__inner {
  position: relative;
  block-size: 100%;
  /* Третій рядок порожній і навмисно `1fr`: він забирає весь залишок висоти
     кадру, тому рядки з вмістом лишаються при своїх розмірах (без нього два
     рядки `auto` ділили залишок 980 між собою і заголовок їхав із 161 на
     413), а коробка-предок приколотого підпису — доріжки 1…-1 — лишається
     на всю висоту кадру. Порожній рядок дитини не має, тож рядом плиток цей
     потік більше не виглядає. */
  grid-template-rows: auto auto 1fr;
  /*     572 при низі кадру 596. Вище — 48. */
  padding-block: var(--crumbs-top) var(--ab-hero-pad-end);
  /* Вміст кадру тягнеться по СВОЇЙ коробці, а не по доріжці — так само, як у
     першому екрані Головної. Це не косметика: розтягнуті на всю ширину діти
     роблять кадр «рядом однакових плиток» для `check-grid-step`, і порожній
     рядок `1fr` між заголовком і підписом читається як крок потоку (132 px
     на 375, 522 на 820). Цей проміжок — розпірка фіксованої пропорції, а не
     ритм; жодним токеном він і не мусить бути. */
  justify-items: start;
}

/* Крихти в героєві білі по фото: ланки лишаються `--crumb-ink` (`#B8B8B7` у
   вузлі — рівно токен), а поточна сторінка стає білою замість
   `--ink`. Це один рядок, а не копія компонента. */
/* НА ВСІ ДОРІЖКИ, АЛЕ ПО ЛІВОМУ КРАЮ. Одна доріжка на 375 — це 80 px, а
   ланки канону 44 · 8 · 43 · 9 · 39 із зазорами 4 дають 159: список
   переносився в два рядки, і герой втрачав 20 px висоти. `justify-self`
   лишає коробку по вмісту, тож ширшою за канон вона не стає. */
.cover__crumbs { grid-row: 1; grid-column: 1 / -1; justify-self: start; }
.cover__crumbs [aria-current="page"] { color: var(--btn-ink); }

.cover__title {
  grid-row: 2;
  grid-column: 1 / -1;
  margin-block-start: var(--ab-crumbs-gap);
  max-inline-size: min(var(--ab-title-w), 100%);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: 500;
}

/* ПІДПИС ПРИКОЛОТИЙ ДО НИЗУ КАДРУ, А НЕ ВІДСУНУТИЙ ВІД ЗАГОЛОВКА — і це не
   стиль запису, а різниця по суті. Канон ставить його на 48 від НИЗУ кадру
   (992 при кадрі до 1040), тобто величина, яка тут задана, — відстань до
   низу, а проміжок над підписом є ЗАЛИШКОМ висоти кадру.

   Розсунути верх і низ порожнім рядком `1fr` не можна: залишок став би
   «кроком потоку» — 132 px на 375, 522 на 820, жодне з ряду токенів, — і
   `check-grid-step` назвав би це дефектом справедливо. У ряду однакових
   плиток нерівний або нетокенний крок дефектом і є; кадр рядом плиток не є.

   Абсолютне позиціювання ВСЕРЕДИНІ сітки, а не замість неї: у грід-контейнера
   абсолютна дитина з `grid-row`/`grid-column` бере своєю коробкою-предком
   саме доріжки, тому лівий край підпису й далі приходить із сітки (слот 1 у
   першому кадрі, слот 3 у другому), а не з піксельного `left`. */
.cover__note {
  position: absolute;
  grid-row: 1 / -1;
  grid-column: 1 / -1;
  inset-block-end: 0;
  max-inline-size: min(var(--ab-note-w), 100%);
}

/* Другий герой: заголовка сторінки в ньому немає, тому рядків три, а не
   чотири, і зверху те саме поле 48, що знизу. */
.cover--dark .cover__inner {
  grid-template-rows: auto 1fr;
  padding-block: var(--s-48);
}
.cover--dark .cover__title {
  margin-block-start: 0;
  max-inline-size: min(var(--ab-cover-title-w), 100%);
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: normal;
}
/* «Подіум». Коробки назви й підпису тут НЕ власні числа: канон дає 1003 і
   265 — рівно те, що вже стоїть у `--hero-title-w` і `--hero-note-w` для
   першого екрана Головної. Дизайнер узяв ті самі вузли, тому й верстка бере
   ті самі токени, а не заводить третю пару. */
/* Коробка назви — НЕ власне число сторінки: канон дає 1003, тобто рівно
   `--hero-title-w` першого екрана Головної. Те саме на «Подіумі». */
.cover--podium .cover__title,
/* Коробка H1 колекції — СВОЯ, не героєва: канон дає 300 на мобільному
   тримає 221 на мобільному. На десктопі токен указує на `--hero-title-w`,
   тобто прийняте число не рухається. */
.cover--collection .cover__title { max-inline-size: min(var(--cl-title-w), 100%); }
.cover--podium .cover__note {
  max-inline-size: min(var(--hero-note-w), 100%);
  font-size: var(--fs-hero-note);
  line-height: var(--lh-hero-note);
}
/* НИЖНЄ ПОЛЕ КАДРУ БЕРЕТЬСЯ В ПЕРШОГО ЕКРАНА ГОЛОВНОЇ, А НЕ В «ПРО НАС».
   Артбордів 820 і 375 у «Подіума» немає, і замовник назвав
   джерело прямо: «все адаптується по схожим блокам головної». Спільне
   правило `.cover__inner` тягло `--ab-hero-pad-end` — число сторінки «Про
   нас», плоске 48 на всю смугу; Головна в тій самій смузі веде рампу
   32 → 48 (`--hero-pad-y`). Заміряно: на 768 · 820 · 1023 · 1420 Головна
   давала 32 · 32 · 35 · 41, «Подіум» — 48 усюди. */
.cover--podium .cover__inner { padding-block-end: var(--hero-pad-y); }
/* СТЕЛЯ КАДРУ — та сама, що в першого екрана Головної, і з тієї самої
   причини. Пропорція працює в межах смуги, а не лише на якорі: без стелі
   кадр тягнеться разом із шириною й на верхньому кінці смуги виростає над
   задумом. Заміряно перед правкою: на 634 «Подіум» давав 913 при 540 у
   Головної, на 767 — 1104 при 540, на 1023 — 1045 при 838, на 2560 — 1307
   при 1130.
   ⚠️ ЦІНА НАЗВАНА: на 2560 це МІНЯЄ прийняте число (1307 → 1130) — саме
   тому, що прийняте там було знято без стелі. Знімок замороженої ширини
   лишається червоним, поки замовник не скаже «приймаю»: сторінку я
   адаптую, приймання числа — не моє рішення.
   `inline-size` тут обовʼязкове з тієї ж причини, що й у Головної: пропорція
   двобічна, і стеля висоти без явної ширини обрізала б заразом ширину. */
.cover--podium {
  inline-size: 100%;
  max-block-size: min(var(--hero-cap), calc(100dvh - var(--header-h)));
}

/*

   ВЛАСНОЇ СІТКИ ТУТ НЕМАЄ, І ЦЕ ЗАМІР, А НЕ ЗРУЧНІСТЬ. При гутері 24 і
   зазорі `--doc-shot-gap` = 16 базова чотиридоріжкова сітка дає доріжку
   (772 − 3×16)/4 = 181 і половину 378. Канон називає рівно ці числа:
   портрет 378 на x=24, права колонка на x=418 = 24 + 378 + 16, ряд кадрів
   на стартах 24 · 221 · 615. Тобто планшет уже лежав на наявній сітці —
   лишилось розставити блоки по доріжках.

   Порядок рядків той самий, що на десктопі (цитата → портрет+виноска →
   колонка А+пара → виносний абзац+колонка Б); змінюється лише ширина
   слота: десктопні 3/5 стають 1/3, а 5/7 і 7/9 сходяться в 3/5. */
/* ГЕРОЙ 2 ГЕРОЄМ Є ЛИШЕ НА ДЕСКТОПІ. На обох вузьких якорях канон розкладає
   його на три речі поспіль: ЧОРНИЙ заголовок на білому, під ним кадр, і —
   на мобільному — чорний абзац під кадром.

   кадр 820×524 на y=3402, і жодного напису на кадрі.
   y=3030, кадр 375×240 на y=3278, абзац 343 нижче за кадр — ЧОРНИЙ.

   Твердження «на планшеті абзацу немає» спирається не на погляд у картинку:
   у дампі артборда 47 вузлів, серед них немає ані тексту білого абзацу, ані
   підпису портрета, — при тому, що ОБИДВА сусідні тексти того ж екрана
   (EVIDENCE §15). Разом із написом ідуть і те, що його обслуговувало:
   плівка й нижній градієнт. На мобільному абзац Є — і це не інтерполяція
   між якорями, а два різні вузли двох різних артбордів. */
/* ГРИД, А НЕ ФЛЕКС — і причина в плівці, а не в розкладці.
   Кадр у цій смузі стоїть у ПОТОЦІ (він не фон героя, а смуга під
   заголовком), а плівка над ним лежить окремим вузлом і мусить накрити
   рівно його. У флексі накрити сусіда нема чим: плівка або абсолютна щодо
   всього героя — і тоді темніє біле тло з чорним текстом, — або йде
   наступним рядком. Грид дає обом ОДНУ клітинку, і плівка лишається
   плівкою, тим самим механізмом і тим самим токеном, що на десктопі.
   Тому й рядки тут названі числами, а не `order`: авторозміщення грида
   поставило б плівку в наступний рядок, скільки б їй не дали `order`. */
.cover--dark {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  aspect-ratio: auto;
  overflow: visible;
  background: none;
  color: var(--ink);
}
/* НУТРО РОЗЧИНЯЄТЬСЯ, І ЦЕ НЕ ХИТРІСТЬ, А ЄДИНИЙ СПОСІБ ЗБЕРЕГТИ РОЗМІТКУ.
   Канон ставить абзац ПІД кадром, а в розмітці він лежить усередині
   `.cover__inner` — разом із заголовком і перед кадром. Поки нутро
   лишається коробкою, абзац із неї не вийде: перша спроба дала його поверх
   заголовка (він у базі `position: absolute` — підпис, приколотий до низу
   героя). `display: contents` робить заголовок, абзац і кадр сусідами по
   одному флексу, і тоді порядок називається числами.
   Поле контейнера нутро більше не дає, тому його беруть на себе самі
   тексти: кадр мусить лишитись на всю ширину (375 і 820 у каноні), а
   тексти — стояти по гутеру. */
.cover--dark .cover__inner { display: contents; }
.cover--dark .cover__title,
.cover--dark .cover__note {
  margin-inline: var(--gutter) auto;
  max-inline-size: min(var(--ab-cover-title-w), calc(100% - var(--gutter) * 2));
}
.cover--dark .cover__photo {
  grid-row: 2;
  grid-column: 1;
  position: static;
  inline-size: 100%;
  block-size: auto;
  aspect-ratio: var(--ab-cover-flat-ratio);
}
/* ЗАЗОР ЗАГОЛОВОК → КАДР ЛЕЖИТЬ НА ЗАГОЛОВКУ, А НЕ НА КАДРІ, і переїхав він
   сюди заміром: клітинка грида включає поле свого вмісту, тож із полем на
   кадрі плівка виходила 375×264 при кадрі 375×240 — тобто на 24 px вище й
   темнила білу смугу над ним. Число не змінилось: 24 на мобільному (низ
   заголовка 3254 при верху кадру 3278), 32 на планшеті (3370 → 3402). Два
   якорі — два числа, обидва звіряє `canon-rhythm`. */
.cover--dark .cover__title { margin-block-end: var(--s-24); }
/* ПЛІВКА ЛИШАЄТЬСЯ Й ТУТ — рішення замовника: «затемнення
   всюди повинно бути, а в макеті дійсно на 820 та 375 нема його». Тобто
   вузол не «не знятий», його в артбордах НЕМАЄ (перевірено: планшет
   немає), і замовник назвав це помилкою дизайнера. Рядок у `docs/WAIVERS.md`.

   Накриває вона рівно КАДР, а не героя: обидва стоять у другому рядку грида,
   і плівка малюється поверх за порядком у розмітці — жодного `z-index`, як і
   в накаті секцій.

   ГРАДІЄНТ ЗАЛИШАЄТЬСЯ СХОВАНИМ, і це не половинчастість: він обслуговував
   БІЛИЙ абзац, що лежав на світлій частині кадру, а тут абзац чорний і
   стоїть під кадром. Затемнення просили рівне — рівне й дане. */
.cover--dark .cover__scrim {
  grid-row: 2;
  /* СТОВПЕЦЬ НАЗВАНИЙ ЯВНО, і це не перестраховка. Авторозміщення грида
     НЕ кладе два елементи в одну клітинку: кадр займає (2,1), а плівці з
     самим лише `grid-row: 2` воно завело другий, неявний стовпець нульової
     ширини — доріжки стали `375px 0px`, і плівка лягла коробкою 0×264 при
     правому краю. Замір, не здогад. */
  grid-column: 1;
  position: relative;
  inset: auto;
  z-index: 0;
}
.cover--dark .cover__fade { display: none; }
.cover--dark .cover__title { grid-row: 1; color: var(--ink); }
/* Абзац іде ТРЕТІМ, під кадром, і чорним: стоїть нижче за кадр
   (3278…3518) і бере колір від `black/900`. */
.cover--dark .cover__note {
  grid-row: 3;
  position: static;
  color: var(--ink);
  /* Кадр → абзац: 24 (низ кадру 3518, верх абзаца 3542). */
  margin-block-start: var(--s-24);
  max-inline-size: min(var(--ab-cover-note-w), calc(100% - var(--gutter) * 2));
}

/* ⚠️ НАКАТ ТУТ НЕ ВИМИКАЄТЬСЯ ЖОДНОЮ ШИРИНОЮ — пряме рішення замовника
. Він живе на спільних `.stack__under` / `.stack__over`
   вище по файлу, і саме тому тут немає ні свого правила, ні свого порогу:
   мовчазний `position: static` у базовій смузі й був тим вимикачем.

   Що я цим правилом лікував: герой у мобільній смузі — не повноекранний
   кадр, а смуга на білому, тож під приколотим блоком видно хвіст
   попередньої секції. Ціна названа, рішення показати її на живій сторінці
   замість суперечки словами; єдиний, хто накат знімає, — `prefers-reduced-motion`
, і це доступність, а не смак. */
/* Нижнє поле 48 у липкого блока ЛИШАЄТЬСЯ, хоч липкість і знята: це не
   службовий зазор накату, а крок між секціями, який канон називає прямо
   (низ абзаца 3122 → верх заголовка 3170). Прибравши його разом із накатом,
   я стер канонне число — знайшов це `rhythm-seams`, не око. */

@media (min-width: 768px) {
  .about--lead > *,
  .about--mid  > *,
  .about--tail > * { margin-block-start: 0; }
  /* ⚠️ ДВА КЛАСИ ПРОТИ ДВОХ, А НЕ ПРОТИ ОДНОГО. Рядок вище має вагу (0,1,0),
     а мобільні винятки з кроком абзаца — (0,2,0), тож скидання їх НЕ
     діставало, і 16 px мобільного стику протікали в планшет і десктоп: на
     1920 колонка «Дизайн, який не потребує пояснень» стояла на 5294 замість
     5278, а сторінка виростала на ті самі 16 (6602 → 6618). Спіймав це
     знімок замороженої ширини — і саме тим, що міряє docH, а не коробки:
     коробки всі збігались. Знайдено заміром по комітах, а не оком. */
  .about--lead > .about__col--a,
  .about--tail > .about__col--c { margin-block-start: 0; }

  .about--lead > .about__quote     { grid-row: 1; grid-column: 1 / -1; }
  /* 32 між цитатою і портретом — власне число планшета: цитата тут
     чотирирядкова, і 1134 − 1102 = 32.
     На десктопі та сама пара дає 40. */
  .about--lead > .about__portrait  { grid-row: 2; grid-column: 1 / 3;
                                     margin-block-start: var(--s-32); }
  .about--lead > .about__aside     { grid-row: 2; grid-column: 3 / 5;
                                     align-self: end; }
  /* Підпису портрета на планшеті НЕМАЄ, і градієнта під ним теж: у дампі
     артборда (47 вузлів) немає ані вузла підпису, ані вузла плівки, тоді як
     на місці. Тобто дамп повний, а підпису в ньому немає (EVIDENCE §15). */
  .about__sign,
  .about__portrait-fade { display: none; }
  .about--lead > .about__col--a    { grid-row: 3; grid-column: 1 / 3;
                                     margin-block-start: var(--section-gap); }
  .about--lead > .about__pair      { grid-row: 3; grid-column: 3 / 5;
                                     margin-block-start: var(--section-gap); }
  .about--lead > .about__lead-line { grid-row: 4; grid-column: 1 / 3;
                                     margin-block-start: var(--section-gap); }
  .about--lead > .about__col--b    { grid-row: 4; grid-column: 3 / 5;
                                     margin-block-start: var(--section-gap); }

  /* Правило `:first-child` ширшої коробки на планшеті не діє: канон дає
     ОДНУ ширину на колонку — 300 у колонці А, 343 у колонці Б, — а не
     широкий перший абзац і вузькі наступні, як на десктопі. */
  .about__col--a > p,
  .about__col--a > p:first-child,
  .about__col--c > p,
  .about__col--c > p:first-child { max-inline-size: min(var(--ab-col-w), 100%); }
  /* `:first-child` названий ЯВНО, бо базове правило `.about__col > p:first-child`
     важче за `.about__col--b > p` (три класи проти двох) і перебивало його
     мовчки: перший абзац колонки Б виходив 300 замість канонних 343.
     Знайшов це `canon-geometry` — звірка проти ДЖЕРЕЛА, не проти себе. */
  .about__col--b > p,
  .about__col--b > p:first-child { max-inline-size: min(var(--ab-col-narrow), 100%); }

  /* РЯД КАДРІВ — ТРИ, А НЕ ЧОТИРИ, і стоять вони на доріжках 1 · 2 · 4.
     при артборді 820 — тобто ПОЗА його межами, разом із коробкою групи
     1166, скопійованою з десктопної. Це той самий систематичний випадок,
     що описаний у PAGE-REGISTRY §7: вузол поза межами свого фрейму
     ігнорується. Питання «три чи чотири» винесене замовнику — доти
     верстка робить те, що видно на артборді. */
  /* Мобільні дрібниці, яких на планшеті немає: крок абзаців 24 замість 16,
     жорсткий перенос у H2 працює, стеля знову на всій колонці. */
  .about__col > * + * { margin-block-start: var(--s-24); }
  .about__col--head > h2 br,
  .about__tail-lead br,
  .about__final br { display: revert; }
  .about__col--head { max-inline-size: min(var(--ab-h2-w), 100%); }

  /* ПЛАНШЕТ ВІДРІЗНЯЄТЬСЯ ВІД МОБІЛЬНОГО ДВОМА РЕЧАМИ: абзацу під кадром
     немає, і зазор заголовок → кадр не 24, а 32. Обидва — з вузлів
     (EVIDENCE §15), а не виведені з мобільного. */
  .cover--dark .cover__note { display: none; }
  .cover--dark .cover__title { margin-block-end: var(--s-32); }

  .about--mid > .about__col--head { grid-row: 2; grid-column: 1 / 3;
                                    margin-block-start: var(--section-gap); }

  .about--tail > .about__tail-lead { grid-row: 1; grid-column: 1 / 3; }
  .about--tail > .about__col--c    { grid-row: 1; grid-column: 3 / 5; }
  .about--tail > .about__final     { grid-row: 2; grid-column: 1 / -1;
                                     margin-block-start: var(--section-gap); }

}

@media (min-width: 1024px) {
  /* ДЕСКТОПНА КОМПОЗИЦІЯ: вісім слотів і явні місця блоків. Нижче 1024
     сітка чотирислотна, а блоки йдуть один під одним у порядку розмітки —
     це тимчасовий склад, не канон. */
  .about,
  .cover__inner { grid-template-columns: repeat(8, 1fr); }

  /* Крок одноколонкового складу знімається цілком: на десктопі вертикаль
     тримають рядки сітки й власні відступи блоків, названі нижче. */
  .about--lead > *,
  .about--mid  > *,
  .about--tail > * { margin-block-start: 0; }

  .about--lead > .about__quote     { grid-row: 1; grid-column: 3 / 7; }
  /* 40 між цитатою і портретом — єдина пара блоків сторінки, чий зазор канон
     називає окремим числом. Живе воно ТІЛЬКИ тут: в одноколонковому складі
     нижче 1024 канону немає, а крок потоку там мусить бути один на весь
     потік — два різні (40 і 48) валить `check-grid-step`, і правильно валить:
     нерівний крок у ряду однакових плиток є дефектом, поки його не назвав
     макет. */
  .about--lead > .about__portrait  { grid-row: 2; grid-column: 3 / 5;
                                     margin-block-start: var(--s-40); }
  .about--lead > .about__aside     { grid-row: 2; grid-column: 5 / 7;
                                     align-self: end; }
  .about--lead > .about__col--a    { grid-row: 3; grid-column: 3 / 5;
                                     margin-block-start: var(--section-gap); }
  .about--lead > .about__pair      { grid-row: 3; grid-column: 7 / 9;
                                     margin-block-start: var(--section-gap); }
  /* Та сама доріжкова пастка, що в ліда колекції: дві доріжки на 1024 —
     228 px. Три доріжки до 1420, колонка Б — за ним. */
  .about--lead > .about__lead-line { grid-row: 4; grid-column: 3 / 6;
                                     margin-block-start: var(--section-gap); }
  .about--lead > .about__col--b    { grid-row: 4; grid-column: 6 / 8;
                                     margin-block-start: var(--section-gap); }

  .about--mid > .about__shot--1 { grid-row: 1; grid-column: 2; }
  .about--mid > .about__shot--2 { grid-row: 1; grid-column: 3; }
  .about--mid > .about__shot--3 { grid-row: 1; grid-column: 5; }
  .about--mid > .about__shot--4 { grid-row: 1; grid-column: 7; }
  .about--mid > .about__col--head { grid-row: 2; grid-column: 3 / 8;
                                    margin-block-start: var(--section-gap); }

  .about--tail > .about__tail-lead { grid-row: 1; grid-column: 3 / 5; }
  .about--tail > .about__col--c    { grid-row: 1; grid-column: 5 / 7; }
  .about--tail > .about__final     { grid-row: 2; grid-column: 3 / 8;
                                     margin-block-start: var(--section-gap); }

  /* Білий абзац другого героя починається не з поля кадру, а зі слота 3 —
     тієї самої вертикалі 512, де стоїть уся текстова частина сторінки. */
  .cover--dark .cover__note { grid-column: 3 / -1; }

  /* ЗНЯТТЯ ПЛАНШЕТНОГО СКЛАДУ. Кожен рядок тут відповідає рядку блока 768:
     смуга ≥1024 не «успадковує зверху», а свідомо повертає десктопний стан.
     Без цього четвертий кадр лишався б схованим, а другий герой — білою
     смугою: правила 768 діють і вище за побудовою каскаду. */
  .about--mid > .about__shot--4,
  .about__sign,
  .about__portrait-fade { display: revert; }
  .about__col--a > p,
  .about__col--c > p { max-inline-size: min(var(--ab-col-narrow), 100%); }
  .about__col--a > p:first-child,
  .about__col--b > p:first-child,
  .about__col--c > p:first-child { max-inline-size: min(var(--ab-col-w), 100%); }
  .about__col--b > p { max-inline-size: min(var(--ab-col-narrow), 100%); }

  .cover--dark {
    display: block;
    aspect-ratio: var(--hero-ratio);
    overflow: clip;
    background: var(--black-100);
    color: var(--btn-ink);
  }
  /* Нутро СТАЄ КОРОБКОЮ НАЗАД. Нижче 1024 воно розчинене (`display:
     contents`), щоб абзац міг стати під кадром; на десктопі герой знову
     герой, і без цього рядка нижнє поле кадру виходило 0 замість 100 —
     спіймав `rhythm-seams`, не око. */
  .cover--dark .cover__inner { display: grid; padding-block: var(--s-48); }
  .cover--dark .cover__title,
  .cover--dark .cover__note { margin-inline: 0; }
  .cover--dark .cover__photo {
    position: absolute;
    inline-size: 100%;
    block-size: 100%;
    aspect-ratio: auto;
    margin-block-start: 0;
  }
  .cover--dark .cover__scrim,
  .cover--dark .cover__fade,
  .cover--dark .cover__note { display: revert; position: absolute; }
  /* Плівка повертається З УСІМА своїми числами, а не лише з `position`.
     Нижче 1024 вона накриває КАДР — стоїть у клітинці грида, тримає
     `inset: auto` і малюється поверх фото порядком розмітки. На десктопі
     кадр знову фон героя, і плівка мусить лягти на весь герой ПІД текст:
     без цих двох рядків вона лишилась би нульовою коробкою поверх напису. */
  .cover--dark .cover__scrim { inset: 0; z-index: -1; }
  .cover--dark .cover__title { color: inherit; }

  /* Повертати липкість тут більше нема чого: базова смуга її не знімає
. Рядки `position: sticky/relative` пішли разом із вимикачем. */
}
@media (min-width: 1420px) {
  .about--lead > .about__lead-line { grid-column: 3 / 5; }
  .about--lead > .about__col--b    { grid-column: 5 / 7; }
}

/* ==========================================================================
   СТОРІНКА ОДНІЄЇ КОЛЕКЦІЇ —.

   Своєї РОЗКЛАДКИ сторінка майже не заводить: перший екран — `cover`, ряд із
   чотирьох кадрів — той самий ряд, що в `doc` і «Про нас», накат — `stack`,
   нижня пара — `pair`. Своїх механік три, і всі нижче: галерея з наведенням,
   відеокадр і модальний оверлей товарів.

   ⚠️ ЧИСЛА ПЛОСКІ: артборд один, рампу виводити нема з чого.
   ========================================================================== */

/* --- ПЕРШИЙ ЕКРАН: ложе з двох половин --------------------------------------
   Канон ділить кадр 960 + 960 і кладе на кожну половину СВОЮ плівку — але
   обидві по .4 (заміряно властивістю вузла, не з рендера: дає
   обидві, а різниця чорних. */
.cover__bed {
  position: absolute;
  inset: 0;
  z-index: -2;
  display: grid;
}

/* НИЖЧЕ 1024 ЛОЖЕ ДІЛИТЬСЯ ПО ГОРИЗОНТАЛІ, А НЕ ПО ВЕРТИКАЛІ.

   Тут стояло «друга половина ховається цілком», і підстава була названа
   чесно: канону цієї смуги не існувало, а дві половини по 187 px на
   375 лишили б від кожного знімка вертикальну смужку. Помилка була в
   ІНШОМУ — у припущенні, що поділ може бути тільки вертикальним.

   Артборди, перезняті, кажуть протилежне: обидва кадри на місці
   820×419. Висота ложа збігається з канонною сама (540 і 838), тому рядків
   рівно два й обидва рівні — числа тут не заводяться. */
/* `minmax(0, 1fr)`, а не `1fr`: автоматичний мінімум доріжки рахується від
   ВМІСТУ, і знімок 1920×1960 роздував рядок до 382.8 при ложі 540 — обидві
   половини вилазили за кадр. Та сама пастка, що в `.pair--flat`. */
.cover__bed { grid-template-rows: repeat(2, minmax(0, 1fr)); }

@media (min-width: 1024px) {
  .cover__bed {
    grid-template-columns: repeat(2, 1fr);
    grid-template-rows: none;
  }
}

.cover__half { position: relative; overflow: clip; }
.cover__half > img { inline-size: 100%; block-size: 100%; object-fit: cover; }
/* У вузькій смузі слот удвічі нижчий за кадр, і центр `cover` різав голови.
   Фокус іде за каноном — токени `--cl-hero-focus-*` зняті заміром із
   рендерів артбордів, а не на око. Від 1024 половини знову майже
   квадратні (960×980), і прийнятий центр лишається. */
.cover--collection .cover__half:nth-child(1) > img { object-position: 50% var(--cl-hero-focus-1); }
.cover--collection .cover__half:nth-child(2) > img { object-position: 50% var(--cl-hero-focus-2); }
@media (min-width: 1024px) {
  .cover--collection .cover__half > img { object-position: 50% 50%; }
}

/* Мініатюра 300×412 — КАДР НАВЕДЕННЯ, а не базовий стан: у
   каноні вона намальована лише на правій половині, і намальована ПІД її
   плівкою. Тому вона й тут під плівкою — інакше вийшла б єдиною незатемненою
   плямою кадру.

   Пропорції беруться від наявної мініатюри пари: 31.25 % × 42.041 % кадру
   960×980 — це рівно 300×412. Числа не свої, тому й токенів нових немає. */
/* Селектор із батьком навмисно: сусідній рядок `.cover__half > img` має ту
   саму вагу плюс тип, тому без батька він перебиває розмір мініатюри й
   розтягує її на весь кадр — 960×980 замість 300×412. */
.cover__half > img.cover__card {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  translate: -50% -50%;
  inline-size: var(--pair-card-w);
  block-size: var(--pair-card-h);
  object-fit: cover;
  opacity: 0;
}

@media (hover: hover) and (min-width: 1024px) {
  .cover__half--card:hover .cover__card { opacity: 1; }
  .cover__card { transition: opacity var(--dur-base) var(--ease); }
}

/* Рядок «подія • дата» стоїть на місці підпису героя. Крапка між частинами —
   не символ, а знак: коло 6×6. */
.cover__meta {
  display: flex;
  align-items: center;
  gap: var(--s-8);
  max-inline-size: none;
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
}

/* Крихти цієї сторінки — ТРИ ланки (235 у каноні), а не дві: одного слота
   їм замало, і без цього рядка «Весна-Літо 2026» падало другим рядком.
   Слот тут не оформлення, а місце перенесення. */
.cover--collection .cover__crumbs { grid-column: 1 / span 2; }

.cover__dot {
  inline-size: 6px;
  block-size: 6px;
  border-radius: 50%;
  background: currentColor;
}

/* --- БЛОК «ПРО КОЛЕКЦІЮ» ---------------------------------------------------
   Та сама сітка з восьми слотів, що тримає всю сторінку. Кроки рядів тут
   РІЗНІ (48 · 88 · 70 · 100), і це не ритм, який кудись з'їхав: канон
   протягнув коробки рукою. Тому в кожного ряду свій названий токен — той
   самий прийом, що в `doc__shots` / `doc__text`. */
.coll {
  inline-size: 100%;
  max-inline-size: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  column-gap: var(--doc-shot-gap);
  row-gap: 0;
  align-items: start;
  font-size: var(--cl-text-fs);
  line-height: var(--cl-text-lh);
}

@media (min-width: 1024px) {
  .coll { grid-template-columns: repeat(8, 1fr); }
}

.coll--about { margin-block-start: var(--cl-head-top); }

/* ⚠️ ПІН ЗНИЗУ, А НЕ ЗВЕРХУ, І ЦЕ ВИПРАВЛЕННЯ ДЕФЕКТУ, А НЕ СМАК.

   Спільне правило `.stack__under` приколює блок під шапкою (`--header-h`).
   Для блока, НИЖЧОГО за екран, це правильно. Цей блок 1176 при екрані 1080:
   приколотий верхом, він тримає власний низ на 1236, тобто кнопка «Більше»
   і хвіст тексту стояли на 1136 — за згином, і не з'являлись у ЖОДНОМУ
   положенні прокрутки. Заміряно перебором усіх положень на п'яти вікнах:
   максимум видимої частини кнопки — мінус 32 пікселі, тобто нуль.

   `min()` бере відʼємне зміщення лише тоді, коли блок не вміщається: на
   екрані 1080 це −96 (низ блока сходиться з низом вікна, текст видно
   цілком), на екрані 1400 — звичайні 60. Правило одне на всі висоти, а не
   поріг у медіа-запиті. Сторінку «Про нас», що ділить клас, це не чіпає:
   правило адресоване документальному блоку колекції. */
.coll--about.stack__under {
  inset-block-start: min(var(--header-h), calc(100dvh - var(--cl-doc-h)));
}

/* Зазор перед відеокадром лежить у НИЖНЬОМУ ПОЛІ липкого блока, а не у
   відступі того, хто наїжджає: у кадру пропорція, і власне поле відрізалось
   би від канонних 980. Те саме рішення, що на «Про нас». */
.stack > .coll--about { padding-block-end: var(--section-gap); }

/* ЗАЗОР ПЕРЕД ГАЛЕРЕЄЮ ПЕРЕЇЖДЖАЄ У ЇЇ ВЛАСНЕ ПОЛЕ — те саме рішення, що в
   групі накату Головної. Зовнішній відступ прозорий за побудовою, і жодне
   тло його не закриває; усередині групи під ним лежить ПРИКОЛОТИЙ блок, і
   крізь ці пікселі його було б видно смугою. Геометрія не змінюється:
   число те саме, змінюється лише те, чиє воно — і, отже, чи є під ним
   фарба. Тло галереї названо тут-таки: без нього крізь її порожні клітинки
   так само прозирав би приколотий блок. */
.stack > .looks {
  margin-block-start: 0;
  padding-block-start: var(--section-gap);
  background: var(--surface);
  /* ⚠️ ШАР НАЗВАНИЙ ЯВНО — те саме, що робить `.stack__over` для відеокадру.
     Самого тла НЕ ДОСИТЬ: приколотий блок липкий, тобто позиційований, а
     галерея — ні, і за правилами малювання позиційований іде ПОВЕРХ. На
     знімку прокрутки це було видно прямо: текст приколотого блока проступав
     крізь прогалини сітки між плитками. */
  position: relative;
  z-index: 1;
}

/* ⚠️ НАКАТ ЖИВЕ Й У МОБІЛЬНІЙ СМУЗІ — пряме рішення замовника
   «давай на мобайлі повернемо накат». Ціна названа числом і
   лишається чинною: липке тримається межами своєї групи, тож хід залипання
   = група − блок − шапка. На 375 це блок 736 + поле 48 + відеокадр 240 =
   375×240) УТРИЧІ нижчий за блок і накрити його цілком не може за
   побудовою. На планшеті кадр 524 при блоці 827 дає хід 516.

   Тобто ефект у мобільній смузі частковий, і це не дефект верстки, а
   наслідок канонних висот. Замовник вирішив показати його на живій
   сторінці — так само, як на «Про нас». Свого вимикача тут немає
   накат живе на спільних `.stack__under` / `.stack__over`, і знімає його
   лише `prefers-reduced-motion`. */

.coll > * { min-inline-size: 0; }

.coll__eyebrow {
  grid-row: 1;
  grid-column: 1 / -1;
  /* Той самий кегль, що в колонок блока: канон обох вузьких якорів дає
     на планшеті, і ці 4 px переносились на ВСІ девʼять блоків нижче —
     заміром видно як рівномірний зсув +4, який жодна коробка не показувала
     сама по собі. На десктопі `--cl-text-*` указує на `--fs-lead`, тобто
     прийняте число не рухається. */
  font-size: var(--cl-text-fs);
  line-height: var(--cl-text-lh);
  font-weight: var(--fw-regular);
}

.coll__quote {
  grid-row: 2;
  grid-column: 1 / -1;
  margin-block-start: var(--cl-quote-top);
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  font-weight: var(--fw-medium);
  max-inline-size: min(var(--cl-quote-w), 100%);
}

.coll__shot {
  inline-size: 100%;
  aspect-ratio: var(--doc-shot-ratio);
  object-fit: cover;
}

.coll__lead,
.coll__col {
  grid-column: 1 / -1;
  /* Крок «ряд кадрів → текст» — свій, не загальний `--section-gap`: канон
     дає 24 на мобільному (956 → 980) і 32 на планшеті (1433 → 1465). */
  margin-block-start: var(--cl-lead-top);
}

.coll__lead {
  font-size: var(--cl-lead-fs);
  line-height: var(--cl-lead-lh);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-h1);
  max-inline-size: min(var(--cl-lead-w), 100%);
}

.coll__col { max-inline-size: min(var(--cl-col-w), 100%); }
.coll__note { margin-block-start: var(--cl-col-step); max-inline-size: min(var(--cl-note-w), 100%); }
/* КЕГЛЬ КНОПКИ СВІЙ, А НЕ УСПАДКОВАНИЙ ВІД КОЛОНКИ. Успадкований давав 20/24
   на десктопі (`--cl-text-fs` там указує на `--fs-lead`), і кнопка була
   набрана більшим кеглем, ніж текст, який вона розкриває, на решті ширин.
   Канон обох вузьких якорів дає слову «Більше» коробку 52×20 (на
: «Згорнути Кнопка 20 п а має бути 16 п». */
.coll__more {
  margin-block-start: var(--s-16);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* РЯД КАДРІВ: ТРИ З ЧОТИРЬОХ ДОРІЖОК, третя порожня — і це канон обох
   `29658`, `29659`): x = 24 · 221 · 615 при кроці 197. В обох випадках це
   доріжки 1, 2 і 4; четвертий кадр зʼявляється лише на десктопі, де ряд
   іде вісьмома доріжками.

   ⚠️ До тут стояло «кадр 1 на всю ширину, решта сховані» — з
   підписом «смуга нижче 1024 канону не має». Тепер канон є, і рядок
   замінено ним, а не здогадом. */
.coll__shot { grid-row: 3; margin-block-start: var(--cl-shots-top); }
.coll__shot--1 { grid-column: 1; }
.coll__shot--2 { grid-column: 2; }
.coll__shot--3 { grid-column: 4; }
.coll__shot--4 { display: none; }

/* Ліва й права колонки тексту: на мобільному одна під одною з кроком абзаца
    на x 418, тобто доріжки 1–2 і 3–4). */
.coll__lead,
.coll__col { grid-row: auto; }
.coll__col { margin-block-start: var(--cl-col-step); }

@media (min-width: 768px) {
  /* ⚠️ ВИСОТА ЦИТАТИ НАЗВАНА ЧИСЛОМ, І ЦЕ НЕ ПІДГОНКА — це різниця РЕНДЕРА,
     456×160: перенос після «Я завжди вірила,» плюс три рядки другого
     абзацу. У браузері той самий текст тим самим кеглем (36/40 Medium) при
     тій самій ширині 456 лягає ДВОМА рядками — разом 120. Заміряно: 120 з
     `<br>` і 120 без нього; чотири рядки браузер дає лише при ширині 400,
     тобто Figma переносить раніше.

     Ширину міняти не можна — вона канонна. Тому тримається й друге канонне
     число: від низу цитати відлічується ВЕСЬ блок нижче (кадри, колонки,
     кнопка, відеокадр, сітка), і без нього девʼять позицій поїхали б на
     40 px угору. Різниця видима лише як вільний рядок під текстом.. */
  .coll__quote { min-block-size: var(--cl-quote-h); }
  .coll__lead { grid-row: 4; grid-column: 1 / span 2; }
  .coll__col  { grid-row: 4; grid-column: 3 / span 2;
                margin-block-start: var(--cl-lead-top); }
}

@media (min-width: 1024px) {
  .coll__quote { grid-column: 3 / span 4; min-block-size: 0; }
  .coll__shot { grid-row: 3; }
  .coll__shot--2,
  .coll__shot--3,
  .coll__shot--4 { display: block; }
  .coll__shot--1 { grid-column: 2; }
  .coll__shot--2 { grid-column: 3; }
  .coll__shot--3 { grid-column: 5; }
  .coll__shot--4 { grid-column: 7; }
  /* ЛІД БЕРЕ ТРИ ДОРІЖКИ, А НЕ ДВІ, ДО 1420. Дві доріжки з восьми на 1024 —
     це 228 px, і жодна стеля цього не підніме: сітка вужча за текст аж до
     ~1400. Тому три доріжки під стелею `--cl-lead-w` (лід ніколи не ширший
     за неї), а колонка стає за ним — на шосту. З 1420 дві доріжки вже
     ширші за стелю (318 проти 317.5), і склад повертається до канонного
     без стрибка ширини ліду; зсув колонки на одну доріжку — на порозі
     складу 1420, тобто структура стрибає там, де їй дозволено. */
  .coll__lead { grid-row: 4; grid-column: 3 / span 3; }
  .coll__col  { grid-row: 4; grid-column: 6 / span 3; }
}
@media (min-width: 1420px) {
  .coll__lead { grid-column: 3 / span 2; }
  .coll__col  { grid-column: 5 / span 3; }
}

/* --- ВІДЕОКАДР -------------------------------------------------------------
   ⚠️ ФАСАД БЕЗ ДЖЕРЕЛА. Знак «Play» тут — декорація (`aria-hidden`), а
   не кнопка: кнопка, яка нічого не вантажить, гірша за відсутню. Коли
   надійде адреса, знак стане кнопкою, а завантаження піде через
   `youtube-nocookie.com`. */
.video {
  position: relative;
  isolation: isolate;
  /* ПРОПОРЦІЯ СВОЯ, НЕ ГЕРОЄВА. До тут стояв `--hero-ratio` — і
     це було правдою лише на десктопі, де обидва кадри 1920×980. Артборди
     820 і 375 показали інше: відеокадр 820×524 і 375×240, тобто на
     мобільному він удвічі нижчий за героя. */
  aspect-ratio: var(--cl-video-ratio);
  overflow: clip;
  background: var(--black-100);
  color: var(--btn-ink);
}

.video__poster {
  position: absolute;
  inset: 0;
  z-index: -1;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.video__label {
  position: absolute;
  inset-block-start: var(--s-48);
  inset-inline-start: var(--gutter);
  font-size: var(--fs-lead);
  line-height: var(--lh-lead);
}

/* Програвач стає ПОВЕРХ знімка, а не замість нього: знімок лишається фоном
   блока, тому `<iframe>` не змінює жодної коробки й не міняє висоти
   секції. */
.video__slot {
  position: absolute;
  inset: 0;
}
.video__slot:empty { display: none; }
.video__slot > iframe {
  inline-size: 100%;
  block-size: 100%;
  border: 0;
  display: block;
}

.video__play {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  translate: -50% -50%;
  inline-size: var(--cl-play-w);
  aspect-ratio: var(--cl-play-ratio);
}

.video__play { padding: 0; border: 0; background: none; cursor: pointer; }
.video__play[hidden] { display: none; }

/* Розмір числом, а не відсотком: обчислений стиль SVG віддає «100%» рядком,
   і будь-який замір коробки читає його як 100 px. */
.video__play > svg { display: block; inline-size: var(--cl-play-w); block-size: auto; }

/* --- ГАЛЕРЕЯ ---------------------------------------------------------------
   Вісім слотів прийнятого контейнера, крок ряду — той самий проміжок, що й
   між колонками. Порожні клітинки — оформлення: кадр знає свій слот
   класом, а не порядком у потоці, тому CMS може віддати скільки завгодно
   кадрів і візерунок не розсиплеться. */
.looks {
  inline-size: 100%;
  max-inline-size: calc(var(--container) + var(--gutter) * 2);
  margin-inline: auto;
  padding-inline: var(--gutter);
  margin-block: var(--section-gap);
}

.looks__title {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  font-weight: var(--fw-medium);
}

/*   колонки: плитки 168 при кроці 175, тобто 343 контейнера. Планшет
 — чотири: 181 при кроці 197, разом 772. Десктоп — вісім.
   До база була чотириколонковою, і на 375 плитка виходила 80
   удвічі вужча за канон. */
.looks__grid {
  margin-block-start: var(--cl-grid-top);
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--doc-shot-gap);
}

/* Мобільний узор прогалин — цикл на 10 плиток (див. коментар нижче). */
.looks__grid > :nth-child(10n+1)  { grid-column: 1; }
.looks__grid > :nth-child(10n+2)  { grid-column: 2; }
.looks__grid > :nth-child(10n+3)  { grid-column: 1; }
.looks__grid > :nth-child(10n+4)  { grid-column: 2; }
.looks__grid > :nth-child(10n+5)  { grid-column: 2; }
.looks__grid > :nth-child(10n+6)  { grid-column: 1; }
.looks__grid > :nth-child(10n+7)  { grid-column: 2; }
.looks__grid > :nth-child(10n+8)  { grid-column: 1; }
.looks__grid > :nth-child(10n+9)  { grid-column: 2; }
.looks__grid > :nth-child(10n+10) { grid-column: 1; }

/* ⚠️ ПРОГАЛИНИ В СІТЦІ — ЦЕ КОМПОЗИЦІЯ, А НЕ ЗАЛИШОК. Замовник
   «вони всюди повинні бути з прогалинами, це фішка дизайну». Канон обох
   вузьких якорів справді лишає клітинки порожніми:

       ряд 1: 1·2 ряд 2: 1·2 ряд 3: —·2
       ряд 4: 1·2 ряд 5: 1·2 ряд 6: 1·—

       ряд 1: 1·2·—·4 ряд 2: —·2·—·4
       ряд 3: 1·2·3·4 ряд 4: 1·—·3·4

   Узор ПОВТОРЮВАНИЙ, тому він і виражений циклом `nth-child`, а не списком
   із 45 правил: у макеті плиток 10 і 12, у верстці 45 — сторінка показує
   всі товари, а макет лише зразок.

   ЧОМУ ДОСИТЬ НАЗВАТИ ДОРІЖКУ, БЕЗ РЯДКА. Авторозміщення грида має курсор,
   який не повертається назад: коли названа доріжка вже позаду курсора,
   елемент іде в НАСТУПНИЙ рядок, лишаючи пропущені клітинки порожніми. Саме
   так живе й десктопний ряд `tile--cN`. Перевірено поклітинно на обох
   якорях, а не виведено з міркування. */
/* ТРЕТІЙ СКЛАД — 3 ДОРІЖКИ, і поріг узятий із реєстру, а не вигаданий.
   Замовник: «з 4 відразу на 2 переходить, а треба щоб ще ці 3
   було». Канон дає рівно два склади — 4 на 820 і 2 на 375, — тож проміжний
   виводиться інтерполяцією (`ADAPTATION.md`), і його узор теж:

     ряд 1: 1·2·— ряд 2: —·2·3
     ряд 3: 1·2·3 ряд 4: 1·—·3

   Девʼять плиток на дванадцять клітинок — та сама щільність, що в канонних
   складах (12 з 16 на планшеті, 10 з 12 на мобільному), тобто прогалини не
   вигадані наново, а перенесені.

   634 — ЗАРЕЄСТРОВАНИЙ поріг складу (`config.json → thresholds.compose`),
   а не нове число: заводити свій означало б поріг-сироту й FAIL гейта. */
@media (min-width: 634px) {
  .looks__grid { grid-template-columns: repeat(3, 1fr); }
  .looks__grid > :nth-child(9n+1) { grid-column: 1; }
  .looks__grid > :nth-child(9n+2) { grid-column: 2; }
  .looks__grid > :nth-child(9n+3) { grid-column: 2; }
  .looks__grid > :nth-child(9n+4) { grid-column: 3; }
  .looks__grid > :nth-child(9n+5) { grid-column: 1; }
  .looks__grid > :nth-child(9n+6) { grid-column: 2; }
  .looks__grid > :nth-child(9n+7) { grid-column: 3; }
  .looks__grid > :nth-child(9n+8) { grid-column: 1; }
  .looks__grid > :nth-child(9n+9) { grid-column: 3; }
}

/* ⚠️ ПОРЯДОК БЛОКІВ У ФАЙЛІ Є ЧАСТИНОЮ МЕХАНІЗМУ. Цикли 10, 9 і 12 мають
   ОДНАКОВУ вагу (0,2,0) і кожен покриває всі елементи без винятку, тож у
   смузі ≥768 діють усі три — виграє той, що стоїть нижче. Переставити ці
   блоки місцями означає розсипати склад, і жоден селектор про це не
   попередить. */
@media (min-width: 768px) {
  .looks__grid { grid-template-columns: repeat(4, 1fr); }
  .looks__grid > :nth-child(12n+1)  { grid-column: 1; }
  .looks__grid > :nth-child(12n+2)  { grid-column: 2; }
  .looks__grid > :nth-child(12n+3)  { grid-column: 4; }
  .looks__grid > :nth-child(12n+4)  { grid-column: 2; }
  .looks__grid > :nth-child(12n+5)  { grid-column: 4; }
  .looks__grid > :nth-child(12n+6)  { grid-column: 1; }
  .looks__grid > :nth-child(12n+7)  { grid-column: 2; }
  .looks__grid > :nth-child(12n+8)  { grid-column: 3; }
  .looks__grid > :nth-child(12n+9)  { grid-column: 4; }
  .looks__grid > :nth-child(12n+10) { grid-column: 1; }
  .looks__grid > :nth-child(12n+11) { grid-column: 3; }
  .looks__grid > :nth-child(12n+12) { grid-column: 4; }
}

@media (min-width: 1024px) {
  .looks__grid { grid-template-columns: repeat(8, 1fr); }
  /* Вага (0,2,0) — та сама, що в циклів вище, і рядки стоять НИЖЧЕ по файлу,
     тому на десктопі виграє ряд із класами. Без цього цикл планшета
     перебивав би `tile--cN` і десктопна композиція розсипалась би. */
  .looks__grid > .tile--c1 { grid-column: 1; }
  .looks__grid > .tile--c2 { grid-column: 2; }
  .looks__grid > .tile--c3 { grid-column: 3; }
  .looks__grid > .tile--c4 { grid-column: 4; }
  .looks__grid > .tile--c5 { grid-column: 5; }
  .looks__grid > .tile--c6 { grid-column: 6; }
  .looks__grid > .tile--c7 { grid-column: 7; }
  .looks__grid > .tile--c8 { grid-column: 8; }
}

.tile { position: relative; }

.tile__btn {
  display: block;
  inline-size: 100%;
  padding: 0;
  border: 0;
  background: none;
  color: var(--btn-ink);
  cursor: pointer;
}

.tile__photo {
  display: block;
  inline-size: 100%;
  /* НЕ `--doc-shot-ratio`: на двох старших якорях вони збігаються (181×255,
     200×282), а на мобільному розходяться — ряд кадрів блока «Про
     колекцію» 80×116 (0.690), плитка товару 168×237 (0.709). */
  aspect-ratio: var(--cl-tile-ratio);
  object-fit: cover;
}

/* Плівка наведення — `--scrim` (.4). У вузлі канону той самий 0.4, але на
   іншому чорному (#232221); чому взято токен. */
.tile__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
  opacity: 0;
}

.tile__zoom {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 50%;
  translate: 0 -50%;
  text-align: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-medium);
  opacity: 0;
}

/* ЗНАК «TRANSFORMER» НА ПЛИТЦІ. Місце — канонне: 16/16 від краю кадру, так у
   текстова плашка, намальована під картку каталогу 432 завширшки, а плитка
   галереї 148…200 — заміряно, коробка плашки 99 px на плитці 148
   тобто дві третини кадру. Замовник: «чорну на другому фото видали», «іконку
   трансформер … під розмір картинки адаптуй».

   РОЗМІР НЕ НОВИЙ, А НАЯВНИЙ: це компактний скін знака з мобільної сітки
   каталогу — кружок 24 і знак 14 (`--badge-h`, `--flag-icon`, блок `.grid
   .card` вище). Заводити третє число означало б завести й третю роль.

   Знак ПОЗА кнопкою кадру: інакше його підпис читався б як частина назви
   контрола «Збільшити». Це самостійний контрол із підказкою — той самий, що
   на картці каталогу. */
.tile__flag {
  position: absolute;
  inset-block-start: var(--s-16);
  inset-inline-start: var(--s-16);
  z-index: 1;
  display: inline-flex;
  --badge-h: var(--s-24);
  --flag-icon: 14px;
}

/* ⚠️ ПІДКАЗКА РОЗКРИВАЄТЬСЯ ВПРАВО ВІД ЗНАКА, А НЕ ВІД ЙОГО ЦЕНТРУ. На
   картці каталогу вона центрована під знаком, і це там безпечно: картка 432,
   знак стоїть на 24 від краю, капсула 99 — половина її (50) у 24 не влазить
   лише на 26, а картка має поле. На плитці 148 те саме центрування виносило
   капсулу ЗА кадр ліворуч на 22 px (замовник: «щоб при наведенні
   не виходив елемент за рамки фото»).

   Зсунуто підказку, а не знак: знак стоїть на канонному місці плашки
   єдине, що в цій позначці лишилось від макета. Праворуч капсула вміщається
   з запасом на найвужчій плитці: 16 + 99 = 115 при 148. */
.tile__flag-tip {
  inset-inline-start: 0;
  translate: 0;
}

@media (hover: hover) and (min-width: 1024px) {
  .tile__scrim,
  .tile__zoom { transition: opacity var(--dur-fast) var(--ease); }
  .tile__btn:hover .tile__scrim,
  .tile__btn:hover .tile__zoom { opacity: 1; }
}

/* Клавіатура бачить те саме, що й миша: без цього рядка кадр був би
   відкривабельний, але мовчазний — фокус не показував би, що саме
   відкриється. */
.tile__btn:focus-visible .tile__scrim,
.tile__btn:focus-visible .tile__zoom { opacity: 1; }

/* --- НИЖНЯ ПАРА ------------------------------------------------------------
   Той самий `pair`, кадр удвічі нижчий: 960×490 замість 960×980. Пропорція —
   єдине, що відрізняє. */
/* ⚠️ `minmax(0, 1fr)`, А НЕ `1fr` — І ЦЕ НЕ ОФОРМЛЕННЯ.

   Автоматичний мінімум елемента з `aspect-ratio` переноситься з іншої осі:
   при пропорції 960/490 браузер вивів мінімальну ширину половини як 1920, і
   доріжки `1fr 1fr` роздулися до 1920 кожна — пара ставала одноколонковою в
   1920 завширшки, тобто сторінка їхала вбік удвічі.

   Заміряно, не вгадано: без класу доріжки 955 px, з класом — 1920 px, з
   `minmax(0, 1fr)` — знову 955. На решті пар цього не видно, бо там пропорція
   майже квадратна (960/980) і перенесений мінімум збігається з наявною
   шириною. Тому правило стоїть тут, а не в спільному `.pair`. */
/* ⚠️ ПОРІГ 768, А НЕ 935, І ЦЕ КАНОН, А НЕ ЗРУЧНІСТЬ. Планшетний артборд
   на 820 — по 410 кожна. Поріг 935 тут був виміряним дном складу тієї доби,
   коли вузького канону не існувало; тепер це стіна режиму з
   `config.json → modes`, тобто число замовника, а не заміру. */
@media (min-width: 768px) {
  .pair--flat { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
/* `min-inline-size: 0` — друга половина тієї самої пастки: доріжки вже 955,
   а САМ елемент лишався 1920, бо автоматичний мінімум сидить і на ньому.
   Обидва рядки потрібні, і кожен доведений заміром окремо. */
.pair--flat .pair__half {
  aspect-ratio: var(--cl-pair-ratio);
  min-inline-size: 0;
  /* ТРЕТІЙ рядок тієї самої пастки, і без нього два попередні нічого не
     дають: при `inline-size: auto` браузер бере висоту за визначену й
     ВИВОДИТЬ ширину з пропорції (980 x 960/490 = 1920), ігноруючи
     розтяг по доріжці. Явна ширина міняє напрям виведення: ширина —
     від доріжки, висота — з пропорції. Заміряно: 1920 -> 955. */
  inline-size: 100%;
}

/* ⚠️ ЧЕТВЕРТИЙ рядок, і теж не косметика. З 1920 повноекранні кадри проєкту
   скасовують пропорцію й беруть висоту токеном `--hero-cap` (980 на якорі,
   далі рампа до +150). Правило написане на `.pair__half`, тобто накриває й
   плоску пару — без цього рядка вона стала б 980 замість 490.

   Половина висоти — не нове число, а той самий токен: на 1920 це рівно
   канонні 490, вище — рампа разом із рештою кадрів. */
@media (min-width: 1920px) {
  .pair--flat .pair__half { block-size: calc(var(--hero-cap) / 2); }
}

/* --- ОВЕРЛЕЙ ТОВАРІВ НА ЗНІМКУ ---------------------------------------------
    (1920) · (820) · (375).

   ⚠️ ЦЕ ДВА РІЗНІ СКЛАДИ, А НЕ ОДИН ЩІЛЬНІШИЙ. Вище стіни 1024 канон малює
   дві колонки: кадр ліворуч на всю висоту вікна, картка товару й стрічка
   мініатюр праворуч. Нижче стіни правої колонки НЕМАЄ ЗОВСІМ — канон дає
   аркуш, який гортається: повноширинний кадр 3:4 угорі, під ним ПЕРЕЛІК
   товарів рядками «назва — ціна» з волосинами між ними.

   Аркуш ВИЩИЙ ЗА ВІКНО за побудовою — 1534 при ширині 820 і 860 при 375, —
   тому оверлей тут не «вміщає композицію у вікно», а прокручується. Це
   знімає всі стелі висоти, якими вузька смуга трималась, доки власного
   канону не мала. */
.zoom {
  position: fixed;
  inset: 0;
  /* Вище за відео-лайтбокс картки товару (60): це другий модальний
     шар проєкту, і сходинка між ними мусить бути названа, а не
     випадкова. */
  z-index: 70;
  display: block;
  overflow-y: auto;
  /* ⚠️ ВБІК АРКУШ НЕ ЇЗДИТЬ НІКОЛИ, і це не косметика, а те, що робить
     смугу між якорями ПЕРЕВІРНОЮ. `overflow-y: auto` сам собою дає й
     `overflow-x: auto` (правило пари в специфікації), тобто зайвий вміст
     поїхав би вбік ВСЕРЕДИНІ оверлея — а `check-hscroll` міряє ширину
     ДОКУМЕНТА і внутрішні прокрутки свідомо не бачить. Дефект був би
     невидимий жодному чекеру.
     `clip` замість `hidden`: зайве ОБРІЗАЄТЬСЯ, а обрізане за контейнером
     ловить `check-clipped-out` — той самий чекер, що закрив дірку
     підвала. */
  overflow-x: clip;
  /* Прокрутка аркуша не «протікає» на сторінку під ним: `JK.lockScroll`
     тримає тіло, а це правило тримає ланцюг прокрутки. */
  overscroll-behavior: contain;
  background: var(--surface);
}

.zoom[hidden] { display: none; }

/*   дає 375×500, — 820×1094, тобто рівно 3:4 (`--cl-zoom-shot-ratio`).

   ⚠️ РІЗАТИ ЗНІМОК ТУТ — РІШЕННЯ КАНОНУ, А НЕ НЕДОГЛЯД. Файл `look-NN.jpg`
   має 400×564 (0.709), коробка канону — 0.75; різницю бере `cover`, і це
   близько 5 % по висоті. До коробка тримала пропорцію ФАЙЛА саме
   для того, щоб не різати, — але тоді вузьких артбордів не існувало й
   звіряти форму не було з чим. Тепер вони є, і геометрію дає макет.

   Десктоп цього правила не бере: там ширина колонки канонна (810/1920), і
   перекривач у смузі ≥1024 скасовує пропорцію. */
.zoom__shot {
  position: relative;
  margin: 0;
  inline-size: 100%;
  aspect-ratio: var(--cl-zoom-shot-ratio);
  overflow: clip;
  background: var(--black-100);
}
/* ⚠️ КРІП ВІД ВЕРХУ, А НЕ ВІД ЦЕНТРУ — і це число з канону, не смак.

   Коробка канону 0.75 (375×500, 820×1094, 810×1080), а файл `look-NN.jpg` —
   0.7092 (400×564). Різницю бере `cover`: при ширині 375 зайвої висоти 29 px,
   при 820 — 63, при 810 на десктопі — 62. Дефолтний `center` ділить її
   НАВПІЛ, тобто зрізає верх — і замовник назвав саме це
   «чому в адаптиві верх фото обрізається?».

   ) над волоссям є поле, а черевики зрізані нижнім краєм.
   `50% 0` віддає верх файла цілим і забирає всю різницю знизу — рівно так,
   як малює макет, і однаково на всій смузі: причина одна на весь діапазон,
   тому й правило одне, без порогів. */
.zoom__photo {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: 50% 0;
}

/*   канон дає рядки «назва — ціна», розділені волосинами.

   ТРИ ЧИСЛА, І ОБИДВА КАНОННІ НАБОРИ ВИВЕДЕНІ З ГУТЕРА, а не написані
   двічі: бічне поле 16/24 — сам гутер; поле згори й знизу 24/32 — гутер
   плюс 8 (`--cl-zoom-edge`); крок «рядок → волосина → рядок» — знову гутер
   (16 на 375, 24 на 820). Волосина всередині кроку, тому вона рахується
   бордюром рядка, а не окремим вузлом. */
.zoom__list {
  margin-block: var(--cl-zoom-edge);
  margin-inline: var(--gutter);
  padding: 0;
  list-style: none;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.zoom__row {
  display: flex;
  align-items: flex-start;
  gap: var(--s-16);
}

.zoom__row + .zoom__row {
  margin-block-start: var(--gutter);
  padding-block-start: var(--gutter);
  border-block-start: var(--hairline) solid var(--line);
}

/* Ширина назви — 168 на ОБОХ вузьких якорях.
   Це не «третина рядка», а стале число макета: на 375 воно лишає ціні
   вільний хвіст, на 820 — широку порожнечу посередині, і саме так канон
   і малює.

   ⚠️ АЛЕ СТИСКАТИСЬ ВОНА МУСИТЬ, і це не послаблення числа. Нижче
   мобільного якоря нового канону немає — там мобільна структура лише
   стискається (ADAPTATION §2.1). На 320 із парою цін ряд просить
   168 + 16 + 144 = 328 при переліку 288, і зайве йшло за аркуш — спіймано
   `check-clipped-out` у стані `zoom-open`. `flex-shrink: 1` віддає ці
   40 px назві, і вона доливає рядок; від 375 і вище запас у рядку є, тому
   канонні 168 стоять недоторкані — це звіряє canon-geometry. */
.zoom__row-name {
  flex: 0 1 auto;
  inline-size: 168px;
  min-inline-size: 0;
  color: var(--ink);
}

/* Ціна прибита до ПРАВОГО краю переліку: канон ставить її на 359 із 359
   (375) і на 796 із 796 (820), тобто по краю контейнера, а не за назвою.

   ⚠️ ТОН І ЗАКРЕСЛЕННЯ ТУТ НЕ ОГОЛОШУЮТЬСЯ — їх дає наявний компонент
   проєкту `.price` (`<del class="price__old">` + `<ins class="price__new">`),
   той самий, що в картках каталогу: сіра ціна без знижки, а зі знижкою
   стара сіра закреслена й нова темна. Канон вузької смуги дає рівно ці два
   заводити другий носій того самого факту нема причини.
   Своє в цього рядка одне — ПРОМІЖОК пари: компонент носить `--s-8`, число
   картки каталогу, а вузли цього переліку дають 7 на обох якорях. */
.zoom__row-price {
  flex: none;
  margin-inline-start: auto;
  gap: var(--cl-zoom-sale-gap);
}

/* ⚠️ БІЛЕ ТУТ — КОЛО, А НЕ ЗНАК, і різниця не косметична: біла стрілка в
   прозорому контурі зникала б на світлій частині знімка зовсім.

   `<circle r="28" fill="white"/>` без `stroke` взагалі, а знак —
   `stroke="#232221"` (це `--ink`) товщиною 1.4. Звідси три речі:
   заливка біла, знак темний, обведення кола немає.

   Спільне правило `.btn-round:hover` інвертує коло в темне — а тут під
   кнопкою завжди знімок, і темне коло на темному кадрі зникає так само.
   Тому біла заливка названа в усіх трьох станах: стрілка лишається білою
   незалежно від того, що під нею. */
.zoom__nav,
.zoom__nav:focus-visible {
  position: absolute;
  inset-block-end: var(--cl-zoom-edge);
  color: var(--ink);
  border-color: transparent;
  background: var(--btn-ink);
}

/* Наведення названо ОКРЕМО й усередині запиту можливості вказівника: ширина
   можливістю не є, і `:hover` поза `(hover: hover)` на дотику залипає. */
@media (hover: hover) {
  .zoom__nav:hover {
    color: var(--ink);
    border-color: transparent;
    background: var(--btn-ink);
  }
}
.zoom__nav--prev { inset-inline-start: var(--cl-zoom-inset); }
.zoom__nav--next { inset-inline-end: var(--cl-zoom-inset); }
.zoom__nav--next > svg { rotate: 180deg; }
/* ⚠️ `stroke-width` НАЗВАНИЙ ТУТ, і це не дубль атрибута розмітки. Спільне
   правило `.btn-round svg` ставить знак 20 з обведенням 1.8 — набір круглої
   кнопки проєкту. Канон оверлея дає знак 24, а набір товщин
   проєкту для 24 — 1.4. Без цього рядка знак 24 ніс би 1.8, і саме це
   спіймав `icon-scale`. */
.zoom__nav > svg {
  inline-size: var(--icon);
  block-size: var(--icon);
  stroke-width: 1.4;
}

/* ⚠️ FLEX, А НЕ GRID, І ЦЕ ЄДИНЕ, ЩО ТУТ ПРАЦЮЄ.

   У сітці картка не стискається взагалі: відсоток проти доріжки `fr`
   обчислюється як `auto`, тому `min(674px, 100%)` дорівнює 674 і при вікні
   1000 — стрічка виїжджає за нижній край (колонка 1142 при вікні 1000).
   `block-size: 100%` дає те саме з тієї самої причини.

   У флексі стиснення від відсотків не залежить: картка має `flex-shrink` і
   `min-block-size: 0`, тому віддає висоту першою. Це й дозволяє оверлею
   вміщатись у вікно, нижче за канонні 1080. */
.zoom__side {
  /* НИЖЧЕ СТІНИ 1024 ПРАВОЇ КОЛОНКИ НЕМАЄ В КАНОНІ ЗОВСІМ — там аркуш із
     переліком (`.zoom__list`). Увесь набір колонки нижче по файлу лишається
     написаним один раз: він просто не малюється, доки не ввімкне
     перекривач ≥1024. */
  display: none;
  flex-direction: column;
  align-items: center;
  min-inline-size: 0;
  gap: var(--cl-zoom-strip-gap);
  /* НИЖНЄ ПОЛЕ ВІДДАЄ І ЗАЗОР, І САМУ СМУГУ: обидва вже пораховані
     всередині вузла прокрутки. Лишившись іще й тут, вони зробили б колонку
     на 18 вищою, і платить за це картка — саме вона забирає залишок
     висоти (склад колонки — `css/tokens.css`, блок `--cl-zoom-*`). */
  padding-block: var(--cl-zoom-top)
                 calc(var(--cl-zoom-bottom) - var(--cl-zoom-bar-top)
                      - var(--cl-zoom-bar-h));
  min-block-size: 0;
}

/* ⚠️ БЕЗ СТЕЛІ ВИСОТИ, і це не послаблення, а арифметика канону.

   Канон намальований під вікно рівно 1080, і на ньому склад колонки
   сходиться точно (склад колонки — `css/tokens.css`, блок `--cl-zoom-*`). Отже
   «вільна висота картки» на 1080 дає РІВНО канонні 674 — а на вищому вікні росте разом із ним.
   Стеля лишала б під стрічкою мертву смугу тим ширшу, чим вищий екран.

   Ширина при цьому виводиться з висоти пропорцією кадру, тобто знімок
   зберігає канонну форму 432/612 на будь-якій висоті. */
/* КАРТКА — СТОВПЧИК КАНОНУ: кадр товару, під ним назва й ціна.
   Живе тільки в парі з правою колонкою, тобто від стіни 1024 і вище. */
.zoom__card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--cl-zoom-cap-gap);
  flex: 1 1 auto;
  min-block-size: 0;
  padding-inline: 0;
  /* Ширина картки — ВСЯ колонка, а не «по вмісту». Причина в порядку
     обчислень: ширина кадру виводиться з його висоти пропорцією, а висота
     приходить від флексу — тобто на момент, коли браузер міряє вміст
     колонки, кадр іще не має ширини й не бере участі в ній. Картка «по
     вмісту» через це схлопувалась до підпису (309 замість 432), а кадр
     виїжджав за її межі. */
  inline-size: 100%;
}

.zoom__frame {
  position: relative;
  flex: 1 1 auto;
  min-block-size: 0;
  aspect-ratio: var(--cl-zoom-photo-ratio);
}

/* Знімок лежить АБСОЛЮТНО в кадрі, а не в потоці. Причина не косметична:
   у потоці його власна ширина (864) стала б максимальним вмістом колонки, і
   картка вийшла б 864 замість канонних 432. Поза потоком він не бере участі
   в обчисленні ширини взагалі — форму задає кадр своєю пропорцією. */
.zoom__good {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Поле ♡ їде за розміром кадру: на вузькій картці кадр 140 заввишки, і
   канонні 24 з'їдали б третину його ширини. */
.zoom__like {
  position: absolute;
  inset-block-start: var(--s-8);
  inset-inline-end: var(--s-8);
  inline-size: var(--icon);
  block-size: var(--icon);
  padding: 0;
  border: 0;
  background: none;
  color: var(--ink);
  cursor: pointer;
}
.zoom__like > svg { inline-size: var(--icon); block-size: var(--icon); }
.zoom__like[aria-pressed="true"] > svg { fill: currentColor; }

.zoom__cap {
  flex: none;
  min-inline-size: 0;
  display: grid;
  justify-items: center;
  row-gap: var(--s-6);
  inline-size: min(var(--cl-zoom-cap-w), 100%);
  text-align: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.zoom__name { color: var(--ink); }
.zoom__price { color: var(--price-ink); }

/* Стрічка мініатюр із власним індикатором прокрутки. Індикатор — це СТАН, а
   не оформлення: канон малює його при девʼяти товарах і не малює при пʼятьох
   і при одному, тобто він з'являється рівно тоді, коли є що гортати. */
.zoom__rail {
  flex: none;
  /* ⚠️ БЕЗ ЦЬОГО РЯДКА СТРІЧКА РОЗДУВАЄ ВЕСЬ ОВЕРЛЕЙ. Автоматичний мінімум
     флекс-елемента дорівнює ШИРИНІ ВМІСТУ, а вмісту тут дванадцять
     мініатюр; `overflow-x: auto` сам по собі його не знімає. У вузькій
     смузі це давало доріжку 1504 при вікні 375 — модалка вилазила за
     екран учетверо. Заміряно, не вгадано. */
  min-inline-size: 0;
  /* ВИСОТА ЧИСЛОМ, а не полем: поле віддало б висоту вмісту, і стрічка
     стояла б там, де її поставить найвища мініатюра, а не на канонних
     910…1040. */
  block-size: var(--cl-zoom-thumb-h);
  overflow-x: auto;
  /* Другий скролер без закону «гортаю себе, а не сторінку». */
  overscroll-behavior-x: contain;
  /* СИСТЕМНА СМУГА ЗНЯТА ЗОВСІМ, а не підфарбована. Канон дає 2 пікселі
     висоти; системна смуга такої не буває — у Firefox її висоту задати
     нічим, а перевірити результат у цьому середовищі неможливо взагалі:
     headless не малює смуг ні в коробці, ні на знімку. Прокрутка лишається
     справжньою (колесо, тач, клавіатура), видимим стає власний індикатор. */
  scrollbar-width: none;
}
.zoom__rail::-webkit-scrollbar { display: none; }

/*   10, заливка #232221. Довжина й положення повзунка приходять від двигуна:
   це стан прокрутки, а не число макета. Канонні 169 — це стан ДЕВʼЯТИ
   товарів; у прикладі їх дванадцять, і підганяти довжину числом означало б
   намалювати брехню про те, скільки товарів попереду. */
.zoom__scroll {
  flex: none;
  align-self: start;
  /* Ті самі 48, що тримають стрілки й хрестик: індикатор починається там,
     де починається стрічка, і закінчується там, де вона. */
  inline-size: calc(100% - var(--cl-zoom-inset) * 2);
  margin-inline-start: var(--cl-zoom-inset);
}

.zoom__bar {
  margin-block-start: var(--cl-zoom-bar-top);
  block-size: var(--cl-zoom-bar-h);
}

.zoom__bar-thumb {
  display: block;
  block-size: 100%;
  /*     двигун лишає собі тільки положення (`js/collection.js`, `paintBar`).
     `min(…, 100%)` — на вузькій смузі доріжка коротша за 169 (343 на 375),
     і повзунок не має права вилазити за неї. */
  inline-size: min(var(--cl-zoom-bar-len), 100%);
  border-radius: 10px;
  background: var(--ink);
  /* Прокрутки може не бути — тоді повзунка не видно. Прозорість, а не
     `display`: коробка лишається на місці, і поява не смикає колонку.
     Нуль стоїть у CSS, а не в двигуні: до першого малювання індикатор
     повідомляв би про прокрутку на всю ширину, тобто брехав би. */
  opacity: 0;
  transition: opacity var(--dur-fast) var(--ease);
}


.zoom__strip {
  display: flex;
  gap: var(--s-32);
  block-size: var(--cl-zoom-thumb-h);
}

.zoom__thumb {
  position: relative;
  flex: none;
  inline-size: var(--cl-zoom-thumb-w);
  block-size: var(--cl-zoom-thumb-h);
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

.zoom__thumb > img { inline-size: 100%; block-size: 100%; object-fit: cover; }

/* Коло 84×84 при 0.8 позначає АКТИВНУ мініатюру — та сама логіка, що на
   картці товару. Ціна названа: коло закриває знімок на 80 %, тому
   впізнати активний товар по самому кадру не можна, і фокус клавіатури
   позначається окремо — обведенням. */
.zoom__mark {
  position: absolute;
  inset-inline-start: 50%;
  inset-block-start: 50%;
  translate: -50% -50%;
  inline-size: var(--cl-zoom-mark);
  block-size: var(--cl-zoom-mark);
  border-radius: 50%;
  background: var(--black-900);
  opacity: 0;
}

.zoom__thumb[aria-selected="true"] .zoom__mark { opacity: .8; }
.zoom__mark { transition: opacity var(--dur-fast) var(--ease); }

/* Фокус клавіатури позначається окремо від вибору: інакше людина, яка
   ходить стрічкою табом, не бачила б, де вона стоїть, доки не натисне. */
.zoom__thumb:focus-visible { outline: var(--hairline) solid var(--ink); outline-offset: 2px; }

.zoom__close {
  /* FIXED, А НЕ ABSOLUTE, і це не косметика: нижче стіни 1024 сам оверлей —
     аркуш, який ГОРТАЄТЬСЯ (`.zoom` має `overflow-y: auto`). Абсолютна
     кнопка їхала разом із вмістом, і на переліку товарів закрити оверлей
     ставало нічим — треба було спершу догорнути назад до кадру.

     Координати від цього не міняються: `.zoom` дорівнює вікну
     (`position: fixed; inset: 0`), тому та сама пара `inset` дає ті самі
     756/32 на 820 і 319/24 на 375, що й раніше. Вище стіни аркуш не
     гортається взагалі, і правило там нічого не міняє. */
  position: fixed;
  /*     збоку — гутер. Десктоп повертає канонні 48 перекривачем. */
  inset-block-start: var(--cl-zoom-edge);
  inset-inline-end: var(--cl-zoom-inset);
  inline-size: 40px;
  block-size: 40px;
  display: grid;
  place-items: center;
  padding: 0;
  /*     stroke="#201F1F" stroke-opacity="0.12"/>` без `fill`. Тобто це НЕ білий
     диск, як стрілки поруч, а порожнє коло з тінькою обведення — рівно той
     волос 12 %, який у проєкті зветься `--line`.

     ⚠️ Кільце, видне на знімках цієї кнопки, належить обвідці ФОКУСУ:
     пастка фокуса ставить каретку сюди при відкритті оверлея. Без рядка
     нижче кнопка малювалась би білим по білому, а знімок однаково показував
     би коло — тобто підтверджував би оформлення, якого немає. */
  border: var(--hairline) solid var(--line);
  border-radius: 50%;
  color: var(--ink);
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

/* ⚠️ ЗНАКІВ У КНОПЦІ ДВА, І ЦЕ НЕ ДУБЛЬ РОЗМІТКИ. Канон малює хрестик у
   вузькій смузі — обидва обведенням 1.5.
   Обведення СТАЛЕ, а геометрія — ні, тому одним `<svg>` це не робиться:
   масштабуючи знак 40 до 18, браузер масштабує разом із ним і обведення
   (1.5 → 0.675), а це рівно той дефект, який ловить `check-icon-scale`.
   Два вузли — два знаки, кожен у СВОЇЙ коробці, масштаб рівно 1.

   Довжина самого хрестика в каноні пропорційна колу: 20 із 64 на десктопі,
   9 із 40 у вузькій смузі (`M15.5 15.5L24.5 24.5` у вузлі). */
.zoom__close > svg { stroke-width: 1.5; }
.zoom__close-mark--narrow { inline-size: 18px; block-size: 18px; }
.zoom__close-mark--wide { display: none; inline-size: 40px; block-size: 40px; }

/* ⚠️ У ВУЗЬКІЙ СМУЗІ ХРЕСТИК СТАЄ БІЛИМ КОЛОМ — рівно як стрілки поруч, і з
   тієї самої причини. Канон малює його порожнім кільцем із волосом
   12 % тому, що на десктопі він лежить на БІЛІЙ колонці праворуч. Нижче
   1024 колонки праворуч немає: кадр займає весь верх, і кнопка опиняється
   НА ЗНІМКУ — темний знак на темному кадрі зникає зовсім.

   Спіймано знімком самої кнопки, а не оком по сторінці: на 1920 у кільці
   видно хрестик, на 820 і 375 — порожнє коло. Те, що властивості вузла
   (svg 40×40, stroke `--ink`) на всіх трьох однакові, лише підтверджує:
   ділося не оформлення, а контраст із тим, що ПІД кнопкою. */
.zoom__close {
  background: var(--btn-ink);
  border-color: transparent;
}

@media (hover: hover) {
  .zoom__close:hover { background: var(--btn-ink); border-color: transparent; color: var(--ink); }
}

/* Наведення на десктопі — те саме, що в круглих кнопок проєкту: коло темніє,
   знак інвертується. Стрілки оверлея лишаються названим винятком
   вони лежать на знімку, де темне коло зникає так само, як темна стрілка
. */
@media (min-width: 1024px) {
  .zoom__close { background: none; border-color: var(--line); }
  @media (hover: hover) {
    .zoom__close:hover { background: var(--btn-bg); border-color: var(--btn-bg); color: var(--btn-ink); }
  }
}

/* ⚠️ ЛАНДШАФТНОГО СКЛАДУ ТУТ БІЛЬШЕ НЕМАЄ, і це не спрощення.

   До у вікні, ширшому за високе, вузька смуга перемикалась на
   двоколонковий склад десктопа — запитом `(min-aspect-ratio: 1/1)`. Він
   існував рівно тому, що вузького канону не було: повний знімок над карткою
   не вміщався в низьке вікно, і замовник назвав обидві
   альтернативи поганими («в адаптиві не треба зменшувати велике фото по
   ширині»).

   Канон вузької смуги знімає саме цю задачу: аркуш ГОРТАЄТЬСЯ, тобто вміщати
   його у вікно не треба ні в портреті, ні в ландшафті. А склад, що
   перемикався всередині смуги режиму не на стіні режиму, суперечив
   інваріанту 18: обличчя сторінки міняється на стіні, а не за формою вікна
. */

@media (min-width: 1024px) {
  .zoom {
    /* Аркуш вузької смуги знову стає композицією, яка ВМІЩАЄТЬСЯ у вікно:
       дві колонки, власної прокрутки в оверлея немає. */
    display: grid;
    overflow: hidden;
    /* `minmax(0, 1fr)`, а не `1fr`: мінімум `auto` бере МІНІМАЛЬНИЙ ВМІСТ
       колонки, а це вся стрічка мініатюр разом (при дванадцяти товарах —
       1456). Колонка через це роздувалась до 1504 і виштовхувала оверлей за
       вікно. Прокрутка на те й прокрутка, щоб не диктувати ширину. */
    grid-template-columns: var(--cl-zoom-shot) minmax(0, 1fr);
    /* `minmax(0, 1fr)`, а не `none`: при неявному рядку `auto` колонка
       переростала вікно (заміряно: 1142 при вікні 1000) і стрічка їхала за
       нижній край. Стеля рядка — те, що взагалі дозволяє картці стиснутись. */
    grid-template-rows: minmax(0, 1fr);
  }

  /* Кадр повертає розтяг по колонці: ширина тут КАНОННА (810/1920), і
     виводити її з висоти означало б підмінити число макета своїм. Пропорція
     скасовується — форму на десктопі задає доріжка, а не вузол знімка. */
  .zoom__shot {
    justify-self: stretch;
    align-self: stretch;
    block-size: auto;
    inline-size: auto;
    aspect-ratio: auto;
  }

  /* ПРАВА КОЛОНКА ЗʼЯВЛЯЄТЬСЯ РІВНО ТУТ, а перелік рядками зникає: це
     та сама стіна режиму, на якій перемикається обличчя сторінки. */
  .zoom__side { display: flex; }
  .zoom__list { display: none; }

  /*     виростає до 64: у вузькій смузі він лежить НА ЗНІМКУ й канон дає йому
     40. */
  .zoom__close {
    inset-block-start: var(--cl-zoom-inset);
    inline-size: 64px;
    block-size: 64px;
  }
  .zoom__close-mark--narrow { display: none; }
  .zoom__close-mark--wide { display: block; }
}

/* ==========================================================================
    (форма) · (форма з незаповненими полями).

   Три артборди — ТРИ СТАНИ однієї сторінки: шапка, крихти, заголовок і
   рядок під ним стоять на тих самих координатах на всіх трьох. Перемикач —
   пара табів «Карта / Зворотний звʼязок» у самому рядку.

   РОЗКЛАДКА СТОРІНКИ — ДВІ ПОЛОВИНИ КОНТЕЙНЕРА, і це не «дві колонки з
   проміжком». Канон ставить праву частину на 976 при центрі полотна 960, а
   50%`). Тому обидва ряди — і рядок контактів, і форма з фотосмугою —
   стоять на `1fr 1fr` без проміжку, а праву половину відсуває від центру
   `--ct-half-gap`. Проміжок між колонками дав би мітку на 944: половина
   вирівнюється по ЦЕНТРУ полотна, а не по краю сусідньої колонки.

   ТРИ ЯКОРІ, ТРИ НАБОРИ ЧИСЕЛ. Планшетні артборди
   усі з файла `uPrDHmTNp0a1vA1erS5oZf`.

   ЩО ПЕРЕМИКАЄТЬСЯ НА СТІНАХ:
     768 рядок контактів із стовпчика на дві колонки, волосина переїжджає
           з-під адреси НАД таби, поле форми — у два ряди, кнопка й примітка
           стають у рядок;
     1024 рядок стає ОДНИМ рядом (таби · адреса · телефон), карта переходить
           з плоскої висоти 448 на пропорцію 1920/800, зʼявляється смуга фото.

   ⚠️ КРИХТИ НА ЧОТИРЬОХ АДАПТИВНИХ АРТБОРДАХ — ЧУЖІ: 820 показує «Головна /
   Каталог / Для неї», 375 — «Головна / Оформити замовлення». Це той самий
   залишок сторінки-донора, що й на десктопних станах; істина — канонічний
   артборд: «Головна / Контакти».
   ========================================================================== */

/* РИТМ РОЗДІЛІВ ТУТ СВІЙ. `main > .container` несе `margin-block-end:
   var(--section-gap)` — на 1920 це 100, і саме воно відсувало карту на 525
   при канонних 448 (замір, не здогад). Шви цієї сторінки зняті з макета
   підряд і тримаються токенами `--ct-*`; загальний ритм розділів тут не
   діє, як він не діє й на 404 (`main > .container:has(> .e404)`). */
main > .container:has(> .ct-bar),
main > .container:has(> .ct-view) { margin-block-end: 0; }

.ct__title {
  margin-block-start: var(--ct-title-top);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  color: var(--ink);
}

/*   Три елементи, дві доріжки. Пара «Офіс-шоурум» лежить на ОБОХ доріжках і
   ділить себе тією ж лінією центру: мітка закінчується на 960, значення
   починається на 976. Таби й телефон стоять у тих самих клітинках по краях —
   вони не стикаються (таби 48…252, мітка від 859; значення 976…1456,
   телефон від 1647), і ця відстань є в каноні, а не «має бути».
   Нижче десктопної стіни ряд розпадається, і накладання зникає разом із
   двома доріжками.

   ⚠️ ВОЛОСИНА В РЯДКУ ЛЕЖИТЬ НА РІЗНИХ ЕЛЕМЕНТАХ ПО РІЗНІ БОКИ СТІНИ 768, і
   це не примха: на 375 вона розділяє адресу й телефон (на 332
   при низі адреси 308), на 820 адреса й телефон стоять ПОРУЧ і ділити їх
   нічим — там вона відділяє пари від табів (на 388 при низі пар
   364). Тому її несе телефон нижче стіни й таби вище неї. Проміжок один і
   той самий — `--ct-bar-gap`.

   Проміжки заданi полями кожного блока, а не `gap` рядка: у стовпчику вони
   різні (24 · 24 · 32), і спільний `gap` довелося б потім тричі скасовувати. */
.ct-bar {
  margin-block-start: var(--ct-bar-top);
  display: grid;
  gap: 0;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* Таби ПІДНЯТІ над сусідом по клітинці. Пара «Офіс-шоурум» лежить на обох
   доріжках рядка й іде в розмітці ПІСЛЯ табів — тобто малюється поверх них і
   разом із фарбою забирає вказівник, хоч видимого перекриття немає
   (`check-controls-clickable` назвав це на 1024…2560). */
.ct-bar__tabs {
  position: relative;
  z-index: 1;
  order: 3;
  margin-block-start: var(--ct-tabs-top);
  display: flex;
  gap: var(--ct-tab-gap);
}

/* Неактивний таб на цій сторінці — `black/400`, а не `black/600` легенди
. Клас `.tab` лишається спільним: у ньому механіка підкреслення й
   активного стану, а тон — власний. */
.ct-tab[aria-selected="false"] { color: var(--label-ink); }

/* Нижче стіни 1024 пара — стовпчик «мітка над значенням»; вище — рядок. */
.ct-bar__pair { display: flex; flex-direction: column; gap: var(--ct-pair-gap); }

.ct-bar__pair--addr { order: 1; }

/* ⚠️ ВОЛОСИНА — ПСЕВДОЕЛЕМЕНТ У ПРОМІЖКУ, А НЕ `border` + `padding`.
   Рамка з падінгом дає ту саму картинку й ІНШУ коробку: canon-geometry
   зміряв телефон 73 при канонних 48 і таби 45 при канонних 21
. Коробка компонента — це те, що звіряється з макетом, тому
   декорація живе поза нею. Проміжок подвійний: 24 до волосини і 24 після. */
.ct-bar__pair--tel {
  order: 2;
  position: relative;
  margin-block-start: calc(2 * var(--ct-bar-gap) + var(--hairline));
}

.ct-bar__pair--tel::before,
.ct-bar__tabs::before {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-start: calc(-1 * (var(--ct-bar-gap) + var(--hairline)));
  block-size: var(--hairline);
  background: var(--line);
}

/* Вище стіни 768 волосину несуть таби, а не телефон (пари стоять поруч, і
   ділити їх нічим); вище 1024 її немає зовсім — рядок один. */
.ct-bar__tabs::before { content: none; }

.ct-bar__label { color: var(--label-ink); }

@media (min-width: 768px) {
  /* 772 = 378 + 16 + 378: пари стають двома колонками, волосина переїжджає
     під них — над таби. */
  /* `align-items: start` — не косметика: без нього телефон розтягується на
     висоту сусідньої адреси. */
  .ct-bar {
    grid-template-columns: 1fr 1fr;
    column-gap: var(--ct-half-gap);
    align-items: start;
  }

  .ct-bar__pair--addr { grid-area: 1 / 1; }

  .ct-bar__pair--tel { grid-area: 1 / 2; margin-block-start: 0; }

  .ct-bar__pair--tel::before { content: none; }

  .ct-bar__tabs {
    grid-area: 2 / 1 / 3 / -1;
    margin-block-start: calc(2 * var(--ct-bar-gap) + var(--hairline));
  }

  .ct-bar__tabs::before { content: ""; }
}

@media (min-width: 1024px) {
  .ct-bar {
    grid-template-columns: 1fr 1fr;
    align-items: start;
    gap: 0;
  }

  .ct-bar__pair { flex-direction: row; }

  .ct-bar__tabs { grid-area: 1 / 1; justify-self: start; margin-block-start: 0; }

  .ct-bar__tabs::before { content: none; }

  .ct-bar__pair--tel { grid-area: 1 / 2; justify-self: end; }

  .ct-bar__pair--addr {
    grid-area: 1 / 1 / 2 / -1;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0;
  }

  .ct-bar__pair--addr .ct-bar__label { justify-self: end; }

  /* ⚠️ ЗНАЧЕННЯ АДРЕСИ НЕ МОЖЕ ДІЙТИ ДО ТЕЛЕФОНА. Коробка значення займає
     праву половину ЦІЛКОМ (960…1872 на 1920) і лежить під парою телефона —
     так у каноні, і на 1920 це нічим не загрожує: текст кінчається на 1456,
     тобто до телефона лишається 191 px порожнечі. Але порожнеча ця
     НЕ ЗАДАНА нічим, і коротшає вона перша: на 1024 половина 484, текст
     адреси 640 — рядок ліг просто під телефон (зміряно: значення 512…996,
     телефон 779…996). Оком на 1920 цього не видно за побудовою.

     Резерв — власна ширина пари телефона плюс той самий проміжок 16, що
     розсуває половини; обидва числа канонні, свого тут немає жодного.

     ЩО ВИЙШЛО, ЗМІРЯНО: на 1920 обмеження не спрацьовує зовсім (текст
     кінчається на 1455 при телефоні від 1649 — прийнята ширина не
     рухається на жоден піксель), від 1680 адреса лишається в один рядок,
     на 1180…1536 переходить на два, на 1024 — на три. Композиція при цьому
     та сама: міняється тільки скільки рядків займає текст.

     ⚠️ 1536 БУВ ОДНИМ РЯДКОМ І СТАВ ДВОМА — свідома ціна, а не недогляд.
     Там до телефона лишалось 19 px, тобто рядок тримався випадковим
     запасом: інший текст адреси, інший кегль чи інший шрифт — і він ліг би
     під телефон так само, як на 1420. Обмеження прибирає саму можливість,
     а не окремий її випадок. */
  .ct-bar__pair--addr .ct-bar__value {
    padding-inline-start: var(--ct-half-gap);
    max-inline-size: calc(100% - var(--ct-tel-w) - var(--ct-half-gap));
  }
}

/*   Повноширинна смуга: у каноні вона від 0 до 1920, тобто в bleed, а не в
   контейнері. Висота задана ПРОПОРЦІЄЮ, а не плоскими 800: усередині стоїть
   шпилька на конкретному перехресті знімка, і при плоскій висоті кадр
   обрізався б по-різному на кожній ширині — шпилька зʼїхала б з вулиці. */
.ct-view--map {
  position: relative;
  margin-block-start: var(--ct-bar-gap);
}

/* Нижче стіни 1024 висота ПЛОСКА: канон дає 448 і на 820, і на 375 — те саме
   число при ширині, що відрізняється вдвічі. Пропорція там була б вигадкою. */
.ct-map__frame {
  position: relative;
  block-size: var(--ct-map-h);
  overflow: clip;
}

.ct-map__img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Оверлей лежить НА кадрі на всіх трьох якорях — той самий контейнер, що й
   решта сторінки, тому години сідають на лівий гутер самі.

   ⚠️ КАДР БУДИНКУ МІНЯЄ КУТ НА СТІНІ 768. На 375 він стоїть унизу ліворуч
   (— 16:773 при карті 465…913, тобто той самий відступ 24 від
   нижнього краю, що й години від верхнього), на 820 і 1920 — угорі праворуч
   (правим краєм на 795 при гутері 796; — на 1872).
   Тому нижче стіни оверлей — стовпчик на всю висоту карти, вище — рядок. */
.ct-map__over {
  position: absolute;
  inset-block: var(--ct-map-pad);
  inset-inline: 0;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  align-items: flex-start;
  gap: var(--s-24);
}

.ct-map__hours {
  inline-size: var(--ct-hours-w);
  color: var(--surface);
  /* Ширина ЗАДАНА, а не обмежена: у гнучкому ряду коробка з `max-inline-size`
     стискається до вмісту, і рядок «Понеділок-Пʼятниця 10.00-19.00» ламався
     втретє там, де канон тримає два рядки. */
  /* Два відрізки рядка не ламаються ВСЕРЕДИНІ: без цього дефіс у
     «10.00-19.00» дає рушієві законну точку переносу, і замість канонних
     двох рядків виходило три.
     Нерозривний тут саме відрізок, а не весь рядок: `nowrap` на всьому
     абзаці виносив би текст за край на вузьких ширинах. */
  /* Роль Lead (`Txt-20` у макеті): на десктопі це рівно 20/24 канону.
     `--fs-phrase` тут був помилкою — він росте до 24/28 з 1024, і рядок
     ламався втретє саме через це (зміряно: 343 px замість 286 у коробці
     334). Нижче стіни канон дає коробку 255×40 — два рядки по 20, тобто
     основний текст, а не Lead. Роль вибирає токен. */
  font-size: var(--ct-hours-fs);
  line-height: var(--ct-hours-lh);
}

.ct-map__hours span { white-space: nowrap; }

/* Ширина й висота ОКРЕМО, а не пропорцією: канон дає 80×116 · 181×255 ·
   200×282 — три різні пропорції, а `aspect-ratio` не інтерполюється. */
.ct-map__shot {
  flex: none;
  inline-size: var(--ct-shot-w);
  block-size: var(--ct-shot-h);
  object-fit: cover;
}

/* Шпилька стоїть часткою кадру — і розміром теж: кадр карти масштабується
   разом зі сторінкою, а знак, прибитий до пікселів, при цьому переставав би
   вказувати на своє перехрестя. */
.ct-map__pin {
  position: absolute;
  inset-inline-start: var(--ct-pin-x);
  inset-block-start: var(--ct-pin-y);
  inline-size: var(--ct-pin-w);
  aspect-ratio: var(--ct-pin-ratio);
  block-size: auto;
}

@media (min-width: 768px) {
  /* Кадр будинку переїжджає у верхній правий кут — оверлей стає рядом і
     перестає розтягуватись на всю висоту карти. */
  .ct-map__over {
    inset-block-end: auto;
    flex-direction: row;
  }
}

@media (min-width: 1024px) {
  /* Вище стіни режиму кадр карти росте разом зі сторінкою, тому висота
     задана ПРОПОРЦІЄЮ: при плоскій висоті знімок обрізався б по-різному на
     кожній ширині і шпилька зʼїхала б з перехрестя. */
  .ct-map__frame {
    block-size: auto;
    aspect-ratio: var(--ct-map-ratio);
  }
}

/*   Волосина над вмістом є ЛИШЕ в станах форми: у стані карти
   її в каноні немає, і намальована вона там була б світлою рискою по
   верхньому краю знімка. */
.ct-view--form {
  margin-block-start: var(--ct-bar-gap);
  padding-block: var(--ct-view-top) var(--ct-tail);
  border-block-start: var(--hairline) solid var(--line);
}

.ct-cols { display: grid; gap: var(--s-48); }

.ct-form { max-inline-size: var(--ct-form-w); }

.ct-form__title {
  font-size: var(--fs-ct-h2);
  line-height: var(--lh-ct-h2);
  font-weight: var(--fw-medium);
}

/* Проміжок колонок нижче стіни дорівнює проміжку рядів: на 820 канон дає 16
   1920 вони розходяться — 24 і 32. */
.ct-form__grid {
  margin-block-start: var(--ct-head-gap);
  display: grid;
  gap: var(--field-gap);
}

/* Смуга фото — це права половина цілком: 200·4 + 32·3 = 896. Канон малює
   ТРИ кадри в чотирьох слотах, і третій слот порожній (кадри стоять на 976,
   1208 і 1672). Порожнеча навмисна: вона є на ОБОХ артбордах форми, тобто
   це не одноразова недбалість.

   ⚠️ НИЖЧЕ СТІНИ 1024 СМУГИ НЕМАЄ ЗОВСІМ, і це сказано двома вузлами, а не
   одним кадром: на артборді форми 820 і на артборді форми 375
 немає ЖОДНОГО прямокутника із заливкою-зображенням — самі
   поля, кнопка й примітка. */
.ct-shots {
  display: none;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--s-32);
}

.ct-shots__shot {
  inline-size: 100%;
  aspect-ratio: var(--ct-shot-ratio);
  object-fit: cover;
}

@media (min-width: 768px) {
  .ct-form__grid { grid-template-columns: repeat(2, 1fr); }
}

@media (min-width: 1024px) {
  .ct-cols {
    grid-template-columns: 1fr 1fr;
    gap: 0;
  }

  /* Відступ від центру — ПОЛЕМ, а не падінгом: падінг лишив би коробку
     смуги на всю половину (912 замість канонних 896), і звірка з
     розтягування на висоту сусідньої колонки: група в каноні 282 заввишки,
     тобто рівно кадр. */
  .ct-shots {
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    margin-inline-start: var(--ct-half-gap);
    align-self: start;
  }

  .ct-shots__shot:nth-child(3) { grid-column: 4; }

  .ct-form__grid { gap: var(--field-gap) var(--s-32); }

  /* ⚠️ ПОРЯДОК КОЛОНОК НА 1920 ІНШИЙ, НІЖ НА ДВОХ АДАПТИВНИХ ЯКОРЯХ, і це
     канон, а не помилка: десктоп ставить E-mail праворуч від «Імʼя»
     ). На 375 усе стовпчиком: Імʼя · Прізвище · Телефон · E-mail
.

     Розмітка йде в порядку 375/820 — він же й порядок ФОКУСА, і він
     логічніший за десктопний (Імʼя → E-mail → Прізвище → Телефон). Десктоп
     повертає намальовану картинку двома явними клітинками; чотири поля
     незалежні одне від одного, тому порядок читання тут не несе змісту
     (WCAG 1.3.2), а порядок фокуса лишається послідовним (2.4.3). */
  .ct-form__grid > .field--mail { grid-area: 1 / 2; }

  .ct-form__grid > .field--tel  { grid-area: 2 / 2; }
}

/* --- ПОЛЕ ФОРМИ (компонент сайту, не сторінки) -----------------------------
   Класи навмисно БЕЗ сторінкового префікса: те саме поле стоїть на
   ті самі 316×64, ті самі чотири стани, той самий відступ 24. Префікс
   `ct-` тут означав би, що друга сторінка мусить завести копію правил, а
   копія спільного блоку — дефект за побудовою. Перейменовано разом із
   появою «Оформлення замовлення»; числа не зачеплені жодне.

   Чотири стани зняті властивістю вузла, не з рендера:

   Підпис поля в каноні намальований плейсхолдером — і тільки ним. Тут поруч
   стоїть справжній `label`, схований візуально: плейсхолдер зникає з першим
   символом, і людина, яка повернулась до поля, лишалась би без назви поля
   взагалі. Вигляд від цього не міняється жодним пікселем. */
/* `min-inline-size: 0` — не косметика. `input` і `textarea` мають ВЛАСНИЙ
   внутрішній розмір (атрибути `size`/`cols`, ~220 px разом із падінгами), а
   автоматичний мінімум елемента сітки дорівнює його min-content. Тому доріжка
   `1fr` не могла стиснутись нижче за цей розмір, і на 1024 форма 342.7
   розпиралась полем до 502.8 — доїжджаючи до колонки підсумку (777.8) і
   накриваючи її на 3 px. На трьох якорях це не було видно за побудовою: там
   доріжка ширша за внутрішній розмір поля (378, 343, 316), і рівно тому
   дефект прожив до першого адаптиву проміжних ширин. Правиться в КОМПОНЕНТІ
   САЙТУ, а не латкою на сторінці (ADAPTATION §8, М8). */
.field { position: relative; display: grid; min-inline-size: 0; }

/* Висота ЗАДАНА, а не виведена з падінгів: 22 + 20 + 22 + дві волосини рамки
   дало б 66 при канонних 64. Рядок у полі центрується сам, тому вертикальний
   падінг тут не потрібен зовсім — він потрібен текстовій рамці нижче. */
.field__input {
  inline-size: 100%;
  min-inline-size: 0;
  block-size: var(--field-h);
  padding-inline: var(--field-x);
  border: var(--hairline) solid var(--field-line);
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  transition:
    border-color var(--dur-fast) var(--ease),
    background-color var(--dur-fast) var(--ease);
}

.field__input::placeholder { color: var(--label-ink); }

.field__input:focus { border-color: var(--field-focus); }

/* Контур фокуса лягає ВСЕРЕДИНУ рамки, а не поруч із нею. Канон на наборі
   малює ОДНУ чорну рамку, а глобальний контур
   стоїть із відступом назовні — разом виходило дві рамки, що замовник і
   побачив на 1920. Відʼємний відступ у товщину контуру кладе його
   врівень із краєм поля: видно один суцільний кант, індикатор фокуса
   лишається на місці (він же й у forced-colors), а розкладка не їде на
   жоден піксель — контур геометрії не займає. */
.field__input:focus-visible { outline-offset: calc(-1 * var(--focus-ring)); }

/* Заповнене поле світлішає — саме так канон відрізняє його від порожнього.
   `:not(:focus)`, бо під час набору канон малює чорну рамку без заливки. */
.field__input:is(input, textarea):not(:placeholder-shown):not(:focus) {
  border-color: var(--field-fill);
  background: var(--field-fill);
}

/* Список так само світлішає, коли в ньому щось обрано (власниця, 12.09):
   країна й область лишались білими поруч із сірими сусідами, ніби порожні.
   Стан читається з форми, без класу: поки нічого не обрано, обовʼязковий
   `select` лишається `:invalid` — на цьому вже стоїть його колір тексту. */
.field__input:is(select):valid:not(:focus) {
  border-color: var(--field-fill);
  background: var(--field-fill);
}

.field__input[aria-invalid="true"],
.field__input[aria-invalid="true"]:not(:placeholder-shown):not(:focus) {
  border-color: var(--field-error);
  background: transparent;
}

/* Текстова рамка — те саме поле, тільки на 160 і на обидві доріжки. */
.field--area { grid-column: 1 / -1; }

.field--area .field__input {
  block-size: var(--field-area-h);
  /* Верх рядка в каноні — 763 при рамці 741, тобто 22 разом із волосиною. */
  padding-block: calc(var(--field-y) - var(--hairline));
  resize: vertical;
}

/* Підказка стоїть УСЕРЕДИНІ поля праворуч (— 592…689 при полі
   397…713, тобто 24 від правого краю), а в текстовій рамці — на рядку
   плейсхолдера. */
.field__hint {
  position: absolute;
  inset-inline-end: var(--field-x);
  inset-block-start: 50%;
  translate: 0 -50%;
  color: var(--field-error);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

.field--area .field__hint {
  inset-block-start: var(--field-y);
  translate: 0 0;
}

/* ⚠️ ПІДКАЗКА ФОРМИ ЗВЕРНЕННЯ — ПІД ПОЛЕМ (власниця, 12.09). У каноні вона
   стоїть усередині поля праворуч: там слово одне — «Не введені дані». Ми
   додали слова про формат («Перевірте адресу пошти»), і в полі вони
   налазять на набране.

   Звичайним потоком, а не абсолютом під полем: абсолют не займає місця й
   ліг би на сусіднє поле — сітка тут із зазором 8. Потік зсуває наступні
   поля на висоту рядка лише тоді, коли підказка ВИДНА, тобто в спокої
   сторінка та сама, що в макеті. */
.ct-form .field__hint {
  position: static;
  translate: none;
  display: block;
  margin-block-start: var(--s-8);
}

/* ⚠️ ТРЕТІЙ РАЗ ТА САМА ПАСТКА: `display` у правилі компонента стоїть у
   таблиці браузера ПІСЛЯ `[hidden]` і перебиває його. Без цього рядка
   підказка «Не введені дані» висіла під КОЖНИМ полем, зокрема заповненим. */
.ct-form .field__hint[hidden] { display: none; }

/* ⚠️ ПОМИЛКА НЕ РОЗСУВАЄ ФОРМУ (власниця, 12.09: «коли помилка, то відступ
   між полями не збільшуємо»). Підказка стоїть під полем, але АБСОЛЮТНО —
   тобто займає той проміжок, який між полями вже є, і не додає свого.
   Сітка форми не ворушиться: поля лишаються там, де стояли до помилки.

   Проміжок у каноні: 24 на десктопі, 16 на планшеті, 8 на телефоні. Двох
   останніх для рядка тексту мало, тому нижче 1024 поле резервує місце
   ЗАВЖДИ — і з помилкою, і без неї. Резерв сталий, отже поведінка та сама:
   ніщо не стрибає. */
.ct-form .field {
  position: relative;
  padding-block-end: var(--ct-hint-h);
}

.ct-form .field__hint {
  position: absolute;
  inset-block-start: auto;
  inset-block-end: 0;
  inset-inline-start: 0;
  inset-inline-end: auto;
  margin-block-start: 0;
}

@media (min-width: 1024px) {
  /* Тут проміжок 24 — підказка вміщається в нього, і резерв не потрібен. */
  .ct-form .field { padding-block-end: 0; }
  .ct-form .field__hint { inset-block-end: calc(-1 * var(--ct-hint-h)); }
}

/* Ряд «кнопка + примітка»: 216 і 414 з проміжком 24 (кнопка 50…266,
   примітка від 290). Примітка вирівняна по центру кнопки.

   ⚠️ НА 375 ЦЕ НЕ РЯД, А СТОВПЧИК ПО ЦЕНТРУ: кнопка 192 стоїть на 91, тобто
   рівно посередині 375, примітка — під нею, коробкою 329 на
   23 при контейнері 343. Проміжок 24 — той самий і в ряду, і
   в стовпчику, тому він тут числом, а не `--field-gap`: той нижче стіни
   дорівнює 8. */
.ct-form__actions {
  margin-block-start: var(--s-24);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--s-24);
  text-align: center;
}

/* Кнопка цієї форми — ВЛАСНА, а не сайтова 216×64: канон дає 192×56 на обох
   адаптивних якорях. Правити `--btn-h` означало б рухати всі сторінки. */
.ct-form__actions .btn {
  min-block-size: var(--ct-btn-h);
  min-inline-size: var(--ct-btn-w);
}

@media (min-width: 768px) {
  .ct-form__actions {
    flex-direction: row;
    flex-wrap: wrap;
    text-align: start;
  }
}

.ct-form__note {
  max-inline-size: var(--ct-note-w);
  color: var(--label-ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* Посилання на політику всередині примітки НЕ виділяється ні тоном, ні вагою:
   у каноні це суцільний приглушений рядок без жодної ланки.
   Базовий `.link` дав би чорний медіум посеред сірого тексту — тобто інший
   вигляд, ніж намальовано. Лишається сам жест: підкреслення при наведенні. */
.ct-form__note .link {
  color: inherit;
  font-weight: inherit;
}

/* ============================================================================
   ОФОРМЛЕННЯ ЗАМОВЛЕННЯ — (Place an order_1920) і чотири стани
   ----------------------------------------------------------------------------
   Сторінка нічого не винаходить: вона лягає на сітку сайту без залишку.
   Контейнер 1824 = 8 × 200 + 7 × 32; форма — колонки 3–5 (664), підсумок —
   7–8 (432). Тому тут майже немає власних ширин: їх видає сітка, а токени
   `--co-*` тримають лише вертикальні шви, зняті з канону підряд.

   Поле, текстова рамка й підказка помилки — спільний компонент `.field`
   (вище): та сама коробка й ті самі чотири стани, що на «Контактах». Коробка
   ця тепер РІЗНА за якорями — 64 на 1920 і 56 нижче за стіну 1024
   в компонента сайту, а не сторінки.

. Нижче за стіну 1024 сторінка йде ОДНІЄЮ колонкою, і це
   тепер канон, а не робочий вигляд: підсумок, плашка й примітка стоять під
   формою на всю ширину контейнера. Розкладка перемикається рівно на стінах
   режимів (768 і 1024); самі числа живуть у токенах `--co-*`.
   ============================================================================ */

/* Проміжки між трьома блоками сторінки НЕ спільні: H1 → форма і форма →
   підсумок різні на 375 (40 і 48). Тому `row-gap` нульовий, а кожен блок
   несе власний відступ — на всіх трьох якорях однаково. */
.co {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  row-gap: 0;
}

.co__title {
  margin-block-start: var(--co-title-top);
  max-inline-size: min(var(--co-title-w), 100%);
  font-size: var(--fs-doc-title);
  line-height: var(--lh-doc-title);
  font-weight: var(--fw-medium);
}

.co__form {
  display: grid;
  margin-block-start: var(--co-head-gap);
}

.co__form > * + * { margin-block-start: var(--co-sec-gap); }

/* Прапорець «Звʼязатись для уточнень» — не секція: він стоїть за 24 від
   рамки коментаря на 1920 (1186 → 1210) і за 16 на обох адаптивних якорях.
   Ширина 316 — власна ширина компонента, однакова на ВСІХ трьох артбордах
   доріжка 378, на 375 контейнер 343, а коробка все одно 316. */
.co__form > .co-opt--bare {
  margin-block-start: var(--co-bare-top);
  max-inline-size: min(var(--co-opt-bare-w), 100%);
}

/* Згода з умовами — НЕ канонний прапорець: підпис у неї довший, і канонні
   316 різали його навпіл (власниця, 12.09). Смуга віддається вся, і рядок
   лишається один. Підказка під нею — звичайним потоком: `.field__hint`
   позиціонується абсолютно, а тут позиціонованого предка немає, і слово
   відлітало б у куток сторінки. */
.co__form > .co-opt--terms { max-inline-size: 100%; }

.co-terms__hint {
  position: static;
  translate: none;
  display: block;
  margin-block-start: var(--s-8);
}

/* СЕКЦІЯ. Мітка 24/28 медіум із трекінгом −0.01em — це роль
   `--fs-doc-h`, та сама, що заголовок рівня в текстових сторінках: числа
   збіглись, тому нової ролі немає. */
.co-sec { display: grid; }

.co-sec > * + * { margin-block-start: var(--co-label-gap); }

.co-sec__label {
  font-size: var(--fs-doc-h);
  line-height: var(--lh-doc-h);
  font-weight: var(--fw-medium);
  letter-spacing: var(--ls-h1);
}

/* Секція оплати відсунута на 42, а не на 40 — так у каноні на чотирьох
   артбордах із пʼяти. Відхилення на 2 px названо токеном, а не заокруглено:
   заокруглення — це правка канону без рішення. */
.co__form > .co-sec--pay { margin-block-start: var(--co-pay-sec-gap); }

/* Мітка «Оплата*» відстоїть від першого варіанта на 22, а не на 24 (798 →
   820): рядок варіанта починається текстом, а не рамкою поля. */
.co-sec--pay > .co-opts { margin-block-start: var(--co-opt-top); }

/* --- ВАРІАНТ ВИБОРУ (доставка, оплата, «звʼязатись») ---------------------
   Порожній прапорець — КВАДРАТ 16×16 з обведенням `black/200`, обраний —
   зі станом, як у фільтрі каталогу: це два різні знаки, а не «оформлення
   прапорця». Підпис приглушений у спокої й чорніє обраним. */
/* `fieldset` скидається ТОЧКОВО: суцільне `margin: 0` має ту саму вагу, що
   спільний шов секції (`.co-sec > * + *`), і, стоячи нижче, з'їдало його —
   замір показав нуль там, де канон дає 24 (484 → 508). Власна маржа
   `fieldset` тільки бічна, тому досить її. */
.co-opts {
  display: grid;
  gap: var(--co-opt-row) var(--co-form-col);
  border: 0;
  margin-inline: 0;
  padding: 0;
}

/* `hidden` мусить перемагати ВЛАСНИЙ display кожного блока, який ним
   керується: `.co-sec`, `.co-opts`, `.co-ship__fields` і `.field` мають свій
   `display: grid`, а `.co-opt` — `flex`, і всі вони сильніші за браузерне
   правило для атрибута. Пропущений тут блок не ховається взагалі — саме так
   на першому знімку сторінки всі умовні блоки стояли одночасно. */
.co-opts[hidden],
.co-ship__fields[hidden],
.co-office[hidden],
.co-sec[hidden],
.co-opt[hidden],
.co-half[hidden] { display: none; }

/* Ряд полів. На мобільному — один стовпець із кроком 8 (і далі
   424 · 488 · 552 · 616 при коробці 56), на 820 і 1920 — два стовпці. */
.co-ship__fields {
  display: grid;
  gap: var(--field-gap) var(--co-form-col);
}

/* Половина доріжки — див. `.co-half` під стіною планшета: на мобільному
   сторінка йде одним стовпцем, і список на 155 px там був би не «як у
   макеті», а невживаним. */

.co-opt {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--co-opt-gap);
  padding-block-end: var(--co-opt-pad);
  border-block-end: var(--hairline) solid var(--line);
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}

/* Прапорець «Звʼязатись для уточнень» стоїть сам по собі й волосини під
   собою не має (— коробка 20 заввишки, рівно рядок). */
.co-opt--bare {
  padding-block-end: 0;
  border-block-end: 0;
}

.co-opt__box {
  position: absolute;
  inline-size: var(--co-opt-box);
  block-size: var(--co-opt-box);
  margin: 0;
  opacity: 0;
}

.co-opt__mark {
  flex: none;
  inline-size: var(--co-opt-box);
  block-size: var(--co-opt-box);
  margin-block-start: 2px;   /**/
  border: var(--hairline) solid var(--co-box-line);
  transition: background-color var(--dur-fast) var(--ease);
}

.co-opt__box:checked + .co-opt__mark {
  border-color: transparent;
  border-radius: var(--radius-pill);
  background: var(--ink);
}

.co-opt:has(.co-opt__box:checked) { color: var(--ink); }

.co-opt__box:focus-visible + .co-opt__mark {
  outline: var(--focus-ring) solid var(--ink);
  outline-offset: var(--focus-ring);
}

/* --- ВИБІР ЗІ СПИСКУ -----------------------------------------------------
   Рідний `select` під канонною рамкою: розкриття, клавіатура й екранний
   читач лишаються системними. Знак — експорт вузла, розвернутий
   на 180°, як розвертає його сам канон. */
.co-select { position: relative; }

.co-select__input {
  inline-size: 100%;
  appearance: none;
  padding-inline-end: calc(var(--co-select-inset) + var(--co-select-icon) + var(--s-8));
}

/* Поки не обрано нічого, `select` лишається `:invalid` — і саме так канон
   чорнилом. Стан читається з форми, без жодного класу. */
.co-select__input:invalid { color: var(--label-ink); }

.co-select__arrow {
  position: absolute;
  inset-inline-end: var(--co-select-inset);
  inset-block-start: calc((var(--field-h) - var(--co-select-icon)) / 2);
  inline-size: var(--co-select-icon);
  block-size: var(--co-select-icon);
  rotate: 180deg;
  pointer-events: none;
}

/* --- САМОВИВІЗ -----------------------------------------------------------
   ): 715 → 751 → 795 при рядку 20. */
.co-office { display: grid; }

.co-office__label { color: var(--label-ink); }

.co-office__addr { margin-block-start: var(--s-16); }

.co-office__rule {
  block-size: var(--hairline);
  margin-block-start: var(--field-x);
  background: var(--line);
}

/* --- ПРАВА КОЛОНКА ------------------------------------------------------- */

.co-aside {
  align-self: start;
  margin-block-start: var(--co-aside-top);
}

/*   80/116 проти 84/118. Тому `auto-fill` від ширини слота, а не `repeat(4)`:
   а розмір кадру — якір. */
.co-thumbs {
  display: grid;
  grid-template-columns: repeat(var(--co-thumb-cols), var(--co-thumb-w));
  justify-content: start;
  gap: var(--co-thumb-gap);
}

/* Висота ЗАДАНА, а не виведена пропорцією: пропорція кадру на якорях різна
   (80/116 проти 84/118), тож `aspect-ratio` мусив би стрибати там, де канон
   дає дві точки й пряму між ними. Два розміри — дві рампи. */
.co-thumb {
  inline-size: 100%;
  block-size: var(--co-thumb-h);
  object-fit: cover;
}

.co-sum { margin-block-start: var(--co-sum-top); }

.co-sum__row {
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* 49 = 24 + волосина + 24. Волосина стоїть за 25 над рядком, тобто рівно
   посередині шва; останній рядок волосини під собою не має, бо в каноні
    лежить під «До оплати» з `hidden="true"`, а прихований вузол
   малюється як відсутній. */
.co-sum__row + .co-sum__row { margin-block-start: var(--co-sum-step); }

.co-sum__row + .co-sum__row::before {
  content: "";
  position: absolute;
  inset-block-start: var(--co-sum-rule);
  inset-inline: 0;
  block-size: var(--hairline);
  background: var(--line);
}

.co-sum__key { color: var(--label-ink); }
.co-sum__val { margin: 0; font-weight: var(--fw-medium); }

/* ПЛАШКА ДІЙ — чорний прямокутник 432×88 з полем 16 з усіх боків; дві
   кнопки по 192 із зазором 16 (192·2 + 16·3 = 432). */
.co-panel {
  margin-block-start: var(--co-panel-top);
  display: flex;
  flex-wrap: wrap;
  gap: var(--co-panel-gap);
  padding: var(--co-panel-pad);
  background: var(--btn-bg);
}

.co-btn {
  display: grid;
  place-items: center;
  /* ОДИН МЕХАНІЗМ НА ТРИ ЯКОРІ: кнопки ділять плашку навпіл, і половина
     саме тому: воно виводиться, а не задається.

     `max-content` — не «щоб красивіше», а межа складу без жодного числа в
     CSS: поки два підписи з проміжком уміщаються в плашку, вони стоять у
     ряд; коли ні — переносяться. У смузі десктопа плашка живе колонкою 218
     (на 1024) і доростає до канонних 432 лише на 1920, тому нижня частина
     смуги фізично не тримає ряд — див.. */
  flex: 1 1 0;
  min-inline-size: max-content;
  block-size: var(--co-btn-h);
  border: var(--hairline) solid transparent;
  border-radius: var(--radius-pill);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  text-align: center;
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease);
}

/*   `black/200` під підписом `black/600`) — замовник назвав його
   тим, чим він є: «зараз весь час неактивний вид». Тобто це та сама
   пара, якою бібліотека вже малює `.btn[aria-disabled="true"]`, і збіг двох
   тонів не випадковий.

   Готовий стан канон не малює жодного разу, тому він узятий не з фантазії, а
   з МІСЦЯ: кнопка лежить на чорній плашці, і чорна кнопка на ній була б
   невидима — лишається інверсія, чорним по білому. Пара
   `--ink × --btn-ink` іде в контраст-гейт, на відміну від сірої: вимкнені
   контроли WCAG 2.2 з-під 1.4.3 виводить, а готові — ні. */
.co-btn--pay {
  background: var(--btn-ink);
  color: var(--ink);
}

.co-btn--pay[aria-disabled="true"] {
  background: var(--co-cta-bg);
  color: var(--co-cta-ink);
  cursor: not-allowed;
}

.co-btn--alt {
  border-color: var(--co-alt-line);
  color: var(--btn-ink);
}

.co-note .link {
  color: inherit;
  font-weight: inherit;
}

.co-note {
  margin-block-start: var(--co-note-top);
  max-inline-size: min(var(--co-note-w), 100%);
  margin-inline: auto;
  color: var(--label-ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  text-align: center;
}

/* --- ПЛАНШЕТНИЙ ЯКІР ------------------------------------------------------
   Стіна режиму `config.json → modes.tablet.from`. Артборд (820).
   Сторінка лишається однією колонкою — міняється те, що ВСЕРЕДИНІ форми стає
   у два стовпці: контактні дані (378 + 16 + 378 = 772) і варіанти оплати. */
@media (min-width: 768px) {
  .co-opts { grid-template-columns: repeat(2, 1fr); }

  /* ПОРЯДОК ВАРІАНТІВ ОПЛАТИ — ЗАПОВНЕННЯ ПО СТОВПЦЯХ, а не по рядах.
     Канон малює той самий набір двома розкладками: на 375 підряд згори
     («онлайн» 736 → «при отриманні» 789 → PayPal 842), а на 820 і 1920 —
     «онлайн» і «при отриманні» лівим стовпцем, PayPal праворуч угорі
     (512 проти 860).

     Тому в розмітці порядок МОБІЛЬНИЙ — він же порядок читання й обходу
     клавіатурою, — а два стовпці набираються `grid-auto-flow: column`.
     Зворотний хід (порядок розмітки десктопний, а на мобільному
     переставляти `order`-ом) дав би радіогрупу, де око бачить одне, а Tab
     веде інше. */
  .co-sec--pay > .co-opts {
    grid-template-rows: repeat(2, auto);
    grid-auto-flow: column;
  }

  /* ДВА ВАРІАНТИ — В ОДИН РЯД (власниця, 11.09, D563; виправлено 12.09).
     На сайті PayPal знято (D561), і заповнення по стовпцях ставило два
     варіанти один під одним. Розкладку обирає САМ НАБІР, а не сторінка:
     третього варіанта немає — значить, ряд. Так канонна сторінка з трьома
     варіантами лишається канонною, а `canon-geometry` бачить її незмінною. */
  .co-sec--pay > .co-opts:not(:has(.co-opt:nth-of-type(3))) {
    grid-template-rows: auto;
    grid-auto-flow: row;
  }

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

  /* «Відділення» лишається на ЛІВІЙ доріжці, як у каноні 1920 (512), хоч
     сусіда праворуч у нього більше немає: без цього рядка воно піднялось би
     до «Області» на 860 — місце, якого канон йому не давав. */
  .co-branch__last { grid-column: 1; }

  /*     доріжках: регіон 512, місто 860, індекс 512. */
  .co-abroad__wide { grid-column: 1 / -1; }

  /*     664). Половина мінус гутер, а не абсолютні 316: на 820 це 378. */
  .co-half { max-inline-size: calc((100% - var(--co-form-col)) / 2); }

}

/* --- ДЕСКТОПНИЙ ЯКІР ------------------------------------------------------
   Стіна цієї сторінки — `config.json → modes.desktop.pages`, тобто 1180, а не
   загальні 1024. Артборд (1920). Вище стіни сторінка стає
   тим, що намальовано: сітка на 8 колонок, форма на 3–5, підсумок правим
   краєм по контейнеру. */
@media (min-width: 1180px) {
  /* Чотири доріжки, а не вісім: порожнеча ліворуч, форма, вільний проміжок,
     підсумок. Сітка на 8 колонок описує ЯКІР — на 1920 ці чотири числа
     дають рівно її (464 + 664 + 264 + 432 = 1824, форма на 512, підсумок на
     1440), — а нижче по смузі вона перестає бути правдою: там дві порожні
     колонки коштують більше, ніж уся права колонка. */
  .co {
    grid-template-columns:
      var(--co-form-x) var(--co-form-w) minmax(var(--s-32), 1fr) var(--co-aside-w);
    column-gap: 0;
  }

  .co__title { grid-column: 2; }

  .co__form { grid-column: 2; }

  /* Права колонка ЛИПНЕ під шапкою (рішення власниці 11.09, D561): форма
     довга, і підсумок із кнопкою «Оформити» мусить лишатись у полі зору.
     Лише тут, де колонок дві: нижче стіни сторінка йде однією колонкою, і
     липкий підсумок закрив би форму. */
  .co-aside {
    grid-column: 4;
    grid-row: 2;
    position: sticky;
    inset-block-start: calc(var(--header-h) + var(--s-24));
  }

  /* Знак списку стоїть за 4 від краю рамки, а не за 20, — тому й запас під
     нього інший, ніж рахує базова формула. */
  .co-select__input { padding-inline-end: calc(var(--field-x) + var(--icon)); }

}

/* ============================================================================
   ----------------------------------------------------------------------------
   ДВІ СТОРІНКИ, ОДНА РОЗКЛАДКА. Ліворуч меню кабінету, праворуч ряди
   «підпис — поле»; міняється лише вміст рядів і те, що стоїть під ними.
   Тому блок один, а не два: другий був би копією з іншим префіксом, і
   перша ж правка розвела б їх.

   ЧИСЛА НА СІТКУ САЙТУ ЛЯГАЮТЬ ЛИШЕ НАПОЛОВИНУ. Поле стоїть рівно на
   колонках 5–8 (976 = 48 + 4×232, ширина 896 = 4×200 + 3×32), а підпис — на
   326, тобто МІЖ колонками (друга починається на 280, третя на 512). Тому
   колонка меню задана числом `--acc-rail`, а ряд ділиться пропорцією
   650fr: 896fr — на якорі це рівно канонні 650 і 896, а нижче обидві
   частини тануть разом, замість того щоб колонка зʼїдала поле.

   ⚠️ «Обране» в меню кабінету НЕ верстається: замовник назвав
   цей пункт помилкою дизайнера. Меню від цього коротшає на один крок (196
   замість 249), і саме тому висота меню з каноном не звіряється.

   КАНОН НИЖЧЕ 1024 ТЕПЕР Є: чотири артборди файла
   («Персональні дані» 820 і 375) і / («Допомога»).
   І він каже, що нижче стіни це ІНША композиція, а не стиснута десктопна:
   шапка сайту заміняється смугою «назад», вітання, рядок контактів і меню
   зникають зі сторінки цілком, а мітка розділу стає заголовком екрана.
   Меню кабінету нижче стіни стає ПЕРЕЛІКОМ на самій сторінці: поточний
   розділ у ньому розкритий, решта ведуть на свої адреси.
   ========================================================================== */

.acc__title {
  margin-block-start: var(--acc-title-top);
  font-size: var(--fs-doc-title);
  line-height: var(--lh-doc-title);
  font-weight: var(--fw-medium);
}

/*   Той самий рядок, що на «Контактах», тільки без табів: дві пари «мітка —
   значення» і волосина під ними. Друга пара стоїть не на половині смуги
   (960), а на 976 — на початку колонки поля, — і тримає це той самий
   прийом, що в `.ct-bar__pair--addr`: половина плюс гутер. */
.acc-bar {
  margin-block-start: var(--acc-bar-top);
  display: grid;
  gap: var(--acc-bar-pad);
  padding-block-end: var(--acc-bar-pad);
  border-block-end: var(--hairline) solid var(--line);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* Нижче десктопної стіни пара розпадається на два рядки: мітка й значення
   в один рядок стискали б мітку до переносу («Офіс-
шоурум:»). */
.acc-bar__pair { display: grid; gap: var(--s-4); }

.acc-bar__label { color: var(--label-ink); }

/* --- РОЗКЛАДКА КАБІНЕТУ --------------------------------------------------- */
.acc { margin-block-start: var(--acc-head-gap); }

/* Нижче стіни меню стоїть НАД вмістом, тому між ними потрібен свій крок:
   у каноні вони сусіди по горизонталі й такого шва не мають зовсім. */
.acc-body { margin-block-start: var(--s-48); }

/* Нижче стіни мітка розділу — ЄДИНИЙ видимий заголовок екрана: 24/28 медіум
   бере роль `--fs-ct-h2`. */
.acc__label {
  font-size: var(--acc-sec-fs);
  line-height: var(--acc-sec-lh);
  font-weight: var(--fw-medium);
}

/* Меню кабінету. Підкреслення в каноні завширшки з САМ ПУНКТ, а
   не з колонку: «Вихід» 39, «Мої замовлення» 220. Тому пункт — блок по
   вмісту, а не смуга. Крок 53 = рядок 20 + 16 до волосини + волосина + 16
   до наступного пункту. */
.acc-menu__list { display: grid; gap: var(--acc-menu-gap); }

.acc-menu__item {
  justify-self: start;
  display: flex;
  align-items: center;
  gap: var(--acc-menu-count);
  padding-block-end: var(--acc-menu-pad);
  border-block-end: var(--hairline) solid var(--line);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
}

/*   сусідів). Це не тільки накреслення: `aria-current` несе той самий факт
   тому, хто накреслення не бачить. */
.acc-menu__item[aria-current="page"] { font-weight: var(--fw-medium); }

/* Лічильник приглушений. У каноні це `black/900` під 40 % — 2.6:1 на білому;
   тут узятий крок `--ink-muted` (4.94:1), як у неактивного таба за
   число замовлень — інформація, а не декорація, і читати його мусять усі. */
.acc-menu__count { color: var(--ink-muted); }

/* --- РЯДИ «ПІДПИС — ПОЛЕ» -------------------------------------------------- */
.acc-row {
  position: relative;
  display: grid;
  padding-block-end: var(--acc-row-pad);
  border-block-end: var(--hairline) solid var(--line);
}

/* Проміжок між рядами міряється від ЛІНІЇ: 545 → 561 це 16 разом із
   волосиною, тому в токені вона віднята. */
.acc-row + .acc-row { margin-block-start: var(--acc-row-gap); }

/* ⚠️ ПОВІДОМЛЕННЯ СЕРВЕРА В КАБІНЕТІ — РЯДОК НАД ФОРМОЮ, А НЕ ПІДКАЗКА ПОЛЯ
   (власниця, 13.09). `.field__hint` стоїть абсолютно від найближчого
   позиціонованого предка, а у формі кабінету його немає: «Посилання
   підтвердження застаріло» висіло за правим краєм поля. Тут це звичайний
   рядок потоком — над першим рядом (у «Допомозі» — над кнопкою), лівим
   краєм форми. Успіх — чорнилом, помилка — тоном помилки. */
.acc-body [data-jk-note] {
  position: static;
  translate: none;
  display: block;
  margin: 0 0 var(--s-16);
}
.acc-body [data-jk-note][hidden] { display: none; }
.acc-body [data-jk-note].is-ok { color: var(--ink); }

/*   лежить під ним із `hidden="true"`, тобто вузол вимкнений. На «Допомозі»
   останній ряд волосину має, і саме тому це модифікатор, а не
   правило `:last-child`: дві сторінки — два різні канони. */
.acc-row--bare {
  padding-block-end: 0;
  border-block-end: 0;
}

/* Ряд із текстовою рамкою: 24 до волосини замість 16 (низ рамки 1009 →
   волосина 1033). */
.acc-row--area { padding-block-end: var(--acc-area-pad); }

/* Підпис ряду — тоном канону `black/400`. Той самий випадок, що приглушені
   підписи картки товару: пари `--label-ink × --surface` у
   контраст-гейті немає, поріг 4.5 для решти пар не займаний.

   Падінг 22 робить коробку підпису рівно 64 — тією ж, що в поля поруч
   (22 + 20 + 22). Тому ряд не потребує ані центрування, ані власної висоти:
   обидві клітинки однакові самі. */
.acc-row__label {
  padding-block: var(--field-y);
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* НИЖЧЕ СТІНИ 1024 КОЛОНКИ ПІДПИСУ НЕМАЄ: на (375) підпис лежить
   УСЕРЕДИНІ поля — 16 від лівого краю, 18 від верхнього, тон `black/400`, —
   і зникає, щойно поле заповнене (поле «юля@E-mail» підпису не має).

   Це той самий підпис, а не другий: `placeholder` полів цієї сторінки —
   пробіл, і саме він дозволяє `:placeholder-shown` відрізнити порожнє поле
   від заповненого. Два тексти на одному місці означали б два джерела одного
   факту, тому підпис лишається один і просто переїжджає. */
.acc-row__label {
  position: absolute;
  inset-block-start: var(--field-y);
  inset-inline-start: var(--field-x);
  padding-block: 0;
  pointer-events: none;
}

.acc-row:not(:has(.field__input:placeholder-shown)) .acc-row__label { opacity: 0; }

/* --- ПАРОЛЬ ---------------------------------------------------------------- */
/* Око й «Змінити пароль» стоять УСЕРЕДИНІ поля праворуч: у каноні знак на
   749…773 при полі 976…1872, тобто 24 від правого краю до підпису і 24 між
   «Допомоги»). */
.acc-pass__tools {
  position: absolute;
  inset-inline-end: var(--field-x);
  inset-block-start: 50%;
  translate: 0 -50%;
  display: flex;
  align-items: center;
  gap: var(--acc-eye-gap);
}

/* Кнопка ока — сам знак, без плашки. Розмір — знак канону 24×24; клікабельна
   площа добирається падінгом, а не збільшенням знака. */
.acc-eye {
  display: flex;
  padding: var(--s-8);
  margin: calc(-1 * var(--s-8));
  color: var(--ink);
}

/* `:not([hidden])` тут обовʼязкове: `display: block` інакше перебиває
   службове `[hidden]`, і в полі стоять ОБИДВА знаки один під одним. Та сама
   пастка, що в `.auth-eye` нижче — і саме тому знак тут заведений тим самим
   способом, а не «схожим». */
.acc-eye img:not([hidden]) {
  display: block;
  inline-size: var(--acc-eye);
  block-size: var(--acc-eye);
}

.acc-pass__change {
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  color: var(--ink-muted);
}

@media (hover: hover) {
  .acc-pass__change:hover { color: var(--ink); }
}

/*   тобто рівно колонка поля. Тому блок повторює РЯД: порожня колонка підпису
   ліворуч, вміст — у колонці поля. Без цього пара ділила б усю ширину
   вмісту й давала б 757 замість 432 (заміряно, а не припущено). */
.acc-pass { margin-block-start: var(--acc-pass-top); }

.acc-pass[hidden] { display: none; }

.acc-pass__acts {
  margin-block-start: var(--acc-pass-top);
  display: flex;
  flex-wrap: wrap;
  gap: var(--acc-btn-gap);
}

/**/
.acc-btn { min-width: var(--acc-btn-w); }

/* «Відмінити»: обведення `black/100`, підпис чорнилом, заливки немає — тон
   знятий властивістю вузла, а не з рендера. */
.acc-btn--ghost {
  background: transparent;
  color: var(--ink);
  border: var(--hairline) solid var(--line);
}

@media (hover: hover) {
  .acc-btn--ghost:hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
    color: var(--btn-ink);
  }
}

/* --- НИЗ «ДОПОМОГИ» -------------------------------------------------------- */
/* Кнопка й примітка стоять НА ОДНІЙ верхній лінії (обидві на 1065), але в
   різних колонках: примітка на місці підпису, кнопка — правим краєм
   контейнера. */
.acc-actions {
  margin-block-start: var(--acc-actions-top);
  display: grid;
  gap: var(--field-gap);
  justify-items: start;
}

.acc-actions__note {
  max-inline-size: var(--acc-note-w);
  color: var(--label-ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* Посилання всередині примітки не виділяється ні тоном, ні вагою — так само,
   приглушений рядок без жодної ланки. */
.acc-actions__note .link {
  color: inherit;
  font-weight: inherit;
}

.acc-send { min-width: var(--acc-send-w); }

/* --- НИЖЧЕ СТІНИ: ПЕРЕЛІК РОЗДІЛІВ НА САМІЙ СТОРІНЦІ ----------------------
   Розпорядження замовника: «в адаптиві буде типу таб відкритий та
   закриті, але не таби будуть працювати, а переходи по сторінкам». Тобто
   сторінка розділу нижче стіни показує ВЕСЬ перелік, поточний рядок у ньому
   відхилення.

   Рядки й вміст мусять чергуватись, а перелік має лишитись СПИСКОМ для
   допоміжної техніки. Тому `nav` і `ul` віддають своїх дітей батьківському
   флексу через `display: contents`, а порядок на екрані дає `order`: вміст
   стає між своїм рядком і наступним. Другого екземпляра розмітки немає —
   перелік один на три сторінки. */
.page-cabinet .acc {
  display: flex;
  flex-direction: column;
  gap: var(--of-item-gap);
}

.page-cabinet .office,
.page-cabinet .office__list { display: contents; }

.page-cabinet .office__list > li:nth-child(1) { order: 10; }
.page-cabinet .office__list > li:nth-child(2) { order: 20; }
.page-cabinet .office__list > li:nth-child(3) { order: 30; }

.page-cabinet .office__exit { order: 40; margin-block-start: 0; }

/* Вміст сторінки стає ПІСЛЯ свого рядка: 25 між 20 і 30, 35 між 30 і 40. */
.page-cabinet--account .acc-body { order: 25; }

.page-cabinet--help .acc-body { order: 35; }

/* Розкритий рядок нікуди не веде — він і є ця сторінка. Знак перевернутий,
   підпис медіумом: рядок читається як відкритий, а не як ще одне посилання. */
.page-cabinet .office__item[aria-current="page"] {
  font-weight: var(--fw-medium);
  cursor: default;
}

.page-cabinet .office__item[aria-current="page"] .office__go { rotate: 180deg; }

/* Заголовок розділу окремим рядком зникає: його роль бере рядок переліку, і
   два однакові підписи поспіль читались би як помилка. Для дерева
   доступності він лишається — сторінка без H2 втратила б назву розділу. */
.page-cabinet .acc__label {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* --- НИЖЧЕ СТІНИ: ЧОГО НА СТОРІНЦІ НЕМАЄ ----------------------------------
   Вітання й рядок контактів не намальовані на жодному з чотирьох вузьких
   артбордів, а меню кабінету поїхало на власну сторінку. Вітання лишається в
   розмітці схованим, а не вирізаним: воно H1, і документ без H1 читався б
   допоміжною технікою як сторінка без назви. Рядок контактів і меню
   вимикаються цілком — їх на екрані немає в жодному стані. */
.page-cabinet .acc__title {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.page-cabinet .acc-bar,
.page-cabinet .acc-menu { display: none; }

/* Крихти → мітка розділу: 32 на 375, 40 на 820. Вище стіни крок лишається
   тим, що був (волосина рядка контактів → мітка). */
.page-cabinet .acc { margin-block-start: var(--acc-label-top); }

.page-cabinet .acc-body { margin-block-start: 0; }

/* Пара полів нового пароля нижче стіни стоїть СТОВПЧИКОМ: на і
    вони одна під одною з кроком 72 = поле 56 + 16. */
.acc-pass__pair { display: grid; gap: var(--acc-pass-gap); }

.acc-pass__acts {
  margin-block-start: var(--acc-pass-top);
  display: flex;
  gap: var(--acc-btn-gap);
}

/* Нижче стіни кнопки кабінету 168×56, а не 192×64: /`41933` на
   820 і /`41771` на 375. Ширина задана, а не мінімальна, — інакше
   падінг капсули робить її 169.5, пара не влазить у 343 і другий рядок
   переноситься під перший. 168 + 7 + 168 = 343 рівно. */
.acc-btn,
.acc-send {
  min-height: var(--acc-btn-h);
  block-size: var(--acc-btn-h);
}

.acc-btn {
  inline-size: var(--acc-btn-w);
  min-width: 0;
}

/* --- НИЖЧЕ СТІНИ: НИЗ «ДОПОМОГИ» ------------------------------------------
   На 375 кнопка стоїть ПОСЕРЕДИНІ смуги й ВИЩЕ примітки (на 707
   розмітці. Міняється саме порядок показу: у розмітці примітка лишається
   перед кнопкою, бо прочитати умову треба ДО того, як її прийняли. */
.acc-actions { justify-items: center; }

.acc-actions__note { order: 2; }

.acc-send { order: 1; }

/* ==========================================================================
   --------------------------------------------------------------------------
   Нижче стіни 1024 меню кабінету не стоїть колонкою, а стає окремим екраном:
   вітання, три розділи, «Вихід». Вище стіни канону цієї сторінки немає жодним
   артбордом — там вона показує ту саму оболонку, що решта кабінету (вітання,
   рядок контактів, меню колонкою), і це виведення, а не знімок: рядок.
   ========================================================================== */
.office__list {
  margin-block-start: var(--of-list-top);
  display: grid;
  gap: var(--of-item-gap);
}

/* Крок пункту 69 = рядок 20 + 24 до волосини + сама волосина + 24 до
   наступного. Однаковий на обох вузьких якорях; різна лише ширина смуги. */
.office__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s-16);
  padding-block-end: var(--of-item-pad);
  border-block-end: var(--hairline) solid var(--line);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  color: var(--ink);
}

/* Лічильник у дужках і приглушений — так у каноні.
   Тон узятий крок темніше за канонний, як і в меню кабінету: число замовлень
   це інформація, а не декорація. */
.office__count { color: var(--ink-muted); }

/*   стоїть із `-rotate-90`. Файл окремий (`arrow-down-ink.svg`), бо наявний
   `arrow-down.svg` тієї самої родини має БІЛЕ обведення — він для темного
   тла, і на білій сторінці його не видно зовсім. */
/* Знак рядка дивиться ВНИЗ (замовник: «ці штуки буде потрібно
   в низ розвернути»); у розкритого рядка — вгору (`aria-current`). */
.office__go {
  flex: none;
  inline-size: var(--of-arrow);
  block-size: var(--of-arrow);
}

/* Вище стіни 1024 смуга розділів капиться, а не тягнеться на весь контейнер:
   керування завширшки 1824, у якого підпис ліворуч, а знак за 1.8 м праворуч,
   не є продовженням якоря — це його розтягнута тінь. Стеля взята з сусідніх
   сторінок кабінету, де найширша доріжка — 896. */
@media (min-width: 1024px) {
  .office__list { max-inline-size: var(--of-list-max); }
}

.office__exit {
  margin-block-start: var(--of-exit-top);
  margin-block-end: var(--of-exit-bottom);
  display: flex;
  justify-content: center;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

/* --- ПЛАНШЕТНА СМУГА КАБІНЕТУ — канон 820 ---------------------------------
   Ряд повертає колонку підпису: 24…221 під підпис, 221…796 під поле. Меню й
   вітання так само немає — вони на сторінці-хабі. */
@media (min-width: 768px) {
  .acc-row {
    grid-template-columns: var(--acc-label-fr) var(--acc-field-fr);
  }

  /* Селектор повторює гасіння один в один, і саме тому повертає його: правило
     нижче стіни специфічніше за просте `.acc-row__label`, і коротший запис
     лишив би підпис заповненого поля згаслим на 820 — а канон його там ПОКАЗУЄ
. */
  .acc-row__label,
  .acc-row:not(:has(.field__input:placeholder-shown)) .acc-row__label {
    position: static;
    padding-block: var(--field-y);
    opacity: 1;
  }

  .acc-pass {
    display: grid;
    grid-template-columns: var(--acc-label-fr) var(--acc-field-fr);
  }

  .acc-pass__body { grid-column: 2; }

  /* Текстова рамка «Допомоги» стоїть у колонці ПОЛЯ, а не на обидві доріжки:
     правило `.field--area` написане для форми «Контактів», де доріжки дві й
     обидві належать формі. */
  .acc-row .field--area { grid-column: auto; }

  /* Примітка й кнопка повертаються на одну лінію: примітка лівим краєм
     контейнера, кнопка правим. */
  .acc-actions {
    grid-template-columns: minmax(0, 1fr) auto;
    align-items: start;
    justify-items: start;
    gap: var(--s-16);
  }

  .acc-actions__note { order: 0; }

  .acc-send { order: 0; justify-self: end; }
}

/* --- ДЕСКТОПНА СТІНА ------------------------------------------------------- */
@media (min-width: 1024px) {
  .acc-bar {
    grid-template-columns: 1fr 1fr;
    align-items: start;
    gap: 0;
    padding-block-end: var(--acc-bar-pad);
  }

  /* Друга пара починається на 976, а не на 960: половина плюс гутер. */
  .acc-bar__pair--tel { padding-inline-start: var(--acc-half-gap); }

  .acc-bar__pair { display: flex; gap: var(--acc-pair-gap); }

  .acc {
    display: grid;
    grid-template-columns: var(--acc-rail) minmax(0, 1fr);
    align-items: start;
  }

  /* Меню опущене на 15 від верху мітки розділу — так на ОБОХ артбордах
     «Персональних даних» (412 при 397 і 411 при 396). */
  .acc-menu {
    inline-size: var(--acc-menu-w);
    margin-block-start: var(--acc-menu-top);
  }

  /* Меню й вміст стають сусідами по горизонталі — шва між ними немає. */
  .acc-body { margin-block-start: 0; }

  /* Вище стіни сторінка знову має вітання, рядок контактів і меню, а перелік
     розділів поступається місцем колонці меню. */
  .page-cabinet .office,
  .page-cabinet .office__exit { display: none; }

  .acc-btn,
  .acc-send {
    min-height: var(--acc-btn-h);
    block-size: auto;
  }

  .acc-btn {
    inline-size: auto;
    min-width: var(--acc-btn-w);
  }

  /* ⚠️ `margin` ТУТ ДВОМА РЯДКАМИ, І ДРУГИЙ ОБОВʼЯЗКОВИЙ. Ховання нижче стіни
     ставить `margin: -1px`, тому повернення мусить його зняти — але голий
     `margin: 0` б'є специфічністю власний відступ заголовка
     (`.acc__title { margin-block-start: var(--acc-title-top) }`), і вітання
     піднімалось до крихт, тягнучи за собою всю сторінку на 60 px. Заміряно
 порівнянням кадрів 1920 до і після. */
  .page-cabinet .acc__title {
    position: static;
    width: auto; height: auto;
    margin: 0;
    margin-block-start: var(--acc-title-top);
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }

  .page-cabinet .acc-bar { display: grid; }

  .page-cabinet .acc-menu { display: block; }

  .page-cabinet .acc {
    margin-block-start: var(--acc-head-gap);
    display: grid;
    gap: 0;
  }

  .page-cabinet .acc__label {
    position: static;
    width: auto; height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }

  .acc-pass__acts { gap: var(--acc-btn-gap); }

  .acc-actions { justify-items: start; }

  .acc-row {
    grid-template-columns: var(--acc-label-fr) var(--acc-field-fr);
    gap: 0;
  }

  /* ⚠️ РЯД ПРИМІТКИ НЕ ДІЛИТЬСЯ ЯК РЯД ПОЛЯ, і це не спрощення. Канон на 1920
     ставить примітку на 326 — початок колонки підпису — але завширшки 414 при
     колонці 650, тобто вона там просто НЕ на всю доріжку. Поділ давав
     той самий x лише на самому якорі: на 1024 колонка підпису стискається до
     273, і примітка ламалась на чотири рядки замість двох (заміряно
 чекером check-line-count на 1024 і 1140).
     `minmax(0, 1fr) auto` дає той самий x і ту саму ширину на 1920, але не
     душить примітку в смузі 1024…1419. */

  /* Пара полів пароля і кнопки під нею живуть у колонці ПОЛЯ, а не на всю
     ширину ряду: у каноні вони починаються на тих самих 976. */
  .acc-pass {
    display: grid;
    grid-template-columns: var(--acc-label-fr) var(--acc-field-fr);
  }

  .acc-pass__body { grid-column: 2; }

  .acc-pass__pair { grid-template-columns: 1fr 1fr; }

  /* Текстова рамка на цій сторінці НЕ розтягується на обидві доріжки: у
     `.field--area` це правило написане для форми «Контактів», де доріжки
     дві й обидві належать формі. Тут друга доріжка — колонка поля, і рамка
     вже стоїть у ній. */
  .acc-row .field--area { grid-column: auto; }
}

/* ==========================================================================
   ВХІД · РЕЄСТРАЦІЯ · ВІДНОВЛЕННЯ ПАРОЛЯ — `.auth-*`

   Що чотири «Log_1920_» — це СТАНИ, а не чотири сторінки, перевірено
   вузлами, а не назвою: у всіх чотирьох крихти, смуга кадрів, заголовок,
   поля, кнопка й підпис стоять на тих самих координатах; різниця живе в
   наповненні полів і в одному зайвому вузлі підказки.

   ОДНА ВІСЬ НА ТРИ СТОРІНКИ. Колонка форми — 896 (512…1408), тобто рівно
   колонки 3–6 сітки, і вона стоїть посеред контейнера. Усередині ділиться
   навпіл: 432 + 32 + 432.

   ⚠️ РЕЄСТРАЦІЇ, КРОКУ ПОШТИ І КРОКУ НОВОГО ПАРОЛЯ СЕРЕД НИХ НЕМАЄ. Ті три
   сторінки стоять на тих самих `.auth-*`, тобто числа дістають самі — але
   це ВИВЕДЕНО, а не знято з макета, і для них лишається чинним.

   ЩО ПЕРЕМИКАЄТЬСЯ НА СТІНАХ:
     768 ряд полів стає двома колонками, перемикач «Зареєструватися»
           повертається в рядок заголовка з-під ряду постачальників;
     1024 колонка форми відривається від контейнера (896 посеред 1824),
           смуга кадрів стає шістьма слотами замість чотирьох.
   ========================================================================== */

/*   артбордах. Підпису в неї немає, наведення теж: це декорація, тому весь
   блок схований від дерева доступності.

   Шість слотів по 200 з кроком 232, а плиток пʼять: слот 5 (976) у каноні
   порожній. Це не пропуск верстки — так намальовано на кожному артборді.

   ⚠️ НИЖЧЕ СТІНИ 1024 СЛОТІВ ЧОТИРИ, І ПОРОЖНІЙ — ТРЕТІЙ. Канон дає плитки
   16 · 104 · 280 при кроці 88, тобто четвертий
   слот зайнятий, а третій порожній. Тому нижче стіни ховаються плитки 3 і 6,
   а порожня (4-та в розмітці) лишається — вона й стає третім слотом. Так
   набір слотів у CSS завжди дорівнює набору в каноні, і крок колонки
   лишається рівним (це міряє `check-grid-step`).

   КАДРИ — З МАКЕТА. Макет ставить у пʼять плиток ТРИ фото, одне з
    і побайтово однаковий. */
.auth-band {
  margin-block-start: var(--auth-band-top);
  margin-inline: auto;
  max-inline-size: var(--auth-band-w);
  display: grid;
  gap: var(--auth-band-gap);
  grid-template-columns: repeat(4, 1fr);
}

/* Висота — числом, а не пропорцією: канон дає 80×116 · 181×255 · 200×282,
   тобто три РІЗНІ пропорції, а `aspect-ratio` не інтерполюється. */
.auth-band__tile {
  block-size: var(--auth-band-h);
  overflow: hidden;
}

.auth-band__tile img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

/* Нижче стіни режиму видно чотири слоти з шести: ховаються ТРЕТЯ і ШОСТА
   плитки, тому лишається «кадр · кадр · порожньо · кадр» — рівно як у
   каноні обох адаптивних якорів. Третя і пʼята плитки несуть той самий
   файл, тож картинка від підміни не міняється жодним пікселем. */
.auth-band__tile:nth-child(3),
.auth-band__tile:nth-child(6) { display: none; }

/* Колонка форми. Нижче стіни режиму вона просто вміст контейнера.

   ⚠️ ФЛЕКС-СТОВПЧИК ТУТ НЕ КОСМЕТИКА. Нижче стіни 768 канон переносить
   перемикач «Зареєструватися» з рядка заголовка ПІД ряд постачальників
   його в розмітці не можна — це два однакові посилання в дереві
   доступності, — тому переставляє його `order`, а `display: contents` на
   шапці робить заголовок і перемикач прямими дітьми цієї колонки.
   Решта дітей лишається з `order: 0`, тобто в порядку розмітки. */
.auth {
  margin-inline: auto;
  max-inline-size: var(--auth-w);
  display: flex;
  flex-direction: column;
}

/* Вище стіни 768 перестановки немає, тому немає й флекса: колонка знову
   звичайний блок. Це не косметика — флекс робить її ПОТОКОМ для
   `check-grid-step`, і той починає міряти крок між блоками, які кроку не
   мають: заголовок, форма, постачальники й примітка стоять на РІЗНИХ швах
   (16 · 24 · 32), кожен зі своєю рампою. */
@media (min-width: 768px) {
  .auth { display: block; }
}

/* Заголовок і посилання-перемикач («Зареєструватися» ↔ «Вхід в кабінет»).
   У каноні вони стоять на РІЗНИХ верхах — 522 і 534 на 1920, 415 і 419 на
   820, — але низи однакові: рядки вирівняні по базовій лінії, а не по
   верху. */
.auth__head { display: contents; }

.auth__title {
  margin-block-start: var(--auth-head-top);
  font-size: var(--fs-auth-h);
  line-height: var(--lh-auth-h);
  font-weight: var(--fw-medium);
}

/* Знак — «arrow up» ПОВЕРНУТИЙ на 90°, а не окремий малюнок стрілки вправо:
   тому, що наявний `arrow-up.svg` намальований білим — для темного тла. */
.auth__switch {
  order: 1;
  margin-block-start: var(--auth-switch-top);
  align-self: center;
  display: inline-flex;
  align-items: center;
  gap: var(--s-8);
  justify-self: start;
  font-weight: var(--fw-medium);
}

/* Примітка йде ПІСЛЯ перемикача — це єдиний блок, який на 375 лишається
   під ним. */
.auth-legal { order: 2; }

.auth__switch img {
  inline-size: var(--icon);
  block-size: var(--icon);
  rotate: 90deg;
}

.auth-form { margin-block-start: var(--auth-form-top); }

.auth-row {
  display: grid;
  gap: var(--auth-row-gap);
}

.auth-row + .auth-row { margin-block-start: var(--auth-row-gap); }

/* Поле на цих сторінках підпису поруч не має — підпис намальований самим
   плейсхолдером. Справжній `label` стоїть поруч схованим: плейсхолдер
   зникає з першим символом, і людина, яка повернулась до поля, лишилась би
   без назви поля взагалі. */
.auth-field { position: relative; }

/* Поле НЕ росте від власної внутрішньої ширини `<input>`. Без цього на 375
   рамка з падінгом під «Забули пароль?» (172) виходила за контейнер: у
   `<input>` є своя типова ширина, і в гріді вона стає min-content. */
.auth-field .field__input { inline-size: 100%; min-inline-size: 0; }

/* Підказка помилки стоїть НАД полем і праворуч (: 1311…1408 при
   полі 976…1408, низ 582 при верху поля 590). Вона абсолютна навмисно: в
   обох артбордах помилки поле лишається на тих самих 590, тобто поява
   підказки розкладку не зсуває. */
.auth-field .field__hint {
  inset-block-start: auto;
  inset-block-end: calc(100% + var(--auth-hint-gap));
  inset-inline-end: 0;
  translate: none;
}

/* Інструменти всередині поля пароля: око, а на вході — ще й «Забули
   пароль?». Праву межу тримає падінг поля, тому обидва випадки описуються
   одним правилом. */
.auth-tools {
  position: absolute;
  inset-inline-end: var(--auth-tools-pad);
  inset-block-start: 50%;
  translate: 0 -50%;
  display: flex;
  align-items: center;
  gap: var(--auth-eye-gap);
}

.auth-eye {
  display: block;
  inline-size: var(--icon);
  block-size: var(--icon);
}

/* `:not([hidden])` тут обовʼязкове: `display: block` інакше перебиває
   службове `[hidden]`, і в полі стоять ОБИДВА знаки один під одним. */
.auth-eye img:not([hidden]) {
  display: block;
  inline-size: 100%;
  block-size: 100%;
}

.auth-forgot {
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  white-space: nowrap;
}

/* Текст поля не заходить під інструменти. Два числа, бо інструментів два
   набори: саме око — 48, око з посиланням — 172 (поле 432, око на 260). */
.auth-field--eye .field__input { padding-inline-end: var(--auth-eye-pad); }
.auth-field--forgot .field__input { padding-inline-end: var(--auth-forgot-pad); }

/* Кнопка стоїть посеред колонки форми на ВСІХ трьох якорях: 864 + 96 = 960
   на 1920, 314 + 96 = 410 на 820, 91 + 96 = 187.5 на 375. До появи
   адаптивних артбордів вона нижче стіни стояла до лівого краю — це був
   робочий вигляд без канону, і канон його не підтвердив. */
.auth-actions {
  margin-block-start: var(--auth-cta-top);
  display: flex;
  justify-content: center;
}

/* Висота власна, а не сайтова `--btn-h`: 56 на обох адаптивних якорях. */
.auth-cta {
  min-width: var(--auth-cta-w);
  min-block-size: var(--auth-cta-h);
  padding-inline: var(--s-24);
}

.auth-cta--lg { min-width: var(--auth-cta-w-lg); }

/* «Продовжити через» + три кнопки постачальників. Ряд 392 = 3×120 + 2×16,
   стоїть посеред колонки (764 + 196 = 960). */
.auth-social {
  margin-block-start: var(--auth-social-top);
  /* Ширина блока — по ряду кнопок (392), а не по колонці форми: у каноні це
     власна коробка на 764…1156, тобто 392 по центру.

     ⚠️ ЧИСЛОМ, А НЕ `fit-content`. `fit-content` міряє ряд по НАЙДОВШОМУ
     ТЕКСТУ («Facebook» 70 + падінги), і поки кнопка мала `min-width: 104`,
     збіг був випадковим. Щойно ширина стала базисом, коробка схлопнулась до
     274 — і ряд поламався надвоє вже на 375, тобто на самому якорі.
     `min(…, 100%)` лишає стиснення на 320. */
  inline-size: min(var(--auth-social-row-w), 100%);
  margin-inline: auto;
  display: grid;
  justify-items: center;
  gap: var(--auth-social-gap);
}

.auth-social__label {
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
}

/* `inline-size: 100%` — не косметика. Блок вище стоїть гридом із
   `justify-items: center`, тобто його діти беруть ширину ПО ВМІСТУ, а не по
   коробці блока. Ряд кнопок від цього міряв себе по найдовшому тексту (274)
   замість трьох кнопок із проміжками (328) — і ламався надвоє на кожній
   ширині, включно з обома якорями. Підпис вище центрується далі як був. */
.auth-social__list {
  inline-size: 100%;
  display: flex;
  /* ⚠️ `nowrap` НАВМИСНО. Флекс із переносом кладе елементи по рядках за
     їхнім БАЗИСОМ і лише потім стискає те, що лягло, — тому на 320 три
     кнопки по 104 їхали в другий ряд замість того, щоб стиснутись до 90.7
     ((288 − 16) / 3). Ряд із трьох кнопок не має де переноситись: підпис
     кожної короткий, а падінг нижче якоря тече до 8 і лишає місце. */
  flex-wrap: nowrap;
  justify-content: center;
  gap: var(--auth-social-step);
}

/* Кнопка постачальника — контурна капсула 120×64: обведення `black/100`,
   «Відмінити» кабінету, тому нового кольору тут не зʼявляється. */
/* Бічний падінг — той самий, що в поля (16 нижче стіни, 24 вище), а не
   плоскі 24. З 24 підпис «Facebook» (70) плюс 48 давав 118 при канонних
   104: кнопка розпирала себе зсередини, ряд ставав 375 при канонних 328 і
   на 375 ламався в два рядки. Зміряв це canon-geometry, не око. */
/* ⚠️ ШИРИНА — БАЗИС, А НЕ МІНІМУМ. `min-width` не давав кнопкам
   стиснутись, і нижче мобільного якоря ряд ламався надвоє: 3×104 + 2×8 =
   328 при контейнері 288 на 320. Тепер 104 — це те, з чого кнопка починає,
   і на обох якорях вона там і лишається (на 375 ряд 328 у контейнері 343);
   нижче 375 ряд стискає всі три однаково й лишається одним рядом.
   `min-width: 0` обовʼязкове: без нього діють сайтові 216 з `.btn`. */
.auth-social__btn {
  flex: 0 1 var(--auth-social-w);
  min-width: 0;
  min-block-size: var(--auth-social-h);
  padding-inline: var(--auth-social-pad);
  background: transparent;
  color: var(--ink);
  border: var(--hairline) solid var(--line);
}

/* Наведення — чорна кнопка з білим підписом, як у решти обведених. */
@media (hover: hover) {
  .auth-social__btn:hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
    color: var(--btn-ink);
  }
}

.auth-legal {
  margin-block-start: var(--auth-legal-top);
  margin-inline: auto;
  max-inline-size: var(--auth-legal-w);
  color: var(--label-ink);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  text-align: center;
}

/* --- ВІДНОВЛЕННЯ ПАРОЛЯ: три кроки одного екрана --------------------------
   Кроки 2 і 3 у спокої не існують (`hidden`, а не прозорість: для дерева
   доступності «немає» і «невидиме» — різні речі). Їхні коробки знімаються
   іменованими станами ранера `recovery-code` і `recovery-new`.
   ------------------------------------------------------------------------ */

/* ⚠️ ЗАГОЛОВОК КРОКУ «КОД» НА 375 ЛАМАЄТЬСЯ НАДВОЄ, і це канон, а не
   випадковість переносу: — коробка 216×56, тобто два рядки по
   28, при контейнері 343, де рядок вмістився б цілком. На 820 той самий
   лише нижче стіни 768. */
.auth__title--wrap { max-inline-size: var(--auth-code-title-w); }

@media (min-width: 768px) {
  .auth__title--wrap { max-inline-size: none; }
}

/*   ламає його на два рядки. */
.auth-lead {
  margin-block-start: var(--auth-lead-top);
  max-inline-size: var(--auth-lead-w);
}

/* Пʼять комірок коду. На 1920 це 5×166 + 4×16 = 894, тобто на 2 px вужче
   1240…1406), і 2 px не вирівнюються мовчки, бо тоді комірка перестала б
   бути 166.

   ⚠️ НИЖЧЕ СТІНИ РЯД ЗАЙМАЄ КОНТЕЙНЕР ЦІЛКОМ, і ширину комірки видає сам
   контейнері 343). Обидва — це «контейнер мінус проміжки, поділити на
   пʼять», тому механізм один, а чисел нема жодного. */
.auth-code {
  margin-block-start: var(--auth-code-top);
  display: flex;
  gap: var(--auth-code-gap);
}

.auth-code__cell {
  min-inline-size: 0;
  flex: 1 1 0;
  block-size: var(--field-h);
  /* Бічного падінга в комірки НЕМАЄ навмисно: цифра в ній одна й стоїть по
     центру, тобто падінг вигляду не міняє, а на 320 саме він не давав пʼяти
     коміркам стиснутись і виносив сторінку за вікно на 10 px. */
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  text-align: center;
}

.auth-code__cell:focus { border-color: var(--field-focus); }
.auth-code__cell:focus-visible { outline-offset: calc(-1 * var(--focus-ring)); }

/* «Код дійсний протягом 10 хвилин» — 844…1076, тобто коробка 232 посеред
   колонки, а не рядок на всю її ширину: центрувати текст у колонці 896 дало
   б ту саму картинку, але іншу коробку, і замір проти канону це побачив.
   Роль тексту нижче 768 інша — Info 14/16 замість основних 16/20
. */
.auth-note {
  margin-block-start: var(--auth-note-top);
  inline-size: fit-content;
  max-inline-size: 100%;
  margin-inline: auto;
  font-size: var(--auth-note-fs);
  line-height: var(--auth-note-lh);
  text-align: center;
}

@media (min-width: 768px) {
  /* Вище стіни перемикач повертається в рядок заголовка, тому шапка знову
     стає власною коробкою, а не `display: contents`. */
  .auth__head {
    margin-block-start: var(--auth-head-top);
    display: grid;
    gap: var(--s-8);
  }

  .auth__title { margin-block-start: 0; }

  .auth__switch {
    order: 0;
    margin-block-start: 0;
    align-self: end;
  }

  .auth-legal { order: 0; }

  /* Дві доріжки — лише там, де поруч із заголовком стоїть перемикач. На
     трьох кроках відновлення його немає, і заголовок мусить мати всю
     колонку: у половині «Код підтвердження надіслано» ламається на два
     рядки, тобто коробка стає вдвічі вища за канонну. */
  .auth__head--split {
    grid-template-columns: 1fr 1fr;
    gap: var(--auth-col-gap);
    align-items: end;
  }

  .auth-row {
    grid-template-columns: 1fr 1fr;
    gap: var(--auth-row-gap) var(--auth-col-gap);
  }

  /* Поле, яке в каноні займає всю колонку форми (пошта на кроці 1
     відновлення —, 896 завширшки). */
  .auth-field--wide { grid-column: 1 / -1; }
}

@media (min-width: 1024px) {
  /* Смуга кадрів ширша за колонку форми і вужча за контейнер: 1360 посеред
     1824. Шість слотів, четвертий порожній — так на кожному артборді. */
  .auth-band { grid-template-columns: repeat(6, 1fr); }

  .auth-band__tile:nth-child(3),
  .auth-band__tile:nth-child(6) { display: block; }

  /* Ряд комірок коду вужчий за колонку форми на 2 px — це число канону, а
     не залишок, тому ширину тут задає сам набір комірок. */
  .auth-code {
    inline-size: fit-content;
    max-inline-size: 100%;
  }

  .auth-code__cell { flex: 0 1 var(--auth-code-w); }
}

/* ==========================================================================
   ОБРАНЕ — («Liked + banner_1920_»).

   ЧОТИРИ АРТБОРДИ — ЦЕ ОДНА СТОРІНКА В ЧОТИРЬОХ СТАНАХ, і поділ зроблено
   заміром, а не назвою (той самий критерій, що звів чотири «Log_1920_» в
   один екран). Банер закривається хрестиком, список буває порожній
   разом це 2×2:


   Пари відрізняються рівно на 320 — висоту банера: 748 проти 428 у першого
   ряду, 1284 проти 964 у плашки дій, 1472 проти 1152 у підвала. Там, де
   банера немає, його місце займає ВОЛОСИНА на 404: список завжди починається
   волосиною, і банер її просто накриває.

   ⚠️ ЛІЧИЛЬНИКА «1 − +» ТУТ НЕМАЄ НАВМИСНО. У каноні між «Розміром» і
   знаки. Замовник: «1+- НЕ верстай, то помилка дизайнера».
   Кількість на сторінці обраного не має сенсу — це поле кошика. Разом із
   групою пішли її 176 px і один із проміжків 40, тому ряд керувань
   `…; тут 678 замість канонних 894:.
   ========================================================================== */

/* --- ЗАГОЛОВОК -------------------------------------------------------------
   Роль заголовка тут DISPLAY, а не H1 каталогу: коробка канону
   заввишки 164, тобто рівно `--lh-display` на 1920 (у `--lh-h1` там 144).
   Те саме число несуть H1 «Про нас» і «Подіуму» — 328 на два рядки. */
.wish-head__title,
.cart-head__title {
  margin-block-start: var(--wish-title-top);
  color: var(--black-800);
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
  font-weight: var(--fw-medium);
}

/*   Повноширинна смуга: у каноні вона від 0 до 1920, тобто в bleed, а не в
   контейнері, — тому в розмітці стоїть сусідом контейнера, а не всередині.
   Знімок кадрується `object-fit: cover`: висота смуги плоска (320), а ширина
   тече, і будь-яке інше підганяння розтягувало б обличчя.

   Смуга ДЕКОРАТИВНА, але не безголоса: у ній є підпис розділу й кнопка
   закриття, тому `aria-hidden` на неї не ставиться — ховати від читача
   екрана довелося б і хрестик. */
/* РИТМ ПЕРЕД БАНЕРОМ — 80, А НЕ ЗАГАЛЬНІ 100. Канон: коробка заголовка
   кінчається на 324, банер починається на 404. `main > .container` несе
   `--section-gap`, тому він тут гаситься, а число ставить сам банер —
   інакше довелося б писати відʼємне поле, тобто ховати одне число за
   різницею двох. Той самий прийом, що з `:has(> .ct-view)`. */
main > .container:has(+ .wish-banner) { margin-block-end: 0; }

.wish-banner {
  position: relative;
  /* Нижче десктопної стіни — загальний ритм розділів: канону цієї смуги
     немає, і власне число там означало б вигадане. */
  margin-block-start: var(--section-gap);
  /* Стрічка кадрів їде всередині смуги — сусідні кадри за краєм не видно. */
  overflow: hidden;
}

/* Стрічка — три кадри в ряд, видимий один; зсув кладе двигун (`translate`,
   як у першого екрана). Без JS стоїть перший кадр. */
.wish-banner__reel {
  display: flex;
  inline-size: 100%;
}

.wish-banner__img {
  display: block;
  flex: 0 0 100%;
  inline-size: 100%;
  block-size: var(--wish-banner-h);
  object-fit: cover;
}

/* Оверлей — ТОЙ САМИЙ контейнер, що й решта сторінки. Канон ставить підпис
   на 50, а хрестик на 1838, тобто на 2 px усередину від гутера сторінки
   (48 і 1872); ці 2 px — тремтіння самого макета, і воно програє загальному
   краю сторінки. */
.wish-banner__over {
  position: absolute;
  inset-block-start: var(--wish-banner-pad);
  inset-inline: 0;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s-24);
}

.wish-banner__label {
  color: var(--black-000);
  font-size: var(--wish-txt-fs);
  line-height: var(--wish-txt-lh);
}

.wish-banner__close {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--wish-banner-close);
  block-size: var(--wish-banner-close);
  border-radius: 50%;
  /* ЗАЛИВКА Є НИЖЧЕ СТІНИ Й НЕМАЄ ВИЩЕ НЕЇ — це різні вузли, а не одне
     оформлення на всю смугу. Обидва читані властивістю вузла, не з PNG:
                stroke немає жодного; знак `stroke="#232221"`
                тобто крізь коло видно знімок
     На вузькому екрані коло лежить на світлій частині кадру, і контурне
     читалося б як подряпина; на десктопі воно стоїть у темному куті. */
  border: 0;
  background: var(--black-000);
}

.wish-banner__close img { display: block; }

/* Хрестик тримається ПРАВОГО краю сам, а не завдяки сусідові. У еталоні в
   смузі двоє — підпис і хрестик, — і `space-between` розводив їх по краях.
   На сайті підпису немає («банер — це просто фото», D565), і єдиний вузол
   притискався ЛІВОРУЧ: −1792 px від канонного місця на 1920. Автоматичне
   поле дає той самий правий край і з підписом, і без нього. */
.wish-banner__close { margin-inline-start: auto; }

/* Вище стіни коло в каноні було без заливки — лише волосина, і на світлому
   кадрі знак губився. Власниця за макетом (11.09, D562): напівпрозоре біле
   коло. Нижче стіни лишається суцільне біле канону. */
@media (min-width: 1024px) {
  .wish-banner__close { background: var(--banner-close-bg); }
}

/*   білий, решта — той самий білий на 40 % (прозорість вузлів і
   банер слайдером — сегменти стали кнопками своїх кадрів. Коробка
   сегмента та сама, 48×2; площа натискання — псевдоелементом. */
.wish-banner__dots {
  position: absolute;
  inset-block-end: var(--wish-dot-bottom);
  inset-inline-start: 50%;
  translate: -50% 0;
  display: flex;
  gap: var(--wish-dot-gap);
  margin: 0;
  padding: 0;
  list-style: none;
}

.wish-banner__dot {
  position: relative;
  display: block;
  inline-size: var(--wish-dot-w);
  block-size: var(--wish-dot-h);
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--black-000);
  opacity: var(--wish-dot-dim);
  cursor: pointer;
}

/* Площа пальця — 24 по висоті навколо сегмента 2, без зміни коробки. */
.wish-banner__dot::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block: calc(0px - var(--s-12));
}

.wish-banner__dot[aria-current="true"] { opacity: 1; }

/* --- СПИСОК ----------------------------------------------------------------
   ВІДСТУП НАД СПИСКОМ НАЛЕЖИТЬ СПИСКУ, А НЕ БАНЕРУ. Спершу ці 80 стояли
   полем банера — і зникали разом із ним, коли його закривали хрестиком:
   заголовок сідав просто на волосину. У каноні відступ є в ОБОХ станах —
   заголовок кінчається на 324, а те, що йде далі, починається на 404: банер
   список, а банер його лише забирає собі, поки стоїть.
   ---------------------------------------------------------------------- */
.wish { margin-block-start: var(--section-gap); }

.wish[data-state="banner"] { margin-block-start: 0; }
/* --- СПИСОК ----------------------------------------------------------------
   Волосина СТОЇТЬ ЗАВЖДИ, але при відкритому банері вона прозора, а не
   вилучена: інакше поява й зникнення банера зсували б увесь список на
   піксель, і жоден замір цього не пояснив би. */
.wish__list {
  margin: 0;
  padding: 0;
  list-style: none;
  border-block-start: var(--hairline) solid var(--line);
}

.wish[data-state="banner"] .wish__list { border-block-start-color: transparent; }

.wish__item {
  padding-block: var(--wish-row-pad);
  border-block-end: var(--hairline) solid var(--line);
}

/* ОСТАННІЙ РЯД НИЖЧЕ СТІНИ 1024 ВОЛОСИНИ НЕ МАЄ. Обидва вузькі артборди
   на 820); під списком її немає. На 1920 вона є (волосина 1220), тому там
   повертається. Це заміряно по кадру канону, а не вирішено на око: у смузі
   440…1120 на кожному з артбордів рівно один рядок пікселів кольору `line`. */
.wish__list > .wish__item:last-child { border-block-end: 0; }

/*   Нижче десктопної стіни ряд стає сіткою «кадр | текст», а керування йдуть
   під ними на всю ширину: у 343 сім керувань в один рядок не стають нічим,
   крім обрізання. Канону цієї смуги немає — артбордів 820 і 375 у сторінки
   не існує. */
.wish-row {
  display: grid;
  grid-template-columns: var(--wish-pic-w) minmax(0, 1fr);
  column-gap: var(--wish-body-gap);
  row-gap: var(--wish-row-gap);
  align-items: start;
}

/* Кадр несе ОБИДВА розміри числом, а не пропорцією: пропорція між якорями не
   стала — 80/116 на 375 і 120/168 на 820 — і `aspect-ratio` мовчки вибрала б
   одну з них на всю смугу. */
.wish-row__pic {
  grid-area: 1 / 1;
  inline-size: var(--wish-pic-w);
  block-size: var(--wish-pic-h);
  overflow: clip;
}

.wish-row__pic img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}

.wish-row__body {
  grid-area: 1 / 2;
  display: grid;
  gap: var(--wish-body-step);
  align-content: center;
  inline-size: var(--wish-body-w);
}

/* Назва тримається коробки 197 — саме вона задає перенос у три рядки, і
   саме вона, а не висота, звіряється з каноном: кількість рядків залежить
   від метрик живого шрифту. */
.wish-row__name {
  display: block;
  color: var(--ink);
  font-size: var(--wish-txt-fs);
  line-height: var(--wish-txt-lh);
  font-weight: var(--fw-medium);
}

/* Ціна обтягує текст, назва — ні: коробка назви (197) ТРИМАЄ перенос у три
   рядки, і саме вона звіряється з каноном. */
.wish-row__price { justify-self: start; color: var(--price-ink); }

/* Обгортка РОЗЧИНЯЄТЬСЯ нижче стіни 1024: бейдж мусить стати у верхній ряд
   поруч із назвою, а набір керувань — окремим рядом сітки. Поки обгортка була
   коробкою, обидва стояли під нею одним блоком, і бейдж не міг піднятись до
   назви. Той самий прийом, що з переліком розділів кабінету. */
.wish-row__tools { display: contents; }

/* Бейдж стоїть правим краєм контейнера у верхньому ряду: 305…359 на 375,
   742…796 на 820 — тобто рівно край сторінки. */
.wish-row .badge {
  grid-area: 1 / 2;
  justify-self: end;
}

/*   підряд: 40 між списками, 62 перед «В корзину», 16 перед кошиком. Тому
   вони стоять полями елементів, а не одним `gap`. */
.wish-ctl {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: var(--wish-ctl-row) var(--wish-ctl-col);
}

/* Нижче десктопної стіни керування стоять ПАРАМИ: при жорстких 168 у
   контейнері 343 в рядок влазить рівно одне, і ряд виростав до чотирьох
   смуг. Дві доріжки дають дві смуги — списки й дії. */
.wish-sel {
  position: relative;
  block-size: var(--wish-ctl-h);
}

/* «Оберіть розмір» — ПІД керуваннями ряду й тоном підказки, як слово про
   залишок у кошику (власниця, 12.09). Смуга ціла, тому підказка стоїть під
   розміром, а не втискається між ним і кнопкою. */
.wish-row__note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-xs);
}

.wish-row__note[hidden] { display: none; }

/* ПОЛЕ — КНОПКА ЗІ СВОЇМ СПИСКОМ, а не рідний `select`: рідну
   випадашку малює браузер, і на вузьких ширинах вона ширша за поле —
   замовник: «Ширину випадашки адаптуй під ширину поля». Список
   стоїть під полем рівно на його ширину (`inset-inline: 0`), вигляд рядків
   — той самий, що в списку розміру на картці (`.psize__opt`): рядки з
   волосиною, крок 16, поля 24. Значення для форми несе схований `input`.
   Поки не обрано нічого, підпис приглушений (`is-placeholder`) — так канон
   малює «Розмір» і «Колір», а обраний «Білий» чорнилом. */
.wish-sel__btn {
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  padding-inline: var(--wish-sel-pad)
                  calc(var(--wish-sel-pad) + var(--icon) + var(--s-8));
  border: var(--hairline) solid var(--line);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font-family: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  text-align: start;
  cursor: pointer;
}

.wish-sel__label { display: block; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.wish-sel__label.is-placeholder { color: var(--label-ink); }

/* Відкрите поле піднімається над сусідами — список лягає поверх ряду. */
.wish-sel.is-open { z-index: 5; }

.wish-sel__list {
  position: absolute;
  inset-inline: 0;
  inset-block-start: 100%;
  margin: 0;
  padding: var(--psize-pad) var(--wish-sel-pad) 0;
  border: var(--hairline) solid var(--line);
  border-block-start: 0;
  background: var(--surface);
  list-style: none;
}

.wish-sel__list[hidden] { display: none; }

.wish-sel__opt {
  padding-block-end: var(--s-16);
  border-block-end: var(--hairline) solid var(--line);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  cursor: pointer;
}

.wish-sel__opt + .wish-sel__opt { margin-block-start: var(--s-16); }
.wish-sel__opt:last-child { border-block-end: 0; padding-block-end: var(--psize-pad); }
.wish-sel__opt[aria-selected="true"] { font-weight: var(--fw-regular); }

@media (hover: hover) {
  .wish-sel__opt:hover { color: var(--ink-muted); }
}

.wish-sel__sign {
  position: absolute;
  inset-inline-end: var(--wish-sel-pad);
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: var(--icon);
  block-size: var(--icon);
  pointer-events: none;
}

/* «В корзину» нижче стіни 768 займає ОБИДВІ доріжки: канон дає 343×56 на всю
   ширину контейнера. */
.wish-cart {
  grid-column: 1 / -1;
  min-width: 0;
  min-height: var(--wish-ctl-h);
  padding-inline: var(--wish-sel-pad);
}

/*   «Видалити» зі знаком 18 праворуч, по центру сторінки; коло 56 зʼявляється
   означав би коло на всій смузі, а коло тут є лише вище стіни — і його
   довелося б розкручувати назад шістьма рядками скасувань. */
.wish-del {
  grid-column: 1 / -1;
  justify-self: center;
  display: inline-flex;
  align-items: center;
  gap: var(--wish-del-gap);
  padding: 0;
  border: 0;
  background: none;
  color: var(--label-ink);
  font-family: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
}

.wish-del img {
  display: block;
  inline-size: var(--wish-del-icon);
  block-size: var(--wish-del-icon);
}

/*   Темний прямокутник 512×88 посеред сторінки з полем 16 з усіх боків:
   16 + 232 + 16 + 232 + 16 = 512. Канон каже 511 — той самий піксель
   недбалості, що в підвала. */
/* Нижче стіни 768 кнопки стоять СТОВПЧИКОМ на всю ширину плашки: канон
   там дає вміст (16+56+8+56+16 = 152), тому плоского числа тут немає. */
.wish-actions {
  margin-block-start: var(--wish-actions-top);
  margin-inline: auto;
  inline-size: min(var(--wish-actions-w), 100%);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--wish-actions-gap);
  padding: var(--wish-actions-pad);
  background: var(--black-900);
}

.wish-cta {
  min-width: 0;
  inline-size: 100%;
  min-height: var(--wish-ctl-h);
  padding-inline: var(--s-16);
}

/* Ліва кнопка — контурна НА ТЕМНОМУ: обведення `black/700`, підпис білий
. Обидві пари зняті властивістю вузлів, не з рендера. */
.wish-cta--ghost {
  background: transparent;
  color: var(--black-000);
  border: var(--hairline) solid var(--black-700);
}

.wish-cta--fill {
  background: var(--black-000);
  color: var(--black-900);
}

/* `.btn` ставить `display: inline-flex` тією самою вагою, що `[hidden]` у
   базі, і йде ПІЗНІШЕ — тобто схована кнопка лишалась би в потоці. Замір це
   й показав: три кнопки на плашці замість двох, кожна 149 замість 232. */
.wish-cta[hidden] { display: none; }
/* Те саме для плашки: `.wish-actions { display: flex }` перебивав `[hidden]`,
   і на спорожнілому обраному плашка лишалась (D562). */
.wish-actions[hidden] { display: none; }

/*   Підпис стоїть по центру сторінки, а плашка дій відсувається від нього на
   236 замість 64: у порожньому стані ряду немає, і від чого відступати —
   теж. Обидва числа зняті з артборда, а не вигадані. */
.wish__empty {
  margin-block-start: var(--wish-empty-top);
  inline-size: fit-content;
  max-inline-size: 100%;
  margin-inline: auto;
  color: var(--price-ink);
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  text-align: center;
}

/* Порожній стан — посередині між банером і підвалом (власниця, 11.09,
   D563): нижнє поле підпису дорівнює верхньому. Нижнє поле контейнера
   (`--section-gap`) з ним ЗЛИПАЄТЬСЯ, а не додається, тож різниця тут
   дала б 152 проти 201 — замір. */
.wish__empty:not([hidden]) {
  margin-block-end: var(--wish-empty-top);
}

.wish__empty:not([hidden]) + .wish-actions {
  margin-block-start: var(--wish-empty-gap);
}

@media (hover: hover) {
  /* Коло хрестика під курсором — чорне, знак (файл SVG чорнилом) — інверсією
     білий. */
  .wish-banner__close:hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
  }
  .wish-banner__close:hover img { filter: invert(1); }
  .wish-cta--fill:hover { background: var(--black-100); }
  .wish-cta--ghost:hover { background: var(--black-700); }
}

/* --- ПЛАНШЕТ: КАДР ЛІВОРУЧ, ТЕКСТ І КЕРУВАННЯ ПРАВОРУЧ ---------------------
   Канон: кадр 120×168 стоїть на всю висоту ряду, а права колонка
   ділиться навпіл по вертикалі — назва з ціною згори (88), рядок керувань
   знизу (56), між ними 24. Тобто це НЕ мобільна розкладка з ширшими числами:
   керування переїжджають з-під кадру вбік від нього. Стіна — 768. */
@media (min-width: 768px) {
  /* Кадр НЕ просто високий, а на всю висоту ряду. Власне число лишається
     нижньою межею: у ряду з коротшою назвою (48 замість 88) висоту ряду задає
     саме воно, а в ряду з довшою — текст, і тоді кадр мусить дотягнутись. На
     820 обидва шляхи дають канонні 168; на 768 без цього кадр не доїжджав до
     низу керувань на 5 px, і жоден замір коробки цього не показував, бо
     кожна коробка окремо була правильна. */
  /* ВИСОТУ РЯДУ ЗАДАЄ РЯД, А НЕ КАДР. На 820 обидва числа збігаються (168), і
     доки нижньою межею кадру стояла його власна рампа, у смузі 768…819 ряди
     розходились: ряд із трирядковою назвою тримався текстом (167), а ряд з
     однорядковою — кадром (162). Однакові за складом ряди мусять мати
     однакову висоту на КОЖНІЙ ширині, а не лише на якорі. */
  .wish-row { min-block-size: var(--wish-row-h); }

  .wish-row__pic {
    grid-row: 1 / span 2;
    align-self: stretch;
    block-size: auto;
  }

  /* Керування йдуть у ДРУГУ колонку, а не на всю ширину: 168…796 на 820, тобто
     рівно від правого краю кадру до краю сторінки. */
  /* `align-self: end` — не оформлення, а канон: керування стоять НИЗОМ кадру
     (881…937 при кадрі 769…937), а не серединою правої колонки. Без цього
     рядок із коротшою назвою («Бомбер» — одна смуга замість трьох) висить на
     19 px вище кадру: зайву висоту сітка ділить між обома доріжками порівну,
     і нижня доїжджає не до низу. */
  .wish-ctl {
    grid-column: 2;
    align-self: end;
    display: flex;
    align-items: center;
    gap: var(--wish-gap-sel);
    /* Перенос зʼявляється лише разом із підказкою (схований абзац —
       `display: none`), тож ряд без неї лишається тією самою однією лінією. */
    flex-wrap: wrap;
  }

  .wish-row__note {
    order: 99;
    flex-basis: 100%;
    margin-block-start: calc(-1 * var(--wish-gap-sel) + var(--s-8));
    text-align: start;
  }

  .wish-sel { flex: none; inline-size: var(--wish-sel-w); }

  /* «В корзину» — ЄДИНЕ, що тягнеться: два списки й коло стоять канонними
     числами, і саме кнопка добирає залишок смуги. На самому якорі це дає
     канонні 180 (628 − 168 − 16 − 168 − 24 − 16 − 56), а між 768 і 1023 —
     рівний правий край без розриву. */
  .wish-cart {
    grid-column: auto;
    flex: 1 1 auto;
    margin-inline-start: calc(var(--wish-gap-act) - var(--wish-gap-sel));
  }

  /* Коло вертається саме тут — і повністю, а не скасуванням мобільних
     рядків: — 56×56, обведення `black/100`, знак 18. */
  .wish-del {
    grid-column: auto;
    justify-self: auto;
    flex: none;
    justify-content: center;
    inline-size: var(--btn-round);
    block-size: var(--btn-round);
    border: var(--hairline) solid var(--line);
    border-radius: 50%;
    background: var(--surface);
    color: var(--ink);
    margin-inline-start: calc(var(--wish-gap-del) - var(--wish-gap-sel));
  }

  /* Підпис кнопки лишається доступним іменем, але зі смуги зникає: тут його
     місце займає сам знак у колі. */
  .wish-del__text {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .wish-actions {
    flex-direction: row;
    gap: var(--wish-actions-pad);
    min-block-size: var(--wish-actions-h);
  }

  .wish-cta { inline-size: var(--wish-cta-w); max-inline-size: 100%; }
}

/* --- ДЕСКТОП: РЯД У ОДНУ СМУГУ --------------------------------------------
   Від стіни режиму ряд стає тим, що в каноні: кадр ліворуч, назва з ціною
   поруч, увесь набір керувань — правим краєм по контейнеру. */
@media (min-width: 1024px) {
  /* Висота МІНІМАЛЬНА, а перенос лишається дозволеним: на 1920 весь набір
     стає в один рядок і ряд рівно 200, як у каноні, а на 1024 сім керувань
     у 960 не стають — і без переносу сторінка їхала б убік на 142 px
     (заміряно `check-hscroll`). */
  .wish-row {
    display: flex;
    align-items: center;
  }

  /* Вище стіни коло знову контурне — див.. */

  .wish { margin-block-start: var(--wish-banner-top); }

  .wish-banner { margin-block-start: var(--wish-banner-top); }

  .wish-row__pic { flex: none; inline-size: var(--wish-pic-w); }

  /* `flex: none` — проти стиснення, а не заради ширини. На 1024 набір керувань
     ще не влазить в один рядок і переноситься, і поки коробка назви була
     гнучкою, гумка забирала в неї 18 px: 164 на 1023, 146 на 1024, знову 167
     на 1140. Немонотонність на трьох точках невидима — її дає лише прогін
     ширин поспіль (ADAPTATION §3.9). На 1920 нічого не міняє: там ряд і так
     стоїть однією смугою. */
  .wish-row__body {
    flex: none;
    margin-inline-start: var(--wish-body-gap);
    inline-size: var(--wish-body-w);
  }

  /* Обгортка знову КОРОБКА: на десктопі бейдж і керування стоять однією
     смугою праворуч, тобто мають спільного батька. */
  .wish-row__tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    margin-inline-start: auto;
    justify-content: flex-end;
    gap: var(--wish-gap-sel);
  }

  .wish__list > .wish__item:last-child {
    border-block-end: var(--hairline) solid var(--line);
  }

  .wish-ctl {
    flex: none;
    display: flex;
    flex-wrap: wrap;
    justify-content: flex-end;
    gap: var(--wish-gap-sel);
  }

  /* Смуга керувань ШИРША за самі керування (вона тримає канонну коробку
     ряду з двома списками, а другий — «Колір» — на сайті окремий товар) і
     притиснута до правого краю. Тому підказка відступає рівно на цю
     порожнечу: 100 % мінус два списки, два проміжки й коло «Видалити» — і
     починається під «Розміром», першим керуванням ряду. Відʼємний відступ
     браузер бере за нуль, тож на вужчих рядах правило нічого не зсуває. */
  .wish-row__note {
    text-align: start;
    padding-inline-start: calc(100% - 2 * var(--wish-sel-w)
                               - 2 * var(--wish-gap-sel) - var(--wish-ctl-h));
  }

  /* На якорі керування стоять КАНОННИМИ 168, без росту й стиснення. */
  .wish-sel { flex: none; inline-size: var(--wish-sel-w); }

  /* Від стіни 1024 кнопка більше не тягнеться: на 1920 канон дає їй ті самі
     168, що й спискам. */
  .wish-cart { flex: 0 0 auto; min-width: var(--wish-sel-w); }

  .wish-actions { flex-wrap: nowrap; }
}


/* ==========================================================================
   МЕГАМЕНЮ МАГАЗИНУ. Артборд («menu_1920»).

   ⚠️ ЯКІР ОДИН. Вузьких артбордів меню не існує, тому компонент живе ТІЛЬКИ
   від стіни десктопа — нижче неї навігацію веде панель бургера, яка вже
   прийнята, а «Онлайн магазин» лишається звичайним посиланням у каталог.
   Це не «сховали, щоб не морочитись»: намалювати мегаменю на 375 без
   артборда означало б вигадати композицію.
   ========================================================================== */
.shopmenu { display: none; }

@media (min-width: 1024px) {
  /* Накладка починається під шапкою й тягнеться до низу вікна: затемнення в
     рядком шапки, а не на висоту панелі. */
  .shopmenu {
    display: block;
    position: fixed;
    inset-block-start: var(--header-h);
    inset-block-end: 0;
    inset-inline: 0;
  }
  /* `hidden` мусить перемагати `display: block` вище: без цього рядка панель
     стояла б відкритою в кожного, у кого не виконався JS. */
  .shopmenu[hidden] { display: none; }

  .shopmenu__scrim {
    position: absolute;
    inset: 0;
    background: var(--scrim-soft);
  }

  /* Панель НЕ ріже вміст: ряд кадрів свідомо звисає з її низу на затемнення
     (`--menu-card-bleed`), і `overflow: hidden` зрізав би рівно те, заради
     чого цей звис намальований. */
  .shopmenu__panel {
    position: relative;
    background: var(--surface);
    padding-block-start: var(--menu-pad-top);
    /* `flow-root` — не косметика. Звис кадрів заданий відʼємним нижнім полем
       ряду, а поля, що СХЛОПУЮТЬСЯ, з коробки батька виходять назовні: панель
       лишалась 592 замість канонних 520, тобто звис не знімався взагалі
       (спіймав `canon-geometry`, не око). Власний контекст форматування
       робить те саме поле частиною висоти панелі. */
    display: flow-root;
  }

  .shopmenu__nav {
    display: grid;
    grid-template-columns: repeat(24, minmax(0, 1fr));
    column-gap: var(--s-32);
    row-gap: var(--menu-label-gap);
  }

  /* `nth-of-type`, а НЕ `nth-child`, і тут це правильний вибір, а не той
     самий недогляд, що в підвалі: підписи — єдині `<p>` у цьому вузлі, а
     списки — єдині `<ul>`, тож рахунок по тегу однозначний. `nth-child`
     навпаки склав би підписи й списки в один ряд нумерації. */
  .shopmenu__label {
    grid-row: 1;
    margin: 0;
    color: var(--ink-subtle);
    font-size: var(--fs-ui);
    line-height: var(--lh-ui);
  }
  .shopmenu__label:nth-of-type(1) { grid-column: 4 / span 3; }
  .shopmenu__label:nth-of-type(2) { grid-column: 13 / span 3; }

  .shopmenu__list {
    grid-row: 2;
    /* `start`, а не типове `stretch`: три списки стоять в ОДНОМУ ряду сітки,
       і найвищий із них (5 пунктів, 174) розтягував коробку чотирипунктового
       до тієї ж висоти — 174 замість канонних 136. На екрані цього не видно
       (пункти лишаються вгорі), але коробка бреше, і наступний, хто на неї
       обіпреться, отримає чужі 38 px. */
    align-self: start;
    display: grid;
    gap: var(--menu-list-gap);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  .shopmenu__list:nth-of-type(1) { grid-column: 4 / span 3; }
  .shopmenu__list:nth-of-type(2) { grid-column: 7 / span 3; }
  .shopmenu__list:nth-of-type(3) { grid-column: 13 / span 3; }

  .shopmenu__list a {
    color: var(--ink);
    font-size: var(--menu-link-fs);
    line-height: var(--menu-link-lh);
    font-weight: var(--fw-medium);
  }

  /* Ряд кадрів стоїть на ТІЙ САМІЙ сітці, що й пункти: колонки 4 / 7 / 13 / 19,
     по три штуки, і три колонки на 1920 дають рівно канонні 200 (3 × 45.33 +
     2 × 32). Ширина кадру не написана числом саме тому — вона наслідок сітки.

     Відʼємне поле знизу — це і є звис: панель кінчається на 581, кадри на 652.
     Так канон і малює, і саме тому кадри лежать частково на затемненні. */
  .shopmenu__cards {
    display: grid;
    grid-template-columns: repeat(24, minmax(0, 1fr));
    column-gap: var(--s-32);
    margin-block-start: var(--menu-cards-gap);
    margin-block-end: calc(var(--menu-card-bleed) * -1);
  }
  .shopmenu__card {
    display: block;
    inline-size: 100%;
    block-size: auto;
    aspect-ratio: var(--menu-card-ratio);
    object-fit: cover;
  }
  .shopmenu__card:nth-child(1) { grid-column: 4 / span 3; }
  .shopmenu__card:nth-child(2) { grid-column: 7 / span 3; }
  .shopmenu__card:nth-child(3) { grid-column: 13 / span 3; }
  .shopmenu__card:nth-child(4) { grid-column: 19 / span 3; }

  /* Хрестик стоїть по краю КОНТЕЙНЕРА (1872 на 1920), а не по краю вікна,
     тому відступ береться гутером, а не числом: вище 1920 гутер росте разом
     із порожнечею, і кнопка лишається врівень із рештою вмісту. */
  .shopmenu__close {
    position: absolute;
    inset-block-start: var(--menu-close-top);
    inset-inline-end: var(--gutter);
    display: inline-flex;
    align-items: center;
    justify-content: center;
    inline-size: var(--menu-close);
    block-size: var(--menu-close);
    padding: 0;
    border: var(--hairline) solid var(--line);
    border-radius: 50%;
    background: transparent;
    color: var(--ink);
    cursor: pointer;
  }
  /* Знак стоїть у коробці СВОГО РОЗМІРУ — 64 при viewBox 64, тобто масштаб
     рівно 1. `100%` тут давало 62 (кнопка 64 мінус дві волосини рамки), і
     хрест разом із коробкою стискався в 0.969: обведення 1.5 малювалось як
     1.45. Дефект живе в ДОБУТКУ — ні в розмітці, ні в CSS його не видно, —
     і спіймав його `check-icon-scale`, а не око. Полотно 64 несе хрест
     22…42, тобто канонні 20; те, що воно на 1 px виступає за поле кнопки з
     кожного боку, нічого не змінює: коло малює рамка CSS, а не svg. */
  .shopmenu__close svg {
    flex: none;
    inline-size: var(--menu-close);
    block-size: var(--menu-close);
  }

  /*     («Vector 30»): риска 119 під «Онлайн магазин». Це той самий градієнт, що
     веде наведення й фокус решти посилань шапки, тому окремого оформлення
     тут немає — лише інша умова. */
  .header__shop[aria-expanded="true"] { background-size: 100% var(--hairline); }
}


/* ==========================================================================
   `3QojL2lmdB71wIza55JH2J`; (1920) з файла `brJtdGRPsRJ20nUaRltBEw`.

   ⚠️ ВУЗЬКІ АРТБОРДИ — АРТБОРДИ «ОБРАНОГО» З ЛІЧИЛЬНИКОМ І ПІДСУМКОМ: банер,
   кадр, назва, списки, «Видалити» й плашка дій збігаються вузол у вузол із
   схоже», а тому, що це той самий компонент, знятий і тут, і там (
 закрито — дзеркала більше немає, є власний канон).

   БАЗА — МОБІЛЬНИЙ ЯКІР (375): кадр 80×116 ліворуч, назва з ціною праворуч,
   бейдж у правому куті, під кадром керування у дві доріжки, «Видалити»
   рядком по центру. Стіна 768 — планшетний ряд: керування одним рядом низом
   кадру. Стіна 1420 — канонний ряд 1920: три
   ракурси, назва, права група з шести керувань.

   ТРИ РЯДИ КАНОНУ — ТРИ СТАНИ одного компонента: «New», «Sale» зі старою
   ціною, і без бейджа. Вузькі артборди малюють два з них («New» двічі на
   820; «New» і «Sale» на 375) — третій ряд лишається за 1920.
   ========================================================================== */
.cart { margin-block-start: var(--cart-list-top); }

.cart__list {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Волосина стоїть ПІД кожним рядом, зокрема під останнім: у каноні
   волосиною й під нею — два різні числа на 375 (20 і 24), тому два токени. */
.cart__item {
  padding-block: 0 var(--cart-row-pad-b);
  border-block-end: var(--hairline) solid var(--line);
}
.cart__item + .cart__item { padding-block-start: var(--cart-row-gap); }

/* --- РЯД НИЖЧЕ СТІНИ 768 — сітка «кадр | текст» ----------------------------
   Пʼять рядків сітки: назва · ціна · залишок · керування · «Видалити». Кадр
   тягнеться через перші три, і саме «залишок» (1fr) забирає різницю між
   висотою кадру (116) і висотою тексту (назва + 8 + ціна): без нього сітка
   розділила б зайве між рядками назви й ціни порівну, і ціна зʼїхала б з
   канонних 8 під назвою. Керування починаються від НИЗУ кадру через
   `--wish-row-gap` (580 → 596 на 375), а не від низу тексту. */
.cart-row {
  position: relative;
  display: grid;
  grid-template-columns: var(--wish-pic-w) minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto auto;
  column-gap: var(--cart-body-gap);
  align-items: start;
}

/* Кадр несе ОБИДВА розміри числом, а не пропорцією: 80/116 на 375 і 120/168
   на 820 — різні пропорції, і `aspect-ratio` мовчки обрав би одну. */
.cart-row__thumbs {
  grid-area: 1 / 1 / span 3;
  display: flex;
  inline-size: var(--wish-pic-w);
  block-size: var(--wish-pic-h);
  overflow: clip;
}
.cart-row__thumbs img {
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
}
/*   другий і третій ракурси зі смуги зникають, розмітка лишається одна. */
.cart-row__thumbs img + img { display: none; }

/*   коробка тримає перенос довгої назви в три рядки на обох вузьких якорях, і
   саме вона звіряється з каноном. Текст тут «Брюки» з власного артборда
   1920 — один рядок; коробка від цього не міняється. */
.cart-row__name {
  grid-area: 1 / 2;
  display: block;
  inline-size: var(--wish-body-w);
  color: var(--ink);
  font-size: var(--wish-txt-fs);
  line-height: var(--wish-txt-lh);
  font-weight: var(--fw-medium);
}

/* Обгортка правої групи нижче стіни РОЗЧИНЯЄТЬСЯ: бейдж, керування й ціна
   стають дітьми сітки ряду. Той самий прийом, що на «Обраному». */
.cart-row__tools { display: contents; }

/*   першого рядка сітки: бейдж 32 заввишки, а однорядкова назва — 20, і як
   комірка він підняв би рядок назви до 32, зсунувши ціну з канонних 8 під
   назвою на 20. */
.cart-row__badge {
  position: absolute;
  inset-block-start: 0;
  inset-inline-end: 0;
  display: inline-flex;
}

/*   1920; 820 малює її `black/400` — канон проти самого себе
. Закреслена стара лишається приглушеною — її тон несе `.price__old`. */
.cart-row__price {
  grid-area: 2 / 2;
  justify-self: start;
  margin-block-start: var(--wish-body-step);
  color: var(--ink);
  font-size: var(--wish-txt-fs);
  line-height: var(--wish-txt-lh);
}

/* Керування — від низу кадру через `--wish-row-gap`, на всю ширину ряду, у
   дві доріжки: «Розмір» | лічильник, під ними «Колір» на обидві, під ним
   «Видалити». Зазор між першим і другим рядом — 5
   між другим і «Видалити» — 16 (`--wish-ctl-row`). Вирівнювання ВЕРХОМ, а не
    — обидва 596). */
.cart-ctl {
  grid-area: 4 / 1 / auto / -1;
  margin-block-start: var(--wish-row-gap);
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--wish-ctl-col);
  row-gap: var(--cart-ctl-gap);
  align-items: start;
}

.cart-sel { inline-size: 100%; }

/* Слово сервера про залишок — під розміром і кількістю, на обидві доріжки
   (власниця, 12.09: «зменшити, стилізувати та розмістити під розміром та
   кількістю»). Дрібним і тоном підказки: це не помилка людини, а межа
   складу. */
.cart-row__note {
  grid-column: 1 / -1;
  margin: 0;
  color: var(--ink-muted);
  font-size: var(--fs-xs);
  line-height: var(--lh-xs);
}

.cart-row__note[hidden] { display: none; }

/* «Колір» стоїть ЗА лічильником і займає обидві доріжки: 343×56
   тримає відступ цього списку на десктопі. */
.cart-qty + .cart-sel { grid-column: 1 / -1; }

/* Лічильник: три кола з проміжком (52 і 6 на 375 — …415`; 56 і
   кнопка: у каноні воно залите чорним і не має ані знака, ані обведення,
   тож це `<output>`, який читач екрана оголошує як значення. Два наступні —
   контурні кнопки «−» і «+». */
.cart-qty {
  display: flex;
  gap: var(--cart-qty-gap);
  /* Кола тримаються ПРАВОГО краю доріжки: на 375 лічильник кінчається рівно
     контейнером (356 на 767), кола — ні, і без цього вони відпливали б від
     краю ліворуч, лишаючи порожнечу там, де канон її не малює. */
  justify-self: end;
}
/* ЗНАКИ НЕ СТИСКАЮТЬСЯ. Типове `flex-shrink: 1` дозволяє колу віддати ширину
   сусідові, і коло стає овалом — саме це показав `check-breakpoints` на 375 і
   360. `flex: none` знімає стиск, а не маскує його розміром. */
.cart-qty__value,
.cart-qty__step { flex: none; }
.cart-qty__value,
.cart-qty__step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--cart-qty-dot);
  block-size: var(--cart-qty-dot);
  border-radius: 50%;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.cart-qty__value {
  background: var(--btn-bg);
  color: var(--btn-ink);
  font-weight: var(--fw-regular);
}
.cart-qty__step {
  padding: 0;
  border: var(--hairline) solid var(--line);
  background: transparent;
  color: var(--ink);
  cursor: pointer;
}
/* Знак у коробці СВОГО розміру: viewBox 24 при коробці 24 дає масштаб 1,
   тобто обведення 1.4 лишається 1.4 (той самий закон, що ловить
   `check-icon-scale`). */
.cart-qty__step svg {
  inline-size: var(--icon);
  block-size: var(--icon);
}

/* «ВИДАЛИТИ» НИЖЧЕ СТІНИ 768 — НЕ КОЛО, А РЯДОК: текст Txt-16
   Medium у `black/400` і знак 18 через 8, по центру ряду; знак теж
   на стіні 768 — див. нижче. Кнопка НЕ несе `.btn-round` з тієї ж причини,
   що на «Обраному»: клас означав би коло на всій смузі. */
.cart-del {
  grid-column: 1 / -1;
  justify-self: center;
  margin-block-start: calc(var(--wish-ctl-row) - var(--cart-ctl-gap));
  display: inline-flex;
  align-items: center;
  gap: var(--wish-del-gap);
  padding: 0;
  border: 0;
  background: none;
  color: var(--label-ink);
  font-family: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: var(--fw-regular);
  cursor: pointer;
}
.cart-del img {
  display: block;
  inline-size: var(--cart-del-icon);
  block-size: var(--cart-del-icon);
}

/* НАВЕДЕННЯ — НАБІР ПРОЄКТУ, А НЕ ВИГАДКА ПІД ЦЮ СТОРІНКУ. Канон кошика —
   артборди в одному стані, наведення в них немає взагалі. Але «−»
   «+» і кошик — контурні круглі кнопки, а в проєкту вже є єдиний закон для
   таких: коло темніє, знак інвертується.
   Замовник: «І ефекти тут також повинні бути при наведенні, плюс
   мінус та кошик» — тобто той самий закон, а не третій набір станів.

   ⚠️ ВИМКНЕНИЙ «−» НАВЕДЕННЯ НЕ БЕРЕ: кнопка на одиниці нічого не робить, і
   темне коло під курсором обіцяло б дію, якої немає. `:not(:disabled)` — не
   косметика, а те, що тримає цю обіцянку.

   ⚠️ КОШИК бере це наведення лише ТАМ, ДЕ ВІН КОЛО — від стіни 768 (нижче
   вона рядок тексту, і темна плашка під словом «Видалити» була б вигадкою). */
.cart-qty__step,
.cart-del {
  transition:
    background-color var(--dur-fast) var(--ease),
    color var(--dur-fast) var(--ease),
    border-color var(--dur-fast) var(--ease);
}

@media (hover: hover) {
  .cart-qty__step:not(:disabled):hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
    color: var(--btn-ink);
  }
}

@media (hover: hover) and (min-width: 768px) {
  .cart-del:hover {
    background: var(--btn-bg);
    border-color: var(--btn-bg);
    color: var(--btn-ink);
  }

  /* Знак — файл SVG чорнилом: `color` його не фарбує, тому білий — фільтром
     (на темному колі сірий знак губився, D562). */
  .cart-del:hover img { filter: brightness(0) invert(1); }
}

/* ВИМКНЕНИЙ КОНТРОЛ ВИДНО — інакше «−» на одиниці нічим не відрізняється від
   робочого «+», і людина натискає в порожнечу. Тон — `--ctl-off`, той самий,
   яким проєкт малює неактивні контроли (про його контраст; вимкнений
   контрол із вимоги контрасту виведений у WCAG за побудовою). */
.cart-qty__step:disabled {
  color: var(--ctl-off);
  cursor: default;
}

.cart-total {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin-block: var(--cart-total-top) 0;
}
/*   токен наявний, а не заведений заново. */
.cart-total__label {
  color: var(--label-ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
/*   — 89×24), 26/30 з трекінгом від стіни 1420: обидві пари несе один токен
   із сегментом. Це НЕ `--fs-lead`: та роль на 375 дає 18/22. */
.cart-total__sum {
  color: var(--ink);
  font-size: var(--cart-total-fs);
  line-height: var(--cart-total-lh);
  letter-spacing: var(--cart-total-ls);
  font-weight: var(--fw-medium);
}

.cart .wish-actions { margin-block-start: var(--cart-actions-top); }

/* --- ПЛАНШЕТ: КАДР НА ВСЮ ВИСОТУ, КЕРУВАННЯ ОДНИМ РЯДОМ НИЗОМ КАДРУ -------
   Канон: кадр 120×168 стоїть на всю висоту ряду, права колонка
   ділиться навпіл по вертикалі — назва з ціною згори (88), рядок керувань
   знизу (56), між ними 24. «Розмір» 168 → 16 → лічильник 176 → 16 → «Колір»
   168, кошик 56 правим краєм колонки (740…796): відстань до нього — 28 —
   не число, а ЗАЛИШОК колонки, тому пишеться `auto`. Стіна — 768. */
@media (min-width: 768px) {
  /* ВИСОТУ РЯДУ ЗАДАЄ ЧИСЛО РЯДУ, А НЕ КАДР І НЕ КАРТИНКА — той самий урок,
     що на «Обраному»: ряди однакового складу мусять бути однакової висоти на
     КОЖНІЙ ширині. Кадр тягнеться через усі чотири рядки сітки, але висоту
     несе ТИМ САМИМ числом, що й ряд, а не `stretch`: при `stretch` і
     `block-size: auto` картинка вносила в сітку свою інтринсику при ширині
     120 — `look-2.jpg` (400×564) просила 169.2, і ряд «Sale» ставав на 1.2
     вищим за ряд «New» (`wish-1.jpg` 572×800 дає 167.8). Замір показав, око
     — ні. */
  .cart-row {
    grid-template-rows: auto auto 1fr auto;
    min-block-size: var(--wish-row-h);
  }

  .cart-row__thumbs {
    grid-row: 1 / span 4;
    block-size: var(--wish-row-h);
  }

  /* Керування йдуть у ДРУГУ колонку низом кадру: 665…721 при кадрі
     553…721. `align-self: end` — канон, а не оформлення: у ряду з коротшою
     назвою рядок інакше висів би посередині колонки. */
  /* ОДНА ЛІНІЯ НА ВСІЙ СМУЗІ, без переносу. На якорі ряд стає рівно (628 =
     168+16+176+16+168+28+56), а на стіні 768 колонці лишається 581.6 при
     сталих 568 — тому проміжок між керуваннями ВИВЕДЕНИЙ рампою від стіни
     (`--cart-gap-t`, 4 → 16 на 768…820), а не взятий плоским 16: із 16 кошик
     виходив за вікно на 11 px (`check-hscroll`), а перенос ховав би це від
     ока. Переносу немає навмисно: якщо ряд колись не вміститься
     нехай це покаже чекер, а не другий рядок. */
  .cart-ctl {
    grid-area: 4 / 2;
    align-self: end;
    display: flex;
    align-items: center;
    gap: var(--cart-gap-t);
  }

  .cart-sel { flex: none; inline-size: var(--wish-sel-w); }
  .cart-qty { flex: none; }
  .cart-qty + .cart-sel { grid-column: auto; }

  /* Слово про залишок — ОКРЕМИМ рядком під керуваннями, а не між ними: у
     смузі керувань воно стиснулось би в колонку з двох слів. Перенос
     зʼявляється лише разом зі словом (схований абзац — `display: none`), тож
     ряд без нього лишається тією самою однією лінією. `order` відсуває
     абзац у кінець: без нього на другий рядок поїхали б і кнопка, і сума. */
  .cart-ctl { flex-wrap: wrap; }

  /* ПІД РОЗМІРОМ І КІЛЬКІСТЮ, а не під ціною (власниця, 12.09): перенесений
     рядок починається там само, де смуга керувань, тож слово стоїть саме під
     тим, чого стосується. `order` відсуває абзац у кінець: без нього на
     другий рядок поїхали б і кнопка, і сума. */
  .cart-row__note {
    order: 99;
    flex-basis: 100%;
    margin-block-start: var(--s-8);
    text-align: start;
  }

  /* Коло вертається саме тут — і повністю, а не скасуванням мобільних
     рядків: — 56×56, обведення `black/100`, знак 18. */
  .cart-del {
    flex: none;
    margin-inline-start: auto;
    margin-block-start: 0;
    justify-content: center;
    gap: 0;
    inline-size: var(--cart-del);
    block-size: var(--cart-del);
    border: var(--hairline) solid var(--line);
    border-radius: 50%;
    background: var(--surface);
  }

  /* Підпис кнопки лишається доступним іменем, але зі смуги зникає: тут його
     місце займає сам знак у колі. */
  .cart-del__text {
    position: absolute;
    inline-size: 1px;
    block-size: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* --- КАНОННИЙ РЯД: одна лінія від ВЛАСНОЇ стіни сторінки -------------------
   Стіна тут 1420, а не глобальна 1024, і це ЗАМІР, а не смак: ряд канону
   просить 1407 px вмісту (мініатюри 332 + 32 + назва 61 + керування 982), а
   самі керування — 982 — ширші за контейнер уже на 1024, де його всього 967.
   Тобто нижче своєї стіни ця композиція не «виглядає тісно», а не існує.

   Число взяте з РЕЄСТРУ, а не вигадане: 1420 уже зареєстроване
   (`config.json → thresholds.registered`), і сторінка оголошує його своєю
   стіною в `modes.desktop.pages` — тим самим механізмом, що «Оформлення
   замовлення» з його 1180. ⚠️ Там число НАЗВАВ замовник, тут воно
   ВИМІРЯНЕ — рядок чекає підтвердження.

   У смузі 1420…1920 мініатюри гнучкі: на 1420 їм лишається 271 на трьох, на
   1920 вони доростають до канонних 100. Це не рампа з двох якорів, а
   залишок ширини — і саме тому число тут не пишеться. */
@media (min-width: 1420px) {
  .cart-row {
    display: flex;
    align-items: center;
    min-block-size: var(--cart-row-h);
  }

  /* Висота мініатюр — ЧИСЛО РЯДУ, а не власна пропорція кадру. `stretch` тут
     не помагає: висота ряду виводиться з вмісту, вміст — зі `100%` висоти
     батька, і коло замикається на інтринсику картинки. Замір показав рівно
     це: `look-1.jpg` при ширині 100 просить 141, і ряд ставав 141 замість
     канонних 140 — на екрані не видно, у замірі видно. Той самий урок, що на
     «Обраному», тільки там його дала рампа, а тут — пропорція. */
  .cart-row__thumbs {
    flex: 0 1 auto;
    align-self: auto;
    min-inline-size: 0;
    inline-size: auto;
    block-size: var(--cart-row-h);
    gap: var(--cart-thumb-gap);
    overflow: visible;
  }
  /* Ширину кадру задає ВЛАСТИВІСТЬ, а не флекс-базис: при `inline-size: auto`
     кожен кадр брав свою інтринсику при висоті 140 — 100.1 у одного, 99.3 у
     іншого, — і ряд виходив 331.3 замість канонних 332. `flex: 0 1 auto`
     лишає їм канонні 100 там, де місце є, і дозволяє стиснутись там, де його
     бракує (нижче 1920 і до власної стіни сторінки). Спіймав замір, не око. */
  .cart-row__thumbs img {
    flex: 0 1 auto;
    min-inline-size: 0;
    inline-size: var(--cart-thumb-w);
  }
  /* Три ракурси — лише тут. */
  .cart-row__thumbs img + img { display: block; }

  /* Назва й ціна ряду — Txt-20 (20/24): коробки канону 61×24 («Брюки») і
     89×24 («3 400 грн») дають саме цей кегль, і він у проєкті вже є. Ширина
     назви — довжина рядка, не коробка. */
  .cart-row__name {
    flex: none;
    inline-size: auto;
    margin-inline-start: var(--cart-name-gap);
    font-size: var(--fs-lead);
    line-height: var(--lh-lead);
  }

  /* Права група притиснута до краю контейнера — і саме тому вона лягає на
     канон ТОЧНО. Ряд канону вужчий за контейнер на 2 px (50 замість 48,
), але правий край у них спільний, тож усе, що рахується від нього
     збігається; розходяться лише мініатюри й назва — вони від лівого.
     54 + 32 + 168 + 40 + 176 + 40 + 168 + 208 + 40 + 56 = 982. Обгортка тут
     знову КОРОБКА, а обгортка керувань усередині неї — розчинена. */
  .cart-row__tools {
    display: flex;
    align-items: center;
    margin-inline-start: auto;
    gap: 0;
  }
  .cart-ctl { display: contents; }

  /* Той самий перенос, але вже в смузі керувань: `.cart-ctl` тут не коробка,
     і флекс-контейнером абзацу стає `.cart-row__tools`. */
  .cart-row__tools { flex-wrap: wrap; }

  /* Вище цієї стіни смуга керувань починається з коробки бейджа, і без
     відступу слово стало б під ним, а не під розміром. */
  .cart-row__note {
    padding-inline-start: calc(var(--cart-badge-w) + var(--cart-gap-badge));
  }

  /* Коробка бейджа стоїть і порожня — саме вона тримає решту керувань на
     канонних x у ряду без бейджа. */
  .cart-row__badge {
    position: static;
    flex: none;
    inline-size: var(--cart-badge-w);
  }

  .cart-sel {
    flex: none;
    inline-size: var(--wish-sel-w);
    margin-inline-start: var(--cart-gap-badge);
  }
  .cart-qty { flex: none; margin-inline-start: var(--cart-gap-ctl); }
  .cart-qty + .cart-sel { margin-inline-start: var(--cart-gap-ctl); }

  /* Ціна праворуч у власній коробці 208: текст канону 89 закінчується на
     1726, тобто впритул до проміжку перед кошиком. Порожнеча ліворуч від
     суми — частина коробки, а не окремий проміжок. Тон чорний і Medium —
     20/24), а не `--price-ink` картки. */
  .cart-row__price {
    flex: none;
    inline-size: var(--cart-price-w);
    justify-content: flex-end;
    margin-block-start: 0;
    font-size: var(--fs-lead);
    line-height: var(--lh-lead);
    font-weight: var(--fw-medium);
  }

  /* Кошик — ОСТАННІЙ у групі, після ціни: у розмітці він стоїть усередині
     обгортки керувань (бо нижче стіни йде під «Колір»), а ціна — поза нею.
     `order` переставляє рівно один елемент, і саме там, де канон малює його
     останнім (1816 + 56 = 1872, край контейнера). */
  .cart-del {
    order: 1;
    margin-inline-start: var(--cart-gap-ctl);
  }
}


/* ==========================================================================
   `3QojL2lmdB71wIza55JH2J`.

   Усі три артборди — кабінет із РОЗКРИТИМ розділом «Мої замовлення»: нижче
   стіни — рядок переліку → замовлення впритул під ним → волосина → наступні
   розділи; на 1920 — оболонка кабінету (вітання, рядок контактів, меню
   колонкою) і панель на всю колонку вмісту (328…1874). Тому оболонка та
   сама (`.acc`, `.office`), а свого тут — лише `.orders`, згортки
   замовлень і товари в них.

   ⚠️ СТІНА ДЕСКТОПНОЇ КОМПОЗИЦІЇ ЦІЄЇ СТОРІНКИ — 1420, НЕ 1024
   (`config.json → modes.desktop.pages`). У десктопній оболонці на
   1024 колонка вмісту — 650, тобто панель ВУЖЧА за планшетний якір (772):
   рядок канону 820 (№ · дата · «Скасувати» · сума · знак = 662) у неї не
   лягає, а десктопний ряд товару (кадри · назва · довідка · ціна = 915 і
   рампа) вміщається лише від 1420 — там колонка назви стискається до
   коробки назви (`minmax`), решта чисел іде рампою. Той самий механізм, що
   в кошика з його 1420: між 1024 і 1419 сторінка лишається хабом
   а спільні правила оболонки `.page-cabinet` з 1024 тут знімаються і
   повертаються на 1420 — двома блоками, бо проти порогу (`max-width`) у
   проєкті не пишуть.
   ========================================================================== */

/* Вміст стає між рядком «Мої замовлення» (10) і «Персональні дані» (20). */
.page-cabinet--orders .acc-body { order: 15; }

/* НА ЦІЙ СТОРІНЦІ ВІТАННЯ НИЖЧЕ СТІНИ ВИДИМЕ — так на обох артбордах
   «Персональних даних» і «Допомоги», де його немає. Повернення — тими самими
   двома рядками `margin`, що й вище стіни: голий `margin: 0` збив би
   власний відступ заголовка. Крихти → вітання 48 · 56 · 60 — та сама рампа
   `--acc-title-top`, що на хабі. */
.page-cabinet--orders .acc__title {
  position: static;
  width: auto; height: auto;
  margin: 0;
  margin-block-start: var(--acc-title-top);
  overflow: visible;
  clip-path: none;
  white-space: normal;
}

/* Низ вітання → перший рядок переліку: 40 на 375 (180 → 220), 56 на 820
   (188 → 244) — число хаба `--of-list-top`, не крок «крихти → мітка». */
.page-cabinet--orders .acc { margin-block-start: var(--of-list-top); }

/* Замовлення стоять ВПРИТУЛ до волосини свого рядка (264 при низі рядка 265
   на 375; 288 при 289 на 820), а не через крок переліку: крок флексу
   гаситься тут навмисно і рівно на одну величину. Під переліком — 24, своя
   волосина і ще 24 до «Персональних даних» (1187 → 1211 → 1235): друге 24
   дає крок флексу, перше й волосину — сам перелік. */
.page-cabinet--orders .acc-body { margin-block-start: calc(0px - var(--of-item-gap)); }

.orders {
  display: grid;
  gap: var(--ord-gap);
  padding-block-end: var(--ord-tail);
  border-block-end: var(--hairline) solid var(--line);
}

/* --- ЗАМОВЛЕННЯ — згортка `<details>` ---------------------------------------
   заввишки з рамкою включно.
   — `opacity-40`), закритий — без заливки. */
.order {
  border: var(--hairline) solid var(--line);
  /* Кнопка скасування на 820 стоїть у рядку замовлення абсолютно — від
     цього краю рахується її положення. */
  position: relative;
}

/* Рядок міряється ЗОВНІШНЬОЮ коробкою замовлення (772×55, 343×55): у каноні
   це прямокутник на всю панель, а не вміст усередині рамки, тому він
   перекриває рамку відʼємними полями на волосину. */
.order__head {
  list-style: none;
  cursor: pointer;
  box-sizing: border-box;
  min-block-size: var(--ord-row-h);
  margin: calc(0px - var(--hairline)) calc(0px - var(--hairline)) 0;
  padding-inline: var(--ord-pad-x);
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-4);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.order__head::-webkit-details-marker { display: none; }

.order[open] .order__head {
  background: color-mix(in srgb, var(--line) calc(var(--ord-head-dim) * 100%), transparent);
}

/*   `text-[#a0a09f]`). Живуть лише нижче стіни 768: на 820 частини стоять
   колонками. */
.order__sep { color: var(--label-ink); }

/* Стан замовлення — пігулка того самого компонента, що бейджі товару
   (власниця, 12.09). Макет його не малює, тож власних мір у нього немає: усе
   бере від `.badge`, і лише відступ від суми — свій. */
.order__state { margin-inline-start: var(--s-16); }

/**/
.order__go {
  flex: none;
  margin-inline-start: auto;
  inline-size: var(--of-arrow);
  block-size: var(--of-arrow);
  rotate: 180deg;
}
.order[open] .order__go { rotate: 0deg; }

/* «Скасувати замовлення» — геометрія бейджа, тон кнопки: капсула 32 з
   `border black/900`), і хоч на екрані воно зливається із заливкою, фарба
   звіряється властивістю, а не оком. На 375 — під товарами по центру, через
   24 згори і 24 знизу (981 → 1005 → 1037 → 1061). */
.order__cancel {
  justify-self: center;
  margin-block-start: var(--s-24);
  display: inline-flex;
  align-items: center;
  block-size: var(--s-32);
  padding-inline: var(--s-12);
  border: var(--hairline) solid var(--ink);
  border-radius: var(--radius-pill);
  background: var(--ink);
  color: var(--btn-ink);
  font-family: inherit;
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  font-weight: var(--fw-regular);
  white-space: nowrap;
  cursor: pointer;
}

/* Тіло розкритого замовлення — товари, під ними кнопка. Поле панелі 16 на
   375 (кадри на 32 при панелі 16) і 24 на 820 (48 при 24) РАХУЄ РАМКУ, тому
   власне поле на волосину менше; низ — 24 на обох (1037 → 1061, 709 → 733).
   Це окрема коробка, а не сітка на самому `<details>`: розкладку на ньому
   рушій тримає не всюди. */
.order__body {
  display: grid;
  padding: var(--ord-pad-y) calc(var(--ord-pad-x) - var(--hairline)) calc(var(--s-24) - var(--hairline));
}

.order__items {
  margin: 0;
  padding: 0;
  list-style: none;
}

/* Волосина між товарами: 24 над нею, 24 під нею (666 → 690 → 714 на 375;
   514 → 538 → 562 на 820; 735 → 759 → 784 на 1920). Під останнім товаром
   лінії немає. Волосина стоїть ПОЗА своїми 24: на 375 і 1920 вона
   на 820 це вектор нульової висоти (48) — два артборди проти одного, і
   висота розкритого замовлення сходиться з 734 (375) і 508 (1920). */
.oitem:not(:last-child) {
  padding-block-end: var(--ord-item-gap);
  border-block-end: var(--hairline) solid var(--line);
}
.oitem + .oitem { margin-block-start: var(--ord-item-gap); }

/* --- ТОВАР У ЗАМОВЛЕННІ -----------------------------------------------------
   Нижче стіни 768 — стос: три кадри на всю ширину, під ними назва ліворуч і
   ціна праворуч, під ними довідка (розмір · кількість · колір). */
.oitem {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
}

/* Три кадри 101×147 з проміжком 4 = 311 — рівно внутрішня ширина панелі на
   375. Ширина кадру — третина без проміжків, а не число
   нижче мобільного якоря (320…374) панель вужча, і три числа по 101 не
   вміщались би; пропорція тут законна, бо на ОБОХ якорях кадр однаковий —
   101×147, — тобто жодної рампи вона не ховає. */
.oitem__thumbs {
  grid-column: 1 / -1;
  display: flex;
  gap: var(--ord-thumb-gap);
}
.oitem__thumbs img {
  flex: none;
  inline-size: calc((100% - 2 * var(--ord-thumb-gap)) / 3);
  aspect-ratio: var(--ord-thumb-ratio);
  block-size: auto;
  object-fit: cover;
}

/*   рядки; кадри → назва 16 на 375 (546 → 562). На 1920 — 197 у ТРИ рядки
   й кошику (`--wish-txt-*`: 16 → 20 від 820 до 1920). */
.oitem__name {
  grid-area: 2 / 1;
  display: block;
  margin-block-start: var(--ord-body-gap);
  inline-size: var(--ord-name-w);
  color: var(--ink);
  font-size: var(--wish-txt-fs);
  line-height: var(--wish-txt-lh);
  font-weight: var(--fw-medium);
}

/* Ціна — правим краєм, у тому самому рядку, що назва; чинна ціна чорна
, як у кошику. Пара «Sale» нижче стіни стоїть
   й парі в один рядок не стати. */
.oitem__price {
  grid-area: 2 / 2;
  justify-self: end;
  margin-block-start: var(--ord-body-gap);
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: 0;
  color: var(--ink);
}

/*   на обох артбордах (32 → 86 → 138 на 375; 383 → 436.9 → 489 на 820). Назва
   → довідка 16 (602 → 618). Це `<p>`, а не керування: змінити тут нічого. */
.oitem__ctl {
  grid-column: 1 / -1;
  margin: var(--ord-ctl-top) 0 0;
  display: flex;
  align-items: center;
  gap: var(--ord-chip-gap);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}
.oitem__dot + .oitem__dot {
  margin-inline-start: calc(var(--ord-dot-gap) - var(--ord-chip-gap));
}

.oitem__dot {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: var(--ord-dot);
  block-size: var(--ord-dot);
  border: var(--hairline) solid var(--line);
  border-radius: 50%;
}

/**/
.oitem__chip {
  flex: none;
  display: inline-flex;
  align-items: center;
  block-size: var(--ord-dot);
  padding-inline: var(--ord-chip-pad);
  border: var(--hairline) solid var(--line);
  border-radius: var(--radius-pill);
  white-space: nowrap;
}

/* --- ВІД 768: рядок замовлення колонками, товар — кадри ліворуч -----------
   Канон: № на 48, дата на 221, «Скасувати» на 418, сума правим
   краєм перед знаком (720 → 752 → 772). Колонки по 197 — це колонка підпису
   кабінету на 820, і кнопка стоїть рівно через дві
   якорями рампи (`--ord-col-*`). Стіна — 768; ці правила живуть і вище
   стіни сторінки 1420 — там міняється лише ряд товару. */
@media (min-width: 768px) {
  /* Колонки рахуються від внутрішнього краю панелі: № у першій, дата в
     другій, кнопка — на їхній сумі. */
  .order__head {
    display: grid;
    grid-template-columns: var(--ord-col-no) var(--ord-col-date) minmax(0, 1fr) auto var(--of-arrow);
    gap: 0;
  }
  .order__sep { display: none; }
  .order__no   { grid-column: 1; }
  .order__date { grid-column: 2; }
  .order__sum  { grid-column: 4; margin-inline-end: var(--ord-sum-gap); }

  /* Стан замовлення СТАВИТЬСЯ В КОЛОНКУ, а не розкладається сам: без
     вказівки сітка клала пігулку за останньою колонкою — вона виходила за
     край сторінки на 2 px і давала горизонтальний скрол на 820. Вільна
     доріжка тут третя, між датою і сумою. */
  .order__state { grid-column: 3; justify-self: start; }
  .order__go   { grid-column: 5; margin-inline-start: 0; }

  /* ⚠️ УСІ ЧАСТИНИ РЯДКА — В ПЕРШОМУ РЯДКУ СІТКИ (власниця, 13.09: «поїхала
     верстка»). Колонка без рядка розкладається курсором: стан (доріжка 3)
     іде в розмітці ПІСЛЯ суми (доріжка 4), курсор назад не вертається — і
     стан разом зі стрілкою падали в другий рядок під номер і дату. */
  .order__head > * { grid-row: 1; }

  /* ⚠️ КНОПКА НЕ В РЯДКУ ЗАМОВЛЕННЯ, А ПІД ТОВАРАМИ — як на 375 (власниця,
     13.09). Канон ставив її абсолютом у третю доріжку рядка (418 на 820,
     1176 на 1920), але цю доріжку тепер займає стан замовлення, якого канон
     не малює: кнопка лягала поверх «Виконано». Під товарами вона нічому не
     заважає й видна лише в розкритому замовленні — поруч із тим, що саме
     скасовується. */

  /* Товар: кадри 311 ліворуч на всю висоту, праворуч назва вгорі й довідка
     низом кадрів (466…514 при кадрах 367…514); кадри → назва 24 по
     горизонталі (359 → 383). */
  .oitem {
    grid-template-columns: auto minmax(0, 1fr) auto;
    grid-template-rows: auto 1fr auto;
    column-gap: var(--ord-body-gap);
  }
  .oitem__thumbs {
    grid-area: 1 / 1 / span 3;
    inline-size: calc(3 * var(--ord-thumb-w) + 2 * var(--ord-thumb-gap));
  }
  /* Від 768 кадр — два числа, не пропорція: 101×147 на 820 і 118×165 на
     1920 мають різну пропорцію, і рампа йде по кожній стороні окремо. */
  .oitem__thumbs img {
    inline-size: var(--ord-thumb-w);
    block-size: var(--ord-thumb-h);
    aspect-ratio: auto;
  }
  .oitem__name  { grid-area: 1 / 2; margin-block-start: 0; }
  .oitem__price {
    grid-area: 1 / 3;
    margin-block-start: 0;
    flex-direction: row;
    align-items: baseline;
    gap: var(--s-8);
  }
  .oitem__ctl {
    grid-area: 3 / 2 / auto / -1;
    align-self: end;
    margin-block-start: 0;
  }
}

/* --- 1024…1419: ХАБ ЛИШАЄТЬСЯ ------------------------------------------------
   Спільні правила `.page-cabinet` вмикають оболонку кабінету на 1024, а
   стіна ЦІЄЇ сторінки — 1420 (шапка розділу). Тут ті правила знімаються
   рядок у рядок лише для цієї сторінки, а блоком нижче повертаються на
   1420. Проти порогу (`max-width`) у проєкті не пишуть — реєстр порогів
   знає лише `min-width`, — тому два блоки, а не вікно. */
@media (min-width: 1024px) {
  .page-cabinet--orders .acc-bar,
  .page-cabinet--orders .acc-menu { display: none; }

  .page-cabinet--orders .acc {
    display: flex;
    gap: var(--of-item-gap);
    align-items: stretch;
    margin-block-start: var(--of-list-top);
  }

  .page-cabinet--orders .office,
  .page-cabinet--orders .office__list { display: contents; }

  .page-cabinet--orders .office__exit { display: flex; }

  .page-cabinet--orders .acc__label {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}

/* --- ДЕСКТОП, стіна сторінки 1420: оболонка кабінету, панель на всю колонку
   118×165 з проміжком 8, назва 197 у три рядки 20/24, кола 56, сума правим
   краєм на 1708, знак 1830…1850 (закриті рядки ставлять його на 1850 — два
   вузли проти одного, і на 820 знак теж стоїть на 24 від краю), «Скасувати»
   на 1176 у рядку. ----------------------------------------------------------- */
@media (min-width: 1420px) {
  /* Оболонка — ті самі рядки, що `.page-cabinet` вмикає на 1024. */
  .page-cabinet--orders .acc-bar { display: grid; }

  .page-cabinet--orders .acc-menu { display: block; }

  .page-cabinet--orders .acc {
    margin-block-start: var(--acc-head-gap);
    display: grid;
    gap: 0;
    align-items: start;
  }

  .page-cabinet--orders .office,
  .page-cabinet--orders .office__exit { display: none; }

  .page-cabinet--orders .acc__label {
    position: static;
    width: auto; height: auto;
    margin: 0;
    overflow: visible;
    clip-path: none;
    white-space: normal;
  }

  .page-cabinet--orders .acc-body { margin-block-start: 0; }

  /* Активний пункт меню тут — «Мої замовлення 105», і медіумом він у 220
     не стає (228): на «Персональних даних» той самий пункт регулярний і
     стан «поточний» у меню кабінету він не малює ніде; пункт лишається одним
     рядком і виходить у проміжок до вмісту на 8 — це не стискає ані меню,
     ані колонку вмісту (рейка 278 при меню 220). */
  .page-cabinet--orders .acc-menu__item[aria-current="page"] { white-space: nowrap; }

  /* Мітка «Мої замовлення» → панель: 32 (397…433 → 465). Панель — уся
     колонка вмісту (1546 = 1874 − 328), стелі переліку розділів тут немає;
     під панеллю ні волосини, ні хвоста — далі підвал. */
  .orders {
    margin-block-start: var(--s-32);
    padding-block-end: 0;
    border-block-end: 0;
  }

  /*     назва 612…684, кола 620…676, ціна 638…658): кадри · назва · довідка ·
     ціна правим краєм. Колонка назви — 422 (754 → 1176, як колонка дати в
     рядку), але не менша за коробку назви: на 1420 їй лишається саме
     коробка, і саме тому стіна стоїть тут, а не нижче. */
  /* Дві колонки повітря — назви (до 422) і від ціни до краю (до знака з
     проміжком «сума → знак», 142 на 1920: ціна 1707 стоїть ПІД сумою 1708,
     а решту бере проміжок довідка → ціна (1452 → 1562 = 110 на 1920).
     Коли панель вужча (1420: 998 при вмісті 969), стискаються саме ці дві
     колонки, а не ціна: ціна в один рядок ніде не ламається. На 820 ціна
     при самому краю (772) при сумі на 720 — інший розклад, і міняється він
     на стіні сторінки разом із рядом. */
  .oitem {
    grid-template-columns:
      auto
      minmax(var(--ord-name-w), var(--ord-col-date))
      auto
      minmax(0, 1fr)
      auto
      minmax(0, calc(var(--ord-sum-gap) + var(--of-arrow)));
    grid-template-rows: auto;
    align-items: center;
    column-gap: 0;
  }
  .oitem__thumbs { grid-area: 1 / 1; margin-inline-end: var(--ord-body-gap); }
  .oitem__name   { grid-area: 1 / 2; }
  .oitem__ctl    { grid-area: 1 / 3; align-self: center; }
  .oitem__price  { grid-area: 1 / 5; white-space: nowrap; }
}


/* ==========================================================================
   (Search_1920), файл `3QojL2lmdB71wIza55JH2J`.

   Один компонент у двох місцях: `.srch--float` — накладка в шапці (темне
   `.srch--page` — та сама форма на сторінці пошуку (світле поле, під ним
   підсумок і сітка каталогу). Числа — `--srch-*`, рампи 375 → 820 → 1920.
   Сторінка нижче 1024 канону не має: поле бере числа накладки й
   ширину контейнера, видача — сітка каталогу своєї смуги.
   ========================================================================== */

.srch__form {
  position: relative;
  inline-size: min(var(--srch-w), 100%);
  margin-inline: auto;
  block-size: var(--srch-h);
}

/* Поле без власної рамки: коробка — це заливка `black/100` на 40 %
   (32 + коло 32 + 32), щоб довгий запит не заходив під нього. */
.srch__input {
  box-sizing: border-box;
  inline-size: 100%;
  block-size: 100%;
  margin: 0;
  padding: 0 calc(2 * var(--srch-pad) + var(--srch-close)) 0 var(--srch-pad);
  border: 0;
  border-radius: 0;
  background: color-mix(in srgb, var(--line) calc(var(--srch-field-dim) * 100%), transparent);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  appearance: none;
}
/* ФОКУС — ВОЛОСИНА ПО КРАЮ ПОЛЯ, А НЕ ЖИРНА КОРОБКА. Глобальний контур
 має товщину 2, і на решті полів сайту вона не читається жирною: там
   під контуром уже стоїть власна рамка поля, і разом це одна лінія. У поля
   пошуку рамки НЕМАЄ ЗОВСІМ — коробка це заливка, — тому 2 px чорного лягали
   на світлу плашку окремою грубою рамкою (замовник: «не таке
   жирне поле повинно бути, коли ставимо курсор»).

   Індикатор не знятий, а стоншений до волосини й покладений УСЕРЕДИНУ поля:
   саме одну чорну лінію канон і малює навколо набраного поля (
). Розкладка не їде — контур геометрії не займає. */
.srch__input:focus-visible {
  outline: var(--hairline) solid var(--field-focus);
  outline-offset: calc(0px - var(--hairline));
}

.srch__input::placeholder { color: var(--ink); opacity: var(--srch-ph-dim); }
.srch__input::-webkit-search-decoration,
.srch__input::-webkit-search-cancel-button { appearance: none; display: none; }

/* Хрестик — коло 32 з волосиною `black/100` на 20 %, знак 18 (
   експорт: stroke #E7E7E7 / #232221). Кільце малює сам SVG (як в експорті):
   рамка кнопки стискала б знак до 30 і масштабувала б обведення. Кнопка, не
   посилання: на сторінці очищає поле, у накладці закриває її. */
.srch__close {
  position: absolute;
  inset-inline-end: var(--srch-pad);
  inset-block-start: 50%;
  translate: 0 -50%;
  inline-size: var(--srch-close);
  block-size: var(--srch-close);
  padding: 0;
  border: 0;
  background: none;
  /*     (замовник: «наведення — квадрат, а не коло»). */
  border-radius: 50%;
  color: var(--ink);
  cursor: pointer;
}
/* Розмір знака — числом, не `100%`: у схованій накладці відсоток нема від
   чого рахувати, і чекер масштабу знаків читав би його як 100 px. */
.srch__close svg { display: block; inline-size: var(--srch-close); block-size: var(--srch-close); }

/* Наведення — коло чорне, знак білий; у темній накладці — навпаки
   там коло вже на чорному полі. */
@media (hover: hover) {
  .srch--page .srch__close:hover { background: var(--btn-bg); color: var(--btn-ink); }
  .srch--float .srch__close:hover { background: var(--surface); color: var(--ink); }
}

/* --- Сторінка -------------------------------------------------------------- */
.srch--page {
  margin-block-start: var(--srch-top);
  padding-block-end: var(--srch-tail);
}

/*   ). Без запиту порожній, але в потоці: сітка не стрибає. */
.srch__count {
  margin: var(--srch-count-top) 0 0;
  min-block-size: var(--lh-body);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.srch__grid { margin-block-start: var(--srch-grid-top); }
/* Картку, яку запит не вибрав, двигун ховає атрибутом `hidden`; правило
   картки в сітці задає `display` і переважило б атрибут — тому явно. */
.srch__grid > [hidden] { display: none; }

/* --- Накладка в шапці ------------------------------------------------------
   у контейнері: на 1920 — колонки 3–6 (896), на 820 і 375 — увесь контейнер
   (772, 343), тобто та сама стеля `--srch-w` і ті самі поля сторінки. */
.srch--float {
  position: fixed;
  inset-block-start: var(--header-h);
  inset-block-end: 0;
  inset-inline: 0;
}
/* `hidden` мусить перемагати `position: fixed` — інакше накладка стояла б
   відкритою в кожного, у кого не виконався JS. */
.srch--float[hidden] { display: none; }

.srch__scrim {
  position: absolute;
  inset: 0;
  background: var(--scrim);
}

.srch--float .srch__form {
  position: relative;
  margin-block-start: var(--srch-top);
  inline-size: min(var(--srch-w), calc(100% - 2 * var(--gutter)));
}

.srch--float .srch__input {
  background: var(--ink);
  color: var(--surface);
}
.srch--float .srch__input::placeholder { color: var(--surface); }

/* У накладці поле темне: чорна волосина на ньому не видна зовсім — та сама
   причина, що в хрестика лайтбокса. */
.srch--float .srch__input:focus-visible { outline-color: var(--surface); }

.srch--float .srch__close { color: var(--surface); }

/* ПІДКАЗКИ ДОВІДНИКА НА ОФОРМЛЕННІ (F3, D557) — доповнення композиції:
   список міст і пунктів Нової Пошти під полем. Вигляд — випадного списку
   «Обраного» (`.wish-sel__list`): та сама волосина, поверхня й роздільник,
   тобто нової фігури сторінка не отримує. Висота обмежена: у Києві
   пунктів тисячі, і список без стелі пересунув би всю форму. */
.co-select:has(.co-suggest) { position: relative; }

.co-suggest {
  display: block;
  position: absolute;
  z-index: 5;
  inset-inline: 0;
  inset-block-start: 100%;
  max-block-size: 20rem;
  margin: 0;
  padding: var(--psize-pad) var(--wish-sel-pad) 0;
  overflow-y: auto;
  border: var(--hairline) solid var(--line);
  border-block-start: 0;
  background: var(--surface);
  list-style: none;
}

.co-suggest[hidden] { display: none; }

.co-suggest__opt,
.co-suggest__none {
  display: grid;
  padding-block-end: var(--s-16);
  border-block-end: var(--hairline) solid var(--line);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.co-suggest__opt { cursor: pointer; }
.co-suggest__opt + .co-suggest__opt { margin-block-start: var(--s-16); }
.co-suggest > :last-child { border-block-end: 0; padding-block-end: var(--psize-pad); }
.co-suggest__opt[aria-selected="true"] { font-weight: var(--fw-regular); }
.co-suggest__meta { color: var(--ink-muted); }
.co-suggest__none { color: var(--ink-muted); }

/* Контур фокуса на полях оформлення знятий (рішення власниці 11.09, D561):
   на текстовому полі він ставав ПОВЕРХ чорної рамки фокуса з еталона, і
   рамка читалась подвійною — «зайвою жирністю». Фокус лишається видимим
   рамкою еталона — той самий прийом, що на полі ціни каталогу (D551). */
.co .field__input:focus-visible { outline: none; }

/* Відповідь сервера, що не стосується поля (D563) — над плашкою, у потоці:
   `.field__hint` позиціонується абсолютно й лягав посеред підсумку. */
.co-alert {
  margin: 0 0 var(--s-16);
  color: var(--field-error);
  font-size: var(--fs-ui);
  line-height: var(--lh-ui);
  text-align: center;
}

/* Місто чекає області (D563): поле видно, але воно приглушене. */
.co .field__input:disabled { background: var(--surface); opacity: .5; cursor: not-allowed; }

/* Сторінка подяки (D561) — на композиції входу: абзаци підсумку й
   компоненти правої колонки оформлення. */
.thanks__lead {
  margin-block-start: var(--auth-form-top);
  color: var(--ink);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
}

.thanks__thumbs { margin-block-start: var(--auth-form-top); }

@media (hover: hover) {
  .co-suggest__opt:hover { color: var(--ink-muted); }
}
