/* dzin.pro Dark — дизайн-система (токены + компонентный слой).
 *
 * Источник: design-system/styles.css репозитория (см. .design-sync/conventions.md).
 * Файл, а не инлайн в шаблоне: 41 КБ, и следующая страница редизайна должна
 * подключать ЭТУ копию, а не заводить третью. Подключается только там, где нужен —
 * гейт dzin_is_razbor_page() в functions.php, чтобы не грузить систему на страницах
 * старого дизайна.
 *
 * Правки сюда не вносить: это копия. Меняется design-system/styles.css, потом
 * переносится сюда целиком.
 */
/* dzin.pro Dark — единственная таблица стилей дизайн-системы.
   Токены + компонентный слой. Источник значений — DESIGN.md и BRAND.md
   репозитория dzin-bot; расхождения с текущим продом сюда НЕ переносятся,
   они живут долгом в DESIGN.md и .planning/KANBAN.md.

   Правило номер один: никогда не хардкодить hex, имя шрифта или px, который
   уже несёт токен. Меняется система здесь, а не на странице.

   Правило номер два: компонентный класс объявляет свойство ПОЛНОСТЬЮ, а не
   дельтой к чужому дефолту. Система живёт внутри WordPress, где родительская
   тема стилизует голый button/input по типу элемента. Класс выигрывает
   только по тем свойствам, которые сам объявил: именно так кнопка оплаты
   получила чужой радиус 3px, объявив один только background. */

/* WordPress-копия не тянет Google Fonts: те же четыре subset-файла Manrope
   уже лежат в теме и используются статьями. Диапазон веса 400–800 сохраняет
   контракт исходной дизайн-системы без внешнего запроса и без FOUT из-за 404. */
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/manrope/manrope-cyrillic-ext.woff2') format('woff2');
  unicode-range: U+0460-052F, U+1C80-1C8A, U+20B4, U+2DE0-2DFF, U+A640-A69F, U+FE2E-FE2F;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/manrope/manrope-cyrillic.woff2') format('woff2');
  unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/manrope/manrope-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Manrope';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('../fonts/manrope/manrope-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* ── Грунты ──────────────────────────────────────────────────────────
     Три слоя фона расслаивают страницу. Базовый — под всем, панель — под
     выделенной секцией, подвал — под футером. Больше грунтов не заводить:
     четвёртый оттенок синего читается как ошибка сборки, а не как уровень. */
  --dh-bg: #0a2540;
  --dh-bg-panel: #0c2d57;
  --dh-bg-footer: #06192e;

  /* ── Лестница текста ─────────────────────────────────────────────────
     Четыре ступени, монотонные и проходящие AA на всех трёх грунтах
     (измерено: 15.54 / 11.57 / 7.30 / 5.36 на базовом фоне). Пятой ступени
     нет намеренно — ниже quaternary начинается зона, где текст перестаёт
     читаться на панели, а разница со ступенью выше уже неразличима. */
  --dh-text: #ffffff;
  --dh-text-2: #cfe0f5;
  --dh-text-3: #9db4d0;
  --dh-text-4: #829ab6;

  /* ── Роли ────────────────────────────────────────────────────────────
     blue — синий бренда: ссылки, номера шагов, акцентные секции.
     cta  — единственный цвет кликабельного призыва.
     glyph — цвет ЗНАКА и декоративных глифов. Фоном интерактивного
     элемента запрещён: с белым даёт 2.70:1 и не проходит AA. Выпилить его
     нельзя — все три файла логотипа построены на нём (BRAND.md §2). */
  --dh-blue: var(--dh-blue-500);
  --dh-cta: var(--dh-cta-500);
  --dh-glyph: #F57C1A;
  --dh-glyph-lt: #ffb877;

  /* Текст поверх залитой роли. Канон называет их отдельными ролями
     (on-primary / on-tertiary), поэтому и здесь они токены, а не белый
     по месту: заливка роли может потемнеть, и тогда меняется одно место. */
  --dh-on-cta: #ffffff;
  --dh-on-blue: #ffffff;

  /* Светлая подложка. Нужна там, где тёмная система граничит со светлым
     носителем: локап для белого фона, печать, вложение в письмо. Не
     использовать как фон интерфейсной поверхности — система тёмная. */
  --dh-paper: #ffffff;

  --dh-success: #4fd08a;  /* 7.93:1 на базовом фоне */
  --dh-error: #ff9090;    /* 7.14:1 на базовом фоне */

  /* ── Рампы интерактивных ролей ───────────────────────────────────────
     Ступени только у синего и оранжевого CTA — у ролей, которым нужны
     hover, active и приглушённые заливки. Остальные роли плоские.
     Сгенерировано в OKLCH на общей лестнице светлоты:
     design-system/tools/build-ramps.mjs. Правя базовый цвет, ПЕРЕСЧИТАТЬ
     скриптом, а не подгонять ступени руками.

     Контраст ступеней на базовом фоне #0a2540 указан у каждой строки.
     Практическое следствие: -500 обеих ролей ниже 4.5:1, то есть годится
     под заливку и крупный текст, но НЕ под абзац. Для ссылки в теле текста
     брать -300 (6.77:1 у синего), а не базовый цвет роли. */
  --dh-blue-100: #d4e3fd;  /* 11.99:1 */
  --dh-blue-200: #abc8f7;  /*  9.13:1 */
  --dh-blue-300: #7facf7;  /*  6.77:1 — минимальная ступень для текста абзаца */
  --dh-blue-400: #4f8efa;  /*  4.87:1 */
  --dh-blue-500: #1a6ef5;  /*  3.40:1 — канон бренда; с белым 4.57:1 */
  --dh-blue-600: #0055cf;  /*  2.37:1 — hover заливки; с белым 6.56:1 */
  --dh-blue-700: #0040a0;  /*  1.66:1 — active заливки; с белым 9.37:1 */
  --dh-blue-800: #002c74;  /*  1.19:1 — приглушённая заливка */
  --dh-blue-900: #00194a;  /*  1.09:1 — подложка секции */

  --dh-cta-100: #f0dcd1;  /* 11.74:1 */
  --dh-cta-200: #e0bca8;  /*  8.83:1 */
  --dh-cta-300: #d49c7b;  /*  6.54:1 — CTA как ТЕКСТ берётся отсюда */
  --dh-cta-400: #c87948;  /*  4.65:1 */
  --dh-cta-500: #b85700;  /*  3.26:1 — канон CTA; с белым 4.77:1 */
  --dh-cta-600: #944400;  /*  2.28:1 — hover заливки; с белым 6.80:1 */
  --dh-cta-700: #713300;  /*  1.61:1 — active заливки; с белым 9.65:1 */
  --dh-cta-800: #502200;  /*  1.16:1 */
  --dh-cta-900: #311200;  /*  1.11:1 */

  /* ── Поверхности ─────────────────────────────────────────────────────
     Карточка — полупрозрачная, а не собственный hex: так она одинаково
     ложится на все три грунта. Композитинг проверен: поверх базового даёт
     #142e48 и 13.92:1 с белым текстом. Линтер, читающий rgba как
     непрозрачный цвет, покажет здесь 1.00:1 — это ограничение линтера. */
  --dh-surface: rgba(255, 255, 255, 0.04);
  --dh-surface-strong: rgba(255, 255, 255, 0.07);
  --dh-border: rgba(255, 255, 255, 0.10);
  --dh-border-strong: rgba(255, 255, 255, 0.25);

  /* ── Фокус ───────────────────────────────────────────────────────────
     outline-offset здесь не косметика, а условие видимости. Кольцо поверх
     CTA-заливки даёт 1.81:1 — худшую пару в системе. Отступ уводит его на
     грунт, где оно даёт 5.89:1. Убрать offset = сделать фокус на главной
     кнопке невидимым. */
  --dh-focus: #5fa0ff;
  --dh-focus-width: 2px;
  --dh-focus-offset: 2px;

  /* ── Шрифт ───────────────────────────────────────────────────────────
     Manrope — единственная гарнитура бренда. Inter к бренду отношения не
     имеет: это легаси светлой темы. */
  --dh-font: 'Manrope', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --dh-h1: 3.375rem;
  --dh-h2: 2.5rem;
  --dh-h3: 1.75rem;
  --dh-body: 1rem;
  --dh-body-sm: 0.875rem;
  --dh-eyebrow: 0.875rem;

  --dh-weight-regular: 400;
  --dh-weight-medium: 600;
  --dh-weight-bold: 700;
  --dh-weight-black: 800;

  --dh-tracking-h1: -0.03em;
  --dh-tracking-h2: -0.02em;
  --dh-tracking-eyebrow: 0.12em;

  /* ── Ритм ────────────────────────────────────────────────────────────
     Пять ступеней. Шестую не заводить: промежуточное значение почти всегда
     означает, что компонент верстается мимо сетки. */
  --dh-space-sm: 8px;
  --dh-space-md: 16px;
  --dh-space-lg: 24px;
  --dh-space-xl: 32px;
  --dh-space-2xl: 64px;

  /* ── Форма ───────────────────────────────────────────────────────────
     Скругление растёт со значимостью элемента. */
  --dh-radius-sm: 8px;   /* пилюли, иконки */
  --dh-radius-md: 12px;  /* кнопки, поля, мелкие карточки */
  --dh-radius-lg: 18px;  /* карточка контента */
  --dh-radius-xl: 22px;  /* акцентная и тарифная карточка */
  --dh-radius-pill: 999px;

  /* ── Глубина ─────────────────────────────────────────────────────────
     Обе тени тёмно-синие, не серые: серая тень на синем грунте читается
     как грязь. */
  --dh-shadow-overlay: 0 24px 50px rgba(0, 0, 0, 0.35);
  --dh-shadow-accent: 0 24px 60px color-mix(in srgb, var(--dh-blue-500) 30%, transparent);

  /* ── Движение ────────────────────────────────────────────────────────
     Три длительности, больше не заводить. transition: all запрещён —
     перечислять свойства. Анимировать только transform и opacity. */
  --dh-dur-micro: 120ms; /* @kind other */
  --dh-dur-state: 180ms; /* @kind other */
  --dh-dur-reveal: 250ms; /* @kind other */
  --dh-ease: cubic-bezier(0.32, 0.72, 0, 1); /* @kind other */

  /* ── Фиксированные слои ──────────────────────────────────────────────
     Общий контракт, иначе слои перекрывают друг друга и контент. Числа
     разнесены так, чтобы sticky-CTA и cookie-баннер НЕ делили одно
     значение: когда они равны, порядок между ними определяется порядком в
     исходнике, то есть случайно — отсюда живой дефект «баннер перекрывает
     кнопку оплаты на мобиле».
     Два нижних слоя — липкая кнопка и cookie-баннер — перекрыться не могут
     вовсе: они лежат в одном контейнере-колонке (.dh-bottom-stack), а не
     подпираются переменной высоты друг друга. */
  --dh-z-header: 50; /* @kind other */
  --dh-z-sticky-cta: 900; /* @kind other */
  --dh-z-cookie: 1000; /* @kind other */
  --dh-z-bottom-nav: 1010; /* @kind other */
  --dh-z-progress: 1020; /* @kind other */

  /* Нижних слоёв два, и перекрываться они не могут по построению — см.
     .dh-bottom-stack. Отдельного токена высоты баннера тут нет намеренно:
     он был бы дубликатом, который расходится с реальностью в тот день,
     когда текст согласия займёт три строки вместо одной. */

  /* ── Ширины ──────────────────────────────────────────────────────────
     1440px — потолок контента, не медиазапрос. Порогов ровно два: 1100px
     сужает паддинги шапки, 768px переводит в мобильную раскладку.
     Направление — desktop-first (max-width). */
  --dh-content-max: 1440px;
  --dh-measure: 68ch;
}

/* ══════════════════════════════════════════════════════════════════════
   База
   ══════════════════════════════════════════════════════════════════════ */

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

/* Шрифт задаётся НА ОБЁРТКЕ и наследуется. Правило по типу элемента
   (h1…h6 { font-family }) здесь запрещено: оно бьёт наследование и обнуляет
   типографику всюду, где система живёт поверх чужой темы. Именно так весь
   сайт до 04.09.2026 набирал каждый заголовок не тем шрифтом, и в отдельно
   взятом файле этого видно не было — конфликт возникал только в собранном
   каскаде двух таблиц. Ниже заголовки получают размер, вес и трекинг, но
   НЕ гарнитуру. */
.dh-root {
  margin: 0;
  background: var(--dh-bg);
  color: var(--dh-text);
  font-family: var(--dh-font);
  font-size: var(--dh-body);
  font-weight: var(--dh-weight-regular);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

.dh-h1, .dh-h2, .dh-h3 { margin: 0 0 var(--dh-space-md); line-height: 1.1; color: var(--dh-text); }
.dh-h1 { font-size: var(--dh-h1); font-weight: var(--dh-weight-black); letter-spacing: var(--dh-tracking-h1); }
.dh-h2 { font-size: var(--dh-h2); font-weight: var(--dh-weight-black); letter-spacing: var(--dh-tracking-h2); }
.dh-h3 { font-size: var(--dh-h3); font-weight: var(--dh-weight-black); letter-spacing: var(--dh-tracking-h2); }

.dh-eyebrow {
  display: block;
  font-size: var(--dh-eyebrow);
  font-weight: var(--dh-weight-bold);
  letter-spacing: var(--dh-tracking-eyebrow);
  text-transform: uppercase;
  color: var(--dh-blue-300);
  margin-bottom: var(--dh-space-sm);
}

.dh-text { margin: 0 0 var(--dh-space-md); max-width: var(--dh-measure); color: var(--dh-text-2); }
.dh-text-muted { color: var(--dh-text-3); }
.dh-text-faint { color: var(--dh-text-4); }
.dh-text-sm { font-size: var(--dh-body-sm); }

/* Ссылка в теле текста берёт ступень -300, а не базовый синий: базовый даёт
   на грунте 3.40:1 и под абзац не проходит. */
.dh-link {
  color: var(--dh-blue-300);
  text-decoration: underline;
  text-underline-offset: 3px;
  transition: color var(--dh-dur-state) var(--dh-ease);
}
@media (hover: hover) { .dh-link:hover { color: var(--dh-blue-200); } }
.dh-link:active { color: var(--dh-blue-100); }

/* Фокус — один контракт на всю систему. outline: none без замены запрещён. */
/* Специфичность здесь работает НА нас, а не против. :where() веса не даёт,
   и общее правило весило бы (0,1,0) — его перебивало бы любое чужое
   `button:focus` или `.foo__input:focus{outline:none}` из темы, где такое
   уже есть. Единственное место системы, которое обязано выигрывать, не
   может быть самым слабым, поэтому :where() снят, а компонентам добавлен
   свой уровень. */
/* :focus гасится только там, где есть чем его заменить. Без этой оговорки в
   браузере без :focus-visible (Safari до 15.4) индикация фокуса исчезает
   совсем — то есть правило, написанное ради доступности, её и убирает. */
@supports selector(:focus-visible) {
  .dh-root :focus { outline: none; }
}
.dh-root :focus-visible {
  outline: var(--dh-focus-width) solid var(--dh-focus);
  outline-offset: var(--dh-focus-offset);
}
.dh-root .dh-btn:focus-visible,
.dh-root .dh-input:focus-visible,
.dh-root .dh-textarea:focus-visible,
.dh-root .dh-select:focus-visible,
.dh-root .dh-dropzone:focus-visible,
.dh-root .dh-link:focus-visible,
.dh-root .dh-nav__link:focus-visible {
  outline: var(--dh-focus-width) solid var(--dh-focus);
  outline-offset: var(--dh-focus-offset);
}
.dh-root ::selection { background: var(--dh-blue-800); color: var(--dh-text); }

.dh-wrap { max-width: var(--dh-content-max); margin-inline: auto; padding: 0 var(--dh-space-2xl); }
.dh-panel { background: var(--dh-bg-panel); }
.dh-stack { display: grid; gap: var(--dh-space-md); }
.dh-row { display: flex; flex-wrap: wrap; align-items: center; gap: var(--dh-space-sm); }

/* Видно только скринридеру. Нужен для подписей, которые в макете
   избыточны, а без разметки оставляют контрол безымянным. */
.dh-sr {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0;
}

/* ══════════════════════════════════════════════════════════════════════
   Кнопки
   Имя модификатора несёт РОЛЬ, не внешность: --primary/--secondary/
   --outline/--ghost/--danger. Цвет живёт в токене. Класс с цветом в имени
   (btn-orange, btn-blue) ломает систему в тот день, когда роль сменит цвет,
   и его ловит grep-гард в CI.
   ══════════════════════════════════════════════════════════════════════ */

/* Полный сброс, а не дельта: перечислены ВСЕ свойства, которые родительская
   тема задаёт голому button. Убрать любую строку — вернуть чужое значение. */
.dh-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--dh-space-sm);
  box-sizing: border-box;
  min-height: 48px;
  padding: 12px var(--dh-space-lg);
  margin: 0;
  border: 1px solid transparent;
  border-radius: var(--dh-radius-md);
  background: transparent;
  background-image: none;
  color: var(--dh-text);
  font-family: var(--dh-font);
  font-size: var(--dh-body);
  font-weight: var(--dh-weight-bold);
  line-height: 1.2;
  letter-spacing: 0;
  text-align: center;
  text-decoration: none;
  text-transform: none;
  box-shadow: none;
  cursor: pointer;
  appearance: none;
  transition: background-color var(--dh-dur-state) var(--dh-ease),
              border-color var(--dh-dur-state) var(--dh-ease),
              color var(--dh-dur-state) var(--dh-ease),
              transform var(--dh-dur-micro) var(--dh-ease);
}
.dh-btn:active { transform: scale(0.98); transition-duration: 0s; }
/* Отличие не только по прозрачности: у выключенной кнопки обязан быть
   текстовый признак рядом или в самой подписи — иначе состояние не
   различают ни при дальтонизме, ни на солнце. */
.dh-btn:disabled,
.dh-btn[aria-disabled='true'] { opacity: 0.45; cursor: not-allowed; transform: none; }

/* Загрузка. States называет это состояние обязательным, поэтому оно есть у
   кнопки, а не только у дропзоны: ждать после нажатия «Оплатить» человек
   будет именно здесь.
   Разметка парная: aria-busy сам по себе на кнопке НЕ озвучивается — это
   свойство обновляемого региона, и ни один скринридер о нём не сообщит.
   Наблюдаемое состояние даёт aria-disabled, он же и ставится; aria-busy
   остаётся признаком для стилей и для живого региона рядом. pointer-events
   закрывает повторное нажатие: «Оплатить» дважды — это второй платёж. */
.dh-btn[aria-busy='true'] { cursor: progress; pointer-events: none; }
.dh-btn[aria-busy='true']::before {
  content: '';
  width: 16px;
  height: 16px;
  flex: none;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: var(--dh-radius-pill);
  animation: dh-spin 0.8s linear infinite;
}
@keyframes dh-spin { to { transform: rotate(360deg); } }

.dh-btn--primary { background-color: var(--dh-cta); color: var(--dh-on-cta); }
@media (hover: hover) { .dh-btn--primary:hover:not(:disabled) { background-color: var(--dh-cta-600); } }
.dh-btn--primary:active:not(:disabled) { background-color: var(--dh-cta-700); }

.dh-btn--secondary { background-color: var(--dh-blue); color: var(--dh-on-blue); }
@media (hover: hover) { .dh-btn--secondary:hover:not(:disabled) { background-color: var(--dh-blue-600); } }
.dh-btn--secondary:active:not(:disabled) { background-color: var(--dh-blue-700); }

.dh-btn--outline { border-color: var(--dh-border-strong); color: var(--dh-text); }
@media (hover: hover) { .dh-btn--outline:hover:not(:disabled) { background-color: var(--dh-surface-strong); border-color: var(--dh-text-2); } }
.dh-btn--outline:active:not(:disabled) { background-color: var(--dh-surface); }

.dh-btn--ghost { color: var(--dh-blue-300); padding-inline: var(--dh-space-sm); }
@media (hover: hover) { .dh-btn--ghost:hover:not(:disabled) { background-color: var(--dh-blue-900); color: var(--dh-blue-200); } }
.dh-btn--ghost:active:not(:disabled) { background-color: var(--dh-blue-800); }

/* Разрушительное действие — контурная кнопка цветом ошибки, а не заливка.
   Заливки под неё в каноне нет, и заводить шестой цвет ради «отменить
   заказ» дороже, чем польза: действий этого класса в продукте единицы.
   #ff9090 на грунте даёт 7.14:1. */
.dh-btn--danger { border-color: var(--dh-error); color: var(--dh-error); }
@media (hover: hover) { .dh-btn--danger:hover:not(:disabled) { background-color: color-mix(in srgb, var(--dh-error) 12%, transparent); } }
.dh-btn--danger:active:not(:disabled) { background-color: color-mix(in srgb, var(--dh-error) 20%, transparent); }

.dh-btn--block { display: flex; width: 100%; }

/* ══════════════════════════════════════════════════════════════════════
   Поля
   ОДНА система на все поверхности. На проде их сейчас четыре — три цвета
   ошибки и четыре радиуса; сюда переносится канон, а не это.
   ══════════════════════════════════════════════════════════════════════ */

.dh-field { display: grid; gap: 6px; margin-bottom: var(--dh-space-md); }

.dh-label { font-size: var(--dh-body-sm); font-weight: var(--dh-weight-medium); color: var(--dh-text-2); }
.dh-label__req { color: var(--dh-cta-300); }
.dh-hint { font-size: var(--dh-body-sm); color: var(--dh-text-3); }

/* Группа полей — своими классами, а не «сбросьте fieldset у себя по месту».
   Без сброса рамка и внутренние отступы браузера видны во всех движках, и
   вызывающий каждый раз дописывает style="border: 0; padding: 0" — ровно это
   и лежало в нашей собственной витрине полей до появления этих двух правил.
   Ограничение в документации такого не удерживает: оно проигрывает тому, что
   видно на экране прямо сейчас.
   min-width: 0 — известный дефект fieldset: без него он отказывается сжиматься
   внутри grid и flex.
   Раскладка колонкой на fieldset работает не с самим legend, а с анонимным
   контейнером содержимого, который движок заводит под остальными детьми, —
   поэтому отступ под подписью задаётся её собственным margin, а не gap.
   Измерено в Chromium (tools/verify.mjs, карточка components/forms.html). */
.dh-fieldset {
  display: grid;
  gap: 6px;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}
/* Подпись группы читается как подпись поля — это она и есть, просто у
   нескольких полей сразу. */
.dh-legend {
  padding: 0;
  margin-bottom: 6px;
  font-size: var(--dh-body-sm);
  font-weight: var(--dh-weight-medium);
  color: var(--dh-text-2);
}

/* Полный сброс — тема стилизует голый input по типу элемента. */
.dh-input,
.dh-textarea,
.dh-select {
  box-sizing: border-box;
  width: 100%;
  min-height: 48px;
  padding: 12px var(--dh-space-md);
  margin: 0;
  /* Ширина рамки НЕ меняется между состояниями — меняется только цвет.
     Иначе поле дёргается на фокусе и в ошибке. */
  border: 1px solid var(--dh-border);
  border-radius: var(--dh-radius-md);
  background: var(--dh-surface);
  background-image: none;
  color: var(--dh-text);
  font-family: var(--dh-font);
  font-size: var(--dh-body);
  line-height: 1.4;
  box-shadow: none;
  appearance: none;
  transition: border-color var(--dh-dur-state) var(--dh-ease),
              background-color var(--dh-dur-state) var(--dh-ease);
}
.dh-textarea { min-height: 132px; resize: vertical; }

/* Индикатор раскрытия. appearance: none снимает системную стрелку вместе со
   сбросом, а background-image: none выше убирает и её остатки — после этого
   select визуально неотличим от обычного текстового поля, и раскрывают его
   только те, кто уже знает, что это список. Замена обязана быть в системе:
   иначе стрелку дорисуют по месту — своим SVG, своим цветом и мимо токенов.
   Рисуется двумя градиентами по токену, а не картинкой: SVG в data-URI
   пришлось бы красить литералом цвета, то есть мимо токенов и мимо гарда.
   Место под стрелку резервируется паддингом — без него длинный вариант
   заезжает под неё. */
.dh-select {
  padding-right: calc(var(--dh-space-md) + 18px);
  background-image:
    linear-gradient(45deg, transparent 50%, var(--dh-text-3) 50%),
    linear-gradient(135deg, var(--dh-text-3) 50%, transparent 50%);
  background-position:
    right calc(var(--dh-space-md) + 5px) center,
    right var(--dh-space-md) center;
  background-size: 5px 5px;
  background-repeat: no-repeat;
}
.dh-input::placeholder,
.dh-textarea::placeholder { color: var(--dh-text-4); opacity: 1; }
@media (hover: hover) {
  .dh-input:hover, .dh-textarea:hover, .dh-select:hover { border-color: var(--dh-border-strong); }
}
.dh-input:focus-visible,
.dh-textarea:focus-visible,
.dh-select:focus-visible { border-color: var(--dh-focus); }
.dh-input:disabled,
.dh-textarea:disabled,
.dh-select:disabled { opacity: 0.45; cursor: not-allowed; }

/* Ошибка — per-field, под полем. Не общим баннером наверху формы: баннер
   не говорит, какое из двенадцати полей чинить. На поле обязательны
   aria-invalid="true" и aria-describedby на этот текст. */
.dh-input--invalid,
.dh-textarea--invalid,
.dh-select--invalid { border-color: var(--dh-error); }
.dh-error {
  display: flex;
  gap: 6px;
  font-size: var(--dh-body-sm);
  font-weight: var(--dh-weight-medium);
  color: var(--dh-error);
}

/* Нативные чекбокс и радио. accent-color красит их цветом CTA, оставляя
   родное поведение и родной фокус — кастомный контрол здесь не окупается. */
.dh-check { display: flex; align-items: flex-start; gap: var(--dh-space-sm); cursor: pointer; }
.dh-check input[type='checkbox'],
.dh-check input[type='radio'] {
  flex: none;
  width: 20px;
  height: 20px;
  margin: 2px 0 0;
  accent-color: var(--dh-cta);
  cursor: pointer;
}
.dh-check__text { font-size: var(--dh-body-sm); color: var(--dh-text-2); }

/* Дропзона. tabindex="0" и role="button" ОБЯЗАТЕЛЬНЫ, как и обработка
   Enter и Space: скрытый input[type=file] выпадает из порядка табуляции,
   и без этого загрузить документ с клавиатуры физически нельзя. На проде
   сейчас недостижима на всех трёх поверхностях. */
.dh-dropzone {
  display: grid;
  place-items: center;
  gap: var(--dh-space-sm);
  padding: var(--dh-space-xl);
  border: 2px dashed color-mix(in srgb, var(--dh-focus) 40%, transparent);
  border-radius: 14px;
  background: var(--dh-surface);
  color: var(--dh-text-2);
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dh-dur-state) var(--dh-ease),
              background-color var(--dh-dur-state) var(--dh-ease);
}
@media (hover: hover) { .dh-dropzone:hover { border-color: var(--dh-focus); background: var(--dh-surface-strong); } }
.dh-dropzone[data-state='dragover'] { border-color: var(--dh-focus); background: var(--dh-surface-strong); }
.dh-dropzone[data-state='loading'] { border-style: solid; border-color: var(--dh-border-strong); }
.dh-dropzone[data-state='success'] { border-style: solid; border-color: var(--dh-success); }
.dh-dropzone__title { font-weight: var(--dh-weight-bold); color: var(--dh-text); }

/* ══════════════════════════════════════════════════════════════════════
   Карточки
   ══════════════════════════════════════════════════════════════════════ */

.dh-card {
  padding: var(--dh-space-lg);
  border: 1px solid var(--dh-border);
  border-radius: var(--dh-radius-lg);
  background: var(--dh-surface);
}
.dh-card__title { margin: 0 0 var(--dh-space-sm); font-size: 1.25rem; font-weight: var(--dh-weight-bold); }
.dh-card__body { margin: 0; color: var(--dh-text-2); }
.dh-card__meta { margin-top: var(--dh-space-md); font-size: var(--dh-body-sm); color: var(--dh-text-3); }

.dh-card--accent {
  border-radius: var(--dh-radius-xl);
  border-color: transparent;
  background: linear-gradient(140deg, var(--dh-blue-700), var(--dh-blue-900));
  box-shadow: var(--dh-shadow-accent);
}
/* Подпись на акцентной карточке берёт ступень выше. Градиент светлеет до
   --dh-blue-700, и обычный --dh-text-3 даёт на этом конце 4,41:1 — ниже AA
   для 14px. --dh-text-2 даёт 6,98:1. Замер именно по светлому концу: по
   среднему цвету градиента проверка не считается. */
.dh-card--accent .dh-card__meta { color: var(--dh-text-2); }

.dh-card--tariff { border-radius: var(--dh-radius-xl); background: var(--dh-surface-strong); }
.dh-card__price { font-size: var(--dh-h3); font-weight: var(--dh-weight-black); letter-spacing: var(--dh-tracking-h2); }

/* Предупреждение — карточка с рамкой, НЕ модалка. Модалка требует решения
   немедленно и прерывает заполнение; предупреждение живёт рядом с полем,
   которое его вызвало, и остаётся видимым, пока причина не снята. */
.dh-card--warning { border-color: var(--dh-cta-400); background: color-mix(in srgb, var(--dh-cta) 10%, transparent); }
.dh-card--info { border-color: var(--dh-blue-400); background: color-mix(in srgb, var(--dh-blue) 10%, transparent); }
.dh-card--ok { border-color: var(--dh-success); background: color-mix(in srgb, var(--dh-success) 10%, transparent); }
.dh-card--danger { border-color: var(--dh-error); background: color-mix(in srgb, var(--dh-error) 10%, transparent); }

.dh-card--warning .dh-card__title { color: var(--dh-cta-300); }
.dh-card--ok .dh-card__title { color: var(--dh-success); }
.dh-card--danger .dh-card__title { color: var(--dh-error); }

/* ══════════════════════════════════════════════════════════════════════
   Навигация
   ══════════════════════════════════════════════════════════════════════ */

.dh-nav {
  position: sticky;
  top: 0;
  z-index: var(--dh-z-header);
  display: flex;
  align-items: center;
  gap: var(--dh-space-lg);
  min-height: 56px;
  padding: var(--dh-space-lg) var(--dh-space-2xl);
  background: var(--dh-bg);
  border-bottom: 1px solid var(--dh-border);
}
.dh-nav__brand { display: inline-flex; align-items: center; gap: 10px; margin-right: auto; text-decoration: none; color: var(--dh-text); }
/* Словесный знак набирается синим бренда. Светло-синий -lt здесь ошибка:
   во всех файлах логотипа «.pro» именно #1A6EF5. */
.dh-nav__brand-word { font-weight: var(--dh-weight-black); font-size: 1.25rem; letter-spacing: -0.01em; }
.dh-nav__brand-word span { color: var(--dh-blue); }
.dh-nav__link {
  color: var(--dh-text-2);
  text-decoration: none;
  font-size: var(--dh-body-sm);
  font-weight: var(--dh-weight-medium);
  transition: color var(--dh-dur-state) var(--dh-ease);
}
@media (hover: hover) { .dh-nav__link:hover { color: var(--dh-text); } }
.dh-nav__link[aria-current='page'] { color: var(--dh-text); }

.dh-footer {
  padding: var(--dh-space-2xl);
  background: var(--dh-bg-footer);
  color: var(--dh-text-3);
  font-size: var(--dh-body-sm);
}
.dh-footer a { color: var(--dh-text-2); }

/* Прогресс шагов. Текущий шаг помечается aria-current="step", а не только
   цветом: без этого клиент на скринридере не знает, где находится. */
.dh-steps { display: flex; flex-wrap: wrap; gap: var(--dh-space-md); list-style: none; margin: 0 0 var(--dh-space-xl); padding: 0; }
.dh-steps__item { display: flex; align-items: center; gap: var(--dh-space-sm); font-size: var(--dh-body-sm); color: var(--dh-text-4); }
.dh-steps__num {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: var(--dh-radius-pill);
  border: 1px solid var(--dh-border-strong);
  font-weight: var(--dh-weight-bold);
}
.dh-steps__item[aria-current='step'] { color: var(--dh-text); }
.dh-steps__item[aria-current='step'] .dh-steps__num { background: var(--dh-blue); border-color: var(--dh-blue); color: var(--dh-on-blue); }
.dh-steps__item[data-done='true'] { color: var(--dh-text-2); }
.dh-steps__item[data-done='true'] .dh-steps__num { border-color: var(--dh-success); color: var(--dh-success); }

/* ══════════════════════════════════════════════════════════════════════
   Обратная связь
   ══════════════════════════════════════════════════════════════════════ */

.dh-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--dh-radius-sm);
  font-size: var(--dh-body-sm);
  font-weight: var(--dh-weight-bold);
}
.dh-badge--info { background: var(--dh-blue-800); color: var(--dh-blue-100); }
.dh-badge--ok { background: color-mix(in srgb, var(--dh-success) 16%, transparent); color: var(--dh-success); }
.dh-badge--warning { background: color-mix(in srgb, var(--dh-cta) 20%, transparent); color: var(--dh-cta-300); }
.dh-badge--danger { background: color-mix(in srgb, var(--dh-error) 16%, transparent); color: var(--dh-error); }

.dh-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 14px;
  border: 1px solid var(--dh-border);
  border-radius: var(--dh-radius-pill);
  font-size: var(--dh-body-sm);
  color: var(--dh-text-2);
}

.dh-empty { padding: var(--dh-space-2xl) var(--dh-space-lg); text-align: center; color: var(--dh-text-3); }

/* Экран отказа. Тон нейтральный: человек не сделал ничего неправильного,
   мы просто не беремся за этот класс дел. Показывается ДО оплаты и обязан
   называть причину и следующий шаг — иначе это тупик, а не отказ. */
.dh-exit { max-width: 640px; margin: 0 auto; padding: var(--dh-space-2xl) 0; text-align: left; }
.dh-exit__reason { padding: var(--dh-space-lg); border-left: 3px solid var(--dh-cta-400); background: var(--dh-surface); margin-bottom: var(--dh-space-lg); }

/* Глиф знака. Оранжевый допустим ТОЛЬКО здесь — как цвет фигуры, не как
   фон под текстом. */
.dh-glyph { color: var(--dh-glyph); }

/* ══════════════════════════════════════════════════════════════════════
   Фиксированные слои
   ══════════════════════════════════════════════════════════════════════ */

/* Нижние фиксированные слои живут в ОДНОМ контейнере-колонке, и это не
   украшение.
   Пока они были двумя независимыми fixed-элементами, кнопка уступала баннеру
   только если кто-то знал его высоту. Разнести z-index не помогает: при
   равных значениях порядок решает исходник (лотерея), при разнесённых баннер
   перекрывает кнопку уже всегда. Переменная подъёма тоже не помогает — её
   значение это дубликат высоты баннера, а высота зависит от длины текста и
   ширины экрана: на однострочной фикстуре сходится, на живом баннере с двумя
   строками и рядом кнопок расходится, и проверка остаётся зелёной ровно там,
   где дефект уже вернулся. Подпорка через :has() добавляла третью зависимость
   — от того, каким именно способом баннер скрывают.
   Одна колонка снимает вопрос: элементы лежат в потоке друг под другом и
   перекрыться не могут, высоту знать не нужно никому.
   Любой фиксированный слой обязан уступать место интерактивному элементу под
   собой, и проверяется это document.elementFromPoint в центре кнопки на узком
   экране, а не глазами: tools/verify.mjs это и делает. */
.dh-bottom-stack {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: var(--dh-z-cookie);
  display: grid;
}

/* Порядок в разметке и есть порядок на экране: кнопка выше баннера. */
.dh-sticky-cta {
  display: none;
  padding: var(--dh-space-md);
  background: var(--dh-bg-footer);
  border-top: 1px solid var(--dh-border);
}
.dh-cookie {
  padding: var(--dh-space-md);
  background: var(--dh-bg-panel);
  border-top: 1px solid var(--dh-border);
}

/* Липкая кнопка сама по себе, без баннера: только тогда ей нужен собственный
   номер слоя. */
.dh-sticky-cta:only-child { z-index: var(--dh-z-sticky-cta); }

/* ══════════════════════════════════════════════════════════════════════
   Пороги. Ровно два, направление desktop-first.
   ══════════════════════════════════════════════════════════════════════ */

@media (max-width: 1100px) {
  .dh-nav { padding-inline: var(--dh-space-lg); }
  .dh-wrap { padding-inline: var(--dh-space-lg); }
}

@media (max-width: 768px) {
  :root { --dh-h1: 2rem; --dh-h2: 1.625rem; --dh-h3: 1.25rem; }
  .dh-nav { padding: 0 var(--dh-space-md); }
  .dh-wrap { padding-inline: var(--dh-space-md); }
  .dh-card { padding: var(--dh-space-md); }
  /* Sticky-CTA показывается только на узком экране: на десктопе кнопка
     видна в потоке, и полоса внизу только съедает высоту. */
  .dh-sticky-cta { display: block; }
}

/* Гасим движение, оставляя кроссфейд. Обязательно для КАЖДОЙ анимации:
   на проде сейчас одно вхождение этого запроса на всю тему. */
@media (prefers-reduced-motion: reduce) {
  .dh-root *,
  .dh-root *::before,
  .dh-root *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 150ms !important;
    scroll-behavior: auto !important;
  }
  .dh-btn:active { transform: none; }
  /* Спиннер не крутится, но состояние остаётся видимым: рамка на месте,
     aria-busy никуда не девается. */
  .dh-btn[aria-busy='true']::before { animation: none; opacity: 0.6; }
}
