/* =============================================================================
   mailcore.uz — общий слой компонентов
   site/assets/mailcore.css

   Один файл на три поверхности: маркетинговый сайт, мастер подключения домена
   и панель владельца. Сборщика нет и не будет — страница подключает этот файл
   одним <link>, и весь словарь классов доступен сразу.

   ЧТО ЭТО ЗА СИСТЕМА. Ориентир один — ПАНЕЛЬ Cloudflare, тот вид, где после
   входа перечислены домены. Она собрана на «kumo» поверх Tailwind v4: плотная,
   продуктовая, база 14px. Маркетинговый сайт cloudflare.com — другая система,
   с кеглем 56px и лицензионным шрифтом; он здесь не используется вообще.

   Значения не пересказаны и не выведены: они СНЯТЫ браузером со страницы
   панели в светлой теме и лежат в design/kumo-dashboard.json — 349 токенов,
   вычисленные стили h1/h2/h3/button/input/a/nav/header и метрики body.

   Цвета берутся один в один, включая фирменные: синий oklch(57.72% .2324 260)
   ведёт органы управления, оранжевый #f6821f — текстовый акцент. Наше здесь
   только УЗКОЕ: логотип, словесное обозначение, название продукта и тексты.
   Ни знака Cloudflare, ни формулировок о связи с ними в файле нет.

   ЯЗЫК ИНТЕРФЕЙСА — РУССКИЙ, отсюда выбор шрифта. Inter несёт cyrillic и
   cyrillic-ext (U+0400–045F), JetBrains Mono — тоже. Hanken Grotesk, Figtree
   и Schibsted Grotesk кириллицы не имеют вообще: русский текст на них
   разваливается на середине слова, подставляя системный шрифт. Проверено по
   unicode-range в ответе Google Fonts, а не по описанию на сайте.

   ТЕМА ОДНА — СВЕТЛАЯ, и это решение владельца, а не упущение. Тёмных блоков
   в файле нет: ни под prefers-color-scheme, ни под [data-theme]. Переключателя
   тоже нет. У панели тёмная половина есть, но берётся только светлая.
   Скин Roundcube (server/roundcube/skins/mailcore/) тёмную тему СОХРАНЯЕТ и
   этого файла не подключает: у него собственный :root и собственный набор.

   ПОЛОЖЕНИЕ СЛОЁВ. Фон страницы — холст oklch(98.75% 0 0), чуть темнее белого.
   Карточки, панели и поля лежат на БЕЛОМ поверх него. Из-за этой разницы в
   1.25% светлоты страница читается слоями, а не одним полем; если поставить
   холст в белый, вся конструкция схлопнется.

   АНИМАЦИЯ берётся только из токенов --duration-* и --ease-*. Кривые — те, что
   объявлены у панели: --ease-out: cubic-bezier(0, 0, .2, 1), умолчание
   --duration-base: 200ms. Наведение меняет цвет и прозрачность, и больше
   ничего: ни сдвига, ни масштаба, ни роста тени. Двигается только нажатие.

   ДОСТУПНОСТЬ. У каждого интерактивного элемента есть видимое кольцо
   :focus-visible на токенах. Цвет нигде не единственный носитель смысла.
   Цель нажатия — не меньше 36px (--control-h-base); там, где извлечённый
   размер меньше, он поднимается на пальцевом вводе.

   ЗАВИСИМОСТЕЙ НЕТ, кроме Google Fonts. Иконки — инлайновый SVG на сетке
   24×24, обводка 1.5, круглые концы и стыки (класс .icon задаёт это за вас).

   ПОДКЛЮЧЕНИЕ. В <head>, до этого файла, поставьте два preconnect — иначе
   к шрифтам добавится лишний DNS + TLS на критическом пути:

     <link rel="preconnect" href="https://fonts.googleapis.com">
     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
     <link rel="stylesheet" href="/assets/mailcore.css">

   Скрипта, выставляющего тему до отрисовки, больше не нужно: тема одна.
   Если он остался в разметке — уберите его вместе с кнопкой переключателя,
   ключом "theme" в localStorage и парой <meta name="theme-color">.

   -----------------------------------------------------------------------------
   СОДЕРЖАНИЕ

    0.  Шрифты
    1.  Токены — замер панели, design/kumo-dashboard.json
    2.  Дополнения к токенам
    3.  Сброс
    4.  База документа и глобальная типографика
    5.  Фокус, выделение, служебные классы доступности
    6.  Компоновка: контейнеры, секции, сетка, стопка, кластер, разворот
    7.  Каркас приложения и страница входа
    8.  Страничная типографика — .prose (ряд заголовков панели)
    9.  Документная типографика — .doc (24/20/16/14, текст 16px)
   10.  Ссылки, текстовые роли и утилиты
   11.  Кнопки
   12.  Поля ввода и формы
   13.  Флажки, радиокнопки, переключатели
   14.  Карточки
   15.  Таблицы данных
   16.  Боковая навигация
   17.  Бейджи
   18.  Баннеры
   19.  Тосты
   20.  Модальные окна
   21.  Вкладки
   22.  Пустое состояние и скелетон
   23.  Иконки, разделители, подсказки, аватары, клавиши
   24.  Код, значения для копирования, статусы проверки
   25.  Шаги мастера подключения
   26.  Шапка и подвал сайта
   27.  Появление при прокрутке
   28.  Панель владельца: плитки, шкалы, факты, раскрытие строки,
        устаревший снимок, плотные колонки
   29.  Уменьшенная анимация и печать
   ============================================================================= */

/* =============================================================================
   0. ШРИФТЫ

   Параметр subset перечислен явно — этого требует ТЗ, — но полагаться на него
   не нужно: css2 и без него отдаёт все подмножества, размеченные unicode-range,
   и браузер тянет только те, что реально встретились в тексте. Кириллица
   приходит двумя файлами: cyrillic (U+0400–045F, основной русский) и
   cyrillic-ext (U+0460–052F, старославянский и языки бывшего СССР).

   Диапазон весов 400..700 — это вариативная ось, а не семь отдельных файлов.
   Из него берутся 400 (текст), 450 (--font-weight-code), 500 (подписи кнопок,
   маркетинговые заголовки) и 600 (заголовки продукта). 300 и 800 не
   используются нигде, поэтому ось на них не растянута.

   opsz 14..32 — настоящая оптическая ось Inter v4. Её крутят .font-heading и
   .font-title; это бесплатная отделка, которой нет больше нигде в системе.
   ============================================================================= */

@import url("https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,400..700&family=JetBrains+Mono:wght@400..700&subset=cyrillic,cyrillic-ext,latin,latin-ext&display=swap");

/* =============================================================================
   1. ТОКЕНЫ

   ИСТОЧНИК ЗНАЧЕНИЙ — design/kumo-dashboard.json. Это не пересказ отгруженного
   CSS и не вывод из документации: это 349 пользовательских свойств, снятых
   настоящим браузером со страницы панели в СВЕТЛОЙ теме, плюс замеренные
   вычисленные стили h1/h2/h3/button/input/a/nav/header и собственные метрики
   body. Где JSON и наши прежние догадки расходились — прав JSON; все такие
   расхождения помечены ниже словом ЗАМЕР.

   В отгруженном CSS каждый цвет упакован как
       var(--lightningcss-light,X)var(--lightningcss-dark,Y)
   и раздел "resolved" уже содержит вычисленный СВЕТЛЫЙ X. Берём его.

   ФОРМА ЗАПИСИ — oklch там, где в JSON oklch, и hex там, где в JSON hex.
   Причина не в моде: --brand это oklch(57.72% .2324 260), и его sRGB-запись
   #056DFF — обрезанная по охвату проекция, а не тот же цвет. На дисплее P3
   oklch отдаёт настоящий цвет, hex — обрезанный. Плюс каждый перевод
   oklch -> hex это ещё одно округление, которое некому проверить. Hex
   оставлен в комментарии рядом: по нему удобно грепать и сравнивать с
   design/README.md. Поддержка oklch — Baseline с 2023 года, а файл и раньше
   считал color-mix(in oklab), то есть той же эпохи.

   ТЕМА ОДНА — СВЕТЛАЯ. Тёмных блоков в этом файле больше нет: ни под
   prefers-color-scheme, ни под [data-theme]. Скин Roundcube тёмную тему
   сохраняет, но он самодостаточен — объявляет собственный :root на 297
   свойств и этот файл не подключает, — поэтому удаление здесь его не задевает.
   ============================================================================= */

/* =============================================================================
   mailcore.uz — design tokens
   Measured from the Cloudflare dashboard ("kumo", Tailwind v4) in light mode,
   2026-08-28. Ground truth: design/kumo-dashboard.json.

   PROVENANCE. Every value below is one of:
     - the light half of a resolved kumo custom property, quoted verbatim;
     - a computed style measured off a real element on that page (marked ЗАМЕР);
     - our own judgement, marked НАШЕ.

   The dashboard is the only reference. www.cloudflare.com is a different design
   system with 56px display type and a licensed face; it is deliberately not
   consulted anywhere in this file.

   IDENTITY. Colours, metrics and type are functional facts and are taken 1:1,
   brand blue and brand orange included. What stays ours is the narrow thing:
   our logo, our wordmark, our product name, our copy. No Cloudflare mark
   appears here and no text claims a relationship with them.
   ============================================================================= */

:root {
  color-scheme: light;   /* одна тема; заодно светлые системные контролы */

  /* ==========================================================================
     1. NEUTRAL RAMP
     kumo's achromatic ramp: Tailwind v4's --color-neutral-* plus the extra
     --color-kumo-neutral-* steps. The oklch strings are the values; the hex in
     the comment is the sRGB projection, for grepping only.
     ========================================================================== */
  --neutral-0:     #FFFFFF;             /* --color-white */
  --neutral-25:    oklch(99% 0 0);      /* --color-neutral-25        #FCFCFC */
  --neutral-50:    oklch(98.5% 0 0);    /* --color-neutral-50        #FAFAFA */
  --neutral-60:    oklch(98.75% 0 0);   /* --color-kumo-neutral-50   #FBFBFB  холст */
  --neutral-75:    oklch(98% 0 0);      /* --color-kumo-neutral-75   #F8F8F8 */
  --neutral-100:   oklch(97% 0 0);      /* --color-neutral-100       #F5F5F5 */
  --neutral-125:   oklch(96.5% 0 0);    /* --color-kumo-neutral-125  #F3F3F3 */
  --neutral-150:   oklch(96% 0 0);      /* --color-neutral-150       #F2F2F2 */
  --neutral-175:   oklch(93.5% 0 0);    /* hairline seed             #E9E9E9 */
  --neutral-200:   oklch(92.2% 0 0);    /* --color-neutral-200       #E5E5E5 */
  --neutral-250:   oklch(90% 0 0);      /* --color-neutral-250       #E3E3E3  (новое: есть у kumo, у нас не было) */
  --neutral-300:   oklch(87% 0 0);      /* --color-neutral-300       #D4D4D4 */
  --neutral-400:   oklch(70.8% 0 0);    /* --color-neutral-400       #A1A1A1 */
  --neutral-500:   oklch(55.6% 0 0);    /* --color-neutral-500       #737373 */
  --neutral-600:   oklch(43.9% 0 0);    /* --color-neutral-600       #525252 */
  --neutral-700:   oklch(37.1% 0 0);    /* --color-neutral-700       #404040 */
  --neutral-750:   oklch(32% 0 0);      /* --color-kumo-neutral-750  #333333 */
  --neutral-800:   oklch(26.9% 0 0);    /* --color-neutral-800       #262626 */
  --neutral-850:   oklch(23% 0 0);      /* --color-neutral-850       #1D1D1D */
  --neutral-900:   oklch(20.5% 0 0);    /* --color-neutral-900       #171717 */
  --neutral-925:   oklch(17% 0 0);      /* --color-kumo-neutral-925  #0F0F0F  (у Tailwind-ступени --color-neutral-925 своё значение, 17.5%) */
  --neutral-940:   oklch(15% 0 0);      /* --color-kumo-neutral-950  #0B0B0B  focus seed */
  --neutral-950:   oklch(14.5% 0 0);    /* --color-neutral-950       #0A0A0A */
  --neutral-975:   oklch(12% 0 0);      /* --color-kumo-neutral-975  #060606  contrast seed */
  --neutral-1000:  oklch(10% 0 0);      /* --color-kumo-neutral-1000 #030303 */
  --neutral-black: #000000;             /* --color-black */

  /* ==========================================================================
     2. BRAND
     Владелец решил брать kumo целиком, включая фирменные цвета: подменять их
     на свой индиго больше не нужно. Отличается только знак — логотип и
     словесное обозначение остаются нашими.

     ДВА фирменных цвета, и они разного назначения. Синий ведёт органы
     управления: заливку основной кнопки, кольцо фокуса, активное состояние.
     Оранжевый — ТЕКСТОВЫЙ акцент (--text-color-kumo-brand), и только он: на
     заливке он даёт 1.88:1 белым и не годится под кнопку.
     ========================================================================== */
  --brand:       oklch(57.72% .2324 260);         /* --color-kumo-brand        #056DFF */
  --brand-hover: oklch(48.8% .243 264.376);       /* --color-kumo-brand-hover  blue-700 #1447E6 */
  --brand-tint:  oklch(93.2% .032 255.585);       /* blue-100                  #DBEAFE */
  --on-brand:    #FFFFFF;                         /* kumo Button primary: !text-white; 4.53:1 на --brand */

  /* Три производные — формула из модуля kumo Button:
       color-mix(in oklch, <seed>, black 10% | white 30% | white 15%)
     Записаны РЕЗУЛЬТАТОМ, а не живым color-mix, и причина конкретная: в
     полярном пространстве примесь чёрного или белого тянет за собой ещё и УГОЛ
     ТОНА, и разные движки разрешают этот случай по-разному. Кольцо фокуса —
     не то место, где допустим разброс между браузерами. Округления при этом
     нет: считалось в oklch и записано в oklch, меняются только L и C, тон 260
     остаётся ровно тем же.
     Если правите --brand — пересчитайте эти три строки по формуле выше. */
  --brand-emphasis-ring:       oklch(51.948% .20916 260);  /* #045EDE, 5.55:1 на холсте */
  --brand-emphasis-bg:         oklch(70.404% .16268 260);  /* #619EFF */
  --brand-emphasis-grad-start: oklch(64.062% .19754 260);  /* #3C86FF */
  --brand-emphasis-grad-end:   var(--brand);

  /* ==========================================================================
     3. SURFACES
     Шесть плоскостей. Компонент выбирает одну и никогда не сочиняет фон.
     Холст 98.75% темнее белого — на нём карточка из --surface-base читается
     как поднятая панель, а не сливается с полем.
     ========================================================================== */
  --surface-canvas:     oklch(98.75% 0 0);  /* --color-kumo-canvas    #FBFBFB  фон страницы */
  --surface-elevated:   oklch(98% 0 0);     /* --color-kumo-elevated  #F8F8F8  шапки и подвалы карточек, меню */
  --surface-recessed:   oklch(96.5% 0 0);   /* --color-kumo-recessed  #F3F3F3  колодцы, дорожка вкладок, подложка модалки */
  --surface-base:       #FFFFFF;            /* --color-kumo-base               карточки, строки таблиц, вторичные кнопки */
  --surface-tint:       oklch(97% 0 0);     /* --color-kumo-tint      #F5F5F5  наведение, зебра, выбранная строка */
  --surface-overlay:    oklch(98.75% 0 0);  /* --color-kumo-overlay   #FBFBFB  ЗАМЕР: равен холсту (в отгруженном фолбэке стояло 97.5%) */
  --surface-control:    #FFFFFF;            /* --color-kumo-control            фон полей ввода, панели инструментов */
  --surface-fill:       oklch(92.2% 0 0);   /* --color-kumo-fill      #E5E5E5  линейки таблиц, заливка вторичного бейджа */
  --surface-fill-hover: oklch(96.5% 0 0);   /* --color-kumo-fill-hover #F3F3F3 (тот же шаг, что и recessed) */
  --surface-interact:   oklch(87% 0 0);     /* --color-kumo-interact  #D4D4D4 */
  --surface-contrast:   oklch(12% 0 0);     /* --color-kumo-contrast  #060606  ЗАМЕР: 12%, а не 8.5% — фолбэк в CSS протух */

  /* ==========================================================================
     4. LINES, HAIRLINES, FOCUS
     kumo рисует почти каждый край КОЛЬЦОМ (box-shadow), а не border, поэтому
     край ничего не двигает в потоке. Покоящийся фокус — контрастное, почти
     чёрное кольцо; фирменный цвет появляется только на :focus-visible.
     ========================================================================== */
  --line:             oklch(14.5% 0 0 / .1);  /* --color-kumo-line      rgba(10,10,10,.1) */
  --hairline:         oklch(93.5% 0 0);       /* --color-kumo-hairline  #E9E9E9 */
  --focus-ring-color: oklch(15% 0 0);         /* --color-kumo-focus     #0B0B0B */

  /* ==========================================================================
     5. TEXT
     Контраст на --surface-base: default 17.91:1, subtle 4.73:1,
     placeholder 2.58:1, inactive 1.48:1. --text-inactive допустим только на
     действительно отключённом контроле, живому тексту его давать нельзя.
     ========================================================================== */
  /* ЗАМЕР и токен здесь расходятся, и оба настоящие: <body> панели покрашен в
     rgb(49,49,49) — это #313131 из старой палитры cl1-gray-1, — тогда как
     ступень "default" в токенах равна neutral-900. Замер и есть то, чем
     реально написан текст страницы, поэтому body берёт --text-body, а
     компоненты (метки, значения, заголовки) — более тёмный --text-default.
     Получается обычная иерархия: набор чуть светлее подписей. */
  --text-body:        #313131;               /* ЗАМЕР body color, 12.55:1 на холсте */
  --text-default:     oklch(20.5% 0 0);      /* --text-color-kumo-default     neutral-900 #171717 */
  --text-strong:      oklch(14.5% 0 0);      /* --text-color-kumo-strong      neutral-950 #0A0A0A */
  --text-subtle:      oklch(55.6% 0 0);      /* --text-color-kumo-subtle      neutral-500 #737373 */
  --text-inactive:    oklch(87% 0 0);        /* --text-color-kumo-inactive    neutral-300 #D4D4D4 */
  --text-placeholder: oklch(70.8% 0 0);      /* --text-color-kumo-placeholder neutral-400 #A1A1A1 */
  --text-inverse:     oklch(97% 0 0);        /* --text-color-kumo-inverse     neutral-100 #F5F5F5 */
  --text-link:        oklch(42.4% .199 265.638);  /* --text-color-kumo-link    blue-800 #193CB8, 8.84:1 на белом */
  --text-info:        oklch(42.4% .199 265.638);  /* --text-color-kumo-info    blue-800 */
  --text-success:     oklch(43.2% .095 166.913);  /* --text-color-kumo-success emerald-800 #006045 */
  --text-danger:      oklch(50.5% .213 27.518);   /* --text-color-kumo-danger  red-700 #C10007 */
  --text-warning:     oklch(59.7% .144 57.5);     /* --text-color-kumo-warning #BD6500 */
  /* Оранжевый фирменный. ТОЛЬКО текст: надзаголовки, акцентное слово в
     заголовке, активный пункт. Под заливку не годится — см. группу 2. */
  --text-brand:       #f6821f;               /* --text-color-kumo-brand */

  /* ==========================================================================
     6. STATUS
     FILL — для точек, переключателей и сплошных знаков; TEXT (группа 5) — для
     слов; TINT — полупрозрачные подложки баннеров и бейджей. Заливка никогда
     не работает цветом текста.
     ========================================================================== */
  --info:    oklch(62.3% .214 259.815);   /* --color-kumo-info  ЗАМЕР: blue-500 #2B7FFF. В CSS фолбэком стоял sky-500 (68.5% .169 237.3) — он протух, вычисленное значение синее */
  --success: oklch(59.6% .145 163.225);   /* --color-kumo-success emerald-600 #009966 */
  --warning: oklch(73.9% .177 58.2);      /* --color-kumo-warning #FA8900 */
  --danger:  oklch(63.7% .237 25.331);    /* --color-kumo-danger  red-500 #FB2C36 */
  --info-tint:      oklch(93.2% .032 255.6 / .45);  /* --color-kumo-info-tint */
  --success-tint:   oklch(96.2% .043 156.7 / .57);  /* --color-kumo-success-tint */
  --warning-tint:   oklch(93.1% .107 94.6 / .2);    /* --color-kumo-warning-tint */
  --danger-tint:    oklch(93.6% .032 17.7 / .42);   /* --color-kumo-danger-tint */
  --banner-info:    oklch(93.2% .032 255.585 / .7); /* --color-kumo-banner-info */
  --banner-warning: oklch(97.3% .071 103.193);      /* yellow-100 #FEF9C2 — НАШЕ: парного --color-kumo-banner-warning на панели нет, взята та же ступень, что и у info */

  /* Опасная кнопка — та же формула, но от --danger, и так же записана
     результатом. Тон 25.331 сохранён без изменений. */
  --danger-emphasis-ring:       oklch(57.33% .2133 25.331);   /* #DA252E, 4.73:1 на холсте */
  --danger-emphasis-bg:         oklch(74.59% .1659 25.331);   /* #FF7D75 */
  --danger-emphasis-grad-start: oklch(69.145% .20145 25.331); /* #FF5B57 */

  /* ==========================================================================
     7. BADGE PALETTE
     --color-kumo-badge-*. Сплошные заливки несут белый текст — кроме
     оранжевой: она слишком светлая (белым 1.88:1) и несёт ЧЁРНЫЙ, 11.17:1.
     Это извлечено, а не придумано.
     ========================================================================== */
  --badge-red:      oklch(57.7% .245 27.325);   /* red-600     #E7000B */
  --badge-green:    oklch(59.6% .145 163.225);  /* emerald-600 #009966 */
  --badge-orange:   oklch(81.5% .197 76);       /* orange-650  #FFAC00 — чёрный текст */
  --badge-purple:   oklch(55.8% .288 302.321);  /* purple-600  #9810FA */
  --badge-teal:     oklch(54.9% .096 184.565);  /* teal-650    #058378 */
  --badge-blue:     oklch(54.6% .245 262.881);  /* blue-600    #155DFC */
  --badge-neutral:  oklch(55.6% 0 0);           /* neutral-500 #737373 */
  --badge-inverted: oklch(14.5% 0 0);           /* neutral-950 #0A0A0A */
  --badge-teal-subtle:         oklch(91% .096 180.426);   /* teal-200 #96F7E4 — НАШЕ: подложки для приглушённого бейджа у панели нет, взята ступень, парная тексту teal-800 */
  --text-badge-teal-subtle:    oklch(43.7% .078 188.216); /* --text-color-kumo-badge-teal-subtle    teal-800 */
  --text-badge-neutral-subtle: oklch(26.9% 0 0);          /* --text-color-kumo-badge-neutral-subtle neutral-800 */
  --text-badge-orange-subtle:  oklch(47% .157 37.304);    /* --text-color-kumo-badge-orange-subtle  orange-800 */
  --text-badge-inverted:       #FFFFFF;                   /* --text-color-kumo-badge-inverted */

  /* ==========================================================================
     8. SHADOW INGREDIENTS, SKELETON, SELECTION
     ========================================================================== */
  --shadow-edge: oklch(0% 0 0 / .12);  /* --color-kumo-shadow-edge */
  --shadow-drop: oklch(0% 0 0 / .08);  /* --color-kumo-shadow-drop */
  /* Стрелка подсказки. Раньше эти два токена были взяты из маркетингового
     бандла; на панели они называются --color-kumo-arrow-edge / -stroke, и в
     светлой теме край рисует тень, а обводки нет вовсе. */
  --tip-shadow:  oklch(14.5% 0 0 / .1);  /* --color-kumo-arrow-edge (то же значение, что --line) */
  --tip-stroke:  transparent;            /* --color-kumo-arrow-stroke */
  --tooltip-border: oklch(92.8% .006 264.531);  /* --color-tooltip-border gray-200 — новое, у нас его не было */
  --skeleton-bg:      oklch(96.7% .003 264.542);  /* gray-100 #F3F4F6 */
  --skeleton-shimmer: oklch(0% 0 0 / .08);        /* #00000014 */

  /* Выделение мышью — доля бренда, поэтому пересчитывается само при смене
     --brand и не требует отдельного значения. */
  --selection-bg:   color-mix(in oklch, var(--brand) 10%, transparent);
  --selection-text: var(--text-body);

  /* ==========================================================================
     9a. TYPOGRAPHY — families and rendering
     ЗАМЕР --font-sans: "Inter Variable", ui-sans-serif, system-ui, sans-serif.
     Ровно она и стоит ниже; "Inter" добавлена вторым именем потому, что Google
     Fonts отдаёт семейство под именем Inter, а не Inter Variable.
     Моноширинную подменяем: kumo ставит "Paper Mono", у которой нет ни
     публичной лицензии, ни отдаваемого файла. JetBrains Mono — OFL-1.1, и обе
     несут cyrillic и cyrillic-ext, что для русского интерфейса обязательно.
     ========================================================================== */
  --font-sans: "Inter Variable", "Inter", ui-sans-serif, system-ui, sans-serif,
               "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
  --font-mono: "JetBrains Mono Variable", "JetBrains Mono", ui-monospace, SFMono-Regular,
               Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;  /* НАШЕ: замена нелицензируемой "Paper Mono" */

  /* cv02/03/04 = открытые 4/6/9; calt = контекстные альтернативы (панель);
     cv11 = одноэтажная a (документация). Строки различаются только слотом 4. */
  --font-feature-ui:    "cv02", "cv03", "cv04", "calt";
  --font-feature-prose: "cv02", "cv03", "cv04", "cv11";
  /* Настоящая оптическая ось Inter v4; панель её крутит. Бесплатная отделка. */
  --opsz-heading: 32;
  --opsz-title:   24;

  /* ==========================================================================
     9b. TYPOGRAPHY — the size ladder
     ЗАМЕР: --text-xs 12px, --text-sm 13px, --text-base 14px, --text-lg 16px.
     Панель бросает rem-умолчания Tailwind и прибивает плотный конец лестницы
     к ПИКСЕЛЯМ с шагом в 1px, чтобы плотная обвязка и плотное содержимое
     стояли рядом без видимого скачка. Именно поэтому база 14px, а не 16px.

     Верхний конец (xl и выше) у панели, наоборот, в rem — так он и оставлен.
     Это не небрежность источника, а разделение: плотная обвязка не должна
     ехать от системной настройки размера шрифта, а крупный текст должен.

     Интерлиньяжи записаны той же формулой calc(), что в отгруженном CSS, —
     дословно, без округления до 1.1765.
     ========================================================================== */
  --text-2xs:  11px;      --text-2xs-lh:  1;                   /* НАШЕ: kumo Sidebar.MenuBadge text-[11px]/none */
  --text-xs:   12px;      --text-xs-lh:   calc(1 / .75);       /* -> 16px */
  --text-sm:   13px;      --text-sm-lh:   calc(1 / .85);       /* -> 15.3px */
  --text-base: 14px;      --text-base-lh: 1.5;                 /* -> 21px */
  --text-lg:   16px;      --text-lg-lh:   1.5;                 /* -> 24px */
  --text-xl:   1.25rem;   --text-xl-lh:   calc(1.75 / 1.25);   /* 20px / 1.4 */
  --text-2xl:  1.5rem;    --text-2xl-lh:  calc(2 / 1.5);       /* 24px / 1.3333 */
  --text-3xl:  1.875rem;  --text-3xl-lh:  calc(2.25 / 1.875);  /* 30px / 1.2 */
  --text-4xl:  2.25rem;   --text-4xl-lh:  calc(2.5 / 2.25);    /* 36px / 1.1111 */
  --text-5xl:  3rem;      --text-5xl-lh:  1;                   /* 48px — верх лестницы панели */

  --font-weight-light:     300;
  --font-weight-normal:    400;
  --font-weight-code:      450;  /* НАШЕ: сдвиг вариативной оси для строчного <code>, а не скачок в 500 */
  --font-weight-medium:    500;
  --font-weight-semibold:  600;
  --font-weight-bold:      700;
  --font-weight-extrabold: 800;

  /* ТРЕКИНГ. Панель задаёт его ОДИН раз, на html, и больше нигде.
     ЗАМЕР: у h1 32px, у h2 24px, у input 13.33px letter-spacing одинаков и
     равен -0.16px. Так и должно быть: letter-spacing наследуется уже
     вычисленной ДЛИНОЙ, а не долей кегля, поэтому -0.01em на корне в 16px
     превращается в -0.16px и в таком виде доезжает до каждого потомка.
     Из этого следует правило: заголовки НЕ переопределяют letter-spacing.
     Собственный трекинг остаётся только там, где он несёт смысл: моноширинный
     сброс в 0 и разрядка капслока. */
  --tracking-ui:      -0.01em;   /* html { letter-spacing } -> -0.16px, глобально */
  --tracking-tight:   -0.025em;
  --tracking-normal:  0em;       /* сброс на моноширинном, иначе код уезжает с сетки */
  --tracking-wide:    0.025em;
  --tracking-wider:   0.05em;
  --tracking-widest:  0.1em;
  --tracking-eyebrow: 0.14em;    /* НАШЕ: кириллица в капслоке без разрядки читается плохо */

  --leading-none:    1;      /* НАШЕ */
  --leading-tight:   1.25;
  --leading-snug:    1.375;
  --leading-normal:  1.5;
  --leading-relaxed: 1.625;
  --leading-loose:   2;

  --measure-prose:      43.5rem;  /* НАШЕ: ~78 знаков на 16px; у панели длинного текста нет */
  --measure-prose-wide: 52rem;    /* НАШЕ */
  --measure-max:        65ch;     /* НАШЕ */

  /* ==========================================================================
     9c. TYPOGRAPHY — the heading ramp
     ЗАМЕР, дословно:
         h1  32px / 400 / 40px  (1.25)
         h2  24px / 600 / 32px  (1.3333)
         h3  20px / 600 / 25px  (1.25)
     Ниже h3 панель заголовков не размечает вообще — там кончается лестница
     заголовков и начинается обычная. Отсюда правило для h4-h6, и оно одно:

       h1-h3 — три выделенные ступени 32/24/20;
       h4-h6 — это НЕ отдельные кегли, а обычная лестница текста (lg / base /
               sm = 16 / 14 / 13), взятая полужирной. Ниже 20px заголовок
               отличается ВЕСОМ, а не размером, потому что размеров между 20 и
               16 у панели нет и выдумывать их значило бы уйти от замера.

     Веса тоже из замера, и они переворачивают прежнее правило файла: раньше
     все заголовки весили 500 (это шло от маркетинга, где отгружены всего два
     начертания). У панели h1 ЛЁГКИЙ — 400, — а h2 и h3 полужирные, 600.
     Крупный лёгкий заголовок над плотным полужирным текстом и есть узнаваемая
     осанка панели.

     ПОТОЛОК ПЕРВОГО ЭКРАНА. Прежняя шкала доходила до 56px (h1) и 64px
     (герой). Такого яруса у панели нет: её h1 равен 32px, а самый крупный
     кегль во всём наборе токенов — --text-5xl, 48px. Посадочной странице
     присутствие нужно, поэтому герой получает РОВНО ОДНУ ступень выше
     страничного заголовка, и берётся она не с потолка, а с верха той же
     лестницы: 48px на широком экране, 36px (--text-4xl) на узком. Ничего выше
     48px в панели не существует, а 56 и 64 приехали из маркетингового набора,
     который здесь не используется. Вес герою оставлен тот же, что у h1 —
     400: именно лёгкость на 48px и держит страницу и панель одним продуктом.
     Интерлиньяж 1.1 — единственное выведенное здесь число: у --text-5xl он
     равен 1, и на двух строках русского заголовка строки склеиваются.
     ========================================================================== */
  /* Три выделенные ступени. 32px в лестнице --text-* своего имени не имеет:
     соседние ступени — 30px (--text-3xl) и 36px (--text-4xl). Поэтому размер
     записан числом и помечен как замер, а не подтянут к ближайшему токену.
     У h3 интерлиньяж 1.25, а не 1.4 из --text-xl-lh: замер даёт 20/25. */
  --type-h1-size: 2rem;               --type-h1-leading: 1.25;               --type-h1-weight: 400;  /* ЗАМЕР 32/400/40 */
  --type-h2-size: var(--text-2xl);    --type-h2-leading: var(--text-2xl-lh); --type-h2-weight: 600;  /* ЗАМЕР 24/600/32 */
  --type-h3-size: var(--text-xl);     --type-h3-leading: 1.25;               --type-h3-weight: 600;  /* ЗАМЕР 20/600/25 */
  /* Ниже h3 заголовков панель не размечает. h4-h6 — не отдельные кегли, а та
     же лестница текста lg/base/sm, взятая полужирной. Правило видно прямо в
     коде: справа стоят те же токены, что и у обычного текста. */
  --type-h4-size: var(--text-lg);     --type-h4-leading: var(--text-lg-lh);   --type-h4-weight: 600;  /* 16px */
  --type-h5-size: var(--text-base);   --type-h5-leading: var(--text-base-lh); --type-h5-weight: 600;  /* 14px */
  --type-h6-size: var(--text-sm);     --type-h6-leading: var(--text-sm-lh);   --type-h6-weight: 600;  /* 13px */
  --type-p-size:     var(--text-lg);  --type-p-leading:     var(--text-lg-lh);  --type-p-weight:     400;  /* ЗАМЕР: <body> панели 16px/24px */
  --type-small-size: var(--text-sm);  --type-small-leading: var(--text-sm-lh);  --type-small-weight: 400;
  --type-code-size:  0.85em;          --type-code-leading:  1.3;  /* доля от окружения: одинаково сидит и в тексте, и в заголовке */

  /* Первый экран и плотные врезки — переопределяют только кегли. */
  --type-hero-h1-size: var(--text-4xl);  --type-hero-h1-size-md: var(--text-5xl);  --type-hero-h1-leading: 1.1;  /* 36 -> 48px, верх лестницы */
  --type-hero-h2-size: var(--text-2xl);  --type-hero-h2-size-md: var(--text-3xl);  /* 24 -> 30px */
  --type-hero-h3-size: var(--text-xl);   --type-hero-h3-size-md: var(--text-2xl);  /* 20 -> 24px */
  --type-hero-p-size:  var(--text-lg);   --type-hero-p-size-md:  var(--text-xl);   /* 16 -> 20px */
  --type-compact-h1-size: var(--text-2xl);
  --type-compact-h2-size: var(--text-xl);
  --type-compact-h3-size: var(--text-lg);
  --type-compact-p-size:  var(--text-base);

  /* Документная шкала — на ступень ниже страничной, потому что .doc всегда
     лежит ВНУТРИ страницы: ответ в FAQ, инструкция шага мастера. Тот же ряд
     24 / 20 / 16 / 14, все весом 600. */
  --type-doc-h1-size: var(--text-2xl);   --type-doc-h1-weight: 600;  /* 24px */
  --type-doc-h2-size: var(--text-xl);    --type-doc-h2-weight: 600;  /* 20px */
  --type-doc-h3-size: var(--text-lg);    --type-doc-h3-weight: 600;  /* 16px */
  --type-doc-h4-size: var(--text-base);  --type-doc-h4-weight: 600;  /* 14px */

  /* ==========================================================================
     10. SPACING
     --spacing: .25rem, всё остальное как calc(--spacing * N). Несущее
     подмножество — 0/2/4/8/12/16/20/24/32/40/48/64/96px на сетке 4px.
     ========================================================================== */
  --space-unit: 0.25rem;   /* 4px — сетка */
  --space-0:    0;
  --space-px:   1px;
  --space-0-5:  0.125rem;  /*   2px */
  --space-1:    0.25rem;   /*   4px */
  --space-1-5:  0.375rem;  /*   6px */
  --space-2:    0.5rem;    /*   8px */
  --space-2-5:  0.625rem;  /*  10px */
  --space-3:    0.75rem;   /*  12px — горизонтальный отступ контрола и ячейки */
  --space-3-5:  0.875rem;  /*  14px */
  --space-4:    1rem;      /*  16px */
  --space-5:    1.25rem;   /*  20px */
  --space-6:    1.5rem;    /*  24px */
  --space-7:    1.75rem;   /*  28px */
  --space-8:    2rem;      /*  32px */
  --space-9:    2.25rem;   /*  36px */
  --space-10:   2.5rem;    /*  40px */
  --space-12:   3rem;      /*  48px */
  --space-14:   3.5rem;    /*  56px */
  --space-16:   4rem;      /*  64px — отступ пустого состояния */
  --space-20:   5rem;      /*  80px */
  --space-24:   6rem;      /*  96px */
  --space-32:   8rem;      /* 128px */

  /* ==========================================================================
     11. RADII
     ЗАМЕР: у кнопки border-radius 8px. Это и есть --radius-lg, умолчание
     приложения. 12px — диалоги и тосты.
     ========================================================================== */
  --radius-xs:   0.125rem;  /*  2px */
  --radius-sm:   0.25rem;   /*  4px — флажок, контролы xs */
  --radius-md:   0.375rem;  /*  6px — контролы sm, пункты меню, подсказки */
  --radius-lg:   0.5rem;    /*  8px — ЗАМЕР: кнопки, поля, карточки, строки навигации */
  --radius-xl:   0.75rem;   /* 12px — диалоги, тосты */
  --radius-2xl:  1rem;      /* 16px */
  --radius-3xl:  1.5rem;    /* 24px */
  --radius-pill: 9999px;    /* НАШЕ: бейджи и рельсы переключателя (у панели это rounded-full) */

  /* ==========================================================================
     12. BORDER / RING WIDTHS
     Края — кольца (box-shadow), поэтому они ничего не стоят в потоке. Фокус на
     поле — это СМЕНА ТОЛЩИНЫ (1px -> 1.5px), а не вынесенное кольцо: смещение
     кольца во всём извлечённом CSS равно нулю.
     ========================================================================== */
  --border-hair:       1px;
  --border-thick:      2px;
  --ring-width:        1px;
  --ring-width-focus:  1.5px;
  --ring-width-strong: 2px;
  --ring-width-hair:   0.5px;
  --ring-width-toast:  0.3px;
  --ring-offset:       0px;

  /* ==========================================================================
     13. SHADOWS
     Умолчания Tailwind v4 плюс составные kumo.
     ========================================================================== */
  --shadow-xs:  0 1px 2px 0 rgba(0, 0, 0, 0.05);
  --shadow-sm:  0 1px 3px 0 rgba(0, 0, 0, 0.10), 0 1px 2px -1px rgba(0, 0, 0, 0.10);
  --shadow-md:  0 4px 6px -1px rgba(0, 0, 0, 0.10), 0 2px 4px -2px rgba(0, 0, 0, 0.10);
  --shadow-lg:  0 10px 15px -3px rgba(0, 0, 0, 0.10), 0 4px 6px -4px rgba(0, 0, 0, 0.10);
  --shadow-xl:  0 20px 25px -5px rgba(0, 0, 0, 0.10), 0 8px 10px -6px rgba(0, 0, 0, 0.10);
  --shadow-2xl: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
  --shadow-drop-sm: 0 1px 2px rgba(0, 0, 0, 0.15);  /* --drop-shadow-sm #00000026 */
  /* Диалог перебивает shadow-xl на пару с альфой 3%: модалка опирается на
     подложку, а не на тяжёлую тень. */
  --shadow-dialog: 0 20px 25px -5px rgba(0, 0, 0, 0.03), 0 8px 10px -6px rgba(0, 0, 0, 0.03);
  /* Примитив подъёма kumo: волосяной край плюс падение на 2px. */
  --shadow-elevation:    0 0 1px 0.5px var(--shadow-edge), 0 1px 2px var(--shadow-drop);
  --shadow-switch-thumb: var(--shadow-elevation);
  /* То, что реально рисует карточка: кольцо + shadow-xs. */
  --shadow-card: 0 0 0 var(--ring-width) var(--line), var(--shadow-xs);

  /* Размытия — --blur-* панели. Раньше 12px стояло числом в .site-header. */
  --blur-sm: 8px;
  --blur-md: 12px;
  --blur-xl: 24px;

  /* ==========================================================================
     14. BREAKPOINTS AND CONTAINERS
     Точек перелома панель токенами не объявляет (кроме --breakpoint-2xl 96rem),
     поэтому ИМЕНА здесь наши, а числа — стоковые Tailwind v4.
     ЗАМЕЧАНИЕ: Roundcube Elastic пользуется 1200/1024/768/576/480/320.
     Совпадают только 768 и 1024; наборы не смешивать.
     ========================================================================== */
  --bp-sm:  40rem;  /*  640px */
  --bp-md:  48rem;  /*  768px — несущая точка */
  --bp-lg:  64rem;  /* 1024px */
  --bp-xl:  80rem;  /* 1280px */
  --bp-2xl: 96rem;  /* 1536px — --breakpoint-2xl */

  --container-page:           1000px;  /* --page-max-width, узкие страницы */
  --container-page-wide:      1600px;  /* --page-max-width, широкие страницы */
  --container-marketing:      1200px;  /* НАШЕ: основная колонка витрины */
  --container-marketing-wide: 1480px;  /* НАШЕ: внешняя рамка */
  --container-section:        1080px;  /* НАШЕ: узкие секции */
  --container-content:        var(--measure-prose);
  --page-padding-x:    16px;  /* --page-padding-x, малые экраны. ЗАМЕР header padding: 0 16px */
  --page-padding-x-lg: 32px;  /* --page-padding-x, большие экраны */

  /* ==========================================================================
     15. COMPONENT METRICS
     Кнопки и поля делят ОДНУ лестницу высот, отступов и радиусов. Именно из-за
     неё панели инструментов панели выстраиваются в линию.
     ========================================================================== */
  --control-h-xs:   20px;  --control-px-xs:    6px;  --control-gap-xs:   4px;
  --control-h-sm:   26px;  --control-px-sm:    8px;  --control-gap-sm:   4px;
  --control-h-base: 36px;  --control-px-base: 12px;  --control-gap-base: 6px;
  --control-h-lg:   40px;  --control-px-lg:   16px;  --control-gap-lg:   8px;

  --header-height:      58px;  /* ЗАМЕР высоты header */
  --row-height:         40px;
  --row-height-compact: 35px;  /* Roundcube @listing-line-height */
  --row-height-touch:   47px;  /* Roundcube @listing-touch-line-height */
  --cell-pad-x:         12px;
  --cell-pad:           12px;

  --sidebar-width:        16.25rem;  /* 260px */
  --sidebar-width-icon:   57px;
  --sidebar-item-h:       34px;
  --sidebar-item-h-sm:    28px;
  --sidebar-bg:           var(--surface-base);
  --sidebar-active-bg:    var(--surface-tint);
  --sidebar-icon-opacity: 0.4;       /* иконки приглушаются, а не перекрашиваются */

  --toast-width:    340px;
  --toast-inset:    16px;
  --toast-inset-lg: 32px;
  --toast-gap:      0.75rem;
  --toast-peek:     0.75rem;

  --icon-size-base: 16px;
  --icon-size-sm:   14px;
  --icon-grid:      24px;  /* рисуем на 24, показываем на 16 — 1.5px на 24 даёт ровно 1.0px на 16 */
  --icon-stroke:    1.5;

  /* ==========================================================================
     16. Z-INDEX
     Верхние три — токены панели дословно. Нижние ступени названы нами, числа
     взяты из переписи классов на отгруженной разметке.
     ========================================================================== */
  --z-base:     0;
  --z-raised:   1;
  --z-sticky:   10;
  --z-header:   20;
  --z-overlay:  40;
  --z-dropdown: 50;
  --z-modal:    9999;     /* --z-index-modal */
  --z-drawer:   99999;    /* --z-index-drawer */
  --z-toast:    1000000;  /* --z-index-toast */

  /* ==========================================================================
     17. MOTION
     Кривые — из набора панели, дословно: --ease-out, --ease-in, --ease-in-out.
     Раньше здесь стояли экспоненциальные кривые Пеннера — они приезжали с
     маркетингового сайта, который теперь не используется вовсе, поэтому
     заменены. Разница слышная: панель тормозит ровно, витрина «выстреливала».
     Одной строки достаточно, чтобы вернуть прежнее ощущение, если оно
     понадобится, — все 20 потребителей ходят через --ease-out.
     ========================================================================== */
  --duration-instant: 0ms;
  --duration-default: 100ms;   /* --default-transition-duration: .1s */
  --duration-fast:    150ms;
  --duration-control: 160ms;
  --duration-base:    200ms;   /* УМОЛЧАНИЕ */
  --duration-medium:  250ms;
  --duration-slow:    300ms;
  --duration-slower:  400ms;
  --duration-sheet:   500ms;
  --duration-reveal:  600ms;
  --duration-hero:    900ms;
  --duration-ambient: 2000ms;  /* только декоративные наплывы прозрачности */
  --duration-sidebar: 250ms;   /* --sidebar-animation-duration */

  --ease-out:      cubic-bezier(0, 0, .2, 1);    /* --ease-out — всё, что входит */
  --ease-in:       cubic-bezier(.4, 0, 1, 1);    /* --ease-in  — всё, что выходит */
  --ease-in-out:   cubic-bezier(.4, 0, .2, 1);   /* --ease-in-out */
  --ease-standard: cubic-bezier(.4, 0, .2, 1);   /* --default-transition-timing-function */
  --ease-control:    cubic-bezier(0.25, 0.46, 0.45, 0.94);   /* easeOutQuad — нажатие кнопки */
  --ease-control-in: cubic-bezier(0.55, 0.085, 0.68, 0.53);  /* easeInQuad — приезд заливки при наведении */
  --ease-disclosure: cubic-bezier(0.87, 0, 0.13, 1);         /* каретка и раскрытие */
  --ease-sheet:      cubic-bezier(0.32, 0.72, 0, 1);         /* нижний лист */
  --ease-media:      cubic-bezier(0.22, 1, 0.36, 1);         /* easeOutQuint — стопка тостов */
  --ease-overshoot:  cubic-bezier(0.34, 1.56, 0.64, 1);      /* единственная пружинящая кривая */
  --ease-overshoot-soft: cubic-bezier(0.34, 1.2, 0.64, 1);
  --ease-sidebar:    cubic-bezier(0.77, 0, 0.175, 1);        /* --sidebar-easing */
  --ease-loop:       linear;  /* все непрерывные циклы */

  /* Ступень задержки между соседями при появлении. */
  --stagger-tight:   7.5ms;
  --stagger-snappy:  15ms;
  --stagger-normal:  33ms;   /* умолчание */
  --stagger-relaxed: 60ms;
  --stagger-delay-children: 50ms;
  --reveal-slide:    20px;
  --reveal-blur:     12px;
  --reveal-scale-up: 0.95;
}

/* =============================================================================
   REDUCED MOTION
   Cloudflare's own global clamp collapses durations to .01ms rather than using
   `animation: none`, so `forwards`/`both` fill modes still land on their end
   state, and infinite loops are clamped to one iteration. Reproduced here at
   the token level so every consumer inherits it. Pair it with the property-level
   clamp on * / ::before / ::after in your base stylesheet.
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
  :root {
    --duration-fast:    0.01ms;
    --duration-control: 0.01ms;
    --duration-base:    0.01ms;
    --duration-medium:  0.01ms;
    --duration-slow:    0.01ms;
    --duration-slower:  0.01ms;
    --duration-sheet:   0.01ms;
    --duration-reveal:  0.01ms;
    --duration-hero:    0.01ms;
    --duration-ambient: 0.01ms;
    --duration-sidebar: 0.01ms;
    --stagger-tight:    0ms;
    --stagger-snappy:   0ms;
    --stagger-normal:   0ms;
    --stagger-relaxed:  0ms;
    --stagger-delay-children: 0ms;
    --reveal-slide:     0px;
    --reveal-blur:      0px;
    --reveal-scale-up:  1;
  }
}

/* =============================================================================
   2. ДОПОЛНЕНИЯ К ТОКЕНАМ

   Всё, чего нет в замере, но без чего компоненты ниже не собираются. Список
   намеренно короткий: каждый добавленный токен — это ещё одно место, которое
   придётся сверять при следующем снятии панели.

   Тема одна, светлая, поэтому у каждого токена ровно одно значение и ни одно
   не спрятано внутрь media-запроса.
   ============================================================================= */

:root {
  /* --text-subtle на --surface-base даёт 4.73:1 — AA проходит. На --surface-tint
     (полосатая строка, наведённая строка) остаётся 4.34:1, на --surface-recessed
     4.28:1, на --surface-elevated 4.47:1 — все три чуть ниже порога. Это
     посчитано, а не предположено. Для второстепенного текста, лежащего прямо на
     подложке, берём ступень темнее: neutral-600 даёт 7.05:1. */
  --text-subtle-strong: oklch(43.9% 0 0);  /* neutral-600 #525252 */

  /* Знак поверх сплошной заливки --success: белая галочка на #009966 — 3.67:1.
     Для графического знака (не текста) порог 3:1, так что проходит. Токен
     оставлен отдельным именно потому, что запас маленький: если заливка когда-
     нибудь посветлеет, менять придётся здесь, а не в пятнадцати местах. */
  --on-success: #FFFFFF;

  /* Кольцо :focus-visible на ЗАЛИТЫХ кнопках — основной и опасной. Обычная
     кнопка берёт кольцо --brand, и на холсте его видно. У залитой кнопки кольцо
     фирменного цвета легло бы на фирменную же заливку и исчезло, поэтому оно
     рисуется соседней ступенью того же семейства — той, что на 10% темнее:
       --brand-emphasis-ring  #045EDE — 5.55:1 на холсте;
       --danger-emphasis-ring #DA252E — 4.73:1 на холсте.
     Ниже 3:1 фокус опускаться не имеет права: это единственное, чем клавиатура
     показывает, где она находится. */
  --focus-ring-brand:  var(--brand-emphasis-ring);
  --focus-ring-danger: var(--danger-emphasis-ring);

  /* Штриховка «данные устарели». Белые полосы под 135 градусов ложатся ПОВЕРХ
     цветной заливки, поэтому одно значение работает на всех вариантах шкалы —
     зелёной, фирменной, оранжевой, красной и серой, — и второго токена не
     требует. Шаг 6px при полосе 3px подобран под дорожку в 8px: мельче —
     рябит, крупнее — на короткой заливке видна одна полоса и читается как
     дефект отрисовки, а не как пометка. Потребители — в разделе 28. */
  --hatch-stale: repeating-linear-gradient(135deg,
                 transparent 0 3px, oklch(100% 0 0 / .55) 3px 6px);
}

/* =============================================================================
   3. СБРОС

   Не нормализация «как в браузере по умолчанию», а обнуление под систему:
   элемент без класса не должен выглядеть так, как решил агент пользователя.
   Отсюда сброс кегля и насыщенности у заголовков — размеры задают .prose,
   .doc и .type-*, и ни один h2 не приедет случайно полуторным жирным.
   ============================================================================= */

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

/* Внешние отступы задаются .stack и .section, а не браузером. :where() даёт
   нулевую специфичность — любой компонент перебивает без !important. */
:where(body, h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd, pre, fieldset, legend) {
  margin: 0;
}

/* ЗАМЕР В БРАУЗЕРЕ. Здесь стояло font-size: inherit / font-weight: inherit —
   заголовок без класса приезжал основным текстом 14px/400. У панели это не так:
   голый <h1> на ней считается 32px/400/40px, <h2> 24px/600, <h3> 20px/600/25px,
   <h4> 16px/600/24px — ряд несёт САМ ЭЛЕМЕНТ, а не класс. Сверено бок о бок:
   два http-сервера, наша страница и сохранённая панель, одинаковые пустые
   h1…h4, вставленные в обе. Раз ряд берётся один в один, его несёт элемент.

   Специфичность прежняя, нулевая: :where() не перебивает ничего. .prose h1
   (0,1,1), .type-h2 (0,1,0), .card__title (0,1,0), .step__title (0,1,0) — все
   выше и продолжают выигрывать. Проверено замером: ни один заголовок на
   витрине и в мастере после этой правки не изменил вычисленный кегль.

   Величины — те же токены --type-h*-*, что у .prose и .type-*: одно место
   правки, никакой второй лестницы. У h2 ведущая 32px, а не 30px как у голого
   h2 панели: 32px — это ЗАМЕР настоящего h2 со страницы панели, и его же
   называет ТЗ. Голый элемент там просто не встречается. */
:where(h1) {
  font-size: var(--type-h1-size);
  font-weight: var(--type-h1-weight);
  line-height: var(--type-h1-leading);
}

:where(h2) {
  font-size: var(--type-h2-size);
  font-weight: var(--type-h2-weight);
  line-height: var(--type-h2-leading);
}

:where(h3) {
  font-size: var(--type-h3-size);
  font-weight: var(--type-h3-weight);
  line-height: var(--type-h3-leading);
}

:where(h4) {
  font-size: var(--type-h4-size);
  font-weight: var(--type-h4-weight);
  line-height: var(--type-h4-leading);
}

:where(h5) {
  font-size: var(--type-h5-size);
  font-weight: var(--type-h5-weight);
  line-height: var(--type-h5-leading);
}

:where(h6) {
  font-size: var(--type-h6-size);
  font-weight: var(--type-h6-weight);
  line-height: var(--type-h6-leading);
}

:where(ul, ol) {
  margin: 0;
}

/* Список с классом — это разметка компонента, а не текст: маркеры ему не
   нужны. Голые ul и ol внутри .doc и .prose маркеры сохраняют. */
:where(ul[class], ol[class]) {
  padding: 0;
  list-style: none;
}

:where(img, picture, video, canvas, svg, iframe) {
  display: block;
  max-width: 100%;
}

:where(img, video) {
  height: auto;
}

:where(input, button, textarea, select) {
  font: inherit;
  color: inherit;
  /* Обязательная строка. Глобальный трекинг -0.01em в поля формы сам не
     наследуется, и без неё текст в поле уезжает с общей сетки — заметно,
     когда подпись и поле стоят рядом. */
  letter-spacing: inherit;
}

:where(button) {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
}

:where(button, [role="button"]) {
  touch-action: manipulation;  /* убирает 300ms ожидания двойного тапа */
}

:where(textarea) {
  resize: vertical;
}

:where(table) {
  border-collapse: collapse;
  border-spacing: 0;
}

:where(hr) {
  border: 0;
  border-top: var(--border-hair) solid var(--line);
  margin: 0;
}

:where(pre, code, kbd, samp) {
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-normal);  /* иначе моноширинный уезжает с сетки */
}

:where(pre) {
  overflow-x: auto;
}

:where(summary) {
  cursor: pointer;
  list-style: none;
}

:where(summary)::-webkit-details-marker {
  display: none;
}

:where(fieldset) {
  border: 0;
  padding: 0;
  min-inline-size: 0;
}

:where(abbr[title]) {
  text-decoration: underline dotted;
  cursor: help;
}

[hidden] {
  display: none !important;
}

/* =============================================================================
   4. БАЗА ДОКУМЕНТА И ГЛОБАЛЬНАЯ ТИПОГРАФИКА
   ============================================================================= */

html {
  /* Корень остаётся 16px, хотя продуктовая шкала ставит html в 14px.
     Причина конкретная: маркетинговая шкала (--type-*) и меры
     (--measure-prose, весь --space-*) заданы в rem и посчитаны от 16. При
     html:14px заголовок h1 превратился бы из 40px в 35px, колонка текста —
     из 696px в 609px, а сетка отступов перестала бы попадать в 4px.
     Поэтому продуктовые 14px вешаем на body: шкала --text-* пиксельная и от
     размера корня не зависит. Побочная выгода — уважение к настройке размера
     шрифта в браузере. */
  font-size: 100%;
  -webkit-text-size-adjust: 100%;
  text-size-adjust: 100%;
  tab-size: 2;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--header-height) + var(--space-4));
  font-family: var(--font-sans);
  font-feature-settings: var(--font-feature-ui);
  letter-spacing: var(--tracking-ui);  /* -0.01em, глобально */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  background: var(--surface-canvas);
  /* Серый прямоугольник по тапу выглядит как чужой элемент. Убираем не в
     transparent, а в фирменный след: обратная связь на касание остаётся. */
  -webkit-tap-highlight-color: color-mix(in oklab, var(--brand) 14%, transparent);
}

body {
  min-height: 100dvh;
  background: var(--surface-canvas);   /* холст, а не белый: карточки лежат ВЫШЕ него */
  /* ЗАМЕР: <body> панели покрашен в rgb(49,49,49), а не в ступень "default" из
     токенов. Оба значения настоящие и оба нужны — см. группу 5 токенов. */
  color: var(--text-body);
  font-family: var(--font-sans);
  /* ЗАМЕР даёт у <body> 16px/24px, но панель не вешает продуктовый кегль на
     body вовсе: 14px приезжает утилитой text-base на каждый компонент. У нас
     утилит нет и сборщика нет, поэтому продуктовая база ставится здесь.
     16px/1.5 при этом никуда не делся — им набран .prose, то есть ровно тот
     текст, который на панели и оставался бы 16px. */
  font-size: var(--text-base);         /* 14px */
  line-height: var(--leading-normal);  /* 1.5 -> 21px */
  font-weight: var(--font-weight-normal);
}

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

:where(p) {
  text-wrap: pretty;
}

/* Заголовок не должен заканчиваться одиноким словом на строке. В русском это
   заметнее, чем в английском: слова длиннее, висячее слово торчит сильнее. */
.type-h1, .type-h2, .type-h3, .type-h4, .type-h5, .type-h6,
.prose :where(h1, h2, h3, h4, h5, h6),
.doc :where(h1, h2, h3, h4),
.card__title,
.empty__title,
.modal__title {
  text-wrap: balance;
}

/* Оптическая ось Inter v4. Крупный кегль на opsz 32 получает более плотные
   пробелы и более контрастные штрихи, мелкий на 24 — наоборот. Этого нет
   больше нигде в системе; отделка бесплатная, потому что шрифт вариативный. */
.font-heading {
  font-variation-settings: "opsz" var(--opsz-heading);  /* 32 */
}

.font-title {
  font-variation-settings: "opsz" var(--opsz-title);    /* 24 */
}

/* =============================================================================
   5. ФОКУС И СЛУЖЕБНЫЕ КЛАССЫ

   Двухступенчатый фокус — самое полезное решение из всего разбора. Покоящийся
   фокус рисуется КОНТРАСТНЫМ кольцом (--focus-ring-color, oklch(15% 0 0) —
   почти чёрное), а фирменный цвет появляется только на :focus-visible.
   Клавиатурный фокус видно с одного взгляда, и спутать его с мышиным нельзя.

   Правило намеренно не применяется к `*`. Почти каждый компонент здесь несёт
   собственный box-shadow — кольцо карточки, ринг кнопки, обводка поля, — и
   глобальная тень его бы стёрла. Поэтому селектор перечисляет только
   интерактивные теги: специфичность «тег + псевдокласс» (0,1,1) ниже, чем у
   компонентного `.класс:focus-visible` (0,2,0), и компоненты ниже спокойно
   ставят своё кольцо поверх.
   ============================================================================= */

a:focus,
button:focus,
input:focus,
select:focus,
textarea:focus,
summary:focus,
[tabindex]:focus {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width) color-mix(in oklab, var(--focus-ring-color) 50%, transparent);
}

a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

/* Мышиный след убираем.

   Правило выше ставит контрастное кольцо на :focus, а браузер выставляет
   :focus и по щелчку мышью — не только с клавиатуры. Получалось, что после
   обычного клика по строке домена вокруг неё оставалась тёмная рамка и
   висела там, пока не щёлкнешь мимо. Измерено: в покое тени нет, после
   щелчка мышью 1px тёмное кольцо, после Tab 2px фирменное. Первое — шум.

   Гасим только тот случай, когда браузер САМ говорит, что показывать кольцо
   не надо. Написано именно как :focus:not(:focus-visible), а не удалением
   правила выше: браузер без поддержки :focus-visible не поймёт этот селектор,
   правило целиком не применит — и клавиатурный фокус там останется видимым.
   Тихо потерять кольцо у тех, кто ходит по клавишам, было бы хуже, чем
   лишняя рамка у тех, кто кликает. */
a:focus:not(:focus-visible),
button:focus:not(:focus-visible),
input:focus:not(:focus-visible),
select:focus:not(:focus-visible),
textarea:focus:not(:focus-visible),
summary:focus:not(:focus-visible),
[tabindex]:focus:not(:focus-visible) {
  box-shadow: none;
}

/* `tabindex="-1"` makes structural targets programmatically focusable for
   skip links and step changes. A ring around a full-page `main` or heading is
   not useful: it renders as a long bar across the viewport. These targets are
   outside the sequential Tab order, while interactive controls and scrollable
   regions (`tabindex="0"`) keep the visible focus treatment above. */
[tabindex="-1"]:focus,
[tabindex="-1"]:focus-visible {
  outline: none;
  box-shadow: none;
}

/* Экранным дикторам видно, глазам нет. clip-path вместо старого clip: rect —
   не ломает выделение текста и не создаёт горизонтальной прокрутки. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  white-space: nowrap;
  border: 0;
  clip-path: inset(50%);
}

/* Первая ссылка страницы: «Перейти к содержимому». Появляется только по Tab. */
.skip-link {
  position: fixed;
  top: var(--space-2);
  left: var(--space-2);
  z-index: var(--z-toast);
  translate: 0 -200%;
  display: inline-flex;
  align-items: center;
  height: var(--control-h-base);
  padding-inline: var(--control-px-base);
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  color: var(--text-default);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand), var(--shadow-lg);
  transition: translate var(--duration-base) var(--ease-out);
}

.skip-link:focus,
.skip-link:focus-visible {
  translate: 0 0;
}

/* =============================================================================
   6. КОМПОНОВКА

   Пять примитивов закрывают всю посадочную страницу и весь мастер:
   .container (ширина), .section (вертикальный ритм), .grid (колонки),
   .stack (поток по вертикали), .cluster (поток по горизонтали).
   Шестой, .split, — двухколоночный разворот «текст + картинка».

   Все ширины взяты из токенов контейнеров, все отступы — с сетки 4px.
   Точка перелома одна и та же везде: 48rem / 768px. Это та же точка, на
   которой переключается вся маркетинговая шкала кеглей, и тот же порог, на
   котором боковая панель уходит за экран.
   ============================================================================= */

.container {
  width: 100%;
  max-width: var(--container-marketing);   /* 1200px */
  margin-inline: auto;
  padding-inline: var(--page-padding-x);   /* 16px */
}

@media (min-width: 64rem) {
  .container {
    padding-inline: var(--page-padding-x-lg);  /* 32px */
  }
}

.container--wide      { max-width: var(--container-marketing-wide); }  /* 1480px */
.container--section   { max-width: var(--container-section); }         /* 1080px */
.container--page      { max-width: var(--container-page); }            /* 1000px */
.container--page-wide { max-width: var(--container-page-wide); }       /* 1600px */
.container--prose     { max-width: var(--container-content); }         /* 43.5rem */
.container--flush     { padding-inline: 0; }

/* Вертикальный ритм. 64px на узком экране, 96px на широком — это извлечённые
   значения сетки, а не подобранные на глаз. */
.section {
  padding-block: var(--space-16);  /* 64px */
}

@media (min-width: 48rem) {
  .section {
    padding-block: var(--space-24);  /* 96px */
  }
}

.section--tight { padding-block: var(--space-10); }  /* 40px */

@media (min-width: 48rem) {
  .section--tight { padding-block: var(--space-12); }  /* 48px */
}

.section--loose { padding-block: var(--space-24); }

@media (min-width: 48rem) {
  .section--loose { padding-block: var(--space-32); }  /* 128px */
}

.section--flush-top    { padding-block-start: 0; }
.section--flush-bottom { padding-block-end: 0; }
.section--recessed     { background: var(--surface-recessed); }
.section--elevated     { background: var(--surface-elevated); }
.section--base         { background: var(--surface-base); }
.section--ruled        { border-block-start: var(--border-hair) solid var(--line); }

/* --text-subtle посчитан под --surface-base: там 4.74:1, AA проходит. На
   утопленной секции остаётся 4.28:1, на приподнятой 4.47:1 — оба чуть ниже
   4.5:1, и это посчитано, а не предположено. Поэтому приглушённый текст,
   лежащий ПРЯМО на такой секции, берёт ступень темнее (--text-subtle-strong,
   7.05:1). Внутри карточки, баннера, тоста или модалки фон снова свой, поэтому
   :not(...) их исключает — иначе весь второстепенный текст на странице уехал
   бы в neutral-600 без всякой причины. */
:is(.section--recessed, .section--elevated)
  :is(.lead, .text-subtle, .step__desc, .field-hint):not(
    :is(.card, .banner, .toast, .modal, .table, .field-group) *) {
  color: var(--text-subtle-strong);
}

.section-head {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
  max-width: 44rem;
  margin-block-end: var(--space-12);
}

.section-head--center {
  align-items: center;
  text-align: center;
  margin-inline: auto;
}

.grid {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 40rem) {
  .grid--2,
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 48rem) {
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 64rem) {
  .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

/* Без точек перелома: колонки набираются сами, пока помещаются. --grid-min
   задаётся на месте, если 17rem не подходит. */
.grid--auto {
  /* min() вокруг минимума обязателен, и вот почему.
     repeat(auto-fit, minmax(17rem, 1fr)) на контейнере УЖЕ 17rem не сжимается:
     дорожка остаётся 272px и вылезает за край. Измерено на панели при ширине
     окна 360 — контейнер 328, колонка 352, страница ездит вбок на 8 пикселей.
     min(…, 100%) разрешает дорожке стать шириной контейнера, когда места
     меньше минимума; на широких экранах поведение не меняется вовсе. */
  grid-template-columns: repeat(auto-fit, minmax(min(var(--grid-min, 17rem), 100%), 1fr));
}

.grid--gap-sm { gap: var(--space-4); }
.grid--gap-lg { gap: var(--space-8); }
.grid__full   { grid-column: 1 / -1; }

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--stack-gap, var(--space-4));
}

.stack--xs { --stack-gap: var(--space-2); }
.stack--sm { --stack-gap: var(--space-3); }
.stack--lg { --stack-gap: var(--space-6); }
.stack--xl { --stack-gap: var(--space-10); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--cluster-gap, var(--space-3));
}

.cluster--sm      { --cluster-gap: var(--space-2); }
.cluster--lg      { --cluster-gap: var(--space-6); }
.cluster--center  { justify-content: center; }
.cluster--between { justify-content: space-between; width: 100%; }
.cluster--end     { justify-content: flex-end; }
.cluster--top     { align-items: flex-start; }

/* Разворот. До 1024px колонки складываются в одну — на планшете двухколоночный
   герой уже не читается, а не на 768px, потому что картинке нужна ширина. */
.split {
  display: grid;
  gap: var(--space-10);
  align-items: center;
}

@media (min-width: 64rem) {
  .split {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-16);
  }

  .split--text-first  { grid-template-columns: 1.15fr 1fr; }
  .split--media-first { grid-template-columns: 1fr 1.15fr; }
  .split--reverse > :first-child { order: 2; }
}

.measure       { max-width: var(--measure-prose); }       /* 43.5rem, ~78 знаков */
.measure--wide { max-width: var(--measure-prose-wide); }  /* 52rem */
.measure--ch   { max-width: var(--measure-max); }         /* 65ch */

/* =============================================================================
   7. КАРКАС ПРИЛОЖЕНИЯ

   Для мастера подключения и панели. Слева рельс 260px, справа колонка с
   верхней шапкой 58px и содержимым. Ниже 768px рельс уходит за экран и
   выезжает по [data-open="true"] — тот же порог, что и у всего остального.
   ============================================================================= */

.app {
  display: grid;
  grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
  min-height: 100dvh;
  background: var(--surface-canvas);
}

.app__main {
  display: flex;
  flex-direction: column;
  min-width: 0;  /* иначе широкая таблица распирает грид-колонку */
}

.app__topbar {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  display: flex;
  align-items: center;
  gap: var(--space-3);
  height: var(--header-height);       /* 58px */
  padding-inline: var(--space-4);
  background: var(--surface-canvas);
  border-block-end: var(--border-hair) solid var(--line);
}

.app__title {
  font-size: var(--text-lg);          /* 16px — заголовок верхней панели, не h1 страницы */
  font-weight: var(--font-weight-semibold);
  line-height: var(--text-lg-lh);     /* явно: класс ставится и на h1, и на div */
  color: var(--text-default);
}

.app__content {
  flex: 1 1 auto;
  padding: var(--space-6) var(--page-padding-x);
  min-width: 0;
}

@media (min-width: 64rem) {
  .app__content { padding-inline: var(--page-padding-x-lg); }
}

.app__bottombar {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 10px var(--space-4);
  background: var(--surface-canvas);
  border-block-start: var(--border-hair) solid var(--line);
}

.app__aside {
  width: 200px;
  flex: none;
  border-inline-start: var(--border-hair) solid var(--line);
}

@media (max-width: 47.999rem) {
  .app { grid-template-columns: minmax(0, 1fr); }

  .app__sidebar {
    position: fixed;
    inset-block: 0;
    inset-inline-start: 0;
    z-index: var(--z-drawer);
    translate: -100% 0;
    transition: translate var(--duration-sidebar) var(--ease-sidebar);
  }

  .app__sidebar[data-open="true"] { translate: 0 0; }
}

/* Подложка мобильного рельса. Живёт в разметке всегда, показывается скриптом. */
.nav-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: var(--surface-recessed);
  opacity: 0.8;
  border: 0;
  cursor: pointer;
}

/* Страница входа: холст, центр, 32px сверху и снизу, 24px по бокам. */
.auth {
  display: grid;
  place-items: center;
  min-height: 100dvh;
  padding: var(--space-8) var(--space-6);
  background: var(--surface-canvas);
}

.auth__card {
  width: 100%;
  max-width: 25rem;  /* 400px */
}

/* =============================================================================
   8. СТРАНИЧНАЯ ТИПОГРАФИКА — .prose

   Ряд заголовков панели, дословно: 32/400, 24/600, 20/600, дальше 16/14/13 на
   весе 600 (правило и его обоснование — в группе 9c токенов).

   ТРИ ВЕЩИ, КОТОРЫЕ РАНЬШЕ БЫЛИ ИНАЧЕ И ИЗМЕНИЛИСЬ ОСОЗНАННО.

   1. Перелома на 48rem больше нет. Прежняя шкала меняла все кегли разом на
      768px (и h5 на переломе даже уменьшался). У панели переломов в типографике
      нет вовсе: 32 остаётся 32. Единственное исключение — первый экран, ему
      перелом оставлен, там он несёт смысл.

   2. Заголовки не задают letter-spacing. Панель объявляет трекинг один раз, на
      html; дальше он наследуется вычисленной длиной -0.16px и одинаков у
      заголовка в 32px и у поля в 13px. Любое переопределение здесь ломало бы
      это совпадение — см. комментарий к --tracking-ui.

   3. Вес. Раньше все заголовки весили 500 и strong был опущен до 500 — это
      правило приехало с маркетингового сайта, где отгружены всего два
      начертания. У панели h1 весит 400, h2 и h3 — 600, а полужирный в тексте
      нормальный, 600. Inter у нас вариативный, ограничения нет.

   Кегли ставятся через промежуточные --_h1…--_p, поэтому вариант шкалы
   (.prose--hero, .prose--compact) переопределяет несколько строк, а не восемь
   блоков. Значения при этом остаются токенами.
   ============================================================================= */

.prose,
.type-h1, .type-h2, .type-h3, .type-h4, .type-h5, .type-h6,
.type-p, .type-small {
  --_h1: var(--type-h1-size);
  --_h2: var(--type-h2-size);
  --_h3: var(--type-h3-size);
  --_h4: var(--type-h4-size);
  --_h5: var(--type-h5-size);
  --_h6: var(--type-h6-size);
  --_h1-leading: var(--type-h1-leading);
  --_p:  var(--type-p-size);
  --_small: var(--type-small-size);
}

/* Первый экран. Ровно одна ступень выше страничного заголовка, и обе взяты с
   верха лестницы панели: 36px (--text-4xl) до перелома, 48px (--text-5xl)
   после. Выше 48px в панели нет ничего. */
.prose--hero,
.prose[data-type="hero"] {
  --_h1: var(--type-hero-h1-size);
  --_h2: var(--type-hero-h2-size);
  --_h3: var(--type-hero-h3-size);
  --_h1-leading: var(--type-hero-h1-leading);
  --_p:  var(--type-hero-p-size);
}

@media (min-width: 48rem) {
  .prose--hero,
  .prose[data-type="hero"] {
    --_h1: var(--type-hero-h1-size-md);
    --_h2: var(--type-hero-h2-size-md);
    --_h3: var(--type-hero-h3-size-md);
    --_p:  var(--type-hero-p-size-md);
  }
}

/* Мельче базовой — для плотных блоков: подписи под карточками, сноски,
   врезки внутри секции. */
.prose--compact,
.prose[data-type="compact"] {
  --_h1: var(--type-compact-h1-size);
  --_h2: var(--type-compact-h2-size);
  --_h3: var(--type-compact-h3-size);
  --_p:  var(--type-compact-p-size);
}

.prose {
  color: var(--text-body);
  font-size: var(--_p);
  line-height: var(--type-p-leading);
  font-weight: var(--type-p-weight);
  font-feature-settings: var(--font-feature-prose);
}

.prose > * + *          { margin-block-start: var(--space-5); }
.prose :where(h1, h2, h3, h4) + *,
.prose :where(h5, h6) + * { margin-block-start: var(--space-3); }
.prose > :where(h1, h2, h3, h4) { margin-block-start: var(--space-10); }
.prose > :first-child   { margin-block-start: 0; }

.prose h1, .type-h1 {
  font-size: var(--_h1);
  line-height: var(--_h1-leading, var(--type-h1-leading));
  font-weight: var(--type-h1-weight);   /* 400 — ЗАМЕР. Крупный лёгкий заголовок и есть осанка панели */
  font-variation-settings: "opsz" var(--opsz-heading);
  color: var(--text-strong);
}

.prose h2, .type-h2 {
  font-size: var(--_h2);
  line-height: var(--type-h2-leading);
  font-weight: var(--type-h2-weight);
  font-variation-settings: "opsz" var(--opsz-heading);
  color: var(--text-strong);
}

.prose h3, .type-h3 {
  font-size: var(--_h3);
  line-height: var(--type-h3-leading);
  font-weight: var(--type-h3-weight);
  font-variation-settings: "opsz" var(--opsz-title);
  color: var(--text-strong);
}

.prose h4, .type-h4 {
  font-size: var(--_h4);
  line-height: var(--type-h4-leading);
  font-weight: var(--type-h4-weight);
  font-variation-settings: "opsz" var(--opsz-title);
  color: var(--text-strong);
}

/* h5 и h6 отличаются от текста ВЕСОМ, а не кеглем: между 20px и 16px у панели
   размеров нет. h6 в 13px мельче основного текста и работает как подпись
   раздела — так же, как метки в панели. */
.prose h5, .type-h5 {
  font-size: var(--_h5);
  line-height: var(--type-h5-leading);
  font-weight: var(--type-h5-weight);
  color: var(--text-default);
}

.prose h6, .type-h6 {
  font-size: var(--_h6);
  line-height: var(--type-h6-leading);
  font-weight: var(--type-h6-weight);
  color: var(--text-default);
}

.prose p, .type-p {
  font-size: var(--_p);
  line-height: var(--type-p-leading);
  font-weight: var(--type-p-weight);
}

.prose small, .type-small {
  font-size: var(--_small);
  line-height: var(--type-small-leading);
  font-weight: var(--type-small-weight);
  color: var(--text-subtle);
}

.prose strong,
.prose b {
  font-weight: var(--font-weight-semibold);  /* 600 — см. пункт 3 в шапке раздела */
}

.prose a {
  color: var(--text-link);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hair);
  text-underline-offset: 0.2em;
  transition: color var(--duration-base) var(--ease-out);
}

.prose a:hover { color: var(--brand-hover); }

.prose :where(ul, ol) {
  padding-inline-start: var(--space-6);
}

.prose li + li { margin-block-start: var(--space-2); }

.prose :where(figure) > figcaption {
  margin-block-start: var(--space-3);
  font-size: var(--type-small-size);
  color: var(--text-subtle);
}

/* Подзаголовок первого экрана. 16px на узком экране, 20px (--text-xl) на
   широком — обе ступени с лестницы панели. Интерлиньяж snug: 1.4 от --text-xl
   рассчитан на строку-другую, а здесь бывает и три. */
.lead {
  font-size: var(--type-hero-p-size);
  line-height: var(--leading-snug);
  color: var(--text-subtle);
}

@media (min-width: 48rem) {
  .lead { font-size: var(--type-hero-p-size-md); }
}

/* Надзаголовок: капслок, широкий трекинг, мелкий кегль. Кириллица в капслоке
   без разрядки читается плохо — отсюда --tracking-eyebrow. */
.eyebrow {
  display: inline-block;
  font-size: var(--text-xs);
  font-weight: var(--font-weight-semibold);
  letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase;
  color: var(--text-subtle);
}

/* Надзаголовок фирменным цветом — СИНИМ, а не оранжевым, и это единственное
   место, где 1:1 пришлось развести с замером.
   --text-brand (#f6821f) на холсте даёт 2.49:1. Надзаголовок — это 12px в
   капслоке, то есть самый мелкий живой текст на странице; 2.49:1 там не читается
   ни при каком освещении. Оранжевый остаётся в наборе как ТОКЕН и применяется
   там, где он безопасен (знак логотипа, утилита .text-brand для крупного
   декоративного акцента). Текстовую же роль «фирменный цвет в строке» у панели
   несёт синяя ветка ramp'а — --text-color-kumo-link / -info, — и её мы и берём:
   8.53:1 на холсте. */
.eyebrow--brand { color: var(--text-link); }

/* =============================================================================
   9. ДОКУМЕНТНАЯ ТИПОГРАФИКА — .doc

   Этим набирается длинный текст: инструкции мастера («добавьте запись MX»,
   «проверьте SPF») и ответы в FAQ.

   Заголовки опущены на ступень относительно страничных — 24 / 20 / 16 / 14,
   все весом 600, — потому что .doc всегда лежит ВНУТРИ страницы, у которой
   уже есть свой h1 в 32px. Прежние 35px были из системы документации
   Cloudflare, а не из панели, и торчали выше страничного потолка.

   Текст 16px (--text-lg). Интерлиньяж 1.625 (--leading-relaxed) — самая
   просторная ступень, которая у панели есть; прежние 1.75 приезжали из той же
   системы документации и в наборе панели отсутствуют. Мера 43.5rem, ~78 знаков:
   без неё строка на широком экране уезжает за предел, на котором глаз находит
   начало следующей.
   ============================================================================= */

.doc {
  max-width: var(--measure-prose);
  font-size: var(--text-lg);            /* 16px */
  line-height: var(--leading-relaxed);  /* 1.625 -> 26px */
  color: var(--text-body);
  font-feature-settings: var(--font-feature-prose);
}

.doc > * + *                    { margin-block-start: var(--space-4); }
.doc > :where(h1, h2, h3, h4)   { margin-block-start: var(--space-8); }
.doc > :first-child             { margin-block-start: 0; }

/* letter-spacing нигде не переопределяется: он наследуется вычисленной
   длиной -0.16px и одинаков у всей страницы — см. --tracking-ui. */
.doc h1 {
  font-size: var(--type-doc-h1-size);       /* 24px */
  font-weight: var(--type-doc-h1-weight);   /* 600 */
  line-height: var(--type-h2-leading);      /* 1.3333, как у h2 панели */
  color: var(--text-strong);
}

.doc h2 {
  font-size: var(--type-doc-h2-size);       /* 20px */
  font-weight: var(--type-doc-h2-weight);
  line-height: var(--type-h3-leading);      /* 1.25, как у h3 панели */
  color: var(--text-strong);
}

.doc h3 {
  font-size: var(--type-doc-h3-size);       /* 16px */
  font-weight: var(--type-doc-h3-weight);
  line-height: var(--leading-normal);
  color: var(--text-strong);
}

.doc h4 {
  font-size: var(--type-doc-h4-size);       /* 14px */
  font-weight: var(--type-doc-h4-weight);
  line-height: var(--leading-normal);
  color: var(--text-strong);
}

.doc strong { font-weight: var(--font-weight-semibold); }

.doc a {
  color: var(--text-link);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hair);
  text-underline-offset: 0.2em;
  transition: color var(--duration-base) var(--ease-out);
}

.doc a:hover { color: var(--brand-hover); }

.doc :where(ul, ol) { padding-inline-start: var(--space-6); }
.doc li + li        { margin-block-start: var(--space-2); }

.doc blockquote {
  padding-inline-start: var(--space-4);
  border-inline-start: var(--border-thick) solid var(--line);
  color: var(--text-subtle);
}

.doc hr { margin-block: var(--space-8); }

.doc table {
  width: 100%;
  font-size: var(--text-base);
  line-height: var(--leading-normal);
}

.doc :where(th, td) {
  padding: var(--space-2) var(--cell-pad-x);
  text-align: start;
  border-block-end: var(--border-hair) solid var(--surface-fill);
  vertical-align: top;
}

.doc th {
  font-weight: var(--font-weight-semibold);
  color: var(--text-strong);
}

/* Строчный код. 0.85em — доля от кегля окружения, поэтому одинаково хорошо
   сидит и в тексте .doc, и внутри заголовка. */
:where(.doc, .prose) :not(pre) > code {
  padding: 0.15em 0.35em;
  border-radius: var(--radius-sm);
  background: var(--surface-recessed);
  font-family: var(--font-mono);
  font-size: var(--type-code-size);
  font-weight: var(--font-weight-code);   /* 450 — сдвиг оси, а не скачок в 500 */
  letter-spacing: var(--tracking-normal);
  line-height: var(--type-code-leading);
  color: var(--text-default);
  word-break: break-word;
}

/* =============================================================================
   10. ССЫЛКИ, ТЕКСТОВЫЕ РОЛИ, УТИЛИТЫ
   ============================================================================= */

a {
  color: var(--text-link);
  text-decoration: none;
  transition: color var(--duration-base) var(--ease-out);
}

/* Наведение красит только ГОЛУЮ ссылку. Ссылка с классом — это компонент:
   кнопка, пункт навигации, карточка, — и цвет наведения у неё свой. Без
   :not([class]) правило `a:hover` (0,1,1) перебивало бы `.btn` (0,1,0), и
   ссылка-кнопка на наведении внезапно перекрашивалась бы в синий. Проверьте
   это первым делом, если однажды кнопка «поедет» по цвету. */
a:not([class]):hover { color: var(--brand-hover); }

.link:hover { color: var(--brand-hover); }

.link {
  color: var(--text-link);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hair);
  text-underline-offset: 0.2em;
}

/* Ссылка, которая не должна кричать: в подвале, в хлебных крошках, в строке
   таблицы. Подчёркивание появляется по наведению — цвет не единственный
   признак интерактивности. */
.link--quiet {
  color: var(--text-default);
  text-decoration: none;
}

.link--quiet:hover {
  color: var(--text-link);
  text-decoration: underline;
  text-decoration-thickness: var(--border-hair);
  text-underline-offset: 0.2em;
}

.text-strong  { color: var(--text-strong); }
.text-subtle  { color: var(--text-subtle); }
.text-link    { color: var(--text-link); }
.text-info    { color: var(--text-info); }
.text-success { color: var(--text-success); }
.text-danger  { color: var(--text-danger); }

/* 4.18:1 на белом — ниже AA для обычного текста. Поэтому предупреждающий
   текст всегда идёт весом 500 и не мельче 14px. */
.text-warning {
  color: var(--text-warning);
  font-weight: var(--font-weight-medium);
}

/* Фирменный оранжевый в тексте. ТОЛЬКО крупный декоративный акцент: 2.49:1 на
   холсте — это ниже даже порога 3:1 для крупного текста, поэтому класс не
   ставится ни абзацу, ни подписи, ни ссылке, и смысл им не передаётся. Область
   применения одна: одно-два слова кеглем от 24px рядом со знаком, где цвет
   работает как часть знака, а не как текст.
   Токен --text-brand при этом взят у панели один в один (#f6821f) — сужена
   область применения, а не значение. */
.text-brand {
  color: var(--text-brand);
  font-size: var(--text-2xl);
  font-weight: var(--font-weight-semibold);
  line-height: var(--text-2xl-lh);
}

.text-2xs  { font-size: var(--text-2xs);  line-height: var(--text-2xs-lh); }
.text-xs   { font-size: var(--text-xs);   line-height: var(--text-xs-lh); }
.text-sm   { font-size: var(--text-sm);   line-height: var(--text-sm-lh); }
.text-base { font-size: var(--text-base); line-height: var(--text-base-lh); }
.text-lg   { font-size: var(--text-lg);   line-height: var(--text-lg-lh); }
.text-xl   { font-size: var(--text-xl);   line-height: var(--text-xl-lh); }

.font-medium   { font-weight: var(--font-weight-medium); }
.font-semibold { font-weight: var(--font-weight-semibold); }

.mono {
  font-family: var(--font-mono);
  letter-spacing: var(--tracking-normal);
}

/* Единственное осознанное отступление от источника по цифрам: там табличные
   цифры включены ровно в одном месте, в календаре. У нас цифры стоят
   столбиками везде — квоты, размеры, счётчики, даты, — и без tabular-nums
   они пляшут при обновлении. */
.tnum { font-variant-numeric: tabular-nums; }

.truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.nowrap      { white-space: nowrap; }
.text-center { text-align: center; }
.text-end    { text-align: end; }
.text-pretty { text-wrap: pretty; }
.text-balance{ text-wrap: balance; }

/* Имена по тому, ГДЕ элемент скрыт, а не где показан: «hide-sm» каждый раз
   читается наоборот. Порог тот же самый, 48rem. */
@media (max-width: 47.999rem) {
  .hide-mobile { display: none !important; }
}

@media (min-width: 48rem) {
  .hide-desktop { display: none !important; }
}

/* =============================================================================
   11. КНОПКИ

   Кнопки и поля ввода делят ОДНУ лестницу высоты, внутреннего отступа и
   радиуса. Именно из-за этого панель инструментов собирается ровно, без
   подгонки: 36px / 12px / 8px — единица управления.

     размер   высота   отступ   зазор   радиус   кегль
     xs       20px     6px      4px     4px      12px
     sm       26px     8px      4px     6px      12px
     base     36px     12px     6px     8px      14px
     lg       40px     16px     8px     8px      14px

   Вариант по умолчанию — ВТОРИЧНЫЙ, а не главный. Голый .btn это уже готовая
   вторичная кнопка; .btn--secondary существует как явный синоним для
   разметки, где важно назвать вариант вслух.

   Про контраст: белая подпись на фирменной заливке ниже AA для обычного
   текста. Замер по пикселям: 3.52:1 у верхнего края кнопки, 3.95:1 под самой
   подписью, 4.48:1 у нижнего. Это унаследованная от источника проблема — у
   панели та же кнопка плоская и даёт 2.68:1, — и мы её не замалчиваем: вес
   подписи у .btn--primary и .btn--danger поднят до 600, а для крупных
   призывов на посадочной есть .btn--cta с кеглем 16px. Полностью проблему
   решает только затемнение заливки, и это осознанно не сделано — иначе
   кнопка перестанет быть тем же слотом. Подробный разбор — у .btn--primary.
   ============================================================================= */

.btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: max-content;
  height: var(--control-h-base);
  padding-inline: var(--control-px-base);
  gap: var(--control-gap-base);
  border: 0;
  border-radius: var(--radius-lg);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  letter-spacing: var(--tracking-ui);
  white-space: nowrap;
  text-decoration: none;
  color: var(--text-default);
  background: var(--surface-base);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-xs);
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  /* Только цвет и тень. Правило системы: наведение ничего не двигает. */
  transition: background-color var(--duration-control) var(--ease-control),
              box-shadow      var(--duration-control) var(--ease-control),
              color           var(--duration-control) var(--ease-control);
}

/* Явный синоним состояния по умолчанию: нужен там, где кнопка сначала
   получает другой вариант из шаблона, а потом возвращается к обычной. */
.btn--secondary {
  background: var(--surface-base);
  color: var(--text-default);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-xs);
}

.btn:hover { background: var(--surface-tint); }

.btn:focus {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width) color-mix(in oklab, var(--focus-ring-color) 50%, transparent);
}

.btn:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.btn:disabled,
.btn[aria-disabled="true"] {
  cursor: not-allowed;
  background: color-mix(in oklab, var(--surface-base) 50%, transparent);
  color: color-mix(in oklab, var(--text-default) 70%, transparent);
}

/* Триггер меню в открытом состоянии. У источника здесь --surface-base; на
   холсте это видно, а на карточке, которая сама лежит на --surface-base, — нет.
   Берём --surface-tint по той же причине, по которой ниже отказываемся от
   --surface-overlay в строках таблицы: невидимое состояние бесполезно. */
.btn[data-state="open"] { background: var(--surface-tint); }

.btn--xs {
  height: var(--control-h-xs);
  padding-inline: var(--control-px-xs);
  gap: var(--control-gap-xs);
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
}

.btn--sm {
  height: var(--control-h-sm);
  padding-inline: var(--control-px-sm);
  gap: var(--control-gap-sm);
  border-radius: var(--radius-md);
  font-size: var(--text-xs);
}

.btn--lg {
  height: var(--control-h-lg);
  padding-inline: var(--control-px-lg);
  gap: var(--control-gap-lg);
}

.btn--icon  { width: var(--control-h-base); padding-inline: 0; }
.btn--icon.btn--xs { width: var(--control-h-xs); }
.btn--icon.btn--sm { width: var(--control-h-sm); }
.btn--icon.btn--lg { width: var(--control-h-lg); }

.btn--block { width: 100%; }
.btn--pill  { border-radius: var(--radius-pill); }

/* Требование доступности: цель нажатия не меньше 36px (--control-h-base).
   Кегли xs и sm извлечены как есть и годятся для плотных панелей под мышью.
   На пальцевом вводе поднимаем их до базовой высоты — рисунок на экранах с
   курсором при этом не меняется вообще. */
@media (pointer: coarse) {
  .btn--xs,
  .btn--sm { height: var(--control-h-base); }

  .btn--icon.btn--xs,
  .btn--icon.btn--sm { width: var(--control-h-base); }
}

/* --- Главная и разрушающая: один рецепт, разный посев ---------------------

   ЧЕСТНО О ПРОИСХОЖДЕНИИ ГЛЯНЦА. Здесь стояло «у источника он лежит под
   подписью» — это неверно, и проверено прямым замером. Главная кнопка панели
   (Create Worker, Change) считается так:
       background-color  oklch(0.704038 0.162695 260)
       background-image  none            <- градиента НЕТ
       ::before          content: none   <- слоя НЕТ
       color #fff, font-weight 500, 14px, radius 8px, высота 36px,
       box-shadow  oklch(0.51948 0.20916 260) 0 0 0 1px, rgba(0,0,0,.05) 0 1px 2px
   Совпадает всё, кроме двух вещей, и обе НАШИ:
     1) глянцевый слой ::before,
     2) вес подписи 600 вместо 500.
   Оба отступления сделаны ради одного — контраста белой подписи. Числа сняты
   с настоящих пикселей отрендеренной кнопки, а не посчитаны по токенам:
       плоская заливка панели  #619eff  2.68:1
       наш градиент, верх      #3985ff  3.52:1
       наш градиент, середина  #267aff  3.95:1   <- здесь и стоит подпись
       наш градиент, низ       #096eff  4.48:1
   Ниже AA всё равно, но 3.95 против 2.68 — разница видимая, а рисунок кнопки
   при этом тот же.
   Цвета в градиенте — ТОЖЕ фирменные: середина между --brand и белым сверху,
   чистый --brand снизу. Ни одного постороннего оттенка здесь нет.
   Решение «взять панель один в один» это отступление формально нарушает,
   поэтому оно названо вслух: снять ::before и вернуть вес 500 — две строки,
   и кнопка станет точной копией панели ценой 2.86:1.

   Механика слоя. Глянец — отдельный слой, а не background-image кнопки,
   потому что разметка ручная: <button class="btn btn--primary">Начать</button>
   — самая вероятная запись, а голый текстовый узел никакой z-index не
   поднимет. Кнопка объявляет собственный контекст наложения (isolation), а
   глянец уходит на z-index:-1: он оказывается между фоном кнопки и текстом,
   и оборачивать подпись в span не нужно. */

.btn--primary,
.btn--danger,
.btn--destructive {
  isolation: isolate;
  overflow: hidden;
  color: var(--on-brand);
  font-weight: var(--font-weight-semibold);
}

.btn--primary::before,
.btn--danger::before,
.btn--destructive::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  transition: background var(--duration-control) var(--ease-control-in);
}

.btn--primary {
  background: var(--brand-emphasis-bg);       /* mix(brand, white 30%) */
  box-shadow: 0 0 0 var(--ring-width) var(--brand-emphasis-ring), var(--shadow-xs);
}

.btn--primary::before {
  background: linear-gradient(to bottom,
              var(--brand-emphasis-grad-start),  /* mix(brand, white 15%) */
              var(--brand-emphasis-grad-end));   /* brand */
  box-shadow: inset 0 1px 0 0 var(--brand-emphasis-bg);
}

/* Светлеет только ВЕРХ градиента. Низ остаётся фирменным. */
.btn--primary:hover::before {
  background: linear-gradient(to bottom,
              var(--brand-emphasis-bg),
              var(--brand-emphasis-grad-end));
}

.btn--primary:hover { background: var(--brand-emphasis-bg); }

/* Покоящееся состояние оставляем как извлечено. У :focus-visible цвет кольца
   идёт из --focus-ring-brand — это та же ступень, на 10% темнее заливки:
   на холсте 5.55:1, и на самой кнопке кольцо видно, потому что оно темнее её. */
.btn--primary:focus {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand-emphasis-ring), var(--shadow-xs);
}

.btn--primary:focus-visible {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--focus-ring-brand), var(--shadow-xs);
}

.btn--danger,
.btn--destructive {
  background: var(--danger-emphasis-bg);
  box-shadow: 0 0 0 var(--ring-width) var(--danger-emphasis-ring), var(--shadow-xs);
}

.btn--danger::before,
.btn--destructive::before {
  background: linear-gradient(to bottom,
              var(--danger-emphasis-grad-start),
              var(--danger));
  box-shadow: inset 0 1px 0 0 var(--danger-emphasis-bg);
}

.btn--danger:hover::before,
.btn--destructive:hover::before {
  background: linear-gradient(to bottom,
              var(--danger-emphasis-bg),
              var(--danger));
}

.btn--danger:hover,
.btn--destructive:hover { background: var(--danger-emphasis-bg); }

.btn--danger:focus,
.btn--destructive:focus {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--danger-emphasis-ring), var(--shadow-xs);
}

/* См. --focus-ring-brand выше: тот же приём, тот же повод. */
.btn--danger:focus-visible,
.btn--destructive:focus-visible {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--focus-ring-danger), var(--shadow-xs);
}

.btn--primary:disabled,
.btn--primary[aria-disabled="true"],
.btn--danger:disabled,
.btn--danger[aria-disabled="true"],
.btn--destructive:disabled,
.btn--destructive[aria-disabled="true"] {
  opacity: 0.5;
  color: var(--on-brand);
}

/* --- Прозрачная ---------------------------------------------------------- */

.btn--ghost {
  background: transparent;
  color: var(--text-default);
  box-shadow: none;
}

.btn--ghost:hover { background: var(--surface-tint); }

.btn--ghost:disabled,
.btn--ghost[aria-disabled="true"] {
  background: transparent;
  color: var(--text-subtle);
}

/* --- Контурная ----------------------------------------------------------- */

.btn--outline {
  background: transparent;
  box-shadow: 0 0 0 var(--ring-width) var(--line);
}

.btn--outline:hover {
  background: transparent;
  color: var(--text-strong);
  box-shadow: 0 0 0 var(--ring-width) color-mix(in oklab, var(--focus-ring-color) 25%, transparent);
}

.btn--outline:disabled,
.btn--outline[aria-disabled="true"] {
  background: transparent;
  color: var(--text-subtle);
}

/* --- Вторичная разрушающая ------------------------------------------------
   Для «Удалить домен» рядом с «Отмена»: опасность несёт цвет подписи, а не
   заливка, поэтому кнопка не перетягивает внимание с главного действия. */

.btn--danger-secondary {
  background: var(--surface-base);
  color: var(--text-danger);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-xs);
}

.btn--danger-secondary:hover {
  background: var(--surface-base);
  box-shadow: 0 0 0 var(--ring-width) color-mix(in oklab, var(--danger) 30%, transparent),
              var(--shadow-xs);
}

.btn--danger-secondary:disabled,
.btn--danger-secondary[aria-disabled="true"] {
  background: color-mix(in oklab, var(--surface-base) 50%, transparent);
  color: color-mix(in oklab, var(--text-danger) 70%, transparent);
}

/* --- Маркетинговая хореография нажатия ------------------------------------
   Это кнопка посадочной страницы, а не панели: в извлечённой карте вариантов
   приложения никакого преобразования при нажатии нет вообще.

   Кольцо-послесвечение — самая узнаваемая микровзаимодействие первоисточника:
   входит мгновенно, гаснет 600ms с задержкой 100ms. Мгновенно внутрь, медленно
   наружу. В почтовом клиенте этого быть не должно. */

.btn--marketing {
  transition: background-color var(--duration-control) var(--ease-control),
              box-shadow      var(--duration-control) var(--ease-control),
              color           var(--duration-control) var(--ease-control),
              opacity         var(--duration-control) var(--ease-control),
              scale           var(--duration-control) var(--ease-control),
              translate       var(--duration-control) var(--ease-control);
}

.btn--marketing:hover  { opacity: 0.95; }
.btn--marketing:active { scale: 0.98; translate: 0 1px; }

.btn--marketing::after {
  content: "";
  position: absolute;
  inset: -1px;
  z-index: 2;
  border: var(--border-hair) solid currentColor;
  border-radius: inherit;
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-reveal) var(--ease-control) 100ms;
}

.btn--marketing:active::after {
  opacity: 1;
  transition: none;  /* мгновенно внутрь */
}

/* Главная кнопка обрезает себя по overflow ради глянца. Кольцо на inset:-1px
   при этом срезалось бы, а глянец и без обрезки не вылезает: у него
   border-radius:inherit и inset:0. Поэтому в связке снимаем обрезку. */
.btn--primary.btn--marketing,
.btn--danger.btn--marketing,
.btn--destructive.btn--marketing { overflow: visible; }

/* Крупный призыв на посадочной. 16px/600 — тот кегль, на котором белая
   подпись на градиенте перестаёт быть проблемой контраста. */
.btn--cta {
  height: var(--control-h-lg);
  padding-inline: var(--space-6);
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
}

@media (min-width: 48rem) {
  .btn--cta { border-radius: var(--radius-pill); }
}

/* --- Содержимое кнопки ---------------------------------------------------- */

.btn__icon {
  width: var(--icon-size-base);
  height: var(--icon-size-base);
  flex: none;
}

.btn--lg .btn__icon { width: 18px; height: 18px; }
.btn--xs .btn__icon,
.btn--sm .btn__icon { width: var(--icon-size-sm); height: var(--icon-size-sm); }

.btn__spinner {
  width: var(--icon-size-sm);   /* 14px; 16px на lg */
  height: var(--icon-size-sm);
  flex: none;
  border: var(--border-thick) solid currentColor;
  border-top-color: transparent;
  border-radius: var(--radius-pill);
  animation: mc-spin 700ms var(--ease-loop) infinite;
}

.btn--lg .btn__spinner { width: var(--icon-size-base); height: var(--icon-size-base); }

.btn[aria-busy="true"] { cursor: progress; }

@keyframes mc-spin {
  to { rotate: 360deg; }
}

/* Глобальный зажим уменьшенной анимации оставил бы спиннер замершим в
   случайном положении. Даём ему осмысленное состояние покоя: замкнутое
   кольцо в полсилы. Так делает и первоисточник — не «animation: none», а
   эстетически завершённый кадр. */
@media (prefers-reduced-motion: reduce) {
  .btn__spinner {
    animation: none;
    border-top-color: currentColor;
    opacity: 0.55;
  }
}

/* --- Группа кнопок / панель инструментов ---------------------------------- */

.btn-group {
  display: inline-flex;
  align-items: center;
  border-radius: var(--radius-lg);
  background: var(--surface-control);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-xs);
}

.btn-group > .btn {
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}

.btn-group > .btn:hover { background: var(--surface-tint); }

.btn-group > .btn:first-child {
  border-start-start-radius: var(--radius-lg);
  border-end-start-radius: var(--radius-lg);
}

.btn-group > .btn:last-child {
  border-start-end-radius: var(--radius-lg);
  border-end-end-radius: var(--radius-lg);
}

/* Разделитель рисуется внутренней тенью, а не border: border поменял бы
   ширину кнопки и сломал бы выравнивание с соседней панелью. */
.btn-group > .btn + .btn { box-shadow: inset 1px 0 0 0 var(--line); }

.btn-group > .btn:focus-visible {
  z-index: var(--z-raised);
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.btn-group > .btn[aria-pressed="true"] {
  background: var(--surface-tint);
  color: var(--text-strong);
  font-weight: var(--font-weight-semibold);
}

/* =============================================================================
   12. ПОЛЯ ВВОДА И ФОРМЫ

   Геометрия у поля ровно та же, что у кнопки, — в этом весь смысл общей
   лестницы. 36px / 12px / 8px.

   Фокус — СМЕНА ТОЛЩИНЫ кольца, 1px → 1.5px, без смещения. Offset нулевой
   везде, это извлечённое значение.

   Про :focus-visible на полях. Текстовое поле по спецификации совпадает с
   :focus-visible всегда — даже когда в него ткнули мышью, потому что дальше
   всё равно последует ввод с клавиатуры. Значит контрастное кольцо на нём
   практически недостижимо, и делать вид, что оно там есть, бессмысленно.
   Поэтому у поля фирменное кольцо на :focus-visible (требование доступности)
   и контрастное на «просто :focus» — то есть на программной установке фокуса
   через .focus(). Толщина в обоих случаях извлечённая, 1.5px.
   ============================================================================= */

.form {
  display: grid;
  gap: var(--space-4);   /* 16px между полями */
}

/* Стопка одного поля: подпись / контрол / сообщение. */
.form-field {
  display: grid;
  gap: var(--space-2);   /* 8px */
}

.label {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  margin: 0;
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  color: var(--text-default);
  cursor: pointer;
}

.label__optional {
  font-weight: var(--font-weight-normal);
  color: var(--text-subtle);
}

.label__required {
  color: var(--text-danger);
}

.field-hint {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.field-error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-1);
  font-size: var(--text-sm);
  color: var(--text-danger);
}

/* Цвет не может быть единственным носителем смысла. К красной рамке всегда
   идёт текст ошибки, и к тексту — знак. Если разметка знак не дала,
   псевдоэлемент рисует хотя бы восклицательный в кружке. */
.field-error::before {
  content: "!";
  display: grid;
  place-items: center;
  flex: none;
  width: 14px;
  height: 14px;
  margin-block-start: 1px;
  border-radius: var(--radius-pill);
  background: currentColor;
  color: var(--surface-base);
  font-size: 10px;
  font-weight: var(--font-weight-bold);
  line-height: 1;
}

.field-error:has(svg)::before,
.field-error:has(.icon)::before { content: none; }

.field {
  display: block;
  width: 100%;
  height: var(--control-h-base);
  padding-inline: var(--control-px-base);
  border: 0;
  border-radius: var(--radius-lg);
  font-family: inherit;
  font-size: var(--text-base);
  letter-spacing: inherit;   /* иначе поле уезжает с сетки -0.01em */
  background: var(--surface-control);
  color: var(--text-default);
  box-shadow: 0 0 0 var(--ring-width) var(--line);
  outline: none;
  transition: box-shadow var(--duration-control) var(--ease-control),
              background-color var(--duration-control) var(--ease-control);
}

.field::placeholder { color: var(--text-placeholder); }

.field:focus {
  box-shadow: 0 0 0 var(--ring-width-focus) color-mix(in oklab, var(--focus-ring-color) 50%, transparent);
}

.field:focus-visible {
  box-shadow: 0 0 0 var(--ring-width-focus) var(--brand);
}

.field[aria-invalid="true"] {
  box-shadow: 0 0 0 var(--ring-width) var(--danger);
}

.field[aria-invalid="true"]:focus,
.field[aria-invalid="true"]:focus-visible {
  box-shadow: 0 0 0 var(--ring-width-focus) var(--danger);
}

/* --text-inactive это 1.48:1 — законно ровно здесь, на выключенном контроле,
   и больше нигде. Для приглушённого живого текста есть --text-subtle. */
.field:disabled {
  cursor: not-allowed;
  color: var(--text-inactive);
  background: var(--surface-recessed);
  box-shadow: 0 0 0 var(--ring-width) var(--hairline);
}

.field:read-only:not(:disabled) {
  background: var(--surface-recessed);
}

/* Текстовая область. field-sizing растит поле по содержимому — тогда ручка
   изменения размера не нужна. Там, где свойства нет, возвращаем ручку. */
.field--area {
  height: auto;
  min-height: calc(var(--control-h-base) * 2);
  padding-block: var(--space-2);
  line-height: var(--leading-normal);
  resize: none;
  field-sizing: content;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

@supports not (field-sizing: content) {
  .field--area { resize: vertical; }
}

/* Выпадающий список. Стрелка нарисована повёрнутым уголком border, а не
   вшита data-URI: data-URI не умеет currentColor, и стрелка не поехала бы за
   цветом текста ни при отключении, ни при наведении. Толщина 1.5px — та же,
   что у иконок. */
.select {
  position: relative;
  display: block;
}

.select > .field {
  appearance: none;
  -webkit-appearance: none;
  padding-inline-end: calc(var(--control-px-base) + 22px);
  cursor: pointer;
}

.select::after {
  content: "";
  position: absolute;
  inset-inline-end: calc(var(--control-px-base) + 1px);
  inset-block-start: 50%;
  width: 8px;
  height: 8px;
  margin-block-start: -6px;
  border-inline-end: 1.5px solid var(--text-subtle);
  border-block-end: 1.5px solid var(--text-subtle);
  border-end-end-radius: 1px;
  rotate: 45deg;
  pointer-events: none;
}

/* Если разметка кладёт свою иконку — рисованный уголок убираем. */
.select:has(> svg)::after { content: none; }

/* Группа: приставка + поле + добавка в одной рамке. Кольцо фокуса переезжает
   на обёртку — иначе внутри рамки появилась бы вторая рамка.
   Ровно это нужно для «имя ящика @ домен». */
.field-group {
  display: flex;
  align-items: center;
  border-radius: var(--radius-lg);
  background: var(--surface-control);
  box-shadow: 0 0 0 var(--ring-width) var(--line);
  transition: box-shadow var(--duration-control) var(--ease-control);
}

.field-group:focus-within {
  box-shadow: 0 0 0 var(--ring-width-focus) var(--brand);
}

.field-group > .field {
  flex: 1 1 auto;
  min-width: 0;
  background: transparent;
  border-radius: 0;
  box-shadow: none;
}

.field-group > .field:focus,
.field-group > .field:focus-visible { box-shadow: none; }

.field-group > .field:first-child { padding-inline-start: var(--control-px-base); }

.field-group__addon {
  display: flex;
  align-items: center;
  flex: none;
  height: var(--control-h-base);
  padding-inline: var(--control-px-base);
  font-size: var(--text-base);
  color: var(--text-subtle);
  white-space: nowrap;
  user-select: none;
}

.field-group__addon + .field { padding-inline-start: 0; }
.field-group > .field + .field-group__addon { padding-inline-start: 0; }

.field-group[data-invalid="true"] {
  box-shadow: 0 0 0 var(--ring-width) var(--danger);
}

.field-group[data-invalid="true"]:focus-within {
  box-shadow: 0 0 0 var(--ring-width-focus) var(--danger);
}

.fieldset {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.fieldset__legend {
  padding: 0;
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  color: var(--text-default);
}

.option-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.option-list--cards { gap: var(--space-3); }

/* =============================================================================
   13. ФЛАЖКИ, РАДИОКНОПКИ, ПЕРЕКЛЮЧАТЕЛИ

   Отмеченный флажок заливается --surface-contrast — почти чёрным, а НЕ
   фирменным цветом. Так в извлечённой карте вариантов, и это правильно:
   список из двадцати фирменно-синих галочек читается как список ошибок.
   Фирменный цвет остаётся редким.

   Галочка и точка нарисованы рамками, а не картинками: остаются чёткими на
   любом масштабе и сами перекрашиваются вместе с темой.
   ============================================================================= */

.checkbox,
.radio {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 16px;
  height: 16px;
  margin: 0;
  border-radius: var(--radius-sm);
  background: var(--surface-base);
  box-shadow: 0 0 0 var(--ring-width) var(--hairline);
  cursor: pointer;
  transition: background-color var(--duration-control) var(--ease-control),
              box-shadow var(--duration-control) var(--ease-control);
}

.radio { border-radius: var(--radius-pill); }

/* Область нажатия растянута псевдоэлементом — сам квадрат остаётся 16px.
   В источнике вылет -12px/-8px, то есть 40×32; по горизонтали это меньше
   требуемых 36px, поэтому берём -10px по обеим осям и получаем ровно 36×36.
   Приём тот же, значение подправлено под требование доступности. */
.checkbox::before,
.radio::before {
  content: "";
  position: absolute;
  inset: -10px;
}

/* Внутри .choice кликабельна вся строка, и растянутая область только мешала
   бы: у соседних строк они бы наложились. */
.choice .checkbox::before,
.choice .radio::before,
.table .checkbox::before { content: none; }

.checkbox:checked,
.checkbox:indeterminate,
.radio:checked {
  background: var(--surface-contrast);
  box-shadow: 0 0 0 var(--ring-width) var(--surface-contrast);
}

.checkbox:checked::after {
  content: "";
  position: absolute;
  inset-inline-start: 5px;
  inset-block-start: 2px;
  width: 4px;
  height: 8px;
  border: solid var(--text-inverse);
  border-width: 0 2px 2px 0;
  border-radius: 1px;
  rotate: 45deg;
}

.checkbox:indeterminate::after {
  content: "";
  position: absolute;
  inset-inline: 3px;
  inset-block-start: 50%;
  height: 2px;
  margin-block-start: -1px;
  border-radius: 1px;
  background: var(--text-inverse);
}

.radio:checked::after {
  content: "";
  position: absolute;
  inset: 4.5px;
  border-radius: var(--radius-pill);
  background: var(--text-inverse);
}

.checkbox:focus-visible,
.radio:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.checkbox:disabled,
.radio:disabled {
  cursor: not-allowed;
  background: var(--surface-recessed);
  box-shadow: 0 0 0 var(--ring-width) var(--hairline);
}

.checkbox:disabled:checked,
.radio:disabled:checked {
  background: var(--text-inactive);
  box-shadow: 0 0 0 var(--ring-width) var(--text-inactive);
}

/* Строка выбора: настоящий <label>, поэтому кликается целиком, включая
   описание. Высота не меньше базовой единицы управления. */
.choice {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  min-height: var(--control-h-base);
  padding-block: var(--space-2);
  font-size: var(--text-base);
  color: var(--text-default);
  cursor: pointer;
}

/* (21px строки − 16px квадрата) / 2 — квадрат встаёт по оптической середине
   первой строки, а не по её верху. */
.choice > .checkbox,
.choice > .radio { margin-block-start: 2px; }

.choice__text {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.choice__desc {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.choice:has(:disabled) {
  cursor: not-allowed;
  color: var(--text-subtle);
}

.choice--card {
  padding: var(--space-3);
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: var(--shadow-card);
  transition: background-color var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}

.choice--card:hover { background: var(--surface-tint); }

/* Выбранная карточка отличается не только заливкой: у неё ещё и кольцо в
   полную толщину. Плюс сам флажок внутри остаётся отмеченным — три признака,
   из которых цветом является один. */
.choice--card:has(:checked) {
  background: var(--brand-tint);
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.choice--card:has(:focus-visible) {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand), var(--shadow-sm);
}

/* Переключатель. Дорожка 36×18, ползунок 18. Радиус 5px, а на движках с
   corner-shape — сквиркл 10px: форма из источника, деталь заметная. */
.switch {
  position: relative;
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 36px;
  height: 18px;
  margin: 0;
  border-radius: 5px;
  background: var(--surface-fill);
  cursor: pointer;
  transition: background-color var(--duration-fast) var(--ease-out);
}

@supports (corner-shape: squircle) {
  .switch { border-radius: 10px; corner-shape: squircle; }
}

.switch::after {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 18px;
  height: 18px;
  border-radius: 5px;
  background: var(--surface-base);
  box-shadow: var(--shadow-switch-thumb);
  transition: translate var(--duration-fast) var(--ease-out);
}

@supports (corner-shape: squircle) {
  .switch::after { border-radius: 10px; corner-shape: squircle; }
}

.switch:checked { background: var(--brand); }
.switch:checked::after { translate: 18px 0; }

.switch:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.switch:disabled {
  cursor: not-allowed;
  opacity: 0.5;
}

/* =============================================================================
   14. КАРТОЧКИ

   Край карточки — КОЛЬЦО (box-shadow), а не border. Кольцо не занимает места
   в потоке, поэтому карточка может получить и потерять край без перекомпоновки
   соседей. Это правило действует по всему файлу.
   ============================================================================= */

.card {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: var(--shadow-card);   /* кольцо 1px --line + --shadow-xs */
  color: var(--text-default);
}

.card__header {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface-elevated);
  border-block-end: var(--border-hair) solid var(--line);
}

.card__title {
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
  /* Ведущая объявлена явно, а не унаследована. Этот класс висит и на <h3>, и
     на <p>, и на <span> — на витрине все три сразу, — а у заголовочных тегов
     своя ведущая из ряда --type-h*. Без этой строки одинаковые с виду
     заголовки карточек считались бы 24px и 20px в зависимости от тега.
     16px/24px — та же ступень, что h4 у панели. */
  line-height: var(--text-lg-lh);
  color: var(--text-default);
}

.card__desc {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.card__body {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
}

.card__body--lg { padding: var(--space-6); gap: var(--space-4); }

.card__footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--surface-elevated);
  border-block-start: var(--border-hair) solid var(--line);
}

.card__footer--end { justify-content: flex-end; }

.card--flush > .card__body { padding: 0; }

/* Слоёная: внешняя оболочка лежит на --surface-elevated и ограничена
   волосяной линией, внутренние карточки — на --surface-base. Так стопка
   вложенных панелей не превращается в лестницу теней. */
.card--layered {
  background: var(--surface-elevated);
  box-shadow: 0 0 0 var(--ring-width) var(--hairline);
}

.card--layered > .card__header,
.card--layered > .card__footer {
  padding: var(--space-4);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  color: var(--text-subtle);
  border-color: var(--hairline);
}

.card--inner {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: 16px 12px 16px 16px;
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: 0 0 0 var(--ring-width) var(--surface-fill);
}

/* Карточка целиком ссылка. Наведение меняет только цвет: никакого подъёма,
   никакого роста тени — на всём разобранном сайте таких эффектов нет. */
.card--interactive {
  display: block;
  color: inherit;
  transition: background-color var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}

.card--interactive:hover {
  background: var(--surface-tint);
  color: inherit;
}

.card--interactive:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand), var(--shadow-xs);
}

/* Выделенный тариф. Кольцо фирменного цвета плюс бейдж в разметке —
   цвет снова не единственный признак. */
.card--featured {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand), var(--shadow-sm);
}

/* Крупное число для посадочной: аптайм, квота, срок. */
.stat { display: grid; gap: var(--space-1); }

.stat__value {
  font-size: var(--text-4xl);
  font-weight: var(--font-weight-medium);
  line-height: var(--leading-none);
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  font-variation-settings: "opsz" var(--opsz-heading);
  color: var(--text-strong);
}

.stat__label {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.price {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
}

.price__amount {
  font-size: var(--type-h3-size);
  font-weight: var(--font-weight-medium);
  line-height: var(--leading-none);
  letter-spacing: var(--tracking-tight);
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

.price__period {
  font-size: var(--text-base);
  color: var(--text-subtle);
}

/* Список возможностей тарифа. Галочка нарисована рамкой — той же техникой,
   что и в флажке, и по тем же причинам. */
.list-check {
  display: grid;
  gap: var(--space-2);
}

.list-check > li {
  display: grid;
  grid-template-columns: 16px minmax(0, 1fr);
  gap: var(--space-2);
  align-items: start;
  font-size: var(--text-base);
  color: var(--text-default);
}

.list-check > li::before {
  content: "";
  width: 5px;
  height: 9px;
  margin: 4px 0 0 4px;
  border: solid var(--success);
  border-width: 0 1.5px 1.5px 0;
  border-radius: 1px;
  rotate: 45deg;
}

/* Пункт, которого в тарифе нет: не просто серый, а с прочерком. Цвет здесь
   тоже не единственный носитель. */
.list-check > li.is-absent { color: var(--text-subtle); }

.list-check > li.is-absent::before {
  width: 9px;
  height: 0;
  margin: 9px 0 0 3px;
  border-width: 1.5px 0 0 0;
  border-color: var(--text-inactive);
  rotate: none;
}

/* =============================================================================
   15. ТАБЛИЦЫ ДАННЫХ

   Высота строки 40px, ячейка 0 12px: вертикальную работу делает высота, а не
   внутренние отступы. Тогда строка не «дышит» от длины содержимого.

   Два осознанных отступления от источника.
   Первое: наведение там красится в --surface-overlay, а он по замеру равен
   холсту, oklch(98.75% 0 0). На белой строке это разница в 4 из 255 — то есть
   не видно вообще. Берём --surface-tint, oklch(97% 0 0).
   Второе: там же --surface-tint отвечает и за выделение, а список писем
   обязан показывать наведение и выделение ОДНОВРЕМЕННО. Выделению отдаём
   --brand-tint плюс полосу слева.
   ============================================================================= */

.table-wrap {
  width: 100%;
  overflow-x: auto;
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: var(--shadow-card);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.table {
  width: 100%;
  font-size: var(--text-base);
  background: var(--surface-base);
  color: var(--text-default);
}

.table th {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-raised);
  height: var(--header-height);   /* 58px */
  padding: 0 var(--cell-pad-x);
  text-align: start;
  white-space: nowrap;
  font-weight: var(--font-weight-semibold);
  color: var(--text-default);
  background: var(--surface-base);
  border-block-end: var(--border-hair) solid var(--surface-fill);
}

.table td {
  height: var(--row-height);      /* 40px */
  padding: 0 var(--cell-pad-x);
  vertical-align: middle;
  border-block-end: var(--border-hair) solid var(--surface-fill);
}

.table tbody tr {
  background: var(--surface-base);
  /* Только фон. Строку таблицы не двигают никогда. */
  transition: background-color var(--duration-base) var(--ease-out);
}

.table tbody tr:last-child td { border-block-end: 0; }

.table--zebra tbody tr:nth-child(even) { background: var(--surface-tint); }

.table tbody tr:hover { background: var(--surface-tint); }

/* Второй экземпляр той же полосы слева — убран по той же причине, что и у
   плитки состояния (см. .status-tile--attention): односторонних цветных полос
   в источнике нет ни одной. Выделение несёт заливка, а дикторам оно и так
   объявлено самим aria-selected, то есть цвет здесь и не был единственным. */
.table tbody tr[aria-selected="true"] {
  background: var(--brand-tint);
}

.table tbody tr.is-dragging { opacity: 0.3; }

/* На подложке --surface-tint приглушённый текст теряет контраст. Подмена
   описана в разделе 2 этого файла.

   РАСКРЫТАЯ СТРОКА (tr.is-open) СТОИТ В ЭТОМ СПИСКЕ НЕ ДЛЯ СИММЕТРИИ. Она
   красится тем же --surface-tint, что зебра и наведение, но в списке её не
   было, и замер это поймал: «на нас» и «закрыта» в раскрытой строке домена
   давали 4.34:1 при норме 4.5. Ниже, в разделе 28, для .cell__sub раскрытая
   строка уже учтена — здесь строчка просто потерялась. Подложка одна, значит и
   список должен быть один. */
.table--zebra tbody tr:nth-child(even) .text-subtle,
.table tbody tr:hover .text-subtle,
.table tbody tr.is-open .text-subtle,
.table tbody tr[aria-selected="true"] .text-subtle { color: var(--text-subtle-strong); }

/* Подпись шкалы — тот же --text-subtle, только не через класс .text-subtle, а
   собственным свойством .meter__caption, поэтому правило выше её не достаёт.
   А шкала занятого места стоит как раз внутри строки домена: «245 КБ из 1 ГБ»
   на раскрытой строке давало те же 4.34:1. */
.table--zebra tbody tr:nth-child(even) .meter__caption,
.table tbody tr:hover .meter__caption,
.table tbody tr.is-open .meter__caption,
.table tbody tr[aria-selected="true"] .meter__caption { color: var(--text-subtle-strong); }

/* Плотный режим: 35px — метрика почтового списка, не таблицы отчётов. */
.table--compact td { height: var(--row-height-compact); }

.table--compact th {
  height: auto;
  padding-block: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-strong);
  background: var(--surface-elevated);
}

/* У пальцевого ввода строка обязана быть выше — 47px, метрика мобильного
   списка писем. У источника отдельного пальцевого яруса нет вовсе. */
@media (pointer: coarse) {
  .table td,
  .table--compact td { height: var(--row-height-touch); }
}

.table td.is-num,
.table th.is-num {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

.table td.is-check,
.table th.is-check {
  width: 40px;
  line-height: 1;
}

.table__sort {
  display: inline-flex;
  align-items: center;
  min-height: var(--control-h-base);
  gap: var(--space-1);
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.table__sort svg {
  width: var(--icon-size-base);
  height: var(--icon-size-base);
  color: var(--text-subtle);
}

.table th[aria-sort="ascending"] .table__sort svg,
.table th[aria-sort="descending"] .table__sort svg {
  width: var(--icon-size-sm);
  height: var(--icon-size-sm);
  color: var(--text-default);
}

/* Действия строки: показываются по наведению и по фокусу внутри строки —
   иначе с клавиатуры до них не добраться. */
.row-actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  opacity: 0;
  transition: opacity var(--duration-base) var(--ease-out);
}

.table tbody tr:hover .row-actions,
.table tbody tr:focus-within .row-actions,
.row-actions:focus-within { opacity: 1; }

@media (pointer: coarse) {
  .row-actions { opacity: 1; }
}

/* =============================================================================
   16. БОКОВАЯ НАВИГАЦИЯ

   Активный пункт — ЗАЛИТЫЙ СКРУГЛЁННЫЙ ФОН. Не полоска слева, не смена цвета
   текста, не жирная точка. Это главная находка разбора и самое узнаваемое
   решение всей системы.

   Иконки ПРИГЛУШЕНЫ прозрачностью 0.4, а не перекрашены в серый: при смене
   темы ничего не надо пересчитывать, а на активном фоне они не спорят с
   подписью.

   Одно добавление к извлечённому: активный пункт получает ещё и вес 600.
   Заливка — это цвет, а цвет не имеет права быть единственным носителем
   смысла. Плюс aria-current="page" в разметке для экранных дикторов.
   ============================================================================= */

.nav {
  display: flex;
  flex-direction: column;
  width: var(--sidebar-width);      /* 260px */
  background: var(--sidebar-bg);
  border-inline-end: var(--border-hair) solid var(--line);
}

.nav__header {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  height: var(--header-height);     /* 58px */
  padding-inline: var(--space-3);
  border-block-end: var(--border-hair) solid var(--line);
}

.nav__content {
  flex: 1 1 auto;
  padding: var(--space-3) 14px;
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
}

.nav__footer {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  height: 48px;
  padding-inline: var(--space-4);
  position: sticky;
  inset-block-end: 0;
  background: var(--sidebar-bg);
  border-block-start: var(--border-hair) solid var(--line);
}

/* Строки стоят в 1px друг от друга: заливка активного пункта должна читаться
   как отдельная плитка, а не как продолжение соседней. */
.nav__group {
  display: flex;
  flex-direction: column;
  gap: 1px;
}

.nav__label {
  margin: var(--space-4) 0 var(--space-2);
  padding-inline: var(--space-3);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  color: var(--text-subtle);
}

.nav__item {
  position: relative;
  display: flex;
  align-items: center;
  width: 100%;
  min-height: var(--sidebar-item-h);   /* 34px */
  padding-inline: var(--space-3);
  gap: 10px;
  border-radius: var(--radius-lg);
  font-size: var(--text-sm);           /* 13px */
  font-weight: var(--font-weight-medium);
  text-align: start;
  text-decoration: none;
  color: var(--text-default);
  background: transparent;
  cursor: pointer;
  outline: none;
  transition: color var(--duration-sidebar) var(--ease-sidebar),
              background-color var(--duration-sidebar) var(--ease-sidebar),
              box-shadow var(--duration-sidebar) var(--ease-sidebar);
}

.nav__item:hover,
.nav__item[aria-current="page"] {
  background: var(--sidebar-active-bg);   /* --surface-tint */
  color: var(--text-default);
}

.nav__item[aria-current="page"] {
  color: var(--text-strong);
  font-weight: var(--font-weight-semibold);
}

.nav__item:focus-visible {
  background: var(--sidebar-active-bg);
  color: var(--text-strong);
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.nav__item svg {
  width: var(--icon-size-base);
  height: var(--icon-size-base);
  flex: none;
  opacity: var(--sidebar-icon-opacity);   /* 0.4 */
}

.nav__item[aria-current="page"] svg { opacity: 0.75; }

.nav__item > span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav__item--sm { min-height: var(--sidebar-item-h-sm); }

/* 34px и 28px — извлечённые высоты строк рельса, они рассчитаны на курсор.
   Пальцу нужна базовая единица управления. */
@media (pointer: coarse) {
  .nav__item,
  .nav__item--sm { min-height: var(--control-h-base); }
}

/* Подпункты: вертикальная направляющая на 19px от края рельса. */
.nav__sub {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding-inline-start: var(--space-6);
}

.nav__sub::before {
  content: "";
  position: absolute;
  inset-inline-start: 19px;
  inset-block: 0;
  width: 1px;
  background: var(--line);
}

/* Счётчик непрочитанного. Он ПУНКТИРНЫЙ — это извлечённая деталь, не описка.
   Пунктир отличает счётчик от бейджа состояния, который сплошной. */
.nav__count {
  margin-inline-start: auto;
  padding: 2px 6px;
  border-radius: var(--radius-pill);
  border: var(--border-hair) dashed var(--line);
  font-size: var(--text-2xs);      /* 11px */
  line-height: 1;
  font-weight: var(--font-weight-medium);
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
  user-select: none;
}

/* Свёрнутый рельс: 57px, только иконки. Подпись обязана лежать в <span> —
   именно его мы прячем. */
.nav--rail {
  width: var(--sidebar-width-icon);
  overflow: hidden;
}

.nav--rail .nav__item {
  justify-content: center;
  padding-inline: 0;
}

.nav--rail .nav__item > span,
.nav--rail .nav__label,
.nav--rail .nav__count { display: none; }

/* =============================================================================
   17. БЕЙДЖИ

   Сплошные заливки несут белый текст — кроме оранжевой, которая слишком
   светлая и несёт ЧЁРНЫЙ. Это исключение извлечено из карты вариантов, а не
   придумано.

   Для всего, что повторяется списком (метки, флаги, состояния доменов),
   берите тонированные пары badge--success / --warning / --error / --info, а
   не сплошные: колонка сплошных бейджей нечитаема.
   ============================================================================= */

.badge {
  display: inline-flex;
  align-items: center;
  width: fit-content;
  flex: none;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
  line-height: var(--text-xs-lh);
  white-space: nowrap;
  background: var(--badge-inverted);
  color: var(--text-badge-inverted);
}

.badge--with-dot { gap: 6px; padding-inline-start: 6px; }

.badge__dot {
  width: 7px;
  height: 7px;
  flex: none;
  border-radius: var(--radius-pill);
  background: currentColor;
}

/* Бейдж внутри наведённой ссылки обводится — подсказка, что он часть ссылки. */
a:hover .badge { box-shadow: 0 0 0 1px currentColor; }

.badge--primary   { background: var(--badge-inverted); color: var(--text-badge-inverted); }
.badge--secondary { background: var(--surface-fill);   color: var(--text-badge-neutral-subtle); }
.badge--success   { background: var(--success-tint);   color: var(--text-success); }
/* Та же поправка, что у предупреждающего баннера: --text-warning на
   --warning-tint замерен как 4.00:1 на кегле 12px, а по правилу этого файла
   мелкий предупреждающий текст берёт ступень темнее. С orange-800 — 7.09:1. */
.badge--warning   { background: var(--warning-tint);   color: var(--text-badge-orange-subtle); }
.badge--error     { background: var(--danger-tint);    color: var(--text-danger); }
.badge--info      { background: var(--info-tint);      color: var(--text-info); }

.badge--outline {
  background: var(--surface-base);
  color: var(--text-default);
  box-shadow: 0 0 0 var(--border-hair) var(--surface-fill);
}

/* Пунктирная обводка фирменным — метка «бета», «скоро». */
.badge--beta {
  background: transparent;
  color: var(--text-link);
  border: var(--border-hair) dashed var(--brand);
}

.badge--red     { background: var(--badge-red);     color: var(--neutral-0); }
.badge--green   { background: var(--badge-green);   color: var(--neutral-0); }
.badge--purple  { background: var(--badge-purple);  color: var(--neutral-0); }
.badge--teal    { background: var(--badge-teal);    color: var(--neutral-0); }
.badge--blue    { background: var(--badge-blue);    color: var(--neutral-0); }
.badge--neutral { background: var(--badge-neutral); color: var(--neutral-0); }
.badge--orange  { background: var(--badge-orange);  color: var(--neutral-black); }  /* чёрный — исключение из источника */

.badge--teal-subtle {
  background: var(--badge-teal-subtle);
  color: var(--text-badge-teal-subtle);
}

/* Точечный вариант: смысл несёт цвет точки, а бейдж остаётся нейтральным.
   Годится там, где рядом стоят пять состояний подряд. */
.badge--dot {
  background: transparent;
  color: var(--text-default);
  box-shadow: 0 0 0 var(--border-hair) var(--hairline);
  gap: 6px;
  padding-inline-start: 6px;
}

.badge--dot.badge--success .badge__dot { background: var(--success); }
.badge--dot.badge--warning .badge__dot { background: var(--badge-orange); }
.badge--dot.badge--error   .badge__dot { background: var(--badge-red); }
.badge--dot.badge--info    .badge__dot { background: var(--info); }
.badge--dot.badge--neutral .badge__dot { background: var(--badge-neutral); }
.badge--dot.badge--success,
.badge--dot.badge--warning,
.badge--dot.badge--error,
.badge--dot.badge--info,
.badge--dot.badge--neutral { color: var(--text-default); }

/* =============================================================================
   18. БАННЕРЫ

   Ни рамки, ни разделения цвета значка и текста: смысл несёт тонированный
   фон, а значок и слова красятся одинаково. Так в источнике, и так спокойнее
   выглядит длинная страница, где баннеров несколько.
   ============================================================================= */

.banner {
  display: flex;
  align-items: flex-start;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  font-size: var(--text-base);
}

.banner__icon {
  flex: none;
  width: var(--icon-size-base);
  height: var(--icon-size-base);
  margin-block-start: 2px;
}

.banner__body {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

.banner__title {
  font-weight: var(--font-weight-medium);
  line-height: var(--leading-snug);
}

.banner__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-1);
}

.banner--info    { background: var(--banner-info);    color: var(--text-info); }
.banner--success { background: var(--success-tint);   color: var(--text-success); }
.banner--error   { background: var(--danger-tint);    color: var(--text-danger); }

/* СТУПЕНЬ ВЗЯТА ТЕМНЕЕ, И ЭТО ИСПРАВЛЕНИЕ, А НЕ ВКУС. Прежняя пара
   --text-warning (#BD6500) на --banner-warning (#FEF9C2) замерена браузером и
   даёт 3.89:1 — ниже AA. Вес 500 в этом не помогает никак: WCAG считает вес
   только начиная с 18.66px, а текст баннера идёт на 14px. Ровно этот выход
   тут и был записан прежним комментарием: --text-badge-orange-subtle для того
   и заведён. С ним получается 7.07:1.

   1:1 с источником при этом не нарушено: подложка --banner-warning у
   Cloudflare парного токена не имеет и придумана нами (см. раздел 2), так что
   и пара «текст + фон» здесь наша, а не скопированная. */
.banner--warning {
  background: var(--banner-warning);
  color: var(--text-badge-orange-subtle);
  font-weight: var(--font-weight-medium);
}

.banner--compact {
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
  gap: var(--space-2);
}

/* =============================================================================
   19. ТОСТЫ

   Правый нижний угол, 16px от края, 32px на широком экране, ширина 340px.

   У вариантов состояния фон НЕ тонируется — меняется только волосяная обводка
   и цвет значка с заголовком. Толщина обводки 0.3px, это намеренно
   субпиксельное значение из источника: на обычном экране оно даёт мягкий
   цветной контур вместо жирной рамки.
   ============================================================================= */

.toast-viewport {
  position: fixed;
  inset-inline-end: var(--toast-inset);
  inset-block-end: var(--toast-inset);
  z-index: var(--z-toast);
  display: flex;
  flex-direction: column;
  gap: var(--toast-gap);
  width: min(var(--toast-width), calc(100vw - var(--toast-inset) * 2));
  pointer-events: none;   /* сквозь пустое место виевпорта можно кликать */
}

@media (min-width: 40rem) {
  .toast-viewport {
    inset-inline-end: var(--toast-inset-lg);
    inset-block-end: var(--toast-inset-lg);
  }
}

.toast {
  position: relative;
  pointer-events: auto;
  padding: var(--space-4);
  padding-inline-end: var(--space-8);   /* место под крестик */
  border-radius: var(--radius-xl);      /* 12px */
  background: var(--surface-base);
  background-clip: padding-box;
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-lg);
  display: flex;
  gap: var(--space-3);
  animation: mc-toast-in var(--duration-sheet) var(--ease-media) both;
}

.toast[data-state="closed"] {
  animation: mc-toast-out var(--duration-base) var(--ease-in) both;
}

@keyframes mc-toast-in {
  from { opacity: 0; translate: 0 150%; }
  to   { opacity: 1; translate: 0 0; }
}

@keyframes mc-toast-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.toast__icon {
  flex: none;
  width: var(--icon-size-base);
  height: var(--icon-size-base);
  margin-block-start: 2px;
  color: var(--text-subtle);
}

.toast__body {
  display: grid;
  gap: var(--space-1);
  min-width: 0;
}

/* Кегли действительно вне лестницы: 0.975rem и 0.925rem. Сохранены как
   извлечены; если хочется на лестницу — 15px и 14px. */
.toast__title {
  font-size: 15.6px;
  line-height: 20px;
  font-weight: var(--font-weight-medium);
  color: var(--text-default);
}

.toast__desc {
  font-size: 14.8px;
  line-height: 20px;
  color: var(--text-subtle);
}

.toast__actions {
  display: flex;
  gap: var(--space-2);
  margin-block-start: var(--space-2);
}

.toast__close {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: var(--radius-sm);
  color: var(--text-subtle);
  transition: background-color var(--duration-control) var(--ease-control);
}

/* Сам крестик 20px — этого мало для пальца. Растягиваем область нажатия до
   36×36 тем же приёмом, что и у флажка: рисунок не меняется. */
.toast__close::after {
  content: "";
  position: absolute;
  inset: -8px;
}

.toast__close:hover { background: color-mix(in oklab, currentColor 15%, transparent); }

.toast__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.toast--success { box-shadow: 0 0 0 var(--ring-width-toast) var(--success), var(--shadow-lg); }
.toast--error   { box-shadow: 0 0 0 var(--ring-width-toast) var(--danger),  var(--shadow-lg); }
.toast--warning { box-shadow: 0 0 0 var(--ring-width-toast) var(--warning), var(--shadow-lg); }
.toast--info    { box-shadow: 0 0 0 var(--ring-width-toast) var(--info),    var(--shadow-lg); }

.toast--success .toast__icon, .toast--success .toast__title { color: var(--text-success); }
.toast--error   .toast__icon, .toast--error   .toast__title { color: var(--text-danger); }
.toast--warning .toast__icon, .toast--warning .toast__title { color: var(--text-warning); }
.toast--info    .toast__icon, .toast--info    .toast__title { color: var(--text-info); }

/* =============================================================================
   20. МОДАЛЬНЫЕ ОКНА

   Диалог намеренно опускает тень с --shadow-xl до пары слоёв по 3% альфы:
   окно держится на подложке, а не на тяжёлой тени.

   Поддержаны обе разметки. Родная — <dialog class="modal">, она даёт
   ловушку фокуса, Esc и верхний слой бесплатно; для неё есть @starting-style
   и allow-discrete, поэтому анимация входа и выхода работает без единой
   строки JS. Ручная — div с .modal-backdrop рядом, для случаев, когда
   <dialog> почему-то не подходит.
   ============================================================================= */

.modal {
  position: fixed;
  inset-block-start: 50%;
  inset-inline-start: 50%;
  transform: translate(-50%, -50%);
  z-index: var(--z-modal);
  width: 100%;
  max-width: min(32rem, calc(100vw - 2rem));
  max-height: calc(100dvh - 4rem);
  overflow: hidden;
  display: flex;
  flex-direction: column;
  border: 0;
  padding: 0;
  border-radius: var(--radius-xl);   /* 12px */
  background: var(--surface-base);
  color: var(--text-default);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-dialog);
  transition: scale var(--duration-fast) var(--ease-standard),
              opacity var(--duration-fast) var(--ease-standard),
              overlay var(--duration-fast) var(--ease-standard) allow-discrete,
              display var(--duration-fast) var(--ease-standard) allow-discrete;
}

.modal--sm { max-width: min(24rem, calc(100vw - 2rem)); }
.modal--lg { max-width: min(42rem, calc(100vw - 2rem)); }

/* Командная палитра прижата к верху: список результатов растёт вниз и не
   прыгает по вертикали при изменении длины. */
.modal--command {
  inset-block-start: 10vh;
  transform: translate(-50%, 0);
  max-width: min(42rem, calc(100vw - 2rem));
}

.modal[data-starting-style],
.modal[data-ending-style],
.modal[data-state="closed"] {
  scale: 0.9;
  opacity: 0;
}

dialog.modal { opacity: 1; scale: 1; }

/* Авторский `display: flex` у .modal перебивает браузерное
   `dialog:not([open]) { display: none }` — авторский слой всегда выигрывает у
   пользовательского агента, независимо от специфичности. Без строки ниже
   закрытый <dialog class="modal"> остаётся в потоке: не видно (opacity: 0), но
   кнопки внутри ловят Tab и клики, и клавиатурный фокус уходит в невидимое
   окно. Возвращаем display: none явно.
   Анимация выхода не страдает: в transition у .modal уже перечислены display и
   overlay с allow-discrete, поэтому display переключается в конце перехода. */
dialog.modal:not([open]) { opacity: 0; scale: 0.9; display: none; }

@starting-style {
  dialog.modal[open] { opacity: 0; scale: 0.9; }
}

dialog.modal::backdrop {
  background: var(--surface-recessed, #F3F3F3);
  opacity: 0.8;
  transition: opacity var(--duration-fast) var(--ease-standard),
              overlay var(--duration-fast) var(--ease-standard) allow-discrete,
              display var(--duration-fast) var(--ease-standard) allow-discrete;
}

dialog.modal:not([open])::backdrop { opacity: 0; }

@starting-style {
  dialog.modal[open]::backdrop { opacity: 0; }
}

/* Ручной вариант подложки. Оба имени поддержаны: .modal-backdrop по факту
   сосед окна, а не его часть, но README называет его .modal__backdrop. */
.modal-backdrop,
.modal__backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-overlay);
  background: var(--surface-recessed);
  opacity: 0.8;
  border: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
}

.modal-backdrop[data-starting-style],
.modal-backdrop[data-ending-style],
.modal-backdrop[data-state="closed"],
.modal__backdrop[data-starting-style],
.modal__backdrop[data-ending-style] { opacity: 0; }

.modal__header {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4);
  padding-inline-end: var(--space-10);
  border-block-end: var(--border-hair) solid var(--line);
}

.modal__title {
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
  line-height: var(--text-lg-lh);     /* явно: класс ставится и на h2, и на div */
  color: var(--text-default);
}

.modal__desc {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.modal__body {
  flex: 1 1 auto;
  padding: var(--space-4);
  overflow-y: auto;
  overscroll-behavior: contain;
}

.modal__footer {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-elevated);
  border-block-start: var(--border-hair) solid var(--line);
}

.modal__close {
  position: absolute;
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-md);
  color: var(--text-subtle);
  transition: background-color var(--duration-control) var(--ease-control),
              color var(--duration-control) var(--ease-control);
}

.modal__close::after {
  content: "";
  position: absolute;
  inset: -4px;   /* 28 + 8 = 36 по цели нажатия */
}

.modal__close:hover { background: var(--surface-tint); color: var(--text-default); }

.modal__close:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

/* Пока открыт родной диалог, страница под ним не должна прокручиваться. */
body:has(dialog.modal[open]) { overflow: hidden; }

/* =============================================================================
   21. ВКЛАДКИ

   Два варианта, оба извлечённые.

   Сегментированные — белая приподнятая плитка, скользящая по утопленной
   дорожке. Годятся для двух-трёх коротких переключателей.

   Подчёркнутые — для длинных списков разделов; при переполнении список
   прокручивается вбок, полоса прокрутки скрыта, overscroll заперт.
   ============================================================================= */

.tabs { display: flex; flex-direction: column; }

.tabs__list {
  display: inline-flex;
  align-items: center;
  align-self: flex-start;
  height: var(--control-h-base);
  padding-inline: 2px;
  border-radius: var(--radius-lg);
  background: var(--surface-recessed);
  box-shadow: 0 0 0 var(--ring-width) color-mix(in oklab, var(--hairline) 70%, transparent);
}

.tabs__tab {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  height: calc(var(--control-h-base) - 4px);   /* 32px + поля 2+2 = дорожка 36px */
  margin-block: 2px;
  padding-inline: 10px;
  border-radius: var(--radius-md);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  white-space: nowrap;
  color: var(--text-subtle);
  background: transparent;
  cursor: pointer;
  transition: color var(--duration-base) var(--ease-out),
              background-color var(--duration-base) var(--ease-out),
              box-shadow var(--duration-base) var(--ease-out);
}

.tabs__tab:hover { color: var(--text-default); }

.tabs__tab[aria-selected="true"] {
  color: var(--text-default);
  background: var(--surface-base);
  box-shadow: 0 0 0 var(--ring-width) var(--line), var(--shadow-sm);
}

.tabs__tab:focus-visible {
  outline: none;
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

.tabs--sm .tabs__list { height: var(--control-h-sm); border-radius: var(--radius-md); }

.tabs--sm .tabs__tab {
  height: calc(var(--control-h-sm) - 4px);
  margin-block: 2px;
  padding-inline: 8px;
  border-radius: var(--radius-sm);
  font-size: var(--text-xs);
}

.tabs--underline .tabs__list {
  align-self: stretch;
  height: auto;
  gap: var(--space-4);
  padding: 0 0 var(--space-2);
  border-radius: 0;
  background: transparent;
  box-shadow: none;
  border-block-end: var(--border-hair) solid var(--hairline);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}

.tabs--underline .tabs__list::-webkit-scrollbar { display: none; }

.tabs--underline .tabs__tab {
  position: relative;
  height: auto;
  margin: 0;
  padding: 12px 8px;
  border-radius: var(--radius-md) var(--radius-md) 0 0;
  background: transparent;
  box-shadow: none;
}

.tabs--underline .tabs__tab:hover {
  background: var(--surface-tint);
  color: var(--text-default);
}

.tabs--underline .tabs__tab[aria-selected="true"] {
  background: transparent;
  box-shadow: none;
  color: var(--text-default);
  font-weight: var(--font-weight-semibold);
}

/* Индикатор перекрывает волосяную линию списка: 2px поверх 1px. */
.tabs--underline .tabs__tab[aria-selected="true"]::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  inset-block-end: calc((var(--space-2) + 1px) * -1);
  height: 2px;
  background: var(--brand);
}

.tabs--underline .tabs__tab:focus-visible {
  box-shadow: 0 0 0 var(--ring-width-strong) var(--brand);
}

/* Вкладка ростом 32px — цель нажатия для курсора, не для пальца. Поднимаем
   и дорожку, и вкладку на ступень вверх по той же лестнице управления. */
@media (pointer: coarse) {
  .tabs__list { height: var(--control-h-lg); }
  .tabs__tab  { height: calc(var(--control-h-lg) - 4px); }
}

.tabs__panel { padding-block-start: var(--space-4); }

/* =============================================================================
   22. ПУСТОЕ СОСТОЯНИЕ И СКЕЛЕТОН

   Пустое состояние — это 64px отступа, центрированная колонка, 16px зазоры и
   приглушённое описание. Ни иллюстрации, ни карточки, ни рамки. Весь
   компонент — четыре правила.
   ============================================================================= */

.empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-16);   /* 64px */
  text-align: center;
}

/* Специфичность поднята намеренно: .icon объявлен ниже по файлу и при равном
   весе перебил бы размер обратно на 16px. */
.empty .empty__icon {
  width: 32px;
  height: 32px;
  color: var(--text-subtle);
  opacity: 0.6;
}

.empty__title {
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
  line-height: var(--text-lg-lh);     /* явно: класс ставится и на h2/h3, и на p */
  color: var(--text-default);
}

.empty__desc {
  max-width: 36rem;
  font-size: var(--text-base);
  color: var(--text-subtle);
}

.empty__actions {
  display: flex;
  justify-content: center;
  gap: var(--space-4);
  flex-wrap: wrap;
}

.skeleton-line {
  position: relative;
  overflow: hidden;
  height: 8px;
  width: var(--skeleton-width, 100%);
  border-radius: 2px;
  background: var(--skeleton-bg);
}

.skeleton-line::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg,
              transparent 0%,
              var(--skeleton-shimmer) 50%,
              transparent 100%);
  animation: mc-shimmer 1.5s var(--ease-loop) infinite;
}

@keyframes mc-shimmer {
  from { translate: -100% 0; }
  to   { translate: 100% 0; }
}

/* Декоративный проблеск не замораживаем, а убираем совсем: замороженный
   градиент посреди полосы выглядит как дефект отрисовки. */
@media (prefers-reduced-motion: reduce) {
  .skeleton-line::after { display: none; }
}

/* =============================================================================
   23. ИКОНКИ, РАЗДЕЛИТЕЛИ, ПОДСКАЗКИ, АВАТАРЫ, КЛАВИШИ

   Иконки рисуются на сетке 24×24 с обводкой 1.5, круглыми концами и стыками,
   без заливки, а показываются на 16px. Арифметика тут не случайная: 1.5 при
   24 даёт ровно 1.0px при 16, штрих ложится на пиксельную решётку и не мылится.
   Класс .icon задаёт всё это за вас — в разметке достаточно viewBox="0 0 24 24"
   и самих path.

   Свою графику рисуем сами. Копировать чужой набор символов нельзя: там,
   помимо прочего, лежат чужие логотипы. Копируется спецификация, а не файлы.
   ============================================================================= */

.icon {
  width: var(--icon-size-base);   /* 16px */
  height: var(--icon-size-base);
  flex: none;
  fill: none;
  stroke: currentColor;
  stroke-width: var(--icon-stroke);   /* 1.5 в единицах viewBox */
  stroke-linecap: round;
  stroke-linejoin: round;
}

.icon--sm { width: var(--icon-size-sm); height: var(--icon-size-sm); }
.icon--lg { width: 20px; height: 20px; }
.icon--xl { width: var(--icon-grid); height: var(--icon-grid); }

/* Иконка, залитая целиком (логотип, монограмма), а не обведённая. */
.icon--filled { fill: currentColor; stroke: none; }

.divider {
  border: 0;
  border-block-start: var(--border-hair) solid var(--line);
  margin-block: var(--space-6);
}

.divider--tight { margin-block: var(--space-3); }

.divider--vertical {
  align-self: stretch;
  width: 0;
  margin: 0 var(--space-2);
  border-block-start: 0;
  border-inline-start: var(--border-hair) solid var(--line);
}

/* Подсказка: только внешний вид. Позиционирование — за разметкой или
   скриптом, потому что якорное позиционирование пока не везде одинаково. */
.tooltip {
  position: absolute;
  z-index: var(--z-dropdown);
  max-width: 20rem;
  padding: 6px 10px;
  border-radius: var(--radius-md);
  background: var(--surface-base);
  color: var(--text-default);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  /* --tooltip-border — отдельный токен панели (gray-200), а не общая
     --surface-fill: у подсказки край чуть холоднее и на полтона светлее, чем у
     линейки таблицы. Подъём при этом держит тень, а не обводка: стрелка
     (--tip-stroke) в светлой теме прозрачна. */
  outline: var(--border-hair) solid var(--tooltip-border);
  box-shadow: var(--shadow-lg);
}

.avatar {
  display: inline-grid;
  place-items: center;
  flex: none;
  width: 32px;
  height: 32px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--surface-fill);
  color: var(--text-default);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  line-height: 1;
  user-select: none;
}

.avatar--sm { width: 24px; height: 24px; font-size: var(--text-xs); }
.avatar--lg { width: 40px; height: 40px; font-size: var(--text-base); }
.avatar--brand { background: var(--brand-tint); color: var(--text-link); }
.avatar img { width: 100%; height: 100%; object-fit: cover; }

.kbd {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding-inline: 6px;
  border-radius: var(--radius-sm);
  background: var(--surface-recessed);
  box-shadow: 0 0 0 var(--ring-width) var(--line);
  font-family: var(--font-mono);
  font-size: var(--text-xs);
  letter-spacing: var(--tracking-normal);
  color: var(--text-default);
}

/* =============================================================================
   24. КОД, ЗНАЧЕНИЯ ДЛЯ КОПИРОВАНИЯ, СТАТУСЫ ПРОВЕРКИ

   Это словарь мастера подключения домена. Человеку показывают DNS-запись,
   он копирует значение к себе в панель регистратора, мы проверяем и
   показываем результат. Отсюда три компонента: блок кода, поле со значением
   и кнопкой копирования, строка статуса.

   Ключи DKIM бывают длиной в 400 знаков и обязаны переноситься где угодно —
   иначе они распирают страницу. Отсюда word-break на значениях.
   ============================================================================= */

.code {
  overflow-x: auto;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-recessed);
  box-shadow: 0 0 0 var(--ring-width) var(--line);
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  line-height: var(--leading-relaxed);
  letter-spacing: var(--tracking-normal);
  color: var(--text-default);
  scrollbar-width: thin;
  scrollbar-color: var(--line) transparent;
  tab-size: 2;
}

.code--wrap {
  overflow-x: visible;
  white-space: pre-wrap;
  word-break: break-all;
}

.record {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: var(--shadow-card);
}

.record__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.record__rows {
  display: grid;
  gap: var(--space-3);
}

.record__row {
  display: grid;
  gap: var(--space-1);
}

@media (min-width: 40rem) {
  .record__row {
    grid-template-columns: 7.5rem minmax(0, 1fr);
    gap: var(--space-2) var(--space-4);
    align-items: center;
  }
}

.record__key {
  font-size: var(--text-xs);
  font-weight: var(--font-weight-medium);
  letter-spacing: var(--tracking-wider);
  text-transform: uppercase;
  color: var(--text-subtle);
}

.copyfield {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h-base);
  padding: var(--space-1) var(--space-1) var(--space-1) var(--control-px-base);
  border-radius: var(--radius-lg);
  background: var(--surface-control);
  box-shadow: 0 0 0 var(--ring-width) var(--line);
}

.copyfield__value {
  flex: 1 1 auto;
  min-width: 0;
  overflow-x: auto;
  white-space: nowrap;
  font-family: var(--font-mono);
  font-size: var(--text-sm);
  letter-spacing: var(--tracking-normal);
  color: var(--text-default);
  scrollbar-width: none;
}

.copyfield__value::-webkit-scrollbar { display: none; }

.copyfield--wrap .copyfield__value {
  white-space: normal;
  word-break: break-all;
  overflow-x: visible;
}

/* Скопировано. Подтверждение живёт секунду и не должно дёргать разметку,
   поэтому меняется только цвет и текст внутри кнопки. */
.copyfield.is-copied {
  box-shadow: 0 0 0 var(--ring-width) var(--success);
}

/* Строка статуса проверки. Цвет плюс слово плюс форма точки — три носителя,
   из которых цвет только один. */
.status {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-medium);
  color: var(--text-subtle);
}

.status__dot {
  flex: none;
  width: 7px;
  height: 7px;
  border-radius: var(--radius-pill);
  background: var(--badge-neutral);
}

.status--ok      { color: var(--text-success); }
.status--ok      .status__dot { background: var(--success); }
.status--pending { color: var(--text-warning); }
.status--pending .status__dot { background: var(--badge-orange); }
.status--fail    { color: var(--text-danger); }
.status--fail    .status__dot { background: var(--badge-red); }
.status--idle    { color: var(--text-subtle); }
.status--idle    .status__dot { background: var(--badge-neutral); }

/* Ожидание — единственный статус, который имеет право пульсировать: он
   означает «мы всё ещё опрашиваем DNS». Под уменьшенной анимацией пульс
   заменяется постоянной полупрозрачностью, а не выключается насухо. */
.status--pending .status__dot {
  animation: mc-pulse 1.6s var(--ease-loop) infinite;
}

@keyframes mc-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

@media (prefers-reduced-motion: reduce) {
  .status--pending .status__dot { animation: none; opacity: 0.55; }
}

/* =============================================================================
   25. ШАГИ МАСТЕРА ПОДКЛЮЧЕНИЯ

   Нумерация считается счётчиком CSS, поэтому вставка шага в середину не
   требует переписывать разметку. Выполненный шаг меняет цифру на галочку —
   опять же, чтобы состояние читалось не только по цвету.
   ============================================================================= */

.steps {
  display: grid;
  gap: var(--space-6);
  counter-reset: mc-step;
}

.step {
  position: relative;
  display: grid;
  grid-template-columns: 28px minmax(0, 1fr);
  gap: var(--space-4);
}

.step::before {
  counter-increment: mc-step;
  content: counter(mc-step);
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--radius-pill);
  background: var(--surface-fill);
  color: var(--text-default);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  font-variant-numeric: tabular-nums;
  line-height: 1;
}

/* Направляющая между кружками. Тянется от низа кружка до начала следующего
   шага, поэтому зависит от зазора .steps. */
.step:not(:last-child)::after {
  content: "";
  position: absolute;
  inset-inline-start: 13.5px;
  inset-block-start: 32px;
  inset-block-end: calc(var(--space-6) * -1 + 4px);
  width: 1px;
  background: var(--line);
}

.step[data-state="current"]::before {
  background: var(--brand);
  color: var(--on-brand);
}

.step[data-state="done"]::before {
  content: "✓";
  background: var(--success);
  /* Не --neutral-0 напрямую: галочка на заливке --success даёт всего 3.67:1,
     и запас там маленький. Токен вынесен отдельно, чтобы менять его в одном
     месте, если заливка когда-нибудь посветлеет. */
  color: var(--on-success);
  font-size: var(--text-base);
}

.step__body {
  display: grid;
  gap: var(--space-3);
  padding-block-end: var(--space-2);
  min-width: 0;
}

.step__title {
  font-size: var(--text-lg);
  font-weight: var(--font-weight-semibold);
  color: var(--text-default);
  line-height: 1.75;   /* 28px при кегле 16px — вровень с центром кружка */
}

.step__desc {
  font-size: var(--text-base);
  line-height: var(--leading-relaxed);
  color: var(--text-subtle);
}

/* =============================================================================
   26. ШАПКА И ПОДВАЛ САЙТА
   ============================================================================= */

/* ЗАМЕР: у шапки дашборда фон — ТОТ ЖЕ холст, что у страницы (oklch(.9875 0 0)),
   без собственной заливки, без тени и без размытия; отделяет её одна волосяная
   линейка. Полупрозрачность с backdrop-filter была нашей добавкой: она даёт
   «стеклянную» шапку, которой в источнике нет, и вдобавок заставляет браузер
   пересобирать слой на каждом кадре прокрутки. Непрозрачный холст с линейкой
   делает ту же работу — отделить шапку от содержимого — и дешевле.

   Заодно ушла ветка @supports: она описывала ровно этот запасной вариант,
   а теперь он единственный. */
.site-header {
  position: sticky;
  inset-block-start: 0;
  z-index: var(--z-header);
  background: var(--surface-canvas);
  border-block-end: var(--border-hair) solid var(--line);
}

.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-6);
  /* ЗАМЕР: шапка панели занимает РОВНО 58px ВМЕСТЕ с нижней линейкой
     (height 58, border-width 0 0 1px). Здесь стояло height: var(--header-height),
     и линейка на .site-header добавлялась сверху — шапка выходила 59px.
     Расхождение видно не только глазом: --header-height используется в
     scroll-padding-top и в inset-block-start липкого рельса мастера, и оба
     промахивались на тот же пиксель. Вычитаем линейку — сумма снова 58. */
  height: calc(var(--header-height) - var(--border-hair));
}

/* --- ЛОГОТИП: ОДИН КОМПОНЕНТ НА ВЕСЬ ПРОДУКТ ------------------------------
   Композиция из docs/brand-logo.md: отдельный знак плюс живой текст. Весь
   lockup одним SVG не запекаем — тогда имя бренда исчезает из разметки, а
   вместе с ним из поиска и из экранного диктора.

   Раньше одно и то же место было описано ТРИЖДЫ и по-разному: витрина
   выравнивала знак по центру и ставила его фиксированными 29 пикселями, блог
   брал 24 и вообще другой, синий знак, панель считала рост от кегля. Один
   компонент, три реализации — и три разных логотипа на одном сайте. Теперь
   определение здесь одно, а контексты задают только размер через --logo-size.

   gap: 0 намеренно. Знак — это буква «M», и расстояние до «a» должно быть
   обычным межбуквенным просветом, а не зазором между иконкой и словом. */
.mailcore-logo {
  display: inline-flex;
  /* baseline, а не center. Знак — буква «M» в слове, и стоять он должен на той
     же строке, что и остальные буквы. При center выравнивались бы СЕРЕДИНЫ
     полей, а не буквы, и знак повисал бы выше текста. */
  align-items: baseline;
  gap: 0;
  /* 22 по умолчанию на всю витрину. Переменная оставлена не для украшения:
     в панели логотип стоит в шапке сайдбара один и там оправданы 28, а здесь
     он делит строку с меню и кнопками. Но менять кегль от страницы к странице
     без причины нельзя — до этой правки на витрине было три разных: 20 на
     онбординге, 22 в блоге и 24 в шапке, и ни одно число ниоткуда не следовало. */
  font-size: var(--logo-size, 22px);
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  letter-spacing: -.025em;
  color: var(--text-strong);
  text-decoration: none;
}

.mailcore-logo:hover { color: var(--text-strong); }

/* Размеры взяты из эталонного lockup дизайнера
   (Logo/source/mailcore-logo-long-on-light-live-text.svg):

     знак   translate(6 18.1) scale(1.15), чернила по вертикали 25.46 … 52.14
     текст  baseline y = 52.5, кегль 36

   То есть низ знака стоит на базовой линии, а высота знака равна высоте
   ПРОПИСНОЙ буквы: (52.14 - 25.46) / 36 = .741em. Отсюда height в em от кегля,
   а не фиксированные пиксели: при фиксированных знак переставал совпадать с
   буквой, как только менялся кегль в соседнем контексте.

   Поле SVG обрезано по чернилам (mailcore-mark.svg, viewBox 2.4 6.4 27.2 23.2),
   иначе равнять было бы нечего: CSS равняет край поля, а глаз видит букву. */
.mailcore-logo__mark {
  height: .741em;
  width: auto;
  flex: none;
  /* Оптический просвет до «a». В эталоне между правым краем чернил (40.04) и
     началом текста (42.5) остаётся 2.46 при кегле 36 — это .068em. */
  margin-inline-end: .068em;
}

/* Точка фирменным цветом, зона — приглушённой: имя читается как «mailcore»
   с уточнением, а не как три равных куска. Оранжевый здесь уместен в полную
   силу: это знак, а не текст, требование контраста 4.5:1 к нему неприменимо. */
.mailcore-logo__dot { color: #F48120; }
.mailcore-logo__tld { color: var(--text-subtle); }

/* --- ВРЕМЕННО: старая разметка логотипа в ещё не перепубликованном блоге ---
   УДАЛИТЬ вместе с write_legacy_alias() в scripts/build-favicons.py.

   Блог и кейсы отдаются не из выкладки сайта, а из отдельного издательского
   релиза (маршрут @editorial в Caddyfile, content-api/README.md), поэтому их
   разметка обновится только при следующей публикации. Пока там остаётся
   прежний .site-logo, а этот файл уже новый — и без правил ниже логотип на
   46 страницах превращается в неоформленную синюю ссылку.

   Знак в шапке намеренно скрыт. В старой разметке рядом с ним стоит ПОЛНОЕ
   «mailcore.uz», а знак теперь буква «M» — вышло бы «Mmailcore.uz», две M
   подряд, прямой запрет из docs/brand-logo.md. Поменять разметку отсюда
   нельзя, поэтому из двух зол выбрано слово: имя бренда важнее знака, и
   именно слово читают и человек, и поисковик. В значке вкладки на тех же
   страницах знак остаётся — там он один и никого не дублирует. */
.site-logo {
  display: inline-flex;
  align-items: baseline;
  gap: 0;
  font-size: 22px;
  font-weight: var(--font-weight-semibold);
  line-height: 1;
  letter-spacing: -.025em;
  color: var(--text-strong);
  text-decoration: none;
}

.site-logo:hover { color: var(--text-strong); }
.site-logo__asset { display: none; }
.site-logo__word .text-subtle { color: var(--text-subtle); }

.site-header .mailcore-logo { flex: none; }

.site-nav {
  display: none;
  align-items: center;
  gap: var(--space-1);
}

/* Порог 64rem, а не 48rem. ЗАМЕР: строка шапки целиком — знак 117px, меню
   521px, кнопки 244px и два зазора по 24px — это 930px плюс поля контейнера.
   На 48rem меню включалось, строка требовала 946px при доступных 736px, и
   витрина ехала горизонтально НА ВСЕЙ полосе 768…944px. Проверено разгоном
   ширины окна с шагом 2px: 89 значений подряд с горизонтальной прокруткой.
   64rem — уже существующий в файле порог (на нём же .container переходит на
   поля 32px), а не новое число; на 1024px строка занимает 930 из 960. Ниже
   порога меню не показывается, как и раньше на узких экранах: якорные ссылки
   остаются доступны прокруткой, а шапка держит знак и кнопку действия. */
@media (min-width: 64rem) {
  .site-nav { display: flex; }
}

.site-nav__link {
  display: inline-flex;
  align-items: center;
  height: var(--control-h-base);
  padding-inline: var(--space-3);
  border-radius: var(--radius-lg);
  font-size: var(--text-base);
  font-weight: var(--font-weight-medium);
  color: var(--text-subtle);
  text-decoration: none;
  transition: color var(--duration-base) var(--ease-out),
              background-color var(--duration-base) var(--ease-out);
}

.site-nav__link:hover {
  color: var(--text-default);
  background: var(--surface-tint);
}

.site-nav__link[aria-current="page"] {
  color: var(--text-default);
  font-weight: var(--font-weight-semibold);
}

.site-header__actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-inline-start: auto;
}

.site-footer {
  padding-block: var(--space-12);
  background: var(--surface-canvas);
  border-block-start: var(--border-hair) solid var(--line);
}

.site-footer__cols {
  display: grid;
  gap: var(--space-8);
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}

.site-footer__title {
  margin-block-end: var(--space-2);
  font-size: var(--text-sm);
  font-weight: var(--font-weight-semibold);
  color: var(--text-default);
}

/* 36px высоты — не для красоты, а чтобы ссылка подвала оставалась целью
   нажатия нормального размера на телефоне. */
.site-footer__link {
  display: flex;
  align-items: center;
  min-height: var(--control-h-base);
  font-size: var(--text-sm);
  color: var(--text-subtle);
  text-decoration: none;
}

.site-footer__link:hover {
  color: var(--text-default);
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.site-footer__legal {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-4);
  margin-block-start: var(--space-10);
  padding-block-start: var(--space-6);
  border-block-start: var(--border-hair) solid var(--line);
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

/* =============================================================================
   27. ПОЯВЛЕНИЕ ПРИ ПРОКРУТКЕ

   Прозрачность плюс сдвиг 20px, по желанию плюс размытие 12px. Шаг задержки
   между соседями — 33ms. Ничего больше: ни параллакса, ни привязки к
   прокрутке, ни перехвата колеса.

   Начальное состояние включается ТОЛЬКО когда скрипт поставил
   data-reveal-root на <html>. Без скрипта — и, что важнее, при его падении —
   содержимое просто видно. Прятать текст в CSS и надеяться на JS нельзя.
   ============================================================================= */

[data-reveal-root] .reveal {
  opacity: 0;
  translate: 0 var(--reveal-slide);
  transition: opacity var(--duration-reveal) var(--ease-out) var(--reveal-delay, 0ms),
              translate var(--duration-reveal) var(--ease-out) var(--reveal-delay, 0ms),
              filter var(--duration-reveal) var(--ease-out) var(--reveal-delay, 0ms);
}

[data-reveal-root] .reveal--blur { filter: blur(var(--reveal-blur)); }

[data-reveal-root] .reveal.is-visible {
  opacity: 1;
  translate: 0 0;
  filter: blur(0);
}

.stagger { --mc-step: var(--stagger-normal); }
.stagger--snappy  { --mc-step: var(--stagger-snappy); }
.stagger--relaxed { --mc-step: var(--stagger-relaxed); }

.stagger > *:nth-child(1) { --reveal-delay: 0ms; }
.stagger > *:nth-child(2) { --reveal-delay: calc(var(--mc-step) * 1); }
.stagger > *:nth-child(3) { --reveal-delay: calc(var(--mc-step) * 2); }
.stagger > *:nth-child(4) { --reveal-delay: calc(var(--mc-step) * 3); }
.stagger > *:nth-child(5) { --reveal-delay: calc(var(--mc-step) * 4); }
.stagger > *:nth-child(6) { --reveal-delay: calc(var(--mc-step) * 5); }
.stagger > *:nth-child(7) { --reveal-delay: calc(var(--mc-step) * 6); }
.stagger > *:nth-child(8) { --reveal-delay: calc(var(--mc-step) * 7); }

/* =============================================================================
   28. ПАНЕЛЬ ВЛАДЕЛЬЦА

   Третья поверхность продукта, и единственная, у которой читатель ровно один —
   владелец сервиса. Он открывает её не читать, а проверить: всё ли цело.
   Отсюда весь словарь ниже — он про ПОКАЗАНИЯ, а не про рассказ, и потому
   ничего маркетингового здесь нет: ни первого экрана, ни крупного кегля, ни
   вертикального ритма в 96px.

   ГЛАВНОЕ ПРАВИЛО — ТИШИНА В НОРМЕ. Плитка состояния, у которой всё хорошо,
   не красится вообще: белая панель, серый значок, обычный текст. Цвет
   появляется ТОЛЬКО когда есть о чём говорить. Ряд из пяти зелёных плиток
   кричит ничуть не тише ряда из пяти красных, и через неделю его перестают
   читать вместе с настоящей тревогой.

   Цвет и здесь не единственный носитель: у встревоженной плитки есть значок и
   слово, у шкалы — подпись с числами и засечка порога, у бейджа — текст.
   ============================================================================= */

/* Полотно панели. Потолок ширины — «широкая страница» источника, 1600px:
   дальше плотная таблица растягивается и глаз теряет строку между столбцами.
   Шаг между разделами 32px — вдвое меньше самого тесного маркетингового
   (.section--tight, 40/48px): на панели разделы читаются подряд, а не по
   одному на экран. */
.dash {
  display: flex;
  flex-direction: column;
  gap: var(--space-8);
  max-width: var(--container-page-wide);
  margin-inline: auto;
}

/* Шапка приложения липкая, поэтому переход по якорю без этой строки прячет
   заголовок раздела ровно под неё. */
.dash-section {
  scroll-margin-block-start: calc(var(--header-height) + var(--space-4));
}

.dash-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--space-2) var(--space-3);
  margin-block-end: var(--space-3);
}

/* УТОЧНЕНИЕ ПРИ ЗАГОЛОВКЕ РАЗДЕЛА: за какой срок посчитано, по чему считано,
   сколько всего. Раньше это была пилюля-бейдж рядом с заголовком — и это
   оказалось штампом, а не находкой: в источнике у заголовков НЕТ соседей-
   бейджей вообще (замер: у всех h1/h2/h3 обеих страниц соседние узлы идут с
   border-radius 0 и прозрачным фоном). Бейдж там означает СОСТОЯНИЕ вещи
   («песочница», «продакшен»), а не примечание к заголовку, и раздавать его
   всем подряд — значит стереть эту разницу.

   Простой приглушённый текст, вставший на базовую линию заголовка
   (у .dash-head align-items: baseline). Ступень темнее приглушённой: лежит
   прямо на холсте, где обычная --text-subtle не добирает до 4.5:1. */
.dash-qual {
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--text-subtle-strong);
}

/* Строка под заголовком раздела: зачем на это вообще смотреть. Ширина держится
   по строке текста, а не по разделу — иначе на 1600px получается газета.
   Ступень темнее приглушённой: лежит прямо на холсте, где обычная --text-subtle
   не добирает до 4.5:1 (посчитано, см. раздел 2). */
.dash-note {
  max-width: var(--measure-prose);
  font-size: var(--text-sm);
  line-height: var(--leading-snug);
  color: var(--text-subtle-strong);
}

/* -----------------------------------------------------------------------------
   ПЛИТКИ СОСТОЯНИЯ

   Имя длинное намеренно. Короткое .tile уже ЗАНЯТО: витрина объявляет его у
   себя в <style> под плашку 32×32 с иконкой возможности. Общий слой грузится
   раньше страничного, так что перебить его страница перебьёт, — но только те
   свойства, которые называет сама. Заливка, кольцо и внутренние отступы отсюда
   протекли бы в её плашку и раздули бы иконку. Общий слой обязан уступать
   странице в имени, а не наоборот.
   -------------------------------------------------------------------------- */

.status-tiles {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(var(--status-tile-min, 12.5rem), 1fr));
}

.status-tile {
  display: grid;
  gap: var(--space-1);
  align-content: start;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-lg);
  background: var(--surface-base);
  box-shadow: var(--shadow-card);
}

.status-tile__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 20px;
}

.status-tile__icon { flex: none; color: var(--text-placeholder); }

.status-tile__label {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.status-tile__value {
  font-size: var(--text-xl);   /* 20px — ступень h3, но это число, а не заголовок */
  font-weight: var(--font-weight-semibold);
  line-height: 1.25;
  font-variant-numeric: tabular-nums;
  color: var(--text-strong);
}

.status-tile__note {
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--text-subtle);
}

/* ПЛИТКА НЕ КРАСИТСЯ ЦЕЛИКОМ, И ЭТО ЗАМЕР, А НЕ ВКУС.

   Сначала про полосу слева: сохранённый дашборд Cloudflare прошёл поэлементно,
   обе страницы целиком, — блоков с толстой рамкой на ОДНОЙ стороне там ноль,
   ни рамкой, ни внутренней тенью, ни псевдоэлементом. Ноль на весь DOM.
   Цветная полоса вдоль края была нашей добавкой поверх источника, и именно её
   owner опознал первой.

   ПОДКРАШЕННАЯ ЗАЛИВКА — ТА ЖЕ ОШИБКА, ПРОСТО РАЗМАЗАННАЯ ПО ВСЕЙ ПЛИТКЕ, и
   снята она теперь по трём измеренным причинам.

   Первая: заливка в сером не работает, и прошлый замер это прямо показывал.
   Яркость после grayscale(1): спокойная 255, внимание 246, авария 246. От
   спокойной обе отличаются на 9 из 255, а ДРУГ ОТ ДРУГА — на ноль. Носителем
   уровня фон не был никогда; он только выглядел носителем.

   Вторая: цвет переставал различать ровно тогда, когда он нужнее всего. Пять
   плиток на «Обзоре»; когда горит всё пять, крашеными оказываются все пять, и
   страница превращается в одно розовое поле, на котором ничто не выделяется.
   Различие обязано расти вместе с бедой, а не исчезать.

   Третья: --banner-warning в этом файле помечен «НАШЕ» — парного токена в
   kumo нет. То есть плитка красилась цветом, которого в источнике не
   существует, — при уговоре брать цвета kumo один в один.

   Что осталось носить тяжесть — то же, что носит её в источнике: цвет лежит
   НА ЗНАЧЕНИИ, а не под ним. У Cloudflare ровно так и сделано: карточка белая,
   число чёрное, а подкрашена дельта рядом с ним, и рядом с дельтой стоит
   стрелка. Три носителя разом, ни один из них не единственный:
     значок — есть или нет (спокойная плитка без значка вовсе), и если есть,
              то треугольник у внимания против круга у аварии: разные силуэты;
     слово  — «Требует внимания.» / «Тревога.» уходит диктору в подпись, а
              глазами читается в самом значении («песочница», «истёк»);
     цвет   — на значении и на значке, где он приходится на 20px/600 и на
              обводку 1.5, то есть там, где его видно.

   Геометрия у всех трёх состояний одна, меняется только цвет значения,
   поэтому ряд не дёргается, когда состояние меняется на живой странице. */

/* ЗАМЕР, А НЕ ВКУС. Под значением теперь белая плашка карточки, а не тонировка,
   и запас по контрасту вырос сам: orange-800 на белом даёт 7.35:1 (на жёлтой
   подложке было 3.89:1 у более светлой ступени), red-700 — 6.42:1. Обе выше
   4.5 с запасом, и обе — кумовские токены без подмен. Значок красится тем же
   цветом: разнотон в одной строке заметнее, чем выигранные полтона
   насыщенности, а порог для него всё равно ниже (3:1 у нечитаемой графики). */
.status-tile--attention .status-tile__icon,
.status-tile--attention .status-tile__value { color: var(--text-badge-orange-subtle); }

.status-tile--alarm .status-tile__icon,
.status-tile--alarm .status-tile__value { color: var(--text-danger); }

/* -----------------------------------------------------------------------------
   ШКАЛА

   Одна конструкция на четыре разных измерения: диск, квота ящика, суточный
   лимит SES, доли отказов и жалоб. Общее у них то, что голое число прочитать
   нельзя: «0,4%» само по себе не говорит, близко это к беде или далеко.
   Поэтому у шкалы всегда есть подпись с числами, а там, где предел назначен
   не нами (Amazon останавливает аккаунт на 5% отказов и 0,1% жалоб), — ещё и
   засечка порога.
   -------------------------------------------------------------------------- */

.meter { display: grid; gap: 6px; }

/* Обёртка нужна ради засечки: у дорожки overflow:hidden ради скруглённых
   концов заливки, и засечка внутри неё срезалась бы по краям. */
.meter__gauge { position: relative; }

.meter__track {
  display: flex;
  height: 8px;
  overflow: hidden;
  border-radius: var(--radius-pill);
  background: var(--surface-fill);
}

/* ЦВЕТ ЗАЛИВКИ МЕРЯЕТСЯ С ДОРОЖКОЙ, А НЕ С ХОЛСТОМ, и это меняет выбор
   ступеней. Дорожка --surface-fill (#E5E5E5) сама светлая, поэтому «яркие»
   ступени рядом с ней проваливаются ниже порога 3:1 для нечитаемой графики:
   emerald-600 #009966 — 2.90:1, orange #FA8900 — всего 1.94:1. Заливка — не
   украшение: её длина и есть измеренное число, и не увидеть её край значит не
   прочитать шкалу. Поэтому взяты ступени темнее, все — из того же набора
   кумовских токенов:
     обычная  emerald-800 #006045 — 6.10:1
     фирменн. #056DFF          — 3.59:1 (проходит, оставлен как есть)
     тревога  orange   #BD6500 — 3.32:1
     авария   red-500  #FB2C36 — 3.02:1 (проходит впритык, оставлен) */
.meter__fill {
  height: 100%;
  background: var(--text-success);
  transition: width var(--duration-slow) var(--ease-out);
}

.meter--brand .meter__fill { background: var(--brand); }
.meter--warn  .meter__fill { background: var(--text-warning); }
.meter--alarm .meter__fill { background: var(--danger); }

/* Показывать нечего: за период не ушло ни одного письма, ящик пуст, квота не
   задана. Серая полоса честнее зелёной — зелёная означала бы «проверено и
   хорошо», а проверять было нечего. */
.meter--empty .meter__fill { background: var(--surface-interact); }

/* Первый сегмент составной дорожки — то, ради чего эта панель существует
   (почта на диске). Отдельный класс, а НЕ инлайновый background: инлайновая
   сокращённая запись сбрасывает background-image, и штриховка устаревшего
   снимка на такой сегмент уже не ложится — единственная полоса на странице
   оставалась бы «уверенной», когда все остальные помечены сомнением. */
.meter__fill--brand { background: var(--brand); }

/* Второй сегмент составной дорожки (система рядом с почтой на диске).
   Ступень 500, а не 400: замер на дорожке --surface-fill (#E5E5E5) даёт у
   neutral-400 всего 2.05:1 — на диске, занятом системой на девять десятых,
   граница «занято/свободно» просто пропадает. neutral-500 (#737373) — 3.76:1
   и та же нейтральная роль. */
.meter__fill--system { background: var(--neutral-500); }

.meter__limit {
  position: absolute;
  inset-block: -3px;
  width: 2px;
  border-radius: 1px;
  background: var(--text-strong);
  translate: -1px 0;   /* засечка стоит серединой на своём проценте */
}

.meter__caption {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  font-variant-numeric: tabular-nums;
  color: var(--text-subtle);
}

.meter--sm .meter__track   { height: 6px; }
.meter--sm .meter__caption { font-size: var(--text-2xs); }

.legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-4);
  font-size: var(--text-xs);
  color: var(--text-subtle);
}

.legend__item {
  display: inline-flex;
  align-items: center;
  gap: 6px;
}

.legend__swatch {
  width: 10px;
  height: 10px;
  flex: none;
  border-radius: 3px;
  background: var(--surface-fill);   /* непокрашенный остаток дорожки */
}

/* Образцы легенды повторяют сегменты составной дорожки и обязаны браться из
   тех же строк, что и они: разъехавшись на одну ступень серого, легенда
   начинает объяснять не ту полосу. */
.legend__swatch--brand  { background: var(--brand); }
.legend__swatch--system { background: var(--neutral-500); }

/* -----------------------------------------------------------------------------
   ФАКТЫ

   Пара «подпись — значение» списком. От .record__row из мастера отличается
   назначением: там ключ DNS-записи капслоком и значение для копирования,
   здесь обычная строка отчёта. Разметка — настоящий <dl> с <div> вокруг пары
   (это валидно с HTML5), чтобы линейка шла по паре, а не по каждому элементу.
   -------------------------------------------------------------------------- */

.facts { display: grid; }

.facts__row {
  display: grid;
  gap: 2px;
  padding-block: var(--space-2);
  border-block-end: var(--border-hair) solid var(--hairline);
}

.facts__row:last-child { border-block-end: 0; }

@media (min-width: 40rem) {
  .facts__row {
    grid-template-columns: 13rem minmax(0, 1fr);
    gap: var(--space-2) var(--space-4);
    align-items: baseline;
  }
}

.facts__key {
  font-size: var(--text-sm);
  color: var(--text-subtle);
}

.facts__val {
  min-width: 0;
  font-size: var(--text-base);
  font-variant-numeric: tabular-nums;
  color: var(--text-default);
}

/* -----------------------------------------------------------------------------
   РАСКРЫТИЕ СТРОКИ ТАБЛИЦЫ

   Строка домена разворачивается в список ящиков. Переключатель — настоящая
   <button> с aria-expanded, а не строка с onclick: иначе с клавиатуры до
   раскрытия не добраться, а диктор не скажет, открыто оно или закрыто.
   -------------------------------------------------------------------------- */

.disclosure {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  min-height: var(--control-h-base);
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-weight: var(--font-weight-medium);
  letter-spacing: inherit;
  text-align: start;
  color: var(--text-default);
  cursor: pointer;
  border-radius: var(--radius-sm);
}

.disclosure:hover { color: var(--text-strong); }

.disclosure__caret {
  flex: none;
  color: var(--text-subtle);
  transition: rotate var(--duration-base) var(--ease-disclosure);
}

.disclosure[aria-expanded="true"] .disclosure__caret { rotate: 90deg; }

/* Раскрытая строка и её продолжение читаются как одна плитка. */
.table tbody tr.is-open { background: var(--surface-tint); }

/* Высота сбрасывается явно: .table td прибит к 40px, а на пальцевом вводе к
   47px, и продолжение строки унаследовало бы фиксированную высоту вместо
   своей. Селектор на класс выше по специфичности обоих. */
.table .subrow > td {
  height: auto;
  padding: 0;
  background: var(--surface-elevated);
}

.subpanel {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-4);
}

/* Вложенная таблица — часть строки, а не отдельный список. Её шапка не имеет
   права липнуть к верху окна: прокрутка утащила бы заголовки ящиков одного
   домена через весь список остальных. */
.subpanel .table th { position: static; }

/* Таблица внутри уже приподнятой поверхности: волосяная линия вместо кольца
   с тенью, иначе получается лестница из панелей. */
.table-wrap--flat {
  box-shadow: 0 0 0 var(--ring-width) var(--hairline);
}

/* -----------------------------------------------------------------------------
   УСТАРЕВШИЙ СНИМОК

   panel/data.json пишет крон раз в пять минут. Если сбор упал, встал крон или
   отвалилась сеть, страница продолжит показывать последние удачные числа — и
   обязана сказать, что они не сегодняшние. Показывать снятое час назад как
   текущее хуже, чем не показывать ничего: владелец примет решение по цифре,
   которой уже нет.

   ЧЕГО ЗДЕСЬ НЕТ И ПОЧЕМУ. Нет прозрачности на числах: приглушённое число
   читается хуже, а оно всё ещё единственное, что у владельца есть. Нет серого
   фильтра на всю страницу: он снял бы и те показания, которые остаются
   верными, — диск, забитый час назад, забит и сейчас. Нет скрытия данных:
   страница, которая прячет числа, отправляет владельца на сервер по ssh.

   Есть три носителя, и цвет среди них только один: СЛОВО в отметке времени,
   ВЕС этой отметки и ШТРИХОВКА на заливке шкалы — единственной графике,
   которая иначе продолжала бы выглядеть живым замером. Сообщение сверху
   берётся штатное, .banner--warning из раздела 18.
   -------------------------------------------------------------------------- */

/* Отметка времени снимка: «данные от 20:45, три минуты назад». Не .badge —
   бейдж это ярлык постоянного содержания, а эта строка перерисовывается
   каждую минуту. Табличные цифры держат её от дёрганья при перерисовке.
   Ступень темнее приглушённой: отметка лежит прямо на холсте, где обычная
   --text-subtle не добирает до 4.5:1 (посчитано в разделе 2). */
.stamp {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1-5);
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  font-variant-numeric: tabular-nums;
  color: var(--text-subtle-strong);
}

/* Работают оба входа: атрибут на корне панели, если устарел весь снимок, и
   класс на самой отметке, если скрипту удобнее пометить одну строку.
   Цвет не --text-warning: на 12px он даёт 4.18:1, а по правилу этого файла
   предупреждающий текст мельче 14px берёт ступень темнее — раздел 18 прямо
   называет её, --text-badge-orange-subtle. */
[data-stale="true"] .stamp,
.stamp--stale {
  color: var(--text-badge-orange-subtle);   /* #9F2D00, 7.4:1 на холсте */
  font-weight: var(--font-weight-medium);
}

/* Третье состояние той же отметки: снимка нет вообще или он не разобрался.
   Оно не «старее» устаревшего, оно другого рода, и оранжевым его красить
   нельзя — устаревшие числа всё-таки числа, а тут их нет. Красный идёт
   ступенью --text-danger: 6.42:1 на холсте на 12px.

   Селектор из ДВУХ классов намеренно. Правило выше — `[data-stale] .stamp` —
   весит (0,2,0), и одиночный `.stamp--error` (0,1,0) проиграл бы ему при любом
   порядке в файле. Удвоенный класс уравнивает вес, а порядок решает остальное. */
.stamp.stamp--error {
  color: var(--text-danger);
  font-weight: var(--font-weight-medium);
}

/* Точка состояния внутри отметки. Тот же приём, что у .badge--dot, но без
   подложки бейджа: отметка живёт прямо в шапке на холсте. По умолчанию точка
   того же цвета, что и слова, — цвет уже несёт состояние, и второй оттенок
   был бы лишним. Исключение одно: спокойное состояние словами не окрашено
   (серый — это «норма»), и зелёная точка возвращает ему знак. */
.stamp__dot {
  width: 6px;
  height: 6px;
  flex: none;
  border-radius: var(--radius-pill);
  background: currentColor;
}

.stamp--ok .stamp__dot { background: var(--success); }

/* Штриховка ложится ПОВЕРХ цветной заливки (background-image рисуется над
   background-color), поэтому одно значение работает на всех вариантах шкалы:
   зелёном, фирменном, оранжевом, красном и сером. Заштрихованная полоса —
   устоявшийся знак «оценка, а не измерение»; здесь она означает ровно это.
   Длина заливки при этом не меняется: от того, что снимок устарел, число не
   стало другим — другой стала уверенность в нём. */
[data-stale="true"] .meter__fill,
[data-stale="true"] .meter__fill--system {
  background-image: var(--hatch-stale);
}

/* Засечка порога (.meter__limit) штриховки НЕ получает и остаётся сплошной:
   предел Amazon в 5% и 0,1% назначен не нами и вместе со снимком не стареет. */

/* -----------------------------------------------------------------------------
   ПЛОТНАЯ ТАБЛИЦА: ПРЕДСКАЗУЕМЫЕ КОЛОНКИ И ДВУХСТРОЧНАЯ ЯЧЕЙКА

   Плотный режим у таблицы уже есть — .table--compact из раздела 15: строка
   35px, шапка 12px на приподнятой подложке. Второго такого здесь нет и не
   нужно. Не хватало другого — того, что появляется, когда в таблицу приходят
   не подписанные руками строки, а данные с сервера.
   -------------------------------------------------------------------------- */

/* Ширина колонок перестаёт зависеть от содержимого. Без этого одна длинная
   строка — адрес ящика, «Up About an hour (healthy)», список пересылок —
   растягивает свою колонку и сжимает остальные, и таблица меняет вид при
   каждом обновлении снимка, хотя данные почти те же. Ширины назначаются на
   <col> или на первом <th>, ячейки обрезаются многоточием. */
.table--fixed { table-layout: fixed; }

/* Только СОБСТВЕННЫЕ ячейки. Дочерние комбинаторы не дают правилу протечь во
   вложенную таблицу ящиков внутри .subpanel: у неё свои колонки, и nowrap
   обрезал бы там адреса пересылки. */
.table--fixed > thead > tr > th,
.table--fixed > tbody > tr > th,
.table--fixed > tbody > tr > td {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Кольцо :focus-visible рисуется тенью и выходит за границу элемента, а
   overflow: hidden срезал бы его — и ровно в той ячейке, где стоит
   .disclosure, с клавиатуры стало бы не видно, где ты находишься.
   overflow: clip обрезает содержимое так же, но принимает поле, и кольцу
   хватает его двух пикселей. Браузер, не знающий overflow-clip-margin,
   ведёт себя как при hidden — то есть как вёл до этой строки. */
.table--fixed > tbody > tr > td:has(:is(a, button, input, select, [tabindex])) {
  overflow: clip;
  overflow-clip-margin: var(--ring-width-strong);
}

/* Продолжение раскрытой строки живёт по содержимому и переносится: обрезать
   там нечего, а многоточие съело бы вложенную таблицу. */
.table--fixed > tbody > tr.subrow > td {
  overflow: visible;
  white-space: normal;
}

/* Двухстрочная ячейка: адрес и под ним пересылка, домен и под ним число
   ящиков, контейнер и под ним его статус. Зазор 2px, а не с сетки в 4px: на
   кеглях 14 и 12 четыре пикселя разваливают пару на две независимые строки, и
   таблица перестаёт читаться построчно. */
.cell {
  display: grid;
  gap: 2px;
  min-width: 0;
}

.cell__main,
.cell__sub {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.cell__main { color: var(--text-default); }

.cell__sub {
  font-size: var(--text-xs);
  line-height: var(--leading-snug);
  color: var(--text-subtle);
}

/* Та же подмена ступени, что раздел 15 делает для .text-subtle: на полосатой,
   наведённой, выбранной и раскрытой строке фон уже не белый, и 4.73:1
   превращается в 4.34:1. Приподнятая подложка .subpanel — тот же случай. */
.table--zebra tbody tr:nth-child(even) .cell__sub,
.table tbody tr:hover .cell__sub,
.table tbody tr.is-open .cell__sub,
.table tbody tr[aria-selected="true"] .cell__sub,
.subpanel .cell__sub { color: var(--text-subtle-strong); }

/* =============================================================================
   29. УМЕНЬШЕННАЯ АНИМАЦИЯ И ПЕЧАТЬ

   Токены уже зажали все --duration-* до 0.01ms. Этот блок ловит то, что
   написано числами мимо токенов, — чужой CSS, инлайновые стили, сторонние
   виджеты.

   0.01ms, а не animation: none. Разница принципиальная: с нулевой, но
   ненулевой длительностью режимы заполнения forwards и both всё равно
   доезжают до конечного кадра, и элемент не остаётся в стартовом состоянии.
   ============================================================================= */

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  *,
  *::before,
  *::after {
    scroll-behavior: auto !important;
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
  }

  [data-reveal-root] .reveal {
    opacity: 1;
    translate: none;
    filter: none;
  }
}

@media print {
  /* Печатают обычно страницу с DNS-записями — чтобы отнести администратору.
     Значит из неё надо убрать всё, что на бумаге бессмысленно, и убедиться,
     что длинные ключи не обрезаются по правому краю. */
  .site-header,
  .site-footer,
  .nav,
  .app__sidebar,
  .app__topbar,
  .toast-viewport,
  .skip-link,
  .row-actions { display: none !important; }

  body {
    background: #FFFFFF;
    color: #000000;
  }

  .card,
  .status-tile,
  .record,
  .copyfield,
  .code {
    box-shadow: none;
    border: 1px solid #999999;
    break-inside: avoid;
  }

  .code,
  .copyfield__value {
    white-space: pre-wrap;
    word-break: break-all;
    overflow: visible;
  }

  a[href^="http"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #555555;
  }
}
