/* ==========================================================================
   Easy Logic — components.css
   Самостоятельные блоки интерфейса по БЭМ: .block, .block__element,
   .block--modifier. Каждый блок не знает, где он стоит, и не задаёт себе
   внешних отступов — расстановкой занимается секция вокруг него.
   ========================================================================== */

/* ==========================================================================
   1. СКИП-ССЫЛКА
   Первый элемент страницы. Не видна, пока на неё не перейдут клавишей Tab.
   Позволяет человеку без мыши перескочить меню и попасть сразу к тексту.
   ========================================================================== */

.skip-link {
  position: absolute;
  top: var(--space-3);
  left: var(--space-3);
  z-index: 200;
  padding: var(--space-3) var(--space-5);
  border-radius: var(--r-pill);
  background-color: var(--c-blue-700);
  color: #FFFFFF;
  font-family: var(--ff-head);
  font-weight: 700;
  text-decoration: none;
  transform: translateY(-200%);
  transition: transform var(--t-fast);
}

.skip-link:focus {
  transform: translateY(0);
  color: #FFFFFF;
}

/* Область содержимого получает фокус после перехода по скип-ссылке.
   Обводить рамкой половину страницы незачем — человек и так увидит,
   что попал в начало текста. */
.page__main:focus {
  outline: none;
}

/* ==========================================================================
   2. КНОПКИ
   ========================================================================== */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 2px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
  line-height: 1.2;
  text-align: center;
  text-decoration: none;
  white-space: nowrap;
  transition: transform var(--t-fast), box-shadow var(--t-fast),
    background-color var(--t-fast), color var(--t-fast), border-color var(--t-fast);
}

.btn .icon {
  width: 1.125em;
  height: 1.125em;
}

/* --- Основная: залитая градиентом ----------------------------------------- */

.btn--primary {
  background-image: var(--grad-brand-deep);
  color: #FFFFFF;
  box-shadow: var(--sh-sm);
}

.btn--primary:hover {
  color: #FFFFFF;
  transform: translateY(-2px);
  box-shadow: var(--sh-lg);
}

.btn--primary:active {
  transform: translateY(0);
}

/* --- Вторичная: контурная -------------------------------------------------- */

.btn--secondary {
  border-color: var(--c-blue-500);
  background-color: var(--c-bg);
  color: var(--c-blue-700);
}

.btn--secondary:hover {
  background-color: var(--c-blue-100);
  color: var(--c-blue-700);
  transform: translateY(-2px);
}

.btn--secondary:active {
  transform: translateY(0);
}

/* --- Прозрачная ------------------------------------------------------------ */

.btn--ghost {
  color: var(--c-ink);
}

.btn--ghost:hover {
  background-color: var(--c-bg-soft);
  color: var(--c-blue-700);
}

/* --- Размеры --------------------------------------------------------------- */

/* Крупная кнопка на телефоне чуть скромнее: там она почти всегда занимает
   всю ширину строки и без лишних 20 px по высоте читается так же.
   Цель по доступности — не меньше 44 px на палец — выполняется с запасом. */
.btn--lg {
  padding: var(--space-3) var(--space-5);
  font-size: var(--fs-small);
}

@media (min-width: 768px) {
  .btn--lg {
    padding: var(--space-4) var(--space-6);
    font-size: var(--fs-body);
  }
}

.btn--block {
  width: 100%;
}

/* --- Кнопка-иконка --------------------------------------------------------- */

.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-sm);
  color: var(--c-ink-soft);
  transition: background-color var(--t-fast), color var(--t-fast);
}

.icon-btn:hover {
  background-color: var(--c-bg-soft);
  color: var(--c-blue-700);
}

.icon-btn .icon {
  width: 1.375rem;
  height: 1.375rem;
}

/* ==========================================================================
   3. МЕТКА
   Класс называется chip, а не tag: Prism помечает теги HTML классом
   token tag, и правило .tag из этого файла превращало каждый тег внутри
   блока кода в голубую пилюлю. Имена БЭМ-блоков не должны совпадать
   с именами токенов подсветки — они попадают в одну и ту же страницу.
   ========================================================================== */

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--r-pill);
  background-color: var(--c-bg-tint);
  color: var(--c-blue-700);
  font-family: var(--ff-head);
  font-size: var(--fs-tiny);
  font-weight: 700;
  letter-spacing: 0.02em;
}

.chip--green {
  background-color: var(--c-green-100);
  color: var(--c-green-700);
}

.chip--soft {
  background-color: var(--c-bg-soft);
  color: var(--c-ink-soft);
}

/* ==========================================================================
   4. КАРТОЧКА
   Базовая карточка, от неё наследуются карточки треков и этапов.
   ========================================================================== */

.card {
  position: relative;
  display: flex;
  flex-direction: column;
  padding: var(--space-6);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background-color: var(--c-bg);
  transition: transform var(--t-base), box-shadow var(--t-base),
    border-color var(--t-base);
}

.card__title {
  font-family: var(--ff-head);
  font-size: var(--fs-h4);
  font-weight: 700;
  line-height: 1.3;
}

/* Заголовок карточки выглядит одинаково, лежит в нём ссылка или нет.
   Без этого правила ссылка внутри заголовка забирала себе общий стиль
   ссылок — синий цвет и подчёркивание, — и карточки этапов выбивались
   из ряда карточек треков, где заголовок просто текст. Кликабельность
   при этом не теряется: вся карточка остаётся кнопкой благодаря
   .card__link::after, а цвет меняется при наведении. */
.card__title a {
  color: inherit;
  text-decoration: none;
}

.card--link:hover .card__title a {
  color: var(--c-blue-700);
}

.card__text {
  margin-top: var(--space-3);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

/* Карточка целиком кликабельна: ссылка растягивается на всю площадь.
   При этом в разметке остаётся настоящая ссылка с текстом — она видна
   программе чтения экрана и открывается правой кнопкой как обычная. */
.card__link::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
}

/* Наведение отмечается рамкой и подложкой, без подъёма и цветной тени.
   Когда на странице подряд идут три ряда карточек, подпрыгивающая тень
   у каждой превращается в шум: глазу не за что зацепиться, всё одинаково
   заметное. Яркой на главной остаётся одна вещь — первый экран. */
.card--link:hover {
  border-color: var(--c-blue-300);
  background-color: var(--c-bg-tint);
}

/* Когда фокус попал на ссылку внутри, подсветить карточку целиком */
.card--link:focus-within {
  border-color: var(--c-blue-500);
}

/* Иконка в шапке карточки */
.card__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 52px;
  height: 52px;
  margin-bottom: var(--space-5);
  border-radius: var(--r-md);
  background-image: var(--grad-soft);
  color: var(--c-blue-700);
}

.card__icon .icon {
  width: 1.625rem;
  height: 1.625rem;
}

/* ==========================================================================
   5. ПЕРВЫЙ ЭКРАН
   ========================================================================== */

/* Отступы первого экрана на узком экране меньше: каждый лишний десяток
   пикселей сверху отодвигает кнопки за нижний край. На широком экране
   места хватает, и воздух возвращается. */
.hero {
  position: relative;
  overflow: hidden;
  padding-block: var(--space-6) var(--space-8);
}

@media (min-width: 1280px) {
  .hero {
    padding-block: var(--space-8) var(--space-9);
  }
}

/* Мягкие цветные пятна на фоне. Чисто декоративные, поэтому вынесены в
   псевдоэлементы: в разметке им делать нечего. */
.hero::before,
.hero::after {
  content: "";
  position: absolute;
  z-index: -1;
  border-radius: 50%;
  filter: blur(70px);
  opacity: 0.5;
  pointer-events: none;
}

.hero::before {
  top: -180px;
  right: -120px;
  width: 460px;
  height: 460px;
  background-color: var(--c-blue-100);
}

.hero::after {
  bottom: -220px;
  left: -160px;
  width: 420px;
  height: 420px;
  background-color: var(--c-green-100);
}

.hero__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-7);
  align-items: center;
}

/* Расстояния внутри первого экрана на телефоне заметно меньше: из них
   складывается то, дотянутся кнопки до нижнего края или нет. На экране
   пошире воздух возвращается — там он ничего не стоит. */
.hero__title {
  margin-top: var(--space-4);
}

@media (min-width: 768px) {
  .hero__title {
    margin-top: var(--space-5);
  }
}

.hero__accent {
  background-image: var(--grad-brand-deep);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.hero__lead {
  max-width: 52ch;
  margin-top: var(--space-4);
  color: var(--c-ink-soft);
  font-size: var(--fs-lead);
  /* Абзац длинный, и на телефоне он занимает девять строк. Межстрочный
     интервал здесь чуть плотнее общего 1.7 — читаемость не страдает,
     а высота блока падает почти на треть строки на каждой строке. */
  line-height: 1.55;
}

.hero__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
}

.hero__meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3) var(--space-5);
  margin-top: var(--space-5);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

@media (min-width: 768px) {
  .hero__lead {
    margin-top: var(--space-5);
    line-height: var(--lh-body);
  }

  .hero__actions,
  .hero__meta {
    margin-top: var(--space-6);
  }
}

.hero__meta-item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.hero__meta-item .icon {
  color: var(--c-green-700);
}

/* Блок кода идёт ПОСЛЕ текста — так, как он стоит в разметке. Раньше на
   этом месте была картинка, её поднимали наверх через order: -1, и первый
   экран телефона занимало одно украшение: слоган, пояснение и кнопки
   уезжали вниз на треть страницы.

   Сообщение первого экрана — заголовок и две кнопки, они и должны попасть
   на первый экран. Код ждёт своей очереди ниже и ограничен по ширине: на
   всю ширину он бы снова съел половину экрана. */
.hero__art {
  width: min(100%, 420px);
  margin-inline: auto;
}

@media (min-width: 768px) {
  .hero__art {
    width: min(100%, 520px);
  }
}

/* Две колонки включаются только с 1280 px, а не с 1024.
   На 1024 текстовая колонка получалась шириной около 460 px: заголовок
   ломался на четыре строки, пояснение — на семь, и кнопки снова уезжали
   за нижний край. Одна широкая колонка на таком экране показывает
   больше смысла, чем две узкие. */
@media (min-width: 1280px) {
  .hero__inner {
    grid-template-columns: 1.05fr 1fr;
    gap: var(--space-8);
  }

  .hero__art {
    width: 100%;
  }
}

/* ==========================================================================
   6. КОД НА ПЕРВОМ ЭКРАНЕ
   Не иллюстрация сайта, а сам сайт: кусок разметки этой же страницы.
   Оформление блока — тёмная тема из assets/vendor/prism, поэтому здесь
   только рамка вокруг него и подпись.
   ========================================================================== */

.code-peek {
  overflow: hidden;
  border-radius: var(--r-lg);
  background-color: var(--code-bg);
  box-shadow: var(--sh-md);
}

.code-peek__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-5);
  border-bottom: 1px solid var(--code-border);
  color: var(--code-comment);
  font-family: var(--ff-mono);
  font-size: var(--fs-tiny);
}

/* Скругление уже есть у обёртки; у самого pre оно даёт двойной контур */
.code-peek__body {
  max-height: 21rem;
  border-radius: 0;
}

.code-peek__note {
  padding: var(--space-4) var(--space-5);
  border-top: 1px solid var(--code-border);
  color: var(--code-comment);
  font-size: var(--fs-tiny);
  line-height: 1.6;
}

/* ==========================================================================
   7. ТРЕКИ «С ЧЕГО НАЧАТЬ»
   ========================================================================== */

.tracks__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

@media (min-width: 768px) {
  .tracks__grid {
    grid-template-columns: repeat(3, 1fr);
  }
}

.track-card__steps {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--c-border);
  list-style: none;
}

.track-card__step {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

.track-card__step .icon {
  width: 1.125rem;
  height: 1.125rem;
  margin-top: 0.25em;
  color: var(--c-green-700);
}

/* margin-top: auto прижимает ссылку к низу карточки: свободное место
   уходит в отступ сверху. Карточки в ряду растянуты на одну высоту, но
   текста в них разное количество — без этого ссылки стояли бы вразнобой.
   Пустой элемент-распорка в разметке для этого не нужен. */
.track-card__go {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-top: auto;
  padding-top: var(--space-5);
  color: var(--c-blue-700);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
  text-decoration: none;
}

.card--link:hover .track-card__go {
  text-decoration: underline;
}


/* ==========================================================================
   8. ЭТАПЫ
   На телефоне — карточки в столбик. С планшета шесть этапов выстраиваются
   в путь: одна колонка, номера-вехи на общей линии, строки без рамок.
   Содержание раздела — последовательность, а сетка из шести одинаковых
   карточек читалась как набор равнозначных пунктов.
   ========================================================================== */

.stages__grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-5);
}

.stage-card {
  gap: 0;
}

.stage-card__top {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-bottom: var(--space-5);
}

/* position: relative поднимает номер над линией-дорожкой: без него
   абсолютно позиционированная линия рисуется поверх обычных элементов
   и перечёркивает цифры. */
.stage-card__num {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background-image: var(--grad-brand-deep);
  color: #FFFFFF;
  font-family: var(--ff-head);
  font-size: 1.125rem;
  font-weight: 800;
}

/* Строка с числом страниц прижата к низу карточки — по той же причине,
   что и ссылка в карточке трека: в ряду карточки одной высоты, а описания
   разной длины, и без этого подписи стояли бы на разных уровнях. */
.stage-card__pages {
  margin-top: auto;
  padding-top: var(--space-5);
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
}

@media (min-width: 768px) {
  .stages__grid {
    position: relative;
    gap: 0;
  }

  /* Линия-дорожка проходит через центры номеров: 24 px отступа строки
     плюс половина номера. Сверху и снизу она подрезана на высоту первого
     и последнего номера, чтобы не торчать за крайними вехами. */
  .stages__grid::before {
    content: "";
    position: absolute;
    top: calc(var(--space-6) + 22px);
    bottom: calc(var(--space-6) + 22px);
    left: calc(var(--space-5) + 21px);
    width: 2px;
    background-image: linear-gradient(180deg, var(--c-blue-300), var(--c-green-300));
  }

  /* Три колонки: веха, текст, число страниц у правого края — как в
     оглавлении книги, где номер страницы стоит напротив названия главы. */
  .stage-card {
    display: grid;
    grid-template-columns: 44px minmax(0, 1fr) auto;
    column-gap: var(--space-5);
    align-items: start;
    padding: var(--space-6) var(--space-5);
    border: 0;
    border-radius: var(--r-md);
    background-color: transparent;
  }

  /* Номер уходит в левую колонку и стоит на всю высоту строки */
  .stage-card__top {
    grid-column: 1;
    grid-row: 1 / span 2;
    margin-bottom: 0;
  }

  .stage-card .card__title {
    grid-column: 2;
    grid-row: 1;
  }

  .stage-card .card__text {
    grid-column: 2;
    grid-row: 2;
  }

  /* Строка текста в путь укладывается во всю ширину экрана, а читать
     строки длиной под 150 знаков невозможно — держим предел в 68 знаков,
     как и в остальном тексте сайта. */
  .stage-card .card__text {
    max-width: var(--measure);
  }

  /* В карточке подпись прижималась к низу через margin-top: auto; здесь
     она встаёт в свою колонку напротив заголовка, и отступ не нужен. */
  .stage-card__pages {
    grid-column: 3;
    grid-row: 1;
    margin-top: 0;
    padding-top: 0.35rem;
  }
}

/* ==========================================================================
   9. БЛОК ОБ АВТОРЕ
   ========================================================================== */

.about-author__inner {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-6);
  align-items: center;
  padding: var(--space-7);
  border: 1px solid var(--c-border);
  border-radius: var(--r-lg);
  background-color: var(--c-bg);
}

@media (min-width: 768px) {
  .about-author__inner {
    grid-template-columns: auto 1fr;
    gap: var(--space-7);
    padding: var(--space-8);
  }
}

.about-author__avatar {
  width: 112px;
  height: 112px;
  border-radius: 50%;
}

.about-author__text {
  max-width: var(--measure);
  margin-top: var(--space-4);
  color: var(--c-ink-soft);
}

.about-author__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-top: var(--space-5);
  list-style: none;
}

/* ==========================================================================
   10. ПРИЗЫВ ПЕРЕЙТИ ДАЛЬШЕ
   ========================================================================== */

.cta__inner {
  position: relative;
  overflow: hidden;
  padding: var(--space-8) var(--space-6);
  border-radius: var(--r-lg);
  background-image: var(--grad-brand-deep);
  color: #FFFFFF;
  text-align: center;
}

.cta__title {
  color: #FFFFFF;
}

.cta__text {
  max-width: 54ch;
  margin: var(--space-4) auto 0;
  color: rgba(255, 255, 255, 0.92);
  font-size: var(--fs-lead);
}

.cta__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  justify-content: center;
  margin-top: var(--space-6);
}

/* Белая кнопка на цветной подложке */
.btn--on-brand {
  background-color: var(--c-bg);
  color: var(--c-blue-700);
}

.btn--on-brand:hover {
  background-color: var(--c-blue-100);
  color: var(--c-blue-700);
  transform: translateY(-2px);
}

/* Контурная кнопка на цветной подложке */
.btn--on-brand-outline {
  border-color: rgba(255, 255, 255, 0.7);
  color: #FFFFFF;
}

.btn--on-brand-outline:hover {
  background-color: rgba(255, 255, 255, 0.14);
  border-color: #FFFFFF;
  color: #FFFFFF;
}

/* На цветной подложке синяя обводка фокуса не видна — делаем белую */
.cta__inner :focus-visible {
  outline-color: #FFFFFF;
}

/* ==========================================================================
   11. ХЛЕБНЫЕ КРОШКИ
   Путь от главной до текущей страницы. Последний пункт — не ссылка:
   вести на страницу, на которой уже находишься, незачем.
   ========================================================================== */

.breadcrumbs__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--fs-tiny);
}

.breadcrumbs__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  color: var(--c-ink-soft);
}

/* Разделитель нарисован уголком из границ, а не символом в разметке:
   программа чтения экрана произносила бы «слэш» между каждым пунктом. */
.breadcrumbs__item + .breadcrumbs__item::before {
  content: "";
  width: 5px;
  height: 5px;
  border-top: 1.5px solid var(--c-ink-muted);
  border-right: 1.5px solid var(--c-ink-muted);
  transform: rotate(45deg);
}

.breadcrumbs__link {
  color: var(--c-ink-soft);
  text-decoration: none;
}

.breadcrumbs__link:hover {
  color: var(--c-blue-700);
  text-decoration: underline;
}

.breadcrumbs__link .icon {
  width: 1rem;
  height: 1rem;
  vertical-align: -0.18em;
}

/* Текущая страница выделена цветом и весом, а не одним лишь отсутствием
   ссылки: на глаз должно быть видно, где ты находишься. */
.breadcrumbs__current {
  color: var(--c-ink);
  font-weight: 500;
}

/* ==========================================================================
   12. ШАПКА ВНУТРЕННЕЙ СТРАНИЦЫ
   Светлая полоса под шапкой сайта: крошки, заголовок, пояснение.
   Отделяет внутреннюю страницу от главной с первого взгляда.
   ========================================================================== */

.page-header {
  padding: var(--space-5) 0 var(--space-7);
  border-bottom: 1px solid var(--c-border);
  background-image: var(--grad-soft);
}

.page-header__title {
  margin-top: var(--space-5);
}

.page-header__lead {
  max-width: var(--measure);
  margin-top: var(--space-4);
  color: var(--c-ink-soft);
  font-size: var(--fs-lead);
}

/* --- Тёмная шапка хаба ----------------------------------------------------
   Вход в раздел — единственное место страницы, которому позволено быть
   ярким (раздел 5.6 спецификации). Светлый градиент на всю ширину эту
   роль не тянет: он одинаково бледный и справа от текста просто пусто.
   Тёмное поле работает и как обложка раздела, и как фон для линии
   маршрута — светящиеся вехи на белом выглядели бы блёкло.
   Внутренние страницы остаются светлыми: разница видна при переходе. */

.page-header--dark {
  position: relative;
  overflow: hidden;
  border-bottom: 0;
  background-color: var(--c-ink-deep);
  background-image: none;
}

/* Два цветных пятна в правой части: там, где раньше был пустой градиент.
   Радиальные, сильно размытые — это подсветка, а не фигура. */
.page-header--dark::before,
.page-header--dark::after {
  content: '';
  position: absolute;
  z-index: 0;
  border-radius: 50%;
  pointer-events: none;
}

.page-header--dark::before {
  top: -30%;
  right: -6%;
  width: 46rem;
  height: 46rem;
  background-image: radial-gradient(circle, rgba(46, 139, 255, 0.20) 0%, rgba(46, 139, 255, 0) 62%);
}

.page-header--dark::after {
  right: 12%;
  bottom: -60%;
  width: 34rem;
  height: 34rem;
  background-image: radial-gradient(circle, rgba(95, 214, 138, 0.16) 0%, rgba(95, 214, 138, 0) 64%);
}

/* Дрейф пятен -------------------------------------------------------------
   Пятна еле заметно плывут и дышат. Смысл — снять ощущение мёртвой
   картинки: обложка тёмная и занимает треть первого экрана, а любое
   движение в ней читается как «страница живая», даже когда его не
   разглядывают. Амплитуда мелкая (до 4% ширины), а циклы — 17 и 23
   секунды: разные и взаимно простые, поэтому пятна не ходят в ногу
   и глаз не ловит повторяющийся ритм. Скорость выбрана пробой: вдвое
   медленнее движение переставало читаться совсем.
   Только transform: браузер двигает готовый слой, перерисовки нет.
   При prefers-reduced-motion правило из base.css гасит это целиком. */

@keyframes spot-drift-blue {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-4%, 3%, 0) scale(1.09); }
}

@keyframes spot-drift-green {
  from { transform: translate3d(0, 0, 0) scale(1.06); }
  to   { transform: translate3d(5%, -4%, 0) scale(1); }
}

.page-header--dark::before {
  animation: spot-drift-blue 17s ease-in-out infinite alternate;
}

.page-header--dark::after {
  animation: spot-drift-green 23s ease-in-out infinite alternate;
}

/* Содержимое поверх пятен */
.page-header--dark > .container {
  position: relative;
  z-index: 1;
}

/* --- Внутренняя страница: та же обложка, вполголоса ----------------------
   Хаб остаётся самым громким входом в раздел: у него два пятна и крупные
   вехи с названиями. Внутри этапа пятно одно и поменьше, шапка ниже.
   Одинаковый приём разной громкости читается как уровень вложенности. */

.page-header--inner {
  padding-bottom: var(--space-6);
}

.page-header--inner::before {
  top: -20%;
  right: -2%;
  width: 34rem;
  height: 34rem;
}

.page-header--inner::after {
  display: none;
}

/* Проход пятна по внутренней обложке ---------------------------------------
   Пятно тут одно, и дрейф на месте ему не идёт: двигать нечему спорить.
   Поэтому оно медленно идёт от левого края блока к правому и обратно —
   один проход занимает 75 секунд, это около 12 пикселей в секунду на
   широком экране. Ход считается от ширины окна: min() оставляет пятно
   на месте там, где оно шире экрана и ехать некуда (узкие телефоны). */

@keyframes spot-sweep {
  from { transform: translateX(min(0px, calc(100% - 100vw))) scale(1); }
  to   { transform: translateX(0) scale(1.08); }
}

.page-header--inner::before {
  animation: spot-sweep 75s ease-in-out infinite alternate;
}

.page-header--dark .page-header__title {
  color: var(--c-on-deep);
}

.page-header--dark .page-header__lead {
  color: var(--c-on-deep-soft);
}

.page-header--dark .breadcrumbs__link {
  color: var(--c-on-deep-soft);
}

.page-header--dark .breadcrumbs__link:hover {
  color: var(--c-on-deep);
}

.page-header--dark .breadcrumbs__current {
  color: var(--c-on-deep-soft);
}

/* Уголок-разделитель крошек нарисован границами, цвет задаётся отдельно */
.page-header--dark .breadcrumbs__item + .breadcrumbs__item::before {
  border-color: var(--c-on-deep-soft);
}

.page-header--dark :focus-visible {
  outline-color: var(--c-blue-300);
}

/* ==========================================================================
   12а. ЛИНИЯ МАРШРУТА В ШАПКЕ РАЗДЕЛА
   Заголовок обещает шесть этапов, а самих этапов в шапке не видно. Линия
   показывает весь путь одной строкой: это и оглавление раздела, и навигация
   (щелчок по вехе прокручивает к карточке этапа).

   Прочерчивается один раз при загрузке — движение отмечает появление
   страницы, а не повторяется при каждой прокрутке.
   ========================================================================== */

.route {
  margin-top: var(--space-7);
}

.route__list {
  position: relative;
  display: flex;
  list-style: none;
}

/* Линия проходит от центра первой вехи до центра последней. Вехи делят
   ширину поровну, значит отступ с каждого края — половина одной доли.
   Сколько вех, говорит переменная --n в разметке: на хабе их шесть,
   внутри этапа — по числу его страниц. */
.route__list {
  --n: 6;
}

.route__list::before {
  content: '';
  position: absolute;
  top: 1.125rem;
  right: calc(50% / var(--n));
  left: calc(50% / var(--n));
  height: 2px;
  border-radius: 2px;
  background-image: linear-gradient(90deg, var(--c-blue-500), var(--c-green-500));
  transform: scaleX(0);
  transform-origin: left center;
  animation: route-draw 900ms ease-out forwards;
}

@keyframes route-draw {
  to {
    transform: scaleX(1);
  }
}

.route__item {
  flex: 1 1 0;
  min-width: 0;
}

.route__link {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding-bottom: var(--space-1);
  color: var(--c-on-deep-soft);
  font-size: var(--fs-tiny);
  text-align: center;
  text-decoration: none;
  opacity: 0;
  animation: route-dot-in 400ms ease-out forwards;
  /* Вехи загораются по очереди, вслед за линией */
  animation-delay: calc(160ms + var(--i) * 110ms);
}

@keyframes route-dot-in {
  from {
    opacity: 0;
    transform: translateY(4px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.route__dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.25rem;
  height: 2.25rem;
  border: 2px solid var(--c-blue-300);
  border-radius: 50%;
  background-color: var(--c-ink-deep);
  color: var(--c-on-deep);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
  transition: border-color var(--t-fast), background-color var(--t-fast),
    transform var(--t-fast);
}

.route__link:hover .route__dot,
.route__link:focus-visible .route__dot {
  border-color: var(--c-green-300);
  background-color: #27313E;
  transform: translateY(-2px);
}

.route__link:hover .route__name {
  color: var(--c-on-deep);
}

.route__name {
  transition: color var(--t-fast);
}

/* Страница, на которой человек сейчас находится. Веха залита, а не просто
   обведена: среди одинаковых кружков залитый читается сразу, и это
   работает даже без цвета — форма отличается, не только оттенок. */
.route__link[aria-current="step"] {
  color: var(--c-on-deep);
}

.route__link[aria-current="step"] .route__dot {
  border-color: transparent;
  background-image: var(--grad-brand);
  color: var(--c-on-brand);
}

/* --- Тихий вариант: маршрут внутри этапа ---------------------------------
   Тот же блок уровнем ниже. Вехи мельче, подписи бледнее: шапка
   внутренней страницы не должна спорить с хабом, который служит входом
   в раздел. Разница в громкости — это и есть иерархия. */

.route--stage {
  margin-top: var(--space-6);
}

.route--stage .route__dot {
  width: 1.75rem;
  height: 1.75rem;
  border-width: 1.5px;
  font-size: var(--fs-tiny);
}

.route--stage .route__list::before {
  top: 0.875rem;
}

/* На узком экране шесть подписей не помещаются: остаются только номера.
   Название никуда не девается — оно скрыто визуально, но остаётся
   доступным именем ссылки для программ чтения экрана. */
@media (max-width: 767px) {
  .route {
    margin-top: var(--space-6);
  }

  .route__name {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
    border: 0;
  }
}

/* ==========================================================================
   13. РАЗВЁРНУТАЯ КАРТОЧКА ЭТАПА
   Не то же самое, что stage-card на главной: здесь у карточки есть список
   подстраниц, поэтому она широкая и целиком некликабельная — ссылок внутри
   несколько, и растянутая на всю площадь ссылка перекрыла бы их.
   ========================================================================== */

.stage-list {
  position: relative;
  display: grid;
  gap: var(--space-5);
}

/* Врезка перед списком этапов — отдельный блок, а не часть карточек,
   поэтому расстояние до списка задаётся здесь, а не отступом внутри врезки */
.callout + .stage-list {
  margin-top: var(--space-6);
}

.stage-detail {
  position: relative;
  /* Щелчок по вехе в шапке ведёт сюда якорем: отступ, чтобы карточка
     не пряталась под липкой шапкой сайта */
  scroll-margin-top: 5.5rem;
}

/* Карточка отвечает на наведение подъёмом и мягкой тенью. Раздел 5.6
   запрещает это рядам одинаковых карточек — там подпрыгивающая тень
   у каждой превращается в шум. Здесь карточек шесть, они крупные и идут
   по одной в ряд: подъём отмечает ту, на которую человек смотрит,
   и ничем не спорит с соседями. */
.stage-detail:hover {
  border-color: var(--c-blue-300);
  box-shadow: var(--sh-lg);
  transform: translateY(-3px);
}

/* Карточка, до которой человек долистал: её же отмечает закрашенная
   часть линии слева. Рамка и тень — без сдвига по вертикали: карточки
   сменяют друг друга прямо во время прокрутки, и подъём заставил бы
   страницу дёргаться под курсором. */
.stage-detail.is-current {
  border-color: var(--c-blue-300);
  box-shadow: var(--sh-md);
}

.stage-detail__head {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
}

.stage-detail__num {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-md);
  background-image: var(--grad-brand-deep);
  color: #FFFFFF;
  font-family: var(--ff-head);
  font-size: 1.375rem;
  font-weight: 800;
}

.stage-detail__title {
  font-family: var(--ff-head);
  font-size: var(--fs-h3);
  font-weight: 700;
  line-height: var(--lh-head);
}

.stage-detail__title a {
  color: inherit;
  text-decoration: none;
}

.stage-detail__title a:hover {
  color: var(--c-blue-700);
  text-decoration: underline;
}

/* Строка фактов под заголовком: сколько страниц, есть ли код */
.stage-detail__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  margin-top: var(--space-2);
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
}

.stage-detail__fact {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.stage-detail__fact .icon {
  width: 1rem;
  height: 1rem;
  color: var(--c-ink-muted);
}

/* Тело карточки: слева описание, справа схема этапа. Колонки заданы через
   flex, а не grid: на узком экране схема просто уходит под текст. */
.stage-detail__body {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5) var(--space-6);
  margin-top: var(--space-5);
}

.stage-detail__about {
  flex: 1 1 280px;
  min-width: 0;
}

/* У этапов без подстраниц описание занимает всю ширину карточки, а это
   больше 130 знаков в строке. Предел тот же, что и в остальном тексте. */
.stage-detail__text {
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

.stage-detail__result {
  margin-top: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-left: 3px solid var(--c-green-300);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  background-color: var(--c-green-100);
  font-size: var(--fs-small);
}

/* На зелёной подложке --c-green-700 даёт 3,97:1 при норме 4,5 — проверено
   axe-core. Для текста поверх зелёного фона есть отдельный токен. */
.stage-detail__result-label {
  color: var(--c-green-800);
  font-weight: 600;
}

/* Список подстраниц вынесен из тела карточки отдельным рядом: в правой
   колонке теперь стоит схема этапа, а список в узком столбце был выше
   самой карточки. Внизу он раскладывается в несколько колонок и занимает
   вдвое меньше высоты. */
.stage-detail__pages {
  margin-top: var(--space-5);
}

.stage-detail__pages-title {
  margin-bottom: var(--space-3);
  color: var(--c-ink-soft);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
}

/* min(17rem, 100%) вместо просто 17rem: на узком экране колонка не имеет
   права быть шире самой сетки, иначе список распирает карточку и страница
   едет вбок. Поймано замером на 320 px. */
.stage-detail__list {
  display: grid;
  gap: var(--space-1);
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  list-style: none;
}

.stage-detail__page {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--r-sm);
  color: var(--c-ink);
  font-size: var(--fs-small);
  text-decoration: none;
  transition: background-color var(--t-fast), color var(--t-fast);
}

.stage-detail__page:hover {
  background-color: var(--c-bg-tint);
  color: var(--c-blue-700);
}

.stage-detail__foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--c-border);
}

/* --- Схема этапа ----------------------------------------------------------
   У каждого этапа своя картинка: не иконка «сервер» или «палитра», которая
   пересказывает заголовок, а маленькая схема того, что в этапе происходит.
   Шесть разных рисунков — карточки перестают быть шестью одинаковыми
   плитками, а правая половина у этапов без подстраниц перестаёт пустовать. */

.stage-detail__art {
  flex: 0 1 15rem;
  min-width: 0;
  align-self: center;
}

.stage-art {
  display: block;
  width: 100%;
  height: auto;
}

/* Части схемы собираются, когда карточка въезжает в экран: один раз,
   короткой очередью. Порядок задаётся переменной --i прямо в разметке —
   это единственный случай, когда style в HTML разрешён (правило 2). */
.js .stage-art .stage-art__part {
  opacity: 0;
  transform: translateY(6px);
  transition: opacity var(--t-base), transform var(--t-base);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.js .stage-art.is-visible .stage-art__part {
  opacity: 1;
  transform: none;
}

/* Цвета схем берутся из палитры: контур — цвет рамок, акцент — синий,
   результат этапа — зелёный. Заливки светлые, чтобы схема не спорила
   с текстом рядом. */
.stage-art__line {
  fill: none;
  stroke: var(--c-blue-300);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.stage-art__line--green {
  stroke: var(--c-green-500);
}

.stage-art__box {
  fill: var(--c-bg-soft);
  stroke: var(--c-border);
  stroke-width: 2;
}

.stage-art__box--tint {
  fill: var(--c-bg-tint);
  stroke: var(--c-blue-300);
}

.stage-art__box--green {
  fill: var(--c-green-100);
  stroke: var(--c-green-300);
}

.stage-art__bar {
  fill: var(--c-blue-300);
}

.stage-art__bar--soft {
  fill: var(--c-border);
}

.stage-art__dot {
  fill: var(--c-green-500);
}

.stage-art__mark {
  fill: var(--c-blue-700);
  font-family: var(--ff-mono);
  font-size: 13px;
  font-weight: 700;
}

.stage-art__mark--lg {
  font-size: 28px;
}

@media (min-width: 768px) {
  .stage-detail__num {
    width: 56px;
    height: 56px;
    font-size: 1.625rem;
  }
}

/* --- Путь из шести остановок ----------------------------------------------
   С широкого экрана номера уезжают из карточек на общую линию слева.
   Содержание раздела — последовательность, и по линии это видно сразу,
   без чтения номеров. Заполненная часть показывает, докуда человек
   долистал; её высоту ставит main.js переменной --route-fill.

   До 1024 px линии нет: на узком экране она съела бы ширину, а номер
   остаётся в потоке рядом с заголовком. */

@media (min-width: 1024px) {
  .stage-list {
    padding-left: 4.5rem;
  }

  /* Линия лежит ниже карточек. Без явного z-index заполненная часть
     рисовалась ПОВЕРХ номеров: ::after идёт в порядке наложения после
     содержимого списка, и градиент перечёркивал кружок с цифрой. */
  .stage-list::before,
  .stage-list::after {
    content: '';
    position: absolute;
    z-index: 0;
    left: calc(1.75rem - 1px);
    width: 2px;
    border-radius: 2px;
  }

  /* Серая нитка проходит через все карточки и растворяется внизу:
     обрезанная по нижнему краю линия выглядела бы обрывом. */
  .stage-list::before {
    top: 3.25rem;
    bottom: 0;
    background-image: linear-gradient(
      180deg,
      var(--c-border) 0%,
      var(--c-border) 85%,
      rgba(227, 234, 240, 0) 100%
    );
  }

  .stage-list::after {
    top: 3.25rem;
    height: var(--route-fill, 0px);
    background-image: var(--grad-brand);
  }

  /* Карточка вместе с вынесенным номером — поверх линии */
  .stage-detail {
    z-index: 1;
  }

  .stage-detail__num {
    position: absolute;
    top: var(--space-5);
    left: -4.5rem;
    /* Белое кольцо вокруг кружка: нитка не упирается в скруглённый
       квадрат вплотную. Тенью, а не подложкой-псевдоэлементом —
       подложке пришлось бы уходить за номер по z-index, а там она
       оказывалась и за линией тоже. */
    box-shadow: 0 0 0 6px var(--c-bg);
  }
}

/* ==========================================================================
   14. ВРЕЗКА
   Замечание в стороне от основного текста: подсказка, предупреждение,
   уточнение. Цвет и иконка задаются модификатором.
   ========================================================================== */

.callout {
  display: flex;
  gap: var(--space-4);
  padding: var(--space-5);
  border-left: 4px solid var(--c-blue-500);
  border-radius: var(--r-md);
  background-color: var(--c-bg-tint);
}

.callout__icon {
  flex: none;
  color: var(--c-blue-700);
}

.callout__icon .icon {
  width: 1.375rem;
  height: 1.375rem;
}

.callout__title {
  font-family: var(--ff-head);
  font-size: var(--fs-body);
  font-weight: 700;
}

.callout__text {
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

.callout__title + .callout__text {
  margin-top: var(--space-2);
}

.callout--tip {
  border-left-color: var(--c-green-500);
  background-color: var(--c-green-100);
}

.callout--tip .callout__icon {
  color: var(--c-green-700);
}

.callout--warning {
  border-left-color: var(--c-warn);
  background-color: #FDF4E6;
}

/* Цвет значка предупреждения темнее токена --c-warn: сам токен рассчитан
   на заливку, для иконки на светлом фоне его контраста не хватает. */
.callout--warning .callout__icon {
  color: #A2650F;
}

.callout--note {
  border-left-color: var(--c-border);
  background-color: var(--c-bg-soft);
}

.callout--note .callout__icon {
  color: var(--c-ink-soft);
}

/* ==========================================================================
   16. ТЕКСТ СТАТЬИ
   Отступы между блоками статьи заданы одним правилом «соседям», а не
   каждому тегу отдельно: так между любыми двумя блоками расстояние
   одинаковое, и добавление нового блока ничего не ломает.
   ========================================================================== */

.article > * + * {
  margin-top: var(--space-5);
}

.article > h2 {
  margin-top: var(--space-8);
}

.article > h3 {
  margin-top: var(--space-6);
}

/* Ширину ограничиваем только тексту. Блоки кода, промптов и врезок
   рассчитаны на всю колонку: в них короткие строки и своя вёрстка. */
.article p,
.article ul,
.article ol,
.article dl {
  max-width: var(--measure);
}

.article ul,
.article ol {
  padding-left: 1.25em;
}

.article li + li {
  margin-top: var(--space-2);
}

.article li::marker {
  color: var(--c-blue-500);
}

/* Первый абзац страницы крупнее: он вводит в тему */
.article__intro {
  color: var(--c-ink-soft);
  font-size: var(--fs-lead);
}

/* Определение термина прямо в тексте */
.term {
  border-bottom: 1px dashed var(--c-blue-300);
  cursor: help;
}

/* ==========================================================================
   17. СПИСОК СТРАНИЦ
   Нумерованный перечень: что читать и в каком порядке. Номера здесь
   не украшение — страницы идут последовательно, и порядок имеет значение.
   ========================================================================== */

/* Селектор с двумя классами намеренно: правило .article ol из раздела
   «Текст статьи» весит столько же, сколько одиночный .link-list, и стоит
   выше по файлу — отступ под маркеры остался бы на месте. */
.article .link-list {
  padding-left: 0;
  counter-reset: link;
  list-style: none;
}

/* Страницы этапа — такой же путь, как шесть этапов на хабе, только
   уровнем ниже: номера-вехи на общей линии, строки без рамок. Разделять
   их линейками незачем — порядок уже показан линией. */
.article .link-list {
  position: relative;
}

.article .link-list::before {
  content: '';
  position: absolute;
  top: 1.5rem;
  bottom: 1.5rem;
  left: calc(0.875rem - 1px);
  width: 2px;
  border-radius: 2px;
  /* Хвост после последней вехи растворяется: обрезанная по нижнему краю
     линия читалась бы как обрыв, а длина описаний у страниц разная,
     и точную высоту последней вехи в CSS не вычислить. */
  background-image: linear-gradient(
    180deg,
    var(--c-blue-300) 0%,
    var(--c-green-300) 72%,
    rgba(154, 231, 186, 0) 100%
  );
}

.link-list__item {
  display: grid;
  grid-template-columns: 1.75rem minmax(0, 1fr);
  gap: var(--space-4);
  padding: var(--space-3) 0;
}

/* Номер лежит на линии, поэтому ему нужен непрозрачный фон, белое кольцо
   вокруг и позиционирование: без position линия рисуется поверх цифры —
   непозиционированные элементы всегда ниже позиционированных. */
.link-list__item::before {
  content: counter(link);
  counter-increment: link;
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  background-color: var(--c-bg);
  box-shadow: 0 0 0 4px var(--c-bg), inset 0 0 0 2px var(--c-blue-300);
  color: var(--c-blue-700);
  font-family: var(--ff-head);
  font-size: var(--fs-tiny);
  font-weight: 700;
  line-height: 1;
}

/* Номер занимает первую ячейку сетки, всё остальное идёт во вторую
   колонку. Без явного grid-column описание переносилось на новую строку
   и попадало под номер — в колонку шириной 1,75rem, по одному слову. */
.link-list__link {
  grid-column: 2;
  font-family: var(--ff-head);
  font-size: var(--fs-body);
  font-weight: 700;
  text-decoration: none;
}

.link-list__link:hover {
  text-decoration: underline;
}

.link-list__desc {
  grid-column: 2;
  margin-top: var(--space-2);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

/* ==========================================================================
   18. ТАБЛИЦА
   Обёртка нужна для узких экранов: таблицу нельзя переносить по словам,
   поэтому она получает собственную горизонтальную прокрутку, а страница
   остаётся без неё.
   ========================================================================== */

.table-wrap {
  overflow-x: auto;
  scrollbar-width: thin;
}

.table {
  min-width: 480px;
  font-size: var(--fs-small);
}

.table__caption {
  margin-bottom: var(--space-3);
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
  text-align: left;
}

.table th,
.table td {
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--c-border);
  text-align: left;
  vertical-align: top;
}

.table thead th {
  border-bottom: 2px solid var(--c-border);
  font-family: var(--ff-head);
  font-weight: 700;
}

.table tbody th {
  color: var(--c-ink);
  font-weight: 600;
}

.table td {
  color: var(--c-ink-soft);
}

/* Код внутри строки текста: подложка помогает отличить его от обычных
   слов. Внутри блока кода подложка не нужна — там свой тёмный фон. */
.article :not(pre) > code {
  padding: 0.12em 0.35em;
  border-radius: var(--r-sm);
  background-color: var(--c-bg-tint);
  color: var(--c-ink);
}

/* ==========================================================================
   19. БЛОК КОДА
   Тёмная плашка с именем файла и кнопкой копирования. Оформление самого
   кода лежит в assets/vendor/prism/prism.css — здесь только обвязка.
   ========================================================================== */

.code-block {
  overflow: hidden;
  border-radius: var(--r-md);
  background-color: var(--code-bg);
}

.code-block__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--code-border);
  color: var(--code-comment);
  font-family: var(--ff-mono);
  font-size: var(--fs-tiny);
}

/* Название файла: единственный span прямо в шапке. Ему разрешено сжиматься
   и переноситься, а шапке — сложиться в две строки. Иначе длинное название
   выдавливало кнопку «Копировать» за край, а .code-block обрезает всё,
   что вылезло: на 320 px кнопки просто не было видно. */
.code-block__head > span {
  min-width: 0;
  overflow-wrap: anywhere;
}

.code-block__body {
  border-radius: 0;
}

/* Кнопка копирования живёт на тёмном фоне, поэтому у неё свои цвета,
   а не .btn--secondary: контурная кнопка дизайн-системы рассчитана
   на светлую подложку и на тёмной пропадает. */
.copy-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--code-border);
  border-radius: var(--r-sm);
  background-color: transparent;
  color: var(--code-text);
  font-family: var(--ff-text);
  font-size: var(--fs-tiny);
  cursor: pointer;
  transition: background-color var(--t-fast), border-color var(--t-fast);
}

.copy-btn:hover {
  border-color: var(--c-blue-300);
  background-color: rgba(143, 192, 255, 0.12);
}

.copy-btn .icon {
  width: 1rem;
  height: 1rem;
}

/* Подтверждение показывается тем же цветом, что и строки в подсветке:
   зелёный на тёмном читается и не спорит с палитрой. */
.copy-btn.is-done {
  border-color: var(--code-string);
  color: var(--code-string);
}

.copy-btn:focus-visible {
  outline-color: var(--c-blue-300);
}

/* ==========================================================================
   20. ДИАЛОГ С НЕЙРОСЕТЬЮ
   Две вариации одного блока: мой запрос и её ответ. Ведут себя одинаково —
   содержимое выше 22rem прячется под градиентное затухание, и его
   раскрывает кнопка. Высота ограничена, потому что ответы длинные:
   без ограничения одна страница превращалась бы в километр текста.
   ========================================================================== */

.prompt-card {
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background-color: var(--c-bg-soft);
}

.prompt-card--answer {
  border-left: 4px solid transparent;
  border-image: var(--grad-brand) 1;
  border-image-slice: 1;
  background-color: var(--c-bg-tint);
}

.prompt-card__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--c-border);
}

.prompt-card__avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex: none;
  border-radius: var(--r-sm);
  background-color: var(--c-blue-100);
  color: var(--c-blue-700);
  font-family: var(--ff-head);
  font-size: var(--fs-tiny);
  font-weight: 800;
}

.prompt-card--answer .prompt-card__avatar {
  background-image: var(--grad-brand-deep);
  color: #FFFFFF;
}

.prompt-card__who {
  margin-right: auto;
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
}

.prompt-card__copy {
  border-color: var(--c-border);
  color: var(--c-blue-700);
}

.prompt-card__copy:hover {
  border-color: var(--c-blue-300);
  background-color: var(--c-bg-tint);
}

.prompt-card__copy.is-done {
  border-color: var(--c-green-500);
  color: var(--c-green-800);
}

/* Тело блока: собственный скролл, тонкий и скруглённый */
.prompt-card__body {
  max-height: var(--prompt-max-h);
  overflow-y: auto;
  padding: var(--space-4);
  scrollbar-width: thin;
  scrollbar-color: var(--c-blue-300) transparent;
}

.prompt-card__body::-webkit-scrollbar {
  width: 8px;
}

.prompt-card__body::-webkit-scrollbar-thumb {
  border-radius: var(--r-pill);
  background-color: var(--c-blue-300);
}

.prompt-card__body > * + * {
  margin-top: var(--space-3);
}

.prompt-card__body p,
.prompt-card__body li {
  font-size: var(--fs-small);
}

.prompt-card__body ul,
.prompt-card__body ol {
  padding-left: 1.25em;
}

/* Пока блок свёрнут, нижний край текста растворяется — так видно, что
   дальше есть продолжение. Затухание сделано маской, а не полосой поверх
   текста: полоса перехватывала бы щелчки и мешала выделить последние
   строки. Скролл при этом остаётся: длинный ответ можно читать и не
   раскрывая блок. */
.prompt-card.is-collapsed .prompt-card__body {
  -webkit-mask-image: linear-gradient(180deg, #000 calc(100% - 3rem), transparent 100%);
  mask-image: linear-gradient(180deg, #000 calc(100% - 3rem), transparent 100%);
}

/* Раскрытый блок показывает всё содержимое целиком */
.prompt-card.is-open .prompt-card__body {
  max-height: none;
}

.prompt-card__more {
  display: block;
  width: 100%;
  padding: var(--space-3);
  border: 0;
  border-top: 1px solid var(--c-border);
  background-color: transparent;
  color: var(--c-blue-700);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
  cursor: pointer;
}

.prompt-card__more:hover {
  background-color: var(--c-bg-tint);
}

@media (max-width: 767px) {
  .prompt-card__body {
    max-height: 16rem;
  }
}

/* ==========================================================================
   21. ЧЕК-ЛИСТ «ЧТО ДОЛЖНО ПОЛУЧИТЬСЯ»
   ========================================================================== */

.checklist {
  padding: var(--space-6);
  border: 1px solid var(--c-green-300);
  border-radius: var(--r-md);
  background-color: var(--c-green-100);
}

.checklist__title {
  font-family: var(--ff-head);
  font-size: var(--fs-h4);
  font-weight: 700;
}

.checklist__list {
  margin-top: var(--space-4);
  list-style: none;
}

/* Значок выведен из потока, а не поставлен flex-элементом рядом с текстом:
   во flex-контейнере каждый тег внутри пункта становится отдельным
   элементом сетки. Строка с <code> разваливалась на колонки и уезжала
   за правый край экрана. Здесь текст остаётся обычным инлайновым
   потоком и переносится по словам. */
.checklist__item {
  position: relative;
  padding-left: calc(1.25em + var(--space-3));
  max-width: var(--measure);
  font-size: var(--fs-small);
}

.checklist__item + .checklist__item {
  margin-top: var(--space-3);
}

.checklist__item .icon {
  position: absolute;
  top: 0.15em;
  left: 0;
  color: var(--c-green-800);
}

/* ==========================================================================
   22. ПЕРЕХОД МЕЖДУ СТРАНИЦАМИ
   Единственное место, где стрелка обозначает направление по-настоящему,
   поэтому здесь она уместна.
   ========================================================================== */

.page-nav {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--space-4);
  margin-top: var(--space-8);
  padding-top: var(--space-6);
  border-top: 1px solid var(--c-border);
}

@media (min-width: 600px) {
  .page-nav {
    grid-template-columns: repeat(2, 1fr);
  }
}

.page-nav__link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  color: var(--c-ink);
  text-decoration: none;
  transition: border-color var(--t-fast), background-color var(--t-fast);
}

.page-nav__link:hover {
  border-color: var(--c-blue-300);
  background-color: var(--c-bg-tint);
}

/* Кнопка «дальше» прижимается к правому краю и разворачивает содержимое:
   стрелка справа, текст справа налево. */
.page-nav__link--next {
  justify-content: flex-end;
  text-align: right;
}

.page-nav__link--next .icon {
  order: 1;
}

.page-nav__link .icon {
  flex: none;
  color: var(--c-blue-700);
}

.page-nav__dir {
  display: block;
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
}

.page-nav__title {
  display: block;
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
}

/* ==========================================================================
   23. ПОЯВЛЕНИЕ ПРИ ПРОКРУТКЕ
   Класс ставится в разметке, снимается скриптом, когда блок доходит до
   экрана. Если скрипт не отработал, содержимое всё равно видно —
   поэтому состояние по умолчанию задаётся только при поддержке JS
   (класс js на <html> ставит сам скрипт).
   ========================================================================== */

.js .reveal {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--t-base), transform var(--t-base);
}

.js .reveal.is-visible {
  opacity: 1;
  transform: none;
}

/* ==========================================================================
   24. УСЛОВИЯ РАБОТЫ МАНУАЛА
   Три строки в тёмной шапке: что получится, сколько времени, что нужно
   знать заранее. Маршрута по разделу у мануалов нет — они не идут друг
   за другом, — а место под заголовком пустовать не должно. В отличие от
   надзаголовка капсом (раздел 5.6 спецификации) эти строки сообщают то,
   чего в заголовке нет.
   ========================================================================== */

.manual-meta {
  display: grid;
  gap: var(--space-4);
  max-width: 46rem;
  margin-top: var(--space-6);
}

@media (min-width: 768px) {
  .manual-meta {
    grid-template-columns: repeat(3, minmax(min(11rem, 100%), 1fr));
    gap: var(--space-5);
  }
}

/* Каждая строка начинается отрезком линии: три блока данных, а не абзац */
.manual-meta__item {
  min-width: 0;
  padding-left: var(--space-4);
  border-left: 2px solid rgba(255, 255, 255, 0.16);
}

.manual-meta__label {
  color: var(--c-on-deep-soft);
  font-size: var(--fs-tiny);
}

.manual-meta__value {
  margin-top: var(--space-1);
  color: var(--c-on-deep);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 600;
  line-height: 1.4;
}

/* Ссылки на тёмном: голубой из палитры даёт не меньше 5,9:1 даже поверх
   светлого края цветного пятна. Подчёркивание оставлено — по одному цвету
   ссылку отличить нельзя. */
.manual-meta__value a {
  color: var(--c-blue-300);
}

/* ==========================================================================
   25. ТЕРМИН С ПОЯСНЕНИЕМ
   Слово в тексте, у которого есть короткое определение. Кнопка, а не span:
   пояснение открывается и с клавиатуры, и пальцем на телефоне, где никакого
   наведения нет. Само слово остаётся в строке текста и переносится вместе
   с ней — поэтому display: inline, а не inline-block.
   ========================================================================== */

.glossary-term {
  position: relative;
}

.glossary-term__word {
  display: inline;
  padding: 0;
  border: 0;
  border-bottom: 1px dashed var(--c-blue-300);
  background: none;
  color: inherit;
  font: inherit;
  cursor: help;
}

.glossary-term__word:hover {
  border-bottom-color: var(--c-blue-700);
  color: var(--c-blue-700);
}

/* Пояснение по умолчанию спрятано. Показывается наведением, фокусом
   и щелчком — последнее нужно на телефоне. Сдвиг --gt-shift ставит скрипт,
   когда пояснение упирается в правый край окна. */
.glossary-term__def {
  position: absolute;
  z-index: 5;
  top: calc(100% + var(--space-2));
  left: 0;
  display: none;
  width: max-content;
  max-width: min(22rem, calc(100vw - 2 * var(--gutter)));
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background-color: var(--c-bg);
  box-shadow: var(--sh-md);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  font-weight: 400;
  line-height: 1.5;
  text-align: left;
  transform: translateX(var(--gt-shift, 0));
}

/* Показывается наведением мыши и раскрытием. Правила на :focus-within
   здесь нет намеренно: слово — кнопка, с клавиатуры её открывает Enter,
   а от одного фокуса пояснение появляться не должно. Иначе Esc снимает
   aria-expanded, но пояснение остаётся висеть — фокус-то никуда не ушёл
   (поймано проверкой в браузере). */
.glossary-term:hover .glossary-term__def,
.glossary-term__word[aria-expanded="true"] + .glossary-term__def {
  display: block;
}

/* ==========================================================================
   26. ФОТОРЯД ПОРЯДКА ДЕЙСТВИЙ
   Шаги с картинками: номер, короткое описание, снимок экрана. Номер
   выведен из потока через position: absolute — внутри описания есть
   инлайновый код, и флексом строка развалилась бы на колонки
   (правило 16 раздела 8 спецификации).
   ========================================================================== */

/* Селектор с двумя классами намеренно, как у списка страниц: правило
   .article ol из раздела «Текст статьи» весит больше одиночного класса —
   отступ под маркеры и предел длины строки в 68ch остались бы на месте,
   и снимок экрана потерял бы 124 px ширины (замерено на 1440). */
.article .steps-gallery {
  display: grid;
  gap: var(--space-7);
  max-width: none;
  padding-left: 0;
  counter-reset: shot;
  list-style: none;
}

/* Снимок идёт под описанием, а не рядом с ним. Рядом он получал 420 px
   при своих 1200 — надписи внутри превращались в серые штрихи, замерено
   на 1440 px. Во всю ширину колонки снимок читается, а подробности
   всё равно смотрят в увеличенном виде. */
.steps-gallery__step {
  display: grid;
  gap: var(--space-4);
  counter-increment: shot;
}

.steps-gallery__text {
  position: relative;
  min-width: 0;
  padding-left: calc(2rem + var(--space-3));
}

/* Номер шага. Цифра на градиенте — только --c-on-brand: белый на синем
   конце даёт 3,4:1 (раздел 5.1). line-height: 1 задан явно, иначе цифра
   наследует межстрочный интервал статьи и съезжает вниз. */
.steps-gallery__text::before {
  content: counter(shot);
  position: absolute;
  top: 0;
  left: 0;
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  background-image: var(--grad-brand);
  color: var(--c-on-brand);
  font-family: var(--ff-head);
  font-size: var(--fs-small);
  font-weight: 700;
  line-height: 1;
}

.steps-gallery__title {
  font-size: var(--fs-body);
  line-height: 1.35;
}

.steps-gallery__desc {
  max-width: var(--measure);
  margin-top: var(--space-2);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

/* Снимок экрана — кнопка: по щелчку открывается во всю страницу */
.steps-gallery__shot {
  display: block;
  width: 100%;
  padding: 0;
  overflow: hidden;
  border: 1px solid var(--c-border);
  border-radius: var(--r-md);
  background-color: var(--c-bg-soft);
  cursor: zoom-in;
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}

.steps-gallery__shot:hover {
  border-color: var(--c-blue-300);
  box-shadow: var(--sh-sm);
}

.steps-gallery__shot img {
  display: block;
  width: 100%;
  height: auto;
}

/* Подпись видна всегда, а не только при наведении: на телефоне наведения
   нет, а понять, что картинка увеличивается, нужно и там. */
.steps-gallery__zoom {
  display: block;
  padding: var(--space-2) var(--space-3);
  border-top: 1px solid var(--c-border);
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
  text-align: right;
}

.steps-gallery__shot:hover .steps-gallery__zoom {
  color: var(--c-blue-700);
}

/* ==========================================================================
   27. УВЕЛИЧЕНИЕ КАРТИНКИ
   Разметку окна создаёт lightbox.js. Взят тег <dialog>: он сам держит фокус
   внутри окна, закрывается по Esc и возвращает фокус на кнопку, с которой
   его открыли. Руками всё это заняло бы вдвое больше кода.
   ========================================================================== */

.lightbox {
  width: min(1100px, calc(100vw - 2 * var(--gutter)));
  max-width: 100%;
  max-height: calc(100vh - 2 * var(--space-5));
  padding: 0;
  border: 0;
  background: none;
  overflow: visible;
}

.lightbox::backdrop {
  background-color: rgba(18, 25, 33, 0.86);
}

.lightbox__fig {
  margin: 0;
}

.lightbox__img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-md);
  background-color: var(--c-bg);
}

.lightbox__caption {
  margin-top: var(--space-4);
  color: var(--c-on-deep-soft);
  font-size: var(--fs-small);
  text-align: center;
}

/* Кнопка закрытия сидит в правом верхнем углу самой картинки, а не над ней:
   над картинкой её было бы видно только тогда, когда картинка ниже окна.
   Подложка нужна, чтобы белый значок читался на любом снимке. */
.icon-btn.lightbox__close {
  position: absolute;
  top: var(--space-3);
  right: var(--space-3);
  background-color: rgba(18, 25, 33, 0.72);
  color: var(--c-on-deep);
}

.icon-btn.lightbox__close:hover {
  background-color: rgba(18, 25, 33, 0.9);
  color: var(--c-on-deep);
}

.lightbox :focus-visible {
  outline-color: var(--c-blue-300);
}

/* ==========================================================================
   28. РАЗБОР ТЕГА
   Один тег, разложенный на части: имя, атрибут, содержимое, закрывающая
   пара. Цвета взяты из темы подсветки кода — те же, которыми Prism красит
   код во всех блоках сайта. Так разбор объясняет заодно и раскраску,
   которую читатель видит на каждой странице с примерами.

   Флекса здесь намеренно нет: строка с тегом — живой текст, и каждый
   span внутри флекс-контейнера стал бы отдельным элементом сетки
   (правило 16 спецификации). Части остаются инлайновыми и переносятся
   на узком экране как обычные слова.
   ========================================================================== */

.tag-anatomy {
  padding: var(--space-5);
  border-radius: var(--r-md);
  background-color: var(--code-bg);
}

.tag-anatomy__line {
  color: var(--code-text);
  font-family: var(--ff-mono);
  font-size: clamp(0.9375rem, 0.85rem + 0.4vw, 1.25rem);
  line-height: 2.2;
  overflow-wrap: anywhere;
}

/* Каждая часть подчёркнута своим цветом — тем же, что у строки легенды.
   Подчёркивание, а не подложка: подложка на инлайновом тексте рвётся
   при переносе на вторую строку.

   inline-block с nowrap задаёт, где строке разрешено переноситься: между
   частями можно, внутри части нельзя. Без этого на 320 px перенос попадал
   в середину закрывающего тега и разбор становился нечитаемым. */
.tag-anatomy__part {
  display: inline-block;
  padding-bottom: 2px;
  border-bottom: 2px solid currentColor;
  white-space: nowrap;
}

.tag-anatomy__part--name {
  color: var(--code-tag);
}

.tag-anatomy__part--attr {
  color: var(--code-attr);
}

/* Значение атрибута Prism красит отдельно от его имени — здесь так же,
   иначе подпись под разбором обещала бы цвет, которого в примере нет. */
.tag-anatomy__val {
  color: var(--code-string);
}

.tag-anatomy__part--text {
  color: var(--code-text);
}

.tag-anatomy__part--close {
  color: var(--code-punct);
}

.tag-anatomy__legend {
  display: grid;
  gap: var(--space-4);
  margin-top: var(--space-5);
}

/* Строка легенды: слева кусок разметки, справа пояснение. На телефоне
   колонка одна — иначе оба столбца становятся слишком узкими. */
.tag-anatomy__row {
  display: grid;
  gap: var(--space-1);
  padding-left: var(--space-4);
  border-left: 2px solid currentColor;
}

@media (min-width: 768px) {
  .tag-anatomy__row {
    grid-template-columns: minmax(0, 12rem) minmax(0, 1fr);
    gap: var(--space-4);
    align-items: baseline;
  }
}

.tag-anatomy__row--name {
  color: var(--code-tag);
}

.tag-anatomy__row--attr {
  color: var(--code-attr);
}

.tag-anatomy__row--text {
  color: var(--code-text);
}

.tag-anatomy__row--close {
  color: var(--code-punct);
}

/* Термин наследует цвет строки, пояснение — общий светлый: цветным
   текстом тут набрано одно слово, а не абзац. */
.tag-anatomy__term {
  color: inherit;
  font-family: var(--ff-mono);
  font-size: var(--fs-small);
  overflow-wrap: anywhere;
}

.tag-anatomy__desc {
  max-width: var(--measure);
  color: var(--c-on-deep-soft);
  font-size: var(--fs-small);
  line-height: 1.6;
}

.tag-anatomy__desc strong {
  color: var(--c-on-deep);
}

.tag-anatomy__caption {
  margin-top: var(--space-5);
  padding-top: var(--space-4);
  border-top: 1px solid var(--code-border);
  max-width: var(--measure);
  color: var(--code-comment);
  font-size: var(--fs-tiny);
  line-height: 1.6;
}

/* Правила .article dl и .article p весят больше одиночного класса и задают
   им max-width: 68ch (правило 18 спецификации). Легенде ширина нужна вся:
   предел строки стоит на пояснении, где действительно текст. */
.article .tag-anatomy__legend {
  max-width: none;
}

/* Клавиша на клавиатуре. Нижняя граница толще остальных — так элемент
   читается как колпачок клавиши, а не как инлайновый код рядом. */
.article kbd {
  padding: 0.1em 0.4em;
  border: 1px solid var(--c-border);
  border-bottom-width: 2px;
  border-radius: var(--r-sm);
  background-color: var(--c-bg-soft);
  color: var(--c-ink);
}

/* ==========================================================================
   29. ФОРМА ОБРАТНОЙ СВЯЗИ
   Поле, подпись, подсказка и сообщение об ошибке — один блок .field.
   Подпись стоит над полем, а не слева: на телефоне два столбца всё равно
   сложились бы в один, а прыгающая раскладка читается хуже постоянной.

   Флекса внутри .field нет намеренно: в подписи и подсказке живой текст
   с тегами (правило 16 спецификации). Блоки и так идут друг под другом.
   ========================================================================== */

.contact-form {
  margin-top: var(--space-6);
}

.field + .field {
  margin-top: var(--space-5);
}

.field__label {
  display: block;
  margin-bottom: var(--space-2);
  color: var(--c-ink);
  font-family: var(--ff-head);
  font-weight: 600;
}

/* Ширина поля намекает, сколько в него писать: имя и адрес короткие,
   растягивать их на всю колонку незачем. Сообщение — длинное, но и ему
   предел нужен: строка длиннее --measure читается тяжело. */
.field__control {
  display: block;
  width: 100%;
  max-width: 26rem;
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--c-border);
  border-radius: var(--r-sm);
  background-color: var(--c-bg);
  color: var(--c-ink);
  transition: border-color var(--t-fast), box-shadow var(--t-fast);
}

.field__control::placeholder {
  color: var(--c-ink-muted);
}

.field__control:hover {
  border-color: var(--c-blue-300);
}

/* Кольцо вокруг активного поля — того же цвета, что фокус во всей
   дизайн-системе: одно и то же состояние не должно выглядеть по-разному. */
.field__control:focus {
  border-color: var(--c-blue-500);
  box-shadow: 0 0 0 3px var(--c-blue-100);
  outline: none;
}

/* Поле для сообщения тянут только вниз: изменение ширины ломало бы сетку. */
.field__control--area {
  max-width: var(--measure);
  min-height: 8rem;
  resize: vertical;
}

.field__hint {
  margin-top: var(--space-2);
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

/* Ошибка — не только красным: цвет один отличать состояния не может,
   поэтому рядом стоит значок. */
.field__error {
  margin-top: var(--space-2);
  max-width: var(--measure);
  padding-left: 1.5rem;
  position: relative;
  color: var(--c-error-700);
  font-size: var(--fs-small);
}

.field__error::before {
  content: "!";
  position: absolute;
  left: 0;
  top: 0.1em;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.05rem;
  height: 1.05rem;
  border-radius: var(--r-pill);
  background-color: var(--c-error-700);
  color: var(--c-bg);
  font-size: 0.75rem;
  font-weight: 700;
}

.field__control[aria-invalid="true"] {
  border-color: var(--c-error);
}

.field__control[aria-invalid="true"]:focus {
  box-shadow: 0 0 0 3px rgba(224, 87, 76, 0.18);
}

/* --- Согласие ------------------------------------------------------------
   Здесь флекс уместен: у строки ровно два элемента — квадратик и подпись,
   и подпись целиком лежит в одном теге <label>. */

.field--check {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
  margin-top: var(--space-5);
}

.field__check {
  width: 1.25rem;
  height: 1.25rem;
  margin-top: 0.15rem;
  accent-color: var(--c-blue-500);
}

.field__check-label {
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  line-height: 1.5;
}

/* Ошибка согласия относится ко всей строке, поэтому занимает обе колонки */
.field--check .field__error {
  grid-column: 1 / -1;
  margin-top: 0;
}

.contact-form__actions {
  margin-top: var(--space-6);
}

/* --- Поле-ловушка --------------------------------------------------------
   Человек его не видит, робот заполняет всё подряд. Уводится за край
   экрана, а не прячется через display: none и hidden: часть роботов
   умеет пропускать спрятанные поля, а «сдвинутое» поле для них обычное.
   От программ чтения экрана блок закрыт атрибутом aria-hidden в разметке,
   а из обхода по Tab поле убрано атрибутом tabindex="-1". */

.form-trap {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --- Сообщение о результате ----------------------------------------------
   Появляется после попытки отправить. До неё блок скрыт: пустая плашка
   на странице читается как поломка. */

.contact-form__status {
  margin-top: var(--space-5);
  max-width: var(--measure);
  padding: var(--space-4);
  border-radius: var(--r-md);
  border-left: 3px solid transparent;
  font-size: var(--fs-small);
}

.contact-form__status--ok {
  border-left-color: var(--c-green-500);
  background-color: var(--c-green-100);
  color: var(--c-green-800);
}

.contact-form__status--error {
  border-left-color: var(--c-error-700);
  background-color: var(--c-error-100);
  color: var(--c-error-700);
}

/* ==========================================================================
   30. КАРТОЧКА АВТОРА НА КОНТАКТАХ
   Та же карточка дизайн-системы, только с аватаром и списком фактов.
   ========================================================================== */

.contact-card {
  text-align: left;
}

.contact-card__avatar {
  width: 72px;
  height: 72px;
}

.contact-card__name {
  margin-top: var(--space-3);
  color: var(--c-ink);
  font-family: var(--ff-head);
  font-size: var(--fs-h4);
  font-weight: 700;
}

.contact-card__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-3);
  list-style: none;
  padding-left: 0;
}

.contact-card__text {
  margin-top: var(--space-4);
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

.contact-topics {
  margin-top: var(--space-6);
}

.contact-topics__title {
  font-size: var(--fs-h4);
}

.contact-topics__list {
  margin-top: var(--space-3);
  padding-left: 1.1em;
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  line-height: 1.6;
}

.contact-topics__list li + li {
  margin-top: var(--space-2);
}

.contact-topics__list li::marker {
  color: var(--c-blue-500);
}

/* ==========================================================================
   КАТАЛОГ РАЗДЕЛА
   Сетка карточек на хабе мануалов и теории. Хаб этапов выглядит иначе
   намеренно: там шесть карточек идут по одной в ряд на общей линии,
   потому что этапы — последовательность. Мануалов двенадцать, статей
   пятнадцать, и никакого порядка между ними нет: это каталог, и его
   работа — дать быстро найти нужное. Отсюда плотная сетка внутри
   смысловых групп, а не ряд крупных карточек со схемами.
   ========================================================================== */

/* auto-fill, а не auto-fit: группы разного размера, и auto-fit растягивал
   две карточки последней группы на половину ряда каждую — рядом с такими
   же карточками по трети ряда в группе выше. Одинаковые вещи должны быть
   одного размера, поэтому пустые колонки остаются пустыми.

   min(19rem, 100%) вместо просто 19rem: на узком экране колонка не имеет
   права быть шире самой сетки, иначе карточка распирает страницу и та
   едет вбок (та же беда уже ловилась на .stage-detail__list). */
.catalog {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fill, minmax(min(19rem, 100%), 1fr));
}

/* Карточка каталога тише карточки этапа: меньше отступ, мельче заголовок.
   Наведение отмечается рамкой и подложкой — правила .card--link хватает,
   своего здесь ничего не нужно. */
.catalog .card {
  padding: var(--space-5);
}

.catalog .card__title {
  font-size: var(--fs-h4);
}

/* Строка «что понадобится»: разметка, JavaScript или сниппет CMS.
   Она отвечает на вопрос, которого нет в заголовке, — сколько придётся
   возиться. Отодвинута вниз карточки, чтобы метки в ряду стояли на одной
   линии независимо от длины описания (раздел 8, правило 11). */
.catalog__need {
  margin-top: auto;
  padding-top: var(--space-4);
}

/* Врезка после каталога не прижимается к последнему ряду карточек */
.catalog + .callout {
  margin-top: var(--space-7);
}

/* ==========================================================================
   31. ПОИСК ПО САЙТУ
   Окно открывается кнопкой в шапке, разметку создаёт search.js. Тег тот же,
   что у увеличения картинки, — <dialog>: фокус сам остаётся внутри окна,
   Esc закрывает, фокус возвращается на кнопку.

   Окно прижато к верху, а не стоит по центру: подсказок бывает восемь, и
   список, растущий вниз от поля ввода, не прыгает под курсором.
   ========================================================================== */

.search-modal {
  width: min(680px, calc(100vw - 2 * var(--gutter)));
  max-width: 100%;
  max-height: min(78vh, 640px);
  margin-top: 8vh;
  margin-inline: auto;
  padding: 0;
  border: 0;
  border-radius: var(--r-lg);
  background-color: var(--c-bg);
  box-shadow: var(--sh-lg);
  overflow: hidden;
}

.search-modal[open] {
  display: flex;
  flex-direction: column;
}

.search-modal::backdrop {
  background-color: rgba(18, 25, 33, 0.58);
}

/* --- Строка ввода --------------------------------------------------------- */

.search-modal__form {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--c-border);
}

.search-modal__icon {
  flex: none;
  width: 1.25rem;
  height: 1.25rem;
  color: var(--c-ink-soft);
}

.search-modal__input {
  flex: 1;
  min-width: 0;
  padding: var(--space-2) 0;
  border: 0;
  background: none;
  color: var(--c-ink);
  font-family: inherit;
  font-size: var(--fs-body);
}

.search-modal__input::placeholder {
  color: var(--c-ink-muted);
}

/* Крестик очистки в Chrome дублирует кнопку закрытия и путает: два крестика
   в одной строке, и оба делают разное. */
.search-modal__input::-webkit-search-cancel-button {
  display: none;
}

.search-modal__input:focus {
  outline: none;
}

/* Обводку получает вся строка целиком: поле без рамки, и подсветить надо
   то, что человек видит как одно поле ввода. */
.search-modal__form:focus-within {
  outline: 2px solid var(--c-blue-500);
  outline-offset: -2px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}

.icon-btn.search-modal__close {
  flex: none;
}

/* --- Строка состояния и список ------------------------------------------- */

.search-modal__status {
  margin: 0;
  padding: var(--space-3) var(--space-4);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
}

/* Пустую строку состояния не показываем: иначе под полем ввода висит
   отступ в никуда. :empty здесь как раз про это. */
.search-modal__status:empty {
  display: none;
}

.search-modal__list {
  margin: 0;
  padding: 0 var(--space-3) var(--space-3);
  overflow-y: auto;
  list-style: none;
}

.search-modal__list:empty {
  display: none;
}

.search-result {
  display: block;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--r-md);
  color: inherit;
  text-decoration: none;
  transition: background-color var(--t-fast);
}

.search-result:hover,
.search-result:focus-visible {
  background-color: var(--c-bg-soft);
}

.search-result__section {
  display: block;
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
}

.search-result__title {
  display: block;
  margin-top: 2px;
  color: var(--c-ink);
  font-family: var(--ff-head);
  font-size: var(--fs-body);
  font-weight: 700;
}

.search-result__desc {
  display: block;
  max-width: var(--measure);
  margin-top: var(--space-1);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  line-height: 1.5;
}

/* Найденные буквы: подложка вместо жёлтой заливки браузера по умолчанию —
   жёлтый в палитре сайта не встречается больше нигде. */
.search-modal mark {
  border-radius: 3px;
  padding: 0 2px;
  background-color: var(--c-blue-100);
  color: var(--c-blue-700);
}

/* ==========================================================================
   32. ПРОГРЕСС ОБУЧЕНИЯ
   Отметка «прочитано» внизу учебной страницы, полосы на хабах и галочки
   у прочитанных страниц. Всю разметку создаёт progress.js — в HTML её нет:
   без скрипта отмечать нечем, а полоса, которая всегда на нуле, была бы
   заготовкой без назначения.
   ========================================================================== */

.read-mark {
  margin-top: var(--space-7);
  padding-top: var(--space-5);
  border-top: 1px solid var(--c-border);
}

.read-mark__btn {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-5);
  border: 1px solid var(--c-border);
  border-radius: var(--r-pill);
  background-color: var(--c-bg);
  color: var(--c-ink-soft);
  font-family: inherit;
  font-size: var(--fs-small);
  font-weight: 600;
  transition: border-color var(--t-fast), background-color var(--t-fast),
              color var(--t-fast);
}

.read-mark__btn:hover {
  border-color: var(--c-blue-300);
  color: var(--c-blue-700);
}

/* Нажатое состояние — зелёная подложка. Текст на ней только --c-green-800:
   --c-green-700 рассчитан на белый фон и на зелёном даёт 3,97:1. */
.read-mark__btn[aria-pressed="true"] {
  border-color: var(--c-green-300);
  background-color: var(--c-green-100);
  color: var(--c-green-800);
}

.read-mark__btn[aria-pressed="true"]:hover {
  border-color: var(--c-green-500);
  color: var(--c-green-800);
}

.read-mark__note {
  margin-top: var(--space-3);
  max-width: var(--measure);
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
}

/* --- Полоса ---------------------------------------------------------------
   Доля закрашенного приходит из скрипта переменной --progress. */

.progress-bar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Ширину ограничиваем, как и строку текста: растянутая на 1200 px
     ниточка читается как декоративная линия, а не как счёт прочитанного. */
  max-width: 560px;
  margin-bottom: var(--space-5);
}

.progress-bar__track {
  flex: 1;
  min-width: 0;
  height: 8px;
  border-radius: var(--r-pill);
  background-color: var(--c-bg-tint);
  overflow: hidden;
}

.progress-bar__fill {
  display: block;
  width: var(--progress, 0%);
  height: 100%;
  border-radius: var(--r-pill);
  background-image: var(--grad-brand);
}

.progress-bar__label {
  flex: none;
  color: var(--c-ink-soft);
  font-size: var(--fs-tiny);
  white-space: nowrap;
}

/* В карточке этапа полоса идёт под списком страниц и не спорит с ним
   отступом сверху. */
.stage-detail__pages .progress-bar {
  margin-top: var(--space-4);
  margin-bottom: 0;
}

/* --- Галочка у прочитанного ----------------------------------------------- */

.read-tick {
  display: inline-flex;
  align-items: center;
  margin-left: var(--space-2);
  color: var(--c-green-700);
  vertical-align: middle;
}

.read-tick .icon {
  width: 1rem;
  height: 1rem;
}

.stage-detail__page.is-read {
  color: var(--c-ink-soft);
}

/* Прочитанная карточка каталога отмечается рамкой — тише, чем наведение,
   но заметно при беглом взгляде на сетку. */
.card.is-read {
  border-color: var(--c-green-300);
}

/* Полоса раздела на хабе мануалов и теории: стоит между тёмной шапкой
   и первой группой каталога, потому что считает весь раздел целиком. */
.section-progress {
  padding-top: var(--space-6);
}

.section-progress .progress-bar {
  margin-bottom: 0;
}
