/* ============ Общий слой компонентов Hodlometer ============
   Один исходник на оба движка: KURS (грузит напрямую) и тема /learn/ (получает копию
   через wp-theme/sync.mjs). Здесь всё, что видно на обоих сайтах и раньше было написано
   дважды: каркас, типографическая шкала, шапка, подвал, карточка, кнопка, чип, таблица,
   фокус, 404. Движок-специфичное живёт в app.css / public.css (KURS) и theme.css (WP);
   оба грузятся ПОСЛЕ этого файла и потому переопределяют его.

   Цвета, радиусы, тени, шрифты, кегли, отступы и высоты контролов — только токенами
   из :root в app.css. Магический px на font-size / padding / margin / gap / border-radius /
   высоте контрола — ошибка; хардкод цвета здесь сломает тёмную тему. */

/* ---------- Шкала на узких экранах ----------
   Переопределяем сами токены, а не каждое правило: одно место на оба движка.
   Брейкпоинты общие с остальной вёрсткой: 1000 / 860 / 640 / 430. */
@media (max-width: 860px) {
  :root {
    --fs-4xl: 52px; --fs-3xl: 38px; --fs-2xl: 30px; --fs-xl: 24px;
    --fs-h1: 32px; --fs-h1-hero: 34px; --fs-h2: 21px;
  }
}
@media (max-width: 640px) {
  :root {
    --fs-4xl: 44px; --fs-3xl: 32px; --fs-2xl: 26px; --fs-xl: 22px;
    --fs-h1: 30px; --fs-h1-hero: 29px; --fs-lg: 19px; --fs-prose: 16.5px;
    /* Ритм тоже токен: секции сжимаются с 56 до 32, сетка с 16 до 12, поля карточки с 20 до 16 */
    --space-section: var(--space-7);
    --space-page-top: var(--space-6);
    --gap-grid: var(--space-3);
    --pad-card: var(--space-4);
    --space-cta: var(--space-5);
  }
}
@media (max-width: 430px) {
  :root { --fs-h1-hero: 26px; }
}

/* ---------- База ---------- */
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
/* Якоря вели себя по-разному в двух движках: в /learn/ прокрутка была плавной, в KURS —
   мгновенной. Правило переезжает сюда, чтобы переход по «#mayer» из статьи в глоссарий
   ощущался одинаково. Отмена — в блоке prefers-reduced-motion внизу файла. */
html { scroll-behavior: smooth; }

/* min-height + flex — прижатый подвал: на короткой странице под ним оставалось до 541px фона */
body {
  font-family: var(--font);
  background: var(--bg);
  color: var(--ink);
  font-size: var(--fs-base);
  line-height: var(--lh-base);
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a { color: var(--brand); text-decoration: none; }
a:hover { text-decoration: underline; }

/* Ссылка внутри ПРИГЛУШЁННОГО текста подчёркивается всегда (WCAG 1.4.1 «Use of Color»).
   В обычном интерфейсном тексте ссылку отличает контраст с --ink, и цвета хватает; внутри
   .sub/.fine/.stat-sub соседний текст сам --muted, разница с --brand падает ниже 3:1, и цвет
   остаётся единственным признаком — Lighthouse ловил это на карточке ETH/BTC. Толщина и
   отступ — те же, что у ссылок лонгрида ниже, чтобы приём был один на весь сайт. */
.sub a, .fine a, .stat-sub a, .legend-note a, .al-note a {
  text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px;
}
.sub a:hover, .fine a:hover, .stat-sub a:hover, .legend-note a:hover, .al-note a:hover {
  text-decoration-thickness: 2px;
}
/* …кроме подсказок глоссария: у них своя пунктирная линия, две подряд читаются как дефект */
.sub .term a, .fine .term a, .stat-sub .term a { text-decoration: underline dotted; }

img, video { max-width: 100%; height: auto; }
svg { max-width: 100%; }

/* Space Grotesk — только «вывеска»: лого и заголовки. Цифры и текст остаются на Inter */
h1, h2, h3, .logo-word, .section-title, .eyebrow, .hero-title, .card-title, .tool-title, .err-title {
  font-family: var(--font-display);
  letter-spacing: -.015em;
}
h1 { font-size: var(--fs-h1); line-height: var(--lh-h1); letter-spacing: -.025em; }
h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); }
h3 { font-size: var(--fs-h3); line-height: var(--lh-h3); }

/* Заголовок не должен ронять одно слово в последнюю строку («now?» само по себе);
   в абзацах балансировка дорога — там достаточно запрета висячих строк. */
h1, h2, .lede, .hero-title, .card-title, .err-title { text-wrap: balance; }
p { text-wrap: pretty; }

::selection { background: var(--brand-bg); color: var(--brand-ink); }

/* ---------- Каркас ----------
   Две ширины на весь сайт: full — 1140 (содержимое .wrap за вычетом паддингов),
   prose — 720 (--measure-prose). Третьей быть не должно: от неё рваный правый край. */
.wrap { max-width: calc(var(--measure-full) + var(--space-8)); margin: 0 auto; padding: 0 var(--space-5); }
/* Расстояние «шапка → h1» задаётся здесь и только здесь — одинаково в обоих движках.
   Собственных верхних отступов у .page-head / .hero / .doc / .article-head быть не должно. */
main { display: block; flex: 1 0 auto; padding: var(--space-page-top) 0 var(--space-10); }
.prose-w { max-width: var(--measure-prose); }

/* ---------- Служебное ---------- */
/* top: -100px, а не translate: -50% -120%: при сдвиге на свою высоту тень оставалась
   видимой полоской по верхней кромке страницы */
.skip-link {
  position: absolute; top: -100px; left: 50%; translate: -50% 0;
  z-index: 100; background: var(--card); color: var(--ink);
  border: 1px solid var(--line); border-radius: 0 0 var(--radius-s) var(--radius-s);
  padding: var(--space-3) var(--space-5); font-weight: 600; box-shadow: var(--shadow);
}
.skip-link:focus { top: 0; text-decoration: none; }

/* Кольцо фокуса — брендовое во всём сайте, а не дефолтное браузерное.
   Контраст --focus к фону ≥3:1 в обеих темах (см. комментарий у токена). */
:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: var(--radius-xs); }

/* Недоступное действие — единое правило на весь сайт. До D3a состояние не было
   определено нигде: отключённая кнопка выглядела обычной. */
[disabled], .is-disabled, .btn[aria-disabled="true"] {
  opacity: .5; cursor: not-allowed; pointer-events: none;
}

/* видно только скринридеру */
.vh, .screen-reader-text {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.nowrap { white-space: nowrap; }

/* ---------- Иконки (спрайт Lucide) ---------- */
.ic {
  width: 1.15em; height: 1.15em; flex: none;
  vertical-align: -0.18em;
  /* внешний <use> не наследует presentation-атрибуты спрайта — задаём обводку здесь */
  fill: none; stroke: currentColor;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round;
}
.ic-sm { width: 1em; height: 1em; }
.ic-lg { width: 1.4em; height: 1.4em; }
button .ic, .btn .ic, .nav a .ic { stroke-width: 2.2; }
.ic-hint { color: var(--muted); cursor: help; vertical-align: -0.15em; }
/* Кнопка-подсказка «?»: выглядит как иконка, но фокусируется и принимает тап.
   Зона нажатия --control-h-xs (24) — минимум для вспомогательного элемента внутри строки. */
.ic-hint-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-xs); height: var(--control-h-xs); padding: 0;
  border: 0; background: none; color: var(--muted); cursor: help;
  border-radius: var(--radius-pill); vertical-align: -0.35em;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.ic-hint-btn:hover { color: var(--ink); background: var(--neutral-bg); }
.ic-hint-btn .ic { vertical-align: 0; }

/* ---------- Надзаголовки ----------
   Подпись над h1 и метка секции — это НЕ уровень документа: всегда <p>, иначе в одном
   `h2` оказывались и 11.5-пиксельная метка, и 26-пиксельный заголовок раздела. */
.eyebrow, .section-title {
  margin: 0 0 var(--space-2);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .08em;
  line-height: 1.4; text-transform: uppercase;
}
.eyebrow { color: var(--brand); }
.eyebrow.dim { color: var(--muted); }
/* Надзаголовок инструмента и симулятора несёт крошку («TOOLS» вёл в никуда, хотя
   ld_breadcrumb() отдавал поисковику полную цепочку). Крошка по контракту B6: без
   подчёркивания в покое, подчёркивание по наведению, текущая страница не выводится. */
.eyebrow a { color: inherit; text-decoration: none; }
.eyebrow a:hover { text-decoration: underline; text-underline-offset: 2px; }
.section-title { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-block); color: var(--muted); }
.section-title .ic { color: var(--muted); }

.lede { font-size: var(--fs-prose); line-height: 1.55; color: var(--ink2); margin: 0 0 var(--space-6); }
.fine { font-size: var(--fs-sm); color: var(--muted); }
.fine a { color: var(--brand); }

/* ---------- Перетяжка-объявление ----------
   Стоит НАД липкой шапкой и сама НЕ липкая: в обычном потоке она уезжает со скроллом,
   поэтому `top: 0` шапки и прокрутка к якорям остаются ровно такими, как были.

   ДВЕ ВЕРСИИ В РОТАЦИИ 50/50. Конструкция одна (.promo-bar — раскладка, размеры, состояния),
   версии отличаются ТОЛЬКО палитрой: .wise-bar и .bybit-bar не переписывают правила, а
   объявляют четыре локальных токена, которые конструкция и читает. Копия всего компонента
   ради второго бренда была бы ровно тем, что запрещает B8.10.

   Цвета обеих версий — чужие фиксированные бренд-цвета (см. --wise- и --bybit- в app.css),
   одинаковые в обеих темах. --focus переопределяется локально, а не правилом: тиловое кольцо
   на forest #163300 даёт 2.7:1 при пороге 3:1, фирменный акцент — 9.4:1 у Wise и 8.8:1
   у Bybit. Общее правило :focus-visible при этом не трогаем.

   Вертикальные поля — на внешнем элементе, а не на внутреннем .wrap: у .wrap боковые поля
   страницы заданы СОКРАЩЕНИЕМ padding, и на ≤640 оно переопределяется в блоке «Адаптив»
   ниже по файлу — то есть позже нашего правила. Любой padding-block на самом .wrap там
   обнулялся, и на телефоне полоса схлопывалась в текст без воздуха. Так же устроена шапка:
   .topbar держит фон и рамку, .topbar-in — только раскладку. */
.wise-bar {
  --promo-bg: var(--wise-ink);
  --promo-fg: var(--wise-on);
  --promo-accent: var(--wise-green);
  --promo-accent-ink: var(--wise-ink);
}
.bybit-bar {
  --promo-bg: var(--bybit-ink);
  --promo-fg: var(--bybit-on);
  --promo-accent: var(--bybit-gold);
  --promo-accent-ink: var(--bybit-ink);
}
.promo-bar {
  --focus: var(--promo-accent);
  background: var(--promo-bg); color: var(--promo-fg);
  font-size: var(--fs-sm); line-height: var(--lh-sm);
  padding-block: var(--space-2);
}
/* Обе полосы лежат в разметке всегда и по умолчанию скрыты; какую показать — решает класс
   на <html>, который ставит /assets/js/ab.js в <head> ДО первого кадра (обычный <script>
   без defer: module и defer дали бы вспышку, инлайн запрещён CSP). Без JS не показывается
   НИ ОДНА — сознательный размен: пустая шапка лучше двух перетяжек разом.
   Дизмисс любой из них снимает класс с <html>, поэтому прячутся обе: перетяжка одна на сайт. */
.promo-bar { display: none; }
html.ab-wise .wise-bar, html.ab-bybit .bybit-bar { display: block; }

.promo-bar-in { display: flex; align-items: center; gap: var(--space-3); }
.promo-bar-text { margin: 0; flex: 1; min-width: 0; }
.promo-bar-lead { font-weight: 600; }

.promo-bar-cta {
  display: inline-flex; align-items: center; flex: none;
  height: var(--control-h-xs); padding: 0 var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--promo-accent); color: var(--promo-accent-ink);
  font-weight: 600; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease);
}
/* Пилюля на hover светлеет, а не темнеет: тёмный вариант фирменного цвета на тёмном фоне
   уводит контраст ниже AA, светлый его только поднимает (белая пилюля с тёмным текстом —
   13.9:1 у Wise, 17.7:1 у Bybit). */
.promo-bar-cta:hover { background: var(--promo-fg); text-decoration: none; }

/* Крестик без рамки и заливки: рамка .icon-btn рассчитана на светлую карточку, на тёмной
   полосе она читалась бы вторым прямоугольником рядом с пилюлей. */
.promo-bar-x {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-xs); height: var(--control-h-xs); flex: none;
  padding: 0; border: 0; border-radius: var(--radius-pill);
  background: none; color: var(--promo-fg); cursor: pointer;
  transition: color var(--dur-fast) var(--ease);
}
.promo-bar-x:hover { color: var(--promo-accent); }

/* ---------- Шапка ----------
   Высота фиксированная (62px + рамка = 63px): при min-height нав на узких экранах
   переносилась вторым рядом и высота шапки гуляла от страницы к странице.
   Ниже 860px нав уезжает в .mnav под бургер. */
.topbar {
  background: var(--card);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 40;
}
.topbar-in { display: flex; align-items: center; gap: var(--space-6); height: 62px; }

.logo { display: flex; align-items: center; gap: var(--space-2); color: var(--ink); }
.logo:hover { text-decoration: none; }
.logo img, .logo svg { display: block; }
/* Лок-ап: колонка вместо <br> — зазор между строками контролируется line-height,
   а суммарная высота текста (21+14px) оптически совпадает с иконкой 34px. */
.logo > span { display: flex; flex-direction: column; }
.logo-word { font-size: var(--fs-lg); font-weight: 700; letter-spacing: -.015em; line-height: 1.05; }
/* --ink2, а не --muted: мелкий кегль — берём более контрастную ступень */
.logo-sub { font-size: var(--fs-xs); color: var(--ink2); letter-spacing: .01em; line-height: 1.15; white-space: nowrap; }

.nav { display: flex; gap: var(--space-1); flex: 1; }
.nav a {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--control-h-sm); padding: 0 var(--space-4); border-radius: var(--radius-pill);
  color: var(--ink2); font-weight: 600; font-size: var(--fs-base);
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.nav a:hover { background: var(--neutral-bg); color: var(--ink); text-decoration: none; }
.nav a.active { background: var(--brand-bg); color: var(--brand-ink); }

.topbar-right { display: flex; align-items: center; gap: var(--space-2); margin-left: auto; font-size: var(--fs-sm); color: var(--ink2); }

/* Кнопка-иконка (тема, бургер). Фон --card, а не transparent: кнопка встречается и вне
   шапки (плавающий переключатель темы на /login), там прозрачная сливалась бы с фоном. */
.icon-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-sm); height: var(--control-h-sm); flex: none;
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--card); color: var(--ink2); cursor: pointer; padding: 0;
  transition: border-color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.icon-btn:hover { border-color: var(--muted); color: var(--ink); background: var(--neutral-bg); }
.icon-btn:active { transform: translateY(1px); }
.icon-btn .ic { width: 1.05em; height: 1.05em; }
.burger { display: none; }

/* Мобильное меню — раскрывается бургером (data-nav-toggle в ui.js / theme.js).
   Раскрытие анимируется CSS-ом, без участия JS: тот только снимает атрибут hidden.
   Приём — grid-template-rows 0fr↔1fr (высота содержимого заранее не известна, а анимировать
   max-height «на глазок» значит либо обрезать длинное меню, либо тормозить короткое).
   display в списке transition с allow-discrete: без него уход в hidden = display:none
   выключал бы панель мгновенно и сворачивание не было бы видно. @starting-style задаёт
   точку, ИЗ которой едет открытие: только что показанный элемент иначе стартует уже в 1fr.
   Без JS панель остаётся с hidden — то есть свёрнутой, как и было. */
.mnav {
  border-top: 1px solid var(--line); background: var(--card);
  display: grid; grid-template-rows: 1fr;
  transition: grid-template-rows var(--dur) var(--ease),
              display var(--dur) var(--ease) allow-discrete;
}
.mnav[hidden] { display: none; grid-template-rows: 0fr; }
@starting-style { .mnav:not([hidden]) { grid-template-rows: 0fr; } }
/* min-height: 0 — без него grid-элемент не сжимается ниже min-content и 0fr ничего не даёт.
   margin-inline: 0 — .wrap несёт общий `margin: 0 auto`, а у grid-элемента авто-поля
   отменяют растягивание и переводят его в fit-content: панель схлопывалась в колонку
   120px по центру экрана, кликабельной оставалась полоса 88px. Та же ловушка описана
   у `.doc { margin: 0 }` в public.css. */
.mnav > .wrap { overflow: hidden; min-height: 0; margin-inline: 0; }
.mnav .wrap { display: flex; flex-direction: column; padding-top: var(--space-2); padding-bottom: var(--space-3); }
.mnav a { padding: var(--space-3); border-radius: var(--radius-s); color: var(--ink2); font-weight: 600; }
.mnav a:hover { background: var(--neutral-bg); color: var(--ink); text-decoration: none; }
.mnav a.active { background: var(--brand-bg); color: var(--brand-ink); }

/* ---------- Подвал ----------
   Четыре колонки, фон прозрачный (страница просвечивает): плоский ряд ссылок на белой
   карточке читался как ещё одна секция контента. */
.site-foot {
  margin-top: var(--space-11); padding: var(--space-7) 0 var(--space-9);
  border-top: 1px solid var(--line);
  color: var(--ink2); font-size: var(--fs-sm); font-weight: 400; line-height: 1.5;
}
/* Колонки — грид с равным шагом: у flex + space-between интервалы гуляли 88/265/223px */
.site-foot-in { display: grid; grid-template-columns: minmax(0, 1.4fr) minmax(0, 3fr); gap: var(--space-7) var(--space-8); }
.foot-brand .logo { margin-bottom: var(--space-2); }
.foot-brand p { margin: 0; max-width: 40ch; color: var(--ink2); }
.foot-nav { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-8); }
.foot-nav div { display: flex; flex-direction: column; gap: var(--space-2); }
.foot-nav .eyebrow { margin-bottom: var(--space-1); }
.foot-nav a { color: var(--ink2); }
.foot-nav a:hover { color: var(--brand); }
.foot-legal {
  grid-column: 1 / -1; margin: 0; padding-top: var(--space-5);
  border-top: 1px solid var(--line-soft); max-width: 90ch;
}
.foot-fine { grid-column: 1 / -1; margin: 0; font-size: var(--fs-xs); color: var(--muted); }

/* ---------- Соцсети ----------
   Марки брендов — заливка, а .ic собран под штриховой Lucide (fill:none): без сброса
   иконки не видно вовсе. Один класс на все три места (шапка, меню, подвал, карточка
   подписки) — список профилей тоже один, social_profiles(). */
.soc-ic { fill: currentColor; stroke: none; }
.soc-row { display: flex; align-items: center; gap: var(--space-1); flex-wrap: wrap; }
/* В шапке ряд не переносится и не сжимается: при нехватке места flex-wrap заворачивал
   марки в столбик, и они вываливались из топбара поверх страницы. Не влезает — значит
   шапка узкая, и ряд целиком прячется медиазапросом ниже. */
.topbar-soc { flex-wrap: nowrap; flex: none; }
/* Без рамки, в отличие от .icon-btn: четыре обведённых кружка рядом с переключателем
   темы читались бы как панель управления, а это ссылки наружу. Зона нажатия сохранена. */
.soc-link {
  display: inline-flex; align-items: center; justify-content: center;
  width: var(--control-h-sm); height: var(--control-h-sm); flex: none;
  border-radius: var(--radius-pill); color: var(--muted);
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.soc-link:hover { color: var(--ink); background: var(--neutral-bg); text-decoration: none; }
.soc-link .ic { width: 1.05em; height: 1.05em; }

/* Подвал: марка + название сети (хэндл — в title, см. layout.php) */
.foot-soc { display: flex; align-items: center; gap: var(--space-2); color: var(--ink2); }
.foot-soc:hover { color: var(--brand); }
.foot-soc .ic { width: 1em; height: 1em; color: var(--muted); }
.foot-soc:hover .ic { color: var(--brand); }

/* Карточка подписки: те же профили кнопками — здесь у них есть подпись и вес решения */
.nl-social { margin-top: var(--space-5); padding-top: var(--space-5); border-top: 1px solid var(--line-soft); }
.nl-social p { margin: 0 0 var(--space-3); color: var(--ink2); max-width: 70ch; }
.nl-social .soc-row { gap: var(--space-2); }
.soc-btn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  height: var(--control-h-sm); padding: 0 var(--space-4);
  border: 1px solid var(--line); border-radius: var(--radius-pill);
  background: var(--card); color: var(--ink2);
  font-size: var(--fs-sm); font-weight: 600;
  transition: border-color var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.soc-btn:hover { border-color: var(--muted); color: var(--ink); background: var(--neutral-bg); text-decoration: none; }
.soc-btn .ic { width: 1em; height: 1em; }

/* ---------- Карточка ---------- */
.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: var(--pad-card);
  transition: box-shadow var(--dur) var(--ease), border-color var(--dur) var(--ease);
}
.card h2, .card h3, .card-title { font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: 700; margin: 0 0 var(--space-1); }
/* `margin-top: 0` выше рассчитан на карточку с ОДНИМ заголовком в самом верху — так было
   на всех страницах, пока /tools/attention не положил два графика в одну карточку. Второй
   заголовок вставал вплотную к предыдущему SVG: подпись оказывалась ближе к чужому графику
   сверху, чем к своему снизу, и читалась как его хвост. Заголовок, идущий ПОСЛЕ содержимого,
   обязан иметь верхний отступ — правило системное, а не для одной страницы. */
.card > * + h2, .card > * + h3 { margin-top: var(--space-6); }
/* Пара графиков в одной карточке — отдельный случай, и 24px здесь не хватает. У серверного
   SVG подписи оси стоят у самого нижнего края, а сверху внутри viewBox есть воздух: заголовок
   оказывался на 24px от чужой оси и на ~20px от своего графика, то есть висел ровно посередине
   и не группировался ни с чем. Отсюда линия и полноценный отступ: она делит карточку на два
   блока явно, а не намёком. Замер делать в НОРМАЛЬНОМ окне — в схлопнутом вьюпорте SVG
   с width:100% отдаёт нулевую высоту, и любые расстояния получаются выдуманными. */
.card > .chart-svg + h2, .card > .chart-svg + h3 {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--line-soft);
}
/* заголовки карточек дашборда несут иконку — она должна стоять в одной строке с текстом */
.card h2, .card h3 { display: flex; align-items: center; gap: var(--space-2); }
.card h2 .ic, .card h3 .ic { color: var(--muted); }
/* а эти двое — обычный текст, и в пустом состоянии он центрируется по карточке */
.card-title, .empty-state h2 { display: block; }
.card .sub { color: var(--muted); font-size: var(--fs-xs); }

/* Анатомия карточки статьи — одна на оба движка: обложка (если есть), заголовок,
   выжимка, мета внизу. Рубрика — чипом в мете, не отдельной строкой над заголовком. */
.post-card { display: flex; flex-direction: column; gap: var(--space-3); padding: 0; overflow: hidden; color: var(--ink); }
.post-card:hover { box-shadow: var(--shadow-hover); text-decoration: none; }
/* Линия под обложкой нужна тёмной теме: обложки сами тёмные, и без неё картинка
   сливается с карточкой в одно пятно. В светлой она просто совпадает с краем. */
.card-thumb { display: block; aspect-ratio: 16 / 9; overflow: hidden; background: var(--card-2); border-bottom: 1px solid var(--line); }
.card-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* flex:1 + margin-top:auto — мета прижата к низу карточки, в ряду не пляшет */
.card-body { display: flex; flex-direction: column; gap: var(--space-2); padding: var(--pad-card); flex: 1 0 auto; }
.card-title { margin: 0; }
.card-title a { color: var(--ink); }
.card-title a:hover { color: var(--brand); text-decoration: none; }
.card-excerpt { margin: 0; color: var(--ink2); font-size: var(--fs-base); flex: 1; }
.post-card .meta-row { margin-top: auto; }

/* Мета записи */
.meta-row {
  display: flex; align-items: center; gap: var(--space-2) var(--space-4); flex-wrap: wrap;
  font-size: var(--fs-sm); color: var(--ink2); font-variant-numeric: tabular-nums;
}
.meta-item { display: inline-flex; align-items: center; gap: var(--space-2); }
/* все пункты меты одного веса: выделенный «Updated» читался важнее даты публикации */
.meta-item.upd { color: var(--ink2); font-weight: inherit; }

/* ---------- Кнопки ----------
   Высота — одна из трёх токенных (40 / 32); вес 600, а не 700: на заливке бренда
   700 читался крикливо. Максимум одна primary в блоке. */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: var(--control-h); padding: 0 var(--space-4);
  background: var(--brand-solid); color: var(--on-solid);
  border: 1px solid transparent; border-radius: var(--radius-s);
  font: inherit; font-size: var(--fs-base); font-weight: 600;
  cursor: pointer; white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease), box-shadow var(--dur-fast) var(--ease);
}
/* text-decoration: none — глобальный a:hover иначе подчёркивает подпись кнопки-ссылки */
.btn:hover { background: var(--brand-hover); color: var(--on-solid); text-decoration: none; }
.btn:active { transform: translateY(1px); }
.btn.secondary { background: var(--card); color: var(--ink); border-color: var(--line); }
.btn.secondary:hover { background: var(--neutral-bg); color: var(--ink); }
/* Разрушительное действие не должно быть громче главного. Сплошная заливка --down работает
   только в светлой теме: в тёмной этот токен — светлый лосось, и «Delete» становился самым
   ярким пятном экрана, ярче primary. Мягкая пара --down-bg/--down (та же, что у .flash.err)
   верна в обеих темах и не занимает статусным токеном роль интерактивной заливки. */
.btn.danger { background: var(--down-bg); color: var(--down-ink); border-color: var(--down); }
.btn.danger:hover { background: var(--down); color: var(--on-solid); }
.btn.sm { height: var(--control-h-sm); padding: 0 var(--space-3); font-size: var(--fs-sm); }

/* ---------- Чип ----------
   Одна пилюля на все роли. Модификаторы: состояние (up/down/flat/warn), зона термометра
   (zone + z1…z5), рубрика (cat), тег (tag), статус инструмента (live/soon).
   Раньше это были четыре независимых класса с разной высотой, радиусом и кеглем.
   Чип обязан нести различие: если у всех элементов набора он одинаков — не выводится. */
.chip {
  display: inline-flex; align-items: center; gap: var(--space-1);
  min-height: var(--control-h-xs); padding: 0 var(--space-3);
  /* Асимметричный корпус: левый торец плоский (--radius-xs), правый круглый.
     Пилюля 999px по обоим торцам — интерфейсный дефолт, одинаковый на любом сайте;
     здесь метка обозначает участок шкалы, и форма это говорит: плоский торец —
     граница диапазона, круглый — направление вправо, в сторону роста. */
  border-radius: var(--radius-xs) var(--radius-pill) var(--radius-pill) var(--radius-xs);
  font-size: var(--fs-xs); font-weight: 600; line-height: var(--lh-xs);
  color: var(--ink2); background: var(--neutral-bg);
  /* Пилюля не переносится никогда: перенос ломает её собственную геометрию —
     «Statistical model» и «Deep Freeze» вставали в две строки и давали 31px
     против номинальных 24px (--control-h-xs), то есть пилюля переставала быть пилюлей. */
  white-space: nowrap;
}
.chip.up { color: var(--up); background: var(--up-bg); }
.chip.down { color: var(--down); background: var(--down-bg); }
.chip.warn { color: var(--warn); background: var(--warn-bg); }
.chip.flat { color: var(--ink2); background: var(--neutral-bg); }
.chip.brand, .chip.cat, .chip.live { color: var(--brand-ink); background: var(--brand-bg); }
a.chip:hover { text-decoration: none; filter: brightness(.97); }
/* контурные: тег ведёт в архив, «coming soon» стоит на карточке без заливки —
   у обоих --neutral-bg совпал бы с фоном страницы и пилюля пропала бы */
.chip.tag, .chip.soon { background: none; border: 1px solid var(--line); color: var(--ink2); }
.chip.tag:hover { border-color: var(--muted); color: var(--ink); filter: none; }
/* зоны термометра: <span class="chip zone z4">Warm</span> */
/* Не точка, а деление шкалы: вертикальный штрих у плоского торца. Круглая точка
   означала бы «статус» (как в списках сервисов), а зона — это отрезок 0–100. */
.chip.zone::before { content: ""; width: 3px; height: 11px; border-radius: 2px; background: currentColor; flex: none; }
.chip.z1 { background: var(--therm-1-bg); color: var(--therm-1-ink); }
.chip.z2 { background: var(--therm-2-bg); color: var(--therm-2-ink); }
.chip.z3 { background: var(--therm-3-bg); color: var(--therm-3-ink); }
.chip.z4 { background: var(--therm-4-bg); color: var(--therm-4-ink); }
.chip.z5 { background: var(--therm-5-bg); color: var(--therm-5-ink); }

/* ---------- Таблицы ----------
   Шапка — одна линейка 1px --line (2px в тёмной теме давали яркую полосу),
   ячейки выровнены по верху, числовые колонки вправо.
   Таблицы и оси — одно из четырёх мест, где tabular-nums уместен. */
/* ячейки core-блока WP приходят с border: 1px по всем сторонам, thead/tfoot — ещё 3px currentColor */
.prose th, .prose td, .prose thead, .prose tfoot { border: 0; }
.tbl, .prose table {
  width: 100%; border-collapse: collapse;
  font-size: var(--fs-sm); font-variant-numeric: tabular-nums;
}
.tbl th, .prose th {
  text-align: left; font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .06em;
  color: var(--muted); font-weight: 700; padding: var(--space-2) var(--space-3); border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.tbl td, .prose td { padding: var(--space-3); border-bottom: 1px solid var(--line-soft); vertical-align: top; }
.tbl tr:last-child td, .prose tr:last-child td { border-bottom: 0; }
.tbl .num, .tbl th.num, .tbl .r, .prose .num { text-align: right; }
/* крайние колонки заподлицо с текстом — иначе таблица в колонке чтения съезжает вправо */
.tbl th:first-child, .tbl td:first-child, .prose th:first-child, .prose td:first-child { padding-left: 0; }
.tbl th:last-child, .tbl td:last-child, .prose th:last-child, .prose td:last-child { padding-right: 0; }
/* Строка «где мы сейчас» в таблице распределения: подложка + полужирный, без цвета.
   Цвет здесь уже занят зоной (контракт B3), а вторая цветовая кодировка в одной строке
   читается как ещё одна категория. Тот же приём, что у .hv-lead на /tools/halving. */
.tbl tr.is-now > td { background: var(--neutral-bg); font-weight: 600; }
/* У подложки обязано быть поле. Крайние ячейки прижаты к краю таблицы (padding 0 —
   текст заподлицо с колонкой чтения), поэтому заливка начиналась ровно по кромке пилюли
   и та выглядела вдавленной в серое. Таблица с подсветкой выезжает за колонку на
   --space-3 и возвращает содержимое на место паддингом крайних ячеек: текст стоит там же,
   где стоял во всех остальных таблицах, а у подложки появляется зазор и скруглённые торцы.
   Через :has() — только там, где подсветка есть: остальные таблицы не трогаем. */
.tbl-wrap:has(tr.is-now) { margin-inline: calc(-1 * var(--space-3)); }
.tbl-wrap:has(tr.is-now) .tbl th:first-child,
.tbl-wrap:has(tr.is-now) .tbl td:first-child { padding-left: var(--space-3); }
.tbl-wrap:has(tr.is-now) .tbl th:last-child,
.tbl-wrap:has(tr.is-now) .tbl td:last-child { padding-right: var(--space-3); }
.tbl tr.is-now > td:first-child { border-radius: var(--radius-s) 0 0 var(--radius-s); }
.tbl tr.is-now > td:last-child { border-radius: 0 var(--radius-s) var(--radius-s) 0; }

/* Скролл-коробка вокруг таблицы, а НЕ сама таблица. Делать скроллером <table>
   (display:block) нельзя: смена display снимает неявные ARIA-роли table/row/cell
   в Chromium и WebKit, и таблица перестаёт быть таблицей для скринридера.
   Градиент у края (см. .tbl-scroll ниже) живёт здесь же. */
/* Подсказка «таблицу можно листать»: две тени у краёв, которые ПОЯВЛЯЮТСЯ только когда
   содержимое шире контейнера, и гаснут, когда доскроллил до края. Приём на четырёх фонах:
   две «маски» цвета карточки прокручиваются вместе с содержимым (background-attachment:
   local) и закрывают тени, пока прокручивать нечего. Без этого читатель на телефоне
   находит скрытую колонку случайно (замечание читателя-скептика к /tools/attention). */
.tbl-wrap {
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--card) 40%, transparent) left center / 36px 100% no-repeat local,
    linear-gradient(to left, var(--card) 40%, transparent) right center / 36px 100% no-repeat local,
    radial-gradient(farthest-side at 0 50%, rgb(0 0 0 / .22), transparent) left center / 14px 100% no-repeat scroll,
    radial-gradient(farthest-side at 100% 50%, rgb(0 0 0 / .22), transparent) right center / 14px 100% no-repeat scroll;
}

/* Подписи-ключи и подсказка о свайпе существуют только в стопке: на десктопе те же
   смыслы несёт видимая шапка, и дублировать их в каждой ячейке нельзя.
   Объявлены ДО медиа-блока: медиа-запрос специфичности не добавляет, и стоя после него
   эти две строки перебивали бы то, что включают ниже. */
.tbl-stack .tk { display: none; }
.tbl-swipe { display: none; }

/* ---------- Мобильная раскладка таблиц (≤640) ----------
   Строка становится блоком: ключ сверху, значения под ним, служебное мельче и тише.
   Разметка не меняется — это CSS-раскладка поверх настоящей <table>.
   Базовая анатомия .tbl (выше) не переписывается: правила добавляются только в @media. */
@media (max-width: 640px) {
  /* Заголовок может переноситься — это ~50% всего горизонтального переполнения сайта:
     при nowrap минимальная ширина колонки равна ширине самой длинной подписи в шапке.
     На ≥641 nowrap остаётся: там место есть и однострочная капс-шапка опрятнее. */
  .tbl th, .prose th { white-space: normal; }

  .tbl-stack { display: block; }

  /* Шапка убирается ИЗ ВИДА, но остаётся в дереве доступности: display:none выкинул бы
     заголовки колонок из a11y-дерева и порвал связь «заголовок → значение».
     clip-path прячет визуально, сохраняя узел читаемым для скринридера. */
  .tbl-stack thead {
    position: absolute; width: 1px; height: 1px;
    margin: -1px; padding: 0; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap;
  }

  .tbl-stack tbody { display: block; }

  .tbl-stack tr {
    display: grid;
    grid-template-columns: auto auto 1fr;   /* ключевая строка блока */
    align-items: center;
    column-gap: var(--space-2);
    row-gap: var(--space-1);
    padding: var(--space-3) 0;
    border-bottom: 1px solid var(--line-soft);
  }
  .tbl-stack tr:last-child { border-bottom: 0; }
  /* Две колонки вместо трёх. Зазор ставится между ВСЕМИ дорожками, поэтому пустая третья
     дорожка молча съедает 8px — там, где ключевая строка состоит из пары, а не из тройки,
     этих 8px не хватало и значения ломались на вторую строку. */
  .tbl-stack.st-2 tr { grid-template-columns: auto 1fr; }
  /* Прозовая стопка (сравнительные таблицы с ячейками-абзацами): одна колонка,
     каждая ячейка — своя строка блока, подпись .tk над ней. Сетка st-2 на прозе
     отдавала длинной ячейке весь трек и оставляла соседям 56px. */
  .tbl-stack.st-prose tr { grid-template-columns: 1fr; }
  /* Ячейки-абзацы читают как текст, а не как цифры — кегль интерфейсный, не табличный */
  .tbl-stack.st-prose { font-size: var(--fs-base); }

  .tbl-stack td {
    display: block; padding: 0; border: 0;
    text-align: left;   /* правое выравнивание внутри блока смысла не имеет */
  }
  .tbl-stack td.r, .tbl-stack td.num { text-align: left; }

  /* Подпись значения — настоящий узел <span class="tk" aria-hidden="true">, а не
     ::before{content:attr(data-label)}: шапка оставлена в a11y-дереве, и генерируемый
     контент дал бы двойное чтение («Source Source AI»). Плюс в accTable три заголовка
     несут интерактивный тултип — attr() отдаёт только текст и убил бы подсказки. */
  .tbl-stack .tk { display: block; font-size: var(--fs-xs); color: var(--muted); font-weight: 600; }
  /* подпись и значение в одну строку, когда значение короткое */
  .tbl-stack .tk.inl { display: inline; margin-right: var(--space-1); }

  /* Раскладка блока — семь утилит, из них собираются все таблицы.
     `order` нужен потому, что порядок колонок на десктопе менять нельзя, а в стопке
     важное («Inside range», «Mean error») обязано стоять выше служебного («Scored»).
     `grid-column-start: 1` — стандартный приём переноса на новую строку в auto-flow:
     элемент едет вниз до строки, где первая колонка свободна. */
  /* Три уровня внутри блока обязаны различаться, иначе стопка читается одной массой:
     ключ — вес, значения — обычный текст, служебное — мельче и тише (правила ниже). */
  .tbl-stack td.k { font-weight: 700; color: var(--ink); }
  .tbl-stack td.k2 { font-weight: 600; color: var(--ink2); }

  .tbl-stack td { order: 1; }                    /* [1] ключевая строка */
  .tbl-stack td.on-row-2 { order: 2; }           /* [2] значения */
  .tbl-stack td.on-row-3 { order: 3; font-size: var(--fs-xs); color: var(--muted); }  /* [3] служебное */
  .tbl-stack td.ord-last { order: 4; }           /* внутри своей строки идёт последней */
  .tbl-stack td.rw { grid-column-start: 1; }     /* начинает новую строку блока */
  .tbl-stack td.full { grid-column: 1 / -1; }    /* занимает строку блока целиком */
  /* Пара «подпись + короткое значение» («In range 73%», «393 scored») не разрывается:
     в auto-колонке она ломалась пополам и распирала блок на 18px за строку.
     Ставится вручную и только там, где содержимое заведомо коротко — как общее правило
     это дало бы переполнение на ячейках с прозой. */
  .tbl-stack td.nw { white-space: nowrap; }
  /* Не выводится на телефоне: колонка, чьё значение постоянно или названо рядом,
     либо вторичный кусок внутри ячейки. Работает и на td/th, и на span внутри,
     и в стопке, и в скроллящейся таблице. */
  .tbl .off-mobile, .prose .off-mobile { display: none; }

  /* Значение, прижатое к правому краю ключевой строки (глубина падения, вес).
     Занимает вторую и третью колонки: в первой стоит ключ строки, и «justify-self: end»
     без спана прижимал бы значение к краю СВОЕЙ узкой колонки, а не строки. */
  .tbl-stack td.key-end { grid-column: 2 / -1; justify-self: end; text-align: right; }
  /* то же, но внутри своей колонки — когда правее в ключевой строке стоит ещё значение */
  .tbl-stack td.je { justify-self: end; text-align: right; }

  /* Подложка «где мы сейчас» переезжает на строку: в стопке ячейки разделены зазорами
     grid, и подложка на каждой td читалась бы как набор полос, а не одна строка. */
  .tbl-stack tr.is-now {
    background: var(--neutral-bg); border-radius: var(--radius-s);
    /* тот же зазор, что на десктопе, но строка тут — сама себе блок, поэтому поле
       даётся ей, а не ячейкам; выезд таблицы за колонку в стопке отключён (ниже) */
    margin-inline: calc(-1 * var(--space-3)); padding-inline: var(--space-3);
  }
  .tbl-stack tr.is-now > td { background: none; }
  .tbl-wrap:has(.tbl-stack tr.is-now) { margin-inline: 0; }

  /* Дата не разрывается посередине: «22 Jun 2025 → 26 Jul 2026» в узкой колонке ломалось
     на три рваные строки и распирало блок со 105px до 123px. Перенос допустим только
     по стрелке, между двумя датами. */
  .tbl-stack time { white-space: nowrap; }

  /* Таблица, остающаяся в горизонтальном скролле (исключение B): подпись-подсказка */
  .tbl-swipe { display: block; margin-bottom: var(--space-2); font-size: var(--fs-xs); color: var(--muted); }
}

/* ---------- Горизонтальный скролл там, где он остаётся (исключение B) ----------
   Колонки-шкала (Worst · 10th · Median · 90th · Best) читаются одним движением глаза;
   разбиение по строкам уничтожает форму распределения. Поэтому скролл сохраняется,
   но становится очевидным: липкая первая колонка + тень у края.

   Тень: два градиента цветом поверхности стоят у краёв с attachment:local (уезжают
   вместе с содержимым), под ними два тёмных с attachment:scroll (прибиты к коробке).
   Прокрутил — «закрашивающий» уехал, тёмный открылся. Тень появляется ровно с той
   стороны, где есть что показать, и гаснет в конце. JS не нужен. */
.tbl-scroll-wrap {
  --tbl-surface: var(--card);
  overflow-x: auto; -webkit-overflow-scrolling: touch;
  background:
    linear-gradient(to right, var(--tbl-surface), transparent)
      left / var(--space-7) 100% no-repeat local,
    linear-gradient(to left, var(--tbl-surface), transparent)
      right / var(--space-7) 100% no-repeat local,
    linear-gradient(to right, var(--edge-fade), transparent)
      left / var(--space-3) 100% no-repeat scroll,
    linear-gradient(to left, var(--edge-fade), transparent)
      right / var(--space-3) 100% no-repeat scroll;
}
.doc .tbl-scroll-wrap, .prose .tbl-scroll-wrap { --tbl-surface: var(--bg); }

/* Липкая первая колонка — только на ≤640. На десктопе эта таблица помещается целиком,
   и вертикальная линейка справа от первой колонки была бы новым элементом, которого
   раньше не было; там всё остаётся как есть, включая border-collapse: collapse.

   border-collapse переключается на separate намеренно: при collapse граница принадлежит
   таблице, а не ячейке, и с липкой колонкой не едет — на прокрутке колонка оставалась
   бы без правой границы. Поэтому границы объявляются на ячейках. */
@media (max-width: 640px) {
  .tbl-scroll { border-collapse: separate; border-spacing: 0; }
  .tbl-scroll th, .tbl-scroll td { border-bottom: 1px solid var(--line-soft); }
  .tbl-scroll thead th { border-bottom: 1px solid var(--line); }
  .tbl-scroll tr:last-child td { border-bottom: 0; }
  .tbl-scroll th:first-child, .tbl-scroll td:first-child {
    position: sticky; left: 0; z-index: 1;
    background: var(--tbl-surface, var(--card));
    border-right: 1px solid var(--line-soft);
    padding-right: var(--space-3);
    /* Липкая колонка — ключ строки, и переноситься ей нельзя: «1 year» в две строки
       поднимает строку с 43px до 63px, а ширину при этом не экономит. Общее снятие
       nowrap с th (§4.1) здесь как раз вредно, поэтому возвращаем точечно. */
    white-space: nowrap;
  }
}

/* ---------- Лонгрид ----------
   Колонка чтения 720px, 17px/1.7 — одинаково в статьях /learn/ и в документах KURS
   (/about, /methodology). Раньше KURS давал 15px/1.5 на 780px: ~95–100 знаков в строке. */
.prose, .doc {
  max-width: var(--measure-prose);
  font-size: var(--fs-prose); line-height: var(--lh-prose); color: var(--ink);
  /* h3 из общей шкалы (17px) совпал бы с текстом лонгрида — здесь ступень выше */
  --fs-h3: var(--fs-lg);
}
/* Разделительная линия секции живёт здесь, в общем слое, а не в public.css: один и тот же
   тип содержимого (длинный текст с якорями и оглавлением) при переходе Learn → About
   менял ритм и терял разделители — в KURS у h2 было 60px воздуха с линией, в статьях WP
   40px без неё. Класс .prose в теме стоит ровно на теле статьи, роль та же. */
.prose h2, .doc h2 {
  font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: 700;
  margin: var(--space-8) 0 var(--space-4); padding-top: var(--space-5);
  border-top: 1px solid var(--line-soft); scroll-margin-top: 84px;
}
.prose h3, .doc h3 {
  font-size: var(--fs-h3); line-height: 1.3; font-weight: 700;
  margin: var(--space-7) 0 var(--space-3); scroll-margin-top: 84px;
}
.prose h4, .doc h4 { font-size: var(--fs-h3); margin: var(--space-6) 0 var(--space-2); font-weight: 700; }
.prose p, .doc p { margin: 0 0 var(--space-5); }
.prose ul, .prose ol, .doc ul, .doc ol { margin: 0 0 var(--space-5); padding-left: var(--space-6); }
.prose li, .doc li { margin-bottom: var(--space-2); }
.prose li::marker, .doc li::marker { color: var(--muted); }
/* Инлайн-ссылки подчёркиваются: одного цвета мало — контраст ссылки к тексту
   3.2:1 в светлой и 2.27:1 в тёмной, то есть ниже порога «различимо без подчёркивания». */
.prose a, .doc a, main p a:not(.btn), main li a:not(.btn) {
  text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px;
}
.prose a:hover, .doc a:hover { text-decoration-thickness: 2px; }

/* ---------- Термин с подсказкой ----------
   Разметка приходит с сервера из хелпера term() (app/services/glossary.php):
   <span class="term"><a class="term-w" href="/glossary#key" …>Mayer Multiple</a>
   <span class="term-pop" role="tooltip" hidden>…</span></span>

   Без JS подсказка остаётся скрытой атрибутом hidden, а термин — обычной ссылкой на
   глоссарий; показывает её assets/js/term.js по наведению и по фокусу (Esc закрывает).
   Определение всё равно доходит до скринридера: ссылка ссылается на него aria-describedby,
   а такой текст читается даже у скрытого элемента.

   Селектор `.term > a.term-w`, а не `.term-w`: блок лонгрида выше задаёт сплошное подчёркивание
   селектором `main p a:not(.btn)` — три элемента и класс, — и одиночный класс его не перебивает.
   Два класса перевешивают, сколько бы элементов ни было во втором селекторе. */
.term { position: relative; }
.term > a.term-w {
  color: inherit;
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
  text-underline-offset: 3px;
  cursor: help;
}
.term > a.term-w:hover, .term > a.term-w:focus-visible {
  color: var(--brand-ink);
  text-decoration: underline dotted;
  text-decoration-thickness: 1px;
}

/* pointer-events: none — курсор не должен «заходить» в подсказку: иначе уход мыши с
   термина на неё считался бы уходом со ссылки и она моргала бы. Ссылок внутри нет.

   Появление: 120 мс fade с подъёмом на 3px — мгновенные 320×154 под курсором читались
   как глюк отрисовки. Закрытие намеренно мгновенное: анимировать исчезновение подсказки
   значит задерживать следующую, а по строке текста мышь идёт быстрее любой анимации.
   Подъём через --term-y: translate по X уже занят подстройкой под край экрана (term.js). */
.term-pop {
  position: absolute; z-index: 60;
  top: calc(100% + var(--space-2)); left: 0;
  translate: var(--term-x, 0px) 0;
  animation: term-in var(--dur-fast) var(--ease);
  width: max-content; max-width: min(320px, calc(100vw - var(--space-7)));
  padding: var(--space-3);
  background: var(--card); color: var(--ink2);
  border: 1px solid var(--line); border-radius: var(--radius-s);
  box-shadow: var(--shadow-pop);
  font-family: var(--font); font-size: var(--fs-sm); line-height: 1.5;
  font-weight: 400; font-style: normal; letter-spacing: normal; text-align: left;
  text-transform: none; white-space: normal;
  pointer-events: none;
}
.term-pop.up { top: auto; bottom: calc(100% + var(--space-2)); animation-name: term-in-up; }
.term-pop b { display: block; margin-bottom: var(--space-1); color: var(--ink); font-weight: 700; }
.term-pop .term-go { display: block; margin-top: var(--space-2); color: var(--muted); font-size: var(--fs-xs); }
/* Подсказка, открытая вниз, выезжает снизу вверх; открытая вверх — сверху вниз:
   в обоих случаях она «выходит из-под» термина, а не наезжает на него. */
@keyframes term-in { from { opacity: 0; translate: var(--term-x, 0px) 3px; } }
@keyframes term-in-up { from { opacity: 0; translate: var(--term-x, 0px) -3px; } }
/* На тач-устройстве первый тап открывает подсказку (term.js гасит переход), и «Full entry
   in the glossary →» становится подсказкой, что делает второй тап. Ссылки внутри по-прежнему
   нет — тапается сам термин, поэтому pointer-events остаются выключенными. */
@media (hover: none) {
  .term-pop .term-go { color: var(--brand); font-weight: 600; }
}

/* ---------- Мостик в глоссарий ----------
   Компактная строка рядом с дисклеймером в статьях /learn/ и под лидом инструментов. */
.gloss-note {
  display: flex; gap: var(--space-3); align-items: flex-start;
  margin: var(--space-7) 0 0; padding: var(--space-3) var(--space-4);
  background: var(--card-2); color: var(--ink2);
  border: 1px solid var(--line-soft); border-radius: var(--radius-s);
  font-size: var(--fs-sm); line-height: 1.55;
  max-width: var(--measure-prose);
}
.gloss-note .ic { flex: none; margin-top: 2px; color: var(--muted); }
.gloss-note b { color: var(--ink); font-weight: 700; }
/* Ссылка здесь лежит в <div>, а не в <p>, и правило подчёркивания лонгрида её не ловило:
   в потоке текста она отличалась только цветом (Lighthouse link-in-text-block на статьях). */
.gloss-note a { text-decoration: underline; text-underline-offset: 2px; text-decoration-thickness: 1px; }

/* ---------- Колонка чтения с боковиком ----------
   Переехало из темы /learn/: длинные документы KURS (/methodology 6218px, /about,
   /tools/mayer, /tools/halving) имели якорные h2, но не имели оглавления — внутри одного
   сайта получались два разных опыта чтения. Теперь раскладка одна на оба движка.
   Побочный и намеренный эффект: на этих страницах появляется правая колонка — та самая,
   куда по ROADMAP лягут будущие баннеры (правило «максимум 2 слота, без попапов»). */
.article-layout {
  display: grid;
  grid-template-columns: minmax(0, var(--measure-prose)) var(--side);
  gap: var(--space-9); align-items: start;
}
/* Документ с оглавлением — такая же глава страницы, как секция, но класса `.section`
   (и его 56px ритма) у него нет: на всех шести инструментах он прилипал к предыдущей
   секции вплотную, и «On this page» начиналось сразу за концом графиков. Отступ даётся
   ТОЛЬКО когда выше что-то есть: на /methodology и /about раскладка идёт первым блоком
   страницы, и там верхний отступ уже отмерен `--space-page-top`. */
* + .article-layout { margin-top: var(--space-section); }
/* …но не когда выше стоит шапка ТОЙ ЖЕ статьи: в теме /learn/ порядок «заголовок → обложка →
   текст с оглавлением», и 56px там разорвали бы статью пополам вместо перехода между главами.
   Свой отступ шапка уже несёт сама (`.article-head { margin-bottom: --space-6 }`). */
.article-head + .article-layout,
.article-cover + .article-layout { margin-top: 0; }
/* Та же причина, что у `.grid > *`: у grid-элемента min-width по умолчанию auto, то есть
   его min-content. Формула и широкая таблица внутри /methodology держали колонку в 475px
   при доступных 343 — и страница на 375px ехала вбок. Ноль возвращает право сжиматься,
   а прокрутку берут на себя .tbl-wrap / .doc .tbl. */
.article-layout > * { min-width: 0; }
/* оглавление едет со страницей; max-height — чтобы длинный список не уезжал за экран */
.article-side { position: sticky; top: 80px; max-height: calc(100vh - 100px); overflow-y: auto; overscroll-behavior: contain; }

.toc {
  border-left: 1px solid var(--line);
  padding: 2px 0 2px var(--space-4);
  font-size: var(--fs-sm);
}
.toc-title {
  display: flex; align-items: center; gap: var(--space-2);
  font-size: var(--fs-xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink2); margin-bottom: var(--space-2);
  list-style: none;   /* треугольник <summary> заменяем своим — он читается как маркер списка */
}
.toc-title::-webkit-details-marker { display: none; }
/* На широком экране оглавление всегда раскрыто, и summary — просто заголовок */
.toc-d[open] > .toc-title { cursor: default; }
.toc-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--space-2); }
/* Подчёркивание снимается явно: в KURS оглавление стоит рядом с .doc, и его правило
   `.doc a { text-decoration: underline }` протекало бы в навигацию (аудит A53). */
.toc-list a { color: var(--ink2); text-decoration: none; transition: color var(--dur-fast) var(--ease); }
.toc-list a:hover { color: var(--brand); text-decoration: none; }
.toc-list .lvl-3 { padding-left: var(--space-4); font-size: var(--fs-sm); }
/* активный пункт: ставит theme.js (/learn/) или ui.js (KURS) по позиции скролла.
   Цветная планка слева — второе кодирование помимо веса: одним жирным пункт не находится. */
.toc-list li { position: relative; }
.toc-list a.is-current { color: var(--ink); font-weight: 700; }
.toc-list a.is-current::before {
  content: ""; position: absolute; left: calc(var(--space-4) * -1 - 1px); top: 0; bottom: 0;
  width: 1px; background: var(--brand); box-shadow: 0 0 0 1px var(--brand);
}

/* ---------- Поделиться статьёй ----------
   Три обычные ссылки и одна кнопка: ни одного SDK, ни одного внешнего запроса, ни одной
   куки. Кнопки соцсетей в их «родном» виде тянут скрипты и трекеры — здесь это просто
   intent-адреса, которые открывают форму публикации уже у самого пользователя. */
.share {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  margin: var(--space-7) 0 0; max-width: var(--measure-prose);
}
.share-lbl { font-size: var(--fs-xs); font-weight: 700; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin-right: var(--space-1); }
.share .btn { flex: none; }
.share .btn.sm { gap: var(--space-2); }

/* ---------- 404 ---------- */
.error-404 { text-align: center; padding: var(--space-10) var(--space-6); }
.err-code {
  font-family: var(--font-display); font-size: var(--fs-4xl); font-weight: 700; line-height: var(--lh-4xl);
  color: var(--brand-2); font-variant-numeric: tabular-nums;
}
.err-title { font-size: var(--fs-h2); line-height: var(--lh-h2); margin: var(--space-4) 0 var(--space-2); }
.err-sub { color: var(--ink2); margin: 0 auto var(--space-6); max-width: 460px; }
.err-links { display: flex; gap: var(--space-2); justify-content: center; flex-wrap: wrap; margin-top: var(--space-5); }
/* Остальные выходы — компактной строкой, а не рядом равновесных кнопок: шесть primary
   рядом заставляют прочесть все шесть, вместо того чтобы предложить один очевидный. */
.err-more {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--space-1) var(--space-2);
  margin: var(--space-5) auto 0; max-width: 60ch; font-size: var(--fs-sm); color: var(--ink2);
}
/* --muted, а не --line: на 13px разделитель цветом линии даёт 1.31:1 — он не «тихий»,
   он невидимый. Остальные точки-разделители на сайте и так --muted. */
.err-more .dot { color: var(--muted); }

/* ---------- Пустое состояние ---------- */
.empty-state { text-align: center; padding: var(--space-9) var(--space-6); }
.empty-state h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); margin: 0 0 var(--space-2); }
.empty-state p { color: var(--ink2); margin: 0 auto var(--space-5); max-width: 460px; }

/* ---------- Движение ----------
   Анимируем только то, что сообщает о причинно-следственной связи (появилось / скрылось /
   переключилось) или гасит скачок вёрстки. Данные, цифры и графики не анимируем: у морфинга
   линии между диапазонами нет честного смысла — это смена ряда, а не его движение.

   Оставлено мгновенным намеренно: кольцо фокуса (задержка ломает ориентацию при табе),
   перерисовка графика при смене диапазона, смена активной сегментной кнопки (слайд пилюли
   обещал бы связность, которой в данных нет), значения цифр — никаких count-up. */

/* Появление данных на месте скелетона: только opacity, 200 мс. translateY здесь намеренно
   нет — сдвиг вернул бы ровно тот CLS, ради которого скелетоны и держат высоту. */
@keyframes data-in { from { opacity: 0; } }

/* Нажатие — transform в списке переходов: :active даёт translateY(1px) (контракт B5),
   и без перехода отпускание кнопки щёлкает. Длительности берутся из собственных
   шорткатов компонентов (все --dur-fast), здесь дописывается только сам список свойств. */
.btn, .icon-btn, .user-trigger { transition-property: background, color, border-color, box-shadow, transform; }

/* Переключение темы — кроссфейд 200 мс и только на время переключения.
   Класс вешает applyTheme() (ui.js / theme.js) и снимает через --dur, поэтому ПЕРВАЯ
   отрисовка страницы кроссфейда не получает: сервер уже отдал верную тему в <html>.
   `html.theme-switching *` = специфичность (0,1,1) и стоит последним в файле — этого
   хватает, чтобы перебить собственные transition компонентов без !important (запрет B8). */
html.theme-switching *,
html.theme-switching *::before,
html.theme-switching *::after {
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease),
              color var(--dur) var(--ease), fill var(--dur) var(--ease),
              stroke var(--dur) var(--ease), box-shadow var(--dur) var(--ease);
}

/* ---------- Адаптив ---------- */
@media (max-width: 1000px) {
  /* Боковик уходит НАД текстом: оглавление в одну колонку под статьёй бесполезно */
  .article-layout { grid-template-columns: minmax(0, 1fr); gap: 0; }
  .article-side { position: static; order: -1; margin-bottom: var(--space-6); max-width: var(--measure-prose); max-height: none; overflow: visible; }
  .toc { border-left: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); padding: var(--space-4) 0; }
  .toc-list a.is-current::before { display: none; }
}
@media (max-width: 860px) {
  /* Свёрнутое оглавление: раскрытое оно съедало ~220px первого экрана и отодвигало h1
     за сгиб, а скролл-спай на телефоне всё равно бесполезен. Атрибут open снимает
     ui.js / theme.js по этому же брейкпоинту; без JS остаётся раскрытым — то есть
     ровно как было, деградация не ухудшается. */
  .toc-d > .toc-title { cursor: pointer; margin-bottom: 0; }
  .toc-d[open] > .toc-title { cursor: pointer; margin-bottom: var(--space-2); }
  .toc-d > .toc-title::after {
    content: ""; margin-left: auto; width: 8px; height: 8px;
    border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor;
    rotate: 45deg; translate: 0 -2px;
    transition: rotate var(--dur-fast) var(--ease);
  }
  .toc-d[open] > .toc-title::after { rotate: 225deg; translate: 0 2px; }
}
@media (max-width: 860px) {
  .nav { display: none; }
  .burger { display: inline-flex; }
  .topbar-in { gap: var(--space-3); }
  .site-foot-in { grid-template-columns: 1fr; gap: var(--space-6); }
}
/* Марки уходят из шапки раньше навигации: с шестью пунктами меню и «Sign in» ряд
   перестаёт помещаться уже около 1080px (в теме /learn/ — те же элементы). Ниже они
   есть в мобильном меню и в подвале, так что нигде не пропадают совсем. */
@media (max-width: 1080px) {
  .topbar-soc { display: none; }
}
@media (min-width: 861px) {
  .mnav { display: none; }
}
/* В меню ряд отделён от пунктов навигации и крупнее: там его нажимают пальцем */
.mnav-soc { margin-top: var(--space-2); padding-top: var(--space-3); border-top: 1px solid var(--line-soft); gap: var(--space-2); }
.mnav-soc .soc-link { width: var(--control-h); height: var(--control-h); color: var(--ink2); }
@media (max-width: 640px) {
  .wrap { padding: 0 var(--space-4); }
  main { padding-bottom: var(--space-8); }
  /* Перетяжка: на 375px хвост предложения уводил её в четыре строки, поэтому остаётся
     только суть — тема статьи и CTA. Режем классом, а не JS: разметка одна на все ширины,
     и то, что слышит скринридер, совпадает с тем, что видно на экране.
     Пилюля и крестик подрастают до 32px — их тут нажимают пальцем. */
  .promo-bar-rest { display: none; }
  .promo-bar-cta, .promo-bar-x { height: var(--control-h-sm); }
  .promo-bar-x { width: var(--control-h-sm); }
  .logo-sub { display: none; }
  .logo img, .logo svg { width: 28px; height: 28px; }
  .logo-word { font-size: var(--fs-h3); }
  /* Бургер и переключатель темы — самые нажимаемые кнопки сайта, а на телефоне они были
     32×32 при рекомендованном минимуме 40. Растёт только зона нажатия, не иконка. */
  .icon-btn { width: var(--control-h); height: var(--control-h); }
  /* В одном ряду — одна высота (B2). Рядом с 40px бургером и переключателем «Sign in»
     на --control-h-sm давал третью высоту в шапке. Кегль подписи не меняем. */
  .topbar-right .btn.sm { height: var(--control-h); }
  /* То же правило для разрушительных действий в таблицах админки: «Delete» рядом с тремя
     соседними кнопками 32px — единственное место, где промах пальцем необратим. */
  .btn.sm.danger { height: var(--control-h); }
  /* Три колонки подвала на 375px переносились по словам — на узком экране их две */
  .foot-nav { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-6); }
  .site-foot { margin-top: var(--space-9); padding: var(--space-6) 0 var(--space-7); }
  .share { gap: var(--space-2); }
}
@media (max-width: 430px) {
  .topbar-in { gap: var(--space-2); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  html { scroll-behavior: auto; }
}
