/* ============================================================================
   UI-Kit редизайна «Рейтинг цифровой зрелости» — токены + компоненты.
   Источник истины: figma-spec.md (узел 15859:247741, desktop 1440, dark).

   Пространство имён — префикс `u-`. Файл автономен: не переопределяет ничего
   вне своего namespace, поэтому подключается рядом со старым styles.css.

   ДВА ЖЁСТКИХ ПРАВИЛА МАКЕТА:
   1) Радиусов нет нигде — border-radius: 0 у всех компонентов.
   2) Теней нет. Единственный эффект — backdrop-filter: blur(.8rem) у u-btn.is-glass.

   МАСШТАБИРУЕМАЯ СЕТКА (как на idaproject.com):
   корневой размер привязан к ширине окна, все размеры кита — в rem
   (значение макета в px ÷ 10). Правило перевода: 20px → 2rem, 36px → 3.6rem.
   ЕДИНСТВЕННОЕ исключение — хайрлайны 1px / .5px и контур :focus-visible:
   они остаются в px, иначе на широких экранах линии становятся толстыми и мутными.
   Это единственное место, где кит выходит за свой namespace (правило для html):
   без него rem-сетка не работает, поэтому оно живёт здесь, у истока сетки.
   ========================================================================== */

/* 1rem = ровно 10px при ширине окна 1440 (1440 × 0.6944% = 10.0px).
   Ниже 1024 фиксируем 10px: дальше вниз масштабировать нечего — там своя
   мобильная раскладка. Ниже 414 снова тянем по ширине (как idaproject.com). */
html { font-size: 0.6944vw; }
@media (max-width: 1023.98px) { html { font-size: 10px; } }
@media (max-width: 413.98px)  { html { font-size: 2.4155vw; } }


:root {
  /* --- Цвета: переменные Figma ------------------------------------------- */
  /* Волна «обновление» (макеты 15970:512315 / 15970:521591): полотно ВСЕЙ
     страницы стало таким же тёмно-синим, как первый экран. Шва между первым
     экраном и списком в макете нет, поэтому значение общее с --u-bg-hero. */
  --u-bg-page:              #081016;             /* полотно страницы (было #1b1c1d) */
  --u-bg-hero:              #081016;             /* полотно первого экрана (макет 15966:506716) */
  --u-text-muted-2:         rgba(229,230,242,.56); /* неактивный таб и плейсхолдер на первом экране */
  --u-bg-neutral-hover:     #27272a;             /* bg/neutral/primary/hover   */
  --u-brand:                #d5ff0c;             /* bg/brand/primary/enabled   */
  --u-border-white-secondary: rgba(255,255,255,.24); /* border/white-secondary */
  --u-border-danger:        #ef3b24;             /* border/danger-primary      */
  --u-icon-white:           #ffffff;             /* icon/white-primary         */
  --u-text-primary:         #ffffff;             /* text/white-primary         */
  --u-text-secondary:       rgba(255,255,255,.6);/* text/white-secondary       */
  --u-text-on-brand:        #1b1c1d;             /* text/default-primary       */
  --u-text-muted:           #95959d;             /* text/default-secondary     */
  --u-text-danger:          #ef3b24;             /* text/danger-primary        */

  /* --- Цвета: 7 значений, захардкоженных в макете (заведены токенами) ---- */
  --u-divider:              rgba(255,255,255,.12); /* все линии-разделители    */
  --u-text-disabled:        #53535a;            /* недоступный пункт фильтра */
  --u-bg-compare:           #212429;            /* кнопка «сравнить себя» (макет 15918:97025) */
  --u-border-compare:       #3a3b40;            /* её рамка */
  --u-space-16:             1.6rem;
  --u-divider-soft:         rgba(255,255,255,.08); /* все линии СПИСКА: между строками и внутри группы-позиции */
  --u-glass:                rgba(39,39,42,.64);    /* фон стеклянной кнопки    */
  --u-cell-partial-bg:      #3a4323;               /* фон ячейки «частично»    */
  --u-cell-no-bg:           #3f272c;               /* фон ячейки «нет»         */
  --u-text-quaternary:      rgba(255,255,255,.32); /* «/ 12» рядом с баллом    */
  /* Бейдж места вне подсвеченных — белый 8 % ПРОЗРАЧНОСТЬЮ (макет 15970:512498).
     На полотне #081016 это ровно #1c2329; на подсвеченной ховером строке
     подложка просвечивает, как и в Figma. */
  --u-bg-rank:              rgba(255,255,255,.08); /* бейдж места вне топ-3 (было #27272a) */
  /* Приглушённый лайм у мест 2 и 3 в виде «по топу ЕРЗ» (15970:512415 / 512455) */
  --u-rank-2:               rgba(213,255,12,.8);
  --u-rank-3:               rgba(213,255,12,.6);
  /* Закреплённые кнопки низа: «сравнить себя», «наверх», «выводы»
     (15970:522903 / 522911 / 522916). Тот же белый 8 %, но НЕПРОЗРАЧНЫЙ —
     под полосой прокручивается список, сквозь него он просвечивать не должен. */
  --u-bg-btn-bar:           #1c2329;
  /* Ховера кнопок в макете нет: сохраняем прежний шаг +4 % белого (было
     #27272a → #313135), пересчитанный от нового полотна. */
  --u-bg-btn-bar-hover:     #262d32;
  --u-border-btn:           rgba(255,255,255,.12);  /* обводка кнопки «сравнить себя» — 15974:530587 (было #3d3d3e) */
  --u-text-dim:             #848891;               /* подпись кнопки в раскрытом состоянии — 15974:530570 */
  --u-brand-dim:            #a8cb02;               /* приглушённый лайм: ховер неоновой пилюли */
  --u-status-third:         rgba(213,255,12,.64);  /* статус «через сторонний сервис» — как глиф в списке (волна «обновление») */
  /* Рамка карточки-подсказки. Волна «обновление» (макет 15970:521534): на новом
     полотне #081016 прежний серый #363738 заменён синеватым #262d32 — тем же,
     что у ховера нижних кнопок. Задан цветом, а не прозрачностью (как в Figma). */
  --u-border-card:          #262d32;               /* было #363738 */
  --u-brand-hard:           #d5ff0c;               /* лайм в hard-coded виде   */
  /* Кнопки полосы эталона «Сменить» и «×» (макет 15970:523019 / 523024): тот же
     непрозрачный #1c2329, что у закреплённых кнопок низа. */
  --u-bg-control:           #1c2329;               /* было #27272a */
  /* Верхняя граница полосы эталона (макет 15970:522927) — общий хайрлайн 12 %. */
  --u-border-band:          rgba(255,255,255,.12);  /* было #373838 */

  /* --- Страница-кейс «Выводы экспертов», полная версия (макет 15985:538666) ---
     Длинный текст исследования нельзя красить в чистый #fff: 14px на колонке 690
     чистым белым выжигает глаз на длинной прокрутке. В Figma тело кейса — белый
     88 %, это же значение и берём. Остальное — та же шкала: подписи к рисункам и
     примечания тише тела, линии таблиц — общий хайрлайн 12 %, зебра чуть заметнее
     полотна. Все четыре значения в макете захардкожены, поэтому заводим токенами. */
  --u-text-body:          rgba(255,255,255,.88);  /* тело статьи (Figma: text/white 88 %) */
  --u-bg-tag:             #1c2329;                /* подложка v-tag: тот же белый 8 % на полотне */
  --u-bg-zebra:           rgba(255,255,255,.03);  /* чётная строка таблицы */
  /* ПОТРЕБИТЕЛЕЙ СЕЙЧАС НЕТ: цитата в разделе выводов набирается прямо на
     полотне (решение заказчика — подложку и брендовую линию убрать, начало
     блока держит хайрлайн). Токен оставлен: он есть в макете и в UI-KIT.md,
     и удалять его только потому, что единственный потребитель перестал им
     пользоваться, — значит расходиться с описанием кита. */
  --u-bg-quote:           rgba(255,255,255,.04);  /* подложка блока цитаты */

  /* --- ТРИ ТОНА ЗНАЧЕНИЯ В ПЛОСКОМ СПИСКЕ (макет 16020:544182, узел 16024:544943) ---
     В макете значение строки покрашено в один из трёх цветов: «Высокий» —
     лайм, «Растущий» — оранжевый, «Пилоты» — приглушённый серый. Это не три
     новых цвета, а ОДНА шкала внимания, поэтому и заводится тремя ролевыми
     алиасами: правило списка не должно выбирать между --u-brand и
     --u-text-muted-2 по смыслу данных.
     Новое значение здесь ровно одно — #ffba0c: ни в ките, ни в styles.css его
     до сих пор не было. Два других — существующие токены под новым именем. */
  --u-tone-high:          var(--u-brand);          /* сервис стал нормой   */
  --u-tone-mid:           #ffba0c;                 /* растёт — НОВЫЙ цвет макета */
  --u-tone-low:           var(--u-text-muted-2);   /* пилоты, единицы      */
  /* Подложка широкой плашки под рисунок с прозрачным фоном (узел 16023:544751).
     Значение то же самое #1c2329, что у плашки тега, закреплённых кнопок и
     полосы эталона, — но роль четвёртая, и правило блока обязано называть
     свою, иначе смена одной из трёх ролей молча перекрасит рисунки. */
  --u-bg-fill:            var(--u-bg-tag);

  /* --- Цвета, доопределённые нами (в макете состояний нет) --------------- */
  --u-brand-pressed:        #bae000;  /* bg/brand/primary/pressed из переменных Figma */
  --u-focus-ring:           #d5ff0c;  /* контур :focus-visible                        */

  /* --- Цвета глифов СТАТУСНОЙ ЯЧЕЙКИ на десктопе (макет 15970:512367) -----
     Заливки и обводки у ячейки больше нет — состояние читается только по
     глифу, поэтому его цвет задаёт CSS (маска), а не сам SVG-файл.
     Значения сняты из ассетов макета: check #D5FF0C, arrow-right-up #D5FF0C
     при opacity .64 у группы, close #EF3B24, «?» — текст #848891. */
  /* ШАГ БАРАБАНА ПОДСКАЗКИ В МОБИЛЬНОМ СРАВНЕНИИ (макет 16032:10848).
     Две секунды — заказчик назвал это число прямо. Пять значений дают полный
     круг за 10 с: столько же, сколько человек тратит на первый проход глазами
     по таблице, то есть легенду он увидит, пока разбирается. */
  /* ПЕРВЫЙ ЭКРАН СТАТЬИ (макет 16020:544182). В рейтинге первый экран во всю
     высоту окна — там под заголовком видео, и обрезать его нельзя. В статье
     видео нет, полотно ровное, и растянутый на 100vh экран давал 280px пустоты
     под тегом: заголовок болтался в середине, а полоса управления уезжала за
     сгиб. В макете высота фиксированная — 583, с воздухом 164 сверху и снизу
     и шагом 32 между надзаголовком, заголовком и тегом. */
  /* ГРАФИК-ПОЛОСА СТАТЬИ (макет 16051:35560). Плоский формат вместо SVG:
     строка подписей сверху, под ней дорожка с заливкой и числом.
     Заливка не доходит до правого края — на её границе стоит белая черта, и
     сразу за чертой печатается значение. Так число всегда читается на тёмном,
     сколько бы ни было заполнено: внутри лаймовой заливки белая цифра на
     лаймовом пропадала бы, а вынесенное к правому краю значение теряло бы
     связь со своей полосой. */
  /* Карточка ссылки и блок авторов статьи (макет 16020:544245..544248). */
  /* Мобильный шаг между разделами статьи — 64 (десктопный 80). */
  /* Бегущая строка надзаголовка на мобиле (эталон a101-redesign). */
  --u-eyebrow-dur:  24s;
  --u-eyebrow-gap:  38px;
  --u-eyebrow-fade: 24px;
  /* Лента надзаголовка на узком экране — по макету 16294:105749: зазор 20
     (он же между подписью и точкой-разделителем), фейд по краям 48. */
  --u-eyebrow-gap-sm:  20px;
  --u-eyebrow-fade-sm: 48px;
  --u-eyebrow-dot:      2px;
  --case-gap-64: 64px;
  /* Карточка ссылки по макету 16313:111774: строка 690 = текст 473 (из них
     56 — правое поле) + обложка 217, высота ряда 122. Прежние 400px под
     обложку и пропорция Open Graph 1.91 оставляли тексту 250 и ломали строку
     на пять строк вместо трёх. Числа в px: строка живёт в колонке 690, а она
     от ширины окна не зависит. */
  --u-linkcard-og:       217px;  /* ширина области под обложку ресурса */
  --u-linkcard-h:        122px;  /* высота ряда карточки */
  --u-linkcard-pr:        56px;  /* правое поле текста до обложки */
  --u-linkcard-cta-h:     24px;  /* кнопка-пилюля: h 24, px 12, gap 4 */
  /* ФОТО АВТОРА И ВСЯ ГЕОМЕТРИЯ ГРАФИКА-ПОЛОСЫ — В px, А НЕ В rem.
     Корневой размер привязан к ширине окна (html{font-size:0.6944vw}), а колонка
     статьи жёстко 690px (см. --case-w-text). В rem дорожка 4.8rem на 1920 давала
     бы 64px внутри той же колонки 690, а фото автора — 85px в ячейке 335: блок
     распухал бы, пока колонка стоит на месте. Числа этих блоков живут в системе
     статьи, а не кита, поэтому и единица у них та же, что у колонки. */
  --u-author-ava:    64px;
  --u-bar-h:         48px;   /* высота дорожки (макет 16301:108186) */
  --u-bar-rule:      2px;    /* белая черта на обоих торцах заливки */
  --u-bar-gap:       12px;   /* строка подписей → дорожка (было 16) */
  --u-bar-pad:       16px;   /* черта заливки → число, снаружи и внутри */
  /* Число на полосе прижато к верхней кромке дорожки: 12 сверху (сверка
     с макетом). Дорожка 48, число 16/1.1 — по центру оно стояло бы на 15.2,
     то есть на три пункта ниже макетного. */
  --u-bar-val-top:   12px;
  --u-bar-accent:   #657a00;  /* «выделить акцентом» — графики ниже зелёной линии */
  /* БАЗОВАЯ ЗАЛИВКА СРАВНЯЛАСЬ С «ВЛОЖЕННОСТЬЮ» (было #232a31). В обновлённом
     компоненте v-filter-1440 тональной игры на заливке больше нет: все дорожки
     одного тона #1c2329 (он же подложка тега и фото автора), а цвет ушёл на
     статус справа. Токен общий на все графики статьи, потому что компонент
     в макете один — правка задевает и графики ниже зелёной линии, там это
     сдвиг тона на шаг, а не смена раскладки. */
  --u-bar-plain:    #1c2329;  /* обычная полоса — белый 8 % на полотне */
  /* ТРЕТЬЯ СТУПЕНЬ ВОССТАНОВЛЕНА. Когда plain опустили с #232a31 до #1c2329 по
     макету, muted оказался ровно таким же — и у семи полос ниже зелёной линии
     (Крым, Башкортостан, Краснодарский край; три верхних корзины баллов;
     «только клиентская бронь») пропало отличие от обычных. Схема там
     трёхуровневая — accent / plain / muted, — и схлопывание в две ступени
     теряет смысл, а не оттенок. Держим тот же шаг по яркости, что и prev:
     plain — белый 8 % на #081016, muted — белый 4 %. */
  --u-bar-muted:    #121a1f;  /* вложенность — белый 4 % на полотне */
  --u-bar-track:    rgba(255,255,255,.08);  /* рамка дорожки */
  --u-case-hero-h:   58.3rem;
  --u-case-hero-pad: 16.4rem;
  --u-case-hero-gap:  3.2rem;
  /* МОБИЛЬНЫЙ ПЕРВЫЙ ЭКРАН СТАТЬИ ЗАДАН В px, А НЕ ДОЛЕЙ ДЕСКТОПНОГО.
     Десктопные значения стоят в rem, а корневой размер привязан к ширине окна,
     и на 390 те же 58.3rem давали 549 при макетных 372 — экран растягивался
     вдвое против макета 16294:105749 (372 + 28 панели). Высота, воздух и шаг
     там свои, а не производные: макет делит первый экран 80 / 212 / 80. */
  --u-case-hero-h-sm:   372px;
  --u-case-hero-pad-sm:  80px;
  --u-case-hero-gap-sm:  20px;
  --u-cmp-drum-step: 2000ms;
  --u-cmp-drum-h:    40px;   /* высота окна барабана = рост прежней плашки */
  --u-cmp-drum-slide: 420ms; /* ход одной прокрутки */
  --u-glyph-own:            #d5ff0c;
  --u-glyph-third:          rgba(213,255,12,.64);
  --u-glyph-no:             #ef3b24;
  --u-glyph-unknown:        #848891;

  /* --- Цвета глифов, запечённые внутри SVG-ассетов (для справки) --------- */
  --u-glyph-check:          #1b1c1d;
  --u-glyph-arrow-up-right: #caff00;
  --u-glyph-close:          #ef3b24;
  --u-glyph-delta-up:       #d5ff0c;
  --u-glyph-delta-down:     #ef3b24;
  --u-glyph-delta-same:     #95959d;

  /* --- Спейсинг-шкала 0 / 2 / 4 / 8 / 12 / 20 / 32 (в rem: px ÷ 10) ------ */
  --u-space-0:   0rem;   /* spacing/spacing-0  */
  --u-space-2:   0.2rem; /* spacing/spacing-05 */
  --u-space-4:   0.4rem; /* spacing/spacing-1  */
  --u-space-8:   0.8rem; /* spacing/spacing-2  */
  --u-space-12:  1.2rem; /* spacing/spacing-3  */
  --u-space-20:  2rem;   /* layout/margin/left-right, gap строк, gap фильтров */
  --u-space-32:  3.2rem; /* заголовки колонок → первая строка */

  /* --- Layout ------------------------------------------------------------ */
  --u-viewport:      144rem; /* layout/viewport/width (справочно: ровно ширина окна) */
  --u-page-margin:   2rem;   /* layout/margin/left-right */
  --u-content-w:     140rem; /* 1440 − 2×20 при 1440; на других ширинах — тянется */
  --u-col-dev-w:     27.6rem;  /* колонка застройщика */
  --u-cells-w:       57.2rem;  /* колонка 12 статусных ячеек */
  --u-col-score-w:   27.6rem;  /* колонка балла */
  --u-cell-size:     3.6rem;   /* сторона квадратной ячейки / бейджа места */
  /* Зазор между ячейками. Выведен из макета: 12 ячеек по 36 в блоке 572 дают
     ровно (572 − 432) / 11 = 12.7272. Раньше он получался сам из space-between,
     теперь задаётся ЯВНО (см. .u-cells): при неполном наборе критериев группа
     должна центрироваться с тем же шагом, а не растягиваться по ширине. */
  --u-cell-gap:      calc((var(--u-cells-w) - 12 * var(--u-cell-size)) / 11); /* 1.27272rem */
  --u-cell-step:     calc(var(--u-cell-size) + var(--u-cell-gap));            /* 4.87272rem */
  --u-btn-h:         3.2rem;
  --u-control-h:     1.6rem;   /* высота строки фильтра / таба / ссылки */
  --u-tabsec-w:      28.5rem;  /* tab-section полосы фильтров (figma-spec-2 §1) */
  --u-search-w:      15.9rem;  /* поле поиска: 181 − 14 иконка − 8 gap = 159 */
  /* Контейнер названия компании. В макете 158 (276 − 118), но 14 из 100 названий
     обрезались многоточием — заказчик попросил 237 (правка №7). Разница 79px
     уходит в пустой зазор между колонкой застройщика и блоком ячеек (на 1440 он
     138px), поэтому наезда на ячейки нет. */
  --u-name-w:        23.7rem;
  --u-header-h:      5.6rem;   /* хедер: высота контента, фолбэк для --header-h */
  /* ЗАПАС, НА КОТОРОМ ЗАКРЕПЛЁННАЯ ПАРА УХОДИТ ПЕРЕД ФУТЕРОМ.
     Раньше это число жило в app.js (FOOT_HIDE_GAP = 100) и отсчитывалось от
     НИЗА окна: «футер подошёл к нижней кромке экрана». На короткой выдаче
     (узкий фильтр — 4 строки) футер стоит у низа постоянно, поэтому хедер
     пропадал навсегда. Теперь отсчёт идёт от нижней кромки самой пары —
     «футер дошёл до закреплённых», см. footHideBand() в app.js.
     ИСКЛЮЧЕНИЕ из rem-перевода: значение читает JS (rootMargin считается в
     реальных px), а getComputedStyle отдаёт кастомное свойство как есть,
     без перевода rem→px. */
  --u-foot-hide-gap: 100px;
  --u-icon-14:       1.4rem;
  --u-icon-16:       1.6rem;
  /* НА СКОЛЬКО РАСТЁТ КНОПКА С ВЫЕЗЖАЮЩЕЙ ИКОНКОЙ (has-icon-slide).
     Замер эталона idaproject.com: 131.8 → 165.8, то есть ровно +34px — это
     глиф 14 плюс 20 отступа до подписи. Держим одним токеном, потому что
     механику переиспользуют два разных компонента (.u-btn в ките и .cmp-btn
     в полосе сравнения), а совпасть они обязаны до пикселя. */
  --u-icon-slide:    calc(var(--u-icon-14) + 2rem);
  --u-glass-blur:    0.8rem;
  --u-row-pb:        2rem;   /* padding-bottom строки таблицы */
  /* В Figma шаг строк ровно 76px (36 контент + 20 padding + 20 gap), а линия-
     разделитель стоит на границе padding и НЕ занимает места. В CSS
     border-bottom место занимает, поэтому gap уменьшен на толщину линии:
     36 + 20 + 1 + 19 = 76. Линия при этом остаётся на той же координате. */
  --u-row-gap:       calc(2rem - 1px);
  /* ИСКЛЮЧЕНИЕ из rem-перевода: хайрлайны остаются в px — на 1920 линия в rem
     стала бы 1.33px и рендерилась мутной полосой. */
  --u-hairline:      1px;
  --u-hairline-thin: .5px;   /* граница красной ячейки */

  /* --- Типографика: семейства ------------------------------------------- */
  --u-font-sans: 'Aeonik Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
  --u-font-display: 'Loos Wide', 'Aeonik Pro', -apple-system, BlinkMacSystemFont, Arial, sans-serif;
  --u-font-mono: 'JetBrains Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
  --u-weight-medium: 500;
  --u-weight-regular: 400;

  /* --- Типографика: 5 именованных стилей Figma (размеры и трекинг в rem) - */
  /* Label/L2 — Aeonik Pro Medium 24 / 1.2 / −1% */
  --u-l2-size: 2.4rem;  --u-l2-lh: 1.2;  --u-l2-ls: -0.024rem;
  /* Label/L4 — Aeonik Pro Medium 16 / 1.1 / −1% */
  --u-l4-size: 1.6rem;  --u-l4-lh: 1.1;  --u-l4-ls: -0.016rem;
  /* Label/L5 — Aeonik Pro Medium 14 / 1.1 / −0.5% */
  --u-l5-size: 1.4rem;  --u-l5-lh: 1.1;  --u-l5-ls: -0.007rem;
  /* Label/L6 — Aeonik Pro Medium 12 / 1.1 / 0 */
  --u-l6-size: 1.2rem;  --u-l6-lh: 1.1;  --u-l6-ls: 0rem;
  /* Text/T1 [M] — Aeonik Pro Medium 16 / 1.3 / −1% */
  --u-t1-size: 1.6rem;  --u-t1-lh: 1.3;  --u-t1-ls: -0.016rem;
  /* Text/T2 [M] — Aeonik Pro Medium 14 / 1.3 / −1% (figma-spec-4: описание
     и строка участия во всплывающей подсказке ячейки) */
  --u-t2-size: 1.4rem;  --u-t2-lh: 1.3;  --u-t2-ls: -0.014rem;

  /* --- Типографика: роли, не покрытые именованными стилями --------------- */
  /* Заголовок hero — Loos Wide Medium 48 / 1.1 / −0.48px / uppercase */
  --u-display-size: 4.8rem; --u-display-lh: 1.1; --u-display-ls: -0.048rem;
  /* Заголовки колонок — JetBrains Mono Medium 10 / 1.2 / −0.2px / uppercase */
  --u-mono-size: 1rem; --u-mono-lh: 1.2; --u-mono-ls: -0.02rem;
  /* подписи колонок матрицы — тот же стиль, но без нижнего предела (см. выше) */
  --u-col-head-size: 1rem;
  /* нижний предел размера текста — работает только ≥1024 (см. ниже) */
  --u-fs-floor: 0px;

  /* --- Motion (доопределено нами; в макете переходов нет) --------------- */
  --u-dur: 120ms;
  --u-ease: linear;
  /* Хедер повторяет механику ховеров idaproject.com: .4s ease-in-out,
     переходы только по конкретным свойствам (никогда `all`). */
  --u-dur-nav: .4s;
  --u-ease-nav: ease-in-out;
  /* ПОДЛОЖКА ХЕДЕРА — отдельная, КОРОТКАЯ длительность (правка заказчика №1).
     .4s ловилась глазом: при резком скролле подпись и кнопки хедера успевали
     наехать на полосу фильтров ещё на прозрачном фоне. 140ms — верх «мгновенной»
     зоны (120–150ms), фон читается как уже стоящий, но не мигает при медленной
     прокрутке. Порог включения тоже сдвинут раньше — см. HEADER_SOLID_GAP в app.js. */
  --u-dur-header: 140ms;
  /* Ховер строки таблицы — сдержанный: только фон, ≤150ms, без движения. */
  --u-dur-row: 140ms;
  --u-row-hover: rgba(255,255,255,.04);
  /* Посимвольный ховер (u-split): ступенька задержки на букву.
     20ms × 9 букв («продукты» / «контакты») + .4s ≈ .58s — в рамке 0.4–0.6s. */
  --u-split-stagger: 20ms;
  --u-split-shift: 110%;

  /* --- ЗАСТАВКА ПЕРВОГО ЭКРАНА (этап 1) ---------------------------------
     Механика снята с главной idaproject.com: строка заголовка выезжает из-под
     кромки обёртки (translateY(120%) → 0, .8s ease), соседние блоки идут со
     ступенькой 0.1s. Держим числа здесь, а не в правилах: раскладку заставки
     правят целиком, и разъехавшиеся по файлу значения пришлось бы искать.
     Стартовые задержки считаются от t=0 — момента, когда данные загружены и
     #appRoot показан (см. runHeroIntro в app.js). */
  --u-intro-fill: #1c2329;        /* заливка; тот же тон, что и бейдж места: белый 8 % на полотне */
  /* ШТОРКА НЕ РАСТВОРЯЕТСЯ, А УЕЗЖАЕТ ВВЕРХ (покадровый замер эталона,
     первая загрузка idaproject.com): нижняя кромка идёт снизу вверх и
     открывает уже готовую страницу под собой. Отсюда transform, а не opacity:
     растворение показывало бы обе картинки разом, а на эталоне видна ровно
     одна кромка.
     Ход почти линейный, с лёгким замедлением в начале (29 % времени → 25 %
     пути, 52 % → 47 %, 85 % → 86 %) — это ровно cubic-bezier(.4,0,.2,1). */
  --u-intro-fill-dur: 1000ms;
  --u-intro-fill-ease: cubic-bezier(.4, 0, .2, 1);
  --u-intro-fill-shift: -100%;    /* конечное положение шторки: целиком за верхней кромкой */
  /* Тень над первым экраном (видео или запасной кадр) уходит СИЛЬНО дольше
     шторки — прямая правка заказчика «плавнее».

     КРИВАЯ СИММЕТРИЧНАЯ, А НЕ ВЫПУСКАЮЩАЯ (правка ошибки прошлой спеки).
     Здесь стояла cubic-bezier(.22,1,.36,1) — она подходит к нулю асимптотически:
     замер показал 39 % пути за первые 10 % времени, половину пути уже на 13 %,
     а opacity ≤ 0.05 (глазом — «тени нет») на 47 % длительности, то есть на
     1130 мс вместо 2400. Получались две беды разом: рывок в начале вместо
     обещанной плавности и секунда пустоты между выходом из тени и зелёными
     линиями ролика, которые идут на 2400.

     cubic-bezier(.3,0,.7,1) — симметричный ease-in-out, подобран по трём
     измеримым признакам спеки (замер, а не формула):
       половина пути  — на 50 % длительности (требование 45–55 %);
       opacity ≤ 0.05 — на 87 % длительности (требование ≥ 85 %);
       за первые 10 % времени уходит 3 % пути (требование ≤ 15 %).
     Мягкий вход снимает ступеньку на старте, а мягкий выход держит остаток
     тени до самого конца — картинка выходит из неё ровно к 2400, когда
     стартуют линии. На этот же момент завязан пуск ролика — см. --u-intro-video-at. */
  --u-intro-media-dur: 2400ms;
  --u-intro-media-ease: cubic-bezier(.3, 0, .7, 1);
  /* Строки заголовка: .8s ease — дефолтная кривая эталона, менять нельзя,
     на ней держится узнаваемый «тяжёлый» выезд. */
  --u-intro-line-dur: 800ms;
  --u-intro-line-ease: ease;
  /* Старт первой строки — 57 % хода шторки (замер эталона: текст проявляется
     не после шторки, а поверх её движения). 0.57 × 1000 ≈ 570. Именно это
     наложение и даёт плавность, на которую указал заказчик. */
  --u-intro-line-at: 570ms;
  --u-intro-line-step: 100ms;     /* ступенька между строками (замер эталона) */
  --u-intro-line-shift: 120%;     /* глубина ухода строки под кромку маски */
  /* Запас маски снизу: без него overflow:hidden срезает выносные элементы
     («р», «у»), а строки схлопываются по вертикали. Компенсируется
     отрицательным margin — высота заголовка обязана остаться прежней. */
  --u-intro-line-pad: .14em;
  /* Дескриптор выходит, когда первая строка заголовка уже доехала:
     570 + 800 = 1370, плюс 100 — чтобы движение и проявление не наложились.
     Дозревает к 2070, то есть текст встаёт раньше, чем картинка выйдет из
     тени (2400) — так и задумано. */
  --u-intro-lead-dur: 600ms;
  --u-intro-lead-ease: ease-in-out;
  --u-intro-lead-at: 1470ms;
  /* ПУСК РОЛИКА — С НУЛЕВОГО КАДРА, С ВЫЕЗДА ЛАЙМОВЫХ ЛИНИЙ (правка 23.09).
     Линии пробегают ОДИН раз в начале ролика (старт 0.3 с, пик 1.2–1.6 с, к
     3.5 с затухают). Ролик стоит на нулевом кадре (он же постер) под шторкой
     и трогается так, чтобы линии вышли ровно из-под её уходящей кромки:
       video-at = fill-dur − video-lines = 1000 − 300 = 700.
     Пик линий тогда приходится на 1.9–2.3 с, когда тень (media-dur 2400)
     почти сошла.
     Прежняя схема ставила ролик в хвост и ждала, пока петля вернёт его на
     ноль: оборот хвост → начало падал на ~2.1 с и был виден как прыжок.
       video-lines — момент линий ВНУТРИ ролика; при замене ролика поправить
                     его и video-at вместе. */
  --u-intro-video-at: 700ms;
  --u-intro-video-lines: 300ms;

  /* --- УХОД НА ДРУГОЙ ДОКУМЕНТ: ПОСЛЕДОВАТЕЛЬНЫЕ ШТОРКИ (этап 2) ---------
     Покадровый замер эталона (idaproject.com) переписал прежнюю модель. Уход
     там — не одна заливка и не растворение, а ДВЕ ПОЛОВИНЫ ЭКРАНА по 50 %
     ширины, которые поднимаются снизу вверх, причём правая стартует позже
     левой. Отсюда три числа ниже.

     ПОЧЕМУ ДВЕ ПОЛОВИНЫ, А НЕ ОДНА ЗАЛИВКА. Одна заливка даёт одну кромку —
     движение читается как «штора опустилась», и уход неотличим от прихода.
     Сдвиг между половинами рождает вторую кромку и диагональ: глаз видит
     направление «слева направо», то есть ровно то же, что задаёт порядок
     чтения. Это единственное, что отличает уход от прихода на эталоне.

     ПОЧЕМУ ИМЕННО 165 мс. Замер по кадрам: значения translateY правой
     половины повторяют значения левой со сдвигом в 165 мс на всём ходу.
     Меньше — сдвиг не читается, обе кромки сливаются в одну; больше — вторая
     половина отстаёт настолько, что перестаёт восприниматься как часть одного
     движения (две отдельные анимации вместо одной). */
  --u-nav-curtain-dur: 900ms;
  --u-nav-curtain-stagger: 165ms;
  /* КРИВАЯ ПОДОБРАНА ПОД ЗАМЕР, А НЕ ПО НАЗВАНИЮ. Замеренная доля пути
     (34 мс → 5.6 %, 100 → 23.4, 200 → 45.9, 300 → 63.4, 400 → 74.8,
     500 → 83.5, 600 → 88.8, 683 → 96.3 %) не ложится ни на expo-out
     (RMSE 21.6 п.п.), ни на quint-out (17.4), ни на quart-out (13.2): все они
     стартуют рывком, а на эталоне первые 34 мс идут почти линейно (5.6 % за
     3.8 % времени). Численный подбор по восьми точкам даёт
     cubic-bezier(.09,.03,.2,.91): среднее отклонение 0.98 п.п., худшее 2.0.
     Контроль спеки «на половине времени ≈75 % пути»: кривая даёт 79.5 %,
     эталон в той же точке (450 мс, интерполяция 400↔500) — 79.2 %. */
  --u-nav-curtain-ease: cubic-bezier(.09, .03, .2, .91);
  /* КОГДА ОТДАВАТЬ ПЕРЕХОД. Не в конце анимации: полный ход второй половины
     кончается на 165+900 = 1065 мс, и ждать его — подарить человеку лишнюю
     секунду «залипшего» клика. Условие спеки: вторая половина прошла ≥95 %
     пути. На этой кривой 95 % набегает на 700 мс её собственного хода,
     плюс сдвиг 165 → 865 мс. В этот момент экран закрыт на 97.3 % площади,
     а браузер после location.href ещё сотни миллисекунд рисует СТАРУЮ
     страницу (шторка продолжает ехать) — к подмене документа половины
     доезжают до конца. Риска «мигнёт контент» нет и в обратную сторону:
     первый кадр приходящего документа — сплошная заливка того же тона
     (.v-intro-fill.is-boot), то есть подмена может только ДОБАВИТЬ
     перекрытия, но не убавить.

     ЧЕСТНАЯ ОГОВОРКА. «≥95 % пути» — это не «экран закрыт целиком»: в этот
     момент вверху правой половины ещё открыта полоса в 5 % высоты окна (45 px
     на 900). Замер живого перехода: запрос уходит на 903 мс, первый кадр
     нового документа — на ~960 мс, а шторка доезжает до 100 % на 1065 мс…
     точнее, последний кадр СТАРОГО документа (951 мс) уже полностью закрыт,
     то есть полосы никто не видит. Риск остаётся ровно один: мгновенный ответ
     из кеша может подменить документ на 880-й мс, и полоса схлопнется на один
     кадр — скачком В СТОРОНУ ПЕРЕКРЫТИЯ, не показав контента. Если этот кадр
     сочтут дефектом, лечится одним числом: 1030ms — момент, где правая
     половина прошла 99.5 % (остаток 4 px), ценой +165 мс к ожиданию. */
  --u-nav-curtain-go: 865ms;
  /* Смена раздела идёт ВНУТРИ документа и происходит мгновенно — браузеру
     нечего догружать. Поэтому подменять раздел на 865ms нельзя: правая
     половина там прошла 95%, сверху открыта полоса в 5% высоты, и подмена
     мигнула бы в ней. Ждём фактического покрытия: 1030ms — это 99.5% хода
     правой половины, остаток 4px. Для ухода на другой документ такой запас
     не нужен: там браузер рисует старую страницу ещё ~60ms и закрывает её
     сам (замерено: последний кадр старого документа закрыт на 100%). */
  --u-nav-curtain-cover: 1030ms;
  /* Уход шторки вверх после подмены раздела — как открытие страницы на
     эталоне: одно полотно, без сдвига между половинами. Сдвиг 165 мс живёт
     только в фазе закрытия; на уходе наверх едет сам контейнер шторки, а
     половины прибиты к нулю (см. .v-nav-curtain.is-up в styles.css) — иначе
     две независимые анимации расходятся на кадр-другой из-за того, что в
     момент подмены одна половина уже доехала, а вторая ещё в пути. Поэтому
     здесь ОДНА длительность на всю шторку и никакого stagger. */
  --u-nav-curtain-up-dur: 700ms;
  /* Страховка: если за это время переход так и не начался (обработчик завис,
     вкладка ушла в фон и таймеры притормозили), уходим всё равно — экран не
     должен остаться залитым без движения. Держим заведомо больше полного хода
     шторки (1065 мс), иначе страховка стреляла бы раньше основного таймера. */
  --u-nav-guard: 1200ms;

  /* Раскрытие приходящего контента — тот же механизм, что у строк заголовка
     первого экрана: обёртка overflow:hidden, внутренняя часть translateY(120%)
     → 0 за .8s ease со ступенькой 0.1s. Числа берём из --u-intro-line-*:
     заводить вторые значения нельзя, иначе первый экран и переходы поедут
     по-разному после первой же правки. */
  --u-reveal-at: 0ms;             /* общий сдвиг старта для группы блоков */

  /* Проявление спиннера-страховки. Он включается уже ПОСЛЕ заставки (данных
     нет к 2400 мс), поэтому появление обязано читаться как продолжение
     движения, а не как вспышка. 400 мс — между шторкой (1000) и уходом на
     другой документ (300). */
  --u-boot-loader-fade: 400ms;

  /* --- КОЛОНКИ И РИТМ РАЗДЕЛА ВЫВОДОВ ------------------------------------
     Три уровня ширины, держим рядом: арифметика у них общая и разъехаться
     они не имеют права.
       355 + 690 + 355 = 1400   — текст внутри полного полотна;
       118 + 237 + 690 + 237 + 118 = 1400 — медиа внутри него же.
     1400 — это ровно ширина контентной полосы на 1440 (#sectionReport>.wrap:
     max-width 1440 минус поля 20+20), поэтому полный уровень совпадает с
     кромкой страницы, а не выходит за неё.

     ПОЧЕМУ px, А НЕ rem, КАК ВЕСЬ ОСТАЛЬНОЙ КИТ. У проекта корневой размер
     привязан к ширине окна (html{font-size:0.6944vw}), и всё, что задано в rem,
     тянется вслед за окном. Колонке статьи тянуться нельзя: на 1920 она стала
     бы 920px, а строка — нечитаемо длинной. Раздел выводов исторически свёрстан
     в px именно по этой причине (см. комментарий у .case в styles.css) — числа
     колонки живут в той же системе.

     ПРЕФИКС case-, А НЕ u-: это не токены компонентов кита, а геометрия одного
     раздела. Лежат здесь только потому, что числа обязаны быть в одном месте. */
  --case-w-text:   690px;   /* абзацы, заголовки, списки, цитаты, узкие таблицы */
  --case-w-media: 1164px;   /* рисунки, живые графики, широкие таблицы          */
  --case-w-full:  1400px;   /* testimonial и разделительные линии во всю полосу */

  /* Шаг ритма. Между разделами 80, внутри раздела 24, вокруг широкого блока
     80/80; 160 — запас на крупные паузы. Единая шкала, промежуточных значений
     (32/48/56) в разделе больше нет. */
  --case-gap-8:     8px;
  --case-gap-16:   16px;
  --case-gap-24:   24px;
  --case-gap-40:   40px;
  --case-gap-80:   80px;
  --case-gap-160: 160px;
  /* Хайрлайн — то же исключение из rem-перевода, что и --u-hairline: в rem на
     1920 линия стала бы 1.33px и рендерилась мутной полосой. Цвет остаётся
     общий, --u-divider. */
  --case-rule:      1px;

  /* --- ГЕОМЕТРИЯ БЛОКОВ РАЗДЕЛА ------------------------------------------
     Числа, которые не выводятся из шкалы ритма и не должны жить в правилах.
     Все они — внутренняя арифметика конкретного блока, поэтому и лежат
     отдельным абзацем, а не в шкале --case-gap-*: добавлять 20 или 56 в общую
     шкалу нельзя, иначе через шаг ими начнут разделять разделы. */

  /* Строка показателей: 335 + 20 + 335 = 690, ровно колонка текста.
     Было 329 + 32 + 329 — та же сходимость при другом дележе; решение
     заказчика — 335/20, ячейка шире, шов между ними тоньше. */
  --case-stats-gap:  20px;
  /* ПРЕЖНИЙ ДЕЛЁЖ КОЛОНКИ — у /methodology и /insight он остался. 329×2 + 32
     тоже даёт 690; статья перешла на 335 + 20, потому что над ячейками
     появилась линия и широкий шов между двумя линиями читался дыркой. Две
     другие страницы линию не получали, менять им дележ было незачем. */
  --case-stats-gap-base: 32px;
  /* Ряды строки показателей разведены шире колонок: между ячейками одного ряда
     шов 20, между рядами 44 (макет 16294:105572) — иначе линии соседних рядов
     встают слишком близко и сетка читается как таблица. На мобиле ряды идут
     через те же 20, что и колонки: ячейка там вдвое ниже. */
  --case-stats-row:     44px;
  --case-stats-row-sm:  20px;

  /* КРУПНЫЕ ЧИСЛА (96 / 14 / 8, макет 16301:108644). Тот же блок показателей,
     но на медиа-уровне: кегль не выводится из шкалы статьи, он свой у блока.
     Поле 118 — ровно (1400 − 1164) / 2, то же, что у testimonial: линия блока
     идёт по полному уровню 1400, а числа стоят на медиа-уровне 1164. */
  --case-bignum:        148px;
  --case-bignum-lsp:  -4.44px;
  --case-bignum-sm:      80px;
  --case-bignum-lsp-sm: -2.4px;

  /* Testimonial (узел макета 15988:540254) стоит на полном уровне 1400:
       20 …… кавычка 49.5×47 …… | текст x=730 w=572 | правое поле 118.
     ПОЛЕ 118 — НЕ ПРОИЗВОЛЬНОЕ: это ровно поле медиа-уровня (1400−1164)/2,
     поэтому правый край текстовой колонки testimonial (1302) ложится на
     правый край рисунков и широких таблиц. Колонка 572 держится жёстко,
     сжимается левое поле — иначе связка с медиа-уровнем разошлась бы. */
  --case-testi-col:     572px;
  --case-testi-gutter:  118px;
  --case-testi-mark-w:  49.5px;
  --case-testi-mark-h:   47px;
  /* Отбивка до спикера внутри testimonial. 56 — из макета; это композиция
     ОДНОГО блока, а не шаг между блоками, поэтому значение и не вошло
     в шкалу ритма (из неё 32/48/56 убраны сознательно). */
  --case-testi-speaker:  56px;

  /* Аватар спикера 64×64 — микрокомпонент .case-person: он же понадобится
     блоку ответственных лиц, если данные для него появятся. */
  --case-avatar:         64px;
  /* Маркер списка — квадрат 6×6 (у кита в табах он 7×7; в тексте 7 на строке
     25.6 читается точкой-кляксой, макет даёт 6). */
  --case-marker:          6px;

  /* --- СТАТЬЯ /insight: ПЛОСКИЕ СПИСКИ И ШИРОКАЯ ПЛАШКА (макет 16020:544182) ---
     Заказчик: «обновить UI графиков — будем использовать более плоский формат»
     и «картинки с прозрачным фоном растягиваются на колонку 1164 и имеют
     фон-заливку». Отсюда два новых компонента статьи — строка «подпись →
     значение» (узел 16024:544943) и пункт-определение, собранный из той же
     строки сменой направления, — плюс плашка под рисунок.

     ПРЕФИКС ins-, А НЕ case-: case-* — геометрия, общая обеим статьям
     (колонка, ритм, хайрлайн); ins-* — числа компонентов, которых на /report
     нет. Разводим, чтобы правка одной статьи не уезжала во вторую.

     В px — по той же причине, что вся колонка статьи выше: корневой размер
     привязан к ширине окна, а эти строки стоят внутри нетянущейся колонки. */

  /* Строка списка: 24 + строка 18 + 24 + 1px линия = 67 в макете. Собственного
     шага у списка нет — ритм держат поля строки и общий разделитель. */
  --ins-row-pad:        24px;   /* поля строки сверху и снизу */
  --ins-row-gap:        24px;   /* зазор подпись ↔ значение */
  --ins-row-fs:         16px;   /* Label/L4: обе части строки одного кегля */
  --ins-row-lh:          1.1;
  --ins-row-ls:      -0.16px;   /* −1 % от 16 в процентной записи Figma */
  /* Отбивка текста под подписью в пункте-определении и mono-строки «как
     проверить» под ним. 12 — половина поля строки: абзац ближе к своей
     подписи, чем к соседнему пункту, иначе связка распадается. */
  --ins-def-gap:        12px;
  /* Полоса покрытия в живом списке: она рисуется ПОВЕРХ разделителя строки,
     поэтому 2px, а не 1 — на хайрлайне 1px заливка не читается как величина. */
  --ins-bar-h:           2px;

  /* Широкая плашка под рисунок (узел 16023:544751). Высота фиксирована:
     при произвольных пропорциях картинок плавающая высота двигала бы ритм
     всей страницы. Поле 118 = (1400 − 1164) / 2 — тот же вылет, что у
     testimonial, поэтому кромки плашки и рисунков совпадают. */
  --ins-fill-h:        640px;
  --ins-fill-pad:       40px;   /* воздух внутри блока сверху и снизу */
  /* Аватар автора — тот же микрокомпонент 64×64, что у спикера testimonial;
     отдельного числа не заводим, берём --case-avatar. */

  /* --- ОГЛАВЛЕНИЕ РАЗДЕЛА (макеты 15988:540365 раскрытое / 15985:538734 свёрнутое) ---
     В px по той же причине, что колонка статьи выше: корневой размер привязан
     к ширине окна, а виджет стоит ВПЛОТНУЮ к тексту, который не тянется. В rem
     на 1024 подпись пункта стала бы 8.5px — нечитаемо, при том что сам текст
     статьи рядом остаётся 16px.

     ШИРИНА ПАНЕЛИ 214, А НЕ 229 ИЗ МАКЕТА. 229 нарисованы под пункт в 27 знаков
     и без соседа справа. У нас справа кнопка «обсудить» (114 = 32 поля + 8 знаков
     × 6.96 + 12 зазор + 14 иконка), и виджет целиком занимает 214 + 8 + 114 = 336
     — ровно столько, сколько даёт свёрнутое состояние макета (170 + 8 + 158).
     Замер на 1440: колонка статьи начинается на x=375, виджет стоит от x=20,
     значит правый край 356 оставляет до текста 19px — это поле страницы (20).
     С панелью 229 остался бы зазор 4px: виджет читался бы как приклеенный
     к тексту. Цена — 22 знака подписи вместо 24 (замер шага моноширинного
     JetBrains Mono 12/−0.24 = 6.96px на знак; 214 − 40 поля − 12 зазор
     − 7 маркер = 155 → 22 знака). Все 14 подписей на 22 знаках различимы,
     полный заголовок остаётся в title. */
  /* Раскрытие аккордеонов и раскрывашек статьи — один набор для всех
     (правка 24.09): высота .4s, фейд содержимого как у фильтра рейтинга. */
  /* Кривая симметричная (standard easing): у быстрого старта (.2,0,0,1)
     длинный список правил выпрыгивал на ~1000px за два кадра и дальше полз —
     на телефоне это читалось как дёрганье (правка 24.09). */
  --u-acc-dur:        450ms;
  --u-acc-ease:       cubic-bezier(.4,0,.2,1);
  --u-acc-fade-in:    300ms;
  --u-acc-fade-out:   200ms;
  --u-acc-fade-delay:  60ms;
  --case-toc-inset:      20px;  /* отступ виджета от левого края окна */
  --case-toc-bottom:     24px;  /* отступ от нижнего края (макет 16294:105741, 23.09) */
  --case-toc-w:         174px;  /* панель — ширина эталона a101-redesign (24.09) */
  /* Навигация 23.09 (16294:105741), ширины по эталону a101-redesign (24.09):
     панель постоянной ширины — по самому длинному пункту (меряет JS, здесь
     нижний предел из макета), кнопка по содержимому. */
  --case-toc-bar-min:   170px;
  --case-toc-w-max:     260px;  /* потолок раскрытой панели: дальше подпись обрезается */
  /* Мобила (16294:105938): поля 16, бар и кнопка поровну, раскрытая панель 229,
     шаг пунктов 16 вместо 12. */
  --case-toc-inset-sm:   16px;
  --case-toc-w-sm:      229px;
  --case-toc-item-gap-sm: 16px;
  --case-toc-sep:  rgba(255,255,255,.12);  /* линия перед «Про исследование» */
  --case-toc-gap:         8px;  /* зазор панель ↔ кнопка */
  --case-toc-pad:        20px;  /* поля внутри панели и бара (макет) */
  --case-toc-item-h:     14px;  /* бокс строки пункта */
  --case-toc-item-gap:   12px;  /* между пунктами; шаг 14 + 12 = 26 */
  --case-toc-mark:        7px;  /* квадрат активного пункта */
  --case-toc-bar-h:      40px;  /* бар-переключатель и кнопка */
  --case-toc-btn-pad:    16px;  /* боковые поля кнопки */
  --case-toc-label-h:    18px;  /* контейнер подписи кнопки (overflow:clip в макете) */
  --case-toc-icon:       14px;
  /* Mono/M2 из макета: JetBrains Mono Medium 12 / 1.2 (бокс 14) / −2 % / uppercase.
     Именованного токена под 12-й кегль в ките нет (--u-mono-* это 10-й). */
  --case-toc-fs:         12px;
  --case-toc-ls:      -0.24px;
  /* Приглушение подписи бара при раскрытом списке — 50 % белого из макета.
     Ближайший токен кита --u-text-secondary это 60 %, подменять нельзя:
     разница видна на тёмной подложке. */
  --case-toc-dim:  rgba(255,255,255,.5);
  /* Тайминги сняты с эталона idaproject.com (TheLandingNavigation):
     список — .35s ease на grid-template-rows, цвет подписи и шеврона — .3s ease. */
  --case-toc-dur:       350ms;
  --case-toc-dur-col:   300ms;
  /* ПОРОГ ПРОКРУТКИ, НА КОТОРОМ СПИСОК САМ СВОРАЧИВАЕТСЯ И РАЗВОРАЧИВАЕТСЯ.
     На эталоне это константа 1200 в коде компонента. Живёт токеном, потому что
     значение читает JS (getComputedStyle отдаёт кастомное свойство как есть —
     отсюда px, как у --u-foot-hide-gap). */
  /* ДО КАКОЙ ШИРИНЫ ОКНА ВИДЖЕТ ПОМЕЩАЕТСЯ В ПОЛЕ, НЕ НАЕЗЖАЯ НА КОЛОНКУ.
     Арифметика: колонка 690 стоит по центру контейнера (окно − 40), значит её
     левый край = 20 + (окно − 730) / 2. Виджет занимает от 20 до 20 + своей
     ширины. Условие «не наехать с запасом в поле страницы» даёт
     окно >= 730 + 2 × (ширина виджета + 20).
       панель 214 + зазор 8 + кнопка 114 = 336  →  окно >= 1442
       одна панель 214                          →  окно >= 1198
     Отсюда два порога. Между ними кнопка «обсудить» прячется (вход в форму
     остаётся в углу экрана), ниже нижнего порога виджет убирается целиком.
     Без этого на 1366 кнопка заходила на колонку на 18px, на 1280 — на 61,
     а на 1024 панель закрывала три строки абзаца подряд и выносную цитату:
     её первые 11 знаков нельзя было ни прочитать, ни выделить. */
  --case-toc-min-cta: 1440px;
  --case-toc-min:     1200px;
}

/* НИЖНИЙ ПРЕДЕЛ ТЕКСТА НА 1024–1279 (правка 24.09). Стоит после :root —
   иначе основной блок токенов, объявленный позже, перебил бы эти значения.
   Сетка тянется корневым размером, и на 1024 (iPad альбомом) мелкие стили доходили до 7–8.5px:
   регион под названием, почта в хедере, моно-подписи. Сетку не трогаем —
   иначе 1400 не влезет, — ограничиваем снизу только размеры шрифта. Выше
   ~1300 пределы не срабатывают (rem уже крупнее), ниже 1024 — своя раскладка.
   Подписи колонок матрицы (--u-col-head-size) предела не получают: на 1024
   на колонку приходится ~34px, и «ИПОТЕКА» крупнее 7px в неё не влезает. */
@media (min-width: 1024px) {
  :root {
    --u-l6-size:   max(1.2rem, 10px);
    --u-mono-size: max(1rem, 9px);
    /* предел для размеров, заданных в стилях напрямую (1.2rem):
       font-size: max(1.2rem, var(--u-fs-floor)) */
    --u-fs-floor:  10px;
  }
}
/* Мобильная раскладка (≤760) строится по-своему — фиксированная ширина имени там
   не нужна и ломала бы перенос. */
@media (max-width: 1023.98px) { :root { --u-name-w: 0rem; } }

/* Box-sizing только внутри namespace: границы ячеек не должны раздувать 36px. */
[class^="u-"],
[class^="u-"]::before,
[class^="u-"]::after,
[class*=" u-"],
[class*=" u-"]::before,
[class*=" u-"]::after { box-sizing: border-box; }

/* Общий запрет радиусов и теней внутри namespace — правило макета. */
[class^="u-"],
[class*=" u-"] { border-radius: 0; box-shadow: none; }

/* ============================================================================
   ТИПОГРАФИЧЕСКИЕ УТИЛИТЫ
   ========================================================================== */

.u-display,
.u-l2, .u-l4, .u-l5, .u-l6, .u-t1, .u-t2, .u-mono {
  margin: 0;
  font-style: normal;
  font-weight: var(--u-weight-medium);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.u-display {
  font-family: var(--u-font-display);
  font-size: var(--u-display-size);
  line-height: var(--u-display-lh);
  letter-spacing: var(--u-display-ls);
  text-transform: uppercase;
  color: var(--u-text-primary);
}

.u-l2 { font-family: var(--u-font-sans); font-size: var(--u-l2-size); line-height: var(--u-l2-lh); letter-spacing: var(--u-l2-ls); color: var(--u-text-primary); }
.u-l4 { font-family: var(--u-font-sans); font-size: var(--u-l4-size); line-height: var(--u-l4-lh); letter-spacing: var(--u-l4-ls); color: var(--u-text-primary); }
.u-l5 { font-family: var(--u-font-sans); font-size: var(--u-l5-size); line-height: var(--u-l5-lh); letter-spacing: var(--u-l5-ls); color: var(--u-text-primary); }
.u-l6 { font-family: var(--u-font-sans); font-size: var(--u-l6-size); line-height: var(--u-l6-lh); letter-spacing: var(--u-l6-ls); color: var(--u-text-muted); }
.u-t1 { font-family: var(--u-font-sans); font-size: var(--u-t1-size); line-height: var(--u-t1-lh); letter-spacing: var(--u-t1-ls); color: var(--u-text-primary); }
.u-t2 { font-family: var(--u-font-sans); font-size: var(--u-t2-size); line-height: var(--u-t2-lh); letter-spacing: var(--u-t2-ls); color: var(--u-text-primary); }

.u-mono {
  font-family: var(--u-font-mono);
  font-size: var(--u-mono-size);
  line-height: var(--u-mono-lh);
  letter-spacing: var(--u-mono-ls);
  text-transform: uppercase;
  color: var(--u-text-muted);
}

/* Роли из таблицы раздела 2, отличающиеся от базового стиля только цветом */
.u-text-phone  { font-family: var(--u-font-sans); font-weight: 500; font-size: var(--u-l5-size); line-height: var(--u-l5-lh); letter-spacing: var(--u-l5-ls); color: var(--u-text-primary); }
.u-text-email  { font-family: var(--u-font-sans); font-weight: 500; font-size: var(--u-l6-size); line-height: var(--u-l6-lh); letter-spacing: var(--u-l6-ls); color: var(--u-text-secondary); }
.u-text-lead   { font-family: var(--u-font-sans); font-weight: 500; font-size: var(--u-t1-size); line-height: var(--u-t1-lh); letter-spacing: var(--u-t1-ls); color: var(--u-text-primary); text-align: center; }

/* ============================================================================
   БАЗА ДЛЯ ИНТЕРАКТИВНЫХ ЭЛЕМЕНТОВ
   ========================================================================== */

.u-btn,
.u-ctl-btn,
.u-filter-btn,
.u-tab,
.u-accent-link,
.u-link {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  border: 0;
  background: none;
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  cursor: pointer;
  text-decoration: none;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

.u-btn:focus-visible,
.u-ctl-btn:focus-visible,
.u-filter-btn:focus-visible,
.u-tab:focus-visible,
.u-accent-link:focus-visible,
.u-link:focus-visible,
.u-cell:focus-visible {
  outline: 2px solid var(--u-focus-ring);
  outline-offset: 2px;
}

/* Hit-area ≥40px у элементов высотой 16px.
   Горизонтально ровно 10px — половина gap 20px, чтобы зоны соседей
   соприкасались, но не перекрывались. */
.u-filter-btn::before,
.u-tab::before,
.u-accent-link::before,
.u-link::before {
  content: '';
  position: absolute;
  inset: -1.2rem -1rem;
}

/* ============================================================================
   1. u-cell — квадрат 36×36. Бейдж места и 4 статуса. 6 вариантов.
   figma-spec §6.5, §6.7
   ========================================================================== */

.u-cell {
  display: flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 auto;
  width: var(--u-cell-size);
  height: var(--u-cell-size);
  border: 0;
  background: transparent;
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  text-align: center;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* Глиф — <img> 16×16 с явными width/height, чтобы не «прыгал» до загрузки. */
.u-cell__glyph {
  display: block;
  width: var(--u-icon-16);
  height: var(--u-icon-16);
  flex: 0 0 auto;
}

/* Глиф-МАСКА 16×16: тот же размер, что у .u-cell__glyph, но цвет приходит из
   CSS (currentColor), а не запечён в файле. Ассет подставляется переменной
   --u-mask на самом элементе или на его родителе:
     .my-icon { --u-mask: url("../img/redesign/ic-bookmark.svg"); }
   Применяется там, где один и тот же ассет нужен в разных цветах или должен
   реагировать на ховер/фокус (см. u-ctl-btn и метку эталона в полосе сравнения). */
.u-cell__mask {
  display: block;
  width: var(--u-icon-16);
  height: var(--u-icon-16);
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--u-mask) no-repeat center / var(--u-icon-16) var(--u-icon-16);
          mask: var(--u-mask) no-repeat center / var(--u-icon-16) var(--u-icon-16);
}

.u-cell.is-yes      { background: var(--u-brand); }
.u-cell.is-partial  { background: var(--u-cell-partial-bg); border: var(--u-hairline) solid var(--u-brand-hard); }
.u-cell.is-no       { background: var(--u-cell-no-bg); border: var(--u-hairline-thin) solid var(--u-border-danger); }
.u-cell.is-unknown  { background: var(--u-bg-neutral-hover); border: var(--u-hairline) dashed var(--u-border-white-secondary); color: var(--u-text-muted); }
.u-cell.is-rank-top,
.u-cell.is-rank     { font-variant-numeric: tabular-nums; }
/* Подсвеченный бейдж: в виде «по топу ЕРЗ» — места 1–3, в виде «по уровню
   цифровизации» — только место 1. Разницу задаёт JS, это решение заказчика. */
.u-cell.is-rank-top { background: var(--u-brand); color: var(--u-text-on-brand); }
/* Волна «обновление»: в виде «по топу ЕРЗ» лайм у мест 2 и 3 приглушён
   (15970:512415 — 80 %, 15970:512455 — 60 %). Место 1 остаётся полным.
   В виде «по уровню цифровизации» подсвечен только лидер, поэтому классов
   is-rank-2/3 там не появляется. */
.u-cell.is-rank-top.is-rank-2 { background: var(--u-rank-2); }
.u-cell.is-rank-top.is-rank-3 { background: var(--u-rank-3); }
/* Остальные места: белый 8 % без обводки. Отдельной подсветки под ховером
   строки нет — бейдж прозрачный, и подложка ховера просвечивает сквозь него
   ровно так же, как в макете (15970:512498 на строке под ховером). */
.u-cell.is-rank     { background: var(--u-bg-rank); color: var(--u-text-primary);
                      border: 0; }
/* Явный запрет на любую обводку у лаймовых бейджей (топ-3 в виде «по ЕРЗ»,
   место 1 в виде «по цифровизации») — макет: «обводки нет». */
.u-cell.is-rank-top { border: 0; }
/* Пустой слот места: вторая и далее компания одной позиции. Колонка сохраняет
   ширину 36px, чтобы имя осталось на x=118 (figma-spec-2 §4.2). */
.u-cell.is-rank-empty { background: transparent; border: 0; }

/* ============================================================================
   1b. u-mark — фирменный знак ИдаПроджект у названия компании.
   figma-spec-2 §3.5: обёртка 12×18, SVG 12×5, отступ от названия 8px (gap
   родителя .u-dev__title). Без фона, рамки, радиуса и паддингов.
   Рисуется ПО ДАННЫМ: у компаний, где есть хотя бы одна ячейка с отметкой
   участия ИдаПроджект (services.*.involvement).
   ========================================================================== */

.u-mark {
  display: inline-flex;
  align-items: center;        /* (18 − 5) / 2 = 6.5px сверху — как в макете */
  justify-content: flex-start;
  width: 1.2rem;
  height: 1.8rem;
  flex: 0 0 auto;
}

.u-mark__glyph {
  display: block;
  width: 1.2rem;
  height: 0.5rem;
}

/* Интерактивная ячейка (подсказка по наведению) — курсор help, а не pointer */
.u-cell[tabindex],
.u-cell[title] { cursor: help; }

/* ============================================================================
   2. u-btn — кнопка h32. Варианты is-primary / is-glass. figma-spec §3
   ========================================================================== */

.u-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--u-space-12);
  height: var(--u-btn-h);
  min-height: var(--u-btn-h);
  max-height: var(--u-btn-h);
  padding: 0 var(--u-space-12);
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  white-space: nowrap;
  transition: background-color var(--u-dur-nav) var(--u-ease-nav);
}

/* Лайм-кнопка: фон на ховере НЕ меняется — вместо этого справа выезжает иконка. */
.u-btn.is-primary { background-color: var(--u-brand); color: var(--u-text-on-brand); }
.u-btn.is-primary:hover  { background-color: var(--u-brand); }
.u-btn.is-primary:active { background-color: var(--u-brand-pressed); }

.u-btn.is-glass {
  background-color: var(--u-glass);
  -webkit-backdrop-filter: blur(var(--u-glass-blur));
  backdrop-filter: blur(var(--u-glass-blur));
  color: var(--u-text-primary);
}
.u-btn.is-glass:hover  { background-color: var(--u-bg-neutral-hover); }
.u-btn.is-glass:active { background-color: var(--u-bg-neutral-hover); }

/* Выезжающая иконка (механика idaproject.com): ширина 0 → --u-icon-slide,
   opacity 0 → 1. Сам ассет лаймовый, поэтому перекрашиваем маской в цвет текста.
   gap кнопки гасим: отступ до иконки задаёт её собственная ширина.
   mask-position:right center — ключевая деталь: глиф прибит к правому краю
   бокса, поэтому едет пустой отступ, а сам глиф не шелохнётся.
   Тот же приём вынесен на .cmp-btn в styles.css — там иконка видна и в покое,
   поэтому концы отсчитываются не от нуля, а от --u-icon-14. */
.u-btn.has-icon-slide { gap: 0; }
.u-btn__icon {
  display: block;
  flex: 0 0 auto;
  width: 0;
  height: var(--u-icon-14);
  opacity: 0;
  background-color: currentColor;
  -webkit-mask: url("../img/redesign/ic-arrow-right.svg?v=2") no-repeat right center / var(--u-icon-14) var(--u-icon-14);
  mask: url("../img/redesign/ic-arrow-right.svg?v=2") no-repeat right center / var(--u-icon-14) var(--u-icon-14);
  transition: width var(--u-dur-nav) var(--u-ease-nav), opacity var(--u-dur-nav) var(--u-ease-nav);
}
.u-btn.has-icon-slide:hover .u-btn__icon,
.u-btn.has-icon-slide:focus-visible .u-btn__icon {
  width: var(--u-icon-slide);
  opacity: 1;
}

/* ============================================================================
   2c. u-ctl-btn — управляющая кнопка h36 на тёмном контроле. Макет 15918:97968
   (полоса эталона сравнения): фон #232325, радиуса и рамки нет, подпись
   JetBrains Mono 12 uppercase / трекинг −0.24 / белая, padding 12 сверху-снизу
   и 16 по бокам. Вариант is-square — квадрат 36×36 с глифом-маской 16×16
   (крестик, «убрать»). Оба варианта ростом ровно в --u-cell-size, поэтому
   встают в одну линию с ячейками и бейджами строки.
   ========================================================================== */

.u-ctl-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--u-space-8);
  height: var(--u-cell-size);
  min-height: var(--u-cell-size);
  padding: var(--u-space-12) var(--u-space-16);
  background-color: var(--u-bg-control);
  color: var(--u-text-primary);
  font-family: var(--u-font-mono);
  font-size: var(--u-l6-size);
  line-height: 1.2;
  letter-spacing: -0.024rem;
  text-transform: uppercase;
  white-space: nowrap;
  transition: background-color var(--u-dur-row) var(--u-ease-nav),
              color var(--u-dur-row) var(--u-ease-nav);
}

/* Ховера в макете нет: держим прежний шаг +4 % белого, пересчитанный от нового
   полотна (#1c2329 → #262d32) — как у закреплённых кнопок низа. */
.u-ctl-btn:hover { background-color: var(--u-bg-btn-bar-hover); }
.u-ctl-btn:active { background-color: var(--u-bg-control); }

/* Квадратная иконочная: 36×36, паддингов нет — глиф центрируется сам. */
.u-ctl-btn.is-square {
  width: var(--u-cell-size);
  padding: 0;
  gap: 0;
}

/* Hit-area ≥40×40: визуальная коробка по макету 36, добираем 2px с каждой
   стороны прозрачным слоем, раскладка при этом не меняется. */
.u-ctl-btn::before {
  content: '';
  position: absolute;
  inset: -0.2rem;
}

/* Глиф кнопки — та же маска 16×16, что и .u-cell__mask: цвет из currentColor,
   ассет приходит переменной --u-mask. */
.u-ctl-btn__icon {
  display: block;
  width: var(--u-icon-16);
  height: var(--u-icon-16);
  flex: 0 0 auto;
  background-color: currentColor;
  -webkit-mask: var(--u-mask) no-repeat center / var(--u-icon-16) var(--u-icon-16);
          mask: var(--u-mask) no-repeat center / var(--u-icon-16) var(--u-icon-16);
}

/* ============================================================================
   2b. u-accent-link — текстовая кнопка перехода со стрелкой. figma-spec-2 §1
   ОБНОВЛЕНО по новым макетам М3/М4: подпись и стрелка БЕЛЫЕ (text/icon
   white-primary), не лаймовые. В новой полосе фильтров лайма нет вовсе —
   правая группа («методология +» и «выводы с экспертами ↗») набрана одним
   белым тоном, а единственная лайм-заливка на первом экране осталась за
   u-btn.is-primary в хедере. Прежний лайм здесь противоречил бы макету и
   ставил бы два первичных действия на одну вертикаль правого края.
   gap 4px — как у u-filter-btn (в макете «выводы с экспертами» это тот же
   паттерн «текст + иконка 14×14 справа»).
   Ховер — существующая механика u-split (§5b): текст перебирается по буквам.
   Хостом ховера служит сама кнопка, а режется её вложенный __label, потому что
   splitLetters() затирает содержимое узла и стёр бы иконку.
   Модификатор is-back зеркалит порядок (row-reverse) и подменяет стрелку:
   ↗ уместна только для перехода «наружу», а возврат «← рейтинг» оставлен
   прежним — обычная стрелка, развёрнутая влево.
   ========================================================================== */

.u-accent-link {
  display: inline-flex;
  align-items: center;
  gap: var(--u-space-4);
  height: var(--u-control-h);
  padding: 0;
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  color: var(--u-text-primary);
  white-space: nowrap;
}

.u-accent-link.is-back { flex-direction: row-reverse; gap: var(--u-space-8); }

/* Стрелка вправо-вверх 14×14 (14. System / 55. Arrow-right-up). Ассет лаймовый,
   поэтому берём его маской и заливаем currentColor — цвет всегда совпадает
   с надписью, как у u-btn__icon. */
.u-accent-link__icon {
  display: block;
  flex: 0 0 auto;
  width: var(--u-icon-14);
  height: var(--u-icon-14);
  background-color: currentColor;
  -webkit-mask: url("../img/redesign/ic-arrow-up-right.svg") no-repeat center / var(--u-icon-14) var(--u-icon-14);
  mask: url("../img/redesign/ic-arrow-up-right.svg") no-repeat center / var(--u-icon-14) var(--u-icon-14);
}

/* «методология +»: та же кнопка и тот же посимвольный ховер, что у «выводы
   с экспертами» (правка заказчика — раздел тоже станет отдельной страницей),
   отличается только глифом. */
.u-accent-link.is-plus .u-accent-link__icon {
  -webkit-mask-image: url("../img/redesign/ic-plus.svg");
  mask-image: url("../img/redesign/ic-plus.svg");
}

/* Возврат «← рейтинг»: горизонтальная стрелка, развёрнутая влево. */
.u-accent-link.is-back .u-accent-link__icon {
  -webkit-mask-image: url("../img/redesign/ic-arrow-right.svg?v=2");
  mask-image: url("../img/redesign/ic-arrow-right.svg?v=2");
  transform: scaleX(-1);
}

.u-accent-link:hover  .u-split__in,
.u-accent-link:focus-visible .u-split__in  { transform: translate3d(0, 0, 0); }
.u-accent-link:hover  .u-split__out,
.u-accent-link:focus-visible .u-split__out { transform: translate3d(var(--u-split-shift), 0, 0); }

/* ============================================================================
   3. u-filter-btn — текст + иконка 14×14. figma-spec §5
   Варианты: is-icon-right (gap 4) / is-icon-left (gap 8) / без иконки.
   Тон: по умолчанию white, is-muted — #95959d.
   ========================================================================== */

.u-filter-btn {
  display: inline-flex;
  align-items: center;
  gap: var(--u-space-4);
  height: var(--u-control-h);
  padding: 0;
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  color: var(--u-text-primary);
  white-space: nowrap;
  transition: color var(--u-dur) var(--u-ease);
}

.u-filter-btn.is-icon-left { gap: var(--u-space-8); }

.u-filter-btn__icon {
  display: block;
  width: var(--u-icon-14);
  height: var(--u-icon-14);
  flex: 0 0 auto;
}

.u-filter-btn.is-muted { color: var(--u-text-muted); }
.u-filter-btn.is-muted:hover,
.u-filter-btn.is-muted:focus-visible { color: var(--u-text-primary); }

/* Ассеты иконок залиты белым. Для приглушённого тона гасим прозрачностью:
   white × .54 на фоне #1b1c1d ≈ #95959d. Точная перекраска потребовала бы
   mask-image, а спецификация требует именно <img>. */
.u-filter-btn.is-muted .u-filter-btn__icon { opacity: .54; transition: opacity var(--u-dur) var(--u-ease); }
.u-filter-btn.is-muted:hover .u-filter-btn__icon,
.u-filter-btn.is-muted:focus-visible .u-filter-btn__icon { opacity: 1; }

/* Раскрытый фильтр (figma-spec-3 §2.5): лаймовая плашка 12×16 под иконкой,
   выступает на 5px правее инстанса; шеврон переворачивается вверх и остаётся
   белым поверх лайма. Плашка — отдельный узел .u-filter-btn__mark, а не
   псевдоэлемент: ::before у кнопки уже занят хит-зоной 40px. */
.u-filter-btn__mark {
  /* Плашка показывается не только у раскрытого фильтра, но и по ховеру —
     логика основного сайта (._VTextButton:hover::before { opacity: 1 }).
     Поэтому не display, а opacity: иначе появление рвано и без перехода. */
  display: block;
  opacity: 0;
  transition: opacity var(--u-dur) var(--u-ease);
  position: absolute;
  right: -0.5rem;
  top: 50%;
  transform: translateY(-50%);
  width: 1.2rem;
  height: 1.6rem;
  background: var(--u-brand);
  border-radius: 0;
  pointer-events: none;
}
.u-filter-btn.is-open { position: relative; }
.u-filter-btn.is-open .u-filter-btn__mark,
.u-filter-btn:hover .u-filter-btn__mark,
.u-filter-btn:focus-visible .u-filter-btn__mark { opacity: 1; }
/* иконка лежит поверх плашки */
.u-filter-btn__icon,
.u-filter-btn .chev { position: relative; z-index: 1; transition: transform var(--u-dur-nav) var(--u-ease-nav); }
.u-filter-btn.is-open .u-filter-btn__icon,
.u-filter-btn.is-open .chev { transform: rotate(180deg); }

/* ============================================================================
   4. u-tab — таб панели управления. figma-spec §5
   ========================================================================== */

.u-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--u-space-8);
  height: var(--u-control-h);
  padding: 0;
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  color: var(--u-text-muted);
  white-space: nowrap;
  transition: color var(--u-dur) var(--u-ease);
}

.u-tab:hover,
.u-tab:focus-visible { color: var(--u-text-primary); }

.u-tab.is-active { color: var(--u-text-primary); }

/* Маркер активного таба — залитый квадрат 7×7, без радиуса.
   Разметка держит его во всех табах (чтобы ширина не прыгала при переключении),
   виден только у активного. */
.u-tab__marker {
  display: none;
  flex: 0 0 auto;
  width: 0.7rem;
  height: 0.7rem;
  background: var(--u-icon-white);
}
.u-tab.is-active .u-tab__marker { display: block; }
/* у неактивных таба маркера нет и gap 8px лишний — схлопываем */
.u-tab:not(.is-active) { gap: 0; }

/* ============================================================================
   5. u-link — пункт меню хедера. figma-spec §3
   ========================================================================== */

.u-link {
  display: inline-flex;
  align-items: center;
  width: -moz-fit-content;
  width: fit-content;   /* ширина hug — фиксированные 56/41/65/62 из макета не переносим */
  height: var(--u-control-h);
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  color: var(--u-text-primary);
  white-space: nowrap;
}

/* Подчёркивания у пунктов меню НЕТ — ховер целиком отдан посимвольной
   анимации u-split (см. блок 5b). */

/* Активный раздел визуально НЕ выделяется (как у idaproject: активная ссылка
   ховер отрабатывает как у остальных пунктов). Семантику несёт aria-current="page". */
.u-link[aria-current="page"] { /* без визуального выделения; ховер работает как у остальных */ }

/* ============================================================================
   5b. u-split — посимвольная анимация текста на ховере (механика idaproject.com)
   Разметку строит splitLetters() в app.js: каждая буква — обёртка
   .u-split__ch с overflow:hidden и ДВУМЯ копиями буквы внутри.
   ▸ .u-split__in  — спрятана слева, translate3d(-110%,0,0)
   ▸ .u-split__out — видима в потоке, transform:none
   На ховере родителя (.u-split-host) они меняются местами со ступенчатой
   задержкой calc(var(--i) * var(--u-split-stagger)) — эффект бежит по слову
   слева направо. Анимируется ТОЛЬКО transform, никаких `all`.
   ========================================================================== */

.u-split { display: inline-block; white-space: nowrap; vertical-align: top; }
/* без vertical-align:top inline-block-буквы садятся на базовую линию и добавляют
   ~3px под строкой: бокс телефона был 19px вместо 16. */

.u-split__ch {
  vertical-align: top;
  position: relative;
  display: inline-block;
  overflow: hidden;
  /* Вертикальный запас: без него overflow:hidden срезал бы выносные элементы
     («й» сверху, «р»/«д»/«у»/«@» снизу) при line-height 1.1. Отрицательные
     маргины возвращают исходную высоту строки — раскладка не меняется. */
  padding-top: var(--u-split-pad-t, .3em);
  padding-bottom: var(--u-split-pad-b, .34em);
  margin-top: calc(var(--u-split-pad-t, .3em) * -1);
  margin-bottom: calc(var(--u-split-pad-b, .34em) * -1);
}

.u-split__in,
.u-split__out {
  display: inline-block;
  transition: transform var(--u-dur-nav) var(--u-ease-nav);
  transition-delay: calc(var(--i, 0) * var(--u-split-stagger));
}

/* Спрятанная копия: абсолют считается от padding-box, поэтому top равен
   верхнему запасу — иначе копия встала бы выше видимой на величину padding. */
.u-split__in {
  position: absolute;
  top: var(--u-split-pad-t, .3em);
  left: 0;
  transform: translate3d(calc(var(--u-split-shift) * -1), 0, 0);
}
.u-split__out { transform: translate3d(0, 0, 0); }

.u-split-host:hover  .u-split__in,
.u-split-host:focus-visible .u-split__in  { transform: translate3d(0, 0, 0); }
.u-split-host:hover  .u-split__out,
.u-split-host:focus-visible .u-split__out { transform: translate3d(var(--u-split-shift), 0, 0); }

/* ============================================================================
   6. u-divider-v — вертикальная линия 1×12. figma-spec §5
   CSS-элемент вместо повёрнутого SVG-ассета (ic-divider.svg не используем).
   ========================================================================== */

.u-divider-v {
  display: block;
  flex: 0 0 auto;
  width: var(--u-hairline);
  height: 1.2rem;
  background: var(--u-divider);
}

/* Вариант на случай, если приёмка подтвердит границу 24% вместо 12% */
.u-divider-v.is-strong { background: var(--u-border-white-secondary); }

/* Горизонтальная линия-разделитель (панель фильтров, строки таблицы) */
.u-divider-h {
  display: block;
  width: 100%;
  height: var(--u-hairline);
  background: var(--u-divider);
}

/* ============================================================================
   7. u-delta — дельта позиции. figma-spec §6.6
   ========================================================================== */

.u-delta {
  display: inline-flex;
  align-items: center;
  gap: var(--u-space-4);
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  white-space: nowrap;
}

.u-delta__icon {
  display: block;
  width: var(--u-icon-14);
  height: var(--u-icon-14);
  flex: 0 0 auto;
}

.u-delta__value { font-variant-numeric: tabular-nums; }

.u-delta.is-up   { color: var(--u-brand-hard); }
.u-delta.is-down { color: var(--u-text-danger); }
/* «Без изменений» — честный вариант: цифры нет вовсе (в макете это opacity:0) */
.u-delta.is-same { color: var(--u-text-muted); }
.u-delta.is-same .u-delta__value { display: none; }

/* ============================================================================
   8. u-score — балл. figma-spec §6.9
   ========================================================================== */

.u-score {
  display: block;
  width: var(--u-col-score-w);
  flex: 0 0 auto;
  text-align: right;
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  letter-spacing: var(--u-l2-ls);
  color: var(--u-text-primary);
  white-space: nowrap;
}

.u-score__value {
  font-size: var(--u-l2-size);
  line-height: var(--u-l2-lh);
  font-variant-numeric: tabular-nums;
}

.u-score__total {
  font-size: var(--u-l4-size);
  line-height: var(--u-l4-lh);
  letter-spacing: var(--u-l4-ls);
  color: var(--u-text-quaternary);
}

/* ============================================================================
   9. u-col-head — подпись колонки таблицы. figma-spec §6.3
   ========================================================================== */

.u-col-head {
  font-family: var(--u-font-mono);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-col-head-size);
  line-height: var(--u-mono-lh);
  letter-spacing: var(--u-mono-ls);
  text-transform: uppercase;
  color: var(--u-text-muted);
  white-space: nowrap;
}

/* ============================================================================
   10. u-row / u-cells / u-heads — сетка таблицы. figma-spec §6.1–6.3
   ========================================================================== */

.u-table { width: 100%; max-width: var(--u-content-w); }

/* Блок заголовков колонок: та же сетка 276 / 572 / 276 */
.u-heads {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  width: 100%;
  margin-bottom: var(--u-space-32);   /* 803 − 771 = 32px из макета */
}

.u-heads__dev   { width: var(--u-col-dev-w);   flex: 0 0 auto; }
.u-heads__score { width: var(--u-col-score-w); flex: 0 0 auto; text-align: right; }

/* Подсказка «наведите на ▢ для информации». В выгрузке на месте квадрата
   оставлены 4 пробела (узла иконки нет) — рисуем контурный квадрат сами,
   иначе фраза читается как пропуск. */
.u-hint-sq {
  display: inline-block;
  width: 0.8rem;
  height: 0.8rem;
  margin: 0 0.6rem;
  border: var(--u-hairline) solid currentColor;
  vertical-align: baseline;
}

/* Группа ячеек. Шаг 48.7272px задаётся ЯВНЫМ gap (--u-cell-gap), а не
   space-between: при 12 критериях 12×36 + 11×12.7272 = ровно 572, то есть
   картина та же, что была. Но когда критериев меньше 12, space-between
   растягивал оставшиеся ячейки по всей ширине блока (при одном критерии
   ячейка вообще прилипала к левому краю) — решение заказчика: группа ячеек
   центрируется внутри 572 с сохранением шага. Это и даёт justify-content:center. */
.u-cells {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--u-cell-gap);
  width: var(--u-cells-w);
  flex: 0 0 auto;
}

/* Подписи над ячейками: 11 слотов (12-й слит с 11-м под «ии-функции»),
   ширины слотов и gap те же, что у ячеек, поэтому центры подписей совпадают
   с центрами ячеек при любом числе выбранных критериев */
.u-cells.is-heads > .u-col-head {
  width: var(--u-cell-size);
  flex: 0 0 auto;
  text-align: center;
  overflow: visible;
}

.u-cells.is-heads > .u-col-head.is-span2 {
  width: calc(2 * var(--u-cell-size) + var(--u-cell-gap));
  flex: 0 0 auto;
}

/* Список строк */
.u-rows {
  display: flex;
  flex-direction: column;
  gap: var(--u-row-gap);
  width: 100%;
}

.u-row {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding-bottom: var(--u-row-pb);
  border-bottom: var(--u-hairline) solid var(--u-divider-soft);
}
/* Дети позиционируем, чтобы ховер-подложка ::before рисовалась ПОД контентом
   (позиционированный псевдоэлемент иначе перекрывает строчный контент). */
.u-row > * { position: relative; }

/* Ховер строки (figma-spec-2 §3.8): только смена фона, без движения.
   Подложка — ВО ВСЮ ШИРИНУ ОКНА (1440), а не по контейнеру 1400: контейнер
   лежит в .wrap с полями --u-page-margin, поэтому растягиваем на эти поля
   влево и вправо. Без 100vw — иначе классический скроллбар давал бы
   горизонтальную прокрутку.
   Геометрия по вертикали: строка занимает 36 контента + 20 padding + 1px
   линии = 57px, сверху к ней относится 20px «своего» поля (19 gap + 1px
   линии предыдущей строки). Подложка начинается на 1px выше полосы и
   заканчивается по низу линии: 20 + 57 = 77 = высота строки 76 + 1. */
.u-row::before {
  content: '';
  position: absolute;
  top: calc(-1 * var(--u-space-20));
  /* −1px: контейнер абсолюта — padding-box, линия-разделитель лежит за ним */
  bottom: calc(-1 * var(--u-hairline));
  left: calc(-1 * var(--u-page-margin));
  right: calc(-1 * var(--u-page-margin));
  background: transparent;
  transition: background-color var(--u-dur-row) var(--u-ease);
  pointer-events: none;
}
.u-row.is-hoverable:hover::before { background-color: var(--u-row-hover); }

/* Разделители над и под наведённой строкой гасятся (figma-spec-2 §3.8 п.2) */
.u-row.is-hoverable:hover,
.u-row:has(+ .u-row.is-hoverable:hover),
.u-row:has(+ .u-pos:hover) { border-bottom-color: transparent; }

/* Ховер-подпись «Есть решения ИдаПроджект» (figma-spec-2 §3.8 п.3):
   12px #95959D, бокс 171px, левый край на x=1093 внутри контейнера 1400
   (1400 − 136 − 171), по центру линии балла. Ставится ТОЛЬКО у компаний с
   отметками участия — решение заказчика: «если не работали, подписи нет». */
.u-row__note {
  position: absolute;
  top: calc(var(--u-cell-size) / 2);
  right: 13.6rem;
  width: 17.1rem;
  transform: translateY(-50%);
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-l6-size);
  line-height: var(--u-l6-lh);
  letter-spacing: var(--u-l6-ls);
  color: var(--u-text-muted);
  text-align: left;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--u-dur-row) var(--u-ease);
}
.u-row.is-hoverable:hover > .u-row__note,
.u-pos:hover .u-row__note { opacity: 1; }

/* ============================================================================
   10b. u-pos — ПОЗИЦИЯ в виде «по уровню цифровизации». figma-spec-2 §4
   Одна позиция = одна или несколько компаний с одинаковым баллом. У второй и
   далее нет номера места, балла и дельты — колонки просто пустые.
   Ховер подсвечивает позицию целиком, обе (и далее) компании.
   ========================================================================== */

.u-pos {
  position: relative;
  display: flex;
  flex-direction: column;
  /* 56 (контент+padding) + 20 = 76px — тот же шаг, что и в виде «по ЕРЗ» */
  gap: var(--u-space-20);
  width: 100%;
  border-bottom: var(--u-hairline) solid var(--u-divider-soft);
}
.u-pos > * { position: relative; }
/* внутри группы своей линии у строки нет — её рисует ::after ниже */
.u-pos > .u-row { border-bottom: 0; }

/* Ховер группы: та же подложка во всю ширину окна */
.u-pos::before {
  content: '';
  position: absolute;
  top: calc(-1 * var(--u-space-20));
  bottom: calc(-1 * var(--u-hairline));
  left: calc(-1 * var(--u-page-margin));
  right: calc(-1 * var(--u-page-margin));
  background: transparent;
  transition: background-color var(--u-dur-row) var(--u-ease);
  pointer-events: none;
}
.u-pos:hover::before { background-color: var(--u-row-hover); }
.u-pos:hover,
.u-pos:has(+ .u-pos:hover),
.u-pos:has(+ .u-row.is-hoverable:hover) { border-bottom-color: transparent; }

/* ЛИПКИЕ МЕСТО И БАЛЛ внутри длинной позиции (правка заказчика №6).
   Вторая реализация — НАТИВНЫЙ position: sticky на отдельном слое.

   Почему не как раньше. Первая версия считала смещение в JS и писала его в
   --u-pos-stick (translateY). Прокрутка колеса/тачпада исполняется на потоке
   композитора, а стиль применяется на главном: страница уже перерисована на
   новой позиции, а бейдж сдвинут по предыдущей. Ошибка равна шагу прокрутки
   за кадр, а он плавает (6–7px) — бейдж мелко дрожал. Замер по пикселям
   скринкаста: вместо неподвижных 105px верх бейджа гулял 96…100px.
   position: sticky считает композитор — лага нет по построению.

   Почему слой. У бейджа ближайший блок-контейнер — .u-dev высотой 36px
   (на десктопе .c-rank раскрыт в display:contents), у балла — .u-row высотой
   36+20: липнуть внутри них некуда. Поэтому на группу кладётся абсолютный слой
   во всю её высоту, а бейдж и балл живут в нём. В строках остаются те же узлы,
   но невидимыми — они держат место в раскладке (x=118 у названия, колонка 276
   у балла). visibility: hidden, а не opacity: 0, — чтобы в дереве доступности
   не было дубля: видимый экземпляр ровно один. */
.u-pos > .u-pos__rail {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  /* Низ слоя = низ контента последней строки группы. padding-box .u-pos уже без
     border 1px, поэтому вычитаем только padding-bottom строки: предельный сдвиг
     получается H − 20 − 1 − 36 = H − 57 — ровно тот, что считал прежний JS. */
  bottom: var(--u-row-pb);
  z-index: 1;
  pointer-events: none;          /* слой лежит поверх ячеек — ховер и клики сквозь него */
}
.u-pos__rail-in {
  position: sticky;
  /* высота хедера + строки заголовков + 20px зазора; пишет setRailTop() в app.js */
  top: var(--u-rail-top, 0px);
  display: flex;
  align-items: center;
  justify-content: space-between;
  height: var(--u-cell-size);
}
/* Первая строка группы адресуется через соседство со слоем, а не :first-child:
   первый ребёнок .u-pos теперь сам слой. */
.u-pos.is-multi > .u-pos__rail + .u-row > .u-dev > .c-rank > .u-dev__rank,
.u-pos.is-multi > .u-pos__rail + .u-row > .u-dev > .u-dev__rank,
.u-pos.is-multi > .u-pos__rail + .u-row .u-score:not(.is-empty) { visibility: hidden; }

/* Мобильная раскладка (≤760): позиция — простая стопка карточек, липнуть нечему.
   Слой убираем целиком, оригиналы в строке возвращаем. */
@media (max-width: 1023.98px) {
  .u-pos > .u-pos__rail { display: none; }
  .u-pos.is-multi > .u-pos__rail + .u-row > .u-dev > .c-rank > .u-dev__rank,
  .u-pos.is-multi > .u-pos__rail + .u-row > .u-dev > .u-dev__rank,
  .u-pos.is-multi > .u-pos__rail + .u-row .u-score:not(.is-empty) { visibility: visible; }
}

/* Внутренняя линия позиции (figma-spec-2 §4.3): короче и слабее внешней —
   x 118…1282 внутри контейнера 1400 (абсолютно 138…1302), цвет 8 %.
   Стоит в 20-пиксельном зазоре между компаниями, по низу предыдущей строки. */
.u-pos > .u-row + .u-row::after {
  content: '';
  position: absolute;
  top: calc(-1 * var(--u-space-20));
  left: 11.8rem;
  right: 11.8rem;
  height: var(--u-hairline);
  background: var(--u-divider-soft);
  pointer-events: none;
}

/* Последняя строка списка (правка заказчика №8): линии снизу нет — ни в одном
   из двух видов, ни у последней архивной строки перед плашкой «Скрыть».
   Гасим ЦВЕТ, а не саму границу: 1px участвует в шаге строки 76px (см.
   --u-row-gap), и border-bottom:0 сдвинул бы весь ритм на пиксель. */
.u-rows > .u-row:last-child,
.u-rows > .u-pos:last-child,
.u-rows > .u-row:has(+ .check-block),
.u-rows > .u-pos:has(+ .check-block) { border-bottom-color: transparent; }

/* ============================================================================
   11. u-dev — левая колонка 276×36. figma-spec §6.8
   В макете дети позиционированы абсолютом (left 0 / 59 / 118). Здесь flex
   с теми же итоговыми отступами: 36 + 23 + 39 + 20 → текст начинается на 118.
   ========================================================================== */

.u-dev {
  display: flex;
  align-items: center;
  width: var(--u-col-dev-w);
  flex: 0 0 auto;
  height: var(--u-cell-size);
}

.u-dev__rank  { margin-right: 2.3rem; }             /* 59 − 36 = 23 */
.u-dev__delta {
  display: inline-flex;
  align-items: center;
  justify-content: center;                         /* дельта центрируется в своём слоте (макет 15859:247741) */
  width: 3.9rem;
  flex: 0 0 auto;
  margin-right: var(--u-space-20);                 /* 118 − (59 + 39) = 20 */
}

/* Контейнер названия. По макету 158px (276 − 118), но 14 из 100 названий на
   этой ширине обрезались многоточием, поэтому заказчик попросил 237 (правка №7).
   Реализовано через min-width, а не width: колонка .u-dev остаётся 276px и сетка
   строки не перестраивается — контейнер просто выходит на 79px в пустой зазор
   между колонкой застройщика и блоком ячеек (на 1440 зазор 138px).
   На 761–1023.98 и на мобиле --u-name-w = 0 (зазора там нет). */
.u-dev__text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--u-space-2);
  min-width: var(--u-name-w);
  flex: 1 1 auto;
}

/* Строка 1 блока имени: название + фирменный знак, gap ровно 8px
   (figma-spec-2 §3.5). Высота строки — 18px, как у обёртки знака. */
.u-dev__title {
  display: flex;
  align-items: center;
  gap: var(--u-space-8);
  max-width: 100%;
  min-width: 0;
}

.u-dev__name {
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-l4-size);
  line-height: var(--u-l4-lh);
  letter-spacing: var(--u-l4-ls);
  color: var(--u-text-primary);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.u-dev__region {
  font-family: var(--u-font-sans);
  font-weight: var(--u-weight-medium);
  font-size: var(--u-l6-size);
  line-height: var(--u-l6-lh);
  letter-spacing: var(--u-l6-ls);
  color: var(--u-text-muted);
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================================================================
   12. Служебные раскладки панели управления (сборка компонентов)
   ========================================================================== */

.u-filter-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  height: 4.8rem;
  padding-bottom: var(--u-space-12);
  border-bottom: var(--u-hairline) solid var(--u-divider);
}

.u-filter-bar__left { display: flex; align-items: center; gap: var(--u-space-20); }
.u-tabs             { display: flex; align-items: center; gap: var(--u-space-20); }
.u-header-links     { display: flex; align-items: center; gap: var(--u-space-20); }

/* Группа вкладок фиксированной ширины (figma-spec-2 §1: tab-section = 285px).
   Маркер 7×7 + gap 8 добавляют активной вкладке ровно 15px и столько же
   снимают с неактивной, поэтому натуральная ширина группы одинакова в обоих
   состояниях; фиксация — страховка от «дыхания» строки при смене подписей. */
.u-tabs.is-fixed { width: var(--u-tabsec-w); flex: 0 0 auto; }

/* ============================================================================
   13. u-filter-panel — панель раскрытия фильтра. figma-spec-3 §1
   Это НЕ дропдаун и НЕ оверлей: блок в потоке страницы шириной с контейнер
   (x=20, w=1400), без фона, рамки и тени; список под ним просто сдвигается вниз.
   Геометрия макета: 20px от линии полосы фильтров до верха контента,
   40px снизу до шапки таблицы. Оба отступа лежат ВНУТРИ компонента
   (padding: 20 / 0 / 40) — так вся высота раскрытия анимируется одним блоком.
   ========================================================================== */

.u-filter-panel {
  padding: var(--u-space-20) 0 4rem;
  background: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* Колонки 217 с gap 20 (x: 20 / 257 / 494 / 731 / 968 при поле страницы 20).
   Примечание — шестой трек шириной 215, прижат вправо: при 4 колонках
   (критерии) зазор до него больше шага сетки, так в макете и есть. */
.u-filter-panel__grid {
  display: flex;
  align-items: flex-start;
  gap: var(--u-space-20);
  width: 100%;
}

.u-filter-panel__note {
  width: 21.5rem;
  flex: 0 0 auto;
  margin-left: auto;
  text-align: right;
  font-size: var(--u-l6-size);
  line-height: 1.2;            /* в панели L6 идёт с lh 1.2: бокс 14 при кегле 12 */
  letter-spacing: var(--u-l6-ls);
  font-weight: var(--u-weight-medium);
  color: var(--u-text-muted);
}

/* ============================================================================
   14. u-filter-group — колонка-группа внутри панели. figma-spec-3 §1
   ========================================================================== */

.u-filter-group {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--u-space-12);
  width: 21.7rem;
  flex: 0 0 auto;
  min-width: 0;
}

.u-filter-group__title {
  height: 1.4rem;
  display: flex;
  align-items: flex-end;
  width: 100%;
  font-size: var(--u-l6-size);
  line-height: 1.2;
  letter-spacing: var(--u-l6-ls);
  font-weight: var(--u-weight-medium);
  color: var(--u-text-muted);
}

/* Пункт 16 + gap 6 = шаг 22 из макета. */
.u-filter-group__items {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 0.6rem;
  width: 100%;
}

/* ============================================================================
   15. u-check-item — пункт фильтра с маркером 7×7. figma-spec-3 §2.2–2.3, §3.2
   Один компонент на критерии и регионы: разница только в надстрочном счётчике
   (u-sup внутри подписи, вплотную к слову).
   Маркер ВСЕГДА занимает место (gap 8 + 7px), в невыбранном состоянии
   opacity 0 — поэтому ширина пункта не меняется при выборе.
   ========================================================================== */

.u-check-item {
  display: inline-flex;
  align-items: center;
  gap: var(--u-space-8);
  height: var(--u-control-h);
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  border-radius: 0;
  font-family: inherit;
  font-size: var(--u-l5-size);
  line-height: var(--u-l5-lh);
  letter-spacing: var(--u-l5-ls);
  font-weight: var(--u-weight-medium);
  color: var(--u-text-primary);
  white-space: nowrap;
  cursor: pointer;
  text-align: left;
  transition: color var(--u-dur-nav) var(--u-ease-nav);
}

/* НЕ flex: внутри подписи живёт <sup class="u-sup">, а vertical-align
   во flex-контейнере игнорируется — счётчик встал бы в базовую строку. */
.u-check-item__label { display: inline-block; }

/* Счётчик вплотную к названию, оканчивающемуся точкой; после буквы — 2px,
   иначе цифра зрительно липнет к последнему знаку. */
.u-sup.has-gap { margin-left: 0.2rem; }

.u-check-item__mark {
  display: block;
  flex: 0 0 auto;
  width: 0.7rem;
  height: 0.7rem;
  background: var(--u-icon-white);
  opacity: 0;
  transition: opacity var(--u-dur-nav) var(--u-ease-nav);
}

.u-check-item.is-selected .u-check-item__mark { opacity: 1; }
/* Ховера у пункта в макете нет, но кликабельный голый текст без отклика читается
   плохо: на наведении показываем маркер приглушённым (то же место, тот же
   размер — при выборе он просто «доходит» до 1). Убрать одной строкой. */
@media (hover: hover) {
  .u-check-item:not(.is-disabled):not(.is-selected):hover { color: var(--u-text-muted); }
}

/* Недоступное значение (связанность фильтров): #53535A — заметно темнее
   ховера (#95959D) и заголовка группы, поэтому состояния не путаются.
   Клик не срабатывает. */
.u-check-item.is-disabled {
  color: var(--u-text-disabled);
  font-weight: var(--u-weight-regular);
  pointer-events: none;
  cursor: default;
}

/* ============================================================================
   16. u-sup — надстрочный счётчик. figma-spec-3 §2.4, §3.3
   Механика с idaproject.com: JetBrains Mono 700, кегль 12, `sups`.
   ВНИМАНИЕ: самохостный сабсет JetBrains Mono (vendor/jetbrains-mono) собран
   без функции `sups` и только с начертанием 500 — поэтому надстрочность даём
   явным масштабом (12 × 0.645 = 7.74px, ровно штатный масштаб `sups`) и
   подъёмом, а не font-feature-settings: иначе цифра вышла бы полнокегельной.
   Трекинг −2.4px из макета не переносим: он задан на кегле 12 и при
   отрисованных 7.74px двузначные счётчики регионов слипаются (см. §6.3 спеки).
   ========================================================================== */

.u-sup {
  font-family: var(--u-font-mono);
  font-weight: 700;
  font-size: 0.774rem;          /* = 12px × 0.645, масштаб OpenType `sups` */
  line-height: 1;
  letter-spacing: 0;
  color: var(--u-text-primary);
  margin-left: 0;               /* макет: отступ 0 (на живом сайте 2) */
  vertical-align: super;
  font-variant-numeric: tabular-nums;
}

/* ============================================================================
   17. u-empty — пустая выдача поиска. figma-spec-3 §4.2
   Один текст по центру, 64px под строкой заголовков колонок.
   ========================================================================== */

.u-empty {
  display: block;
  width: 100%;
  text-align: center;
  font-size: var(--u-t1-size);
  line-height: var(--u-t1-lh);
  letter-spacing: var(--u-t1-ls);
  font-weight: var(--u-weight-medium);
  color: var(--u-text-primary);
}

/* ============================================================================
   18. u-tag — плашка-тег страницы-кейса. Макет 15985:539692 (компонент v-tag)
   Замер живых страниц idaproject.com/landing/*: высота 24, радиус 100px,
   padding 0 8, JetBrains Mono 12 uppercase, трекинг −0.12px.

   ЕДИНСТВЕННОЕ МЕСТО В КИТЕ С РАДИУСОМ. Правило «радиусов нет» описывает экран
   рейтинга; страницы-кейсы на основном сайте — другой шаблон, и там плашка
   всегда пилюля. Ставить её прямоугольной значит разойтись с эталоном, ради
   которого весь раздел и переверстан, поэтому исключение сделано осознанно
   и ограничено этим компонентом.
   ========================================================================== */

.u-tag {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--u-space-4);
  height: 2.4rem;
  padding: 0 var(--u-space-8);
  border: 0;
  border-radius: 10rem;
  background: var(--u-bg-tag);
  font-family: var(--u-font-mono);
  font-size: max(1.2rem, var(--u-fs-floor));
  line-height: 1.2;
  letter-spacing: -0.012rem;
  font-weight: var(--u-weight-medium);
  text-transform: uppercase;
  color: var(--u-text-primary);
  white-space: nowrap;
  text-decoration: none;
  transition: background var(--u-dur) var(--u-ease), color var(--u-dur) var(--u-ease);
}
/* Моно-шрифт сидит в строке выше оптического центра — компенсируем сдвигом
   самой подписи, а не высотой плашки: высота 24 замерена и трогать её нельзя. */
.u-tag__label { display: block; transform: translateY(0.05em); }
.u-tag__icon {
  display: block;
  flex: 0 0 auto;
  width: var(--u-icon-14);
  height: var(--u-icon-14);
  background-color: currentColor;
  -webkit-mask: url("../img/redesign/ic-arrow-up-right.svg") no-repeat center / var(--u-icon-14) var(--u-icon-14);
  mask: url("../img/redesign/ic-arrow-up-right.svg") no-repeat center / var(--u-icon-14) var(--u-icon-14);
}
/* Основная плашка — лаймовая (в кейсах ею помечают главный ярлык), вторичная
   остаётся нейтральной. Ховер есть только у кликабельной (ссылка/кнопка). */
.u-tag.is-brand { background: var(--u-brand); color: var(--u-text-on-brand); }
a.u-tag:hover,
button.u-tag:hover,
a.u-tag:focus-visible,
button.u-tag:focus-visible { background: var(--u-bg-btn-bar-hover); }
a.u-tag.is-brand:hover,
button.u-tag.is-brand:hover { background: var(--u-brand-dim); }

/* ============================================================================
   Motion restraint: уважаем системную настройку
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .u-btn,
  .u-btn__icon,
  .u-filter-btn,
  .u-filter-btn__icon,
  .u-filter-btn .chev,
  .u-tab,
  .u-accent-link,
  .u-link,
  .u-split__in,
  .u-split__out,
  .u-row::before,
  .u-pos::before,
  .u-check-item,
  .u-check-item__mark,
  .u-tag,
  .u-row__note { transition: none; transition-delay: 0s; }
}
