/* Экран season-tracker — раскладка по макету Figma.

   ИСТОЧНИК: файл Season (4KGSU7UHkVWlfmMCs1wwhw), секция 36:32331 «Персонаж
   загружен». Шесть нод-брейкпоинтов:
     1920  15:1621    1280  29:11250   1024  29:10823
      768  29:10401    576  29:9981     320  29:6339
   плюс 36:32332 — липкая панель фильтров и таб-бар поверх прокрученной страницы.

   ЧТО ЗДЕСЬ ЕСТЬ И ЧЕГО НЕТ.
   Контент экрана собран из ГОТОВЫХ узлов библиотеки: banner-countdown,
   character-widget, season-checklist-item, footer, tab-bar + компоненты
   (badge, breadcrumbs, chips, counter, accordion, feedback-question, timer,
   progress-bar, button, link, select, combobox, input, spinner,
   notification-inline). Своей вёрстки под них экран не пишет.

   ЛОКАЛЬНЫЕ УЗЛЫ (по решению владельца 2026-08-14 в библиотеку не выносятся —
   их в ней нет, а собирать блоки на лету регламент запрещает):
     .st-header   медиа-шапка (нода Header 29:11251) — это НЕ блок header,
                  тот собран с главной магазина и выглядит иначе;
     .st-toolbar  ряд чипсов-фильтров + тумблер «Only missing» (Switcher);
     .st-toggle   сам тумблер (компонента Switcher в библиотеке нет);
     .st-group    карточка секции с синей шапкой (нода Group);
     .st-hiw      «How this tracker works?» из четырёх карточек-шагов
                  (одноимённый блок библиотеки — другой узел, с аккордеоном);
     .st-tabbar   медиа-вкладки на классах блока tab-bar.

   ПРАВИЛА, КОТОРЫМ ЭТОТ ФАЙЛ СЛЕДУЕТ:
   - значения только var(--token); структурные габариты прямо из нод помечены;
   - чисел точек перелома здесь НЕТ: экран читает готовые флаги grid.css
     (--bp-tablet / --bp-tlandscape / --bp-desktop) и считает ими в calc();
   - стили готовых блоков не переопределяются глобально: всё, что экран им
     добавляет, стоит под его собственным префиксом (.st-list .sci…). */

body {
  margin: 0;
  background: var(--color-gray-0);
  color: var(--color-gray-900);
  font-family: var(--font-family-base);
  /* Inter Variable сужает глифы осью opsz против статического Inter в Figma. */
  font-optical-sizing: none;
}

*, *::before, *::after { box-sizing: border-box; }

/* clip, а НЕ hidden: hidden делает .page скролл-контейнером, и position: sticky
   у липкой панели фильтров начинает считаться от неё — то есть не работает
   вовсе. clip срезает вылезающие за край ленты так же, но скролл-контейнером
   не становится. */
.page { position: relative; overflow-x: clip; }

/* ══ ОБЩИЕ ПОЛЯ ЭКРАНА ══════════════════════════════════════════════════════
   Контейнер системный (.container из grid.css): 20 → 64 на десктопе, max-width
   1440 — при фрейме 1280 это ровно 1152, как в макете.

   ОДНА ДОБАВКА: на ноде 1024 поля равны 36, а системный контейнер даёт там 20
   (grid.css: 1024 — не активная точка, между 768 и 1280 контейнер течёт).
   У экрана СВОЯ нода на этой ширине, а grid.css именно для такого случая держит
   флаг --bp-tlandscape. Поэтому добавка 16px включается флагом и гаснет на
   десктопе, где поля уже 64. Числа точек перелома по-прежнему только в grid.css. */
.st-wrap,
.st-header__row {
  padding-inline: calc(var(--container-pad) + 16px * var(--bp-tlandscape) * (1 - var(--bp-desktop)));
}

/* ══ ШАПКА МЕДИА-РАЗДЕЛА ════════════════════════════════════════════════════ */

.st-header {
  position: relative;
  z-index: 3;
  background: var(--color-gray-0);
  /* Divider снизу — inset box-shadow, а не border: border добавил бы 1px к
     высоте шапки и сдвинул весь контент ниже (см. ноды: 110 и 102 ровно). */
  box-shadow: inset 0 -1px 0 var(--color-gray-100);
}

.st-header__top { background: var(--color-gray-25); }

.st-header__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--spacing-16);
}

/* Верхняя полоса: 42px = 20 (контент) + поля 11. */
.st-header__top .st-header__row { padding-block: 11px; }

/* Нижняя полоса: 68 на 1024+ (поля 12), 60 ниже (поля 8) — считаем флагом. */
.st-header__menu .st-header__row {
  padding-block: calc(8px + 4px * var(--bp-tlandscape));
}

.st-header__lang { display: flex; align-items: center; gap: var(--spacing-8); }
.st-header__flag { display: block; width: 16px; height: 16px; }

.st-header__lang-btn {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-gray-900);
  cursor: pointer;
  font-family: inherit;
}

.st-header__lang-btn svg { width: 16px; height: 16px; color: var(--color-gray-400); }

/* «English (US)» на планшете и шире, «Eng (US)» ниже — в макете это разный
   текст на разных нодах, поэтому две ветки гасят друг друга. */
.st-header__lang-wide   { display: var(--from-mlandscape); }
.st-header__lang-narrow { display: var(--below-mlandscape); }

/* Строка Trustpilot есть только на широких нодах: в 320, 576 и 768 она в макете
   выключена (visible=false), появляется с 1024. Носитель display — обёртка,
   чтобы внутренний ряд остался flex. */
.st-header__trust-slot { display: var(--from-tlandscape); }

.st-header__trust {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  color: var(--color-gray-900);
  text-decoration: none;
}

.st-header__trust img { display: block; }

.st-header__logo {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  text-decoration: none;
}

/* Логотип: 28px на 1024+, 22px ниже (габариты нод 186×28 и 148×22). */
.st-header__logo .logo svg { height: calc(22px + 6px * var(--bp-tlandscape)); }

.st-header__label {
  display: flex;
  align-items: center;
  padding-inline: var(--spacing-6);
  border-radius: var(--radius-32);
  background: var(--color-accent-400);
  color: var(--color-gray-0);
  font-size: var(--text-body-12m-size);
  line-height: var(--text-body-12m-line-height);
  font-weight: var(--text-body-12m-weight);
}

/* Меню тянется на всю высоту полосы: в ноде оно 68 при контенте полосы 44 —
   то есть выходит за её поля вверх и вниз (иначе шапка стала бы 88 вместо 68,
   а подчёркивание активного пункта не легло бы на нижний край). Отрицательное
   поле повторяет ровно то, что полоса добавила сверху и снизу. */
.st-header__nav {
  display: var(--from-tlandscape);
  align-items: stretch;
  gap: var(--spacing-32);
  margin-block: calc(-8px - 4px * var(--bp-tlandscape));
}

/* Пункт: высота полосы, текст по центру. Активный — цвет accent-600 и полоса
   2px accent-400 по низу (в ноде это отдельный Divider, здесь box-shadow,
   чтобы не менять габарит пункта). */
.st-header__nav-link {
  display: flex;
  align-items: center;
  gap: var(--spacing-4);
  color: var(--color-gray-900);
  text-decoration: none;
}

.st-header__nav-link svg { width: 16px; height: 16px; }

.st-header__nav-link:hover { color: var(--color-accent-400); }

.st-header__nav-link--active {
  box-shadow: inset 0 -2px 0 var(--color-accent-400);
  color: var(--color-accent-600);
}

.st-header__search      { display: var(--from-tlandscape); }
.st-header__search-icon { display: var(--below-tlandscape); }

/* ══ КАРКАС КОНТЕНТА ════════════════════════════════════════════════════════
   Wrapper ноды: поля сверху 24, снизу 0, зазор 24 до контента. Сам контент —
   вертикальная стопка, у каждой секции свой нижний отступ (как в нодах),
   поэтому общего gap у стопки нет. */
.st-wrap { padding-block: var(--spacing-24) 0; }

/* Хлебные крошки в одну строку: на ноде 320 они шире фрейма и просто выходят за
   него; в браузере тот же ряд иначе переносится в четыре строки и поднимает
   заголовок на 60px. Одна строка с горизонтальной прокруткой — то же, что в
   макете, но пользоваться можно. */
.st-brc {
  flex-wrap: nowrap;
  margin-bottom: var(--spacing-24);
  overflow-x: auto;
  scrollbar-width: none;
}

.st-brc::-webkit-scrollbar { display: none; }
.st-brc > * { flex: none; }

.st-content { padding-bottom: var(--spacing-60); }

/* ── Заголовок: бейдж дат + H1 ─────────────────────────────────────────────
   Ноды: 1280 — 112 = бейдж 24 + зазор 20 + заголовок 44 + поле 24;
          320 — 120 = бейдж 24 + зазор 12 + заголовок 64 (две строки) + поле 20.
   Кегль тоже разный: 24/32 на мобиле, 36/40 с 576 (нода Mobile Landscape) —
   переключаем интерполяцией токенов по флагу, как это делают блоки. */
.st-h1 {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(var(--spacing-12) + var(--spacing-8) * var(--bp-mlandscape));
  padding-bottom: calc(var(--spacing-20) + var(--spacing-4) * var(--bp-mlandscape));
}

.st-h1__title {
  margin: 0;
  color: var(--color-gray-900);
  font-size: calc(
    var(--text-display-24-size) +
    var(--bp-mlandscape) * (var(--text-display-36-size) - var(--text-display-24-size))
  );
  line-height: calc(
    var(--text-display-24-line-height) +
    var(--bp-mlandscape) * (var(--text-display-36-line-height) - var(--text-display-24-line-height))
  );
}

/* ── Баннер обратного отсчёта ──────────────────────────────────────────────
   Блок banner-countdown как есть; экран меняет ТОЛЬКО фон и арт — в макете
   инстанс перекрашен в --color-purple-500 (у блока свой teal). Модификатор
   живёт под префиксом экрана, глобальный блок не тронут. Затухания (scrim)
   повторяют геометрию блока, но в новый цвет — их в блоке нельзя
   параметризовать, цвет там подставлен напрямую. */
.st-banner { padding-bottom: var(--spacing-24); }

/* Баннер здесь НЕ кликабелен (правка 2026-08-15): вести с него некуда — список
   сезона уже на этой же странице. Поэтому корень баннера — <div>, а не <a>, и
   гасятся оба признака интерактивности блока: курсор-рука и зум иллюстрации на
   наведении. Гасим под префиксом экрана — в самом блоке баннер по-прежнему
   ссылка (описание Figma), и глобальные стили не трогаем. */
.st-banner .bcd { background: var(--color-purple-500); cursor: default; }
.st-banner .bcd:hover .bcd__art { transform: scale(1); }
.st-banner .bcd__scrim--l { background: linear-gradient(to right, transparent 50%, var(--color-purple-500) 100%); }
.st-banner .bcd__scrim--m { background: linear-gradient(to right, var(--color-purple-500) 0%, transparent 50%); }
.st-banner .bcd__scrim--s { background: linear-gradient(to bottom, transparent 50%, var(--color-purple-500) 100%); }

/* ── Виджет персонажа + фильтры ───────────────────────────────────────────
   Нода Filters: виджет, зазор 32, панель фильтров. */
.st-filters {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-32);
}

/* Портрет персонажа приходит из Armory (character-media, ключ avatar) — тот же
   снимок, что показывает completionism. Пока его нет (персонаж не добавлен,
   профиль без портрета, картинка не загрузилась) слот держит иконка профиля,
   чтобы шапка не расползалась. Скрипт переключает состояние атрибутом. */
.st-avatar {
  display: flex;
  align-items: center;
  justify-content: center;
  /* 68 вместо 44 у блока: портрет с Armory — содержательная картинка, а не
     плейсхолдер, и на 44 разглядеть в нём нечего. Правка живёт под префиксом
     экрана, слот блока в других местах остаётся прежним. */
  width: 68px;
  height: 68px;
  background: var(--color-gray-25);
  color: var(--color-gray-400);
}

.st-avatar svg { width: 24px; height: 24px; }

/* С портретом фон и иконка не нужны: картинка закрывает слот целиком
   (object-fit: cover у .cw__avatar img — правило блока). */
.st-avatar[data-avatar="true"] { background: none; }
.st-avatar[data-avatar="true"] svg { display: none; }

/* Раскрытый список реалмов уходил ПОД панель разделов (.st-toolbar): 362 строки
   не помещаются в карточку и заходят на липкую панель ниже.

   Дело не в самом меню — у него z-index: 2 (combobox.css). Виджет персонажа
   несёт класс .bp-container, а он даёт container-type: inline-size, то есть
   layout containment — и это создаёт КОНТЕКСТ НАЛОЖЕНИЯ. Внутри него меню
   поднимается только над соседями по карточке; наружу его z-index не выходит,
   и весь виджет со своим z-index: auto проигрывает панели с z-index: 2.

   Поэтому поднимаем сам виджет, а не меню: 3 — выше панели разделов (2) и не
   выше прижатого к низу таб-бара (.st-tabbar-slot, 4), который по замыслу
   перекрывает всё. Трогать combobox.css нельзя: там та же цифра работает во
   всех остальных местах, а причина расхождения — местная. */
.st-account { position: relative; z-index: 3; }

/* Строки ошибок под полями («такого реалма нет», «введи имя») в макете не
   предусмотрены, и место им отводится по-разному на двух раскладках формы.

   С ПЛАНШЕТА поля стоят в РЯД и выровнены по низу: ошибка обязана быть вне
   потока, иначе поле с ней становится выше соседних и подписи ряда разъезжаются.

   НИЖЕ ПЛАНШЕТА поля идут КОЛОНКОЙ. Вынутая из потока ошибка ложилась в зазор
   между полями (20) и слипалась со следующей подписью — правка 2026-08-15.
   Здесь она стоит в потоке обычным потомком: место занимает сама, а зазор до
   поля ниже остаётся ровно тем же, что и у полей без ошибок.

   Переключает раскладку носитель position из grid.css (числа перелома живут
   только там), а свой отступ от поля гасим на планшетном флаге: в потоке зазор
   4 уже даёт gap самого компонента (.input / .combobox — flex-колонка). */
.st-account .combobox__hint,
.st-account .st-add__field--name .input__hint {
  position: var(--absolute-from-tablet);
  top: 100%;
  left: 0;
  margin-top: calc(var(--spacing-4) * var(--bp-tablet));
}

.st-account .combobox__hint[hidden],
.st-account .st-add__field--name .input__hint[hidden] { display: none; }

.st-account .st-add__field--name { position: relative; }

/* Место под строку ошибки — любой из двух. С планшета строки вынуты из потока,
   поэтому его добавляет форма: ровно высота строки Body/14 и её отступ от поля,
   и зазор до разделителя остаётся тем же, что и без ошибки. Ниже планшета
   ошибка стоит в потоке и место занимает сама — флаг гасит этот запас, иначе
   он добавился бы вторым. */
.st-app[data-form-error="true"] .st-add {
  padding-bottom: calc((var(--text-body-14-line-height) + var(--spacing-4)) * var(--bp-tablet));
}

/* Крестик очистки в поле имени показываем только когда есть что стирать —
   как у комбобокса, где компонент прячет его классом --filled. */
.st-add__field--name:not([data-filled="true"]) .input__clear { display: none; }

/* Крестик у чипса персонажа — кнопка (его надо нажимать), а у компонента это
   span. Гасим стили кнопки браузера, чтобы чипс выглядел как в макете. */
.st-account .chips__cross {
  padding: 0;
  border: 0;
  background: none;
  color: inherit;
  cursor: pointer;
}

/* ── Статусы запроса ───────────────────────────────────────────────────────
   Узлы экрана, в макете их нет. Каждый статус — своя плашка на компоненте
   notification-inline, показывает их data-state; по умолчанию все скрыты.
   Ширину компонента (400 — размер его ноды) снимаем ТОЛЬКО под своим
   префиксом: в колонке виджета плашка обязана вести себя как её ряд, а узкая
   на широкой карточке читалась бы как чужой элемент. */
.st-account .st-status {
  display: none;
  width: auto;
  align-self: stretch;
}

.st-app[data-state="loading"]     .st-loading  { display: block; }
.st-app[data-state="notfound"]    .st-notfound { display: block; }
.st-app[data-state="private"]     .st-private  { display: block; }
.st-app[data-state="offline"]     .st-offline  { display: block; }

/* «Данные неполные» — не состояние запроса, а свойство уже полученных данных,
   поэтому свой атрибут: плашка висит поверх нормального idle. Во время запроса
   и в ошибках она не нужна — там своя строка. */
.st-app[data-partial="true"][data-state="idle"] .st-partial,
.st-app[data-state="ratelimited"] .st-partial { display: block; }

/* Заливка статуса чтения — белая: голубой фон information-25 читался как ещё
   одно сообщение об ошибке, а тут просто идёт работа. Меняем через переменную
   самого компонента, а не свойством background: остальная схема (цвет значка,
   ссылок) остаётся его, штатной. */
.st-account .st-loading { --notif-bg: var(--color-gray-0); }

/* Спиннер в слоте иконки уведомления: у компонента там 16-20px под svg. */
.st-loading .notif__icon .spinner { display: flex; width: 16px; height: 16px; }
.st-loading .notif__icon svg { width: 100%; height: 100%; }

/* Строка «Updated …»: сутки и больше — предупреждение вместо обычной строки.
   Значков в разметке два, лишний гасится — так JS не трогает разметку.

   Селекторы намеренно длиннее необходимого: у блока стоит
   `.cw__updated-icon svg { display: block }`, и правило в один класс ему
   проигрывало по силе — оба значка показывались разом, второй падал строкой
   ниже. Отсюда пара «класс + класс» и «атрибут + атрибут + класс». */
.cw__updated-icon .st-updated__stale { display: none; }

[data-updated-row][data-stale="true"] { color: var(--color-negative-500); }
[data-updated-row][data-stale="true"] .st-updated__fresh { display: none; }
[data-updated-row][data-stale="true"] .st-updated__stale { display: block; }

/* Что видно с персонажем и без него. Форма открывается кнопкой, поэтому у неё
   отдельный hidden, а не эти правила. */
.st-app[data-has-chars="false"] .st-when-chars { display: none; }
.st-app[data-has-chars="true"]:not([data-adding="true"]) .st-when-empty { display: none; }

/* Режим смены персонажа («Change character»): на экране остаётся форма
   заполнения — шапка с текущим персонажем, прогресс и плитки скрыты. Иначе
   форма разворачивалась под карточкой добавленного персонажа и читалась как
   «добавить кого-то к нему». Данные не теряются: выход из режима (Cancel или
   успешное добавление) возвращает их на место.

   Ряд чипсов — исключение: уже загруженные персонажи в этом состоянии нужны,
   иначе переключиться на прежнего можно только заново набрав его имя. В режиме
   смены ряд показывает ВСЕХ (текущего в шапке сейчас не видно), и тычок по
   чипсу и есть возврат к загруженному персонажу.

   Вместе с рядом остаётся и разделитель (`.cw__divider`) — он в макете отделяет
   форму от данных аккаунта, и без него чипсы липли прямо к полям. Показывается
   он ровно тогда же, когда ряд: без персонажей оба гасятся общим правилом по
   data-has-chars. */
.st-app[data-adding="true"] .st-when-chars:not(.cw__chars):not(.cw__divider) { display: none; }

/* ── Чипсы персонажей = переключатель шапки ───────────────────────────────
   В ряду лежат ТОЛЬКО не показанные сейчас персонажи: текущий уехал в шапку,
   его чипс был бы вторым изображением того же самого. Поэтому отдельная метка
   «выбран» не нужна — палитру компонента не трогаем вовсе, ховер у чипса свой,
   штатный (accent-300). Ряд целиком гаснет, когда персонаж один: подпись
   «Characters added:» иначе висела бы над пустотой. */
.st-account .st-char-chip { cursor: pointer; }

/* Текущий персонаж — синий чипс (штатный вид removable у компонента), на него
   не переключаются: он уже в шапке. Помечен data-current, а НЕ aria-disabled —
   последний выключил бы заодно вложенную кнопку-крестик (так его видят и
   скринридеры, и тесты), а удалять текущего надо: других чипсов, чтобы сперва
   переключиться, может и не быть. */
.st-account .st-char-chip[data-current="true"] { cursor: default; }
.st-account .st-char-chip[data-current="true"] .chips__cross { cursor: pointer; }

/* Остальные — белые с обводкой. Значения не выдуманы: это ровно тот вид, что у
   компонента имеет НЕ removable-чипс в состояниях Default и Hover (gray-0 +
   кольцо gray-100, на наведении accent-600). У removable-варианта своей светлой
   ветки нет, поэтому вид собран здесь, под префиксом экрана. */
.st-account .st-char-chip:not([data-current="true"]) {
  background: var(--color-gray-0);
  color: var(--color-gray-900);
  box-shadow: inset 0 0 0 1px var(--color-gray-100);
}

/* Наведение на белый — как у синего: заливка accent-300 и белый текст. Так
   ховер показывает, чем чипс станет после нажатия (текущим, то есть синим), а
   не уводит в отдельный светлый вид. Значение то же, что у компонента в
   `.chips--removable.chips--default:hover`. */
.st-account .st-char-chip:not([data-current="true"]):hover {
  background: var(--color-accent-300);
  color: var(--color-gray-0);
  box-shadow: none;
}

.st-account .cw__chars[hidden] { display: none; }

.st-account .st-char-chip:focus-visible {
  outline: 2px solid var(--color-accent-600);
  outline-offset: 2px;
}

/* Пока идёт запрос или висит ошибка, приглашение «Add your character…» ни к
   чему: статус уже объясняет, что происходит. */
.st-app[data-state="loading"]     .st-when-empty,
.st-app[data-state="notfound"]    .st-when-empty,
.st-app[data-state="private"]     .st-when-empty,
.st-app[data-state="offline"]     .st-when-empty,
.st-app[data-state="ratelimited"] .st-when-empty { display: none; }

.st-add[hidden] { display: none; }

/* ── Панель фильтров ──────────────────────────────────────────────────────
   Нода Filter & Sort: ряд чипсов (56, линия снизу, поле 20) + строка тумблера
   (24 + поле 32). Ряд чипсов на узких ширинах прокручивается — значит обязан
   уходить за поля контейнера (full-bleed из grid.css). */
/* Лента чипсов прокручивается только на мобиле (с планшета шесть чипсов и
   ссылка влезают в контейнер), поэтому и bleed включён только там. Порог именно
   такой: при --bleed-below-desktop лента на планшете вылезала бы отрицательным
   полем ПОД ссылку «Reset» — она сосед по ряду, а не потомок ленты. */
/* Ряд разделов ЛИПКИЙ НА ВСЕХ ШИРИНАХ (правка 2026-08-15; на мобиле так было и
   раньше — нода 36:33174, 320×60). Список сезона длинный: 116 наград, десять
   экранов прокрутки на десктопе. Без липкой панели, чтобы сменить раздел, надо
   было листать наверх — а это единственный способ переключиться между ними.
   Носитель --sticky-below-tablet больше не нужен: липкость одинакова везде, и
   брейкпоинт в ней не участвует.

   ВАЖНО: режим панели (фильтр или якоря) с липкостью больше НЕ связан — скрипт
   спрашивает флаг --bp-tablet, см. README и anchorMode().

   Ряд лежит соседом списка, а НЕ внутри блока с виджетом: липкий элемент не
   выходит за коробку своего родителя, и из .st-filters он уезжал бы вместе с
   виджетом, не дойдя до списка. Отсюда же и собственный отступ сверху — раньше
   его давал gap колонки .st-filters. */
.st-toolbar {
  --bleed: var(--bleed-below-tablet);
  position: sticky;
  top: 0;
  z-index: 2;
  display: flex;
  align-items: center;
  gap: var(--spacing-20);
  margin-top: var(--spacing-32);
  padding-bottom: var(--spacing-20);
  background: var(--color-gray-0);
  /* Разделителя под чипсами НЕТ. Раньше тут стояла inset-линия из старой ноды
     Filter & Sort; после переноса тумблера в соседний узел она оказывалась
     прямо над списком (а без персонажа тумблера и вовсе нет) и читалась как
     лишняя полоса поперёк страницы. Отделять чипсы от списка на широких
     ширинах нечего — они и так стоят на белом; на мобиле эту роль берёт тень
     прилипшей панели. */
}

/* Прилипшее состояние — отдельная нода, у неё СВОИ поля (12 сверху и снизу
   против 0/20 в потоке) и нет разделителя. Класс вешает скрипт: средствами CSS
   «прилип» от «в потоке» не отличить. Фон тянем от края до края экрана (в
   потоке он не нужен — там и так белое).

   full-bleed у ленты чипсов СОХРАНЯЕТСЯ. Пробовал гасить (--bleed: 0) — лента
   упиралась в поля панели, и чипсы обрезались на 20px от края, будто их
   подрезали. Гасить не нужно: панель раздвинута отрицательным полем до краёв
   экрана и возвращает поля себе padding'ом, поэтому отрицательный margin ленты
   доводит её ровно до края экрана — ни пикселем дальше, горизонтальной
   прокрутки страницы не появляется. */
.st-toolbar--stuck {
  /* Сама панель раздвигается на поля контейнера ТОЛЬКО на мобиле: там от этого
     зависит лента разделов (её full-bleed отсчитывается от полей). Фон при этом
     тянет не панель, а её подложка — она шире окна, см. ниже. */
  --stuck-bleed: calc(var(--container-pad, 0px) * (1 - var(--bp-tablet)));
  margin-inline: calc(-1 * var(--stuck-bleed));
  /* Поля 12+12 сняты (правка 2026-08-15): на мобиле они читались как лишняя
     белая полоса над табами и под линией — панель занимала 85px там, где нода
     липкого ряда 320×60 — это высота самих табов. Место в потоке липкий
     элемент занимает своё, поэтому потерянные 20px нижнего поля возвращаем
     margin'ом: иначе список дёргался бы вверх в момент прилипания. */
  margin-bottom: var(--spacing-20);
  padding: 0 var(--stuck-bleed);
}

/* ПОДЛОЖКА ПРИЛИПШЕЙ ПАНЕЛИ — во всю ширину окна.
   Панель лежит внутри .container, а тот ограничен max-width 1440: на широком
   мониторе её фон обрывался по краям контейнера, и список проезжал в этих
   просветах прямо под табами. Раздвинуть саму панель нельзя — она и есть
   контейнерная колонка, её поля держат раскладку и ленту разделов.

   Поэтому фон и тень рисует псевдоэлемент шириной 100vw, отцентрованный по
   панели: контент остаётся в колонке, подложка идёт от края до края экрана.
   Лишние пиксели (100vw шире клиентской области на ширину скроллбара) режет
   .page с overflow-x: clip — тот самый clip, что стоит там ради sticky.

   z-index: -1 внутри панели, у которой свой z-index: 2, — подложка уходит под
   содержимое панели, но не под список: стек-контекст ограничен панелью.

   Разделитель (inset-линия из потока) уступает место тени: она отделяет
   подложку от уезжающего под неё списка лучше линии. */
.st-toolbar--stuck::before {
  content: "";
  position: absolute;
  z-index: -1;
  top: 0;
  bottom: 0;
  left: 50%;
  width: 100vw;
  transform: translateX(-50%);
  background: var(--color-gray-0);
  box-shadow: var(--shadow-12);
}

/* В прилипшем состоянии линия-разделитель компонента tabs гасится: она идёт по
   ширине КОЛОНКИ, а подложка — во всю ширину окна, и на широком мониторе линия
   обрывалась на краю колонки ступенькой. Роль разделителя тут и так играет тень
   подложки — та идёт от края до края. Гасим цветом, а не `border: none`: 1px
   должен остаться в высоте, иначе панель в момент прилипания станет ниже и
   список дёрнется. Подчёркивание выбранного таба не трогаем — это его
   собственный псевдоэлемент, он и должен быть виден. */
.st-toolbar--stuck .tabs__group { border-bottom-color: transparent; }

/* Разделы — компонент tabs. Своё нижнее поле у него гасим: высоту панели
   задаёт сама панель, иначе ряд стал бы на 16px выше. */
.st-toolbar__tabs {
  flex: 1 1 auto;
  min-width: 0;
  padding-bottom: 0;
}

/* Ряд табов — прокручиваемая лента: на узких ширинах разделы не влезают, и без
   прокрутки последние были бы недоступны. Скроллбар прячем (как у остальных
   лент экрана), листается свайпом и мышью (drag-to-scroll).

   width: auto гасит компонентную width: 100% (tabs.css). С ней лента считала
   ширину по контейнеру (335 на экране 375), а отрицательные поля .bleed её
   только СДВИГАЛИ влево: слева лента доходила до края экрана, справа
   обрывалась за 40px до него — табы и линия-разделитель выглядели подрезанными.
   Это тот самый запрет из CLAUDE.md: ширину ленты с full-bleed не фиксируем. */
.st-toolbar__tabs .tabs__group {
  width: auto;
  overflow-x: auto;
  scrollbar-width: none;
}

.st-toolbar__tabs .tabs__group::-webkit-scrollbar { display: none; }

/* Таб в компоненте — span; здесь это кнопка, поэтому гасим её собственные
   стили и оставляем компонентные. */
.st-tab {
  flex: none;
  margin: 0;
  border: 0;
  background: none;
  font-family: inherit;
}

/* Ссылки «Reset» в панели больше нет (правка 2026-08-15): роль сброса играет
   таб «All sections», а тумблер выключается своим же кликом. */

/* 20px от линии разделов до тумблера — ровно столько даёт нижнее поле самой
   панели (--spacing-20). Своего верхнего поля у строки тумблера нет: иначе
   зазор складывался бы из двух отступов (было 20 + 24 = 44). */
.st-toolbar__sorting { padding-block: 0 var(--spacing-32); }

/* Тумблеру нужно, от чего отталкиваться: без персонажа не получено НИЧЕГО,
   фильтр честно никого не прячет, и переключатель выглядит сломанным. Поэтому
   строки с ним до первого персонажа нет вовсе. В режиме смены персонажа
   (data-adding) она остаётся: список под виджетом никуда не делся и фильтр
   продолжает на него действовать — гасить управляющий элемент, оставив эффект,
   было бы хуже. Из-за этого панель фильтров в пустом состоянии ниже макетных
   136px: нода фильтров снята с экрана, где персонаж уже добавлен. */
.st-app[data-has-chars="false"] .st-toolbar__sorting { display: none; }

/* Тумблер (нода Switcher, 141×24): дорожка 36×20, шарик 16. */
.st-toggle {
  display: flex;
  align-items: center;
  gap: var(--spacing-8);
  padding: 0;
  border: 0;
  background: none;
  color: var(--color-gray-900);
  cursor: pointer;
  font-family: inherit;
}

.st-toggle__track {
  display: flex;
  align-items: center;
  width: 36px;             /* структурные габариты прямо из ноды */
  height: 20px;
  padding: 2px;
  border-radius: var(--radius-32);
  background: var(--color-gray-200);
  transition: background .15s ease;
}

.st-toggle__knob {
  width: 16px;
  height: 16px;
  border-radius: var(--radius-32);
  background: var(--color-gray-0);
  transition: transform .15s ease;
}

.st-toggle[aria-checked="true"] .st-toggle__track { background: var(--color-accent-400); }
.st-toggle[aria-checked="true"] .st-toggle__knob  { transform: translateX(16px); }

.st-toggle:focus-visible { outline: 2px solid var(--color-accent-400); outline-offset: 2px; }

/* ── Ленты, которые листаются мышью ───────────────────────────────────────
   Плитки секций, чипсы персонажей, чипсы фильтров и шаги — прокручиваемые
   ряды со спрятанным скроллбаром: на мобиле их листают свайпом, мышью было
   не за что взяться. Скрипт вешает drag-to-scroll, а классы ниже дают понять,
   что ленту можно тянуть. Курсор ставится ТОЛЬКО когда ряду есть куда ехать
   (класс вешает скрипт), иначе на десктопе плитки в ряд получали бы «схватить»
   без единого пикселя прокрутки. */
.st-grabbable { cursor: grab; }

.st-dragging {
  cursor: grabbing;
  scroll-behavior: auto;      /* плавная доводка мешала бы тянуть */
  user-select: none;
}

/* ══ СПИСОК НАГРАД ══════════════════════════════════════════════════════════
   Нода List: секции стопкой с зазором 12, снизу поле 80. */
.st-list {
  display: flex;
  flex-direction: column;
  gap: var(--spacing-12);
  padding-bottom: var(--spacing-80);
  /* Цель таба «All sections» — тот же отступ под панель. */
  scroll-margin-top: calc(var(--st-panel-h, 60px) + var(--spacing-8));
}

/* Секция = карточка. Фон gray-100 + зазор 1px даёт разделители между строками
   ровно так же, как в Figma (там у Group фон gray-100 и gap 1), и не сдвигает
   содержимое строки, как сделал бы border. */
.st-group {
  display: flex;
  flex-direction: column;
  gap: 1px;
  /* Отступ для прыжка по якорю: на мобиле разделы работают навигацией, и без
     этого шапка раздела уезжала бы ПОД липкую панель. Высоту панели меряет
     скрипт и кладёт в --st-panel-h: числом она была привязана к чипсам и
     разъехалась, как только их заменили табами. Меряется ряд табов, а не сама
     панель, — он одинаков в потоке и в прилипшем состоянии, поэтому все 8px
     остаются зазором. Флага ширины здесь больше нет: с 2026-08-15 панель
     липкая на всех ширинах, и уехать под неё раздел может на любой. */
  scroll-margin-top: calc(var(--st-panel-h, 60px) + var(--spacing-8));
  overflow: hidden;
  border: 1px solid var(--color-gray-100);
  border-radius: var(--radius-12);
  background: var(--color-gray-100);
}

/* Шапка секции — обновлённая нода 29:17959 (файл Season, 1222×48): иконки
   больше нет, поля 20/12, заголовок Display/18, счётчик Body/14 приглушённым
   белым (alpha-light-76), фон accent-400 с диагональным декором. Ниже 1024
   поля и кегль прежние (16/12 и Display/16): своей ноды на этой ширине у новой
   шапки пока нет, выдумывать её нельзя, а старые числа хотя бы проверены.
   position: relative — под декор, он абсолютный. */
.st-group__head {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--spacing-12);
  padding: var(--spacing-12)
           calc(var(--spacing-16) + var(--spacing-4) * var(--bp-tlandscape));
  overflow: hidden;
  background: var(--color-accent-400);
  color: var(--color-gray-0);
}

/* Декор (нода 29:17968). Фигура 400×280 стоит по вертикали по центру шапки, а
   по горизонтали прижата к левому краю. В макете она задана иначе — 211px от
   левого края ЛЕВОЙ ПОЛОВИНЫ, а половина отзеркалена, то есть
   left = ширина/2 − 611 — но на ширине самой ноды (1222) эта формула даёт
   ровно 0, так что на десктопе позиция совпадает попиксельно. Пересчёт по
   формуле уводил декор целиком за левый край на узких ширинах (при 375 это
   −439px), поэтому взят край: декор нужен на мобильной тоже, а своей узкой
   ноды у новой шапки пока нет. Лишнее режет overflow: hidden у шапки; под
   текстом декор не мешает — он ниже по z-порядку как первый абсолютный. */
.st-group__pattern {
  position: absolute;
  top: calc(50% - 140px);
  left: 0;
  width: 400px;
  height: 280px;
  pointer-events: none;
}

.st-group__title {
  position: relative;   /* над декором */
  flex: 1 1 auto;
  margin: 0;
  font-size: calc(
    var(--text-display-16-size) +
    var(--bp-tlandscape) * (var(--text-display-18-size) - var(--text-display-16-size))
  );
  line-height: calc(
    var(--text-display-16-line-height) +
    var(--bp-tlandscape) * (var(--text-display-18-line-height) - var(--text-display-16-line-height))
  );
}

.st-group__count {
  position: relative;   /* над декором */
  flex: none;
  margin: 0;
  color: var(--color-alpha-light-76);
  white-space: nowrap;
}

/* ── Строка = блок season-checklist-item ──────────────────────────────────
   Экран не переопределяет блок, а добавляет к нему три вещи под своим
   префиксом: точку перелома, цвет редкости и состояние «получено».

   ТОЧКА ПЕРЕЛОМА. Блок переключает строку в карточку на своём контейнере 1152
   — у него в Figma всего две ноды (1222 и 416), и порог выведен арифметикой.
   У ЭКРАНА ноды есть: на 1024 (строка 950 широкая) это ещё СТРОКА, а на 768
   (726) уже карточка. Значит перелом стоит на 1024 — это флаг --bp-tlandscape,
   и экран передаёт блоку именно его. Контейнер блока при этом гасится: иначе
   .sci мерил бы себя (1150 < 1152) и на десктопе всегда давал бы карточку. */
.st-wrap { --st-row-desktop: var(--bp-tlandscape); }

.st-list .sci { container-type: normal; }
.st-list .sci__inner { --bp-desktop: var(--st-row-desktop); }

/* Рамка арта и название — цветом качества предмета (в макете имена маунтов
   сиреневые, рамка иконки того же цвета). Без данных о качестве остаётся
   штатная серая рамка блока. */
.st-list .sci__icon {
  position: relative;
  border-color: var(--st-quality, var(--color-gray-100));
}

.st-list .sci__title { color: var(--st-name, var(--color-gray-900)); }

/* Арт и название — ссылки на wowhead (они же зацепки их подсказок), но
   выглядеть должны ровно как раньше: цвет наследуют от .sci__title,
   подчёркивания у строки списка нет. */
.st-list .st-row__wh {
  color: inherit;
  text-decoration: none;
}

.st-list .st-row__wh:focus-visible {
  outline: 2px solid var(--color-accent-400);
  outline-offset: 2px;
}

/* Иконки-заглушки, если картинки награды нет */
.st-row__glyph {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: var(--radius-4);
  background: var(--color-gray-25);
  color: var(--color-gray-400);
  box-shadow: none;
}

.st-row__glyph svg { width: 20px; height: 20px; }

/* Аккаунтное достижение — пометка у названия (данные, в макете её нет) */
.st-row__tag {
  margin-left: var(--spacing-8);
  padding-inline: var(--spacing-4);
  border-radius: var(--radius-4);
  background: var(--color-gray-25);
  color: var(--color-gray-500);
  vertical-align: middle;
}

/* «check in game» — метка строки, которую трекер проверить не может: декор и
   внешности Blizzard отдаёт только с логином игрока. Такая строка всегда
   выглядит неполученной, и без метки это читалось бы как ошибка сверки.
   Оформление то же, что у «account-wide», но приглушённее фоном: это не
   свойство награды, а оговорка о данных. */
.st-row__tag--manual {
  background: var(--color-warning-50);
  color: var(--color-warning-600);
}

/* «Stays after the season» — зелёным вместо красного дедлайна */
.st-list .sci__deadline--stays { color: var(--color-positive-500); }

/* Галочка на арте — только у полученного */
.st-row__check {
  position: absolute;
  top: -8px;
  left: -8px;
  display: none;
  align-items: center;
  justify-content: center;
  width: 20px;             /* габарит значка из ноды */
  height: 20px;
  border-radius: var(--radius-32);
  background: var(--color-gray-0);
  color: var(--color-positive-400);
}

.st-row__check svg { width: 18px; height: 18px; }

/* Наведение на строку — заливка gray-25. Правило стоит ДО «получено» намеренно:
   селекторы равной силы (класс + одна привязка в каждом), поэтому выигрывает
   тот, что ниже, — у полученной строки остаётся её зелёная заливка. Отдельный
   зелёный ховер для неё пробовался и снят: между positive-25 и positive-50 в
   шкале ничего нет, и подсветка выходила громче самой строки. */
.st-list .sci { cursor: pointer; transition: background .12s ease; }
.st-list .sci:hover { background: var(--color-gray-25); }

/* Состояние «получено» (в макете — зелёная строка, галочка, погашенный CTA) */
.st-list .sci[data-owned="true"] { background: var(--color-positive-25); }
.st-list .sci[data-owned="true"] .sci__icon { border-color: var(--color-positive-400); }
.st-list .sci[data-owned="true"] .sci__title { color: var(--color-gray-900); }
.st-list .sci[data-owned="true"] .sci__deadline { color: var(--color-gray-500); }
.st-list .sci[data-owned="true"] .st-row__check { display: flex; }

.st-list .sci[data-owned="true"] .st-row__cta {
  background: var(--color-accent-100);
  pointer-events: none;
}

/* ── Фильтры списка ───────────────────────────────────────────────────────
   Только незакрытое: строку прячем лишь когда персонаж добавлен (без него всё
   и так не получено). Секция без незакрытого гаснет целиком — иначе от неё
   оставалась бы синяя шапка над пустотой. */
.st-app[data-only-missing="true"][data-has-chars="true"] .st-list .sci[data-owned="true"] { display: none; }
.st-app[data-only-missing="true"][data-has-chars="true"] .st-group[data-left="0"] { display: none; }

/* Фильтр по разделу — табы. Совпадение раздела с фильтром считает скрипт и
   кладёт его в data-hidden у группы. Раньше пары «фильтр → раздел»
   перечислялись здесь: правило «при любом фильтре, кроме all, прячем все
   группы» плюс список исключений. Список устарел, как только разделов стало
   семь, и чипсы новых разделов гасили ВЕСЬ список — показывающего правила для
   них не было. Сравнить значения двух атрибутов средствами CSS нельзя, поэтому
   сравнение ушло в скрипт, а стилю остался один флаг. */
.st-group[data-hidden="true"] { display: none; }

/* ══ КАК РАБОТАЕТ ТРЕКЕР ════════════════════════════════════════════════════
   Нода How it Works: заголовок (поле 24) + четыре карточки в ряд с зазором 8,
   ниже блока поле 80. Карточка: фон accent-25, радиус 8, поля 24, зазор 20,
   номер 32×32 белый. Ниже планшета карточки становятся столбиком. */
.st-hiw {
  --bleed: var(--bleed-below-desktop);
  padding-bottom: var(--spacing-80);
}

.st-hiw__title { margin: 0 0 var(--spacing-24); color: var(--color-gray-900); }

/* Ниже 1024 карточки шагов НЕ становятся столбиком: в нодах 320/576/768 у
   ленты шагов ширина 1024 при обрезке фреймом — то есть она прокручивается
   вбок. Значит лента обязана уходить за поля контейнера (full-bleed), иначе
   упрётся в них и будет непонятно, что её листают. На 1024 и шире четыре
   карточки делят ширину поровну. */
.st-steps {
  display: flex;
  gap: var(--spacing-8);
  margin: 0;
  padding: 0;
  overflow-x: auto;
  list-style: none;
  scrollbar-width: none;
}

.st-steps::-webkit-scrollbar { display: none; }

.st-step {
  display: flex;
  /* ниже 1024 — фиксированная карточка ленты (250 из ноды),
     с 1024 — растягивается: grow/shrink включает флаг, basis обнуляется */
  flex: var(--bp-tlandscape) var(--bp-tlandscape) calc(250px * (1 - var(--bp-tlandscape)));
  flex-direction: column;
  gap: var(--spacing-20);
  padding: var(--spacing-24);
  border-radius: var(--radius-8);
  background: var(--color-accent-25);
}

.st-step__num {
  display: flex;
  flex: none;
  align-items: center;
  justify-content: center;
  width: 32px;             /* габарит из ноды */
  height: 32px;
  border-radius: var(--radius-8);
  background: var(--color-gray-0);
  color: var(--color-gray-800);
}

.st-step__text { margin: 0; color: var(--color-gray-800); }

/* ══ SEO ════════════════════════════════════════════════════════════════════ */
.st-seo { padding-bottom: var(--spacing-80); }
.st-seo__title { margin: 0 0 var(--spacing-24); color: var(--color-gray-900); }
.st-seo__text { margin: 0; color: var(--color-gray-900); }

/* ══ FAQ ════════════════════════════════════════════════════════════════════
   Нода FAQ: заголовок со счётчиком (поле 20) + список аккордеонов, снизу 60. */
.st-faq { padding-bottom: var(--spacing-60); }

.st-faq__title {
  display: flex;
  align-items: flex-start;
  gap: var(--spacing-8);
  margin: 0 0 var(--spacing-20);
  color: var(--color-gray-900);
}

.st-faq__list { display: flex; flex-direction: column; }

/* ══ ТАБ-БАР ════════════════════════════════════════════════════════════════
   Блок tab-bar с медиа-вкладками. В нодах 1024/1280/1920 его нет — гасим
   флагом, как это делает главная. Панель прижата к низу окна. */
.st-tabbar-slot { position: sticky; bottom: 0; z-index: 4; }

.st-tabbar { display: var(--below-tlandscape); }
