/* base.css — общий каркас клиентских сайтов.
 *
 * Все цвета, шрифты и размеры приходят из design-tokens.json, скомпилированных
 * в assets/css/tokens.css. Этот файл не знает ни одного бренда — он знает
 * только переменные. Поэтому один агент безопасно правит любой магазин.
 */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
  margin: 0;
  background: var(--bg, #fff);
  color: var(--fg, #111);
  font-family: var(--font-text, system-ui, -apple-system, sans-serif);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

img { max-width: 100%; height: auto; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; }

/* Набор заголовков живёт в токенах (группа `type`), а не константами здесь:
   трекинг и интерлиньяж зависят от гарнитуры. Значения под широкий гротеск
   на антикве склеивают буквы и накладывают строки друг на друга. */
h1, h2, h3, h4 {
  font-family: var(--font-display, var(--font-text, sans-serif));
  margin: 0;
  line-height: var(--display-leading, 1.06);
  letter-spacing: var(--display-track, -0.02em);
}
h1 {
  font-size: calc(clamp(30px, 5.2vw, 58px) * var(--display-scale, 1));
  font-weight: var(--display-weight, 700);
}
h2 { font-weight: var(--display-weight, 700); }
h2 { font-size: calc(clamp(26px, 3.4vw, 46px) * var(--display-scale, 1)); }
h3 { font-size: calc(19px * var(--display-scale, 1)); }
p  { margin: 0 0 1em; }

/* Лид и метка — две роли шкалы, которых раньше не было: подзаголовок первого
   экрана жил 17px серым, а метка над заголовком выглядела бейджем-пилюлей
   даже там, где семейство говорит тонкой разрядкой. */
.lead {
  font-size: clamp(17px, 1.5vw, var(--lead-size, 19px)); line-height: 1.55;
  color: var(--muted, #777); max-width: 52ch; text-wrap: pretty;
}
.eyebrow {
  display: inline-block; font-size: 12px; font-weight: 500;
  letter-spacing: var(--eyebrow-track, .16em); text-transform: uppercase;
  color: var(--accent, currentColor);
}

::selection { background: var(--accent, #111); color: var(--on-accent, #fff); }
:focus-visible { outline: 2px solid var(--accent, #111); outline-offset: 3px; }

.wrap { width: min(var(--container, 1240px), 100% - var(--gutter, 32px) * 2); margin-inline: auto; }
.section { padding-block: var(--section-pad, 72px); }
.section > .wrap > h2 { margin-bottom: 26px; }
.hr { height: 1px; background: var(--line, #e6e6e6); border: 0; margin: 0; }

/* ─── шапка ──────────────────────────────────────────────────────────────── */

.site-header {
  position: sticky; top: 0; z-index: 40;
  display: flex; align-items: center; justify-content: space-between;
  gap: 24px; padding: 16px 0;
  background: color-mix(in srgb, var(--bg, #fff) 88%, transparent);
  backdrop-filter: saturate(140%) blur(10px);
  border-bottom: 1px solid var(--line, #eee);
}
.site-header__brand {
  display: inline-flex; align-items: center; gap: 12px;
  font-family: var(--font-display, inherit);
  /* 700, а не 800: у половины гарнитур платформы жирнее 700 файлов нет,
     и браузер дорисовывает псевдожирный — буквы выглядят кривыми */
  font-weight: 700; font-size: 19px; letter-spacing: var(--display-track, -0.02em);
}
.site-header__logo { display: block; height: 28px; width: auto; max-width: 180px; object-fit: contain; }
/* Знак отдельно от названия: в стопке «знак над словом» слово съедает высоту,
   и сам знак остаётся размером со строчную букву. Рядом — название шрифтом
   сайта, так знак можно поднять до полной высоты шапки. */
.site-header__mark { display: block; height: 56px; width: auto; max-width: 120px; object-fit: contain; }
.site-header__name { font-size: 20px; letter-spacing: .06em; text-transform: uppercase; }
@media (max-width: 720px) {
  .site-header__mark { height: 40px; }
  .site-header__name { font-size: 15px; }
}
.site-nav { display: flex; align-items: center; gap: 26px; font-size: 14px; letter-spacing: .02em; }
.site-nav a { padding-block: 6px; background: linear-gradient(currentColor, currentColor) no-repeat 0 100% / 0 1px; transition: background-size .2s, color .2s; }
.site-nav a:hover { color: var(--accent, #111); background-size: 100% 1px; }

/* Бургер. На 375 у «Клары» в одну строку с логотипом не влезали «Каталог»,
   три документа и «Заказ» — шапка вылезала за экран. Теперь на ≤ 720px пункты
   уходят в выпадающую панель; кнопка — настоящая <button> с aria-expanded,
   панель закрывается Esc и кликом по пункту (скрипт в base.html). */
.site-header__burger {
  display: none; position: relative; width: 44px; height: 44px; margin-right: -10px;
  padding: 0; border: 0; background: none; color: inherit; cursor: pointer;
}
.site-header__burger span,
.site-header__burger::before, .site-header__burger::after {
  content: ""; position: absolute; left: 12px; right: 12px; height: 1.5px;
  background: currentColor; transition: transform .25s, opacity .2s;
}
.site-header__burger::before { top: 16px; }
.site-header__burger span { top: 21px; }
.site-header__burger::after { top: 26px; }
.site-header.is-open .site-header__burger::before { transform: translateY(5px) rotate(45deg); }
.site-header.is-open .site-header__burger span { opacity: 0; }
.site-header.is-open .site-header__burger::after { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 720px) {
  .site-header { padding: 10px 0; gap: 12px; }
  .site-header__brand { min-width: 0; overflow: hidden; }
  .site-header__brand > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .site-header__burger { display: block; flex: none; }
  .site-nav {
    position: absolute; top: 100%; left: calc(var(--gutter, 18px) * -1); right: calc(var(--gutter, 18px) * -1);
    flex-direction: column; align-items: stretch; gap: 0;
    padding: 6px var(--gutter, 18px) 18px;
    background: var(--bg, #fff); color: var(--fg, #111);
    border-bottom: 1px solid var(--line, #eee);
    box-shadow: 0 18px 30px -24px rgb(0 0 0 / 40%);
    font-size: 17px;
    visibility: hidden; opacity: 0; transform: translateY(-6px);
    transition: opacity .2s, transform .2s, visibility 0s .2s;
  }
  .site-nav a { padding: 13px 0; border-bottom: 1px solid var(--line, #eee); background: none; }
  .site-nav a:last-child { border-bottom: 0; }
  .site-header.is-open .site-nav { visibility: visible; opacity: 1; transform: none; transition: opacity .2s, transform .2s; }
}

/* Прозрачная шапка над кадром на весь экран (hero cover): кадр начинается от
   верхнего края, а не из-под полосы цвета фона. Текст — тоном inverse, как и
   у самого первого экрана. Проскроллили — шапка набирает фон обратно. */
.page--index:has(main > .hero--cover:first-child) .site-header:not(.is-solid):not(.is-open) {
  background: transparent; backdrop-filter: none; border-bottom-color: transparent;
  color: var(--tone-inverse-fg, #fff);
}
.page--index:has(main > .hero--cover:first-child) .site-header {
  position: fixed; left: 0; right: 0; top: 0;
  transition: background .3s, color .3s, border-color .3s;
}
.page--index:has(main > .hero--cover:first-child) main > .hero--cover:first-child {
  --header-h: 0px; padding-top: calc(clamp(48px, 8vh, 96px) + 64px);
}

/* ─── герой ──────────────────────────────────────────────────────────────── */

.hero { padding-block: var(--hero-pad, 92px) var(--section-pad, 72px); }
.hero h1 { margin-block: 14px 16px; }
.hero .badge + h1 { margin-top: 18px; }
.hero__tagline {
  color: var(--muted, #777); max-width: 52ch; margin: 0; text-wrap: pretty;
  font-size: clamp(17px, 1.5vw, var(--lead-size, 19px)); line-height: 1.55;
}
/* Шкала h1 первого экрана — из токенов семейства (--h1-min/-fluid/-max,
   --display-weight): люкс — до 92px лёгкой антиквой, минимал — 64px
   гротеском. Раньше у всех был потолок 58px жирным, и люксовая главная
   «Клары» читалась как прайс-лист. Внешний min() — закон мобильной ширины:
   на 375 заголовок не больше 56px, как бы ни выкрутили display_scale.
   Только первый экран: название товара в 92px на его странице — плакат. */
.hero h1 {
  font-size: min(
    calc(clamp(var(--h1-min, 30px), var(--h1-fluid, 5.2vw), var(--h1-max, 58px)) * var(--display-scale, 1)),
    max(56px, 12vw));
  text-wrap: balance;
}
.hero__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* ─── кнопки ─────────────────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 14px 24px;
  border: 1px solid transparent;
  border-radius: var(--r-btn, 999px);
  font-weight: 600; font-size: 15px; cursor: pointer;
  transition: background .15s, color .15s, border-color .15s, transform .12s;
}
.btn:active { transform: translateY(1px); }
.btn--primary { background: var(--accent, #111); color: var(--on-accent, #fff); }
.btn--primary:hover { background: var(--accent-dark, var(--accent, #111)); }
.btn--ghost { border-color: var(--line-strong, #ccc); }
.btn--ghost:hover { background: var(--surface-2, #f5f5f5); }
.btn[disabled] { opacity: .55; pointer-events: none; }

/* ─── сетка каталога ─────────────────────────────────────────────────────── */

.catalog-grid {
  display: grid;
  grid-template-columns: repeat(var(--grid, 3), minmax(0, 1fr));
  gap: var(--gap, 28px);
}
@media (max-width: 1024px) { .catalog-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 620px)  { .catalog-grid { grid-template-columns: 1fr; gap: 20px; } }

/* Карточка — не ссылка, а контейнер со ссылкой-оверлеем. Кнопки (листание
   кадров, выбор цвета) внутри <a> — невалидная разметка и ловушка для
   клавиатуры: они попадали в таб-порядок раньше самой ссылки. Теперь ссылка
   растягивается псевдоэлементом на всю карточку, а кнопки лежат над ней. */
.product-card { display: block; position: relative; counter-increment: card; }
[data-catalog-grid] { counter-reset: card; }
.product-card__link { display: block; }
.product-card__link::after { content: ""; position: absolute; inset: 0; z-index: 0; }
.product-card__link:focus-visible { outline: none; }
.product-card__link:focus-visible::after { outline: 2px solid var(--accent, #111); outline-offset: 3px; }
.product-card__media {
  position: relative; overflow: hidden;
  border-radius: var(--r-card, 14px);
  background: var(--surface-2, #f2f2f2);
  aspect-ratio: var(--card-ratio, 4 / 5);
}
/* Кадры лежат стопкой: активный виден, остальные ждут. Перелистывание —
 * половинками фото и точками (blocks/assets/js/card.js), на таче свайпом.
 * Порядок правил важен: `.is-shown` обязан перебивать `--next`. */
.product-card__img {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
  transition:
    opacity var(--t-base, 350ms) var(--ease-out, ease),
    transform var(--t-drift, 500ms) var(--ease-out, ease);
}
.product-card__img--next { opacity: 0; }
/* На десктопе наведение показывает второй кадр — как было до перелистывания.
 * Но как только посетитель полистал сам (скрипт ставит data-frame), наведение
 * больше не имеет права возвращать второй кадр поверх выбранного. */
@media (hover: hover) {
  .product-card:not([data-frame]):hover .product-card__img:first-child + .product-card__img--next { opacity: 1; }
}
.product-card__img.is-shown { opacity: 1; }
.product-card__img.is-hidden { opacity: 0; }
.product-card:hover .product-card__img { transform: scale(1.03); }

.product-card__dots {
  position: absolute; left: 0; right: 0; bottom: 10px;
  display: flex; gap: 5px; justify-content: center;
  opacity: 0; transition: opacity var(--t-base, 220ms) var(--ease-out, ease);
  pointer-events: none;
}
.product-card:hover .product-card__dots { opacity: 1; }
.product-card__dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: color-mix(in srgb, var(--fg, #111) 30%, transparent);
  transition: background var(--t-fast, 120ms);
}
.product-card__dot.is-active { background: var(--accent, #111); }
/* на таче наведения нет: точки видны всегда, иначе их не найти */
@media (hover: none) { .product-card__dots { opacity: 1; } }

/* Полосы по краям фото листают кадры. Не половины: середина кадра обязана
   вести в товар — покупатель кликает по вещи, чтобы её открыть, а не чтобы
   посмотреть её же с другой стороны. */
/* Низ кадра отдан свотчам цвета: если зона листания дотянется до них, палец
   на телефоне будет попадать в перелистывание вместо выбора цвета. */
.product-card__zone {
  position: absolute; top: 0; bottom: 46px; width: 26%; z-index: 1;
  border: 0; padding: 0; background: none; opacity: 0; cursor: pointer;
}
.product-card__zone--prev { left: 0; }
.product-card__zone--next { right: 0; }
@media (hover: none) { .product-card__zone { display: none; } }
.product-card__tag {
  position: absolute; left: 12px; top: 12px;
  padding: 5px 11px; border-radius: 999px;
  background: color-mix(in srgb, var(--bg, #fff) 88%, transparent);
  font-size: 11px; font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
}
.product-card__body { display: flex; flex-direction: column; gap: 3px; padding-top: 14px; }
.product-card__name { font-weight: 600; font-size: 15px; letter-spacing: -.01em; text-wrap: balance; }
.product-card__hook { color: var(--muted, #888); font-size: 13px; }
/* Цена лёгкая и с табличными цифрами: столбик цен ровный, а жирная цена
   крупнее названия кричит «распродажа» на любом семействе. */
.product-card__price {
  margin-top: 2px; font-weight: var(--price-weight, 600);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: calc(15px * var(--price-scale, 1));
  color: var(--price-color, var(--fg, #111));
}
/* Карточка без снимка: заглушка (assets/img/placeholder.svg) — фирменная
   подложка с монограммой, её сборка рисует из текущих токенов. Номер «01»,
   «02» дорисовываем здесь: двенадцать одинаковых рамок подряд читаются как
   сломанная сетка, а пронумерованные — как витрина, ждущая предметов.
   Работает и для карточек, которые перерисовал catalog.js: он ставит ту же
   заглушку, когда кадра нет. */
.product-card__media:has(> .product-card__img[src*="placeholder.svg"])::after {
  content: counter(card, decimal-leading-zero);
  /* шаблон, который сам нумерует карточки в подписи, гасит угловой номер */
  display: var(--ph-index, block);
  position: absolute; right: 14px; bottom: 12px; z-index: 1; pointer-events: none;
  font-family: var(--font-display, inherit); font-size: 15px; letter-spacing: .12em;
  font-variant-numeric: tabular-nums lining-nums;
  color: color-mix(in srgb, var(--fg, #111) 55%, transparent);
}
.product-card__media:has(> .product-card__img[src*="placeholder.svg"]) .product-card__img { transform: none; }

/* Выбор цвета лежит на самом кадре, в левом нижнем углу: цвет выбирают, глядя
   на вещь, а не на подпись под ней. Квадраты, а не круги — иначе рядом с
   точками-кадрами получится два ряда одинаковых кружков. z-index выше зон
   листания (у них 1): скрипт дописывает зоны после свотчей, и без этого они
   накрыли бы их собой. */
.product-card__colors {
  position: absolute; left: 12px; bottom: 12px; z-index: 2;
  display: flex; gap: 6px;
}
.product-card__color {
  width: 20px; height: 20px; padding: 0; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--fg, #fff) 45%, transparent);
  border-radius: 3px; background: var(--swatch, transparent);
  /* кадр под свотчем бывает любой яркости — тень отделяет его от фотографии */
  box-shadow: 0 1px 6px rgb(0 0 0 / 45%);
}
.product-card__color.is-active { outline: 2px solid var(--accent, #111); outline-offset: 2px; }
.product-card__color:focus-visible { outline: 2px solid var(--accent, #111); outline-offset: 2px; }

/* ─── карточка товара ────────────────────────────────────────────────────── */

.product { display: grid; grid-template-columns: 1.15fr 1fr; gap: 56px; align-items: start; }
@media (max-width: 900px) { .product { grid-template-columns: 1fr; gap: 32px; } }

.product__photo {
  border-radius: var(--r-card, 14px); overflow: hidden;
  background: var(--surface-2, #f2f2f2); aspect-ratio: var(--card-ratio, 4 / 5);
}
.product__photo img { width: 100%; height: 100%; object-fit: cover; }
.product__thumbs { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.thumb {
  width: 64px; height: 78px; padding: 0; overflow: hidden; cursor: pointer;
  border: 1px solid var(--line, #e6e6e6); border-radius: 8px; background: none;
}
.thumb.is-active { border-color: var(--accent, #111); }
.thumb img { width: 100%; height: 100%; object-fit: cover; }

.product__price {
  font-size: calc(26px * var(--price-scale, 1)); font-weight: var(--price-weight, 600);
  font-variant-numeric: tabular-nums lining-nums; margin: 8px 0 20px;
}
.product__row { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 18px; }
.chip {
  padding: 9px 16px; border: 1px solid var(--line-strong, #ccc);
  border-radius: var(--r-chip, 999px); background: none; cursor: pointer; font-size: 14px;
}
.chip.is-active { border-color: var(--accent, #111); background: var(--accent, #111); color: var(--on-accent, #fff); }
/* Кружок расцветки внутри чипа: цвет выбирают глазами, подпись остаётся для
   тех, кому «чёрный» и «графитовый» на кружке не различить. */
.chip__swatch {
  display: inline-block; vertical-align: -2px; margin-right: 8px;
  width: 14px; height: 14px; border-radius: 50%;
  border: 1px solid var(--line-strong, #999); background: var(--swatch, transparent);
}
.product__attrs { margin-top: 24px; font-size: 14px; color: var(--muted, #777); }
.product__attrs dt { font-weight: 600; color: var(--fg, #111); margin-top: 10px; }
.product__attrs dd { margin: 2px 0 0; }

/* ─── формы ──────────────────────────────────────────────────────────────── */

.form { display: grid; gap: 14px; max-width: 480px; }
.form label { display: grid; gap: 6px; font-size: 13px; color: var(--muted, #777); }
.field {
  width: 100%; padding: 13px 15px; font: inherit; font-size: 15px;
  color: var(--fg, #111); background: var(--surface, #fff);
  border: 1px solid var(--line-strong, #ccc); border-radius: var(--r-field, 10px);
}
.field:focus { outline: none; border-color: var(--accent, #111); }
.order-status { font-size: 14px; padding: 10px 0; }
.order-status--ok { color: var(--ok, #3F7A4C); }
.order-status--error { color: var(--error, #B3402E); }
.order-status--pending { color: var(--muted, #888); }
.form__hint { display: block; font-size: 12px; color: var(--muted, #888); }
.form label.is-required .form__hint { color: var(--fg, #111); }

/* ─── страница возврата после оплаты ─────────────────────────────────────── */

.pay-status { padding: 24px 0; }
.pay-status__text { font-size: 17px; color: var(--muted, #777); margin: 12px 0 0; }
.pay-status__actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 24px; }
.pay-status__help { margin-top: 18px; font-size: 14px; color: var(--muted, #888); }
.pay-status--ok .pay-status__title { color: var(--ok, #3F7A4C); }
.pay-status--error .pay-status__title { color: var(--error, #B3402E); }
.pay-status--pending .pay-status__title::after {
  content: ""; display: inline-block; width: .45em; height: .45em; margin-left: .4em;
  border-radius: 50%; background: currentColor; vertical-align: middle;
  animation: pay-pulse 1.2s ease-in-out infinite;
}
@keyframes pay-pulse { 0%, 100% { opacity: .25; } 50% { opacity: 1; } }
/* .btn — inline-flex и перебивает правило браузера для [hidden]: без этого
   спрятанная кнопка «Оплатить ещё раз» видна всегда */
.btn[hidden], .pay-status__actions[hidden], .pay-status__help[hidden], .form__hint[hidden] { display: none; }

/* ─── подвал ─────────────────────────────────────────────────────────────── */

/* Подвал колонками: бренд, навигация, контакты, документы. Раньше это была
   одна строка «© · Telegram · сделано в abrikos» — покупатель не находил ни
   телефона, ни оферты, а у дорогого магазина подвал и есть визитка. */
.site-footer {
  margin-top: var(--section-pad, 72px); padding: 56px 0 32px;
  border-top: 1px solid var(--line, #eee);
  color: var(--muted, #888); font-size: 14px;
}
.site-footer__grid {
  display: grid; grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  gap: 32px 40px; padding-bottom: 40px;
}
.site-footer__brand {
  display: block; margin-bottom: 10px; color: var(--fg, #111);
  font-family: var(--font-display, inherit); font-size: 22px; font-weight: var(--display-weight, 700);
  letter-spacing: var(--display-track, -0.02em);
}
.site-footer__tagline { margin: 0; max-width: 32ch; }
.site-footer__title {
  margin: 0 0 12px; color: var(--fg, #111); font-family: var(--font-text, inherit);
  font-size: 12px; font-weight: 600; letter-spacing: var(--eyebrow-track, .14em); text-transform: uppercase;
}
.site-footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.site-footer__list a { overflow-wrap: anywhere; }
.site-footer a:hover { color: var(--fg, #111); }
.site-footer__row {
  display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between;
  padding-top: 22px; border-top: 1px solid var(--line, #eee); font-size: 13px;
}
@media (max-width: 900px) { .site-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 520px) {
  .site-footer__grid { grid-template-columns: 1fr; gap: 28px; }
}

/* ─── прочее ─────────────────────────────────────────────────────────────── */

/* Бейдж красится отдельной парой токенов, за читаемость которой отвечает
   деривация палитры. Пара accent-dark/accent-wash подразумевала «тёмные
   чернила на светлой подложке» и разваливалась на любой тёмной теме:
   у клиента вышло 1.31 при норме 4.5 — надпись просто исчезла. */
.badge {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  background: var(--badge-bg, var(--accent-wash, rgba(0,0,0,.06)));
  color: var(--badge-fg, var(--accent-dark, var(--fg, #111)));
  /* Тонкая рамка цветом чернил: сама плашка отличается от фона секции всего
     на 1.1–1.4 по контрасту, и без рамки читаемая надпись висит в воздухе. */
  border: 1px solid color-mix(in srgb, var(--badge-fg, currentColor) 30%, transparent);
  font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase;
}
.countdown { display: flex; gap: 14px; margin-top: 22px; font-variant-numeric: tabular-nums; }
.countdown__cell { text-align: center; }
.countdown__num { font-family: var(--font-display, inherit); font-size: 30px; font-weight: 800; line-height: 1; }
.countdown__label { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--muted, #888); }

[data-order-success][hidden] { display: none; }

/* ─── кадры проявляются, а не выпрыгивают ──────────────────────────────── */
/* Класс b-js и слушатель load ставит скрипт в <head> (base.html): без JS
   картинки видны сразу. Кадры с loading="eager" (первый экран) не ждут —
   LCP не платит за красоту. Только «from»: конечная прозрачность остаётся
   той, что задала карточка, — второй кадр стопки так и стоит невидимым. */
:root.b-js img[loading="lazy"]:not(.is-loaded) { visibility: hidden; }
:root.b-js img[loading="lazy"].is-loaded { animation: b-img-in var(--t-slow, 480ms) var(--ease-out, ease) backwards; }
@keyframes b-img-in { from { opacity: 0; } }

@media (prefers-reduced-motion: reduce) {
  :root.b-js img[loading="lazy"].is-loaded { animation: none; }
  .site-nav, .site-header__burger::before, .site-header__burger::after { transition: none; }
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
}
