/* ── Reset & Base ─────────────────────────────────────────────────────────── */
/* Токены дизайна — в static/tokens.css (подключается перед этим файлом). */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

html { font-size: 16px; -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }

body {
  font-family: var(--font-sans);
  background-color: var(--bg);
  background-image: var(--bg-decor);   /* тёмная: золотая аура сверху (эталон .ai) */
  background-repeat: no-repeat;
  background-attachment: fixed;
  color: var(--label);
  min-height: 100vh;
}

/* ── Auth Layout ──────────────────────────────────────────────────────────── */
/* 🔴 Мобильный канон посадки (Fold-first · CEO 27.07):
   · 100dvh вместо 100vh — на телефоне адресная строка динамическая, и 100vh
     считается по «большому» вьюпорту: раскладка думает, что места 844, а видно
     660 → низ карточки уезжает под сгиб, сверху зияет пустота. dvh считает по
     тому, что реально видно СЕЙЧАС.
   · margin-block:auto на контейнере вместо align-items:center — центр в свободной
     высоте, но при нехватке места верх не срезается за край (канон playbook 06,
     группа G п.3). align-items:center на переполнении рубит верх без доступа. */
.auth-body {
  display: flex;
  justify-content: center;
  min-height: 100vh;
  min-height: 100dvh;
  padding: 24px 16px;
  /* Точка отсчёта для .auth-lang: без неё переключатель языка прибивался бы к
     окну и на коротком экране наезжал на карточку при прокрутке. */
  position: relative;
}

.auth-container { width: 100%; max-width: 420px; margin-block: auto; }

/* Переключатель языка на экранах входа и регистрации (12.08.2026).
   Ссылками, а не меню как в шапке приложения: это первый экран человека,
   который сервиса ещё не выбирал, и он обязан работать без JS.

   Позиция абсолютная: .auth-body — flex-центровка карточки, и обычный поток
   сдвинул бы её вверх ради полоски из двух ссылок. Внизу, а не вверху, потому
   что смена языка здесь — редкое действие: наверху она спорила бы за внимание
   с самой формой входа. */
.auth-lang {
  position: absolute;
  inset-inline: 0;
  bottom: 14px;
  display: flex;
  gap: 14px;
  justify-content: center;
  font-size: 13px;
}

.auth-lang a {
  color: var(--text-muted);
  text-decoration: none;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
}

.auth-lang a:hover { color: var(--text); }

/* Текущий язык — цветом и весом, без «кнопочного» вида: это указатель
   состояния, а не второе действие рядом с первым. */
.auth-lang a[aria-current] { color: var(--text); font-weight: 600; }

/* Фокус виден всегда: по этим ссылкам ходят с клавиатуры чаще, чем мышью, —
   человек, не понимающий языка страницы, ищет их первыми. */
.auth-lang a:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.auth-card {
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: var(--radius-lg);
  padding: 40px 36px;
  box-shadow: var(--elev-2);
}

.auth-logo {
  text-align: center;
  margin-bottom: 32px;
}

.logo-icon { display: flex; justify-content: center; margin-bottom: 10px; color: var(--accent); }
/* Куб холдинга на экране входа — тот же знак, что в шапке рабочих экранов.
   🔴 Размер по канону бренд-доминанты (CEO 27.07): знак — герой этого экрана, а не
   иконка при тексте. 120px в карточке 420 = 29% ширины (было 52px = 12%). Ассет
   gjx-cube-280.png держит запас под экраны с двойной плотностью пикселей.
   Ореол усилен: он растворяет тёмную кайму арта на светлой теме. */
.logo-cube { display: block; width: 120px; height: 120px; margin: 0 auto 14px;
  filter: drop-shadow(0 0 26px rgba(235, 197, 84, .34)); }

.logo-text {
  font-family: var(--font-brand);
  font-size: 26px;
  font-weight: 700;
  letter-spacing: .05em;
  color: var(--c-gold);           /* канон: GOLDJAXE — золотом (эталон .ai) */
}
.logo-text span { color: var(--accent); }
.logo-text .br { color: var(--label4); font-weight: 400; }

.logo-sub {
  font-family: var(--font-brand);
  font-size: 15px;
  color: var(--label);
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  margin-top: 7px;
}
/* Канон wordmark (эталон .ai): GOLDJAXE — золото, имя продукта в скобках — цветом
   основного текста. Прежде было наоборот (белое имя холдинга + золотой SCRIBE) —
   это ставило продукт выше холдинга и расходилось с шапками экосистемы. */
.logo-sub .gold { color: var(--label); }
.logo-sub .br { color: var(--label4); font-weight: 400; }

.auth-card h2 {
  font-size: 22px;
  font-weight: 700;
  letter-spacing: -0.3px;
  margin-bottom: 8px;
}

.auth-desc {
  font-size: 15px;
  color: var(--label3);
  line-height: 1.5;
  margin-bottom: 24px;
}

/* Приписка под баннером ошибки: вторая аудитория (сотрудник холдинга) мельче
   основного пути (пробный период). Раньше оба пути жили в одном предложении
   баннера, и человек должен был сам решить, к какому относится (O-02). */
.auth-note {
  margin: -12px 0 20px;
  font-size: 13px;
  color: var(--label4);
  line-height: 1.45;
}

/* ── Sent State ───────────────────────────────────────────────────────────── */
.sent-state { text-align: center; }
.sent-icon { display: flex; justify-content: center; margin-bottom: 16px; color: var(--accent); }
.sent-state h2 { font-size: 22px; font-weight: 700; margin-bottom: 8px; }
.sent-state p { font-size: 15px; color: var(--label3); line-height: 1.5; margin-bottom: 8px; }
.sent-state .note { font-size: 13.5px; color: var(--label4); margin-bottom: 24px; }
/* Адрес в подтверждении — моно: адрес читают ЗНАК В ЗНАК (не там ли опечатка),
   и пропорциональный шрифт для этого худший выбор. Перенос по любому символу:
   длинные корпоративные адреса иначе распирают карточку. */
.sent-addr { font-family: var(--font-mono); font-size: .95em; color: var(--label);
  word-break: break-all; }

/* Подсказки на экране «проверьте почту»: срок жизни ссылки и папка «Спам».
   Второе — первая причина обращений в поддержку по магик-ссылкам, дешевле
   сказать заранее. Слева выровнены при общем центрировании блока: строки
   разной длины, центрированный список рвал бы взгляд на каждой строке. */
.sent-hints {
  list-style: none;
  margin: 0 0 22px;
  padding: 12px 14px;
  text-align: left;
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--radius-sm);
  display: flex; flex-direction: column; gap: 8px;
}
.sent-hints li {
  display: flex; align-items: flex-start; gap: 9px;
  font-size: 13.5px; line-height: 1.45; color: var(--label3);
}
.sent-hints__ic { display: inline-flex; flex: none; margin-top: 2px; color: var(--label4); }
.sent-back { display: inline-block; text-decoration: none; }
.sent-state .note.sent-support { margin: 16px 0 0; }
.sent-state .note.sent-support a { color: var(--c-gold-strong); }

/* ── Пробный доступ (/trial) ────────────────────────────────────────────────
   Экран продающий, но остаётся частью продукта: те же карточка, шрифты и золото,
   что на входе. Отличие ровно одно — до формы человек видит, ЧТО получит. */

/* Три обещания. Не абзац: список читается взглядом за пару секунд, до того как
   человек решит, стоит ли вводить почту. Значки — набор продукта (Lucide). */
.trial-perks {
  list-style: none;
  margin: 0 0 22px;
  padding: 14px 16px;
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-left: 2px solid var(--c-gold-line);   /* золотой торец — фирменный маркер блока */
  border-radius: var(--radius-sm);
  display: flex; flex-direction: column; gap: 10px;
}
.trial-perks li {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 13.5px; line-height: 1.5; color: var(--label2);
}
.trial-perks b { font-weight: 700; color: var(--label); }
/* Значок приглушён до бронзы: он маркер строки, а не событие. Ярким золотом
   три значка подряд перебивали бы кнопку — единственное действие экрана. */
.trial-perks__ic {
  display: inline-flex; flex: none; margin-top: 2px;
  color: var(--c-gold-strong);
}

/* Подпись под полем: зачем оно и что будет дальше. Снимает главный вопрос формы
   регистрации — «что вы сделаете с моей почтой». */
.field-hint {
  font-size: 12.5px;
  line-height: 1.4;
  color: var(--label4);
  margin: 0;
}
/* Отказ по конкретному полю. Тот же красный, что у общей плашки, но у самого
   поля: сообщение «в адресе опечатка» наверху карточки заставляет искать, где
   именно опечатка. */
.field-error {
  display: flex; align-items: flex-start; gap: 6px;
  font-size: 12.5px; line-height: 1.4;
  color: var(--c-danger);
  margin: 0;
}
.field-error::before {
  content: "";
  flex: none;
  width: 3px; height: 1em;
  margin-top: .1em;
  background: var(--c-danger);
}
/* Пока поле в ошибке, подсказка молчит: две строки подряд под одним полем —
   серая «зачем это поле» и красная «что не так» — спорят за внимание, а нужна
   ровно вторая. Подсказка вернётся, как только ошибка снимется. */
.input-group.is-invalid .field-hint { display: none; }
/* Порядок правил в разметке: подсказка идёт ПОСЛЕ поля, поэтому серверный отказ
   (aria-invalid из шаблона, без класса на группе) ловим соседним селектором. */
.input-group input[aria-invalid="true"] ~ .field-hint { display: none; }
/* Красная рамка — ДОПОЛНЕНИЕ к тексту, а не замена: цвет один не должен нести
   смысл (дальтонизм, ч/б печать). Текст ошибки рядом обязателен. */
.input-group.is-invalid input,
.input-group input[aria-invalid="true"] { border-color: var(--c-danger); }
.input-group.is-invalid input:focus,
.input-group input[aria-invalid="true"]:focus {
  border-color: var(--c-danger);
  box-shadow: 0 0 0 1px var(--c-danger-line), 0 0 14px rgba(220,76,76,.16);
}

/* Согласие на обработку ПДн — отдельный блок с рамкой, не ещё одна строка формы:
   действие юридически значимое, и подпись под ним человек должен заметить. */
.consent-box {
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  transition: border-color .15s, background .15s;
}
.consent-box.is-invalid {
  border-color: var(--c-danger);
  background: var(--c-danger-bg);
}
.consent-box .field-error { margin-top: 8px; }
.consent-label {
  display: flex; align-items: flex-start; gap: 11px;
  cursor: pointer;
  /* Область попадания ≥44px по высоте (канон touch-target): галочка 18px сама
     по себе на телефоне промахивается. Кликабельна вся метка целиком. */
  min-height: 20px;
  padding: 2px 0;
}
.consent-label input[type="checkbox"] {
  flex: none;
  width: 18px; height: 18px;
  margin: 1px 0 0;
  cursor: pointer;
  /* accent-color красит родной чекбокс фирменным золотом без своей отрисовки —
     родной остаётся доступным с клавиатуры и понятен скринридеру. */
  accent-color: var(--c-gold);
}
.consent-label input[type="checkbox"]:focus-visible { box-shadow: var(--focus-ring); }
.consent-text {
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--label2);
}
/* Ссылки на документы внутри согласия. Без своих правил браузер красит их
   системным синим — чужим цветом на золотой палитре, и в тёмной теме он ещё и
   проваливается по контрасту. Подчёркивание оставляем: это не декоративная
   ссылка, а обязательный переход к тексту, с которым человек соглашается. */
.consent-text a {
  color: var(--c-gold-strong);
  text-decoration: underline;
  text-underline-offset: 2px;
  text-decoration-thickness: 1px;
}
.consent-text a:hover { color: var(--c-gold); }
.consent-text a:focus-visible { outline: none; box-shadow: var(--focus-ring); }

/* Состояние отправки главной кнопки: она гаснет и говорит, что делает. Кольцо
   крутится только там, где движение разрешено (см. prefers-reduced-motion).

   ВНИМАНИЕ, ПАРНЫЙ БЛОК. Те же правила есть в components.css (для .sc-btn на
   /team). Дублирование сознательное: экраны входа грузят ТОЛЬКО tokens+style, а
   каркас — tokens+components, и общего файла у них нет. Тянуть весь каркас на
   экран входа ради двух десятков строк дороже, чем держать их парой. Меняешь
   здесь — поменяй и там. */
.btn-spin {
  display: none;
  flex: none;
  width: 15px; height: 15px;
  margin-right: 9px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: 50%;
  opacity: .75;
  animation: btn-spin .7s linear infinite;
}
.btn-primary.is-loading .btn-spin { display: inline-block; }
/* Гашеная кнопка по умолчанию уходит в opacity .4 — при отправке это читалось
   бы как поломка. Здесь ожидание, а не отказ: держим текст читаемым. */
.btn-primary.is-loading:disabled { opacity: .8; cursor: progress; }
@keyframes btn-spin { to { transform: rotate(360deg); } }
/* Без движения кольцо остаётся статичным знаком «идёт отправка» — сам факт
   ожидания несёт текст кнопки, вращение только подкрепляет его. */
@media (prefers-reduced-motion: reduce) {
  .btn-spin { animation: none; }
}

/* Подвал формы: срок пробы и ссылка для тех, у кого доступ уже есть. */
.trial-foot { margin-top: 18px; text-align: center; line-height: 1.5; }
.trial-foot a { color: var(--c-gold-strong); font-weight: 600; }

/* Пробный доступ на широком экране — две колонки: слева обещание, справа форма.
   В один столбец страница не помещалась в экран ноутбука, и до кнопки — то есть
   до главного действия — приходилось прокручивать. Порог 900px: ниже него две
   колонки по ~360px уже жмут строки обещаний до переносов на каждом слове.
   Обёртки .trial-cols__* без этого запроса ведут себя как обычные блоки, поэтому
   мобильная вёрстка остаётся ровно прежней. */
@media (min-width: 900px) {
  .auth-body:has(.trial-cols) .auth-container { max-width: 840px; }
  .trial-cols {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 40px;
    align-items: start;
  }
  /* Разделительная линия вместо пустоты: две колонки в одной карточке иначе
     читаются как два несвязанных экрана, случайно оказавшихся рядом. */
  .trial-cols__form {
    border-left: 1px solid var(--separator);
    padding-left: 40px;
  }
  /* Знак и wordmark — над обеими колонками, бренд не дробится пополам. */
  .auth-body:has(.trial-cols) .auth-logo { margin-bottom: 26px; }
  .auth-body:has(.trial-cols) .logo-cube { width: 96px; height: 96px; }
  /* Заголовок больше не по центру карточки, а над своей колонкой. */
  .trial-cols h2, .trial-cols .auth-desc { text-align: left; }
  .trial-cols .trial-perks { margin-bottom: 0; }
  .trial-cols .trial-foot { text-align: left; }
}

/* Невысокий экран (ноутбук 1280×720 и подобные): вертикаль ужимается ещё раз.
   Две колонки сами по себе прокрутку там не убирают — знак и поля по-прежнему
   не помещаются по высоте, а до кнопки нужно доскроллить. Режем то, что не
   несёт смысла: размер знака и воздух вокруг него. */
@media (min-width: 900px) and (max-height: 800px) {
  .auth-body:has(.trial-cols) { padding-block: 14px; }
  .auth-body:has(.trial-cols) .auth-card { padding: 24px 30px; }
  .auth-body:has(.trial-cols) .auth-logo { margin-bottom: 18px; }
  .auth-body:has(.trial-cols) .logo-cube {
    width: 68px; height: 68px; margin-bottom: 8px;
  }
  .auth-body:has(.trial-cols) .logo-text { font-size: 22px; }
  .trial-cols h2 { font-size: 20px; }
  .trial-cols .auth-desc { margin-bottom: 16px; }
  .trial-cols .trial-foot { margin-top: 14px; }
}

/* Запасной путь для браузеров без :has() (Firefox до 121). Ширину карточки они
   не поднимут — но и не сломают: страница останется прежней одноколоночной,
   потому что грид без расширенного контейнера дал бы две колонки по 190px. */
@supports not selector(:has(*)) {
  @media (min-width: 900px) {
    .trial-cols { display: block; }
    .trial-cols__form { border-left: 0; padding-left: 0; }
  }
}

/* Ссылка «Попробовать бесплатно» на экране входа — отбита линией и подана как
   отдельное предложение, а не приписка мелким шрифтом под кнопкой. */
.auth-alt {
  margin-top: 22px;
  padding-top: 18px;
  border-top: 1px solid var(--c-line);
  text-align: center;
}
.auth-alt__title { font-size: 14px; color: var(--label2); margin-bottom: 10px; }
.auth-alt__link {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: 40px;
  padding: 9px 16px;
  border: 1px solid var(--c-gold-line);
  border-radius: var(--radius-sm);
  background: var(--c-gold-bg);
  color: var(--c-gold-strong);
  font-size: 14px; font-weight: 600;
  text-decoration: none;
  transition: background .15s, border-color .15s, color .15s;
}
.auth-alt__link:hover { background: var(--c-gold-line); border-color: var(--c-gold); }
.auth-alt__link .ic-sm { opacity: .8; }

/* После отказа «адрес неизвестен» регистрация перестаёт быть запасным путём:
   она единственный оставшийся шаг. Блок поднимается из подвала карточки —
   заголовок обычного цвета текста вместо приглушённого, кнопка во всю ширину.
   Без этого человек второй раз вводит тот же адрес в форму выше. */
.auth-alt--primary .auth-alt__title {
  color: var(--label);
  font-weight: 600;
}
.auth-alt--primary .auth-alt__link {
  display: flex;
  justify-content: center;
  border-color: var(--c-gold);
}

/* ── Экран входа на телефоне ────────────────────────────────────────────────
   Десктопный вертикальный ритм (паддинги 40/36, отступ логотипа 32) в телефоне
   выдавливал карточку за сгиб — вместе со 100vh это и давало «сверху пусто,
   низ приходится доскроллить». Здесь ритм ужимается так, чтобы вся карточка
   вместе с кнопкой стояла в видимой части экрана даже при показанной адресной
   строке (~660 CSS-px у Brave/Chrome на Android). Знак холдинга при этом
   остаётся крупным — он бренд-доминанта экрана, ужимаем отступы, не лого. */
@media (max-width: 560px) {
  .auth-body { padding: 14px 12px; }
  /* Отступы по бокам срезаны: карточка входа уже отделена от края экрана
     отступом .auth-body, а вложенные блоки (согласие, перечень) добавляли
     свой — вместе 62px из 390 (замер 16.08.2026). Вертикальный воздух
     оставляем: он держит ритм страницы входа. */
  .auth-card { padding: 26px 14px; }
  .auth-logo { margin-bottom: 20px; }
  .logo-cube { width: 96px; height: 96px; margin-bottom: 12px;
    filter: drop-shadow(0 0 22px rgba(235, 197, 84, .34)); }
  .logo-text { font-size: 22px; }
  .logo-sub { font-size: 13px; margin-top: 6px; }
  .auth-card h2 { font-size: 20px; }
  .auth-desc { font-size: 14px; margin-bottom: 18px; }
  .sent-state h2 { font-size: 20px; }
  .sent-state p { font-size: 14px; }
  /* Триал на телефоне: воздух режем, содержание — нет. Три обещания и подписи
     полей остаются: именно они решают, введёт человек почту или закроет. */
  .trial-perks { padding: 12px 13px; gap: 9px; margin-bottom: 18px; }
  .trial-perks li { font-size: 13px; }
  .sent-hints { padding: 11px 12px; margin-bottom: 18px; }
  .consent-box { padding: 11px 12px; }
  .trial-foot { margin-top: 16px; }
  .auth-alt { margin-top: 18px; padding-top: 15px; }
  /* Переключатель темы прижимаем к углу — в узком экране он не должен
     висеть посреди пустоты и спорить с карточкой за внимание.
     Селектор — на ОБЁРТКУ меню, а не на кнопку: position:fixed переехал с
     .app-theme на .gx-menu (иначе fixed отменял бы relative, на котором держится
     выпадающая панель). Прежние `.auth-body ~ .app-theme` и `body.auth-body >
     .app-theme` перестали совпадать с чем-либо, и на телефоне оставался отступ
     16px из инлайн-стиля вместо 10px.
     !important обязателен: координаты заданы инлайном в разметке, а он сильнее
     любого правила из файла.
     Только .auth-body (вход и регистрация): экран закрытой встречи тоже держит
     меню в углу, но style.css не подключает вовсе — там свой набор в <style>. */
  body.auth-body > .gx-menu { top: 10px !important; right: 10px !important; }
}

/* Экстрим 358-390: держим тот же строй, срезаем только воздух */
@media (max-width: 400px) {
  .auth-card { padding: 22px 17px; }
  .logo-cube { width: 88px; height: 88px; }
  .auth-logo { margin-bottom: 17px; }
}

/* ── Error Banner ─────────────────────────────────────────────────────────── */
.error-note {
  background: var(--c-danger-bg);
  border: 1px solid var(--c-danger-line);
  border-radius: var(--radius-sm);   /* панель-уведомление — канон sm (не терминальный бейдж) */
  padding: 12px 14px;
  font-size: 14px;
  color: var(--c-danger);
  margin-bottom: 20px;
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.error-icon { flex-shrink: 0; display: inline-flex; }

/* Плашка ошибки живёт до закрытия крестиком: сообщение о сорванной загрузке нужно
   успеть прочитать и передать в поддержку. Поэтому — колонка: строка сообщения,
   раскрывающиеся подробности, кнопка повтора. */
.error-note { flex-direction: column; gap: 10px; }
/* Нейтральный тон той же плашки: сообщение об отмене — не ошибка, красная рамка
   и треугольник тревоги заставляли бы искать несуществующую проблему. */
/* Рамку выводим из --c-info: отдельного --c-info-line в токенах нет, а хардкод
   цвета разъехался бы с темой. */
.error-note.is-info {
  background: var(--c-info-bg); color: var(--c-info);
  border-color: color-mix(in srgb, var(--c-info) 35%, transparent);
}
/* Тон «сработало правило, а не поломка» — исчерпанная проба. Красный здесь врал:
   человек ничего не сломал, у пробного доступа просто закончился объём, и звать
   его чинить нечего. Рамку выводим из --c-warn (отдельного --c-warn-line нет). */
.error-note.is-warn {
  background: var(--c-warn-bg); color: var(--c-warn);
  border-color: color-mix(in srgb, var(--c-warn) 38%, transparent);
}
/* В светлой теме --c-warn это бронза #7A6614: сам текст читается (AA), но заголовок
   сообщения должен быть заметнее подложки — берём основной цвет чернил. */
.error-note.is-warn .error-note__text { color: var(--label); }
.error-note__head { display: flex; align-items: flex-start; gap: 8px; width: 100%; }
.error-note__text { flex: 1; min-width: 0; word-break: break-word; }
.error-note__close {
  flex: none;
  background: none;
  border: 0;
  padding: 2px;
  margin: -2px -2px 0 0;
  color: inherit;
  opacity: .65;
  cursor: pointer;
  display: inline-flex;
}
.error-note__close:hover { opacity: 1; }
.error-note__close:focus-visible { outline: 2px solid var(--c-danger); outline-offset: 2px; }
.error-note__details { font-size: 13px; }
.error-note__details > summary { cursor: pointer; opacity: .8; }
.error-note__details > summary:hover { opacity: 1; }
.error-note__pre {
  margin: 8px 0 0;
  padding: 10px;
  background: var(--c-bg);
  border: 1px solid var(--c-danger-line);
  border-radius: var(--radius-sm);
  font-family: var(--font-mono, monospace);
  font-size: 12px;
  line-height: 1.5;
  color: var(--c-ink);
  white-space: pre-wrap;
  word-break: break-word;
  user-select: all;
}
.error-note__copy,
.error-note__retry { align-self: flex-start; margin-top: 8px; }
/* Тем же элементом бывает ССЫЛКА (письмо в поддержку при исчерпанной пробе):
   у ссылки нет кнопочных умолчаний, задаём их явно — иначе она проваливается
   в строку текста и не читается как действие. */
a.error-note__retry {
  display: inline-flex; align-items: center;
  min-height: 40px;                 /* область попадания на телефоне */
  text-decoration: none;
  color: var(--label);
}

/* ── Иконки (набор Lucide, наследуют цвет текста) ─────────────────────────── */
.ic  { width: 18px; height: 18px; display: inline-block; vertical-align: -0.18em; flex: none; }
.ic-sm { width: 14px; height: 14px; display: inline-block; vertical-align: -0.15em; flex: none; }
.ic-lg { width: 40px; height: 40px; display: block; flex: none; }

/* ── Forms ────────────────────────────────────────────────────────────────── */
.auth-form { display: flex; flex-direction: column; gap: 16px; }
.form-fields { display: flex; flex-direction: column; gap: 14px; }

.input-group { display: flex; flex-direction: column; gap: 6px; }

/* только МЕТКА поля (прямой ребёнок) — НЕ label.preset-card внутри сетки пресетов */
.input-group > label {
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  color: var(--label2);
  letter-spacing: .08em;
  text-transform: uppercase;
}

.optional { font-weight: 400; color: var(--label4); }

.input-group input {
  width: 100%;
  padding: 12px 14px;
  /* Контур элемента управления, а не разделитель блоков: WCAG 1.4.11 требует
     3:1. Прежний --separator (= --c-line) давал 1.23 на белом — поле было видно
     только по плейсхолдеру (GOL-365). */
  border: 1.5px solid var(--c-control-line);
  border-radius: var(--radius-sm);
  font-size: 16px;
  font-family: inherit;
  color: var(--label);
  background: var(--surface);
  transition: border-color 0.15s, box-shadow 0.15s;
  outline: none;
}
.input-group input:focus {
  border-color: var(--accent);
  box-shadow: var(--focus-ring);
}
.input-group input::placeholder { color: var(--label4); }

/* ── Buttons — фирменный CTA: капс, резкие углы, светлая=бронза+белый,
     тёмная=золото+обсидиан (канон CTA light v2 / dark) ─────────────────────── */
.btn-primary {
  width: 100%;
  min-height: 48px;              /* touch-таргет ≥48 (канон mobile) */
  padding: 15px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--accent);
  color: var(--c-gold-contrast);
  border: 1px solid var(--accent);
  border-radius: var(--radius-sm);   /* главный CTA — канон ui-kit sm (4px), капс-моно сохранён */
  font-size: 14px;
  font-weight: 700;
  font-family: var(--font-mono);
  letter-spacing: .10em;
  text-transform: uppercase;
  cursor: pointer;
  box-shadow: var(--elev-1);
  transition: background 0.2s, border-color 0.2s, box-shadow 0.2s, transform 0.1s;
}
.btn-primary:hover:not(:disabled) {
  /* Канон Макса: яркий золотой CTA на hover СВЕТЛЕЕТ (не темнеет) — заливка идёт
     в яркий стоп #EBC554, тёмный текст (--c-gold-contrast) остаётся читаемым
     (~10:1). Прежний уход в тёмную бронзу давал тёмный-на-тёмном. */
  background: var(--c-gold-mid);
  border-color: var(--c-gold-mid);
  color: var(--c-gold-contrast);
  box-shadow: var(--elev-2), 0 0 18px rgba(153,128,24,.22);
}
.btn-primary:active:not(:disabled) { transform: translateY(1px); box-shadow: none; }
.btn-primary:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }

.btn-secondary {
  padding: 10px 20px;
  background: var(--bg);
  color: var(--label);
  border: 1.5px solid var(--separator);
  border-radius: var(--radius-sm);
  font-size: 15px;
  font-weight: 500;
  font-family: inherit;
  cursor: pointer;
  transition: background 0.15s;
}
.btn-secondary:hover { background: var(--separator); }

/* Хедер — общий для всех экранов: .app-topbar в components.css. */

/* ── Main Content ──────────────────────────────────────────────────────────
   Десктоп: рабочая карточка (пресеты 3 в ряд) — главный экран влезает в фолд
   ноутбука без вертикального скролла (канон Fold-first 1440×800).
   Потолок 960px намеренный: на широком мониторе форма во всю ширину выглядит
   разреженной, компактный блок в центре читается лучше. */
.main-content {
  max-width: 820px;   /* шире прежних 680: запас под рост числа пресетов; вровень с .wrap отчёта */
  /* width:100% обязателен: .app-shell — колоночный flex, и без явной ширины блок
     ужимается по содержимому. Замер на 1024px: было 583px вместо заявленных 820 —
     карточка годами была уже, чем задумано, на планшетах и небольших ноутбуках.
     max-width при этом продолжает ограничивать сверху. */
  width: 100%;
  margin: 0 auto;
  padding: 20px 20px 48px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
/* Расширения на широких экранах НЕТ намеренно: 960 читалось растянуто, минус 15%
   даёт 816 — это те же базовые 820, поэтому отдельный медиазапрос не нужен.
   Карточка одинаковой ширины на любом мониторе. */
/* Шире 960 НЕ идём. Пробовали 1120 на широком мониторе: воздуха по бокам меньше,
   но плитки пресетов растягиваются до ~337px, подпись занимает треть строки, и
   панель выглядит разреженной — хуже, чем компактная карточка в центре. Пустое
   поле по краям на 2000px нормально, растянутая форма — нет. */

/* ── Шапка карточки: заголовок слева + статус-плашка справа (экономия фолда) ── */
.card-head {
  display: flex;
  /* center, а не flex-start: заголовок и статус-плашка — два элемента одной строки,
     при flex-start плашка вставала ниже базовой линии заголовка. */
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 18px;
}
/* Базис 240, а не 520: при 520 заголовок и статус-плашка не помещались в строку
   карточки (685px по факту) и плашка падала на второй ряд — шапка карточки росла до
   83px и выталкивала кнопку за сгиб ноутбука. 240 + плашка 410 + зазор 12 = 662 ≤ 685:
   оба стоят в одну линию (канон симметрии), на узких экранах перенос срабатывает сам. */
.card-head__text { flex: 1 1 240px; min-width: 0; }
.card-head .card-title { margin-bottom: 4px; }
.card-head .card-desc { margin-bottom: 0; }
/* Без padding-top: он подгонял плашку под описание, которого в шапке больше нет,
   и она висела ниже заголовка. Выравнивание задаёт align-items у .card-head. */
.card-head .status-bar { flex: none; }
@media (max-width: 700px) {
  .card-head { flex-direction: column; gap: 10px; }
  /* в колонке flex-basis 520px стал бы ВЫСОТОЙ — сбрасываем */
  .card-head__text { flex: 0 0 auto; }
  .card-head .status-bar { padding-top: 0; }
}

/* ── Остаток пробной квоты (клиент на trial) ────────────────────────────────
   Спокойный информационный блок над зоной загрузки: сколько записей осталось.
   Сознательно НЕ статус-плашка: та капсом и цветом кричит о состоянии железа,
   а здесь счётчик, который человек сверяет глазами и идёт работать дальше.
   Тревожным блок становится ровно в одном случае — записи кончились (.is-out). */
.quota-note {
  margin-bottom: 14px;
  padding: 11px 14px;
  background: var(--c-surface-2);
  border: 1px solid var(--c-line);
  border-left: 2px solid var(--c-gold-line);   /* золотой торец — тот же приём, что на /trial */
  border-radius: var(--radius-sm);
}
.quota-note__row {
  display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 9px;
}
/* Часы и подпись — всегда в одной строке. Иконка не переносится (flex: none),
   а группа с подписью обязана УЖИМАТЬСЯ до остатка ширины: без этого она
   требует свою «естественную» ширину целиком и выталкивает часы на строку выше
   — значок повисает один, без текста, и не значит ничего.
   Растягиваться при этом не должна (flex-grow: 0): растянутая группа уводит «?»
   к правому краю, за полэкрана от подписи, к которой он относится. */
.quota-note__head{ flex: 0 1 auto; }
.quota-note__ic { display: inline-flex; align-self: center; color: var(--c-gold-strong); flex: none; }
/* Подпись со сроком + «?» — одна нерасторжимая группа: во flex-строке кнопка
   считается отдельным элементом и на узком экране уезжала на свою строку.
   flex: 1 — группа занимает остаток строки после часов и не выталкивает их на
   отдельную строку, когда подписи не хватает места. */
.quota-note__head{
  display: inline-flex; align-items: baseline; min-width: 0;
}
/* Подпись переносится ВНУТРИ группы, кнопка остаётся приклеенной к её концу:
   так «?» садится за последним словом даже когда подпись заняла две строки. */
.quota-note__head .quota-note__title{ min-width: 0; }
.quota-note__head .help-q{ align-self: center; }
.quota-note__title {
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 700;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--label2);
}
@media (max-width: 430px){
  /* На узких экранах разрядка съедает больше места, чем даёт читаемости:
     «ПРОБНЫЙ ДОСТУП ДО 2026-08-14» с .08em не помещается в строку и выталкивает
     иконку часов наверх. Гарнитура и капс остаются, ужимается только трекинг. */
  .quota-note__title{ letter-spacing: .02em; }
}
/* Число — вперёд по контрасту: остаток и есть то единственное, ради чего блок
   существует, подпись слева лишь называет, к чему это число относится. */
.quota-note__count { font-size: 13.5px; color: var(--label3); margin-left: auto; }
.quota-note__count b { font-weight: 700; color: var(--label); font-variant-numeric: tabular-nums; }

/* Переключатель сворачивания (GS-146). Зона 44×44 растит кнопку, а не строку:
   отрицательные поля гасят рост высоты, палец получает положенное. Шеврон
   поворачивается в свёрнутом состоянии (см. блок .quota-collapsed ниже).
   Тревоге (data-alarm) переключатель не положен — сворачивать нечего. */
.quota-note__toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; margin: -12px -10px -12px 0; padding: 0;
  appearance: none; background: none; border: 0; cursor: pointer;
  color: var(--label3); align-self: center; flex: none;
  -webkit-tap-highlight-color: transparent;
}
/* author display бьёт UA-правило [hidden] — без этого перечня кнопка видна
   и без скриптов, а нажимать её при выключенном JS некому. */
.quota-note__toggle[hidden] { display: none; }
.quota-note__toggle svg { width: 17px; height: 17px; transition: transform .15s ease; }
.quota-note__toggle:hover { color: var(--c-gold-strong); }
.quota-note__toggle:focus-visible { box-shadow: var(--focus-ring); border-radius: var(--radius-xs); }
.quota-note[data-alarm] .quota-note__toggle { display: none; }

.quota-note__bar {
  height: 3px;
  margin-top: 9px;
  background: var(--c-surface-3);
  border-radius: var(--radius-xs);
  overflow: hidden;
}
.quota-note__bar > span {
  display: block; height: 100%;
  background: var(--c-gold);
  transition: width .3s ease;
}
/* Израсходовано — справка под полосой, не главное число: главное стоит выше
   («осталось 12 ч 30 мин»). Отсюда и размер мельче, и приглушённый цвет. */
.quota-note__spent {
  margin: 6px 0 0;
  font-size: 12px; line-height: 1.4;
  color: var(--label4);
  font-variant-numeric: tabular-nums;
}
.quota-note__hint {
  margin-top: 7px;
  font-size: 12.5px; line-height: 1.45;
  color: var(--label4);
}
.quota-note__hint a { color: var(--c-gold-strong); font-weight: 600; }

/* .upload-eta удалён 19.08.2026 вместе с абзацем под кнопкой загрузки: ответ
   «сколько ждать и можно ли закрыть вкладку» переехал в справку по странице
   (help.index.*). Постоянному пользователю сервиса этот текст нужен один раз, а
   висел он на экране всегда — см. комментарий на месте удаления в index.html. */

/* Продление доступа — КНОПКА, а не ссылка в потоке текста (решение владельца
   15.08.2026). Экран истёкшего доступа был тупиком ровно в тот момент, когда
   человек готов платить: «Продлить доступ» стояло 12.5px обычной ссылкой в
   конце абзаца — то есть выглядело как сноска, а не как выход из положения.
   Заливкой не красим: рядом уже стоит золотая кнопка «Расшифровать запись», и
   два золотых пятна на экране спорят между собой (дисциплина акцента). Здесь
   обводка — действие заметное, но подчинённое главному. */
.quota-note__hint a.quota-note__cta{
  display: inline-flex; align-items: center; gap: 6px;
  margin-top: 8px; padding: 8px 14px; min-height: 36px;
  font-family: var(--font-mono);
  font-size: 12.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--c-gold-strong); text-decoration: none;
  border: 1px solid var(--c-gold); border-radius: var(--radius-sm);
  background: var(--c-gold-bg);
  transition: background .15s ease, border-color .15s ease;
}
.quota-note__hint a.quota-note__cta:hover{ background: var(--c-gold); color: var(--c-gold-contrast); }
.quota-note__hint a.quota-note__cta:focus-visible{ box-shadow: var(--focus-ring); }
/* Имя организации в плашке — подстраховка мобильной шапки, где оно не помещается.
   Порог тот же (760px), что и у скрытия .app-orgname в шапке: разойдись они —
   между 560 и 760 контур не показывался бы нигде. Тон справочный: это ответ на
   вопрос «чья это квота», а не заголовок. */
.quota-note__org{ display: none; }
@media (max-width: 760px){
  .quota-note__org{
    display: block;
    flex-basis: 100%;
    order: 10;                    /* под строкой со сроком, до счётчика */
    font-size: 12px;
    color: var(--label4);
  }
}

/* Название тарифа — капсулой рядом со сроком. В отличие от имени организации
   показывается НА ВСЕХ ширинах: организация продублирована в шапке, а тариф
   не написан больше нигде в продукте. Моноширинный в верхнем регистре — тот же
   приём, что у остальных служебных меток интерфейса: читается как ярлык
   состояния, а не как часть фразы про доступ. */
.quota-note__plan{
  display: inline-flex; align-items: center;
  padding: 1px 7px;
  font-family: var(--font-mono);
  font-size: 10.5px; font-weight: 700; letter-spacing: .06em;
  text-transform: uppercase; white-space: nowrap;
  color: var(--label3);
  border: 1px solid var(--c-line); border-radius: 999px;
}
@media (max-width: 760px){
  /* Под строкой со сроком вместе с организацией, а не между ними: иначе на
     узком экране капсула вклинивается в название и рвёт дату. */
  .quota-note__plan{ order: 11; }
}

/* Записи кончились: блок перестаёт быть справкой и становится препятствием —
   тон меняем на предупреждающий, но не на «ошибку»: человек ничего не сломал,
   у пробы просто закончился объём. */
.quota-note.is-out {
  background: var(--c-warn-bg);
  border-color: color-mix(in srgb, var(--c-warn) 35%, transparent);
  border-left-color: var(--c-warn);
}
.quota-note.is-out .quota-note__ic,
.quota-note.is-out .quota-note__count b { color: var(--c-warn); }
.quota-note.is-out .quota-note__bar > span { background: var(--c-warn); }
.quota-note.is-out .quota-note__hint { color: var(--label2); }

/* Время на исходе (ниже порога QUOTA_LOW_PCT, но ещё не ноль). Фон НЕ трогаем,
   в отличие от is-out: путь дальше открыт, и полноценная тревожная плашка на
   рабочем состоянии — ложная тревога, к которой быстро привыкают. Меняются
   только число и шкала — ровно то, на что человек и смотрит.
   Цвет здесь не единственный признак: рядом с числом стоит словесное «скоро
   закончится» (см. index.html и quotaRender). */
.quota-note.is-low .quota-note__count b { color: var(--c-warn); }
.quota-note.is-low .quota-note__bar > span { background: var(--c-warn); }

/* ── Свёрнутая плашка (GS-146) ────────────────────────────────────────────────
   Класс на <html> ставит head-скрипт index.html из localStorage ДО первой
   отрисовки — вспышки развёрнутого блока у свернувшего нет. Правило адресное
   (:not([data-alarm])): тревога («исчерпано», «истёк», «льготный», «архив»)
   не сворачивается ни при каком сохранённом выборе — свёрнутый вид не имеет
   права прятать сигнал, за которым плашка существует.
   Прячутся: заголовок со сроком и «i», имя организации и детали (шкала,
   расход, подсказка). Остаётся одна строка: иконка, остаток, тариф, шеврон.
   Остаток получает order/растяжение явно — иначе на ≤560px он унаследовал бы
   перенос на свою строку из мобильного правила ниже.
   flex-базис 0, а не auto: при базисе от содержимого (auto) длинная строка
   «осталось … из …» плюс капсула тарифа и шеврон превышали строку, и шеврон
   падал на вторую — wrap решается ДО того, как shrink ужимает остаток
   (проверка на стенде 31.08.2026, 390px). При базисе 0 строка не переполняется
   никогда, а излишек текста режется многоточием. */
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__head,
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__org,
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__details { display: none; }
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__count {
  order: 2; flex: 1 1 0; min-width: 0; margin-left: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__plan { order: 3; }
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__toggle { order: 4; }
.quota-collapsed .quota-note:not([data-alarm]) .quota-note__toggle svg { transform: rotate(-90deg); }

@media (max-width: 560px) {
  /* Узкий экран: счётчик переносим под подпись, иначе «осталось 3 из 3 записей»
     ломается посреди фразы и читается как обрывок. order задан явно — иначе
     счётчик встаёт перед именем организации (у того order: 10), и порядок
     чтения ломается: сначала остаток, потом чья это квота. */
  .quota-note__count { margin-left: 0; flex-basis: 100%; order: 20; }
}
@media (prefers-reduced-motion: reduce) {
  .quota-note__bar > span { transition: none; }
  .quota-note__toggle svg { transition: none; }
}

/* ── Status Bar — терминальная плашка (канон ai-status-badge эталона .ai) ──── */
.status-bar { display: flex; }

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  /* Вертикальный отступ 5px, а не 8: при 8 плашка выходила 35px против 25px у
     заголовка рядом и читалась крупнее и ниже него, хотя центры совпадали.
     Теперь высоты сопоставимы, строка выглядит единой. */
  padding: 5px 14px;
  border-radius: 0;   /* капс-моно статус-плашка — терминальный слой, острый край намеренно */
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  border: 1px solid;
}
/* Знак продукта в статус-плашке — приглушён, чтобы вести взгляд к индикатору
   состояния, а не спорить с ним. */
.status-pill__ic { display: inline-flex; align-items: center; opacity: .72; flex-shrink: 0; }
.status-pill.busy {
  background: var(--c-warn-bg);
  color: var(--c-warn);
  border-color: color-mix(in srgb, var(--c-warn) 35%, transparent);
}
/* .status-pill.ready удалён вместе с зелёной плашкой «Готов к обработке»:
   свободное состояние теперь показывается отсутствием плашки, а не отдельным
   цветом. Единственное оставшееся состояние — busy. */

.pulse {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--c-warn);
  box-shadow: 0 0 7px var(--c-warn);
  animation: pulse 1.4s ease-in-out infinite;
  flex-shrink: 0;
}
@keyframes pulse {
  0%, 100% { opacity: 1; transform: scale(1); }
  50% { opacity: 0.4; transform: scale(0.8); }
}

/* .dot-green убран следом за плашкой «Готов»: он рисовался только в ней. */

/* ── Card ─────────────────────────────────────────────────────────────────── */
.card {
  background: var(--surface);
  border: 1px solid var(--separator);
  border-radius: var(--radius-lg);
  padding: 24px 26px 26px;
  box-shadow: var(--elev-1);
}
.card-title {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 21px;
  font-weight: 800;
  letter-spacing: .01em;
  text-transform: uppercase;
  margin-bottom: 6px;
}
/* «?» рядом с ЗАГОЛОВКОМ крупнее строчной help-q — выравниваем к кеглю заголовка */
.card-title .help-q { width: 18px; height: 18px; font-size: 12px; }
.card-desc {
  font-size: 14px;
  color: var(--label3);
  margin-bottom: 24px;
}

/* ── Drop Zone — терминальный приёмник: сетка-фон + угловые скобки бренда.
     Компактная горизонтальная полоса (иконка + тексты) — экономия фолда. ──── */
.drop-zone {
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--radius-md);   /* крупный приёмник — канон md; угловые скобки держат брутал-акцент */
  display: flex;
  align-items: center;
  justify-content: center;           /* группа иконка+текст по центру зоны, а не в углу */
  column-gap: 16px;
  min-height: 112px;
  padding: 20px 22px;
  text-align: left;
  cursor: pointer;
  transition: border-color 0.2s, background 0.2s, box-shadow 0.25s;
  margin-bottom: 18px;
  position: relative;
  background-image:
    linear-gradient(to right, rgba(153,128,24,.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(153,128,24,.045) 1px, transparent 1px);
  background-size: 24px 24px;
}
:root[data-theme="dark"] .drop-zone,
:root[data-theme="night"] .drop-zone {
  background-image:
    linear-gradient(to right, rgba(235,197,84,.05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(235,197,84,.05) 1px, transparent 1px);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .drop-zone {
    background-image:
      linear-gradient(to right, rgba(235,197,84,.05) 1px, transparent 1px),
      linear-gradient(to bottom, rgba(235,197,84,.05) 1px, transparent 1px);
  }
}
/* угловые маркеры-скобки — square brackets айдентика GOLDJAXE */
.drop-zone::before, .drop-zone::after {
  content: '';
  position: absolute;
  width: 14px; height: 14px;
  border: 2px solid var(--c-gold-line);
  transition: border-color 0.2s;
  pointer-events: none;
}
/* Г-образные скобки сдвинуты внутрь на радиус (md 8px) — обнимают скруглённый
   угол, а не висят вне контура. Свой малый радиус повторяет кривизну угла. */
.drop-zone::before { top: 5px; left: 5px; border-right: 0; border-bottom: 0; border-top-left-radius: var(--radius-xs); }
.drop-zone::after  { bottom: 5px; right: 5px; border-left: 0; border-top: 0; border-bottom-right-radius: var(--radius-xs); }
.drop-zone:hover, .drop-zone.drag-over {
  border-color: var(--accent);
  background-color: var(--accent-bg);
  box-shadow: 0 0 0 1px var(--c-gold-line), 0 0 24px rgba(153,128,24,.12);
}
.drop-zone:hover::before, .drop-zone:hover::after,
.drop-zone.drag-over::before, .drop-zone.drag-over::after { border-color: var(--accent); }
.drop-zone.has-file {
  border-color: var(--green);
  background-color: var(--c-success-bg);
  box-shadow: none;
}
.drop-zone.has-file::before, .drop-zone.has-file::after { border-color: var(--green); }

.drop-icon { flex: none; margin: 0; color: var(--label3); display: flex; justify-content: center; }
.drop-icon .ic-lg { width: 34px; height: 34px; }
.drop-zone:hover .drop-icon, .drop-zone.drag-over .drop-icon { color: var(--accent); }
.drop-text { display: flex; flex-direction: column; }   /* заголовок + подпись + инфо о файле — одна колонка */
.drop-title { font-size: 15px; font-weight: 600; color: var(--label); margin-bottom: 3px; }
.drop-sub { font-family: var(--font-mono); font-size: 13px; letter-spacing: .05em; color: var(--label4); text-transform: uppercase; }

/* ── Приём по ссылке (GOL-367) ─────────────────────────────────────────────
   Зона ссылки занимает МЕСТО зоны файла, а не добавляется к ней: та же рамка,
   та же min-height 112px, те же угловые скобки, тот же отступ снизу. Подмена
   на месте не двигает ни кнопку, ни блок хода работы — макет не прыгает.
   Все размеры продублированы намеренно, а не вынесены в общий класс: зона
   файла — кликабельная область с курсором и hover, зона ссылки — форма ввода,
   и общий класс склеил бы два разных поведения. */
.link-zone {
  border: 1px dashed var(--c-line-strong);
  border-radius: var(--radius-md);
  min-height: 112px;
  /* По бокам отступ тот же, что у зоны файла (22px), сверху и снизу меньше:
     содержимое здесь выше — поле плюс строка разбора, — и при 20px зона
     вырастала до 116px, отчего кнопка уезжала на 4px при каждом переключении.
     Замерено в браузере, а не прикинуто на глаз.

     🔴 СНИЗУ 14 - 26 = скомпенсированный отступ (замечание владельца
     30.08.2026: «поле не по центру блока»). Так и было: под полем лежит
     .link-zone__parsed — она скрыта visibility, но НАМЕРЕННО держит высоту
     (26px вместе с gap), чтобы кнопка «Расшифровать» не дёргалась в момент,
     когда ссылка разобралась. justify-content: center честно считал этот
     невидимый резерв содержимым, и поле уезжало вверх: 26px сверху против
     52px снизу.

     Убирать резерв нельзя — вернётся дёрганье, которое им и лечили. Компенсация
     висит на самой строке разбора (.link-zone__parsed[hidden] ниже): пока она
     невидима, её высота гасится отрицательным margin-bottom, и центрирование
     считает только видимое. Появилась строка — margin снимается, место уже
     забронировано, ничего не двигается. */
  padding: 14px 22px;
  margin-bottom: 18px;
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 6px;
  background-image:
    linear-gradient(to right, rgba(153,128,24,.045) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(153,128,24,.045) 1px, transparent 1px);
  background-size: 24px 24px;
  transition: border-color .2s;
}
:root[data-theme="dark"] .link-zone,
:root[data-theme="night"] .link-zone {
  background-image:
    linear-gradient(to right, rgba(235,197,84,.05) 1px, transparent 1px),
    linear-gradient(to bottom, rgba(235,197,84,.05) 1px, transparent 1px);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .link-zone {
    background-image:
      linear-gradient(to right, rgba(235,197,84,.05) 1px, transparent 1px),
      linear-gradient(to bottom, rgba(235,197,84,.05) 1px, transparent 1px);
  }
}
.link-zone::before, .link-zone::after {
  content: '';
  position: absolute;
  width: 14px; height: 14px;
  border: 2px solid var(--c-gold-line);
  transition: border-color .2s;
  pointer-events: none;
}
.link-zone::before { top: 5px; left: 5px; border-right: 0; border-bottom: 0; border-top-left-radius: var(--radius-xs); }
.link-zone::after  { bottom: 5px; right: 5px; border-left: 0; border-top: 0; border-bottom-right-radius: var(--radius-xs); }
/* Ссылка разобрана — зелёный, как у принятого файла (.drop-zone.has-file):
   один и тот же смысл обязан выглядеть одинаково на обоих путях. */
.link-zone.is-ok { border-color: var(--green); }
.link-zone.is-ok::before, .link-zone.is-ok::after { border-color: var(--green); }
.link-zone.is-bad { border-color: var(--red); }
.link-zone.is-bad::before, .link-zone.is-bad::after { border-color: var(--red); }

.link-zone__row { display: flex; align-items: center; gap: 10px; }
.link-zone__ic { flex: none; color: var(--label3); display: flex; }
.link-zone__ic .ic { width: 22px; height: 22px; }
.link-zone__input {
  flex: 1 1 auto;
  min-width: 0;                        /* иначе длинная ссылка распирает карточку на 390 */
  font-family: var(--font-mono);
  font-size: 16px;                     /* 16px, а не 14: меньше — и Safari на телефоне зумит страницу при фокусе */
  background: transparent;
  border: 0;
  outline: 0;
  color: var(--label);
  padding: 6px 0;
  border-bottom: 1px solid var(--c-line-strong);
}
.link-zone__input::placeholder { color: var(--label4); }
/* Фокус виден УТОЛЩЁННЫМ подчёркиванием, а не рамкой вокруг поля: рамка внутри
   пунктирной зоны читается как вторая коробка и спорит с угловыми скобками.
   Требование доступности при этом соблюдено — признак фокуса заметный и не
   держится на одном цвете. */
.link-zone__input:focus,
.link-zone__input:focus-visible {
  border-bottom-color: var(--accent);
  border-bottom-width: 2px;
  padding-bottom: 5px;   /* компенсируем прирост рамки — поле не дёргается */
  outline: none;
}

.link-zone__parsed {
  display: flex; align-items: center; gap: 8px; font-size: 13.5px;
  /* nowrap, а не wrap: перенос на вторую строку растил зону, и кнопка уезжала
     вниз ровно в тот момент, когда ссылка разобралась. Длинное имя обрезается
     многоточием (см. b ниже) — высота остаётся одной строкой при любом имени. */
  flex-wrap: nowrap; min-width: 0;
  /* Высота строки задана явно: пока разбора нет, элемент скрыт visibility и
     пуст — без min-height он схлопнулся бы в ноль, и резерв места не сработал. */
  min-height: 20px;
}
.link-zone__parsed b {
  color: var(--label); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0;
}
.link-zone__ok { color: var(--green); display: inline-flex; flex: none; }
.link-zone__ok .ic { width: 16px; height: 16px; }
.link-zone__src {
  font-family: var(--font-mono); font-size: 12.5px; letter-spacing: .04em;
  text-transform: uppercase; color: var(--label4);
  border: 1px solid var(--c-line); border-radius: var(--radius-xs); padding: 1px 6px;
  flex: none;
}
/* Ряд площадок под полем ссылки: короткие метки в рамках вместо перечисления
   фразой (16.08.2026). Человек ищет глазами своё название, а не читает список.
   Метки НЕ кнопки и не ссылки — это справка о принимаемых источниках, поэтому
   ни курсора-указателя, ни наведения: акцентом красим то, что требует действия.
   Высота ряда учтена в общей высоте зоны — см. комментарий у .link-zone__hint:
   зона ссылки обязана совпадать по высоте с зоной файла, иначе кнопка под ними
   дёргается при переключении. */
/* Одна строка-подсказка под полем: что произойдёт после вставки. Знак «i» — в
   той же строке, а не под ней: он часть фразы («подробности здесь»), и своей
   строкой висел бы отдельным элементом непонятного назначения.
   Тише поля и кнопок — это пояснение, а не орган управления (эвристика №8). */
/* 🔴 БЛОЧНЫЙ КОНТЕЙНЕР, А НЕ flex: знак «i» обязан идти СРАЗУ ЗА фразой, как
   часть предложения. Во flex-строке подсказка занимает всю ширину и выталкивает
   знак на свою строку — он повисает отдельным элементом непонятного назначения
   (замер на 412px: подсказка 1 строка, знак под ней вторым рядом).
   Здесь оба — inline, поэтому знак прижимается к концу текста и переносится
   вместе с последним словом. */
.link-zone__srcs {
  margin-top: 8px; min-height: 22px;
  font-size: 12.5px; line-height: 1.45; color: var(--label3);
}
.link-srcs__note { display: inline; }
/* Парное к своему display — иначе hidden не сработает (см. .src-switch выше). */
.link-zone__srcs[hidden] { display: none; }

/* Правила .link-src и .link-src--more удалены 30.08.2026 вместе с перечнем
   площадок: страница перестала их называть, потому что поддержку решает узел
   бота, а не фронт (см. комментарий в index.html). Метка РАСПОЗНАННОЙ площадки
   после вставки — это другой класс, .link-zone__src, и он живёт ниже. */
.link-zone__srcs .help-q { margin-left: 1px; }

.link-zone__hint {
  font-size: 13px; color: var(--label4); line-height: 1.3;
  /* РОВНО одна строка в обычном состоянии, с обрезкой лишнего: высота зоны
     обязана совпасть с зоной файла (112px) с точностью до пикселя — любая
     разница видна как дёрганье кнопки при переключении источника. Полный текст
     подсказки живёт в title, он нужен один раз, при первом знакомстве. */
  height: 1.3em;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* ОТКАЗ — исключение: причину человек обязан прочитать целиком, обрезать её
   многоточием нельзя. Даём две строки и убираем строку разбора из потока —
   при ошибке разбора всё равно нет, и высота зоны остаётся прежней. */
.link-zone.is-bad .link-zone__hint {
  height: auto; max-height: 2.6em;
  white-space: normal; text-overflow: clip;
}
.link-zone.is-bad .link-zone__parsed { display: none !important; }
.link-zone__hint.is-bad { color: var(--red); }

/* display:flex перебивает атрибут hidden — возвращаем ему силу. Без этого обе
   зоны (файл и ссылка) видны одновременно: поймано на первом же скриншоте, и
   ровно об этот угол уже споткнулся прототип. */
.link-zone[hidden] { display: none !important; }
/* Та же причина у зоны файла: её прячет переключатель источника. */
.drop-zone[hidden] { display: none !important; }

/* Строка разбора СКРЫВАЕТСЯ, НО ОСТАЁТСЯ В ПОТОКЕ (visibility, а не display).
   С display:none зона теряла её высоту, и в момент, когда ссылка разобралась,
   кнопка «Расшифровать» дёргалась вниз на 6px. Место под строку резервируется
   заранее — подмена и разбор не двигают ничего. */
/* 🔴 …и пока она невидима, её высота НЕ УЧАСТВУЕТ в центрировании: отрицательный
   margin гасит 20px строки плюс 6px gap. Без этого justify-content: center
   считал невидимый резерв содержимым, и поле ввода уезжало вверх — 26px сверху
   против 52px снизу (замечание владельца 30.08.2026, замер на 412px).
   Место при этом остаётся забронированным: строка по-прежнему в потоке, просто
   её вклад в высоту скомпенсирован. Появилась — margin снят, ничего не прыгает. */
.link-zone__parsed[hidden] {
  display: flex !important; visibility: hidden;
  margin-bottom: -26px;
}

/* Переключатель источника. Тихая строка, а не кнопка: ссылками пользуются
   5–15%, и второй яркий элемент рядом с главным действием соревновался бы
   с ним за внимание. */
/* 🔴 ИСТОЧНИКИ — КНОПКИ, А НЕ ТИХАЯ СТРОКА (решение владельца 29.08.2026).
   Прежде это была подпись цветом --label4 (самый бледный) высотой 44px, стоящая
   ВНУТРИ пояснения к зоне файла. Но по замеру частоты, названному владельцем,
   «послать бота» и «создать встречу» — второе и третье по частоте действия
   сервиса, сразу после загрузки файла. Два из четырёх частых путей выглядели
   справочным текстом: это нарушение и §0 канона («частое — на виду»), и закона
   Фиттса (мелкая бледная цель), и эвристики №4 (частое действие оформлено как
   пояснение).

   Стали равноправной парой кнопок 48px с рамкой. Вторичный стиль сохранён
   намеренно — прежний довод автора верен: второй ЯРКИЙ элемент рядом с
   «Расшифровать» соревновался бы с главным действием. Меняется вес, а не
   иерархия: заметно, но тише золотой кнопки. */
.src-switch {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  gap: var(--sp-2); align-items: stretch; margin: 2px 0 18px;
}
/* Парное к своему display: без него атрибут hidden молча не сработает — ряд
   остаётся на экране, когда его место занимает приглашение бота. Та же ловушка,
   что ловили у .preset-grid; сторожит tests/test_hidden_attribute_works.py. */
.src-switch[hidden] { display: none; }
.src-switch__btn {
  background: var(--c-surface-2, transparent);
  border: 1px solid var(--separator); cursor: pointer;
  border-radius: var(--radius-sm);
  min-height: 48px; padding: 10px 12px;
  font-family: var(--font-mono); font-size: 13px; letter-spacing: .05em;
  text-transform: uppercase; color: var(--label2);
  display: inline-flex; align-items: center; justify-content: center; gap: 7px;
  transition: color .2s, border-color .2s, background .2s;
  /* Одна строка при любой подписи. На 390px «или выберите файл с устройства»
     переносилось на вторую строку, блок рос с 38 до 54px — и кнопка
     «Расшифровать» уезжала на 18px при каждом переключении источника.
     На узком экране подпись укорачивается (медиазапрос ниже), а не переносится. */
  white-space: nowrap;
  min-width: 0;
}
.src-switch__btn:hover {
  color: var(--accent); border-color: var(--c-gold-line);
  background: var(--c-gold-bg);
}
/* Разделитель между кнопками больше не нужен — их разводит сетка. */
.src-switch__sep { display: none; }
.src-switch__btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.src-switch__btn .ic { width: 15px; height: 15px; }
/* На узком экране подпись короче: длинный вариант не влезал, а переносить его
   нельзя — высота блока меняется и двигает кнопку. Выбор подписи делает app.js
   по SRC_LABEL_TIGHT; порог здесь обязан совпадать с ним, иначе шрифт ужмётся
   раньше или позже смены текста. 900px — с тех пор, как источники стали парой
   кнопок в половину ширины (29.08.2026). */
@media (max-width: 900px) {
  .src-switch__btn { font-size: 12px; letter-spacing: .03em; padding: 10px 8px; }
}
/* Костыль ::after, дотягивавший зону нажатия до 48px, удалён 29.08.2026: кнопки
   получили настоящий min-height: 48px, когда перестали быть тихой строкой.
   Невидимая область поверх соседей нужна была ровно потому, что реальная высота
   равнялась 37px и растить её через padding было нельзя — блок двигал кнопку
   «Расшифровать». Теперь высота честная, и подменять её псевдоэлементом незачем. */
.src-switch__btn { position: relative; }

.file-info { margin-top: 8px; }
.file-name { font-size: 14px; font-weight: 600; color: var(--label); }
.file-size { font-size: 13.5px; color: var(--label4); margin-top: 2px; }

/* ── Progress ─────────────────────────────────────────────────────────────── */
/* ══════════════════════════════════════════════════════════════════════════
   Ход работы — ОДИН блок на обе фазы: закачку и обработку.
   Меняется содержимое сцены и подписи ячеек; рамка, высота и дорожка общие,
   поэтому переход между фазами не двигает кнопку и соседние элементы.
   Цвет — единственный признак смены фазы, читается периферийным зрением:
   золото на закачке, зелёный на обработке.
   ══════════════════════════════════════════════════════════════════════════ */
.progress-wrap { margin: 12px 0; }

.gauge {
  --g-line: var(--accent);
  --g-soft: color-mix(in srgb, var(--g-line) 20%, transparent);
  /* Неоновая подсветка двумя слоями: ближний даёт плотность, дальний ореол.
     Компенсирует тонкую линию 1.5px — без свечения она читалась бы бледнее
     прежней двухпиксельной. */
  --g-glow: color-mix(in srgb, var(--g-line) 75%, transparent);

  display: grid;
  /* minmax(0, 1fr), а не 1fr: по умолчанию колонка не сжимается ниже min-content,
     и конвейер этапов раздувал её до 348px внутри блока шириной 296px —
     содержимое вылезало за экран и давало горизонтальную прокрутку на телефоне. */
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 12px;
  align-items: stretch;
  border: 1px solid var(--separator);
  border-radius: var(--radius-md);
  padding: 7px 10px;
  background: var(--bg);
}
.gauge.is-processing { --g-line: #58B368; }   /* фаза обработки — зелёный */

.gauge__stage { min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.gauge__view  { flex: 1 1 auto; min-height: 30px; position: relative; }
.gauge__view > * { position: absolute; inset: 0; }
/* Скрытие сцены — КЛАССОМ, а не атрибутом hidden. Для <svg> присваивание
   `el.hidden = true` ставит только свойство и НЕ отражается в атрибут, поэтому
   селектор [hidden] элемент не находил: волна закачки оставалась лежать поверх
   конвейера обработки. !important — потому что ниже .gauge__svg задаёт
   display:block с той же специфичностью. */
.gauge__view .is-off { display: none !important; }

.gauge__svg { display: block; width: 100%; height: 100%; overflow: visible; }
.gauge__area { fill: var(--g-soft); stroke: none; }
.gauge__path {
  fill: none; stroke: var(--g-line); stroke-width: 1.5;
  stroke-linecap: round; stroke-linejoin: round;
  filter: drop-shadow(0 0 2px var(--g-glow)) drop-shadow(0 0 5px var(--g-glow));
}
.gauge__dot { fill: var(--g-line);
  filter: drop-shadow(0 0 3px var(--g-glow)) drop-shadow(0 0 6px var(--g-glow)); }
.gauge__ring { fill: none; stroke: var(--g-line); stroke-width: 1.2; opacity: .4; }

.gauge__bar {
  height: 4px; background: var(--separator);
  border-radius: 2px; overflow: hidden;
}
.gauge__bar i {
  display: block; height: 100%; width: 0%;
  background: var(--g-line);
  box-shadow: 0 0 6px var(--g-glow);
  /* Короткий ease-out: события прогресса приходят раз в ~400 мс, а прежние
     0.3s linear не успевали доехать — полоса вечно отставала от цифры. */
  transition: width .25s ease-out;
}

.gauge__stats { display: flex; gap: 14px; align-items: center; }
/* Подпись ПОД значением: слова перед каждым числом съедали ширину, которая нужнее
   волне. Столбик из двух строк держится в высоте блока, потому что подпись мелкая
   (8.5px) и без своего межстрочного запаса — общая высота ячейки ниже сцены. */
.gauge__cell {
  display: flex; flex-direction: column; align-items: flex-end;
  gap: 1px; line-height: 1.15;
}
.gauge__k {
  font-family: var(--font-mono); font-size: 8.5px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--label3); white-space: nowrap;
}
.gauge__v {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: 13px; font-weight: 700; color: var(--label);
  white-space: nowrap; display: inline-block; text-align: right;
}
/* Ширина зарезервирована под самое длинное значение: иначе «3 мин 15 с» →
   «3 мин 7 с» укорачивалось и соседние ячейки прыгали на каждом обновлении.
   tabular-nums выравнивает цифры между собой, но не спасает от смены их числа. */
.gauge__v[data-w="pct"]   { min-width: 4ch; }
.gauge__v[data-w="speed"] { min-width: 9ch; }
.gauge__v[data-w="eta"]   { min-width: 11ch; }

/* ── Конвейер этапов (фаза обработки) ────────────────────────────────────── */
/* min-width:0 обязателен на обоих уровнях: без него flex не даёт содержимому
   сжаться ниже «естественной» ширины, и конвейер вылезал за правый край
   экрана на телефоне, давая горизонтальную прокрутку. */
.stg { display: flex; align-items: center; height: 100%; min-width: 0; overflow: hidden; }
.stg__item { display: flex; align-items: center; gap: 6px; min-width: 0; }
.stg__dot {
  flex: none; width: 7px; height: 7px; border-radius: 50%;
  background: var(--separator);
  transition: background .3s ease, box-shadow .3s ease;
}
.stg__item.is-done .stg__dot { background: var(--g-line); opacity: .55; }
.stg__item.is-now  .stg__dot {
  background: var(--g-line); box-shadow: 0 0 7px var(--g-glow);
  animation: gaugePulse 1.4s ease-in-out infinite;
}
@keyframes gaugePulse {
  0%, 100% { opacity: .35; transform: scale(.8); }
  50%      { opacity: 1;   transform: scale(1.15); }
}
.stg__name {
  font-family: var(--font-mono); font-size: 9.5px; text-transform: uppercase;
  letter-spacing: .06em; color: var(--label3); white-space: nowrap;
}
.stg__item.is-now  .stg__name { color: var(--label); }
.stg__link {
  flex: 1 1 auto; height: 1px; min-width: 10px; margin: 0 8px;
  background: var(--separator); position: relative; overflow: hidden;
}
.stg__item.is-done + .stg__link {
  background: color-mix(in srgb, var(--g-line) 45%, transparent);
}
.stg__link.is-active::after {
  content: ''; position: absolute; inset: 0; width: 30%;
  background: linear-gradient(90deg, transparent, var(--g-line), transparent);
  animation: gaugeFlow 1.5s linear infinite;
}
@keyframes gaugeFlow { from { transform: translateX(-100%) } to { transform: translateX(400%) } }
/* Узкий экран: названия этапов не влезают, остаются точки — этап читается в ячейке.
   min-width связок тоже снимаем: при 10px четыре этапа со связками не влезали
   в 390px и конвейер вылезал за экран, давая горизонтальную прокрутку. */
@media (max-width: 700px) {
  .stg__name { display: none; }
  .stg__link { margin: 0 5px; min-width: 0; }
}

/* ── Реплика над блоком ──────────────────────────────────────────────────── */
/* Маркер — три точки «печатает», а НЕ пульсирующий кружок: рядом уже мигает
   точка текущего этапа, и две зелёные точки с некратными периодами (1,6 с и
   1,4 с) давали мерцающий узор биения. Здесь анимация про ТЕКСТ, не про
   прогресс, и цвет нейтральный — конкуренции за внимание нет. */
.gauge-say {
  display: flex; align-items: center; gap: 7px;
  margin-bottom: 7px; padding-left: 11px;   /* вровень с первой точкой конвейера */
  font-size: 12.5px; color: var(--label2);
  min-height: 18px;
}
.gauge-say[hidden] { display: none; }
.gauge-say__mark { flex: none; display: inline-flex; gap: 3px; align-items: center; }
.gauge-say__mark span {
  width: 3px; height: 3px; border-radius: 50%;
  background: var(--label3); opacity: .35;
  animation: gaugeTyping 1.4s ease-in-out infinite;
}
.gauge-say__mark span:nth-child(2) { animation-delay: .18s; }
.gauge-say__mark span:nth-child(3) { animation-delay: .36s; }
@keyframes gaugeTyping { 0%, 60%, 100% { opacity: .28 } 30% { opacity: .9 } }
.gauge-say__text {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  transition: opacity .3s ease, transform .3s ease;
}
.gauge-say__text.is-out { opacity: 0; transform: translateY(-3px); }

/* ── Ожидание в очереди ──────────────────────────────────────────────────── */
/* Отдельная фаза, а не «обработка с нулём»: пока распознаватель занят ЧУЖОЙ
   записью, сервис не знает, сколько ей осталось, — своего процента и оценки
   времени у нас нет и быть не может. Прежде здесь шла анимация расшифровки,
   и человек в очереди видел, будто его запись уже разбирают. */
.gauge.is-waiting { --g-line: var(--label3); }
.wai {
  display: flex; align-items: center; min-width: 0;
  overflow: hidden; height: 100%; padding-left: 4px;
}
.wai.is-off { display: none !important; }

/* Радар: точка и расходящиеся от неё круги — «слушаю, жду своей очереди».
   Заполняемая шкала здесь соврала бы (любой её процент выдуман), а бегущий блик
   читался как «работа идёт». Круги говорят только о готовности слушать.
   Перекликается со значком микрофона в плашке статуса рядом. */
.wai__radar { position: relative; flex: none; width: 26px; height: 26px; }
.wai__radar b, .wai__radar s {
  position: absolute; left: 50%; top: 50%;
  width: 5px; height: 5px; margin: -2.5px 0 0 -2.5px; border-radius: 50%;
}
.wai__radar b { background: var(--accent); }
.wai__radar s {
  border: 1px solid var(--accent); opacity: 0;
  animation: waiPing 2.6s ease-out infinite;
}
/* Задержки в трети периода — круги идут ровной чередой, без сгущения. */
.wai__radar s:nth-of-type(2) { animation-delay: .87s; }
.wai__radar s:nth-of-type(3) { animation-delay: 1.74s; }
@keyframes waiPing {
  from { transform: scale(1);   opacity: .75; }
  to   { transform: scale(5.2); opacity: 0; }
}
/* Луч вправо: гасящийся градиент связывает радар с остальной шириной блока,
   иначе точка висела бы в пустоте у левого края. */
.wai__line {
  flex: 1 1 auto; min-width: 0; height: 1px;
  background: linear-gradient(90deg,
    color-mix(in srgb, var(--accent) 35%, transparent), transparent);
}
/* Дорожка в ожидании — пунктир вместо заливки: та же мысль, что и у блика. */
.gauge__bar.is-indet i {
  width: 100% !important; background: transparent; box-shadow: none;
  background-image: repeating-linear-gradient(90deg,
    var(--separator) 0 6px, transparent 6px 12px);
}
/* Без движения радар остаётся статичным знаком: точка и один круг вокруг неё —
   смысл «жду» читается и без анимации. */
@media (prefers-reduced-motion: reduce) {
  .wai__radar s { animation: none; opacity: .4; transform: scale(3); }
  .wai__radar s:nth-of-type(2), .wai__radar s:nth-of-type(3) { display: none; }
}
/* В очереди у нас нет ни скорости, ни срока — прочерки в этих ячейках занимали бы
   ширину, ничего не сообщая. Прячем их, оставляя только статус. */
.gauge__stats.is-lean .gauge__cell:not(:first-child) { display: none; }

/* ── Отмена ──────────────────────────────────────────────────────────────── */
/* Намеренно тихая: мелкая серая ссылка по центру под основной кнопкой. Красной
   кнопки здесь быть не должно — действие редкое и разрушительное, оно не обязано
   притягивать взгляд. Защита от промаха — не размер, а подтверждение в два шага. */
.cancel-row {
  display: flex; justify-content: center; align-items: center;
  gap: 8px; min-height: 22px; margin-top: 8px; flex-wrap: wrap;
}
.cancel-row[hidden] { display: none; }
.cancel-link, .cancel-yes, .cancel-no {
  font: inherit; font-size: 12px; background: none; border: 0;
  padding: 4px 6px; cursor: pointer; color: var(--label3);
  border-radius: var(--radius-sm);
  transition: color .2s ease, background-color .2s ease;
}
.cancel-link:hover { color: var(--label2); }
.cancel-link:focus-visible, .cancel-yes:focus-visible, .cancel-no:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 1px;
}
.cancel-confirm {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: 12px; color: var(--label2);
}
.cancel-confirm[hidden] { display: none; }
/* Подтверждение окрашено, а первый шаг — нет: цвет появляется только тогда,
   когда человек уже выразил намерение. */
.cancel-yes { color: var(--red); font-weight: 600; }
.cancel-yes:hover { background: color-mix(in srgb, var(--red) 12%, transparent); }
.cancel-no { color: var(--label2); font-weight: 600; }
.cancel-no:hover { background: var(--separator); }

/* ── Готово ──────────────────────────────────────────────────────────────── */
.gauge.is-done { --g-line: #58B368; }
.gauge__done {
  display: flex; align-items: center; gap: 8px; min-width: 0;
  font-size: 12.5px; color: var(--label);
}
.gauge__done.is-off { display: none !important; }
.gauge__done svg { flex: none; width: 15px; height: 15px; color: var(--g-line); }
.gauge__done a { color: var(--g-line); font-weight: 600; text-decoration: none;
  border-bottom: 1px solid color-mix(in srgb, var(--g-line) 45%, transparent); }
.gauge__done a:hover { border-bottom-color: var(--g-line); }

@media (max-width: 560px) {
  .gauge { grid-template-columns: minmax(0, 1fr); }
  .gauge__stats { justify-content: space-between; gap: 8px; flex-wrap: wrap; }
  /* Резерв ширины снимаем: три ячейки (4+9+11 знаков) не помещались в 390px и
     вылезали за экран. Прокрутка хуже, чем небольшое смещение цифр, а на
     телефоне ячейки и так стоят через space-between — сдвиг почти не виден. */
  .gauge__v[data-w="pct"],
  .gauge__v[data-w="speed"],
  .gauge__v[data-w="eta"] { min-width: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .gauge *, .gauge *::before, .gauge *::after,
  .gauge-say *, .gauge-say *::before, .gauge-say *::after {
    transition: none !important; animation: none !important;
  }
}

.btn-upload { margin-top: 8px; }
/* Парное к display:inline-flex, который кнопка берёт от .btn-primary. Без него
   атрибут hidden не сработает, и при вызове бота на экране остались бы ДВЕ
   кнопки запуска: «Отправить бота» внутри приглашения и отключённая
   «Расшифровать запись» под ним. Ловушка та же, что у .preset-grid и .src-switch —
   своё display сильнее [hidden] из таблицы стилей браузера. */
.btn-upload[hidden] { display: none; }

/* Стили карточки результата (.result-card, .ios-check, pop-in) удалены вместе с
   самой карточкой: она дублировала блок хода работы худшей версией — её статус
   застывал на «Обработка начнётся сразу» навсегда. */

/* ── Status Page ──────────────────────────────────────────────────────────── */
.job-id-badge {
  display: inline-block;
  background: var(--bg);
  padding: 4px 10px;
  border-radius: var(--radius-sm);
  font-size: 13px;
  color: var(--label3);
  margin-bottom: 24px;
  font-family: var(--font-mono);
}
.status-rows { display: flex; flex-direction: column; gap: 0; }
.srow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 13px 0;
  border-bottom: 1px solid var(--separator);
  font-size: 15px;
}
.srow:last-child { border-bottom: none; }
.slabel { color: var(--label3); }
.sval { font-weight: 500; color: var(--label); text-align: right; max-width: 60%; }

/* ── Значки состояния записи ──────────────────────────────────────────────────
   🔴 СКРУГЛЁННЫЕ, А НЕ КВАДРАТНЫЕ (решение владельца 29.08.2026).

   Прежний комментарий здесь гласил: «острый край (0) намеренно, как канон-бейджи
   ui-kit». Это ошибка чтения канона. П1 «Круглое — предмет, прямоугольное —
   поверхность» ставит **значок-статус и счётчик** в колонку КРУГЛОЕ, вместе с
   главной кнопкой и аватаром; прямоугольны панели, карточки, поля, таблицы,
   вкладки. Логика: прямоугольное — то, НА ЧЁМ что-то лежит. Значок ничего не
   несёт, он сам объект — метка поверх строки. Квадратная рамка делала его
   похожим на ячейку таблицы, то есть на поверхность, которой он не является.

   Убрана и «громоздкость», которую давали четыре приёма разом: рамка, капс,
   разрядка и моноширинный шрифт. Каждый по отдельности мелочь, вместе метка
   весила как заголовок — «ГОТОВО» читалось тяжелее самого названия записи,
   хотя название главное, а статус второстепенен (эвристика №8: лишнее
   конкурирует с важным). Замер: «ГОТОВО» капсом с разрядкой занимало 62px
   против 48px у «готов» — 30% ширины строки, отобранные у названия, которое
   и так режется многоточием.

   Рамка осталась ТОЛЬКО у серого. У цветных заливка сама даёт границу, а
   --c-surface-2 почти сливается с карточкой: без рамки «отменено» и «истекло»
   стали бы плохо различимы. В прототипе серого варианта нет вовсе — там два
   состояния, — поэтому копировать его дословно было нельзя. */
.badge-green,
.badge-orange,
.badge-gray {
  padding: 3px 9px;
  border-radius: var(--radius-pill);
  border: 1px solid transparent;
  font-size: 11px; font-weight: 700;
  white-space: nowrap;
}
/* 🔴 ЗЕЛЁНЫЙ ЗАТЕМНЁН ДО НОРМЫ WCAG AA В СВЕТЛОЙ ТЕМЕ. Замер 29.08.2026:
   --c-success (#16A34A) на белой карточке даёт 3.3:1 при требовании 4.5:1 для
   мелкого текста. Дефект был и раньше, но прежний бейдж набирался 12px капсом с
   разрядкой — теперь текст 11px обычным начертанием, и запас меньше.

   Смешиваем с чёрным ЛОКАЛЬНО, а не правим --c-success: токен используется по
   всему продукту (полосы прогресса, галочки, заливки), и затемнение ради одного
   бейджа сдвинуло бы палитру везде.

   🔴 Затемнение стоит ПО УМОЛЧАНИЮ и отменяется у тёмных тем, а не наоборот.
   Сначала было наоборот — правило висело на [data-theme="light"], — и замер
   поймал дыру: у человека, который тему руками не выбирал, атрибута нет вовсе,
   светлые токены применяются, а селектор не срабатывает — зелёный оставался
   3.3:1. Ровно об этой ловушке предупреждает комментарий у --c-danger в
   tokens.css: тёмных веток три, и «светлая» это НЕ то же самое, что
   «[data-theme=light]».

   78% проверено замером на канвасе (color-mix отдаёт color(srgb …), и наивный
   разбор строки как rgb даёт мусор — первый замер показал 20.95 вместо 3.3):
   100% → 3.3 · 85% → 4.38 · 78% → 5.1 · 70% → 6.04. Взято 78%: норма AA с
   запасом, и цвет остаётся зелёным, а не превращается в чёрный. */
.badge-green {
  background: var(--c-success-bg);
  color: color-mix(in srgb, var(--c-success) 78%, #000);
}
:root[data-theme="dark"] .badge-green,
:root[data-theme="night"] .badge-green { color: var(--c-success); }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .badge-green { color: var(--c-success); }
}
.badge-orange { background: var(--c-warn-bg);    color: var(--c-warn); }
/* Единственный с рамкой: заливка серого не отделяет его от карточки. */
.badge-gray   { background: var(--c-surface-2);  color: var(--label3);
                border-color: var(--c-line); }

/* Строки .rrow/.rlabel/.rval удалены с карточкой результата — других мест
   применения у них не было. */

/* ── Список заданий в работе ──────────────────────────────────────────────────
   Компактные строки, а не копии большой шкалы: осциллограф и конвейер этапов живут
   в единственном экземпляре, и пять таких блоков на экране были бы шумом. Здесь
   ровно то, что нужно знать про НЕ фокусное задание: что это, на каком оно этапе
   и сколько уже идёт. */
.jobs-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 10px; }
/* 🔴 min-width: 0 — НЕ УКРАШЕНИЕ, А ЕДИНСТВЕННОЕ, ЧТО ДЕРЖИТ ШИРИНУ.
   У элемента сетки и флекса минимальный размер по умолчанию `auto`, то есть
   «не уже содержимого», и `overflow: hidden` с многоточием на названии его НЕ
   отменяет: строка сначала распирается под весь текст, и только потом внутри
   неё что-то обрезается.

   Название встречи приходит от человека и бывает любой длины. На телефоне
   строка «искусство Собирать Внимание. ВЕЖЕСТВЕННОСТЬ. ХОЖДЕНИЕ ПО МИРАМ»
   растянула карточку до 746px при экране 412: страница уехала горизонтально,
   контент сжался к левому краю, а блок «В работе» вылез за остальные карточки.
   Замерено на телефоне 23.08.2026.

   Ноль нужен на ВСЕХ трёх уровнях — сетке строки, флекс-шапке и самом
   названии: правило снимает запрет только с того элемента, где стоит, и
   любой пропущенный уровень возвращает распирание целиком. */
.job-row {
  border: 1px solid var(--c-line); padding: 10px 12px;
  display: grid; gap: 8px; background: var(--c-surface-2);
  min-width: 0;
}
/* Задание, которое сейчас ведёт большая шкала: строка остаётся (иначе список
   «прыгает» при смене фокуса), но приглушена — цифры для него уже показаны выше. */
.job-row.is-focused { opacity: .62; }
.job-row__top {
  display: flex; align-items: center; gap: 10px; justify-content: space-between;
  min-width: 0;                                        /* см. врезку у .job-row */
}
.job-row__name {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  /* Многоточие включается ТОЛЬКО с этой строкой: без неё элемент требует всю
     свою ширину, обрезать нечего, и течёт наружу вся строка. */
  min-width: 0;
}
/* Цвет задаём здесь: --g-line объявлена на корне .gauge, а строка списка живёт вне
   его — без этого заливка осталась бы бесцветной. */
.job-row__bar { margin: 0; --g-line: #58B368; --g-glow: transparent; }
/* Ссылка на отчёт — на самом названии записи. Подчёркивание только при наведении:
   в таблице из пятидесяти строк постоянное превращает список в частокол линий. */
.recent-card .recent__row { color: inherit; text-decoration: none; }
.job-row__foot {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  font-size: 13px; color: var(--label3);
}
.job-row__here { font-size: 12px; color: var(--label3); }
.job-row__cancel {
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--label3); font: inherit; text-decoration: underline;
  text-underline-offset: 3px;
}
.job-row__cancel:hover { color: var(--c-danger, #b3261e); }
/* Обрыв связи: список застыл и об этом надо сказать вслух — молчащий устаревший
   список выглядит актуальным, и человек грузит запись повторно. */
.job-row.is-offline {
  border-color: color-mix(in srgb, var(--c-warn) 45%, transparent);
  background: var(--c-warn-bg); color: var(--c-warn); font-size: 13px;
}

/* ── Последние записи (GS-148) ────────────────────────────────────────────────
   Стили .hist-card (свёрнутый <details> с таблицей внутри) удалены: блок стал
   развёрнутым списком строк. Таблица на 360px жалась, а ссылка висела только на
   названии — канон П8 требует кликабельной строки целиком. */
.recent-card { padding: 0; }
/* Заголовок секции: мелкая моноширинная надпись, как в прототипе. Тише
   содержимого — он подписывает список, а не соперничает с ним. Счётчик стоит
   СРАЗУ ЗА словами, а не отброшен к правому краю: «ПОСЛЕДНИЕ ЗАПИСИ · 29»
   читается как одна мысль, а разнесённые по краям слова и число — как два
   разных элемента. */
.recent-head {
  display: flex; align-items: baseline; gap: 8px;
  padding: 16px 20px 10px;
}
.recent-sect {
  margin: 0;
  font-family: var(--font-mono); font-size: 11px; font-weight: 700;
  letter-spacing: .1em; text-transform: uppercase; color: var(--label3);
}
.recent-list { list-style: none; margin: 0; padding: 0; }
.recent + .recent { border-top: 1px solid var(--separator); }

/* 🔴 КЛИКАБЕЛЬНА ВСЯ СТРОКА (канон П8), а не название внутри неё: на телефоне
   целиться в текст дороже, чем в полосу во всю ширину. min-height 60px — выше
   нормы 48px намеренно: строка двухэтажная (название + дата). */
.recent__row {
  display: flex; align-items: center; gap: 12px;
  min-height: 60px; padding: 10px 20px;
  -webkit-tap-highlight-color: transparent;
}
.recent__row:active { background: var(--c-surface-3, rgba(0,0,0,.04)); }
.recent__row:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
/* Строка без отчёта: открывать нечего, поэтому не ссылка — но в списке есть,
   человек пришёл за фактом загрузки. Курсор не обещает перехода. */
.recent__row.is-flat { cursor: default; }
.recent__ic { flex: none; display: grid; place-items: center; color: var(--label4); }
.recent__b { flex: 1 1 auto; min-width: 0; }
/* Длинное название режем многоточием: перенос ломал бы ровный ритм строк, а
   двухэтажная строка и так занимает 60px. */
.recent__t {
  display: block; font-size: 14px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.recent__s { display: block; margin-top: 2px; font-size: 12px; color: var(--label4); }
.recent__badge { flex: none; }
.recent-card .recent__row:hover .recent__t { text-decoration: underline; text-underline-offset: 3px; }

/* Кнопка полного списка. Отделена линией: это не ещё одна запись, а действие
   над списком. Во всю ширину — тот же довод Фиттса, что и у строк. */
.recent-all {
  display: flex; align-items: center; gap: 8px;
  width: 100%; min-height: 48px; padding: 12px 20px;
  appearance: none; background: none; border: 0;
  border-top: 1px solid var(--separator);
  cursor: pointer; color: var(--label2);
  font-family: var(--font-sans); font-size: 14px; font-weight: 600;
  -webkit-tap-highlight-color: transparent;
}
.recent-all[hidden] { display: none; }
.recent-all:hover { color: var(--accent); }
.recent-all:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.recent-all__n { font-family: var(--font-mono); font-size: 12px; color: var(--label4); }
.recent-all__ic { margin-left: auto; display: grid; place-items: center; color: var(--label4); }
.jobs-count { color: var(--label3); font-weight: 400; }

/* ── Empty State ──────────────────────────────────────────────────────────── */
.empty-state { text-align: center; padding: 40px 0; }
.empty-icon { font-size: 40px; margin-bottom: 12px; }
.empty-state p { color: var(--label3); font-size: 15px; }

/* ── Preset block ─────────────────────────────────────────────────────────── */
.preset-block { margin-top: 8px; }
.preset-block-label {
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--font-mono);
  font-size: 12px; font-weight: 700; color: var(--label2);
  text-transform: uppercase; letter-spacing: .08em; margin-bottom: 4px;
}
.preset-sub {
  font-size: 14px; color: var(--label3); margin-bottom: 8px;
}

/* ── Автовыбор проекта по записи (GS-96) ──────────────────────────────────────
   Строка режима над карточками: «Проект определим по записи» + «выбрать самому».
   Показывается только когда автовыбор включён у организации; у остальных витрина
   выглядит ровно как раньше. */
.preset-mode {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 8px; padding: 8px 12px;
  border: 1px dashed var(--c-gold-line);
  border-radius: var(--radius-md);
  background: var(--accent-bg);
}
.preset-mode[hidden] { display: none; }   /* иначе flex перебивает hidden */
.preset-mode-text {
  font-size: 13px; color: var(--label3);
}

/* 🔴 ПЕРЕКЛЮЧАТЕЛЬ НА ДВА ПОЛОЖЕНИЯ, А НЕ ССЫЛКА В ОДНУ СТОРОНУ.
   Прежде здесь была ссылка «выбрать самому», и обратной дороги на автомат не
   было вовсе. Сегментированный переключатель показывает оба режима сразу:
   текущий подсвечен, второй — в одном клике. Состояние читается с экрана, его
   не нужно помнить. */
.preset-mode-switch {
  display: inline-flex; padding: 3px; gap: 2px;
  border: 1px solid var(--separator); border-radius: var(--radius-md);
  background: var(--surface);
}
.preset-mode-opt {
  display: inline-flex; align-items: center; gap: 6px;
  /* 8px по вертикали + 13px строка: ниже опускаться нельзя — переключатель
     живёт на телефоне, где цель меньше ~40px промахивается пальцем. */
  padding: 8px 12px; border: 0; border-radius: calc(var(--radius-md) - 3px);
  background: none; color: var(--label2);
  font: inherit; font-size: 13px; font-weight: 600; line-height: 1;
  cursor: pointer; white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}
.preset-mode-opt:hover { color: var(--label); background: var(--accent-bg); }
.preset-mode-opt:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Выбранное положение: заливка + цвет, а не одна лишь заливка. Цвет как
   единственный признак не проходит по доступности — на монохромном экране и при
   дальтонизме режим стал бы неразличим. */
.preset-mode-opt[aria-pressed="true"] {
  background: var(--accent); color: var(--c-gold-contrast);
}
.preset-mode-opt[aria-pressed="true"]:hover { background: var(--accent); }

/* Правила .preset-grid.is-auto удалены 29.08.2026 (решение владельца).
   Прежде на автовыборе карточки ПРИГЛУШАЛИСЬ (opacity .55) и оставались в
   потоке: шесть штук занимали ~400px и уводили кнопку запуска за нижний край.
   Приглушение вдобавок читается как «недоступно», а карточки по :hover
   разгорались и оставались кликабельными — сигнал спорил с поведением.

   Теперь сетка скрывается целиком (presetGrid.hidden в renderPresetMode).
   Довод прежнего решения — «дать пройти табом и открыть настройку проекта» —
   закрыт иначе: в авторежиме выбирать нечего, а настройка проектов живёт в
   разделе организации, куда ведёт отдельная ссылка. Ради неё держать на экране
   шесть карточек, из которых в этом режиме не выбирают, — плата местом за то,
   чем в этом режиме не пользуются. */

.help-btn {
  width: 22px; height: 22px; border-radius: 50%;
  border: 1px solid var(--separator); background: var(--surface);
  color: var(--label3); font-size: 13px; font-weight: 700; line-height: 1;
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
  transition: all 0.15s ease;
  /* Serif для глифа «i» — как у .help-q в components.css: строчная «i» без
     засечек в кружке читается как «l». Гарнитура и сдвиг обязаны совпадать с
     той кнопкой, иначе два значка информации на соседних экранах выглядят
     по-разному. */
  font-family: Georgia, "Times New Roman", serif;
  padding-bottom: 1px;
}
.help-btn:hover { background: var(--accent-bg); color: var(--accent); border-color: var(--accent); }

.preset-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(172px, 1fr));
  gap: 8px; margin-bottom: 8px;
}
/* 🔴 БЕЗ ЭТОГО ПРАВИЛА presetGrid.hidden НЕ РАБОТАЕТ. Атрибут hidden — это
   всего лишь `display: none` из таблицы стилей браузера, и любое своё
   `display` его перебивает. У соседей (.link-zone, .drop-zone) такое правило
   есть, у сетки не было — и на автовыборе карточки оставались на экране,
   хотя JS честно ставил hidden (замер на стенде 29.08.2026). */
.preset-grid[hidden] { display: none !important; }
/* Три колонки, а не auto-fill: после фикса ширины (.main-content width:100%) карточка
   на 820-960px наконец занимает заявленное место, и auto-fill стал раскладывать
   пресеты по 4 в ряд шириной ~175px — «Синк / команды» рвался пополам, а подзаголовок
   Q-Check растягивался на 4 строки. Три колонки держат плитку не уже ~240px,
   заголовок помещается в строку. */
@media (min-width: 900px) {
  .preset-grid { grid-template-columns: repeat(3, 1fr); }
}
.preset-card {
  position: relative; display: flex; align-items: flex-start; gap: 9px;
  padding: 10px 12px; border: 1px solid var(--c-line-strong);
  border-radius: var(--radius-md); background: var(--surface);   /* карточка выбора — канон md (8px) */
  cursor: pointer;
  transition: transform 0.25s ease, border-color 0.25s ease, background 0.25s ease, box-shadow 0.25s ease;
}
/* брутальный hover — сдвиг + жёсткая золотая тень (мини-версия канона service-card) */
.preset-card:hover {
  border-color: var(--c-gold-line);
  background: var(--accent-bg);
  transform: translate(-2px,-2px);
  box-shadow: 4px 4px 0 rgba(201,163,50,.22), 0 0 18px rgba(153,128,24,.10);
}
:root[data-theme="dark"] .preset-card:hover,
:root[data-theme="night"] .preset-card:hover {
  box-shadow: 4px 4px 0 rgba(235,197,84,.28), 0 0 22px rgba(235,197,84,.14);
}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .preset-card:hover {
    box-shadow: 4px 4px 0 rgba(235,197,84,.28), 0 0 22px rgba(235,197,84,.14);
  }
}
.preset-card.is-active {
  border-color: var(--accent); background: var(--accent-bg);
  box-shadow: inset 0 0 0 1px var(--accent);
}
.preset-card input[type=radio] {
  position: absolute; opacity: 0; pointer-events: none;
}
.preset-icon { color: var(--label2); display: flex; align-items: center; }
.preset-icon svg { width: 22px; height: 22px; }
.preset-card.is-active .preset-icon { color: var(--accent); }
.preset-body { flex: 1; min-width: 0; }
.preset-name {
  display: flex; align-items: flex-start; gap: 5px;
  font-size: 14px; font-weight: 600; color: var(--label); margin-bottom: 2px;
}
.preset-name-text { flex: 0 1 auto; min-width: 0; line-height: 1.3; }
/* Название не залезает под кнопки. Полоса действий позиционирована absolute и
   лежит ПОВЕРХ карточки: длинное имя проезжало под неё, и «Мой разбор звонков»
   читалось как «Мой разбор звонко⧉». Резервируем место ПО ЧИСЛУ кнопок: у
   проекта организации она одна (правка), у личного две (правка и удаление).
   Единый отступ «под максимум» ломал строку там, где кнопка одна: «Синк
   команды» и «Интервью / найм» уезжали на две строки и растили всю сетку. */
.preset-card:has(.pc-act) .preset-name { padding-right: 30px; }
.preset-card:has(.pc-act + .pc-act) .preset-name { padding-right: 56px; }
.preset-short { font-size: 13.5px; color: var(--label3); line-height: 1.35; }
.preset-skeleton, .preset-error {
  grid-column: 1 / -1; text-align: center; color: var(--label3);
  padding: 16px; font-size: 13px;
}
.preset-error { color: var(--red); }

/* Кнопка предпросмотра состава пресета + поповер «Что внутри» */
.preset-info-btn {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none; margin-top: 1px;
  width: 18px; height: 18px; padding: 0;
  border: none; background: transparent; color: var(--label3);
  border-radius: 50%; cursor: pointer;
  transition: color 0.15s ease, background 0.15s ease;
}
.preset-info-btn svg { width: 14px; height: 14px; display: block; }
/* Зона нажатия шире видимого значка (GS-139). Замер 27.08.2026 на 390×844:
   кнопка 18×18 при норме холдинга 48 — по такой цели палец промахивается, а
   промах здесь стоит дорого: рядом сама карточка проекта, и вместо подсказки
   человек меняет выбор.

   Через ::after, а не увеличением кнопки: значок обязан остаться мелким —
   он подчинён названию проекта, крупный спорил бы с ним за внимание.
   Приём тот же, что у .help-q в components.css. */
.preset-info-btn { position: relative; }
.preset-info-btn::after {
  content: ""; position: absolute;
  top: 50%; left: 50%;
  width: 24px; height: 24px;
  transform: translate(-50%, -50%);
}
@media (pointer: coarse){
  .preset-info-btn::after{ width: 48px; height: 48px; }
}
.preset-info-btn:hover,
.preset-info-btn:focus-visible,
.preset-info-btn[aria-expanded="true"] {
  color: var(--accent); background: var(--accent-bg); outline: none;
}
.preset-card.is-active .preset-info-btn { color: var(--accent); }

.preset-popover {
  position: fixed; z-index: 1000; max-width: 300px; width: max-content;
  background: var(--surface); border: 1px solid var(--separator);
  border-radius: var(--radius-md); box-shadow: var(--elev-2);
  padding: 12px 14px; font-size: 13px; color: var(--label2); line-height: 1.5;
  animation: pp-in 0.12s ease;
}
/* Мобила: поповер пресета — по горизонтали центр экрана (координаты ставит JS),
   по вертикали у кнопки. Шире и крупнее для комфортного чтения на телефоне. */
.preset-popover.is-mobile {
  /* Горизонтальный центр — чисто на CSS (не зависит от JS-замера ширины, который
     мог считаться до применения этого класса и мазал). JS ставит только top. */
  left: 50% !important; transform: translateX(-50%);
  width: min(calc(100vw - 28px), 360px); max-width: none;
  padding: 16px 16px 14px; font-size: 14px;
}
@keyframes pp-in {
  from { opacity: 0; transform: translateY(-3px); }
  to   { opacity: 1; transform: none; }
}
.pp-head { display: flex; align-items: center; gap: 7px; margin-bottom: 6px; }
.pp-ic { display: inline-flex; color: var(--accent); }
.pp-ic svg { width: 16px; height: 16px; display: block; }
.pp-title { font-size: 14px; font-weight: 700; color: var(--label); }
.pp-hint { color: var(--label2); margin-bottom: 10px; }
.pp-label {
  font-size: 12px; font-weight: 600; text-transform: uppercase;
  letter-spacing: 0.4px; color: var(--label3); margin-bottom: 6px;
}
.pp-chips { display: flex; flex-wrap: wrap; gap: 5px; }
.pp-chip {
  display: inline-block; padding: 3px 9px; font-size: 13px; font-weight: 600;
  color: var(--accent); background: var(--accent-bg);
  border-radius: 999px; white-space: nowrap;
}
.pp-free { color: var(--label2); }

.preset-help, .custom-prompt-help {
  margin-top: 6px; font-size: 13px; color: var(--label3);
}
.preset-help summary, .custom-prompt-help summary {
  cursor: pointer; color: var(--blue); padding: 6px 0; font-size: 13px;
  list-style: none; display: inline-block;
}
.preset-help summary::-webkit-details-marker,
.custom-prompt-help summary::-webkit-details-marker { display: none; }
.preset-help summary::before, .custom-prompt-help summary::before {
  content: "▸ "; transition: transform 0.15s ease;
}
.preset-help[open] summary::before, .custom-prompt-help[open] summary::before { content: "▾ "; }

.preset-help-body { padding: 8px 0 4px; display: flex; flex-direction: column; gap: 12px; }
.preset-help-item {
  padding: 10px 12px; background: var(--bg); border-radius: var(--radius-sm);
}
.preset-help-title { font-size: 13px; font-weight: 600; color: var(--label); margin-bottom: 4px;
  display: flex; align-items: center; gap: 6px; }
.ph-ic { display: inline-flex; color: var(--label2); }
.preset-help-hint { font-size: 13.5px; color: var(--label2); line-height: 1.5; margin-bottom: 4px; }
.preset-help-sections {
  font-size: 13px; color: var(--label3); margin-top: 4px; font-family: monospace;
}

/* ── Custom prompt ────────────────────────────────────────────────────────── */
.custom-prompt-wrap {
  margin-top: 12px; padding: 14px; border: 1.5px dashed var(--separator);
  border-radius: var(--radius-sm); background: var(--bg);
}
.custom-prompt-header {
  display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px;
}
.custom-prompt-header label {
  font-family: var(--font-mono);
  font-size: 13px; font-weight: 700; color: var(--label2);
  text-transform: uppercase; letter-spacing: .08em;
  /* Подпись забирает свободное место, кнопки прижимаются к правому краю группой.
     Без этого space-between растащил бы три элемента по строке, и «Вставить
     заготовку» повисла бы в середине между подписью и значком «i». */
  margin-right: auto;
}
/* Кнопка вставки заготовки — рядом с полем, которое она заполняет.
   Вторична по весу: главное действие блока — написать своё, заготовка лишь
   отправная точка. Поэтому текстовая кнопка в акцентном цвете, а не заливка:
   иначе она перетягивала бы внимание с самого поля. */
.tpl-btn {
  padding: 4px 8px; border: 1px solid transparent; background: none;
  color: var(--accent); font-family: var(--font-sans);
  font-size: 13px; font-weight: 600; cursor: pointer;
  border-radius: var(--radius-sm); transition: all 0.15s ease;
}
.tpl-btn:hover { background: var(--accent-bg); border-color: var(--accent); }
.tpl-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
#customPrompt, #customContext {
  width: 100%; padding: 12px; border: 1px solid var(--separator);
  border-radius: var(--radius-sm); font-family: var(--font-sans);
  font-size: 14px; line-height: 1.5; resize: vertical; background: var(--surface);
  color: var(--label);
}
#customPrompt:focus, #customContext:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(153, 128, 24, 0.18); }
.custom-prompt-meta {
  font-size: 13px; color: var(--label3); text-align: right; margin-top: 4px;
}
.custom-prompt-meta.over-limit { color: var(--red); }
.custom-prompt-help-body { padding: 8px 0; font-size: 13px; color: var(--label2); }
/* .custom-prompt-template удалён 11.08.2026 вместе со своим блоком: справка
   показывала текст заготовки как <pre> для чтения, а под ним стояла кнопка
   вставки. Читать то, что всё равно вставляешь в поле, незачем — заготовка
   переехала в кнопку у поля (.tpl-btn) и видна сразу в самом поле. */
.custom-prompt-tips { padding-left: 18px; line-height: 1.6; }
.custom-prompt-tips li { margin-bottom: 4px; }

.btn-secondary {
  display: inline-block; padding: 8px 14px; border: 1px solid var(--separator);
  background: var(--surface); color: var(--label2); border-radius: var(--radius-sm);
  font-size: 13px; font-weight: 600; cursor: pointer; transition: all 0.15s ease;
}
.btn-secondary:hover { border-color: var(--accent); color: var(--accent); }
.btn-text {
  background: none; border: none; color: var(--label3); font-size: 13.5px;
  cursor: pointer; padding: 8px 6px; font-weight: 500;
}
.btn-text:hover { color: var(--red); }

/* ── Мои (сохранённые) промты ──────────────────────────────────────────────── */
.preset-card--user { border-style: dashed; }
.preset-card--user .preset-name { color: var(--accent); }
.preset-card--user .preset-icon { color: var(--accent); }
/* Проект, которым поделился коллега (GOL-357). Признак сдержанный — приглушённый
   значок и подпись с адресом автора: пользоваться им можно наравне с проектами
   организации, и кричащая рамка сделала бы его «чужим», а не «общим». Пунктир, как
   у своих личных, не годится: тот означает «мой черновик, я им управляю». */
.preset-card--from-colleague .preset-icon { color: var(--label3); }
.preset-card--from-colleague .preset-short { font-style: italic; }
/* Вход в каталог сценариев (GS-94). Не проект, а дверь к ним: пунктир по всему
   периметру и приглушённый цвет отделяют её от карточек, которые действительно
   что-то выбирают. Кнопка, а не label с radio, — потому выравнивание задаём явно:
   у кнопки свой text-align и font, наследовать их от .preset-card нечему. */
.preset-card--more {
  border-style: dashed; color: var(--label2); text-align: left;
  font: inherit; cursor: pointer;
}
.preset-card--more .preset-icon,
.preset-card--more .preset-name { color: var(--label2); }
.preset-card--more:hover .preset-icon,
.preset-card--more:hover .preset-name { color: var(--accent); }

/* ── Окно каталога ───────────────────────────────────────────────────────── */
.cat-intro { color: var(--label2); font-size: 14px; margin-bottom: 16px; }
.cat-group + .cat-group { margin-top: 18px; }
.cat-group-title {
  font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--label3); margin-bottom: 8px;
}
/* Сетка, а не столбец: сфер шесть, и в столбец каталог уезжает под сгиб — по
   восемнадцати сценариям пришлось бы прокручивать окно целиком. */
.cat-group-items {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
}
.cat-item {
  display: flex; gap: 10px; align-items: flex-start; text-align: left;
  padding: 10px 12px; border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); color: inherit; font: inherit; cursor: pointer;
  transition: border-color 0.15s ease, background 0.15s ease;
}
.cat-item:hover { border-color: var(--accent); background: var(--card-hover, var(--card)); }
.cat-item-ic { flex: none; margin-top: 1px; color: var(--label3); }
.cat-item:hover .cat-item-ic { color: var(--accent); }
.cat-item-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.cat-item-name { font-weight: 500; }
.cat-item-hint {
  font-size: 12.5px; color: var(--label3); line-height: 1.35;
  /* Подсказка — приманка, а не документация: три строки дают понять, о чём
     сценарий, и не превращают плитку в простыню. Полный текст человек увидит на
     карточке проекта после выбора. */
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical;
  line-clamp: 3; overflow: hidden;
}
.pc-act svg { display: block; }
.preset-card-actions {
  position: absolute; top: 6px; right: 6px; display: flex; gap: 2px;
  opacity: 0; transition: opacity 0.15s ease;
}
/* Действия проявляются на карточке ПОД КУРСОРОМ, а не на выбранной
   (11.08.2026): правка появилась и у проектов организации, а правило висело на
   .preset-card--user — кнопка была в разметке, но оставалась прозрачной, то есть
   её просто не существовало для человека. Поймано визуальной проверкой: в DOM
   ссылка есть, на экране пусто.

   is-active в условие НЕ входит: выбранная карточка живёт на экране постоянно, и
   её кнопки висели бы всегда — на скриншоте это читалось как «у всех плиток
   сразу торчат значки». Наведение и фокус — состояния мгновенные, они и
   показывают действия.

   :focus-within обязателен: с клавиатуры карточку не наводят мышью, и без него
   кнопка получала фокус, оставаясь невидимой. */
.preset-card:hover .preset-card-actions,
.preset-card:focus-within .preset-card-actions { opacity: 1; }
.pc-act {
  border: none; background: var(--surface); color: var(--label3);
  border-radius: var(--radius-sm); width: 24px; height: 24px; line-height: 1;
  font-size: 13px; cursor: pointer; padding: 0;
}
.pc-act:hover { background: var(--accent-bg); color: var(--accent); }
/* Зона нажатия под палец (GS-139). Замер 27.08.2026: 24×24 при норме 48.
   Через ::after, а не размером кнопки: она стоит в углу карточки проекта, и
   рост до 48px раздвинул бы саму карточку. Вид прежний, попадание — по норме. */
.pc-act { position: relative; }
@media (pointer: coarse){
  .pc-act::after{
    content: ""; position: absolute;
    top: 50%; left: 50%; width: 48px; height: 48px;
    transform: translate(-50%, -50%);
  }
}
/* Правка проекта организации — ссылка на его страницу настройки, а не кнопка:
   она ведёт на другой экран. Выглядит как соседняя кнопка, поэтому выравниваем
   содержимое так же (у <a> нет центрирования по умолчанию). */
.pc-act--link { display: inline-flex; align-items: center; justify-content: center; }

/* Тач: наведения не существует, и «проявляющиеся» кнопки на телефоне были бы
   недостижимы вовсе — правка и удаление проекта просто отсутствовали бы. Держим
   их видимыми всегда. Правило по способу ввода, а не по ширине экрана: узкое
   окно на десктопе — это по-прежнему мышь. */
@media (hover: none) {
  .preset-card-actions { opacity: 1; }
}

.save-prompt-box {
  margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--separator);
}
.save-prompt-row {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
}
.save-prompt-name {
  flex: 1 1 200px; min-width: 0; padding: 9px 12px;
  border: 1px solid var(--separator); border-radius: var(--radius-sm);
  font-size: 13px; background: var(--surface); color: var(--label);
  font-family: inherit;
}
.save-prompt-name:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(153, 128, 24, 0.18); }
.save-prompt-type {
  padding: 9px 10px; border: 1px solid var(--separator); border-radius: var(--radius-sm);
  font-size: 13px; background: var(--surface); color: var(--label2); font-family: inherit; cursor: pointer;
}
.save-prompt-type:focus { outline: none; border-color: var(--accent); }
/* Галочка «доступен всей организации» (GOL-357). Отдельной строкой под полями, а
   не в ряду с ними: это решение о ВИДИМОСТИ, а не ещё одно поле ввода, и в общем
   ряду его пролистывали бы вместе с названием и типом. */
.save-prompt-visib {
  display: flex; align-items: center; gap: 8px;
  margin-top: 10px; font-size: 13px; color: var(--label2);
  cursor: pointer; user-select: none;
}
.save-prompt-visib input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; flex: none; }
/* .save-prompt-hint удалён (GOL-358): абзац про сохранение и галочку доступа
   переехал под «i» внутрь этой же подписи, носителя у класса не осталось. */

/* Правка сохранённого промта — кнопка становится основным действием: рядом стоит
   «Отмена правки», и в одинаково-серой паре было непонятно, какое действие
   главное и чего от тебя ждут. Цвет появляется только в режиме правки, чтобы в
   обычном состоянии форма не перетягивала внимание с «Загрузить». */
.save-prompt-row .btn-secondary.is-editing {
  border-color: var(--accent);
  background: var(--accent-bg);
  color: var(--accent);
}
.save-prompt-row .btn-secondary.is-editing:hover {
  background: color-mix(in srgb, var(--accent) 18%, transparent);
}

/* Контекст — второе поле промта. Заголовок мельче основного: это дополнение к
   инструкции, а не равный ей раздел. */
.custom-context-header { margin-top: 14px; }
.custom-context-opt { color: var(--label3); font-weight: 400; font-size: 12.5px; }

@media (max-width: 520px) {
  .save-prompt-name, .save-prompt-type, .save-prompt-row .btn-secondary { flex: 1 1 100%; }
}

/* ── Телефон: доводка форм ────────────────────────────────────────────────────
   Блок стоит в самом низу файла намеренно: базовые правила форм объявлены ниже
   мобильного блока экрана входа, и при равной силе селектора они перебивали бы
   мобильные значения. Здесь каскад завершается — правила применяются. */
@media (max-width: 560px) {
  /* Межбуквенный интервал у капс-меток снят: с ним «Название встречи
     (необязательно) ?» не влезало в 296px и знак вопроса уезжал на свою строку. */
  .input-group > label { letter-spacing: normal; }
}

/* Узкий экран: капс-CTA ужимается, чтобы влезать в 358-390 без переноса/обрезки.
   Размер держим на 13 (канон-минимум для читаемых капсов), сжимаем интервал. */
@media (max-width: 430px) {
  .btn-primary { font-size: 13px; letter-spacing: .04em; padding: 14px 8px; }
}

/* ── Телефон: рамки не съедают ширину ──────────────────────────────────────────
   🔴 Замер 16.08.2026: на 390px отступы забирали 140px — 36% ширины экрана, на
   360px — 39%. Полю ссылки оставалось 218 и 188px, и плейсхолдер обрывался на
   середине («https://… ссылка на»).

   Съедали ТРИ вложенные рамки подряд: оболочка 20+20, карточка 26+26 и зона
   ссылки 22+22. На десктопе это воздух, на телефоне — потерянная строка текста.
   Дублирование при этом бессмысленное: карточка уже отделена от фона тенью и
   рамкой, повторять её отступ внутри незачем.

   Снимаем ПОСЛЕДОВАТЕЛЬНО, не убирая рамки совсем: карточка остаётся карточкой
   (граница + тень), пунктирная зона по-прежнему читается как «сюда бросают
   файл». Итог 140 → 56px, то есть ~84px возвращены содержимому. */
@media (max-width: 560px) {
  .main-content { padding-inline: 10px; }
  .card { padding: 18px 14px 20px; }

  /* 🔴 ЗАГОЛОВОК «ЗАГРУЗИТЬ ЗАПИСЬ» СКРЫТ ОТ ГЛАЗ (замечание владельца
     30.08.2026: «огромная надпись, съедает много пикселей — на мобилке
     предлагаю убрать»).

     Замер на 412×915: заголовок с отступами занимал 76px — половину зоны файла,
     и это ДО первого органа управления. При этом он ничего не добавляет:
     сразу под ним «Перетащите файл сюда», то есть экран называет себя дважды.

     🔴 Скрыт ВИЗУАЛЬНО, а не удалён: экранный диктор объявляет заголовок
     страницы, и без него человек с диктором теряет точку входа в содержимое.
     Приём — clip-path вместо display:none, иначе текст выпадает из дерева
     доступности целиком.

     Правило заведено ЗДЕСЬ, а не через .sr-only: тот живёт в admin.css, а он на
     главной не подключён (о чём прямо предупреждает комментарий в modal.css).
     Знак «i» с форматами уезжает вместе с заголовком — он часть той же строки;
     то же объяснение доступно из подсказки под полем ссылки. */
  .upload-card > .card-head {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    clip-path: inset(50%);
    overflow: hidden; white-space: nowrap;
  }
  /* Список записей отступов карточки не берёт: у него свои, у каждой строки.
     Сброс стоит ЗДЕСЬ, а не рядом с .recent-card выше: правило .card в этом
     медиазапросе объявлено позже и при равном весе перебивало бы его — линии
     между строками не доходили бы до краёв карточки на 15px с каждой стороны
     (замер 390px, 29.08.2026). Обошлось без !important. */
  .recent-card { padding: 0; }
  .link-zone { padding: 14px 12px; }
  /* Зона файла — та же рамка и тот же отступ: высоты обеих зон обязаны
     совпадать, иначе кнопка под ними прыгает при переключении источника. */
  .drop-zone { padding-inline: 12px; }
}

/* ── Приглашение бота на встречу (этап 4 плана ВКС) ─────────────────────────
   Появляется ТОЛЬКО когда в зону загрузки вставили ссылку на поддерживаемую
   площадку. Пустое поле «ссылка на встречу» рядом с зоной — это лишний вопрос
   всем ради сценария меньшинства; здесь блок возникает ровно тогда, когда он
   нужен, и исчезает по крестику. */
.bot-invite {
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-md);
  padding: 16px 18px;
  margin-bottom: 18px;
  background: var(--surface-2);
}
.bot-invite__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 6px;
}
.bot-invite__platform {
  font-size: 12px;
  font-weight: 600;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--accent);
}
.bot-invite__clear {
  background: none;
  border: 0;
  padding: 4px;
  cursor: pointer;
  color: var(--muted);
  display: flex;
  border-radius: var(--radius-sm);
}
.bot-invite__clear:hover { color: var(--ink); background: var(--line); }
/* Ссылка бывает длинной: переносим по любому знаку, иначе она распирает блок. */
.bot-invite__url {
  font-size: 13px;
  color: var(--muted);
  word-break: break-all;
  margin-bottom: 14px;
}
.bot-invite__note {
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
  margin: 10px 0 0;
}
.bot-invite__state {
  margin-top: 12px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  font-size: 13px;
  color: var(--ink);
}
/* Отказ обязан быть ВИДИМЫМ: тишина здесь хуже отказа — клиент, думающий, что
   встреча пишется, узнает правду слишком поздно. */
.bot-invite__state.is-failed { color: var(--red); }
/* «Смотреть на странице встреч» — ссылка, а не кнопка: это переход, а не
   действие. Стоит последней в блоке, после кнопки отправки: сначала отправить
   бота, потом идти наблюдать. */
.bot-invite__more {
  display: inline-block;
  margin-top: 10px;
  font-size: 12.5px;
  font-weight: 600;
  color: var(--c-gold-strong);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color .14s;
}
.bot-invite__more:hover { border-bottom-color: var(--gold-line); }
/* Парное к display:inline-block — иначе ссылка останется видимой до отправки
   бота и снова будет уводить со страницы (см. .preset-grid, .src-switch). */
.bot-invite__more[hidden] { display: none; }

/* ═══ ЭКРАНЫ ВХОДА ПОД ПАЛЬЦЕМ (GS-144) ════════════════════════════════════
   Регистрация, вход, оплата, приглашение — первое, что видит человек. По
   правилу пика и конца впечатление складывается из пикового момента и
   КОНЦОВКИ, а не из суммы: экран «проверьте почту» запоминается сильнее
   середины пути.

   Замер 27.08.2026 (390×844, эмуляция касания): поля 46px при норме 48,
   переключатель языка 24px, ссылка «Войти» 20px по высоте.

   Только под pointer: coarse — под мышью прежние размеры компактнее и точнее. */
@media (pointer: coarse){
  /* Поля формы: 48px — норма холдинга. Кегль здесь уже 16px (иначе Safari
     масштабировал бы вьюпорт), недоставало высоты. */
  .input-group input,
  .input-group select{ min-block-size: 48px; }

  /* Переключатель языка внизу страницы: 24px — цель, по которой промахиваются
     почти гарантированно, а стоит она у самого края экрана. Растим высоту
     строки, не трогая кегль: подпись «Русский / English» намеренно мелкая,
     это служебный ряд, а не орган управления первого плана. */
  .auth-lang a{
    display: inline-flex; align-items: center;
    min-block-size: 48px; padding-inline: 12px;
  }

  /* Ссылка «Уже зарегистрированы? Войти» — единственный выход со страницы для
     того, у кого учётная запись уже есть. 20px по высоте для такой роли мало. */
  .trial-foot a,
  .auth-foot a{
    display: inline-flex; align-items: center; min-block-size: 48px;
  }

  /* «Попробовать бесплатно» на странице входа — путь новичка, пришедшего не по
     той ссылке. Была 40px по высоте: для единственной альтернативы на экране
     это мало. */
  .auth-alt__link{ display: inline-flex; align-items: center; min-block-size: 48px; }
}
